package io.vertx.lang.js.generator;
import io.vertx.codegen.*;
import io.vertx.codegen.type.*;
import io.vertx.codegen.writer.CodeWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Map;
import static io.vertx.codegen.type.ClassKind.*;
public class JSClassGenerator extends AbstractJSClassGenerator<ClassModel> {
JSClassGenerator() {
this.name = "JavaScript";
this.kinds = Collections.singleton("class");
}
@Override
public String filename(ClassModel model) {
ClassTypeInfo type = model.getType();
return "resources/" + type.getModuleName() + "-js/" + Helper.convertCamelCaseToUnderscores(type.getRaw().getSimpleName()) + ".js";
}
@Override
public String render(ClassModel model, int index, int size, Map<String, Object> session) {
ClassTypeInfo type = model.getType();
String simpleName = type.getSimpleName();
String ifaceName = Helper.decapitaliseFirstLetter(simpleName);
StringWriter sw = new StringWriter();
CodeWriter writer = new CodeWriter(sw);
genLicenses(writer);
writer.println();
writer.format("/** @module %s */\n", getModuleName(type));
genRequire(model, writer);
writer.println();
genDoc(model, writer);
writer.format("var %s = function(j_val", simpleName);
for (TypeParamInfo.Class param : model.getTypeParams()) {
writer.format(", j_arg_%s", param.getIndex());
}
writer.println(") {");
writer.println();
writer.indent();
writer.format("var j_%s = j_val;\n", ifaceName);
writer.println("var that = this;");
writer.unindent();
for (TypeParamInfo.Class param : model.getTypeParams()) {
writer.format(" var j_%s = typeof j_arg_%s !== 'undefined' ? j_arg_%s : utils.unknown_jtype;", param.getName(), param.getIndex(), param.getIndex());
}
writer.indent();
for (TypeInfo superType : model.getSuperTypes()) {
writer.print(superType.getRaw().getSimpleName());
writer.print(".call(this, j_val");
if (superType instanceof ParameterizedTypeInfo && ((ApiTypeInfo) superType.getRaw()).isConcrete()) {
for (TypeInfo arg : ((ParameterizedTypeInfo) superType).getArgs()) {
if (arg.getKind() == API) {
writer.format(", %s._jtype", arg.getSimpleName());
} else if (arg.isVariable()) {
writer.format(", j_%s", arg.getName());
} else {
writer.print(", undefined");
}
}
}
writer.append(");\n");
}
writer.println();
model.getMethods().forEach(method -> {
writer.format("var __super_%s = this.%s;\n", method.getName(), method.getName());
});
model.getMethods()
.stream()
.filter(method -> !method.isStaticMethod())
.map(MethodInfo::getName).distinct().forEach(methodName -> {
genMethod(model, methodName, false, null, writer);
});
writer
.append("// A reference to the underlying Java delegate\n")
.append("// NOTE! This is an internal API and must not be used in user code.\n")
.append("// If you rely on this property your code is likely to break if we change it / remove it without warning.\n")
.format("this._jdel = j_%s;\n", ifaceName);
writer
.unindent()
.append("};\n");
writer.println();
writer.format("%s._jclass = utils.getJavaClass(\"%s\");\n", simpleName, type.getRaw().getName());
writer
.format("%s._jtype = {", simpleName)
.indent()
.append("accept: function(obj) {\n")
.indent()
.format("return %s._jclass.isInstance(obj._jdel);\n", simpleName)
.unindent()
.append("},")
.append("wrap: function(jdel) {\n")
.indent()
.format("var obj = Object.create(%s.prototype, {});\n", simpleName)
.format("%s.apply(obj, arguments);\n", simpleName)
.append("return obj;\n")
.unindent()
.append("},\n").append("unwrap: function(obj) {\n")
.indent()
.append("return obj._jdel;\n")
.unindent()
.append("}\n")
.unindent()
.append("};\n");
writer
.format("%s._create = function(jdel) {", simpleName)
.indent()
.format("var obj = Object.create(%s.prototype, {});\n", simpleName)
.format("%s.apply(obj, arguments);\n", simpleName)
.append("return obj;\n")
.unindent()
.append("}\n");
model.getMethods()
.stream()
.filter(MethodInfo::isStaticMethod)
.map(MethodInfo::getName).distinct().forEach(methodName -> {
genMethod(model, methodName, true, null, writer);
});
for (ConstantInfo constant : model.getConstants()) {
genConstant(model, constant, writer);
}
writer.format("module.exports = %s;", simpleName);
return sw.toString();
}
@Override
protected void genMethodAdapter(ClassModel model, MethodInfo method, CodeWriter writer) {
if (method.getReturnType().getKind() != VOID) {
if (method.isFluent()) {
writer.format("%s ;\n", genMethodCall(model, method));
writer.format("return %s;\n", method.isStaticMethod() ? model.getType().getSimpleName() : "that");
} else if (method.isCacheReturn()) {
writer
.format("if (that.cached%s == null) {\n", method.getName())
.indent()
.format("that.cached%s = %s;\n", method.getName(), convReturn(model, method, method.getReturnType(), genMethodCall(model, method)))
.unindent()
.append("}\n")
.format("return that.cached%s;\n", method.getName());
} else {
writer.format("return %s ;\n", convReturn(model, method, method.getReturnType(), genMethodCall(model, method)));
}
} else {
writer.format("%s;\n", genMethodCall(model, method));
}
}
private String genMethodCall(ClassModel model, MethodInfo method) {
StringWriter sw = new StringWriter();
PrintWriter writer = new PrintWriter(sw);
String simpleName = model.getType().getSimpleName();
String ifaceName = Helper.decapitaliseFirstLetter(simpleName);
if (method.isStaticMethod()) {
writer.format("J%s", simpleName);
} else {
writer.format("j_%s", ifaceName);
}
writer.format("[\"%s(", method.getName());
boolean first = true;
for (ParamInfo param : method.getParams()) {
if (first) {
first = false;
} else {
writer.print(",");
}
if (param.getType().isParameterized()) {
writer.print(param.getType().getRaw().getName());
} else if (param.getType().isVariable()) {
writer.print("java.lang.Object");
} else {
writer.print(param.getType().getName());
}
}
writer.print(")\"](");
int pcnt = 0;
first = true;
for (ParamInfo param : method.getParams()) {
if (first) {
first = false;
} else {
writer.print(", ");
}
boolean overloaded = model.getMethods().stream().map(MethodInfo::getName).count() > 1;
writer.print(convParam(model, method, "__args[" + (pcnt++) + "]", overloaded, param));
}
writer.print(")");
return sw.toString();
}
@Override
protected String convReturn(ClassModel model, MethodInfo method, TypeInfo returnType, String templ) {
ClassKind kind = returnType.getKind();
if (kind == LIST || kind == SET) {
TypeInfo elementType = ((ParameterizedTypeInfo) returnType).getArg(0);
ClassKind elementKind = elementType.getKind();
if (elementKind.json) {
return String.format("utils.convReturnListSetJson(%s)", templ);
} else if (elementKind == OBJECT) {
return String.format("utils.convReturnListSetObject(%s)", templ);
} else if (elementKind == DATA_OBJECT) {
return String.format("utils.convReturnListSetDataObject(%s)", templ);
} else if (elementKind == ENUM) {
return String.format("utils.convReturnListSetEnum(%s)", templ);
} else if (elementKind == API) {
return String.format("utils.convReturnListSetVertxGen(%s, %s)", templ, elementType.getRaw().getSimpleName());
} else if ("java.lang.Long".equals(elementType.getName())) {
return String.format("utils.convReturnListSetLong(%s)", templ);
} else {
if (kind == LIST) {
return templ;
} else {
return String.format("utils.convReturnSet(%s)", templ);
}
}
} else if (kind == MAP) {
return String.format("utils.convReturnMap(%s)", templ);
} else if (kind.json) {
return String.format("utils.convReturnJson(%s)", templ);
} else if (kind.basic) {
if ("java.lang.Long".equals(returnType.getName())) {
return String.format("utils.convReturnLong(%s)", templ);
} else {
return templ;
}
} else if (kind == API) {
StringWriter buffer = new StringWriter();
PrintWriter writer = new PrintWriter(buffer);
writer.format("utils.convReturnVertxGen(%s, %s", returnType.getRaw().getSimpleName(), templ);
if (returnType.isParameterized()) {
for (TypeInfo arg : ((ParameterizedTypeInfo) returnType).getArgs()) {
ClassKind argKind = arg.getKind();
if (argKind == API) {
writer.format(", %s._jtype", arg.getRaw().getSimpleName());
} else if (argKind == ENUM) {
writer.format(", utils.enum_jtype(%s)", arg.getName());
} else if (argKind == OBJECT) {
ParamInfo classTypeParam = method != null ? method.resolveClassTypeParam((TypeVariableInfo) arg) : null;
if (classTypeParam != null) {
writer.format(", utils.get_jtype(__args[%s])", classTypeParam.getIndex());
} else {
writer.print(", undefined");
}
} else {
writer.print(", undefined");
}
}
}
writer.print(")");
return buffer.toString();
} else if (kind == ENUM) {
return String.format("utils.convReturnEnum(%s)", templ);
} else if (kind == DATA_OBJECT) {
return String.format("utils.convReturnDataObject(%s)", templ);
} else if (kind == THROWABLE) {
return String.format("utils.convReturnThrowable(%s)", templ);
} else if (kind == HANDLER) {
ParameterizedTypeInfo type = (ParameterizedTypeInfo) returnType;
if (type.getArg(0).getKind() == ASYNC_RESULT) {
return String.format("utils.convReturnHandlerAsyncResult(%s, function(result) { return %s; })",
templ,
convParam(model, method, null, false, new ParamInfo(0, "result", null, (((ParameterizedTypeInfo) (type).getArg(0))).getArg(0))));
} else {
return String.format("utils.convReturnHandler(%s, function(result) { return %s; })",
templ,
convParam(model, method, null, false, new ParamInfo(0, "result", null, type.getArg(0))));
}
} else if (returnType.isVariable() && (method != null && method.resolveClassTypeParam((TypeVariableInfo) returnType) != null)) {
ParamInfo classTypeParam = method.resolveClassTypeParam((TypeVariableInfo) returnType);
return String.format("utils.get_jtype(__args[%s]).wrap(%s)", classTypeParam.getIndex(), templ);
} else {
String wrapper = "utils.convReturnTypeUnknown";
ClassTypeInfo type = model.getType();
for (TypeParamInfo.Class param : type.getParams()) {
if (param.getName().equals(returnType.getName())) {
wrapper = "j_" + param.getName() + ".wrap";
}
}
return String.format("%s(%s)", wrapper, templ);
}
}
private void genRequire(ClassModel model, PrintWriter writer) {
ClassTypeInfo type = model.getType();
writer.println("var utils = require('vertx-js/util/utils');");
for (ClassTypeInfo referencedType : model.getReferencedTypes()) {
writer.format("var %s = require('%s');\n", referencedType.getSimpleName(), getModuleName(referencedType));
}
writer.println();
writer.println("var io = Packages.io;");
writer.println("var JsonObject = io.vertx.core.json.JsonObject;");
writer.format("var J%s = Java.type('%s');\n", type.getSimpleName(), type.getName());
for (ClassTypeInfo dataObjectType : model.getReferencedDataObjectTypes()) {
writer.format("var %s = Java.type('%s');\n", dataObjectType.getSimpleName(), dataObjectType.getName());
}
}
}