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