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

net.jangaroo.exml.tools.ExtAsApiGenerator Maven / Gradle / Ivy

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 extClasses;
  private static CompilationUnitModelRegistry compilationUnitModelRegistry;
  private static Set interfaces;
  public static final List 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();
      compilationUnitModelRegistry = new CompilationUnitModelRegistry();
      interfaces = new HashSet();
      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(extClasses.values())) {
        generateClassModel(extClass);
      }
      compilationUnitModelRegistry.complementOverrides();
      compilationUnitModelRegistry.complementImports();
      for (CompilationUnitModel compilationUnitModel : compilationUnitModelRegistry.getCompilationUnitModels()) {
        generateActionScriptCode(compilationUnitModel, outputDir);
      }
    }
  }

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

  private static Set supers(Set extClasses) {
    Set result = new HashSet();
    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

Type of singleton %s.

\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 List filterByOwner(boolean isInterface, ExtClass owner, List members) { List result = new ArrayList(); 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 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 type property of the event object\nfor a {0} 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 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 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 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("

")) { // remove

...

around first paragraph: int endTagPos = asDoc.indexOf("

"); 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 mixins; public List alternateClassNames; public Map> aliases; public boolean singleton; public List requires; public List uses; public String code_type; public boolean inheritable; public Meta meta; public String id; public Members members; public Members statics; public List files; public boolean component; public List superclasses; public List subclasses; public List mixedInto; public List parentMixins; @JsonProperty("abstract") public boolean abstract_; } @SuppressWarnings("UnusedDeclaration") public static class Members { public List cfg; public List property; public List method; public List event; public List css_var; public List 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 files; public boolean accessor; public boolean evented; public List 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 params; @JsonProperty("return") public Param return_; } @SuppressWarnings("UnusedDeclaration") public static class Event extends Member { public List 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 deprecated; public String template; public List author; public List docauthor; public boolean required; public Map removed; } }