org.nuiton.jaxx.compiler.JAXXCompiler Maven / Gradle / Ivy
/*
* #%L
* JAXX :: Compiler
* %%
* Copyright (C) 2008 - 2020 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;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nuiton.eugene.GeneratorUtil;
import org.nuiton.eugene.java.JavaGeneratorUtil;
import org.nuiton.jaxx.compiler.binding.DataBindingHelper;
import org.nuiton.jaxx.compiler.css.StylesheetHelper;
import org.nuiton.jaxx.compiler.finalizers.I18nKeysFileModel;
import org.nuiton.jaxx.compiler.finalizers.JAXXCompilerFinalizer;
import org.nuiton.jaxx.compiler.java.JavaElementFactory;
import org.nuiton.jaxx.compiler.java.JavaField;
import org.nuiton.jaxx.compiler.java.JavaFile;
import org.nuiton.jaxx.compiler.java.JavaFileGenerator;
import org.nuiton.jaxx.compiler.java.JavaMethod;
import org.nuiton.jaxx.compiler.java.parser.ParseException;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptor;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptorHelper;
import org.nuiton.jaxx.compiler.reflect.FieldDescriptor;
import org.nuiton.jaxx.compiler.reflect.MethodDescriptor;
import org.nuiton.jaxx.compiler.reflect.resolvers.ClassDescriptorResolverFromJaxxFile;
import org.nuiton.jaxx.compiler.script.ScriptManager;
import org.nuiton.jaxx.compiler.tags.DefaultObjectHandler;
import org.nuiton.jaxx.compiler.tags.TagHandler;
import org.nuiton.jaxx.compiler.tags.TagManager;
import org.nuiton.jaxx.runtime.ComponentDescriptor;
import org.nuiton.jaxx.runtime.JAXXObject;
import org.nuiton.jaxx.runtime.JAXXObjectDescriptor;
import org.nuiton.jaxx.runtime.css.Rule;
import org.nuiton.jaxx.runtime.css.Stylesheet;
import org.nuiton.jaxx.runtime.i18n.I18nLabelsBuilder;
import org.nuiton.jaxx.runtime.spi.UIHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.XMLFilterImpl;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXSource;
import java.awt.BorderLayout;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
/**
* Compiles a given {@link #jaxxFile} into a {@link #javaFile}.
*
* @author Tony Chemit - [email protected]
* @since 1.0.0
*/
public class JAXXCompiler {
public static final String JAXX_NAMESPACE =
"http://www.jaxxframework.org/";
public static final String JAXX_INTERNAL_NAMESPACE =
"http://www.jaxxframework.org/internal";
/** Maximum length of an inline creation method. */
public static final int INLINE_THRESHOLD = 300;
public static final String BORDER_LAYOUT_PREFIX = BorderLayout.class.getSimpleName() + ".";
/** Logger */
protected static final Logger log = LogManager.getLogger(JAXXCompiler.class);
/** Line separator cached value */
protected static final String lineSeparator =
System.getProperty("line.separator", "\n");
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* True to throw exceptions when we encounter unresolvable classes,
* false to ignore.
*
* This is currently set to false until JAXX has full support for
* inner classes (including enumerations), because currently they don't
* always resolve (but will generally run without error anyway).
*/
public static boolean STRICT_CHECKS;
/*------------------------------------------------------------------------*/
/*-- compiler fields -----------------------------------------------------*/
/*------------------------------------------------------------------------*/
/** The unique object handler used in first pass */
protected final DefaultObjectHandler firstPassClassTagHandler;
/** Contains strings of the form "javax.swing." */
protected final Set importedPackages;
/** Contains strings of the form "javax.swing.Timer" */
protected final Set importedClasses;
/** Keeps track of open components (components still having children added). */
protected final Stack openComponents;
/** to generate ids */
protected final IDHelper idHelper;
/** Binding Util */
protected final DataBindingHelper bindingHelper;
/** table of symbols for this compiler */
protected final SymbolTable symbolTable;
/**
* Base directory used for path resolution (normally the directory in
* which the .jaxx file resides).
*/
protected final File baseDir;
/** jaxx file being compiled. */
protected final File src;
/** Name of class being compiled. */
protected final String outputClassName;
/** script manager */
protected final ScriptManager scriptManager;
/** Contains all attributes defined inline on class tags. */
protected final List inlineStyles;
/**
* Maps objects (expressed in Java code) to event listener classes
* (e.g. MouseListener) to Lists of EventHandlers. The final list
* contains all event handlers of a particular type attached to a
* particular object (again, as represented by a Java expression).
*/
protected final Map>> eventHandlers;
/** Map of event handler method names used in compiler */
protected final Map eventHandlerMethodNames;
/**
* A list of Runnable which will be run after the first compilation pass.
*
* This is primarily used to trigger the creation of CompiledObjects,
* which cannot be created during the first pass and must be created
* in document order.
*/
protected final List initializers;
/** Extra code to be added to the instance initializer. */
protected final StringBuilder initializer;
/** Extra code to be added at the end of the instance initializer. */
protected final StringBuilder lateInitializer;
/** Extra code to be added at the end of the instance initializer. */
protected final StringBuilder actionsInitializer;
/** Extra code to be added to the class body. */
protected final StringBuilder bodyCode;
/** Used for error reporting purposes, so we can report the right line number. */
protected final Stack tagsBeingCompiled;
/** Used for error reporting purposes, so we can report the right source file. */
protected final Stack sourceFiles;
/**
* Maps object ID strings to the objects themselves.
*
* These are created during the second compilation pass.
*/
protected final Map objects;
/**
* Maps objects to their ID strings.
*
* These are created during the second compilation pass.
*/
protected final Map ids;
/**
* engine which references this compiler
* (can be null if compiler is standalone)
*/
protected final JAXXEngine engine;
protected final CompilerConfiguration defaultConfiguration =
new DefaultCompilerConfiguration();
protected final JAXXCompilerFile jaxxFile;
/** flag to detect if an error occurs while compiling jaxx file */
protected boolean failed;
/** Object corresponding to the root tag in the document. */
protected CompiledObject root;
/** Parsed XML of src file. */
protected Document document;
/**
* Combination of all stylesheets registered using
* {@link #registerStylesheet(Stylesheet)}.
*/
protected Stylesheet stylesheet;
/**
* ClassLoader which searches the user-specified class path in
* addition to the normal class path
*/
protected ClassLoader classLoader;
/** true if a main() method has been declared in a script */
protected boolean mainDeclared;
/** the file to be generated */
protected JavaFile javaFile;
/** default decorator to use if none specified */
protected CompiledObjectDecorator defaultDecorator;
/**
* Flag to know if jaxx file ident css was found, otherwise add it
* at the end of the second compile pass.
*
* @since 2.0.2
*/
protected boolean identCssFound;
/**
* A flag to know if SwingUtil must be imported.
*
* @since 2.4
* @deprecated since 2.4.1, will be removed in version 3.0, it is not a good
* idea to do special treatment for a particular class, to use SwingUtil,
* do like for other class : import it!
*/
@Deprecated
protected boolean needSwingUtil;
protected String uiHandler;
/**
* extra interfaces which can by passed to root object via the
* 'implements' attribute
*/
private String[] extraInterfaces;
/** a flag to generate a abstract class */
private boolean abstractClass;
/** the possible generic type of the class */
private String genericType;
/** the possible generic type of the super class */
private String superGenericType;
// private String i18nFormat;
private I18nLabelsBuilder i18nLabelsBuilder;
/** Keeps track of bean scopes. */
private final Stack beanScopeComponents;
public JAXXCompiler() {
this(null, null, null);
}
/*------------------------------------------------------------------------*/
/*-- Initializer methods -------------------------------------------------*/
/*------------------------------------------------------------------------*/
/**
* Creates a new JAXXCompiler.
*
* @param engine engine which use the compiler (could be null if not attach to any engine)
* @param jaxxFile the file to compile
* @param defaultImports list of default imports to add to java files
*/
public JAXXCompiler(JAXXEngine engine,
JAXXCompilerFile jaxxFile,
List defaultImports) {
this.engine = engine;
this.jaxxFile = jaxxFile;
ids = new LinkedHashMap<>();
objects = new LinkedHashMap<>();
bodyCode = new StringBuilder();
lateInitializer = new StringBuilder();
actionsInitializer = new StringBuilder();
initializer = new StringBuilder();
tagsBeingCompiled = new Stack<>();
initializers = new ArrayList<>();
eventHandlerMethodNames = new HashMap<>();
eventHandlers =
new HashMap<>();
inlineStyles = new ArrayList<>();
scriptManager = new ScriptManager(this);
symbolTable = new SymbolTable();
openComponents = new Stack<>();
beanScopeComponents = new Stack<>();
importedPackages = new HashSet<>();
importedClasses = new HashSet<>();
sourceFiles = new Stack<>();
if (jaxxFile == null) {
src = null;
baseDir = null;
outputClassName = null;
} else {
src = jaxxFile.getJaxxFile();
baseDir = src.getParentFile();
outputClassName = jaxxFile.getClassName();
sourceFiles.push(src);
addImport(outputClassName.substring(
0, outputClassName.lastIndexOf(".") + 1) + "*");
}
firstPassClassTagHandler = new DefaultObjectHandler(
ClassDescriptorHelper.getClassDescriptor(Object.class)
);
bindingHelper = new DataBindingHelper(this);
if (defaultImports != null) {
for (String defaultImport : defaultImports) {
addDefaultImport(defaultImport);
}
}
if (engine != null) {
CompilerConfiguration configuration = engine.getConfiguration();
// add extra imports from configuration
if (configuration.getExtraImports() != null) {
for (String extraImport : configuration.getExtraImports()) {
addImport(extraImport);
}
}
defaultDecorator = engine.getDecorator(
configuration.getDefaultDecoratorClass());
if (defaultDecorator == null) {
throw new IllegalArgumentException(
"could not find default decorator : " +
configuration.getDefaultDecoratorClass()
);
}
idHelper = new IDHelper(configuration.isOptimize());
} else {
idHelper = new IDHelper(false);
}
}
/**
* Returns the system line separator string.
*
* @return the string used to separate lines
*/
public static String getLineSeparator() {
return lineSeparator;
}
/*------------------------------------------------------------------------*/
/*-- Compile methods -----------------------------------------------------*/
/*------------------------------------------------------------------------*/
public static String getCanonicalName(Class> clazz) {
if (clazz.isArray()) {
String canonicalName = getCanonicalName(clazz.getComponentType());
if (canonicalName != null) {
return canonicalName + "[]";
}
return null;
}
return clazz.getName().replace('$', '.');
}
public static String getCanonicalName(ClassDescriptor clazz) {
if (clazz.isArray()) {
String canonicalName = getCanonicalName(clazz.getComponentType());
if (canonicalName != null) {
return canonicalName + "[]";
}
return null;
}
return clazz.getName().replace('$', '.');
}
public static String getCanonicalName(CompiledObject compiled) {
ClassDescriptor clazz = compiled.getObjectClass();
if (clazz.isArray()) {
String canonicalName = getCanonicalName(clazz.getComponentType());
if (canonicalName != null) {
if (compiled.getGenericTypesLength() > 0) {
canonicalName += compiled.getGenericTypes();
}
return canonicalName + "[]";
}
return null;
}
String canonicalName = clazz.getName().replace('$', '.');
if (compiled.getGenericTypesLength() > 0) {
canonicalName += compiled.getGenericTypes();
}
return canonicalName;
}
/**
* Escapes a string using standard Java escape sequences, generally in
* preparation to including it in a string literal in a compiled Java file.
*
* @param raw the raw string to be escape
* @return a string in which all 'dangerous' characters have been replaced
* by equivalent Java escape sequences
*/
public static String escapeJavaString(String raw) {
StringBuilder out = new StringBuilder(raw);
for (int i = 0; i < out.length(); i++) {
char c = out.charAt(i);
if (c == '\\' || c == '"') {
out.insert(i, '\\');
i++;
} else if (c == '\n') {
out.replace(i, i + 1, "\\n");
i++;
} else if (c == '\r') {
out.replace(i, i + 1, "\\r");
i++;
} else if (c < 32 || c > 127) {
String value = Integer.toString((int) c, 16);
while (value.length() < 4) {
value = "0" + value;
}
out.replace(i, i + 1, "\\u" + value);
i += 5;
}
}
return out.toString();
}
/*------------------------------------------------------------------------*/
/*-- CompiledObject methods ----------------------------------------------*/
/*------------------------------------------------------------------------*/
public static File URLtoFile(URL url) {
return URLtoFile(url.toString());
}
public static File URLtoFile(String urlString) {
if (!urlString.startsWith("file:")) {
throw new IllegalArgumentException("url must start with 'file:'");
}
urlString = urlString.substring("file:".length());
if (urlString.startsWith("/") &&
System.getProperty("os.name").startsWith("Windows")) {
urlString = urlString.substring(1);
}
try {
return new File(URLDecoder.decode(
urlString.replace('/', File.separatorChar), "utf-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static SAXParser getSAXParser() {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser parser;
parser = factory.newSAXParser();
return parser;
} catch (SAXException | ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
public static Document parseDocument(InputStream in) throws IOException, SAXException {
try {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.setErrorListener(new ErrorListener() {
@Override
public void warning(
TransformerException ex) throws TransformerException {
throw ex;
}
@Override
public void error(
TransformerException ex) throws TransformerException {
throw ex;
}
@Override
public void fatalError(
TransformerException ex) throws TransformerException {
throw ex;
}
});
DOMResult result = new DOMResult();
transformer.transform(
new SAXSource(new XMLFilterImpl(getSAXParser().getXMLReader()) {
Locator locator;
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
@Override
public void startElement(String uri,
String localName,
String qName,
Attributes attributes) throws SAXException {
AttributesImpl resultAttributes = new AttributesImpl(attributes);
resultAttributes.addAttribute(
JAXX_INTERNAL_NAMESPACE,
"line",
"internal:line",
"CDATA",
String.valueOf(locator.getLineNumber())
);
getContentHandler().startElement(uri,
localName,
qName,
resultAttributes
);
}
}, new InputSource(in)), result);
return (Document) result.getNode();
} catch (TransformerConfigurationException e) {
throw new RuntimeException(e);
} catch (TransformerException e) {
Throwable ex = e;
while (ex.getCause() != null) {
ex = ex.getCause();
}
if (ex instanceof IOException) {
throw (IOException) ex;
}
if (ex instanceof SAXException) {
throw (SAXException) ex;
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new RuntimeException(ex);
}
}
public void runInitializers() {
for (Runnable runnable : initializers) {
if (log.isDebugEnabled()) {
log.debug(runnable);
}
try {
runnable.run();
} catch (Exception e) {
//TC - 20081018 report error and quit
log.error("Can't run on initializer of file: " + jaxxFile, e);
reportError(e.getMessage());
return;
}
}
initializers.clear();
}
/**
* Registers a Runnable
which will be executed after the first
* compilation pass is complete.
*
* @param r runnable to register
*/
public void registerInitializer(Runnable r) {
initializers.add(r);
}
public void compileFirstPass(final Element tag) throws IOException {
tagsBeingCompiled.push(tag);
String namespace = tag.getNamespaceURI();
String fullClassName;
String localName = tag.getLocalName();
boolean namespacePrefix = tag.getPrefix() != null;
// resolve class tags into fully-qualified class name
if (namespace != null && namespace.endsWith("*")) {
String packageName = namespace.substring(0, namespace.length() - 1);
if (localName.startsWith(packageName)) {
// class name is fully-qualified already
fullClassName = TagManager.resolveClassName(localName, this);
} else {
// namespace not included in class name, probably need the
// namespace to resolve
fullClassName = TagManager.resolveClassName(
packageName + localName, this);
if (fullClassName == null && !namespacePrefix) {
// it was just a default namespace, try again without
// using the namespace
fullClassName = TagManager.resolveClassName(localName, this);
}
}
} else {
fullClassName = TagManager.resolveClassName(localName, this);
}
if (fullClassName != null) {
// we are definitely dealing with a class tag
addDependencyClass(fullClassName);
namespace = fullClassName.substring(
0, fullClassName.lastIndexOf(".") + 1) + "*";
if (symbolTable.getSuperclassName() == null) {
symbolTable.setSuperclassName(fullClassName);
}
String id = tag.getAttribute(DefaultObjectHandler.ID_ATTRIBUTE);
MethodDescriptor methodDescriptor;
if (!id.isEmpty()) {
symbolTable.getClassTagIds().put(id, fullClassName);
if (tag.getAttributeNode(DefaultObjectHandler.JAVA_BEAN_ATTRIBUTE) != null) {
// add java bean support for this property
String capitalizeName = StringUtils.capitalize(id);
// add method
methodDescriptor = new MethodDescriptor(
"get" + capitalizeName,
Modifier.PUBLIC,
fullClassName,
EMPTY_STRING_ARRAY,
getClassLoader()
);
symbolTable.getScriptMethods().add(methodDescriptor);
if (Boolean.class.getName().equals(fullClassName)) {
methodDescriptor = new MethodDescriptor(
"is" + capitalizeName,
Modifier.PUBLIC,
fullClassName,
EMPTY_STRING_ARRAY,
getClassLoader()
);
symbolTable.getScriptMethods().add(methodDescriptor);
}
methodDescriptor = new MethodDescriptor(
"set" + capitalizeName,
Modifier.PUBLIC,
JAXXCompilerFinalizer.TYPE_VOID,
new String[]{fullClassName},
getClassLoader()
);
symbolTable.getScriptMethods().add(methodDescriptor);
} else {
// add simple get support
String capitalizeName = StringUtils.capitalize(id);
// add method
methodDescriptor = new MethodDescriptor(
"get" + capitalizeName,
Modifier.PUBLIC,
fullClassName,
EMPTY_STRING_ARRAY,
getClassLoader()
);
symbolTable.getScriptMethods().add(methodDescriptor);
}
}
String interfacesStr =
tag.getAttribute(DefaultObjectHandler.IMPLEMENTS_ATTRIBUTE);
if (!interfacesStr.isEmpty()) {
// there is some interfaces to deal with
try {
String[] interfaces =
JavaGeneratorUtil.splitFqnList(interfacesStr, ',');
if (log.isDebugEnabled()) {
log.debug("detect interfaces : " +
Arrays.toString(interfaces));
}
symbolTable.setInterfaces(interfaces);
} catch (Exception e) {
// the syntax is not valid (missed one >)
throw new CompilerException(
"Syntax error of interfaces " +
interfacesStr);
}
// StringTokenizer stk = new StringTokenizer(interfacesStr, ",");
// List tmp = new ArrayList();
// while (stk.hasMoreTokens()) {
// String c = stk.nextToken();
// if (c.contains("<") && !c.contains(">")) {
// // deal with a generic interface with more than one
// // parameter
// boolean done = false;
// while (stk.hasMoreTokens()) {
//
// String next = stk.nextToken();
// if (!next.contains(">")) {
// // still a parameter of the generic type
// continue;
// }
// // ok find the ending token
// done = true;
// break;
// }
// if (!done) {
// // the syntax is not valid (missed one >)
// throw new CompilerException(
// "Syntax error of interfaces " +
// interfacesStr);
// }
// //c = c.substring(0, c.indexOf("<"));
// }
// tmp.add(c.trim());
// }
// String[] interfaces = tmp.toArray(new String[tmp.size()]);
// if (log.isDebugEnabled()) {
// log.debug("detect interfaces : " +
// Arrays.toString(interfaces));
// }
// symbolTable.setInterfaces(interfaces);
}
}
// during the first pass, we can't create ClassDescriptors for JAXX
// files because they may not have been processed yet (and we can't
// wait until they have been processed because of circular
// dependencies).
// So we don't do any processing during the first pass which requires
// having a ClassDescriptor; here we determine whether we have a class
// tag or not (class tag namespaces end in "*") and use a generic
// handler if so.
// The real handler is used during the second pass.
TagHandler handler = namespace != null && namespace.endsWith("*") ?
firstPassClassTagHandler :
TagManager.getTagHandler(tag.getNamespaceURI(),
localName,
namespacePrefix,
this
);
if (!firstPassClassTagHandler.equals(handler) &&
handler instanceof DefaultObjectHandler) {
fullClassName =
((DefaultObjectHandler) handler).getBeanClass().getName();
handler = firstPassClassTagHandler;
}
if (firstPassClassTagHandler.equals(handler)) {
final String finalClassName = fullClassName;
// register an initializer which will create the
// CompiledObject after pass 1
registerInitializer(() -> {
DefaultObjectHandler handler1 =
(DefaultObjectHandler) TagManager.getTagHandler(
null,
finalClassName,
JAXXCompiler.this
);
if (handler1 == null) {
throw new CompilerException(
"Internal error: missing TagHandler for '" +
finalClassName + "'");
}
handler1.registerCompiledObject(tag, JAXXCompiler.this);
});
}
if (handler == null) {
reportError("Could not find a Java class corresponding to: <" +
tag.getTagName() + ">");
failed = true;
} else {
try {
handler.compileFirstPass(tag, this);
} catch (CompilerException e) {
reportError(e);
}
}
Element finished = tagsBeingCompiled.pop();
if (finished != tag) {
throw new IllegalStateException(
"internal error: just finished compiling " + tag +
", but top of tagsBeingCompiled stack is " + finished);
}
}
public void compileSecondPass(Element tag) throws IOException {
tagsBeingCompiled.push(tag);
TagHandler handler = TagManager.getTagHandler(
tag.getNamespaceURI(),
tag.getLocalName(),
tag.getPrefix() != null,
this
);
if (handler == null) {
reportError("Could not find a Java class corresponding to: <" +
tag.getTagName() + ">");
failed = true;
} else {
handler.compileSecondPass(tag, this);
}
Element finished = tagsBeingCompiled.pop();
if (!tag.equals(finished)) {
throw new RuntimeException(
"internal error: just finished compiling " + tag +
", but top of tagsBeingCompiled stack is " + finished);
}
}
public void compileFirstPass() throws IOException {
try (InputStream in = new FileInputStream(src)) {
document = parseDocument(in);
Element documentElement = document.getDocumentElement();
boolean addAutoHandlerUI =
getEngine().getConfiguration().isAddAutoHandlerUI();
String uiHandlerFullClassName;
if (addAutoHandlerUI) {
// try to find the
uiHandlerFullClassName = getOutputClassName() + "Handler";
ClassDescriptor uiHandlerClass = null;
try {
uiHandlerClass = ClassDescriptorHelper.getClassDescriptor(uiHandlerFullClassName, getClassLoader());
if (uiHandlerClass != null && !ClassDescriptorHelper.isAssignableFrom(uiHandlerClass, UIHandler.class)) {
String isAbstractValue = documentElement.getAttribute("abstract");
boolean isAbstract = "true".equals(isAbstractValue);
if (!isAbstract) {
reportWarning(
"Found a handler " + uiHandlerFullClassName
+ " which does not implements " + UIHandler.class.getName());
}
uiHandlerClass = null;
}
} catch (ClassNotFoundException e) {
// not found
}
if (uiHandlerClass == null) {
uiHandlerFullClassName = null;
}
} else {
uiHandlerFullClassName = documentElement.getAttribute(
DefaultObjectHandler.HANDLER_ATTRIBUTE);
if (StringUtils.isNotBlank(uiHandlerFullClassName)) {
// get his class descriptor
ClassDescriptor uiHandlerClass = null;
try {
uiHandlerClass = ClassDescriptorHelper.getClassDescriptor(uiHandlerFullClassName, getClassLoader());
// check if implements UIHandler
if (uiHandlerClass != null && !ClassDescriptorHelper.isAssignableFrom(uiHandlerClass, UIHandler.class)) {
reportError(
"Found a handler " + uiHandlerFullClassName
+ " which does not implements " + UIHandler.class.getName());
uiHandlerClass = null;
}
} catch (ClassNotFoundException e) {
reportError("Could not find type " + uiHandlerFullClassName);
}
if (uiHandlerClass == null) {
uiHandlerFullClassName = null;
}
}
}
if (uiHandlerFullClassName != null) {
setUiHandler(uiHandlerFullClassName);
}
compileFirstPass(documentElement);
} catch (SAXParseException e) {
reportError(e.getLineNumber(), "Invalid XML: " + e.getMessage());
} catch (SAXException e) {
reportError(null, "Error parsing XML document: " + e);
}
}
/*------------------------------------------------------------------------*/
/*-- DataBinding methods -------------------------------------------------*/
/*------------------------------------------------------------------------*/
public void compileSecondPass() throws IOException {
if (!tagsBeingCompiled.isEmpty()) {
throw new RuntimeException(
"Internal error: starting pass two, but tagsBeingCompiled" +
" is not empty: " + tagsBeingCompiled);
}
compileSecondPass(document.getDocumentElement());
if (isUseHandler()) {
String handler = getUIHandlerWithGeneric();
boolean isAbstract = isAbstractClass();
boolean addField = !isAbstract;
boolean superclassIsJAXXObject;
superclassIsJAXXObject = isSuperClassAware(JAXXObject.class);
CompiledObject rootObject = getRootObject();
ClassDescriptor rootObjectClass = rootObject.getObjectClass();
if (superclassIsJAXXObject) {
// test if there is a super handler
try {
rootObjectClass.getDeclaredFieldDescriptor("handler");
addField = false;
} catch (NoSuchFieldException e) {
// no field handler found
}
}
JavaField field = JavaElementFactory.newField(
Modifier.PROTECTED,
handler,
DefaultObjectHandler.HANDLER_ATTRIBUTE,
!addField,
null);
if (addField) {
javaFile.addField(field, false);
} else if (getCompiledObject("handler") == null) {
if (isAbstract) {
String type = field.getType();
int indexOf = type.indexOf("<");
if (indexOf > -1) {
type = type.substring(0, indexOf);
}
javaFile.addAbstractGetterMethod(field.getName(),
Modifier.PUBLIC,
type,
superclassIsJAXXObject);
} else {
// override getter
javaFile.addImport(field.getType());
javaFile.addOverrideGetterMethod(field.getName(),
Modifier.PUBLIC,
GeneratorUtil.getSimpleName(field.getType()),
true);
}
}
}
// We could have a handler in a parent if so and if there is a createHandler method
// then override it (to never use this code)
ClassDescriptor objectClass = getRootObject().getObjectClass();
boolean foundMethod = false;
while (!foundMethod && objectClass != null) {
try {
objectClass.getDeclaredMethodDescriptor("createHandler");
foundMethod = true;
} catch (NoSuchMethodException e) {
objectClass = objectClass.getSuperclass();
}
}
if (foundMethod) {
JavaMethod createHandler = JavaElementFactory.newMethod(
Modifier.PROTECTED,
JAXXCompilerFinalizer.TYPE_VOID,
"createHandler",
"",
true);
javaFile.addMethod(createHandler);
}
}
public void openComponent(CompiledObject component) throws CompilerException {
openComponent(component, null);
}
public void openComponent(CompiledObject component,
String constraints) throws CompilerException {
if (constraints != null) {
// try to add the constraints class in imports
if (constraints.startsWith(BORDER_LAYOUT_PREFIX)) {
addImport(BorderLayout.class.getName());
}
}
CompiledObject parent = getOpenComponent();
openInvisibleComponent(component);
if (parent != null && (!component.isOverride() || component.isAddToContainer())) {
parent.addChild(component, constraints, this);
}
component.getBeanScope().ifPresent(this::registerBeanScope);
}
/*------------------------------------------------------------------------*/
/*-- Script methods ------------------------------------------------------*/
/*------------------------------------------------------------------------*/
public void openInvisibleComponent(CompiledObject component) {
if (!ids.containsKey(component)) {
registerCompiledObject(component);
}
openComponents.push(component);
}
public CompiledObject getOpenComponent() {
if (openComponents.isEmpty()) {
return null;
}
return openComponents.peek();
}
public void closeComponent(CompiledObject component) {
if (component == null) {
throw new NullPointerException("can not close a null component");
}
if (!component.equals(openComponents.pop())) {
throw new IllegalArgumentException(
"can only close the topmost open object");
}
if (component.getBeanScope().isPresent()) {
removeBeanScope();
}
}
public void registerCompiledObject(CompiledObject object) {
// assert engine.symbolTables.values().contains(symbolTable) :
// "attempting to register CompiledObject before pass 1 is complete";
if (root == null) {
root = object;
}
String id = object.getId();
if (ids.containsKey(object)) {
reportError("object '" + object +
"' is already registered with id '" +
ids.get(object) + "', cannot re-register as '" + id +
"'"
);
}
if (objects.containsKey(id) && !(objects.get(id) instanceof Element)) {
reportError("id '" + id + "' is already registered to component " + objects.get(id));
}
objects.put(id, object);
ids.put(object, id);
if (object.getDecorator() == null) {
// use default decorator
object.setDecorator(defaultDecorator);
}
}
public CompiledObject getCompiledObject(String id) {
runInitializers();
// assert engine.symbolTables.values().contains(symbolTable) :
// "attempting to retrieve CompiledObject before pass 1 is complete";
return objects.get(id);
}
/*------------------------------------------------------------------------*/
/*-- StyleSheet methods --------------------------------------------------*/
/*------------------------------------------------------------------------*/
public boolean inlineCreation(CompiledObject object) {
return object.getId().startsWith("$") &&
object.getInitializationCode(this).length() < INLINE_THRESHOLD;
}
public void checkOverride(CompiledObject object) throws CompilerException {
String fieldName = object.getId();
if (fieldName.startsWith("$")) {
return;
}
ClassDescriptor ancestor = root.getObjectClass();
if (object.getObjectClass().equals(ancestor)) {
return;
}
while (ancestor != null) {
try {
FieldDescriptor f =
ancestor.getDeclaredFieldDescriptor(fieldName);
if (!f.getType().isAssignableFrom(object.getObjectClass())) {
reportError(
"attempting to redefine superclass member '" +
fieldName + "' as incompatible type (was " +
f.getType() + ", redefined as " +
object.getObjectClass() + ")"
);
}
object.setOverride(true);
object.setOverrideType(f.getType());
if (!getCanonicalName(f.getType()).equals(getCanonicalName(object))) {
// types are not the same
String simpleType =
getImportedType(getCanonicalName(object));
if (log.isDebugEnabled()) {
log.debug("Simple type for " + object.getId()
+ " : " + getCanonicalName(object) +
" against : " +
getCanonicalName(f.getType()));
}
object.setSimpleType(simpleType);
}
break;
} catch (NoSuchFieldException e) {
if (log.isDebugEnabled()) {
log.debug(">>>>> could not find declared field [" + fieldName + "] in " + ancestor.getName());
}
ancestor = ancestor.getSuperclass();
}
}
}
// public void registerStyleSheetFile(File styleFile, boolean warnAutoCssImport) {
// if (!identCssFound) {
//
// // detects if the given css file is ident to jaxx file
// File identCssFile = jaxxFile.getCssFile();
//
// if (styleFile.equals(identCssFile) && identCssFile.exists()) {
// // ok found ident css file
// identCssFound = true;
// if (warnAutoCssImport) {
// reportWarning("The css file " + styleFile + " can be automatically imported since it is named as his jaxx file.");
// }
// }
//
// }
// String content = loadFile(styleFile);
// getSourceFiles().push(styleFile);
// try {
// Stylesheet style = StylesheetHelper.processStylesheet(content);
// registerStylesheet(style);
// } catch (CompilerException e) {
// String message = "Css file content is not valid :" + styleFile;
// if (e instanceof org.nuiton.jaxx.compiler.css.parser.ParseException) {
// org.nuiton.jaxx.compiler.css.parser.ParseException parseException = (org.nuiton.jaxx.compiler.css.parser.ParseException) e;
//
// message += " (line: " + parseException.getLine() + " - col:" + parseException.getColumn() + ") ";
// }
// reportError(message, e);
// } finally {
//
// // whatever could be result, must pop this source file
// getSourceFiles().pop();
// }
// }
public DataBindingHelper getBindingHelper() {
return bindingHelper;
}
public void registerEventHandler(EventHandler handler) {
String objectCode = handler.getObjectCode();
Map> listeners = eventHandlers.computeIfAbsent(objectCode, k -> new HashMap<>());
ClassDescriptor listenerClass = handler.getListenerClass();
List handlerList = listeners.computeIfAbsent(listenerClass, k -> new ArrayList<>());
handlerList.add(handler);
}
public String getEventHandlerMethodName(EventHandler handler) {
String result = eventHandlerMethodNames.get(handler);
if (result == null) {
if (getConfiguration().isOptimize()) {
result = "$ev" + eventHandlerMethodNames.size();
} else {
//TC-20090309 must get the goal property from the event id
// to make possible inheritance
String id = handler.getEventId().substring(
0, handler.getEventId().indexOf("."));
result = "do" + StringUtils.capitalize(handler.getListenerMethod().getName()) + "__on__" + id;
//TC-20091105 : check the method name is available
// using css can have multi methods with result name (see priority)
if (eventHandlerMethodNames.containsValue(result)) {
int index = 0;
String result2;
do {
result2 = result + "_" + index++;
} while (eventHandlerMethodNames.containsValue(result2));
result = result2;
}
}
eventHandlerMethodNames.put(handler, result);
}
return result;
}
/*------------------------------------------------------------------------*/
/*-- Report methods ------------------------------------------------------*/
/*------------------------------------------------------------------------*/
public void addScriptField(FieldDescriptor field) {
symbolTable.getScriptFields().add(field);
}
public void addScriptMethod(MethodDescriptor method) {
if (method.getName().equals("main") &&
method.getParameterTypes().length == 1 &&
method.getParameterTypes()[0].getName().equals("[Ljava.lang.String;")) {
setMainDeclared(true);
}
symbolTable.getScriptMethods().add(method);
}
public void registerScript(String script) throws CompilerException {
registerScript(script, null);
}
public void registerScript(String script,
File sourceFile) throws CompilerException {
if (sourceFile != null) {
sourceFiles.push(sourceFile);
}
script = script.trim();
if (!"".equals(script) && !script.endsWith("}") &&
!script.endsWith(";")) {
script += ";";
}
scriptManager.registerScript(script);
if (sourceFile != null) {
File pop = sourceFiles.pop();
if (!sourceFile.equals(pop)) {
throw new RuntimeException(
"leaving registerScript(), but " + sourceFile +
" was not the top entry on the stack (found " + pop
+ " instead)");
}
}
}
public String preprocessScript(String script) throws CompilerException {
return scriptManager.preprocessScript(script);
}
public boolean isIdentCssFound() {
return identCssFound;
}
public void detectIdentStyleSheetFile() {
// detects if the given css file is ident to jaxx file
File identCssFile = jaxxFile.getCssFile();
if (identCssFile.exists()) {
// ok found ident css file
identCssFound = true;
try {
registerStyleSheetFile(identCssFile.toURI().toURL(), false);
} catch (MalformedURLException e) {
log.error(e);
}
}
}
static int commonCssDetectedCount =0;
public void detectIdentCommonStyleSheetFile() {
if (isSuperClassAware(JAXXObject.class)) {
CompiledObject rootObject = getRootObject();
ClassDescriptor rootObjectClass = rootObject.getObjectClass();
String fileName = String.format("Common%s.jcss", rootObjectClass.getSimpleName());
String commonCssPath = String.format("%s/%s", rootObjectClass.getPackageName().replaceAll("\\.", "/"), fileName);
URL resource = getClassLoader().getResource(commonCssPath);
if (resource != null) {
if (engine.isVerbose()) {
log.info(String.format("Register Common css: %s [%d] on %s", fileName, ++commonCssDetectedCount, jaxxFile.getJaxxFile().getName()));
}
registerStyleSheetFile(resource, false);
}
}
}
public void registerStyleSheetFile(URL styleFile, boolean warnAutoCssImport) {
if (warnAutoCssImport && identCssFound) {
// detects if the given css file is ident to jaxx file
File identCssFile = jaxxFile.getCssFile();
if (new File(styleFile.getFile()).toPath().equals(identCssFile.toPath()) && identCssFile.exists()) {
// ok found ident css file
reportWarning(String.format("The css file %s can be automatically imported since it is named as his jaxx file.", styleFile));
return;
}
}
String content;
try (InputStream inputStream = styleFile.openStream()) {
content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
} catch (IOException e) {
reportError("Can't load common css: " + styleFile, new CompilerException(e));
return;
}
try {
Stylesheet style = StylesheetHelper.processStylesheet(content);
registerStylesheet(style);
} catch (CompilerException e) {
String message = "Css file content is not valid :" + styleFile;
if (e instanceof org.nuiton.jaxx.compiler.css.parser.ParseException) {
org.nuiton.jaxx.compiler.css.parser.ParseException parseException = (org.nuiton.jaxx.compiler.css.parser.ParseException) e;
message += " (line: " + parseException.getLine() + " - col:" + parseException.getColumn() + ") ";
}
reportError(message, e);
}
}
public void applyStylesheets() {
for (Object o : new ArrayList<>(objects.values())) {
CompiledObject object = (CompiledObject) o;
DefaultObjectHandler tagHandler = TagManager.getTagHandler(object.getObjectClass());
if (tagHandler==null) {
continue;
}
log.debug(String.format("will apply css on object %s from handler %s", object.getId(), tagHandler));
tagHandler.applyStylesheets(object, this);
}
}
/*------------------------------------------------------------------------*/
/*-- Getter methods ------------------------------------------------------*/
/*------------------------------------------------------------------------*/
public void registerStylesheet(Stylesheet stylesheet) {
if (this.stylesheet == null) {
this.stylesheet = stylesheet;
} else {
this.stylesheet.add(stylesheet.getRules());
}
}
public void addInlineStyle(CompiledObject object,
String propertyName,
boolean dataBinding) {
Rule style = StylesheetHelper.inlineAttribute(
object,
propertyName,
dataBinding
);
inlineStyles.add(style);
}
public void reportWarning(String warning) {
Element currentTag = null;
if (!tagsBeingCompiled.isEmpty()) {
currentTag = tagsBeingCompiled.peek();
}
reportWarning(currentTag, warning, 0);
}
public void reportWarning(Element tag, String warning, int lineOffset) {
String lineNumber = null;
if (tag != null) {
String lineAttr =
tag.getAttributeNS(JAXX_INTERNAL_NAMESPACE, "line");
if (lineAttr.length() > 0) {
lineNumber = lineAttr;
}
}
File srcFile = sourceFiles.peek();
try {
srcFile = srcFile.getCanonicalFile();
} catch (IOException e) {
// ignore ?
}
StringBuilder buffer = new StringBuilder();
buffer.append(srcFile);
if (lineNumber != null) {
buffer.append(":");
buffer.append(sourceFiles.size() == 1 ?
Integer.parseInt(lineNumber) + lineOffset :
lineOffset + 1
);
}
buffer.append(getLineSeparator()).append(warning.trim());
if (engine != null) {
engine.addWarning(buffer.toString());
} else {
System.err.println(buffer.toString());
}
}
public void reportError(String error) {
Element currentTag = null;
if (!tagsBeingCompiled.isEmpty()) {
currentTag = tagsBeingCompiled.peek();
}
reportError(currentTag, error);
}
public void reportError(CompilerException ex) {
reportError(null, ex);
}
public void reportError(String extraMessage, CompilerException ex) {
String message = ex.getMessage();
if (UnsupportedAttributeException.class.equals(ex.getClass()) ||
UnsupportedTagException.class.equals(ex.getClass())) {
String exceptionName = ex.getClass().getName();
message = exceptionName.substring(
exceptionName.lastIndexOf(".") + 1) + ": " + message;
}
int lineOffset;
if (ex instanceof ParseException) {
lineOffset = Math.max(0, ((ParseException) ex).getLine() - 1);
} else {
lineOffset = 0;
}
Element currentTag = null;
if (!tagsBeingCompiled.isEmpty()) {
currentTag = tagsBeingCompiled.peek();
}
reportError(currentTag, extraMessage != null ?
extraMessage + message :
message, lineOffset
);
}
public void reportError(Element tag, String error) {
reportError(tag, error, 0);
}
public void reportError(Element tag, String error, int lineOffset) {
int lineNumber = 0;
if (tag != null) {
String lineAttr =
tag.getAttributeNS(JAXX_INTERNAL_NAMESPACE, "line");
if (lineAttr.length() > 0) {
lineNumber = Integer.parseInt(lineAttr);
}
}
lineNumber = Math.max(lineNumber, 1) + lineOffset;
reportError(lineNumber, error);
}
public void reportError(int lineNumber, String error) {
File errorFile = sourceFiles.isEmpty() ? null : sourceFiles.peek();
try {
if (errorFile != null) {
errorFile = errorFile.getCanonicalFile();
}
} catch (IOException e) {
// ignore ?
}
StringBuilder buffer = new StringBuilder();
buffer.append(errorFile != null ? errorFile.getPath() :
"");
if (lineNumber > 0) {
buffer.append(":").append(lineNumber);
}
buffer.append(getLineSeparator()).append(": ").append(error);
if (engine != null) {
engine.addError(buffer.toString());
} else {
System.err.println(buffer.toString());
}
failed = true;
}
public Map getObjects() {
return objects;
}
public Map>> getEventHandlers() {
return eventHandlers;
}
public CompilerConfiguration getConfiguration() {
JAXXEngine engine = getEngine();
if (engine == null) {
return defaultConfiguration;
}
return engine.getConfiguration();
}
public String getOutputClassName() {
return outputClassName;
}
public File getBaseDir() {
return baseDir;
}
public Set getImportedClasses() {
return importedClasses;
}
public Set getImportedPackages() {
return importedPackages;
}
public Iterator getObjectCreationOrder() {
return objects.values().iterator();
}
public CompiledObject getRootObject() {
return root;
}
/*------------------------------------------------------------------------*/
/*-- Buffer --------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
public Stack getSourceFiles() {
return sourceFiles;
}
public ScriptManager getScriptManager() {
return scriptManager;
}
public SymbolTable getSymbolTable() {
return symbolTable;
}
public Stylesheet getStylesheet() {
Stylesheet merged = new Stylesheet();
if (stylesheet != null) {
merged.add(stylesheet.getRules());
}
merged.add(inlineStyles.toArray(new Rule[0]));
return merged;
}
public FieldDescriptor getScriptField(String fieldName) {
for (FieldDescriptor f : symbolTable.getScriptFields()) {
if (fieldName.equals(f.getName())) {
return f;
}
}
return null;
}
public MethodDescriptor getScriptMethod(String methodName) {
for (MethodDescriptor m : symbolTable.getScriptMethods()) {
if (methodName.equals(m.getName())) {
return m;
}
}
return null;
}
public boolean isFailed() {
return failed;
}
/**
* Returns a ClassLoader
which searches the user-specified
* class path in addition to the normal system class path.
*
* @return ClassLoader
to use while resolving class references
*/
public ClassLoader getClassLoader() {
if (classLoader == null) {
CompilerConfiguration configuration = getConfiguration();
if (configuration.getClassLoader() != null) {
classLoader = configuration.getClassLoader();
} else {
throw new NullPointerException(
"compiler configuration requires a classLoader! :\n" +
configuration);
}
}
return classLoader;
}
/*------------------------------------------------------------------------*/
/*-- Other methods -------------------------------------------------------*/
/*------------------------------------------------------------------------*/
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Checks if the super class of the mirrored javaFile is aware of the
* given {@code type}.
*
* Note: If no super-class exist, then returns
* {@code false}.
*
* @param type the type to check against super class
* @return {@code true} if super class exists and is assignable against the
* given type, {@code false} otherwise
*/
public boolean isSuperClassAware(Class> type) {
ClassDescriptor superClass = root.getObjectClass();
if (superClass == null) {
// no super class, not aware of anything
return false;
}
return ClassDescriptorHelper.isAssignableFrom(superClass,
type);
}
public JAXXObjectDescriptor getJAXXObjectDescriptor() {
runInitializers();
CompiledObject[] components = new ArrayList<>(
objects.values()).toArray(new CompiledObject[objects.size()]);
assert initializers.isEmpty() :
"there are pending initializers remaining";
assert root != null : "root object has not been defined";
assert Arrays.asList(components).contains(root) :
"root object is not registered";
ComponentDescriptor[] descriptors =
new ComponentDescriptor[components.length];
// as we print, sort the array so that component's parents are always
// before the components themselves
for (int i = 0; i < components.length; i++) {
CompiledObject parent = components[i].getParent();
while (parent != null) {
boolean found = false;
for (int j = i + 1; j < components.length; j++) {
// found parent after component, swap them
if (components[j] == parent) {
components[j] = components[i];
components[i] = parent;
found = true;
break;
}
}
if (!found) {
break;
}
parent = components[i].getParent();
}
int parentIndex = -1;
if (parent != null) {
for (int j = 0; j < i; j++) {
if (components[j].equals(parent)) {
parentIndex = j;
break;
}
}
}
descriptors[i] = new ComponentDescriptor(
components[i].getId(),
components[i] == root ? outputClassName : components[i].getObjectClass().getName(),
components[i].getStyleClass(),
parentIndex != -1 ? descriptors[parentIndex] : null);
}
Stylesheet css = getStylesheet();
if (css == null) {
css = new Stylesheet();
}
return new JAXXObjectDescriptor(descriptors, css);
}
public StringBuilder getInitializer() {
return initializer;
}
public StringBuilder getLateInitializer() {
return lateInitializer;
}
public StringBuilder getActionsInitializer() {
return actionsInitializer;
}
public StringBuilder getBodyCode() {
return bodyCode;
}
public boolean isMainDeclared() {
return mainDeclared;
}
public void setMainDeclared(boolean mainDeclared) {
this.mainDeclared = mainDeclared;
}
public void appendInitializerCode(String code) {
initializer.append(code);
}
public void appendBodyCode(String code) {
bodyCode.append(code);
}
public void appendLateInitializer(String code) {
lateInitializer.append(code);
}
public void appendActionInitializer(String code) {
actionsInitializer.append(code);
}
public void addImport(Class> clazz) {
addImport(clazz.getName());
}
public void addImport(String text) {
if (text.endsWith("*")) {
importedPackages.add(text.substring(0, text.length() - 1));
} else {
importedClasses.add(text);
}
if (!text.equals("*")) {
getJavaFile().addImport(text);
}
}
protected void addDefaultImport(String text) {
if (text.endsWith("*")) {
importedPackages.add(text.substring(0, text.length() - 1));
} else {
importedClasses.add(text);
}
}
public void addDependencyClass(String className) {
if (engine.containsJaxxFileClassName(className)) {
// already registered in engine
return;
}
// register the jaxx file into the engine
URL jaxxURL = ClassDescriptorHelper.getURL(getClassLoader(), className, "jaxx");
if (jaxxURL == null) {
// not a jaxx file, can quit now
return;
}
if (!engine.isCompileFirstPassTask()) {
throw new IllegalStateException(
"Internal error: adding dependency class " +
className + " during second compilation pass");
}
//FIXME tchemit 20100518 Must unify this with ClassDescriptorHelper to use the less costless resource
if (jaxxURL.toString().startsWith("file:")) {
File jaxxFile = URLtoFile(jaxxURL);
try {
jaxxFile = jaxxFile.getCanonicalFile();
} catch (IOException ex) {
// ignore ? (at least log it...)
log.error(ex);
}
String jaxxFileName =
className.substring(className.lastIndexOf(".") + 1) +
".jaxx";
assert jaxxFile.getName().equalsIgnoreCase(jaxxFileName) :
"expecting file name to match " +
className + ", but found " + jaxxFile.getName();
if (jaxxFile.getName().equals(jaxxFileName)) {
engine.addFileToCompile(jaxxFile, className);
}
}
}
/**
* Loads the given file and returns its content.
*
* Note: If any errors encounters, then report an error in the
* compiler and returns an empty string.
*
* @param file the file to load
* @return the content of the file or empty string if something was wrong
*/
public String loadFile(File file) {
if (!file.exists()) {
reportError("Could not found file " + file);
return "";
}
try {
try (FileReader in = new FileReader(file)) {
StringWriter styleBuffer = new StringWriter();
char[] readBuffer = new char[2048];
int c;
while ((c = in.read(readBuffer)) > 0) {
styleBuffer.write(readBuffer, 0, c);
}
return styleBuffer.toString();
}
} catch (IOException e) {
reportError("Could not read file " + file + " for reason " +
e.getMessage());
return "";
}
}
/**
* Verifies that a snippet of Java code parses correctly.
*
* A warning is generated if the string has enclosing curly braces.
*
* @param javaCode the Java code snippet to test
* @return a "cooked" version of the string which has enclosing curly
* braces removed.
* @throws CompilerException if the code cannot be parsed
*/
public String checkJavaCode(String javaCode) {
return checkJavaCode(javaCode, true);
}
/**
* Verifies that a snippet of Java code parses correctly.
*
* A warning is generated if the string has enclosing curly braces.
*
* @param javaCode the Java code snippet to test
* @param warnOnBrace flag to warn if curly braces are found
* @return a "cooked" version of the string which has enclosing curly
* braces removed.
* @throws CompilerException if the code cannot be parsed
*/
public String checkJavaCode(String javaCode, boolean warnOnBrace) {
javaCode = scriptManager.trimScript(javaCode, warnOnBrace);
scriptManager.checkParse(javaCode);
return javaCode;
}
/**
* Check that a reference exists in symbol table on second compil pass
*
* @param tag the current tag
* @param reference the required reference
* @param strict flag to report an error if reference was not found
* @param attribute (if not null reference the attribute where is defined
* the reference)
* @return true
if reference was found, false
* otherwise and add an error in compiler
*/
public boolean checkReference(Element tag,
String reference,
boolean strict,
String attribute) {
String component = getSymbolTable().getClassTagIds().get(reference);
if (component == null) {
try {
if (isSuperClassAware(JAXXObject.class)) {
ClassDescriptor objectClass = getRootObject().getObjectClass();
if (objectClass instanceof ClassDescriptorResolverFromJaxxFile.JaxxFileClassDescriptor) {
ClassDescriptorResolverFromJaxxFile.JaxxFileClassDescriptor objectClass1 = (ClassDescriptorResolverFromJaxxFile.JaxxFileClassDescriptor) objectClass;
component = objectClass1.getDeclaredFieldDescriptor(reference).getName();
} else {
Optional fieldDescriptor = objectClass.tryToGetFieldDescriptor(reference);
if (fieldDescriptor.isPresent()) {
component = fieldDescriptor.get().getName();
}
}
}
} catch (NoSuchFieldException e) {
// can't happen
}
}
if (component == null) {
if (strict) {
String msg;
if (attribute != null) {
msg = "tag '" + tag.getLocalName() +
"' could not find the reference '" + reference +
"' on attribute [" + attribute + "]";
} else {
msg = "tag '" + tag.getLocalName() +
"' could not find the reference '" + reference + "'";
}
reportError(msg);
}
return false;
}
return true;
}
public String getAutoId(String name) {
return idHelper.nextId(name);
}
public String getUniqueId(Object object) {
return idHelper.getUniqueId(object);
}
public String[] getExtraInterfaces() {
return extraInterfaces;
}
public void setExtraInterfaces(String[] extraInterfaces) {
this.extraInterfaces = extraInterfaces;
}
// 1.5 adds getCanonicalName; unfortunately we can't depend on 1.5 features yet
public boolean isAbstractClass() {
return abstractClass;
}
public void setAbstractClass(boolean abstractClass) {
this.abstractClass = abstractClass;
}
public String getGenericType() {
return genericType;
}
public void setGenericType(String genericType) {
this.genericType = genericType;
}
public String getSuperGenericType() {
return superGenericType;
}
public void setSuperGenericType(String superGenericType) {
this.superGenericType = superGenericType;
}
public void addSimpleField(JavaField javaField) {
getJavaFile().addSimpleField(javaField);
}
public JavaFile getJavaFile() {
if (javaFile == null) {
String outputClassName = getOutputClassName();
if (outputClassName == null) {
javaFile = JavaElementFactory.newFile(0, "");
} else {
int dotPos = outputClassName.lastIndexOf(".");
String packageName = dotPos != -1 ?
outputClassName.substring(0, dotPos) : null;
String simpleClassName = outputClassName.substring(dotPos + 1);
javaFile = JavaElementFactory.newFile(Modifier.PUBLIC, packageName, simpleClassName);
}
}
return javaFile;
}
public void finalizeCompiler() throws Exception {
int dotPos = getOutputClassName().lastIndexOf(".");
String packageName = dotPos != -1 ?
getOutputClassName().substring(0, dotPos) : null;
String simpleClassName = getOutputClassName().substring(dotPos + 1);
CompiledObject compiledObject = getRootObject();
String genericType = getGenericType();
if (StringUtils.isNotEmpty(genericType)) {
// add the generic type to the root object
// generic type can be on form E extends XXX
//keep only the first thing...
StringBuilder sb = new StringBuilder();
String[] allTypes = JavaGeneratorUtil.splitFqnList(genericType, ',');
for (String type : allTypes) {
int anExtends = type.indexOf("extends");
if (anExtends > -1) {
type = type.substring(0, anExtends - 1).trim();
}
sb.append(", ").append(type);
}
String finalType;
if (allTypes.length > 0) {
finalType = sb.substring(2);
} else {
finalType = sb.toString();
}
compiledObject.setGenericTypes(finalType);
}
// finalize all objects via their decorator
for (CompiledObject object : getObjects().values()) {
CompiledObjectDecorator decorator = object.getDecorator();
decorator.finalizeCompiler(this,
root,
object,
javaFile,
packageName,
simpleClassName,
getOutputClassName());
}
// obtain list of finalizers to apply
List realFinalizers =
new ArrayList<>();
for (JAXXCompilerFinalizer finalizer :
getConfiguration().getFinalizers().values()) {
if (finalizer.accept(this)) {
realFinalizers.add(finalizer);
}
}
// call the finalizers finalizeCompiler method
for (JAXXCompilerFinalizer finalizer : realFinalizers) {
// check if finalizer can be apply of this compiler
if (finalizer.accept(this)) {
finalizer.finalizeCompiler(compiledObject,
this,
javaFile,
packageName,
simpleClassName
);
}
}
// call the compiled objects finalizeCompiler method
for (CompiledObject object : getObjects().values()) {
object.finalizeCompiler(this);
}
// compile bindings
getBindingHelper().finalizeBindings();
// call the finalizers prepareJavaFile method
for (JAXXCompilerFinalizer finalizer : realFinalizers) {
finalizer.prepareJavaFile(compiledObject,
this,
javaFile,
packageName,
simpleClassName
);
}
}
public void generate(JavaFileGenerator generator) throws IOException {
File dest;
String fqn = getOutputClassName();
if (getConfiguration().getTargetDirectory() != null) {
dest = new File(getConfiguration().getTargetDirectory(),
fqn.replace('.', File.separatorChar) + ".java"
);
} else {
dest = new File(getBaseDir(),
fqn.substring(fqn.lastIndexOf(".") + 1) + ".java"
);
}
// make sure directory exists
File parentFile = dest.getParentFile();
if (parentFile == null) {
throw new IOException("No parent file for " + dest);
}
if (!parentFile.exists() && !parentFile.mkdirs()) {
throw new IOException("Could not create directory " + parentFile);
}
if (dest.exists() && !dest.setLastModified(System.currentTimeMillis())) {
log.warn("could not touch file " + dest);
}
try (PrintWriter out = new PrintWriter(new FileWriter(dest))) {
generator.generateFile(javaFile, out);
}
}
public JAXXEngine getEngine() {
return engine;
}
public void clear() {
idHelper.clear();
bindingHelper.clear();
objects.clear();
ids.clear();
if (symbolTable != null) {
symbolTable.clear();
}
}
/**
* Try to import the given type into the underlying java file of this compiler.
*
* If import can not be done, will then returns the fully qualified name of
* the type.
*
* @param type the type to simplify
* @return the simplify type or the fqn if type could not be imported for the underlying java file.
* @since 2.4.1
*/
public String getImportedType(Class> type) {
return getJavaFile().getImportedType(type);
}
/**
* Try to import the given type into the underlying java file of this compiler.
*
* If import can not be done, will then returns the fully qualified name of
* the type.
*
* @param type the fqn of the type to simplify
* @return the simplify type or the fqn if type could not be imported for the underlying java file.
* @since 2.4.1
*/
public String getImportedType(String type) {
return getJavaFile().getImportedType(type);
}
public String getImportedTypeForSimpleName(String type) {
String suffix = "." + type;
String result = null;
for (String importedClass : getImportedClasses()) {
if (importedClass.endsWith(suffix)) {
result = importedClass;
break;
}
}
return result;
}
public String getUiHandler() {
return uiHandler;
}
public void setUiHandler(String uiHandler) {
this.uiHandler = uiHandler;
}
public String getUIHandlerWithGeneric() {
String genericType = getGenericType();
String result = uiHandler;
if (genericType != null) {
String currentGenericType = genericType;
int beginIndex = currentGenericType.indexOf(" extends");
boolean first = false;
if (beginIndex > -1) {
result += "<";
while (beginIndex > -1) {
genericType = currentGenericType.substring(0, beginIndex).trim();
if (!first) {
first = true;
} else {
int lastIndexOf = genericType.lastIndexOf(",");
genericType = genericType.substring(lastIndexOf).trim();
}
result += genericType;
currentGenericType = currentGenericType.substring(beginIndex + 8).trim();
beginIndex = currentGenericType.indexOf(" extends");
}
result += ">";
} else {
result += "<" + genericType + ">";
}
}
return result;
}
public boolean isUseHandler() {
return StringUtils.isNotBlank(uiHandler);
}
// public void setI18nFormat(String i18nFormat) {
// this.i18nFormat = i18nFormat;
// }
// public String computeI18n(String editorName, String value) {
// String format = StringUtils.isEmpty(value) ? i18nFormat : value;
// if (StringUtils.isEmpty(format)) {
// throw new IllegalStateException("Can't use computeI18n method if i18nFormat missing on root object");
// }
// return String.format(format, StringUtils.removeEnd(editorName, "Label"));
// }
public String computeI18nProperty(CompiledObject editorName) {
return computeI18nProperty(editorName, null);
}
public String computeI18nProperty(CompiledObject editorName, String suffix) {
Objects.requireNonNull(i18nLabelsBuilder);
return i18nLabelsBuilder.getI18nKey(StringUtils.removeEnd(editorName.getI18nProperty(), "Label")+(suffix==null?"":suffix));
}
public String computeI18nProperty(String editorName) {
Objects.requireNonNull(i18nLabelsBuilder);
return i18nLabelsBuilder.getI18nKey(editorName);
}
public String getI18nProperty(CompiledObject compiledObject) {
return getEngine().getI18nProperty(compiledObject.getObjectClass());
}
public String processDataBindings(String stringValue) throws CompilerException {
return bindingHelper.processDataBindings(stringValue);
}
public boolean isI18nInit() {
return i18nLabelsBuilder != null;
}
public void setI18n(String i18n) {
try {
this.i18nLabelsBuilder = new I18nLabelsBuilder(getClassLoader().loadClass(Objects.requireNonNull(i18n)));
} catch (ClassNotFoundException e) {
throw new IllegalStateException("can't get class: "+i18n);
}
}
public Optional getBeanScope() {
return Optional.ofNullable(beanScopeComponents.isEmpty()?null:beanScopeComponents.peek());
}
public void registerBeanScope(BeanScope beanScope) {
log.debug("add: " + beanScope);
beanScopeComponents.push(beanScope);
}
public void removeBeanScope() {
BeanScope beanScope = beanScopeComponents.pop();
log.debug("remove: " + beanScope);
}
public I18nKeysFileModel getParentI18nKeysFileModel(boolean load) {
return getEngine().getI18nKeysFileModel(getRootObject().getObjectClass().getName(), load);
}
public I18nKeysFileModel getI18nKeysFileModel(boolean load) {
return getEngine().getI18nKeysFileModel(getOutputClassName(), load);
}
}