/*
* Copyright 2008-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.griffon.compile.core.ast.transform;
import griffon.transform.FXObservable;
import griffon.util.GriffonNameUtils;
import groovyjarjarasm.asm.Opcodes;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.FloatProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.MapProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SetProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleFloatProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleSetProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import org.codehaus.griffon.compile.core.AnnotationHandler;
import org.codehaus.griffon.compile.core.AnnotationHandlerFor;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.FieldExpression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.IfStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.transform.GroovyASTTransformation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.codehaus.groovy.ast.ClassHelper.LIST_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.MAP_TYPE;
/**
* Handles generation of code for the {@code @FXObservable}
* <p>
* Generally, it adds (if needed) a javafx.beans.property.Property type
* <p>
* It also generates the setter and getter and wires the them through the
* javafx.beans.property.Property.
*
* @author Andres Almiray
*/
@AnnotationHandlerFor(FXObservable.class)
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class FXObservableASTTransformation extends AbstractASTTransformation implements AnnotationHandler {
private static final ClassNode FXOBSERVABLE_CNODE = makeClassSafe(FXObservable.class);
private static final ClassNode OBJECT_PROPERTY_CNODE = makeClassSafe(ObjectProperty.class);
private static final ClassNode BOOLEAN_PROPERTY_CNODE = makeClassSafe(BooleanProperty.class);
private static final ClassNode DOUBLE_PROPERTY_CNODE = makeClassSafe(DoubleProperty.class);
private static final ClassNode FLOAT_PROPERTY_CNODE = makeClassSafe(FloatProperty.class);
private static final ClassNode INT_PROPERTY_CNODE = makeClassSafe(IntegerProperty.class);
private static final ClassNode LONG_PROPERTY_CNODE = makeClassSafe(LongProperty.class);
private static final ClassNode STRING_PROPERTY_CNODE = makeClassSafe(StringProperty.class);
private static final ClassNode LIST_PROPERTY_CNODE = makeClassSafe(ListProperty.class);
private static final ClassNode MAP_PROPERTY_CNODE = makeClassSafe(MapProperty.class);
private static final ClassNode SET_PROPERTY_CNODE = makeClassSafe(SetProperty.class);
private static final ClassNode SIMPLE_BOOLEAN_PROPERTY_CNODE = makeClassSafe(SimpleBooleanProperty.class);
private static final ClassNode SIMPLE_DOUBLE_PROPERTY_CNODE = makeClassSafe(SimpleDoubleProperty.class);
private static final ClassNode SIMPLE_FLOAT_PROPERTY_CNODE = makeClassSafe(SimpleFloatProperty.class);
private static final ClassNode SIMPLE_INT_PROPERTY_CNODE = makeClassSafe(SimpleIntegerProperty.class);
private static final ClassNode SIMPLE_LONG_PROPERTY_CNODE = makeClassSafe(SimpleLongProperty.class);
private static final ClassNode SIMPLE_STRING_PROPERTY_CNODE = makeClassSafe(SimpleStringProperty.class);
private static final ClassNode SIMPLE_LIST_PROPERTY_CNODE = makeClassSafe(SimpleListProperty.class);
private static final ClassNode SIMPLE_MAP_PROPERTY_CNODE = makeClassSafe(SimpleMapProperty.class);
private static final ClassNode SIMPLE_SET_PROPERTY_CNODE = makeClassSafe(SimpleSetProperty.class);
private static final ClassNode SIMPLE_OBJECT_PROPERTY_CNODE = makeClassSafe(SimpleObjectProperty.class);
private static final ClassNode OBSERVABLE_LIST_CNODE = makeClassSafe(ObservableList.class);
private static final ClassNode OBSERVABLE_MAP_CNODE = makeClassSafe(ObservableMap.class);
private static final ClassNode OBSERVABLE_SET_CNODE = makeClassSafe(ObservableSet.class);
private static final ClassNode FXCOLLECTIONS_CNODE = makeClassSafe(FXCollections.class);
private static final ClassNode SET_TYPE = makeClassSafe(Set.class);
private static final Map<ClassNode, ClassNode> PROPERTY_TYPE_MAP = new HashMap<>();
static {
PROPERTY_TYPE_MAP.put(ClassHelper.STRING_TYPE, STRING_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.boolean_TYPE, BOOLEAN_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.Boolean_TYPE, BOOLEAN_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.double_TYPE, DOUBLE_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.Double_TYPE, DOUBLE_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.float_TYPE, FLOAT_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.Float_TYPE, FLOAT_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.int_TYPE, INT_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.Integer_TYPE, INT_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.long_TYPE, LONG_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.Long_TYPE, LONG_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.short_TYPE, INT_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.Short_TYPE, INT_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.byte_TYPE, INT_PROPERTY_CNODE);
PROPERTY_TYPE_MAP.put(ClassHelper.Byte_TYPE, INT_PROPERTY_CNODE);
//PROPERTY_TYPE_MAP.put(ClassHelper.char_TYPE, INT_PROPERTY_CNODE);
//PROPERTY_TYPE_MAP.put(ClassHelper.Character_TYPE, INT_PROPERTY_CNODE);
}
private static final Map<ClassNode, ClassNode> PROPERTY_IMPL_MAP = new HashMap<ClassNode, ClassNode>();
static {
PROPERTY_IMPL_MAP.put(BOOLEAN_PROPERTY_CNODE, SIMPLE_BOOLEAN_PROPERTY_CNODE);
PROPERTY_IMPL_MAP.put(DOUBLE_PROPERTY_CNODE, SIMPLE_DOUBLE_PROPERTY_CNODE);
PROPERTY_IMPL_MAP.put(FLOAT_PROPERTY_CNODE, SIMPLE_FLOAT_PROPERTY_CNODE);
PROPERTY_IMPL_MAP.put(INT_PROPERTY_CNODE, SIMPLE_INT_PROPERTY_CNODE);
PROPERTY_IMPL_MAP.put(LONG_PROPERTY_CNODE, SIMPLE_LONG_PROPERTY_CNODE);
PROPERTY_IMPL_MAP.put(STRING_PROPERTY_CNODE, SIMPLE_STRING_PROPERTY_CNODE);
PROPERTY_IMPL_MAP.put(LIST_PROPERTY_CNODE, SIMPLE_LIST_PROPERTY_CNODE);
PROPERTY_IMPL_MAP.put(MAP_PROPERTY_CNODE, SIMPLE_MAP_PROPERTY_CNODE);
PROPERTY_IMPL_MAP.put(SET_PROPERTY_CNODE, SIMPLE_SET_PROPERTY_CNODE);
//PROPERTY_IMPL_MAP.put(OBJECT_PROPERTY_CNODE, SIMPLE_OBJECT_PROPERTY_CNODE);
}
/**
* Convenience method to see if an annotated node is {@code @FXObservable}.
*
* @param node the node to check
* @return true if the node is observable
*/
public static boolean hasFXObservableAnnotation(AnnotatedNode node) {
for (AnnotationNode annotation : node.getAnnotations()) {
if (FXOBSERVABLE_CNODE.equals(annotation.getClassNode())) {
return true;
}
}
return false;
}
/**
* This ASTTransformation method is called when the compiler encounters our annotation.
*
* @param nodes An array of nodes. Index 0 is the annotation that triggered the call, index 1
* is the annotated node.
* @param sourceUnit The SourceUnit describing the source code in which the annotation was placed.
*/
@Override
public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
if (!(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
throw new IllegalArgumentException("Internal error: wrong types: "
+ nodes[0].getClass().getName() + " / " + nodes[1].getClass().getName());
}
AnnotationNode node = (AnnotationNode) nodes[0];
AnnotatedNode parent = (AnnotatedNode) nodes[1];
ClassNode declaringClass = parent.getDeclaringClass();
if (parent instanceof FieldNode) {
int modifiers = ((FieldNode) parent).getModifiers();
if ((modifiers & Modifier.FINAL) != 0) {
String msg = "@griffon.transform.FXObservable cannot annotate a final property.";
generateSyntaxErrorMessage(sourceUnit, node, msg);
}
addJavaFXProperty(sourceUnit, node, declaringClass, (FieldNode) parent);
} else {
addJavaFXPropertyToClass(sourceUnit, node, (ClassNode) parent);
}
}
/**
* Adds a JavaFX property to the class in place of the original Groovy property. A pair of synthetic
* getter/setter methods is generated to provide pseudo-access to the original property.
*
* @param source The SourceUnit in which the annotation was found
* @param node The node that was annotated
* @param declaringClass The class in which the annotation was found
* @param field The field upon which the annotation was placed
*/
private void addJavaFXProperty(SourceUnit source, AnnotationNode node, ClassNode declaringClass, FieldNode field) {
String fieldName = field.getName();
for (PropertyNode propertyNode : declaringClass.getProperties()) {
if (propertyNode.getName().equals(fieldName)) {
if (field.isStatic()) {
String message = "@griffon.transform.FXObservable cannot annotate a static property.";
generateSyntaxErrorMessage(source, node, message);
} else {
createPropertyGetterSetter(declaringClass, propertyNode);
}
return;
}
}
String message = "@griffon.transform.FXObservable must be on a property, not a field. Try removing the private, " +
"protected, or public modifier.";
generateSyntaxErrorMessage(source, node, message);
}
/**
* Iterate through the properties of the class and convert each eligible property to a JavaFX property.
*
* @param source The SourceUnit
* @param node The AnnotationNode
* @param classNode The declaring class
*/
private void addJavaFXPropertyToClass(SourceUnit source, AnnotationNode node, ClassNode classNode) {
for (PropertyNode propertyNode : classNode.getProperties()) {
FieldNode field = propertyNode.getField();
// look to see if per-field handlers will catch this one...
if (hasFXObservableAnnotation(field)
|| ((field.getModifiers() & Modifier.FINAL) != 0)
|| field.isStatic()) {
// explicitly labeled properties are already handled,
// don't transform final properties
// don't transform static properties
continue;
}
createPropertyGetterSetter(classNode, propertyNode);
}
}
/**
* Creates the JavaFX property and three methods for accessing the property and a pair of
* getter/setter methods for accessing the original (now synthetic) Groovy property. For
* example, if the original property was "String firstName" then these three methods would
* be generated:
* <p>
* public String getFirstName()
* public void setFirstName(String value)
* public StringProperty firstNameProperty()
*
* @param classNode The declaring class in which the property will appear
* @param originalProp The original Groovy property
*/
private void createPropertyGetterSetter(ClassNode classNode, PropertyNode originalProp) {
Expression initExp = originalProp.getInitialExpression();
originalProp.getField().setInitialValueExpression(null);
PropertyNode fxProperty = createFXProperty(originalProp);
List<AnnotationNode> methodAnnotations = new ArrayList<>();
List<AnnotationNode> fieldAnnotations = new ArrayList<>();
for (AnnotationNode annotation : originalProp.getField().getAnnotations()) {
if (FXOBSERVABLE_CNODE.equals(annotation.getClassNode())) continue;
Class annotationClass = annotation.getClassNode().getTypeClass();
Target target = (Target) annotationClass.getAnnotation(Target.class);
if (isTargetAllowed(target, ElementType.METHOD)) {
methodAnnotations.add(annotation);
} else if (isTargetAllowed(target, ElementType.FIELD)) {
fieldAnnotations.add(annotation);
}
}
String getterName = "get" + MetaClassHelper.capitalize(originalProp.getName());
if (classNode.getMethods(getterName).isEmpty()) {
Statement getterBlock = createGetterStatement(createFXProperty(originalProp));
createGetterMethod(classNode, originalProp, getterName, getterBlock, methodAnnotations);
methodAnnotations = null;
} else {
wrapGetterMethod(classNode, originalProp.getName(), methodAnnotations);
methodAnnotations = null;
}
String setterName = "set" + MetaClassHelper.capitalize(originalProp.getName());
if (classNode.getMethods(setterName).isEmpty()) {
Statement setterBlock = createSetterStatement(createFXProperty(originalProp));
createSetterMethod(classNode, originalProp, setterName, setterBlock, methodAnnotations);
} else {
wrapSetterMethod(classNode, originalProp.getName(), methodAnnotations);
}
// We want the actual name of the field to be different from the getter (Prop vs Property) so
// that the getter takes precedence when we say this.somethingProperty.
FieldNode fxFieldShortName = createFieldNodeCopy(originalProp.getName() + "Prop", null, fxProperty.getField());
createPropertyAccessor(classNode, createFXProperty(originalProp), fxFieldShortName, initExp);
classNode.removeField(originalProp.getName());
classNode.addField(fxFieldShortName);
fxFieldShortName.addAnnotations(fieldAnnotations);
}
private boolean isTargetAllowed(Target target, ElementType elementType) {
if (target == null) {
return false;
}
for (ElementType et : target.value()) {
if (et == elementType) {
return true;
}
}
return false;
}
/**
* Creates a new PropertyNode for the JavaFX property based on the original property. The new property
* will have "Property" appended to its name and its type will be one of the *Property types in JavaFX.
*
* @param orig The original property
* @return A new PropertyNode for the JavaFX property
*/
private PropertyNode createFXProperty(PropertyNode orig) {
ClassNode origType = orig.getType();
ClassNode newType = PROPERTY_TYPE_MAP.get(origType);
// For the ObjectProperty, we need to add the generic type to it.
if (newType == null) {
if (isTypeCompatible(ClassHelper.LIST_TYPE, origType) || isTypeCompatible(OBSERVABLE_LIST_CNODE, origType)) {
newType = makeClassSafe(SIMPLE_LIST_PROPERTY_CNODE);
GenericsType[] genericTypes = origType.getGenericsTypes();
newType.setGenericsTypes(genericTypes);
} else if (isTypeCompatible(ClassHelper.MAP_TYPE, origType) || isTypeCompatible(OBSERVABLE_MAP_CNODE, origType)) {
newType = makeClassSafe(SIMPLE_MAP_PROPERTY_CNODE);
GenericsType[] genericTypes = origType.getGenericsTypes();
newType.setGenericsTypes(genericTypes);
} else if (isTypeCompatible(SET_TYPE, origType) || isTypeCompatible(OBSERVABLE_SET_CNODE, origType)) {
newType = makeClassSafe(SIMPLE_SET_PROPERTY_CNODE);
GenericsType[] genericTypes = origType.getGenericsTypes();
newType.setGenericsTypes(genericTypes);
} else { // Object Type
newType = makeClassSafe(OBJECT_PROPERTY_CNODE);
ClassNode genericType = origType;
if (genericType.isPrimaryClassNode()) {
genericType = ClassHelper.getWrapper(genericType);
}
newType.setGenericsTypes(new GenericsType[]{new GenericsType(genericType)});
}
}
FieldNode fieldNode = createFieldNodeCopy(orig.getName() + "Property", newType, orig.getField());
return new PropertyNode(fieldNode, orig.getModifiers(), orig.getGetterBlock(), orig.getSetterBlock());
}
private boolean isTypeCompatible(ClassNode base, ClassNode target) {
return target.equals(base) || target.implementsInterface(base) || target.declaresInterface(base);
}
/**
* Creates a setter method and adds it to the declaring class. The setter has the form:
* <p>
* void <setter>(<type> fieldName)
*
* @param declaringClass The class to which the method is added
* @param propertyNode The property node being accessed by this setter
* @param setterName The name of the setter method
* @param setterBlock The code body of the method
*/
protected void createSetterMethod(ClassNode declaringClass, PropertyNode propertyNode, String setterName,
Statement setterBlock, List<AnnotationNode> annotations) {
Parameter[] setterParameterTypes = {new Parameter(propertyNode.getType(), "value")};
int mod = propertyNode.getModifiers() | Opcodes.ACC_FINAL;
MethodNode setter = new MethodNode(setterName, mod, ClassHelper.VOID_TYPE, setterParameterTypes,
ClassNode.EMPTY_ARRAY, setterBlock);
if (annotations != null) setter.addAnnotations(annotations);
setter.setSynthetic(true);
declaringClass.addMethod(setter);
}
/**
* If the setter already exists, this method should wrap it with our code and then a call to the original
* setter.
* <p>
* TODO: Not implemented yet
*
* @param classNode The declaring class to which the method will be added
* @param propertyName The name of the original Groovy property
*/
private void wrapSetterMethod(ClassNode classNode, String propertyName, List<AnnotationNode> annotations) {
System.out.println(
String.format("wrapSetterMethod for '%s', property '%s' not yet implemented",
classNode.getName(), propertyName));
}
/**
* Creates a getter method and adds it to the declaring class.
*
* @param declaringClass The class to which the method is added
* @param propertyNode The property node being accessed by this getter
* @param getterName The name of the getter method
* @param getterBlock The code body of the method
*/
protected void createGetterMethod(ClassNode declaringClass, PropertyNode propertyNode, String getterName,
Statement getterBlock, List<AnnotationNode> annotations) {
int mod = propertyNode.getModifiers() | Opcodes.ACC_FINAL;
MethodNode getter = new MethodNode(getterName, mod, propertyNode.getType(), Parameter.EMPTY_ARRAY,
ClassNode.EMPTY_ARRAY, getterBlock);
if (annotations != null) getter.addAnnotations(annotations);
getter.setSynthetic(true);
declaringClass.addMethod(getter);
}
/**
* If the getter already exists, this method should wrap it with our code.
* <p>
* TODO: Not implemented yet -- what to do with the returned value from the original getter?
*
* @param classNode The declaring class to which the method will be added
* @param propertyName The name of the original Groovy property
*/
private void wrapGetterMethod(ClassNode classNode, String propertyName, List<AnnotationNode> annotations) {
System.out.println(
String.format("wrapGetterMethod for '%s', property '%s' not yet implemented",
classNode.getName(), propertyName));
}
/**
* Creates the body of a property access method that returns the JavaFX *Property instance. If
* the original property was "String firstName" then the generated code would be:
* <p>
* if (firstNameProperty == null) {
* firstNameProperty = new javafx.beans.property.StringProperty()
* }
* return firstNameProperty
*
* @param classNode The declaring class to which the JavaFX property will be added
* @param fxProperty The new JavaFX property
* @param fxFieldShortName
* @param initExp The initializer expression from the original Groovy property declaration
*/
private void createPropertyAccessor(ClassNode classNode, PropertyNode fxProperty, FieldNode fxFieldShortName,
Expression initExp) {
FieldExpression fieldExpression = new FieldExpression(fxFieldShortName);
ArgumentListExpression ctorArgs = initExp == null ?
ArgumentListExpression.EMPTY_ARGUMENTS :
new ArgumentListExpression(initExp);
BlockStatement block = new BlockStatement();
ClassNode fxType = fxProperty.getType();
ClassNode implNode = PROPERTY_IMPL_MAP.get(fxType);
if (implNode == null) {
if (fxType.getTypeClass() == SIMPLE_LIST_PROPERTY_CNODE.getTypeClass()) {
if (initExp != null) {
if (initExp instanceof ListExpression ||
(initExp instanceof CastExpression &&
(((CastExpression) initExp).getType().equals(LIST_TYPE) ||
((CastExpression) initExp).getType().declaresInterface(LIST_TYPE))) ||
(initExp instanceof ConstructorCallExpression &&
(((ConstructorCallExpression) initExp).getType().equals(LIST_TYPE) ||
((ConstructorCallExpression) initExp).getType().declaresInterface(LIST_TYPE)))
) {
ctorArgs = new ArgumentListExpression(
new MethodCallExpression(
new ClassExpression(FXCOLLECTIONS_CNODE),
"observableList",
ctorArgs)
);
}
}
implNode = fxType;
} else if (fxType.getTypeClass() == SIMPLE_MAP_PROPERTY_CNODE.getTypeClass()) {
if (initExp != null) {
if (initExp instanceof MapExpression ||
(initExp instanceof CastExpression &&
(((CastExpression) initExp).getType().equals(MAP_TYPE) ||
((CastExpression) initExp).getType().declaresInterface(MAP_TYPE))) ||
(initExp instanceof ConstructorCallExpression &&
(((ConstructorCallExpression) initExp).getType().equals(MAP_TYPE) ||
((ConstructorCallExpression) initExp).getType().declaresInterface(MAP_TYPE)))
) {
ctorArgs = new ArgumentListExpression(
new MethodCallExpression(
new ClassExpression(FXCOLLECTIONS_CNODE),
"observableMap",
ctorArgs)
);
}
}
implNode = fxType;
} else if (fxType.getTypeClass() == SIMPLE_SET_PROPERTY_CNODE.getTypeClass()) {
if (initExp != null) {
if ((initExp instanceof CastExpression &&
(((CastExpression) initExp).getType().equals(SET_TYPE) ||
((CastExpression) initExp).getType().declaresInterface(SET_TYPE))) ||
(initExp instanceof ConstructorCallExpression &&
(((ConstructorCallExpression) initExp).getType().equals(SET_TYPE) ||
((ConstructorCallExpression) initExp).getType().declaresInterface(SET_TYPE)))
) {
ctorArgs = new ArgumentListExpression(
new MethodCallExpression(
new ClassExpression(FXCOLLECTIONS_CNODE),
"observableSet",
ctorArgs)
);
}
}
implNode = fxType;
} else {
implNode = makeClassSafe(SIMPLE_OBJECT_PROPERTY_CNODE);
GenericsType[] origGenerics = fxProperty.getType().getGenericsTypes();
//List<GenericsType> copyGenericTypes = new ArrayList<GenericsType>();
//for()
implNode.setGenericsTypes(origGenerics);
}
}
Expression initExpression = new ConstructorCallExpression(implNode, ctorArgs);
IfStatement ifStmt = new IfStatement(
new BooleanExpression(
new BinaryExpression(
fieldExpression,
Token.newSymbol(Types.COMPARE_EQUAL, 0, 0),
ConstantExpression.NULL
)
),
new ExpressionStatement(
new BinaryExpression(
fieldExpression,
Token.newSymbol(Types.EQUAL, 0, 0),
initExpression
)
),
EmptyStatement.INSTANCE
);
block.addStatement(ifStmt);
block.addStatement(new ReturnStatement(fieldExpression));
String getterName = getFXPropertyGetterName(fxProperty);
MethodNode accessor = new MethodNode(getterName, fxProperty.getModifiers(), fxProperty.getType(),
Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, block);
accessor.setSynthetic(true);
classNode.addMethod(accessor);
// Create the xxxxProperty() method that merely calls getXxxxProperty()
block = new BlockStatement();
VariableExpression thisExpression = VariableExpression.THIS_EXPRESSION;
ArgumentListExpression emptyArguments = ArgumentListExpression.EMPTY_ARGUMENTS;
MethodCallExpression getProperty = new MethodCallExpression(thisExpression, getterName, emptyArguments);
block.addStatement(new ReturnStatement(getProperty));
String javaFXPropertyFunction = fxProperty.getName();
accessor = new MethodNode(javaFXPropertyFunction, fxProperty.getModifiers(), fxProperty.getType(),
Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, block);
accessor.setSynthetic(true);
classNode.addMethod(accessor);
// Create the xxxx() method that merely calls getXxxxProperty()
block = new BlockStatement();
thisExpression = VariableExpression.THIS_EXPRESSION;
emptyArguments = ArgumentListExpression.EMPTY_ARGUMENTS;
getProperty = new MethodCallExpression(thisExpression, getterName, emptyArguments);
block.addStatement(new ReturnStatement(getProperty));
javaFXPropertyFunction = fxProperty.getName().replace("Property", "");
accessor = new MethodNode(javaFXPropertyFunction, fxProperty.getModifiers(), fxProperty.getType(),
Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, block);
accessor.setSynthetic(true);
classNode.addMethod(accessor);
}
/**
* Creates the body of a setter method for the original property that is actually backed by a
* JavaFX *Property instance:
* <p>
* Object $property = this.someProperty()
* $property.setValue(value)
*
* @param fxProperty The original Groovy property that we're creating a setter for.
* @return A Statement that is the body of the new setter.
*/
protected Statement createSetterStatement(PropertyNode fxProperty) {
String fxPropertyGetter = getFXPropertyGetterName(fxProperty);
VariableExpression thisExpression = VariableExpression.THIS_EXPRESSION;
ArgumentListExpression emptyArgs = ArgumentListExpression.EMPTY_ARGUMENTS;
MethodCallExpression getProperty = new MethodCallExpression(thisExpression, fxPropertyGetter, emptyArgs);
ArgumentListExpression valueArg = new ArgumentListExpression(new Expression[]{new VariableExpression("value")});
MethodCallExpression setValue = new MethodCallExpression(getProperty, "setValue", valueArg);
return new ExpressionStatement(setValue);
}
/**
* Creates the body of a getter method for the original property that is actually backed by a
* JavaFX *Property instance:
* <p>
* Object $property = this.someProperty()
* return $property.getValue()
*
* @param fxProperty The new JavaFX property.
* @return A Statement that is the body of the new getter.
*/
protected Statement createGetterStatement(PropertyNode fxProperty) {
String fxPropertyGetter = getFXPropertyGetterName(fxProperty);
VariableExpression thisExpression = VariableExpression.THIS_EXPRESSION;
ArgumentListExpression emptyArguments = ArgumentListExpression.EMPTY_ARGUMENTS;
// We're relying on the *Property() method to provide the return value - is this still needed??
// Expression defaultReturn = defaultReturnMap.get(originalProperty.getType());
// if (defaultReturn == null)
// defaultReturn = ConstantExpression.NULL;
MethodCallExpression getProperty = new MethodCallExpression(thisExpression, fxPropertyGetter, emptyArguments);
MethodCallExpression getValue = new MethodCallExpression(getProperty, "getValue", emptyArguments);
return new ReturnStatement(new ExpressionStatement(getValue));
}
/**
* Generates a SyntaxErrorMessage based on the current SourceUnit, AnnotationNode, and a specified
* error message.
*
* @param sourceUnit The SourceUnit
* @param node The node that was annotated
* @param msg The error message to display
*/
private void generateSyntaxErrorMessage(SourceUnit sourceUnit, AnnotationNode node, String msg) {
SyntaxException error = new SyntaxException(msg, node.getLineNumber(), node.getColumnNumber());
sourceUnit.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(error, sourceUnit));
}
/**
* Creates a copy of a FieldNode with a new name and, optionally, a new type.
*
* @param newName The name for the new field node.
* @param newType The new type of the field. If null, the old FieldNode's type will be used.
* @param f The FieldNode to copy.
* @return The new FieldNode.
*/
private FieldNode createFieldNodeCopy(String newName, ClassNode newType, FieldNode f) {
if (newType == null)
newType = f.getType();
newType = newType.getPlainNodeReference();
return new FieldNode(newName, f.getModifiers(), newType, f.getOwner(), f.getInitialValueExpression());
}
/**
* Generates the correct getter method name for a JavaFX property.
*
* @param fxProperty The property for which the getter should be generated.
* @return The getter name as a String.
*/
private String getFXPropertyGetterName(PropertyNode fxProperty) {
return GriffonNameUtils.getGetterName(fxProperty.getName());
}
}