All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.lang.js.generator.AbstractJSClassGenerator Maven / Gradle / Ivy

There is a newer version: 4.0.0-milestone4
Show newest version
package io.vertx.lang.js.generator;

import io.vertx.codegen.*;
import io.vertx.codegen.annotations.ModuleGen;
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.codegen.doc.Tag;
import io.vertx.codegen.doc.Token;
import io.vertx.codegen.type.*;
import io.vertx.codegen.writer.CodeWriter;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static io.vertx.codegen.type.ClassKind.*;
import static io.vertx.codegen.type.ClassKind.FUNCTION;
import static javax.lang.model.element.ElementKind.CLASS;
import static javax.lang.model.element.ElementKind.INTERFACE;
import static javax.lang.model.element.ElementKind.METHOD;

@SuppressWarnings("WeakerAccess")
public abstract class AbstractJSClassGenerator extends Generator {

  @Override
  public Collection> annotations() {
    return Arrays.asList(VertxGen.class, ModuleGen.class);
  }

  /**
   * Render a tag link to an html link, this function is used as parameter of the
   * renderDocToHtml function when it needs to render tag links.
   */
  protected String renderLinkToHtml(Tag.Link link) {
    ClassTypeInfo rawType = link.getTargetType().getRaw();
    if (rawType.getModule() != null) {
      String label = link.getLabel().trim();
      if (rawType.getKind() == DATA_OBJECT) {
        if (label.length() == 0) {
          label = rawType.getSimpleName();
        }
        return "" + label + "";
      } else if (rawType.getKind() == ENUM && ((EnumTypeInfo) rawType).isGen()) {
        if (label.length() == 0) {
          label = rawType.getSimpleName();
        }
        return "" + label + "";
      } else {
        if (label.length() > 0) {
          label = "[" + label + "] ";
        }
        Element elt = link.getTargetElement();
        String jsType = rawType.getSimpleName();
        ElementKind kind = elt.getKind();
        if (kind == CLASS || kind == INTERFACE) {
          return label + "{@link " + jsType + "}";
        } else if (kind == METHOD) {
          return label + "{@link " + jsType + "#" + elt.getSimpleName().toString() + "}";
        } else {
          System.out.println("Unhandled kind " + kind);
        }
      }
    }
    return null;
  }

  /**
   * Generate the module name of a type
   */
  protected String getModuleName(ClassTypeInfo type) {
    return type.getModuleName() + "-js/" + Case.CAMEL.to(Case.SNAKE, type.getSimpleName());
  }

  /**
   * Generate the JSDoc type of a type
   */
  protected String getJSDocType(TypeInfo type) {
    switch (type.getKind()) {
      case STRING:
        return "string";
      case PRIMITIVE:
      case BOXED_PRIMITIVE:
        switch (type.getSimpleName()) {
          case "boolean":
          case "Boolean":
            return "boolean";
          case "char":
          case "Character":
            return "string";
          default:
            return "number";
        }
      case JSON_OBJECT:
      case DATA_OBJECT:
      case ENUM:
      case OBJECT:
        return "Object";
      case JSON_ARRAY:
        return "Array";
      case API:
        return type.getRaw().getSimpleName();
      case MAP:
        //`Map` before `collection`, because of MAP.collection is true
        return "Object.";
      case HANDLER:
      case FUNCTION:
        return "function";
      case SET:
      case LIST:
        return "Array.<" + getJSDocType(((ParameterizedTypeInfo) type).getArg(0)) + ">";
      default:
        return "todo";
    }
  }

  protected String convParam(M model, MethodInfo method, String argName, boolean overloaded, ParamInfo param) {
    StringWriter buffer = new StringWriter();
    CodeWriter writer = new CodeWriter(buffer);
    String paramName = overloaded ? argName : param.getName();
    ClassKind paramKind = param.getType().getKind();
    boolean funct = paramKind == FUNCTION;
    if (paramKind == HANDLER || funct) {
      ParameterizedTypeInfo type = (ParameterizedTypeInfo) param.getType();
      if (type.getArg(0).getKind() == ASYNC_RESULT) {
        ParameterizedTypeInfo asyncType = (ParameterizedTypeInfo) type.getArg(0);
        if (param.isNullable()) {
          writer.format("%s == null ? null : ", paramName);
        }
        writer.println("function(ar) {");
        writer.indent();
        if (funct) {
          writer.println("var jRet;");
        }
        writer.println("if (ar.succeeded()) {");
        writer.indent();
        if (funct) {
          writer.print("jRet = ");
        }
        writer.print(paramName);
        writer.print("(");
        if ("java.lang.Void".equals(asyncType.getArg(0).getName())) {
          writer.print("null");
        } else {
          writer.print(convReturn(model, method, asyncType.getArg(0), arVal()));
        }
        writer.println(", null);");
        writer.unindent();
        writer.println("} else {");
        writer.indent();
        if (funct) {
          writer.print("jRet = ");
        }
        writer.print(paramName);
        writer.println("(null, ar.cause());");
        writer.unindent();
        writer.println("}");
        if (funct) {
          writer.println("return jRet;");
        }
        writer.unindent();
        writer.print("}");
      } else if ("java.lang.Void".equals(type.getArg(0).getName())) {
        writer.print(paramName);
      } else {
        if (param.isNullable()) {
          writer.print(paramName);
          writer.print(" == null ? null : ");
        }
        writer.println("function(jVal) {");
        writer.indent();
        if (funct) {
          writer.print("var jRet = ");
        }
        writer.print(paramName);
        writer.format("(%s);\n", convReturn(model, method, type.getArg(0), basicVal()));
        if (funct) {
          writer.format("return %s;\n", unwrapToJava(method, param, type.getArg(1), "jRet"));
        }
        writer.unindent();
        writer.print("}");
      }
    } else {
      writer.print(unwrapToJava(method, param, param.getType(), paramName));
    }
    return buffer.toString();
  }

  protected abstract String convReturn(M model, MethodInfo method, TypeInfo returnType, String templ);


  protected void genDoc(M model, CodeWriter writer) {
    writer.println("/**");
    if (model.getIfaceComment() != null) {
      writer.println(Helper.removeTags(model.getIfaceComment()));
    }
    writer.println(" @class");
    writer.println("*/");
  }
  protected String unwrapToJava(MethodInfo method, ParamInfo param, TypeInfo unwrappedType, String unwrappedName) {
    StringWriter buffer = new StringWriter();
    PrintWriter writer = new PrintWriter(buffer);
    ClassKind kind = unwrappedType.getKind();
    switch (kind) {
      case JSON_OBJECT:
        writer.format("utils.convParamJsonObject(%s)", unwrappedName);
        break;
      case JSON_ARRAY:
        writer.format("utils.convParamJsonArray(%s)", unwrappedName);
        break;
      case DATA_OBJECT:
        writer.format("%s  != null ? new %s(new JsonObject(Java.asJSONCompatible(%s))) : null", unwrappedName, unwrappedType.getSimpleName(), unwrappedName);
        break;
      case ENUM:
        if (param.isNullable()) {
          writer.format("%s == null ? null : ", unwrappedName);
        }
        writer.format("%s.valueOf(%s)", unwrappedType.getName(), unwrappedName);
        break;
      case OBJECT:
        if (unwrappedType.isVariable()) {
          TypeVariableInfo type = (TypeVariableInfo) unwrappedType;
          if (type.isClassParam()) {
            writer.format("j_%s.unwrap(%s)", unwrappedType.getName(), unwrappedName);
          } else {
            ParamInfo classTypeParam = method.resolveClassTypeParam(type);
            if (classTypeParam != null) {
              writer.format("utils.get_jtype(__args[%s]).unwrap(%s)", classTypeParam.getIndex(), unwrappedName);
            } else {
              writer.format("utils.convParamTypeUnknown(%s)", unwrappedName);
            }
          }
        } else {
          writer.format("utils.convParamTypeUnknown(%s)", unwrappedName);
        }
        break;
      case THROWABLE:
        writer.format("utils.convParamThrowable(%s)", unwrappedName);
        break;
      case CLASS_TYPE:
        writer.format("utils.get_jclass(%s)", unwrappedName);
        break;
      case LIST:
      case SET: {
        String container = kind == LIST ? "List" : "Set";
        ParameterizedTypeInfo type = (ParameterizedTypeInfo) unwrappedType;
        TypeInfo arg = type.getArg(0);
        String argName = arg.getName();
        ClassKind argKind = arg.getKind();
        //Generics cannot be primitive
        if ("java.lang.Long".equals(argName)) {
          writer.format("utils.convParam%sLong(%s)", container, unwrappedName);
        } else if ("java.lang.Short".equals(argName)) {
          writer.format("utils.convParam%sShort(%s)", container, unwrappedName);
        } else if ("java.lang.Byte".equals(argName)) {
          writer.format("utils.convParam%sByte(%s)", container, unwrappedName);
        } else if (argKind == API) {
          writer.format("utils.convParam%sVertxGen(%s)", container, unwrappedName);
        } else if (argKind == JSON_OBJECT) {
          writer.format("utils.convParam%sJsonObject(%s)", container, unwrappedName);
        } else if (argKind == JSON_ARRAY) {
          writer.format("utils.convParam%sJsonArray(%s)", container, unwrappedName);
        } else if (argKind == DATA_OBJECT) {
          writer.format("utils.convParam%sDataObject(%s, function(json) { return new %s(json); })", container, unwrappedName, arg.getSimpleName());
        } else if (argKind == ENUM) {
          writer.format("utils.convParam%sEnum(%s, function(val) { return Packages.%s.valueOf(val); })", container, unwrappedName, arg.getName());
        } else if (argKind == OBJECT) {
          writer.format("utils.convParam%sObject(%s)", container, unwrappedName);
        } else {
          if (param.isNullable()) {
            writer.format("%s == null ? null : ", unwrappedName);
          }
          writer.format("utils.convParam%sBasicOther(%s)", container, unwrappedName);
        }
        break;
      }
      case MAP: {
        ParameterizedTypeInfo type = (ParameterizedTypeInfo) unwrappedType;
        TypeInfo arg = type.getArg(1);
        String argName = arg.getName();
        ClassKind argKind = arg.getKind();
        //Generics cannot be primitive
        if ("java.lang.Long".equals(argName)) {
          writer.format("utils.convParamMapLong(%s)", unwrappedName);
        } else if ("java.lang.Short".equals(argName)) {
          writer.format("utils.convParamMapShort(%s)", unwrappedName);
        } else if ("java.lang.Byte".equals(argName)) {
          writer.format("utils.convParamMapByte(%s)", unwrappedName);
        } else if (argKind == API) {
          writer.format("utils.convParamMapVertxGen(%s)", unwrappedName);
        } else if (argKind == JSON_OBJECT) {
          writer.format("utils.convParamMapJsonObject(%s)", unwrappedName);
        } else if (argKind == JSON_ARRAY) {
          writer.format("utils.convParamMapJsonArray(%s)", unwrappedName);
        } else if (argKind == OBJECT) {
          writer.format("utils.convParamMapObject(%s)", unwrappedName);
        } else {
          writer.print(unwrappedName);
        }
        break;
      }
      case PRIMITIVE:
      case BOXED_PRIMITIVE:
      case STRING:
        switch (unwrappedType.getName()) {
          case "java.lang.Byte":
            writer.format("utils.convParamByte(%s)", unwrappedName);
            break;
          case "java.lang.Short":
            writer.format("utils.convParamShort(%s)", unwrappedName);
            break;
          case "java.lang.Integer":
            writer.format("utils.convParamInteger(%s)", unwrappedName);
            break;
          case "java.lang.Long":
            writer.format("utils.convParamLong(%s)", unwrappedName);
            break;
          case "java.lang.Float":
            writer.format("utils.convParamFloat(%s)", unwrappedName);
            break;
          case "java.lang.Double":
            writer.format("utils.convParamDouble(%s)", unwrappedName);
            break;
          case "java.lang.Character":
            writer.format("utils.convParamCharacter(%s)", unwrappedName);
            break;
          default:
            writer.print(unwrappedName);
            break;
        }
        break;
      default:
        if (param.isNullable()) {
          writer.format("%s == null ? null : ", unwrappedName);
        }
        writer.format("%s._jdel", unwrappedName);
        break;
    }
    return buffer.toString();
  }

  protected String arVal() {
    return "ar.result()";
  }

  protected String basicVal() {
    return "jVal";
  }

  protected void genConstant(M model, ConstantInfo constant, CodeWriter writer) {

    String templ = "J" + model.getType().getSimpleName() + "." + constant.getName();

    writer.format("%s.%s = %s;\n", model.getType().getSimpleName(), constant.getName(), convReturn(model, null, constant.getType(), templ));

  }

  protected void genMethod(M model, String methodName, boolean genStatic, @SuppressWarnings("SameParameterValue") Predicate methodFilter, CodeWriter writer) {

    List methodList = model.getMethods().stream()
      .filter(method -> method.isStaticMethod() == genStatic && method.getName().equals(methodName))
      .collect(Collectors.toList());

    ClassTypeInfo type = model.getType();
    String simpleName = type.getSimpleName();
    if (methodFilter != null) {
      List methodTmpl = methodList;
      methodList = new ArrayList<>();
      for (MethodInfo method : methodTmpl) {
        if (methodFilter.test(method)) {
          methodList.add(method);
        }
      }
    }
    if (methodList.size() > 0) {
      boolean overloaded = methodList.size() > 1;
      MethodInfo method = methodList.get(methodList.size() - 1);
      if (genStatic == method.isStaticMethod()) {
        writer.println("/**");
        if (method.getDoc() != null) {
          Token.toHtml(method.getDoc().getTokens(), "", this::renderLinkToHtml, "\n", writer);
        }
        writer.println();
        writer.print(" ");
        if (genStatic) {
          writer.format("@memberof module:%s", getModuleName(type)).println();
        } else {
          writer.println("@public");
        }
        boolean first = true;
        for (ParamInfo param : method.getParams()) {
          if (first) {
            first = false;
          } else {
            writer.println();
          }
          writer.format(" @param %s {%s} ", param.getName(), getJSDocType(param.getType()));
          if (param.getDescription() != null) {
            Token.toHtml(param.getDescription().getTokens(), "", this::renderLinkToHtml, "", writer);
            writer.print(" ");
          }
        }
        writer.println();

        if (method.getReturnType().getKind() != VOID) {
          writer.format(" @return {%s}", getJSDocType(method.getReturnType()));
          if (method.getReturnDescription() != null) {
            writer.print(" ");
            Token.toHtml(method.getReturnDescription().getTokens(), "", this::renderLinkToHtml, "", writer);
          }
          writer.println();
        }
        writer.println(" */");

        writer.format("%s.%s = ", genStatic ? simpleName : "this", methodName);
        if (overloaded) {
          writer.println(" function() {");
        } else {
          writer.format(" function(%s) {\n", (method.getParams().stream().map(ParamInfo::getName).collect(Collectors.joining(", "))));
        }
        int mcnt = 0;
        writer.indent();
        writer.println("var __args = arguments;");
        for (MethodInfo m : methodList) {
          writer.print(mcnt++ == 0 ? "if" : " else if");
          int paramSize = m.getParams().size();
          writer.format(" (__args.length === %s", paramSize);
          int cnt = 0;
          if (paramSize > 0) {
            writer.print(" && ");
          }
          first = true;
          for (ParamInfo param : m.getParams()) {
            if (first) {
              first = false;
            } else {
              writer.print(" && ");
            }
            switch (param.getType().getKind()) {
              case PRIMITIVE:
              case BOXED_PRIMITIVE:
                if (param.isNullable()) {
                  writer.print("(");
                }
                writer.format("typeof __args[%s] ===", cnt);
                String paramSimpleName = param.getType().getSimpleName();
                if ("boolean".equalsIgnoreCase(paramSimpleName)) {
                  writer.print("'boolean'");
                } else if ("char".equals(paramSimpleName) || "Character".equals(paramSimpleName)) {
                  writer.print("'string'");
                } else {
                  writer.print("'number'");
                }
                if (param.isNullable()) {
                  writer.format(" || __args[%s] == null)", cnt);
                }
                break;
              case STRING:
              case ENUM:
                if (param.isNullable()) {
                  writer.print("(");
                }
                writer.format("typeof __args[%s] === 'string'", cnt);
                if (param.isNullable()) {
                  writer.format(" || __args[%s] == null)", cnt);
                }
                break;
              case CLASS_TYPE:
                writer.format("typeof __args[%s] === 'function'", cnt);
                break;
              case API:
                writer.format("typeof __args[%s] === 'object' && ", cnt);
                if (param.isNullable()) {
                  writer.format("(__args[%s] == null || ", cnt);
                }
                writer.format("__args[%s]._jdel", cnt);
                if (param.isNullable()) {
                  writer.print(")");
                }
                break;
              case JSON_ARRAY:
              case LIST:
              case SET:
                writer.format("typeof __args[%s] === 'object' && ", cnt);
                if (param.isNullable()) {
                  writer.print("(");
                }
                writer.format("__args[%s] instanceof Array", cnt);
                if (param.isNullable()) {
                  writer.format(" || __args[%s] == null)", cnt);
                }
                break;
              case HANDLER:
                if (param.isNullable()) {
                  writer.print("(");
                }
                writer.format("typeof __args[%s] === 'function'", cnt);
                if (param.isNullable()) {
                  writer.format(" || __args[%s] == null)", cnt);
                }
                break;
              case OBJECT:
                if (param.getType().isVariable() && ((TypeVariableInfo) param.getType()).isClassParam()) {
                  writer.format("j_%s.accept(__args[%s])", param.getType().getName(), cnt);
                } else {
                  writer.format("typeof __args[%s] !== 'function'", cnt);
                }
                break;
              case FUNCTION:
                writer.format("typeof __args[%s] === 'function'", cnt);
                break;
              case THROWABLE:
                writer.format("typeof __args[%s] === 'object'", cnt);
                break;
              default:
                if (!param.isNullable()) {
                  writer.print("(");
                }
                writer.format("typeof __args[%s] === 'object'", cnt);
                if (!param.isNullable()) {
                  writer.format(" && __args[%s] != null)", cnt);
                }
            }
            cnt++;
          }
          writer.println(") {");
          writer.indent();
          genMethodAdapter(model, m, writer);
          writer.unindent();
          writer.print("}");
        }
        if (!genStatic) {
          writer.format(" else if (typeof __super_%s != 'undefined') {\n", method.getName());
          writer.indented(() -> writer.format("return __super_%s.apply(this, __args);\n", method.getName()));
          writer.println("}");
        }
        writer.println("else throw new TypeError('function invoked with invalid arguments');");
        writer.unindent();
        writer.println("};");
        writer.println();
      }
    }
  }

  protected abstract void genMethodAdapter(M model, MethodInfo method, CodeWriter writer);


  protected void genLicenses(PrintWriter writer) {
    writer.println("/*");
    writer.println(" * Copyright 2014 Red Hat, Inc.");
    writer.println(" *");
    writer.println(" * Red Hat licenses this file to you under the Apache License, version 2.0");
    writer.println(" * (the \"License\"); you may not use this file except in compliance with the");
    writer.println(" * License.  You may obtain a copy of the License at:");
    writer.println(" *");
    writer.println(" * http://www.apache.org/licenses/LICENSE-2.0");
    writer.println(" *");
    writer.println(" * Unless required by applicable law or agreed to in writing, software");
    writer.println(" * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT");
    writer.println(" * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the");
    writer.println(" * License for the specific language governing permissions and limitations");
    writer.println(" * under the License.");
    writer.println(" */");
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy