org.nuiton.jaxx.compiler.CompiledObject Maven / Gradle / Ivy
/*
* #%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;
import org.apache.commons.lang3.StringUtils;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptor;
import org.nuiton.jaxx.compiler.reflect.ClassDescriptorHelper;
import org.nuiton.jaxx.compiler.reflect.MethodDescriptor;
import org.nuiton.jaxx.compiler.tags.DefaultComponentHandler;
import org.nuiton.jaxx.compiler.tags.TagHandler;
import org.nuiton.jaxx.compiler.tags.TagManager;
import org.nuiton.jaxx.compiler.types.TypeManager;
import org.nuiton.jaxx.runtime.JAXXUtil;
import java.awt.Container;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* Represents an object in the .java
file being generated during compilation. There is
* a CompiledObject
for each class tag encountered, and certain tags may generate
* additional objects for various reasons.
*/
public class CompiledObject {
/**
* The object's id.
*/
private final String id;
/**
* Java code referring to the object.
*/
private final String javaCode;
/**
* The object's class.
*/
private final ClassDescriptor objectClass;
/**
* The style class.
*/
private String styleClass;
private Set styleClasses;
/**
* The container containing this CompiledObject.
*/
private CompiledObject parent;
/**
* true if this object overrides an object of the same id in a superclass
* of the object being compiled
*/
private boolean override;
/**
* true if this object overrides an object of the same id in a superclass
* of the object being compiled.
*
* And User ask this object not to be added twice (means should be a container, the addChildrenToXXX method would be called twice)
*/
private boolean forceOverride;
/**
* The type of the object without fqn if possible.
*
* This is used when casting on an overridden object and only available if
* {@link #override} is set to {@code true}.
*/
private String simpleType;
/**
* Comma-separated Java code snippets representing the parameters that
* should be passed to the object's constructor.
*/
private String constructorParams;
/**
* Java code snippet which performs basic initialization of the object (after it has already been constructed).
* Because CompiledObject initialization order cannot be guaranteed, it is not safe to refer to other
* CompiledObjects from initializationCode -- you must refer to them from additionCode instead.
*/
private final StringBuilder initializationCode = new StringBuilder();
/**
* Java code snippet which completes setup by adding any child objects, or otherwise manipulates any referenced
* objects. Because CompiledObject initialization order cannot be guaranteed, it is not safe to refer to other
* CompiledObjects from initializationCode -- you must refer to them from additionCode instead.
*/
private StringBuilder additionCode = new StringBuilder();
/**
* List of all registered event handlers.
*/
private final List eventHandlers = new ArrayList<>();
/**
* All properties that have been applied to this CompiledObject.
*/
private final Map properties = new HashMap<>();
/**
* generic types of the compiled object
*/
private String[] genericTypes;
/**
* a flag to indicate if javaBean full support must be support for this
* object by root object
*/
private boolean javaBean;
/**
* code to initialize the bean (can be null)
*/
private String javaBeanInitCode;
/**
* the type of the override object (can be null if no override)
*/
private ClassDescriptor overrideType;
/**
* the decorator
*/
private CompiledObjectDecorator decorator;
/**
* client properties
*/
private Map clientProperties;
/**
* initializer of the object
*/
private String initializer;
private String i18nProperty;
private BeanScope beanScope;
/**
* Should we force to add the object to parent container when overrides
**/
private boolean addToContainer;
public String getSimpleType() {
return simpleType;
}
public Optional getBeanScope() {
return Optional.ofNullable(beanScope);
}
public void setBeanScope(BeanScope beanScope) {
this.beanScope = Objects.requireNonNull(beanScope);
}
public void setAddToContainer(boolean addToContainer) {
this.addToContainer = addToContainer;
}
public boolean isAddToContainer() {
return addToContainer;
}
public class ChildRef {
final CompiledObject child;
final String constraints;
String childJavaCode;
private final String delegateCode;
public ChildRef(CompiledObject child,
String constraints,
String childJavaCode,
String delegateCode) {
this.child = child;
this.constraints = constraints;
this.childJavaCode = childJavaCode;
this.delegateCode = delegateCode;
}
public String getConstraints() {
return constraints;
}
public String getDelegateCode() {
return delegateCode;
}
public CompiledObject getChild() {
return child;
}
public String getChildJavaCode() {
return childJavaCode;
}
public void setChildJavaCode(String childJavaCode) {
this.childJavaCode = childJavaCode;
}
public void addToAdditionCode(StringBuilder buffer,
boolean isRootObject) {
//TC-20091026 do not prefix if on root object
String prefix;
if (isRootObject) {
prefix = "";
} else {
prefix = javaCode + delegateCode + ".";
}
if (constraints != null) {
buffer.append(prefix);
buffer.append("add(");
buffer.append(childJavaCode);
buffer.append(", ");
buffer.append(constraints);
buffer.append(");");
} else {
buffer.append(prefix);
buffer.append("add(");
buffer.append(childJavaCode);
buffer.append(");");
}
buffer.append(JAXXCompiler.getLineSeparator());
}
}
private List childs;
/**
* Creates a new CompiledObject
.
*
* To be useful, the object should be registered with a
* JAXXCompiler
using
* {@link JAXXCompiler#registerCompiledObject(CompiledObject)} .
*
* @param id the object's id
* @param objectClass the object's class
* @param compiler the current JAXXCompiler
* @throws NullPointerException if id or class is null
*/
public CompiledObject(String id,
ClassDescriptor objectClass,
JAXXCompiler compiler) {
this(id, objectClass, compiler, false);
}
/**
* Creates a new CompiledObject
. To be useful, the object should be registered with a
* JAXXCompiler
using {@link JAXXCompiler#registerCompiledObject(CompiledObject)} .
*
* @param id the object's id
* @param objectClass the object's class
* @param compiler the current JAXXCompiler
* @param force true
to force acceptance of invalid ids
* @throws NullPointerException if id or class is null
*/
public CompiledObject(String id,
ClassDescriptor objectClass,
JAXXCompiler compiler,
boolean force) {
this(id, id, objectClass, compiler, force);
}
/**
* Creates a new CompiledObject
. To be useful, the object should be registered with a
* JAXXCompiler
using {@link JAXXCompiler#registerCompiledObject(CompiledObject)} .
*
* @param id the object's id
* @param javaCode Java code referring to the object
* @param objectClass the object's class
* @param force true
to force acceptance of invalid ids
* @param compiler the current JAXXCompiler
* @throws CompilerException if the id is not a valid Java identifier
* @throws NullPointerException if id or class is null
*/
public CompiledObject(String id,
String javaCode,
ClassDescriptor objectClass,
JAXXCompiler compiler,
boolean force) throws CompilerException {
if (!force) {
if (!isValidID(id)) {
compiler.reportError(
"the id '" + id + "' is not a valid Java identifier");
}
}
this.id = id;
this.javaCode = javaCode;
if (objectClass == null) {
throw new NullPointerException();
}
this.objectClass = objectClass;
childs = new ArrayList<>();
}
public static boolean isValidID(String id) {
boolean valid = true;
if (id.length() == 0) {
valid = false;
}
if (valid) {
if (!Character.isJavaIdentifierStart(id.charAt(0))) {
valid = false;
}
if (valid) {
for (int i = 1; i < id.length(); i++) {
if (!Character.isJavaIdentifierPart(id.charAt(i))) {
valid = false;
break;
}
}
}
}
return valid;
}
/**
* True if this object overrides an object in the superclass of the class
* being compiled. For this to be true, the class currently being compiled
* must be a subclass of another JAXXObject
which has an
* identically-named object.
*
* @return true
if this object is an override
* @see #setOverride(boolean)
*/
public boolean isOverride() {
return override;
}
public boolean isForceOverride() {
return forceOverride;
}
public void setForceOverride(boolean forceOverride) {
this.forceOverride = forceOverride;
}
/**
* {@code true} when overrides an object in the superclass of the class
* being compiled AND type is also override.
*
* @return {@code true} if this object is an override AND override type
*/
public boolean isOverrideType() {
return isOverride() && !getObjectClass().equals(getOverrideType());
}
/**
* Sets whether this class overrides an identically-named object in the
* parent class.
*
* @param override true
if this object is an override
* @see #isOverride
*/
public void setOverride(boolean override) {
this.override = override;
}
/**
* Sets the simple type of the object.
*
* Used when cast in an overridden object.
*
* @param simpleType the simple type to use (or the fqn if there is a
* conflict with already imported types of the compiler).
* @since 2.4
*/
public void setSimpleType(String simpleType) {
this.simpleType = simpleType;
}
/**
* Returns this object's CSS style class.
*
* @return the value of the styleClass
attribute
*/
public String getStyleClass() {
return styleClass;
}
public Set getStyleClasses() {
return styleClasses == null
? styleClasses = styleClass == null ? Set.of() : new LinkedHashSet<>(Arrays.asList(styleClass.trim().split("\\s"))) :
styleClasses;
}
public boolean matchStyleClass(String styleClass) {
return styleClass == null || getStyleClasses().contains(styleClass);
}
/**
* Sets this object's CSS style class.
*
* @param styleClass the new style class
*/
public void setStyleClass(String styleClass) {
this.styleClass = styleClass;
styleClasses = null;
}
/**
* Returns this object's parent container. Non-visual components (and
* the root container) return null
.
*
* @return the object's parent container
*/
public CompiledObject getParent() {
return parent;
}
/**
* Sets this object's parent container.
*
* @param parent the parent container
* @throws IllegalArgumentException if parent is not a {@link Container}
*/
public void setParent(CompiledObject parent) throws IllegalArgumentException {
if (!ClassDescriptorHelper.getClassDescriptor(Container.class).isAssignableFrom(parent.getObjectClass())) {
throw new IllegalArgumentException("parent must descend from java.awt.Container");
}
this.parent = parent;
}
/**
* Returns the name of the method that should be generated in the compiled
* .java
file
* in order to create this object. This is just a suggestion and may be
* ignored.
*
* @return the suggested name of the method which initializes this object
*/
public String getCreationMethodName() {
return "create" + StringUtils.capitalize(getId());
}
/**
* Returns the name of the method that should be generated in the compiled
* .java
file in order to add children to this object. This
* is just a suggestion and may be ignored.
*
* @return the suggested name of the method which completes this object's setup
*/
public String getAdditionMethodName() {
return "addChildrenTo" + StringUtils.capitalize(getId());
}
/**
* Returns the type of this object.
*
* @return the class this CompiledObject
represents
*/
public ClassDescriptor getObjectClass() {
return objectClass;
}
/**
* Returns this object's id. Generally, a field with this name will be
* created in the compiled .java
* file in order to represent this object.
*
* @return the id used to refer to this object
*/
public String getId() {
return id;
}
/**
* Returns Java code used to refer to this object in the compiled Java file.
* This is usually the same as its id.
*
* @return the Java code for this object
*/
public String getJavaCode() {
String result = javaCode;
if (isOverride()) {
// handle cases where object is overridden to be a different class
if (simpleType == null) {
// means overridden but still on the same type
result = javaCode;
} else {
result = "((" + simpleType + ") " + javaCode + ")";
// result = "((" + JAXXCompiler.getCanonicalName(this) + ") " + javaCode + ")";
}
}
return result;
}
public String getJavaCodeForProperty(String property) {
if (!isOverride() || simpleType == null) {
return javaCode;
}
String result = "((" + simpleType + ") " + javaCode + ")";
// String result = "((" + JAXXCompiler.getCanonicalName(this) + ") " + javaCode + ")";
// String result = "((" + JAXXCompiler.getCanonicalName(getObjectClass()) + ") " + javaCode + ")";
String methodName = StringUtils.capitalize(property);
try {
MethodDescriptor methodDescriptor = overrideType.getMethodDescriptor("get" + methodName);
if (methodDescriptor != null) {
if (overrideType.getMethodDescriptor("set" + methodName, methodDescriptor.getReturnType()) != null) {
// handle cases where object is overridden to be a different class
result = javaCode;
}
}
} catch (NoSuchMethodException e) {
// lazy error, do nothing
}
return result;
}
/**
* Returns a list of comma-separated Java code snippets that represent the
* parameters to pass to this object's constructor.
*
* @return the raw constructor params
* @see #setConstructorParams(String)
*/
public String getConstructorParams() {
return constructorParams;
}
/**
* Sets the parameters to pass to this object's constructor.
*
* @param constructorParams comma-separated Java code snippets representing
* constructor params
* @see #getConstructorParams
*/
public void setConstructorParams(String constructorParams) {
this.constructorParams = constructorParams;
}
public String getInitializer() {
return initializer;
}
public void setInitializer(String initializer) {
this.initializer = initializer;
}
/**
* Returns the code that performs basic initialization of this object,
* after it has already been constructed.
* This basic code should not reference any other
* CompiledObjects
as they may not have been created yet.
*
* @param compiler compiler to use
* @return the code which initializes this object
*/
public String getInitializationCode(JAXXCompiler compiler) {
StringBuilder result = new StringBuilder(initializationCode.toString());
for (Object eventHandler : eventHandlers) {
EventHandler handler = (EventHandler) eventHandler;
result.append(getInitializationCode(handler, compiler));
}
return result.toString();
}
protected String getInitializationCode(EventHandler handler, JAXXCompiler compiler) {
MethodDescriptor addMethod = handler.getAddMethod();
ClassDescriptor listenerClass = addMethod.getParameterTypes()[0];
String type = compiler.getImportedType(listenerClass.getName());
String prefix = compiler.getImportedType(JAXXUtil.class);
//TC-20091026 use 'this' instead of root object javaCode
//TC-20091105 JAXXUtil.getEventListener is generic, no more need cast and use simple name
return getJavaCode() + '.' + addMethod.getName() + "(" + prefix + ".getEventListener(" + type + ".class, " +
TypeManager.getJavaCode(handler.getListenerMethod().getName()) + ", this, " +
TypeManager.getJavaCode(compiler.getEventHandlerMethodName(handler)) + "));" + JAXXCompiler.getLineSeparator();
}
/**
* Returns Java code to complete final setup on this object. This code may
* reference other CompiledObjects
, as they are guaranteed to
* have all been created by this point.
*
* @return code which adds children and performs final setup
*/
public String getAdditionCode() {
return additionCode.toString();
}
/**
* Appends code to the initialization code block. A line separator is
* automatically appended to the end.
*
* @param code the code to add to the initialization block
* @see #getInitializationCode(JAXXCompiler)
*/
public void appendInitializationCode(String code) {
if (!code.isEmpty()) {
initializationCode.append(code);
initializationCode.append(JAXXCompiler.getLineSeparator());
}
}
/**
* Appends code to the addition code block. A line separator is
* automatically appended to the end.
*
* @param code the code to add to the addition block
* @see #getAdditionCode
*/
public void appendAdditionCode(String code) {
if (!code.isEmpty()) {
additionCode.append(code);
additionCode.append(JAXXCompiler.getLineSeparator());
}
}
/**
* Stores a property for this object. The only effect of calling this
* method is that the property will be returned by {@code getProperties()}.
*
* @param property the name of the property
* @param value the property's value
* @see #getProperties()
*/
public void addProperty(String property, String value) {
properties.put(property, value);
}
public boolean hasClientProperties() {
return clientProperties != null && !clientProperties.isEmpty();
}
public void addClientProperty(String property, String value) {
getClientProperties().put(property, value);
}
public String getClientProperty(String key) {
if (!hasClientProperties()) {
return null;
}
return clientProperties.get(key);
}
public Map getClientProperties() {
if (clientProperties == null) {
clientProperties = new HashMap<>();
}
return clientProperties;
}
/**
* Returns all properties which have been set for this object.
*
* @return a Map
containing all properties defined for this object
* @see #addProperty(String, String)
*/
public Map, ?>/**/ getProperties() {
return properties;
}
/**
* Adds an event listener to this object. The generated code will appear
* in the initialization block.
*
* @param eventId unique (per CompiledObject) identifier for the event handler
* @param addMethod the method which adds the event listener
* @param listenerMethod the method (in the listener class) which is called when the event is fired
* @param code the Java code for the listenerMethod's body
* @param compiler the current JAXXCompiler
* @see #getInitializationCode(EventHandler, JAXXCompiler)
*/
public void addEventHandler(String eventId,
MethodDescriptor addMethod,
MethodDescriptor listenerMethod,
String code,
JAXXCompiler compiler) {
EventHandler handler = new EventHandler(
getId() + "." + eventId,
getJavaCode(),
addMethod,
addMethod.getParameterTypes()[0],
listenerMethod,
code
);
compiler.registerEventHandler(handler);
eventHandlers.add(handler);
if (getJavaCode().contains(".")) {
// object lives in another JAXX file and consequently its initialization code won't be output
compiler.appendInitializerCode(
getInitializationCode(handler, compiler));
}
}
/**
* Adds a child component to this container. This variant allows the Java
* code for a layout constraints object to be specified.
*
* @param child the component to add
* @param constraints Java code for the layout constraints object
* @param compiler the current JAXXCompiler
* @throws CompilerException if this object is not a container
*/
public void addChild(CompiledObject child,
String constraints,
JAXXCompiler compiler) throws CompilerException {
DefaultComponentHandler tagHandler = null;
if (!child.isOverride() || child.isAddToContainer()) {
TagHandler tagHandler1 = TagManager.getTagHandler(getObjectClass());
if (!(tagHandler1 instanceof DefaultComponentHandler)) {
compiler.reportError("component parent " + tagHandler1 + " is not a component");
return;
}
tagHandler = (DefaultComponentHandler) tagHandler1;
if (!tagHandler.isContainer()) {
compiler.reportError("component " + this + " may not have children");
return;
}
}
addChild(child, constraints, compiler, tagHandler);
}
/**
* Adds a child component to this container. This variant allows the Java
* code for a layout constraints object to be specified.
*
* @param child the component to add
* @param constraints Java code for the layout constraints object
* @param compiler the current JAXXCompiler
* @param tagHandler parent tag handler
* @throws CompilerException if this object is not a container
*/
public void addChild(CompiledObject child,
String constraints,
JAXXCompiler compiler,
DefaultComponentHandler tagHandler) throws CompilerException {
try {
if (constraints != null) {
constraints = compiler.checkJavaCode(constraints);
}
} catch (CompilerException e) {
compiler.reportError(
"While parsing 'constraints' attribute: " + e.getMessage());
}
if (!child.isOverride() || child.isAddToContainer()) {
String containerDelegate = tagHandler.getContainerDelegate();
String delegateCode = containerDelegate != null ? "." + containerDelegate + "()" : "";
child.setParent(this);
ChildRef ref = newChildRef(child, constraints, delegateCode);
childs.add(ref);
}
}
protected ChildRef newChildRef(CompiledObject child, String constraints, String delegateCode) {
return new ChildRef(child,
constraints,
child.getJavaCode(),
delegateCode
);
}
@Override
public String toString() {
return getObjectClass().getName() + "[id='" + id + "']";
}
public String getGenericTypes() {
if (getGenericTypesLength() == 0) {
// not using it
return "";
}
String result = "";
for (int i = 0, j = getGenericTypesLength(); i < j; i++) {
result += ", " + genericTypes[i];
}
return "< " + result.substring(2) + " >";
}
public void setGenericTypes(String... genericTypes) {
if (genericTypes == null) {
this.genericTypes = null;
return;
}
this.genericTypes = new String[genericTypes.length];
for (int i = 0, j = genericTypes.length; i < j; i++) {
this.genericTypes[i] = genericTypes[i].trim();
}
}
public boolean isJavaBean() {
return javaBean;
}
public void setJavaBean(boolean javaBean) {
this.javaBean = javaBean;
}
public ClassDescriptor getOverrideType() {
return overrideType;
}
public void setOverrideType(ClassDescriptor overrideType) {
this.overrideType = overrideType;
}
public String getJavaBeanInitCode() {
return javaBeanInitCode;
}
public void setJavaBeanInitCode(String javaBeanInitCode) {
this.javaBeanInitCode = javaBeanInitCode;
}
public List getChilds() {
return childs;
}
public CompiledObjectDecorator getDecorator() {
return decorator;
}
public void setDecorator(CompiledObjectDecorator decorator) {
this.decorator = decorator;
}
public void finalizeCompiler(JAXXCompiler compiler) {
List refList = getChilds();
StringBuilder buffer = new StringBuilder();
if (isForceOverride()) {
// the parent invocation should always be before the new code to add
buffer.append(String.format("super.%s();%s", getAdditionMethodName(), JAXXCompiler.getLineSeparator()));
}
if (refList != null && !refList.isEmpty()) {
// compute additionCode for all children
for (ChildRef childRef : refList) {
childRef.addToAdditionCode(buffer,
equals(compiler.getRootObject()));
}
additionCode = buffer.append(additionCode);
}
}
public int getGenericTypesLength() {
return genericTypes == null ? 0 : genericTypes.length;
}
public String getGetterName() {
return "get" + StringUtils.capitalize(id);
}
public String getSetterName() {
return "set" + StringUtils.capitalize(id);
}
public boolean isUseComputeI18n() {
return i18nProperty != null && !"skip".equals(i18nProperty);
}
public String getI18nProperty() {
return i18nProperty;
}
public void setI18nProperty(String i18nProperty) {
this.i18nProperty = i18nProperty;
}
}