bam.web.dtc.compiler.CodeModelGenerator.java Source code

Java tutorial

Introduction

Here is the source code for bam.web.dtc.compiler.CodeModelGenerator.java

Source

/*
 * 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);
                }
            }

        }
    }

}