Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package bam.web.dtc.compiler; import bam.web.dtc.parse.TypeScriptListener; import bam.web.dtc.parse.TypeScriptParser; import bam.web.dtc.types.TypeResolver; import bam.web.dtc.types.MultiMethodSignature; import bam.web.dtc.types.Types; import bam.web.dtc.util.CodeModelUtil; import bam.web.dtc.util.IterableUtil; import bam.web.dtc.util.exceptions.Exceptions; import com.sun.codemodel.CodeWriter; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JDeclaration; import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JDocCommentable; import com.sun.codemodel.JFieldVar; import com.sun.codemodel.JGenerifiable; import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; import com.sun.codemodel.JPackage; import com.sun.codemodel.JType; import com.sun.codemodel.JVar; import com.sun.codemodel.writer.SingleStreamCodeWriter; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ErrorNode; import org.antlr.v4.runtime.tree.TerminalNode; /** * * @author bennyl */ public class CodeModelGenerator implements TypeScriptListener { private static final Set<Class> CONTAINS_GENERIC_SCOPE = new HashSet<>( Arrays.asList(TypeScriptParser.MethodSignatureContext.class, TypeScriptParser.Interface_Context.class)); int typeNameIndex = 0; JCodeModel model = new JCodeModel(); JPackage utilPackage; JPackage defaultPackage; TypeResolver typeResolver; LinkedList<JPackage> packageStack = new LinkedList<>(); LinkedList<JDeclaration> declerationStack = new LinkedList<>(); LinkedList<MultiMethodSignature> signaturesStack = new LinkedList<>(); LinkedList<Types> typeStack = new LinkedList<>(); String lastDoc = null; @Override public void enterAmbientDecleration(TypeScriptParser.AmbientDeclerationContext ctx) { pushStaticField(ctx.name.getText()); pushTypes(); } @Override public void exitAmbientDecleration(TypeScriptParser.AmbientDeclerationContext ctx) { popTypes(popField()); } @Override public void enterDefinitionsFile(TypeScriptParser.DefinitionsFileContext ctx) { utilPackage = model._package("jsn.core.util"); pushPackage(defaultPackage = model._package("jsn.core")); typeResolver = new TypeResolver(model, defaultPackage); Exceptions.uncheck(() -> pushDec(pkg()._class("Env"))); } @Override public void exitDefinitionsFile(TypeScriptParser.DefinitionsFileContext ctx) { popPackage(); implementNativeMethods(); printModel(); } @Override public void visitTerminal(TerminalNode node) { } @Override public void visitErrorNode(ErrorNode node) { } @Override public void enterEveryRule(ParserRuleContext ctx) { if (CONTAINS_GENERIC_SCOPE.contains(ctx.getClass())) { typeResolver.pushNewGenericScope(); } } @Override public void exitEveryRule(ParserRuleContext ctx) { if (CONTAINS_GENERIC_SCOPE.contains(ctx.getClass())) { typeResolver.popTopMostGenericScope(); } } private void pushPackage(JPackage _package) { packageStack.addFirst(_package); } private void popPackage() { packageStack.removeFirst(); } private JPackage pkg() { return packageStack.getFirst(); } private <T extends JDeclaration> T pushDec(T d) { declerationStack.addFirst(d); return d; } private <T extends JDeclaration> T popDec() { return (T) declerationStack.removeFirst(); } private void printModel() { try { CodeWriter cw = new SingleStreamCodeWriter(System.out); model.build(cw); } catch (IOException ex) { Logger.getLogger(CodeModelGenerator.class.getName()).log(Level.SEVERE, null, ex); } } private JFieldVar pushStaticField(String name) { return pushDec(clz().field(JMod.PUBLIC | JMod.STATIC, model.ref("any"), name)); } private JDefinedClass clz() { return (JDefinedClass) dec(); } private JMethod mtd() { return (JMethod) dec(); } private JDeclaration dec() { return declerationStack.getFirst(); } private JGenerifiable generifiable() { return (JGenerifiable) dec(); } @Override public void enterParam(TypeScriptParser.ParamContext ctx) { } @Override public void exitParam(TypeScriptParser.ParamContext ctx) { if (ctx.variadic != null) { signature().setVariadic(true); } } @Override public void enterFunction(TypeScriptParser.FunctionContext ctx) { pushSignature(ctx.name.getText(), JMod.PUBLIC | JMod.STATIC | JMod.NATIVE, false); } @Override public void exitFunction(TypeScriptParser.FunctionContext ctx) { } @Override public void enterTypeAnnotation(TypeScriptParser.TypeAnnotationContext ctx) { } @Override public void exitTypeAnnotation(TypeScriptParser.TypeAnnotationContext ctx) { } @Override public void enterFunctionType(TypeScriptParser.FunctionTypeContext ctx) { pushPackage(utilPackage); pushInterface(generateTypeName()); pushSignature("call", JMod.PUBLIC, false); } @Override public void exitFunctionType(TypeScriptParser.FunctionTypeContext ctx) { Exceptions.uncheck(() -> { MultiMethodSignature signatures = popSignature(); JDefinedClass prototype = popInterface(); signatures.appendMethods(prototype); for (JMethod method : prototype.methods()) { JDefinedClass ifc = pkg()._interface(generateTypeName()); ifc.methods().add(method); typeResolver.importGenericScope(ifc); final JClass type = model.ref(ifc.fullName()); if (ifc.typeParams().length == 0) { type.narrow(ifc.typeParams()); } types().add(type); } pkg().remove(prototype); popPackage(); }); } @Override public void enterType(TypeScriptParser.TypeContext ctx) { } @Override public void exitType(TypeScriptParser.TypeContext ctx) { } @Override public void enterObjectType(TypeScriptParser.ObjectTypeContext ctx) { types().add(typeResolver.lookup(ctx.name.getText())); } @Override public void exitObjectType(TypeScriptParser.ObjectTypeContext ctx) { } private JVar pushParam(String name) { return pushDec(mtd().param(model.ref("any"), name)); } private MultiMethodSignature popSignature() { MultiMethodSignature result = signaturesStack.removeFirst(); result.setReturnTypes(popTypesOrAny()); popMethod(); return result; } private JDefinedClass pushInterface(String name) { return Exceptions.uncheck(() -> pushDec(pkg()._interface(name))); } private String generateTypeName() { if (dec() instanceof JVar) { return param().name() + (typeNameIndex++); } return "$GEN" + (typeNameIndex++); } private JMethod popMethod() { return popDec(); } private JDefinedClass popInterface() { return popDec(); } private void pushTypes() { typeStack.addFirst(new Types()); } private Types popTypes() { return typeStack.removeFirst(); } private Types types() { return typeStack.getFirst(); } private JVar param() { return (JVar) dec(); } private MultiMethodSignature signature() { return signaturesStack.getFirst(); } private JVar popParam() { return popDec(); } @Override public void enterDefinition(TypeScriptParser.DefinitionContext ctx) { } @Override public void exitDefinition(TypeScriptParser.DefinitionContext ctx) { } @Override public void enterProperty(TypeScriptParser.PropertyContext ctx) { pushTypes(); //return type; } @Override public void exitProperty(TypeScriptParser.PropertyContext ctx) { Types returnTypes = popTypesOrAny(); for (JType returnType : returnTypes) { JMethod getter = clz().method(JMod.PUBLIC, returnType, ctx.name.getText()); JMethod setter = clz().method(JMod.PUBLIC, model.VOID, ctx.name.getText()); setter.param(returnType, "value"); getter.annotate(model.ref("jsn.ano.Property")); setter.annotate(model.ref("jsn.ano.Property")); } } @Override public void enterInterface_(TypeScriptParser.Interface_Context ctx) { pushInterface(ctx.name.getText())._implements(model.ref("jsn.core.JSObjectIfc")); } @Override public void exitInterface_(TypeScriptParser.Interface_Context ctx) { popInterface(); } private JMethod pushMethod(String name) { return pushDec(clz().method(JMod.PUBLIC, typeResolver.lookup("any"), name)); } private JFieldVar popField() { return popDec(); } private void popTypes(JVar field) { Types type = popTypes(); if (type.numOptions() > 0) { field.type(type.single()); } } private Types popTypesOrAny() { Types t = popTypes(); if (t.numOptions() == 0) { t.add(typeResolver.lookup("any")); } return t; } @Override public void enterMethod(TypeScriptParser.MethodContext ctx) { pushSignature(ctx.name.getText(), JMod.PUBLIC, false); } @Override public void exitMethod(TypeScriptParser.MethodContext ctx) { } @Override public void enterScopedDefinition(TypeScriptParser.ScopedDefinitionContext ctx) { } @Override public void exitScopedDefinition(TypeScriptParser.ScopedDefinitionContext ctx) { } @Override public void enterMethodSignature(TypeScriptParser.MethodSignatureContext ctx) { } @Override public void exitMethodSignature(TypeScriptParser.MethodSignatureContext ctx) { popSignature().appendMethods(clz()); } @Override public void enterBareFunction(TypeScriptParser.BareFunctionContext ctx) { if (clz().isInterface()) { pushSignature("invoke", JMod.PUBLIC, false).annotate(model.ref("jsn.ano.Bare")); } else { pushSignature("invoke", JMod.PUBLIC | JMod.STATIC | JMod.NATIVE, false) .annotate(model.ref("jsn.ano.Bare")); } } @Override public void exitBareFunction(TypeScriptParser.BareFunctionContext ctx) { } @Override public void enterEnum_(TypeScriptParser.Enum_Context ctx) { Exceptions.uncheck(() -> { JDefinedClass enum_ = withDoc(pkg()._enum(ctx.name.getText())); for (Token v : ctx.values) { enum_.enumConstant(v.getText()); } }); } @Override public void exitEnum_(TypeScriptParser.Enum_Context ctx) { } @Override public void enterGenericType(TypeScriptParser.GenericTypeContext ctx) { pushTypes(); //bound } @Override public void exitGenericType(TypeScriptParser.GenericTypeContext ctx) { Types bound = popTypes(); if (bound.numOptions() > 0) { typeResolver.addToGenericScope(generifiable().generify(ctx.name.getText(), bound.single().boxify())); } else { typeResolver.addToGenericScope(generifiable().generify(ctx.name.getText())); } } @Override public void enterGenericTypes(TypeScriptParser.GenericTypesContext ctx) { } @Override public void exitGenericTypes(TypeScriptParser.GenericTypesContext ctx) { } @Override public void enterArrayMark(TypeScriptParser.ArrayMarkContext ctx) { types().increaseDimention(); } @Override public void exitArrayMark(TypeScriptParser.ArrayMarkContext ctx) { } private JMethod pushSignature(String name, int mods, boolean constructor) { if (constructor) { return pushSignature(clz().constructor(mods), constructor); } return pushSignature(withDoc(clz().method(mods, typeResolver.lookup("any"), name)), constructor); } private JMethod pushSignature(JMethod prototype, boolean constructor) { signaturesStack.addFirst(new MultiMethodSignature(prototype, model, constructor)); pushDec(prototype); pushTypes(); //return type return prototype; } @Override public void enterConstParam(TypeScriptParser.ConstParamContext ctx) { } @Override public void exitConstParam(TypeScriptParser.ConstParamContext ctx) { signature().addConstParam(ctx.name.getText(), ctx.str.getText()); } @Override public void enterSimpleParam(TypeScriptParser.SimpleParamContext ctx) { pushParam(ctx.name.getText()); pushTypes(); } @Override public void exitSimpleParam(TypeScriptParser.SimpleParamContext ctx) { signature().addParam(popTypes(), popParam().name(), ctx.optional != null); } @Override public void enterConstructor(TypeScriptParser.ConstructorContext ctx) { pushSignature(withDoc(clz().constructor(JMod.PUBLIC)), true); } @Override public void exitConstructor(TypeScriptParser.ConstructorContext ctx) { } @Override public void enterAmbientClassDecleration(TypeScriptParser.AmbientClassDeclerationContext ctx) { Exceptions.uncheck(() -> { JDefinedClass clz = withDoc(pkg()._class("JS" + ctx.name.getText())); clz._implements(model.ref(pkg().name() + "." + ctx.name.getText())); clz._extends(model.ref("jsn.core.JSObject")); pushDec(clz); }); } @Override public void exitAmbientClassDecleration(TypeScriptParser.AmbientClassDeclerationContext ctx) { JDefinedClass clz = popDec(); Set<JMethod> constructors = CodeModelUtil.contructorSet(clz); for (JMethod m : clz.methods().toArray(new JMethod[0])) { if (!constructors.contains(m)) { CodeModelUtil.refactorToStaticNativeMethod(m, clz); } } } @Override public void enterDocComment(TypeScriptParser.DocCommentContext ctx) { lastDoc = ctx.getText().substring("/**".length(), ctx.getText().length() - "*/".length()); lastDoc = lastDoc.replaceAll("\\s+\\*\\s*", "\n").trim(); } @Override public void exitDocComment(TypeScriptParser.DocCommentContext ctx) { } private String takeDoc() { if (lastDoc == null) { return ""; } String pdoc = lastDoc; lastDoc = null; return pdoc; } private <T extends JDocCommentable> T withDoc(T co) { String doc = takeDoc(); if (!doc.isEmpty()) { co.javadoc().add(doc); } return co; } @Override public void enterIndexer(TypeScriptParser.IndexerContext ctx) { pushSignature("get", JMod.PUBLIC, false).annotate(model.ref("jsn.ano.Indexer")); } @Override public void exitIndexer(TypeScriptParser.IndexerContext ctx) { popSignature().appendMethods(clz()); } private void implementNativeMethods() { for (JDefinedClass clz : IterableUtil.iter(defaultPackage.classes())) { LinkedList<JClass> implementationQ = new LinkedList<>(); IterableUtil.iter(clz._implements()).forEach(implementationQ::add); while (!implementationQ.isEmpty()) { JClass ifcType = implementationQ.removeFirst(); if (ifcType.binaryName().equals("jsn.core.JSObjectIfc")) { continue; } JDefinedClass ifc = model._getClass(ifcType.fullName()); if (ifc != null) { for (JMethod method : ifc.methods().toArray(new JMethod[0])) { if ((method.mods().getValue() & JMod.STATIC) == 0) { CodeModelUtil.addNativeImplementation(method, clz); } } IterableUtil.iter(ifc._implements()).forEach(implementationQ::add); } } } } }