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

org.nuiton.jaxx.compiler.CompiledObject 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;

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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy