Package net.jangaroo.exml.tools

Source Code of net.jangaroo.exml.tools.ExtAsApiGenerator$Overrides

package net.jangaroo.exml.tools;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.jangaroo.exml.utils.ExmlUtils;
import net.jangaroo.jooc.Jooc;
import net.jangaroo.jooc.backend.ActionScriptCodeGeneratingModelVisitor;
import net.jangaroo.jooc.backend.JsCodeGenerator;
import net.jangaroo.jooc.model.AnnotationModel;
import net.jangaroo.jooc.model.AnnotationPropertyModel;
import net.jangaroo.jooc.model.ClassModel;
import net.jangaroo.jooc.model.CompilationUnitModelRegistry;
import net.jangaroo.jooc.model.CompilationUnitModel;
import net.jangaroo.jooc.model.FieldModel;
import net.jangaroo.jooc.model.MemberModel;
import net.jangaroo.jooc.model.MethodModel;
import net.jangaroo.jooc.model.MethodType;
import net.jangaroo.jooc.model.ParamModel;
import net.jangaroo.jooc.model.PropertyModel;
import net.jangaroo.utils.AS3Type;
import net.jangaroo.utils.CompilerUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Generate ActionScript 3 APIs from a jsduck JSON export of the Ext JS 4.x API.
*/
public class ExtAsApiGenerator {

  private static Map<String,ExtClass> extClasses;
  private static CompilationUnitModelRegistry compilationUnitModelRegistry;
  private static Set<String> interfaces;
  public static final List<String> NON_COMPILE_TIME_CONSTANT_INITIALIZERS = Arrays.asList("window", "document", "document.body");

  public static void main(String[] args) throws IOException {
    File srcDir = new File(args[0]);
    File outputDir = new File(args[1]);
    File[] files = srcDir.listFiles();
    if (files != null) {
      extClasses = new HashMap<String, ExtClass>();
      compilationUnitModelRegistry = new CompilationUnitModelRegistry();
      interfaces = new HashSet<String>();
      for (File jsonFile : files) {
        ExtClass extClass = readExtApiJson(jsonFile);
        if (extClass != null) {
          extClasses.put(extClass.name, extClass);
          for (String alternateClassName : extClass.alternateClassNames) {
            extClasses.put(alternateClassName, extClass);
          }
          for (String mixin : extClass.mixins) {
            interfaces.add(mixin);
          }
        }
      }
      markTransitiveSupersAsInterfaces(interfaces);
      //markTransitiveSupersAsInterfaces(singletons);
      for (ExtClass extClass : new HashSet<ExtClass>(extClasses.values())) {
        generateClassModel(extClass);
      }
      compilationUnitModelRegistry.complementOverrides();
      compilationUnitModelRegistry.complementImports();
      for (CompilationUnitModel compilationUnitModel : compilationUnitModelRegistry.getCompilationUnitModels()) {
        generateActionScriptCode(compilationUnitModel, outputDir);
      }
    }
  }

  private static void markTransitiveSupersAsInterfaces(Set<String> extClasses) {
    Set<String> supers = supers(extClasses);
    while (interfaces.addAll(supers)) {
      supers = supers(supers);
    }
  }

  private static Set<String> supers(Set<String> extClasses) {
    Set<String> result = new HashSet<String>();
    for (String extClass : extClasses) {
      String superclass = ExtAsApiGenerator.extClasses.get(extClass).extends_;
      if (superclass != null) {
        result.add(superclass);
      }
    }
    return result;
  }

  private static ExtClass readExtApiJson(File jsonFile) throws IOException {
    System.out.printf("Reading API from %s...\n", jsonFile.getPath());
    ExtClass extClass = new ObjectMapper().readValue(jsonFile, ExtClass.class);
    if (JsCodeGenerator.PRIMITIVES.contains(extClass.name)) {
      System.err.println("ignoring built-in class " + extClass.name);
      return null;
    }
    return extClass;
  }

  private static void generateClassModel(ExtClass extClass) {
    CompilationUnitModel extAsClassUnit = createClassModel(convertType(extClass.name));
    ClassModel extAsClass = (ClassModel)extAsClassUnit.getPrimaryDeclaration();
    System.out.printf("Generating AS3 API model %s for %s...%n", extAsClassUnit.getQName(), extClass.name);
    //configClass.setName(extClass.aliases.get("widget").get(0));
    //configClass.setPackage("ext.config");
    extAsClass.setAsdoc(toAsDoc(extClass.doc));
    CompilationUnitModel extAsInterfaceUnit = null;
    if (interfaces.contains(extClass.name)) {
      extAsInterfaceUnit = createClassModel(convertToInterface(extClass.name));
      System.out.printf("Generating AS3 API model %s for %s...%n", extAsInterfaceUnit.getQName(), extClass.name);
      ClassModel extAsInterface = (ClassModel)extAsInterfaceUnit.getPrimaryDeclaration();
      extAsInterface.setInterface(true);
      extAsInterface.setAsdoc(toAsDoc(extClass.doc));
      addInterfaceForSuperclass(extClass, extAsInterface);
    }
    if (isSingleton(extClass)) {
      FieldModel singleton = new FieldModel(CompilerUtils.className(extClass.name), extAsClassUnit.getQName());
      singleton.setConst(true);
      singleton.setValue("new " + extAsClassUnit.getQName());
      singleton.setAsdoc(extAsClass.getAsdoc());
      CompilationUnitModel singletonUnit = new CompilationUnitModel(extAsClassUnit.getPackage(), singleton);
      compilationUnitModelRegistry.register(singletonUnit);

      extAsClass.setAsdoc(String.format("%s\n<p>Type of singleton %s.</p>\n * @see %s %s",
        extAsClass.getAsdoc(),
        singleton.getName(),
        CompilerUtils.qName(extAsClassUnit.getPackage(), "#" + singleton.getName()),
        singletonUnit.getQName()));
    }
    extAsClass.setSuperclass(convertType(extClass.extends_));
    if (extAsInterfaceUnit != null) {
      extAsClass.addInterface(extAsInterfaceUnit.getQName());
    }
    for (String mixin : extClass.mixins) {
      String superInterface = convertToInterface(mixin);
      extAsClass.addInterface(superInterface);
      if (extAsInterfaceUnit != null) {
        extAsInterfaceUnit.getClassModel().addInterface(superInterface);
      }
    }

    if (extAsInterfaceUnit != null) {
      addNonStaticMembers(extClass, extAsInterfaceUnit);
    }

    if (!extAsClass.isInterface()) {
      addFields(extAsClass, filterByOwner(false, extClass, extClass.statics.property));
      addMethods(extAsClass, filterByOwner(false, extClass, extClass.statics.method));
    }
    addNonStaticMembers(extClass, extAsClassUnit);
  }

  private static void addInterfaceForSuperclass(ExtClass extClass, ClassModel extAsInterface) {
    if (extClass.extends_ != null) {
      extAsInterface.addInterface(convertToInterface(extClass.extends_));
    }
  }

  private static CompilationUnitModel createClassModel(String qName) {
    CompilationUnitModel compilationUnitModel = new CompilationUnitModel(null, new ClassModel());
    compilationUnitModel.setQName(qName);
    compilationUnitModelRegistry.register(compilationUnitModel);
    return compilationUnitModel;
  }

  private static void addNonStaticMembers(ExtClass extClass, CompilationUnitModel extAsClassUnit) {
    addEvents(extAsClassUnit, filterByOwner(false, extClass, extClass.members.event));
    ClassModel extAsClass = extAsClassUnit.getClassModel();
    addProperties(extAsClass, filterByOwner(extAsClass.isInterface(), extClass, extClass.members.property));
    addMethods(extAsClass, filterByOwner(extAsClass.isInterface(), extClass, extClass.members.method));
    addProperties(extAsClass, filterByOwner(extAsClass.isInterface(), extClass, extClass.members.cfg));
  }

  private static void generateActionScriptCode(CompilationUnitModel extAsClass, File outputDir) throws IOException {
    File outputFile = CompilerUtils.fileFromQName(extAsClass.getQName(), outputDir, Jooc.AS_SUFFIX);
    //noinspection ResultOfMethodCallIgnored
    outputFile.getParentFile().mkdirs(); // NOSONAR
    System.out.printf("Generating AS3 API for %s into %s...\n", extAsClass.getQName(), outputFile.getPath());
    extAsClass.visit(new ActionScriptCodeGeneratingModelVisitor(new FileWriter(outputFile)));
  }

  private static <T extends Member> List<T> filterByOwner(boolean isInterface, ExtClass owner, List<T> members) {
    List<T> result = new ArrayList<T>();
    for (T member : members) {
      if (member.owner.equals(owner.name) && (!isInterface || isPublicNonStaticMethod(member))) {
        result.add(member);
      }
    }
    return result;
  }

  private static boolean isPublicNonStaticMethod(Member member) {
    return member instanceof Method && !member.meta.static_ && !member.meta.private_ && !member.meta.protected_
      && !"constructor".equals(member.name);
  }

  private static boolean isConst(Member member) {
    return member.meta.readonly || (member.name.equals(member.name.toUpperCase()) && member.default_ != null);
  }

  private static void addEvents(CompilationUnitModel compilationUnitModel, List<Event> events) {
    for (Event event : events) {
      ClassModel classModel = compilationUnitModel.getClassModel();
      String eventTypeQName = generateEventClass(compilationUnitModel, event);
      AnnotationModel annotationModel = new AnnotationModel("Event",
              new AnnotationPropertyModel("name", "'" + event.name + "'"),
              new AnnotationPropertyModel("type", "'" + eventTypeQName + "'"));
      annotationModel.setAsdoc(toAsDoc(event.doc) + String.format("\n * @eventType %s.NAME", eventTypeQName));
      classModel.addAnnotation(annotationModel);
      System.err.println("*** adding event " + event.name + " to class " + classModel.getName());
    }
  }

  public static String capitalize(String name) {
    return name == null || name.length() == 0 ? name : Character.toUpperCase(name.charAt(0)) + name.substring(1);
  }

  private static String generateEventClass(CompilationUnitModel compilationUnitModel, Event event) {
    ClassModel classModel = compilationUnitModel.getClassModel();
    String eventTypeQName = CompilerUtils.qName(compilationUnitModel.getPackage(),
            "events." + classModel.getName() + capitalize(event.name) + "Event");
    CompilationUnitModel extAsClassUnit = createClassModel(eventTypeQName);
    ClassModel extAsClass = (ClassModel)extAsClassUnit.getPrimaryDeclaration();
    extAsClass.setAsdoc(toAsDoc(event.doc) + "\n * @see " + compilationUnitModel.getQName());

    FieldModel eventNameConstant = new FieldModel("NAME", "String", CompilerUtils.quote(event.name));
    eventNameConstant.setStatic(true);
    eventNameConstant.setAsdoc(MessageFormat.format("This constant defines the value of the <code>type</code> property of the event object\nfor a <code>{0}</code> event.\n   * @eventType {0}", event.name));
    extAsClass.addMember(eventNameConstant);

    MethodModel constructorModel = extAsClass.createConstructor();
    constructorModel.addParam(new ParamModel("arguments", "Array"));
    StringBuilder propertyAssignments = new StringBuilder();
    for (int i = 0; i < event.params.size(); i++) {
      Param param = event.params.get(i);

      // add assignment to constructor body:
      if (i > 0) {
        propertyAssignments.append("\n    ");
      }
      propertyAssignments.append(String.format("this['%s'] = arguments[%d];", convertName(param.name), i));

      // add getter method:
      MethodModel property = new MethodModel(MethodType.GET, convertName(param.name), convertType(param.type));
      property.setAsdoc(toAsDoc(param.doc));
      extAsClass.addMember(property);
    }

    constructorModel.setBody(propertyAssignments.toString());

    return eventTypeQName;
  }

  private static void addFields(ClassModel classModel, List<? extends Member> fields) {
    for (Member member : fields) {
      FieldModel fieldModel = new FieldModel(convertName(member.name), convertType(member.type), member.default_);
      fieldModel.setAsdoc(toAsDoc(member.doc));
      setStatic(fieldModel, member);
      fieldModel.setConst(isConst(member));
      classModel.addMember(fieldModel);
    }
  }

  private static void addProperties(ClassModel classModel, List<? extends Member> properties) {
    for (Member member : properties) {
      if (classModel.getMember(member.name) == null) {
        PropertyModel propertyModel = new PropertyModel(convertName(member.name), convertType(member.type));
        propertyModel.setAsdoc(toAsDoc(member.doc));
        setStatic(propertyModel, member);
        propertyModel.addGetter();
        if (!member.meta.readonly) {
          propertyModel.addSetter();
        }
        classModel.addMember(propertyModel);
      }
    }
  }

  private static void addMethods(ClassModel classModel, List<Method> methods) {
    for (Method method : methods) {
      if (classModel.getMember(method.name) == null) {
        boolean isConstructor = method.name.equals("constructor");
        MethodModel methodModel = isConstructor
                ? new MethodModel(classModel.getName(), null)
                : new MethodModel(convertName(method.name), convertType(method.return_.type));
        methodModel.setAsdoc(toAsDoc(method.doc));
        methodModel.getReturnModel().setAsdoc(toAsDoc(method.return_.doc));
        setStatic(methodModel, method);
        for (Param param : method.params) {
          ParamModel paramModel = new ParamModel(convertName(param.name), convertType(param.type));
          paramModel.setAsdoc(toAsDoc(param.doc));
          setDefaultValue(paramModel, param);
          paramModel.setRest(param == method.params.get(method.params.size() - 1) && param.type.endsWith("..."));
          methodModel.addParam(paramModel);
        }
        classModel.addMember(methodModel);
      }
    }
  }

  private static void setStatic(MemberModel memberModel, Member member) {
    memberModel.setStatic(member.meta.static_ || isStaticSingleton(extClasses.get(member.owner)));
  }

  private static String toAsDoc(String doc) {
    String asDoc = doc.trim();
    if (asDoc.startsWith("<p>")) {
      // remove <p>...</p> around first paragraph:
      int endTagPos = asDoc.indexOf("</p>");
      asDoc = asDoc.substring(3, endTagPos) + asDoc.substring(endTagPos + 4);
    }
    if (asDoc.startsWith("{")) {
      int closingBracePos = asDoc.indexOf("} ");
      if (closingBracePos != -1) {
        asDoc = asDoc.substring(closingBracePos + 2);
      }
    }
    return asDoc;
  }

  private static void setDefaultValue(ParamModel paramModel, Param param) {
    String defaultValue = param.default_;
    if (defaultValue != null) {
      if (NON_COMPILE_TIME_CONSTANT_INITIALIZERS.contains(defaultValue)) {
        paramModel.setAsdoc("(Default " + defaultValue + ") " + paramModel.getAsdoc());
        defaultValue = null;
        param.optional = true; // only in case it is set inconsistently...
      }
    }
    if (defaultValue == null && param.optional) {
      defaultValue = AS3Type.getDefaultValue(paramModel.getType());
    }
    paramModel.setValue(defaultValue);
  }

  private static String convertName(String name) {
    return "is".equals(name) ? "matches" :
            "class".equals(name) ? "cls" :
                    "this".equals(name) ? "source" :
                            "new".equals(name) ? "new_" :
                            name;
  }

  private static String convertToInterface(String mixin) {
    String packageName = CompilerUtils.packageName(mixin).toLowerCase();
    String className = "I" + CompilerUtils.className(mixin);
    if (packageName.startsWith("ext")) {
      packageName = "ext4" + packageName.substring(3);
    }
    return CompilerUtils.qName(packageName, className);
  }

  private static String convertType(String extType) {
    if (extType == null) {
      return null;
    }
    if ("undefined".equals(extType)) {
      return "void";
    }
    if ("HTMLElement".equals(extType) || "Event".equals(extType)) {
      return "js." + extType;
    }
    if (extType.endsWith("...")) {
      return "Array";
    }
    if (!extType.matches("[a-zA-Z0-9._$<>]+") || "Mixed".equals(extType)) {
      return "*"; // TODO: join types? rather use Object? simulate overloading by splitting into several methods?
    }
    ExtClass extClass = extClasses.get(extType);
    if (extClass != null) {
      // normalize:
      extType = extClass.name;
    }
    if ("Ext".equals(extType)) {
      // special case: move singleton "Ext" into package "ext":
      extType = "ext.Ext";
    }
    String packageName = CompilerUtils.packageName(extType).toLowerCase();
    String className = CompilerUtils.className(extType);
    if (isSingleton(extClass)) {
      className = "S" + className;
    }
    if (JsCodeGenerator.PRIMITIVES.contains(className)) {
      if ("ext".equals(packageName)) {
        if (isStaticSingleton(extClass)) {
          // for most built-in classes, there is a static ...Util class:
          packageName = "ext.util";
          className += "Util";
        } else {
          // all others in package "ext" are prefixed with "Ext":
          className = "Ext" + className;
        }
      } else {
        // all in other packages are postfixed with the upper-cased last package segment:
        className += ExmlUtils.createComponentClassName(packageName.substring(packageName.lastIndexOf('.') + 1));
      }
    } else if ("is".equals(className)) {
      // special case lower-case "is" class:
      className = "Is";
    }
    if (packageName.startsWith("ext")) {
      packageName = "ext4" + packageName.substring(3);
    }
    return CompilerUtils.qName(packageName, className);
  }

  private static boolean isSingleton(ExtClass extClass) {
    return extClass != null && extClass.singleton && !isStaticSingleton(extClass);
  }

  private static boolean isStaticSingleton(ExtClass extClass) {
    return extClass != null && extClass.singleton && (extClass.extends_ == null || extClass.extends_.length() == 0)
      && extClass.statics.cfg.isEmpty() && extClass.statics.event.isEmpty() && extClass.statics.method.isEmpty()
      && extClass.statics.property.isEmpty() && extClass.statics.css_mixin.isEmpty() && extClass.statics.css_var.isEmpty();
  }

  @SuppressWarnings("UnusedDeclaration")
  @JsonIgnoreProperties({"html_meta", "html_type"})
  public static class Tag {
    public String tagname;
    public String name;
    public String doc;
    @JsonProperty("private")
    public String private_;
    public MemberReference inheritdoc;

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Tag tag = (Tag)o;
      return name.equals(tag.name) && !(tagname != null ? !tagname.equals(tag.tagname) : tag.tagname != null);

    }

    @Override
    public int hashCode() {
      int result = tagname != null ? tagname.hashCode() : 0;
      result = 31 * result + name.hashCode();
      return result;
    }
  }

  @SuppressWarnings("UnusedDeclaration")
  public static class ExtClass extends Tag {
    @JsonProperty("extends")
    public String extends_;
    public List<String> mixins;
    public List<String> alternateClassNames;
    public Map<String,List<String>> aliases;
    public boolean singleton;
    public List<String> requires;
    public List<String> uses;
    public String code_type;
    public boolean inheritable;
    public Meta meta;
    public String id;
    public Members members;
    public Members statics;
    public List<Object> files;
    public boolean component;
    public List<String> superclasses;
    public List<String> subclasses;
    public List<String> mixedInto;
    public List<String> parentMixins;
    @JsonProperty("abstract")
    public boolean abstract_;
  }

  @SuppressWarnings("UnusedDeclaration")
  public static class Members {
    public List<Member> cfg;
    public List<Property> property;
    public List<Method> method;
    public List<Event> event;
    public List<Member> css_var;
    public List<Member> css_mixin;
  }

  @JsonIgnoreProperties({"html_type", "html_meta", "properties"})
  public abstract static class Var extends Tag {
    public String type;
    @JsonProperty("default")
    public String default_;
  }

  @SuppressWarnings("UnusedDeclaration")
  public static class Member extends Var {
    public String owner;
    public String shortDoc;
    public Meta meta;
    public boolean inheritable;
    public String id;
    public List<String> files;
    public boolean accessor;
    public boolean evented;
    public List<Overrides> overrides;
  }

  @SuppressWarnings("UnusedDeclaration")
  public static class MemberReference extends Tag {
    public String cls;
    public String member;
    public String type;
  }

  @SuppressWarnings("UnusedDeclaration")
  public static class Overrides {
    public String name;
    public String owner;
    public String id;
  }

  public static class Property extends Member {
    @Override
    public String toString() {
      return meta + "var " + super.toString();
    }
  }

  public static class Param extends Var {
    public boolean optional;
  }

  public static class Method extends Member {
    public List<Param> params;
    @JsonProperty("return")
    public Param return_;
  }

  @SuppressWarnings("UnusedDeclaration")
  public static class Event extends Member {
    public List<Param> params;
  }

  @SuppressWarnings("UnusedDeclaration")
  public static class Meta {
    @JsonProperty("protected")
    public boolean protected_;
    @JsonProperty("private")
    public boolean private_;
    public boolean readonly;
    @JsonProperty("static")
    public boolean static_;
    @JsonProperty("abstract")
    public boolean abstract_;
    public boolean markdown;
    public Map<String,String> deprecated;
    public String template;
    public List<String> author;
    public List<String> docauthor;
    public boolean required;
    public Map<String,String> removed;
  }
}
TOP

Related Classes of net.jangaroo.exml.tools.ExtAsApiGenerator$Overrides

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.