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

org.nuiton.jaxx.compiler.script.ScriptManager Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * JAXX :: Compiler
 * %%
 * Copyright (C) 2008 - 2024 Code Lutin, Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

package org.nuiton.jaxx.compiler.script;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.nuiton.jaxx.compiler.CompilerException;
import org.nuiton.jaxx.compiler.JAXXCompiler;
import org.nuiton.jaxx.compiler.java.JavaArgument;
import org.nuiton.jaxx.compiler.java.JavaConstructor;
import org.nuiton.jaxx.compiler.java.JavaElementFactory;
import org.nuiton.jaxx.compiler.java.parser.JavaParser;
import org.nuiton.jaxx.compiler.java.parser.JavaParserTreeConstants;
import org.nuiton.jaxx.compiler.java.parser.SimpleNode;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptor;
import org.nuiton.jaxx.compiler.reflect.FieldDescriptor;
import org.nuiton.jaxx.compiler.reflect.MethodDescriptor;
import org.nuiton.jaxx.compiler.tags.TagManager;

import java.io.StringReader;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ScriptManager {

    private final JAXXCompiler compiler;

    public ScriptManager(JAXXCompiler compiler) {
        this.compiler = compiler;
    }

    /**
     * Strips unnecessary curly braces from around the script, generating a warning if they are found.
     *
     * @param script script to trim
     * @return the trimmed script
     */
    public String trimScript(String script) {
        return trimScript(script, true);
    }

    /**
     * Strips unnecessary curly braces from around the script, generating a warning if flag {@code warn} is on
     * and they are found.
     *
     * @param script script to trim
     * @param warn flag to warn if curly braces are found
     * @return the trimmed script
     */
    public String trimScript(String script, boolean warn) {
        script = script.trim();
        if (script.startsWith("{") && script.endsWith("}")) {
            if (warn) {
                compiler.reportWarning("curly braces are unnecessary for script '" + script + "'");
            }
            script = script.substring(1, script.length() - 1);
        }
        return script;
    }

    public void checkParse(String script) throws CompilerException {
        script = trimScript(script);
        JavaParser p = new JavaParser(new StringReader(script));
        while (!p.Line()) {
            // ???
        }
    }

    public String preprocessScript(String script) throws CompilerException {
        script = trimScript(script);
        StringBuilder result = new StringBuilder();
        JavaParser p = new JavaParser(new StringReader(script));
        while (!p.Line()) {
            SimpleNode node = p.popNode();
            if (node != null) {
                preprocessScriptNode(node, false);
                result.append(node.getText());
            }
        }
        return result.toString();
    }

    /**
     * Scans through a compound symbol (foo.bar.baz) to identify and compile
     * the JAXX class it refers to, if any.
     *
     * @param symbol symbol to scan
     */
    private void scanCompoundSymbol(String symbol) {
        String[] tokens = symbol.split("\\.");
        StringBuilder currentSymbol = new StringBuilder();
        for (String token : tokens) {
            if (currentSymbol.length() > 0) {
                currentSymbol.append('.');
            }
            currentSymbol.append(token.trim());

            String contextClass = TagManager.resolveClassName(
                    currentSymbol.toString(), compiler);
            if (contextClass != null) {
                compiler.addDependencyClass(contextClass);
            }
        }
    }

    private void preprocessScriptNode(SimpleNode node,
                                      boolean staticContext) throws CompilerException {
        // identify static methods and initializers -- we can't fire events statically
        if (node.getId() == JavaParserTreeConstants.JJTMETHODDECLARATION) {
            if (node.getParent().getChild(0).getText().contains("static")) {
                staticContext = true;
            }
        } else if (node.getId() == JavaParserTreeConstants.JJTINITIALIZER) {
            if (node.getText().trim().startsWith("static")) {
                staticContext = true;
            }
        }

        int count = node.jjtGetNumChildren();
        for (int i = 0; i < count; i++) {
            preprocessScriptNode(node.getChild(i), staticContext);
        }

        int id = node.getId();
        if (id == JavaParserTreeConstants.JJTNAME ||
                id == JavaParserTreeConstants.JJTCLASSORINTERFACETYPE) {
            scanCompoundSymbol(node.getText());
        }
        //tchemit 2011-02-02 I never understand this code
        //Doing at each assignment a ifre with a called "dummy value" is a none sense
        // Since JAXX can handle pretty well with javaBeans I remove this code
//        if (!staticContext) {
//            String lhs = null;
//            if (id == JavaParserTreeConstants.JJTASSIGNMENTEXPRESSION ||
//                id == JavaParserTreeConstants.JJTPOSTFIXEXPRESSION && node.jjtGetNumChildren() == 2) {
//                lhs = ((SimpleNode) node.jjtGetChild(0)).getText().trim();
//            } else if (id == JavaParserTreeConstants.JJTPREINCREMENTEXPRESSION ||
//                       id == JavaParserTreeConstants.JJTPREDECREMENTEXPRESSION) {
//                lhs = ((SimpleNode) node.jjtGetChild(0)).getText().trim();
//            }
//            if (lhs != null) {
//                FieldDescriptor[] fields = compiler.getScriptFields();
//                for (FieldDescriptor field : fields) {
//                    if (field.getName().equals(lhs)) {
//                        //lhs.substring(lhs.lastIndexOf(".") + 1);
//                        String prefix = compiler.getImportedType(JAXXUtil.class);
//                        node.firstToken.image = prefix + ".assignment(" + node.firstToken.image;
//                        String outputClassName = compiler.getImportedType(compiler.getOutputClassName());
//                        node.lastToken.image = node.lastToken.image + ", \"" + lhs + "\", " + outputClassName + ".this)";
//                    }
//                }
//            }
//        }
    }

    /**
     * Examines a Line to determine its real type.  As all tokens returned by the parser are Lines, and
     * they are just a tiny wrapper around the real node, this method strips off the wrapper layers to identify
     * the real type of a node.
     *
     * @param line line to scan
     * @return the line type
     */
    private int getLineType(SimpleNode line) {
        if (line.jjtGetNumChildren() == 1) {
            SimpleNode node = line.getChild(0);
            if (node.getId() == JavaParserTreeConstants.JJTBLOCKSTATEMENT) {
                if (node.jjtGetNumChildren() == 1) {
                    return node.getChild(0).getId();
                }
            } else if (node.getId() == JavaParserTreeConstants.JJTCLASSORINTERFACEBODYDECLARATION) {
                int id = node.getChild(0).getId();
                if (id == JavaParserTreeConstants.JJTMODIFIERS) {
                    return node.getChild(1).getId();
                }
                if (id == JavaParserTreeConstants.JJTINITIALIZER) {
                    return id;
                }
            }
            return node.getId();
        }
        return JavaParserTreeConstants.JJTLINE; // generic value implying that it's okay to put into the initializer block
    }

    private SimpleNode findExplicitConstructorInvocation(SimpleNode parent) {
        if (parent.getId() == JavaParserTreeConstants.JJTEXPLICITCONSTRUCTORINVOCATION) {
            return parent;
        }

        int count = parent.jjtGetNumChildren();
        for (int i = 0; i < count; i++) {
            SimpleNode result = findExplicitConstructorInvocation(parent.getChild(i));
            if (result != null) {
                return result;
            }
        }
        return null;
    }

    private void processConstructor(String modifiers, SimpleNode node) {
        assert node.getId() == JavaParserTreeConstants.JJTCONSTRUCTORDECLARATION : "expected node to be ConstructorDeclaration, found " + JavaParserTreeConstants.jjtNodeName[node.getId()] + " instead";
        assert node.getChild(0).getId() == JavaParserTreeConstants.JJTFORMALPARAMETERS : "expected node 0 to be FormalParameters, found " + JavaParserTreeConstants.jjtNodeName[node.getChild(1).getId()] + " instead";
        String code = "";
        if (node.getChild(0).jjtGetNumChildren() == 0) {
            compiler.reportError("The default no-argument constructor may not be redefined");
        } else {
            SimpleNode explicitConstructorInvocation = findExplicitConstructorInvocation(node);
            if (explicitConstructorInvocation == null || explicitConstructorInvocation.getText().trim().startsWith("super(")) {
                code = "$initialize();" + JAXXCompiler.getLineSeparator();
                if (explicitConstructorInvocation == null) {
                    node.getChild(1).firstToken.image = node.getChild(1).firstToken.image;
                } else {
                    explicitConstructorInvocation.lastToken.image += code;
                }
            }
        }

        compiler.appendBodyCode(modifiers + " " + node.getText().substring(0, node.getText().length() - 1) + code + "}");
    }

    private void processConstructor(SimpleNode mainNode) {

        compiler.registerInitializer(new RegisterConstructor(mainNode));
    }


    /** Logger */
    static private final Logger log = LogManager.getLogger(ScriptManager.class);

    private void scanScriptNode(SimpleNode node) throws CompilerException {
        int nodeType = getLineType(node);
        if (nodeType == JavaParserTreeConstants.JJTIMPORTDECLARATION) {
            // have to handle imports early so the preprocessing takes them into account
            String text = node.getChild(0).getText().trim();
            if (text.startsWith("import")) {
                text = text.substring("import".length()).trim();
            }
            if (text.endsWith(";")) {
                text = text.substring(0, text.length() - 1);
            }
            compiler.addImport(text);
        }

        preprocessScriptNode(node, false);

        if (nodeType == JavaParserTreeConstants.JJTIMPORTDECLARATION) {
            // do nothing, already handled above
        } else if (nodeType == JavaParserTreeConstants.JJTMETHODDECLARATION) {
            String returnType = null;
            String name = null;
            List parameterTypes = new ArrayList<>();
            //List parameterNames = new ArrayList();
            SimpleNode methodDeclaration = node.getChild(0).getChild(1);
            assert methodDeclaration.getId() == JavaParserTreeConstants.JJTMETHODDECLARATION;
            for (int i = 0; i < methodDeclaration.jjtGetNumChildren(); i++) {
                SimpleNode child = methodDeclaration.getChild(i);
                int type = child.getId();
                if (type == JavaParserTreeConstants.JJTRESULTTYPE) {
                    String rawReturnType = child.getText().trim();
                    returnType = TagManager.resolveClassName(rawReturnType, compiler);
                    // FIXME: this check fails for inner classes defined in this file
                    //if (returnType == null)
                    //    throw new CompilerException("could not find class '" + rawReturnType + "'");
                } else if (type == JavaParserTreeConstants.JJTMETHODDECLARATOR) {
                    name = child.firstToken.image.trim();
                    SimpleNode formalParameters = child.getChild(0);
                    assert formalParameters.getId() == JavaParserTreeConstants.JJTFORMALPARAMETERS;
                    for (int j = 0; j < formalParameters.jjtGetNumChildren(); j++) {
                        SimpleNode parameter = formalParameters.getChild(j);
                        String rawParameterType = parameter.getChild(1).getText().trim().replaceAll("\\.\\.\\.", "[]");
                        String parameterType = TagManager.resolveClassName(rawParameterType, compiler);
                        // FIXME: this check fails for inner classes defined in this file
                        //if (parameterType == null)
                        //    throw new CompilerException("could not find class '" + rawParameterType + "'");
                        parameterTypes.add(parameterType);
                        //parameterNames.add(parameter.getChild(2).getText().trim());
                    }
                }
            }
            compiler.appendBodyCode(node.getText());
            compiler.addScriptMethod(new MethodDescriptor(name, Modifier.PUBLIC, returnType, parameterTypes.toArray(new String[parameterTypes.size()]), compiler.getClassLoader()));
        } else if (nodeType == JavaParserTreeConstants.JJTCLASSORINTERFACEDECLARATION ||
                nodeType == JavaParserTreeConstants.JJTINITIALIZER) {
            String str = node.getText().trim();
            if (str.endsWith(";")) {
                str += ";";
            }
            compiler.appendBodyCode(str);
        } else if (nodeType == JavaParserTreeConstants.JJTCONSTRUCTORDECLARATION) {
            processConstructor(node);
//            processConstructor(node.getChild(0).getChild(0).getText(), node.getChild(0).getChild(1));
        } else if (nodeType == JavaParserTreeConstants.JJTLOCALVARIABLEDECLARATION || nodeType == JavaParserTreeConstants.JJTFIELDDECLARATION) {
            // the "local" variable declarations in this expression aren't actually local -- they are flagged local
            // just because there isn't an enclosing class scope visible to the parser.  "Real" local variable
            // declarations won't show up here, because they will be buried inside of methods.
            String text = node.getText().trim();
            if (!text.endsWith(";")) {
                text += ";";
            }
            String declaration = text;
            int equals = text.indexOf("=");
            if (equals != -1) {
                declaration = declaration.substring(0, equals);
            }
            declaration = declaration.trim();
            if (declaration.contains("<")) {

                // generic declaration detected

                if (log.isDebugEnabled()) {
                    log.debug("Found a declaration with generics : " + declaration);
                }

                int last = declaration.lastIndexOf('>');

                // copy everything before first '<'
                String newDeclaration = declaration.substring(0, declaration.indexOf('<'));
                if (last < declaration.length()) {

                    // copy everithing after last '>'
                    newDeclaration += declaration.substring(last + 1);
                }

                if (log.isDebugEnabled()) {
                    log.debug("==> declaration without generics : " + newDeclaration);
                }
                declaration = newDeclaration;
            }

            String[] declarationTokens = declaration.split("\\s");
            boolean isFinal = Arrays.asList(declarationTokens).contains("final");
            boolean isStatic = Arrays.asList(declarationTokens).contains("static");
            String name = declarationTokens[declarationTokens.length - 1];
            if (name.endsWith(";")) {
                name = name.substring(0, name.length() - 1).trim();
            }
            String className = declarationTokens[declarationTokens.length - 2];
            if (log.isDebugEnabled()) {
                log.debug("Found type : " + className + " : " +
                                  Arrays.toString(declarationTokens));
            }
            String type = TagManager.resolveClassName(className, compiler);
            if (type == null) {

                throw new CompilerException(
                        "Could not find type of " + className +
                                " for expression " + text);
            }
            compiler.addScriptField(new FieldDescriptor(name,
                                                        Modifier.PUBLIC,
                                                        type,
                                                        compiler.getClassLoader())
            );
            // TODO: determine the actual modifiers
            if (equals != -1 && !isFinal && !isStatic) {

                // declare the field in the class body, but wait to actually initialize it
                compiler.appendBodyCode(text.substring(0, equals).trim() + ";");
                String initializer = text.substring(equals + 1).trim();
                if (type.endsWith("[]")) {
                    initializer = "new " + type + " " + initializer;
                }
                final String finalInitializer = name + " = " + initializer;
                compiler.registerInitializer(() -> compiler.registerCompiledObject(new ScriptInitializer(
                        finalInitializer, compiler)));
            } else {
                compiler.appendBodyCode(text);
            }
            compiler.appendBodyCode("\n");
        } else {
            String text = node.getText().trim();
            if (text.length() > 0) {
                if (!text.endsWith(";")) {
                    text += ";";
                }
                compiler.appendInitializerCode(text);
            }
        }
    }

    public void registerScript(String script) throws CompilerException {
        JavaParser p = new JavaParser(new StringReader(script));
        while (!p.Line()) {
            SimpleNode node = p.popNode();
            if (node != null) {
                scanScriptNode(node);
            }
        }
    }

    class RegisterConstructor implements Runnable {

        final SimpleNode mainNode;

        public RegisterConstructor(SimpleNode mainNode) {
            this.mainNode = mainNode;
        }

        @Override
        public void run() {

            String className = mainNode.getChild(0).getChild(1).firstToken.image;
            String modifiers = mainNode.getChild(0).getChild(0).getText();

            SimpleNode node = mainNode.getChild(0).getChild(1);
            int nbArguments = node.getChild(0).jjtGetNumChildren();
            if (log.isDebugEnabled()) {
                log.debug("Constructor found with " + nbArguments + " arguments : " + node.getText());
            }
            assert node.getId() == JavaParserTreeConstants.JJTCONSTRUCTORDECLARATION : "expected node to be ConstructorDeclaration, found " + JavaParserTreeConstants.jjtNodeName[node.getId()] + " instead";
            assert node.getChild(0).getId() == JavaParserTreeConstants.JJTFORMALPARAMETERS : "expected node 0 to be FormalParameters, found " + JavaParserTreeConstants.jjtNodeName[node.getChild(1).getId()] + " instead";
            SimpleNode params = node.getChild(0);
            StringBuilder bodyC = new StringBuilder();
            for (int i = 1; i < node.jjtGetNumChildren(); i++) {
                bodyC.append(node.getChild(i).getText());
            }
            String bodyContent = bodyC.toString().trim();
            JavaArgument[] arguments = new JavaArgument[nbArguments];
            for (int i = 0; i < nbArguments; i++) {
                SimpleNode param = params.getChild(i);
                String paramType = param.getChild(0).firstToken.image;
                ClassDescriptor type = TagManager.resolveClass(paramType, compiler);
                String paramName = param.getChild(2).firstToken.image;
                if (log.isDebugEnabled()) {
                    log.debug("Parameter n°" + i + " --> [" + type + " : " + paramName + "]");
                }
                JavaArgument arg = JavaElementFactory.newArgument(type.getName(), paramName);
                arguments[i] = arg;
            }
            String[] modifierSplit = modifiers.trim().split("\\s");
            if (log.isDebugEnabled()) {
                log.debug("Modifiers = " + Arrays.toString(modifierSplit));
            }
            int finalModifiers = 0;
            for (String mod : modifierSplit) {
                mod = mod.trim();
                if ("public".equals(mod)) {
                    finalModifiers = Modifier.PUBLIC;
                    break;
                }
                if ("protected".equals(mod)) {
                    finalModifiers = Modifier.PROTECTED;
                    break;
                }
                if ("private".equals(mod)) {
                    finalModifiers = Modifier.PRIVATE;
                    break;
                }
            }

            if (!bodyContent.endsWith("$initialize();")) {
                bodyContent += JAXXCompiler.getLineSeparator() + "    $initialize();";
            }
            if (log.isDebugEnabled()) {
                log.debug("Final modifier to use : " + Modifier.toString(finalModifiers));
                log.debug("Constructor body :\n" + bodyContent);
            }
            JavaConstructor constructorMethod = JavaElementFactory.newConstructor(
                    finalModifiers,
                    className,
                    bodyContent,
                    arguments
            );

            compiler.getJavaFile().addConstructor(constructorMethod);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy