jaxx.compiler.binding.DataSource Maven / Gradle / Ivy
/*
* #%L
* JAXX :: Compiler
*
* $Id: DataSource.java 2379 2012-07-04 16:02:22Z tchemit $
* $HeadURL: http://svn.nuiton.org/svn/jaxx/tags/jaxx-2.5.19/jaxx-compiler/src/main/java/jaxx/compiler/binding/DataSource.java $
* %%
* Copyright (C) 2008 - 2010 CodeLutin
* %%
* 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 jaxx.compiler.binding;
import jaxx.compiler.CompiledObject;
import jaxx.compiler.CompilerException;
import jaxx.compiler.JAXXCompiler;
import jaxx.compiler.UnsupportedAttributeException;
import jaxx.compiler.finalizers.JAXXCompilerFinalizer;
import jaxx.compiler.java.JavaElementFactory;
import jaxx.compiler.java.JavaFileGenerator;
import jaxx.compiler.java.JavaMethod;
import jaxx.compiler.java.parser.JavaParser;
import jaxx.compiler.java.parser.JavaParserConstants;
import jaxx.compiler.java.parser.JavaParserTreeConstants;
import jaxx.compiler.java.parser.SimpleNode;
import jaxx.compiler.reflect.ClassDescriptor;
import jaxx.compiler.reflect.ClassDescriptorHelper;
import jaxx.compiler.reflect.FieldDescriptor;
import jaxx.compiler.reflect.MethodDescriptor;
import jaxx.compiler.tags.DefaultObjectHandler;
import jaxx.compiler.tags.TagManager;
import jaxx.compiler.types.TypeManager;
import jaxx.runtime.JAXXUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.beans.Introspector;
import java.io.StringReader;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents a Java expression which fires a PropertyChangeEvent
when it can be
* determined that its value may have changed. Events are fired on a "best effort" basis, and events
* may either be fired too often (the value has not actually changed) or not often enough (the value
* changed but no event was fired).
*/
public class DataSource {
/** Logger */
protected static final Log log = LogFactory.getLog(DataSource.class);
/** type attached to "null" constants in parsed expressions */
private class NULL {
}
/** id of data source */
private final String id;
/** Constant id */
protected final String constantId;
/** The Java source code for the expression. */
private final String source;
/** The current JAXXCompiler
. */
private final JAXXCompiler compiler;
/** List of detected tracker (if none found, it is not a data binding) */
private final List trackers;
/** the delegate of property to be required */
private String objectCode;
protected final List methods;
/**
* Creates a new data source. After creating a DataSource
, use {@link #compile()}
* to cause it to function at runtime.
*
* @param id the DataSource's id
* @param constantId the DataSource constant id
* @param source the Java source code for the data source expression
* @param compiler the current JAXXCompiler
* @param methods where to store extra method to add to binding
*/
public DataSource(String id,
String constantId,
String source,
JAXXCompiler compiler,
List methods) {
this.id = id;
this.constantId = constantId;
this.source = source;
this.compiler = compiler;
this.methods = methods;
trackers = new ArrayList();
}
public String getObjectCode() {
return objectCode;
}
public DataListener[] getTrackers() {
return trackers.toArray(new DataListener[trackers.size()]);
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
b.append("source:id", id);
b.append("source:source", source);
b.append("source:objectCode", getObjectCode());
if (!trackers.isEmpty()) {
b.append("source:trackers", trackers.size());
for (DataListener d : trackers) {
b.append("source:tracker", d);
}
}
return b.toString();
}
public boolean showLog() {
// return DataBindingHelper.SHOW_LOG || log.isDebugEnabled();
return DataBindingHelper.SHOW_LOG;
}
/**
* Compiles the data source expression and listener. This method calls methods in JAXXCompiler
* to add the Java code that performs the data source setup. Adding listeners to DataSource
is
* slightly more complicated than with ordinary classes, because DataSource
only exists at compile
* time. You must pass in a Java expression which evaluates to a PropertyChangeListener
; this
* expression will be compiled and evaluated at runtime to yield the DataSource's
listener.
*
* @return true
if the expression has dependencies, false
otherwise
* @throws CompilerException if a compilation error occurs
* @throws IllegalStateException if data source was already compiled
*/
protected boolean compile() throws CompilerException, IllegalStateException {
if (showLog()) {
log.info("======= Start compile of " + source);
}
JavaParser p = new JavaParser(new StringReader(source));
// detect all expressions to treate
Map> expressions = new LinkedHashMap>();
Map> castsExpressions = new LinkedHashMap>();
List literalExpressions = new ArrayList();
while (!p.Line()) {
SimpleNode node = p.popNode();
if (log.isTraceEnabled()) {
log.trace("will scan node " + node.getText());
}
JavaParserUtil.getExpressions(node, expressions, literalExpressions, castsExpressions);
}
// remove literal expressions
JavaParserUtil.removeLiteralExpressions(expressions, literalExpressions);
literalExpressions.clear();
// remove expressions with dependencies
JavaParserUtil.removeNoneStandaloneExpressions(expressions, castsExpressions);
// scan accepted expressions to detect dependencies and track listeners
for (SimpleNode node : expressions.keySet()) {
if (showLog()) {
log.info("Will parse expression " + node.getText());
}
scanNode(node);
}
if (log.isDebugEnabled()) {
log.debug("trackers=" + trackers);
}
boolean isBinding = !trackers.isEmpty();
if (isBinding) {
Set result = JavaParserUtil.getRequired(expressions.keySet(), castsExpressions);
if (result == null || result.isEmpty()) {
// no requirements
objectCode = "";
} else {
// build the fully test from requirements
StringBuilder buffer = new StringBuilder();
Iterator itr = result.iterator();
overrideIds = new HashSet();
String realSource = getJavaCode(itr.next());
buffer.append(realSource).append(" != null");
while (itr.hasNext()) {
realSource = getJavaCode(itr.next());
buffer.append(" && ").append(realSource).append(" != null");
}
objectCode = buffer.toString().trim();
}
}
castsExpressions.clear();
expressions.clear();
return isBinding;
}
private Set overrideIds;
private String getJavaCode(String s) {
CompiledObject o = compiler.getCompiledObject(s);
if (o != null && o.isOverride()) {
if (showLog()) {
log.info("Use an override identifier : " + o.getJavaCode());
}
overrideIds.add(s);
}
return s;
}
public Set getOverrideIds() {
return overrideIds;
}
/**
* Examines a node to identify any dependencies it contains.
*
* @param node node to scan
* @throws CompilerException ?
*/
private void scanNode(SimpleNode node) throws CompilerException {
if (node.getId() == JavaParserTreeConstants.JJTMETHODDECLARATION ||
node.getId() == JavaParserTreeConstants.JJTFIELDDECLARATION) {
return;
}
if (log.isTraceEnabled()) {
log.trace(node.getText());
}
int count = node.jjtGetNumChildren();
for (int i = 0; i < count; i++) {
scanNode(node.getChild(i));
}
// determine node type
ClassDescriptor type = null;
if (node.jjtGetNumChildren() == 1) {
type = node.getChild(0).getJavaType();
}
switch (node.getId()) {
case JavaParserTreeConstants.JJTCLASSORINTERFACETYPE:
type = ClassDescriptorHelper.getClassDescriptor(Class.class);
break;
case JavaParserTreeConstants.JJTPRIMARYEXPRESSION:
type = determineExpressionType(node);
if (log.isDebugEnabled()) {
log.debug("result of determineExpressionType for " + node.getText() + " = " + type);
}
break;
case JavaParserTreeConstants.JJTLITERAL:
type = determineLiteralType(node);
break;
case JavaParserTreeConstants.JJTCASTEXPRESSION:
type = TagManager.resolveClass(node.getChild(0).getText(), compiler);
break;
}
node.setJavaType(type);
}
/**
* Adds type information to nodes where possible, and as a side effect adds event listeners to nodes which
* can be tracked.
*
* @param expression the node to scan
* @return the class descriptor of the return type or null
*/
private ClassDescriptor determineExpressionType(SimpleNode expression) {
assert expression.getId() == JavaParserTreeConstants.JJTPRIMARYEXPRESSION;
SimpleNode prefix = expression.getChild(0);
if (log.isDebugEnabled()) {
log.debug("for expression " + expression.getText() + " - prefix " + prefix + " - nb childrens of prefix: " + prefix.jjtGetNumChildren() + ", nb childrens of expression : " + expression.jjtGetNumChildren());
}
if (prefix.jjtGetNumChildren() == 1) {
int type = prefix.getChild(0).getId();
if (type == JavaParserTreeConstants.JJTLITERAL || type == JavaParserTreeConstants.JJTEXPRESSION) {
prefix.setJavaType(prefix.getChild(0).getJavaType());
} else if (type == JavaParserTreeConstants.JJTNAME && expression.jjtGetNumChildren() == 1) {
// name with no arguments after it
ClassDescriptor classDescriptor = scanCompoundSymbol(prefix.getText().trim(), compiler.getRootObject().getObjectClass(), false);
if (log.isTraceEnabled()) {
log.trace("scanCompoundSymbol result for node " + prefix.getText().trim() + " = " + classDescriptor);
}
prefix.setJavaType(classDescriptor);
}
}
if (expression.jjtGetNumChildren() == 1) {
return prefix.getJavaType();
}
ClassDescriptor contextClass = prefix.getJavaType();
if (contextClass == null) {
contextClass = compiler.getRootObject().getObjectClass();
}
String lastNode = prefix.getText().trim();
for (int i = 1; i < expression.jjtGetNumChildren(); i++) {
SimpleNode suffix = expression.getChild(i);
if (suffix.jjtGetNumChildren() == 1 && suffix.getChild(0).getId() == JavaParserTreeConstants.JJTARGUMENTS) {
if (suffix.getChild(0).jjtGetNumChildren() == 0) {
// at the moment only no-argument methods are trackable
contextClass = scanCompoundSymbol(lastNode, contextClass, true);
if (log.isTraceEnabled()) {
log.trace("scanCompoundSymbol result for node " + lastNode + " = " + contextClass);
}
if (contextClass == null) {
return null;
}
int dotPos = lastNode.lastIndexOf(".");
String code = dotPos == -1 ? "" : lastNode.substring(0, dotPos);
for (int j = i - 2; j >= 0; j--) {
code = expression.getChild(j).getText() + code;
}
if (code.length() == 0) {
code = compiler.getRootObject().getJavaCode();
}
String methodName = lastNode.substring(dotPos + 1).trim();
if (log.isTraceEnabled()) {
log.trace("try to find type for method " + methodName + ", code : " + code);
}
try {
MethodDescriptor method = contextClass.getMethodDescriptor(methodName);
if (log.isDebugEnabled()) {
log.debug("Will trackMemberIfPossible from method " + method.getName() + " with objectCode = " + code);
}
trackMemberIfPossible(code, contextClass, method.getName(), true);
if (log.isTraceEnabled()) {
log.trace("method found = " + method);
}
return getMethodReturnType(contextClass, method);
} catch (NoSuchMethodException e) {
if (showLog()) {
log.info("Could not find method " + methodName + ", code : " + code + " on : " + contextClass);
if (log.isDebugEnabled()) {
for (MethodDescriptor descriptor : contextClass.getMethodDescriptors()) {
log.debug(" - " + Modifier.toString(descriptor.getModifiers()) + " " + descriptor.getName() + "(...) : " + descriptor.getReturnType());
}
}
}
// happens for methods defined in the current JAXX file via scripts
String propertyName = null;
if (methodName.startsWith("is")) {
propertyName = Introspector.decapitalize(methodName.substring("is".length()));
} else if (methodName.startsWith("get")) {
propertyName = Introspector.decapitalize(methodName.substring("get".length()));
}
if (propertyName != null) {
//TC-20091026 use the getScriptMethod from compiler
MethodDescriptor newMethod = compiler.getScriptMethod(methodName);
if (newMethod != null) {
//TC-20091202 must suffix dependency by property, otherwise can not have two bindings
// on the same parent...
String bindingId = compiler.getRootObject().getId() + "." + propertyName;
if (log.isDebugEnabled()) {
log.debug("detect a dependency [" + bindingId + "] from a script method " + newMethod.getName() + ", will try to add a listener in method is part of javaBean ...");
}
// check this is a javaBean
CompiledObject compiledObject = compiler.getObjects().get(propertyName);
if (compiledObject != null && compiledObject.isJavaBean()) {
String objectCode = null;
if (showLog()) {
log.info("Detect a dependency from compiled object [" + objectCode + "]a script method '" + newMethod.getName() + "' which reflect a javaBean property " + propertyName);
log.info("Try to add a listener [symbol:" + bindingId + ",objectCode:" + objectCode + "]");
log.debug(">> lastnode = " + lastNode + "(), suffix = " + suffix.getText() + ", expression = " + expression.getText());
}
addListener(bindingId,
objectCode,
"addPropertyChangeListener(\"" + propertyName + "\", this);" + JAXXCompiler.getLineSeparator(),
"removePropertyChangeListener(\"" + propertyName + "\", this);" + JAXXCompiler.getLineSeparator());
}
contextClass = newMethod.getReturnType();
}
}
}
}
}
lastNode = suffix.getText().trim();
if (lastNode.startsWith(".")) {
lastNode = lastNode.substring(1);
}
}
return null;
}
private ClassDescriptor determineLiteralType(SimpleNode node) {
assert node.getId() == JavaParserTreeConstants.JJTLITERAL;
if (node.jjtGetNumChildren() == 1) {
int childId = node.getChild(0).getId();
if (childId == JavaParserTreeConstants.JJTBOOLEANLITERAL) {
return ClassDescriptorHelper.getClassDescriptor(boolean.class);
}
if (childId == JavaParserTreeConstants.JJTNULLLITERAL) {
return ClassDescriptorHelper.getClassDescriptor(NULL.class);
}
throw new RuntimeException("Expected BooleanLiteral or NullLiteral, found " + JavaParserTreeConstants.jjtNodeName[childId]);
}
int nodeId = node.firstToken.kind;
switch (nodeId) {
case JavaParserConstants.INTEGER_LITERAL:
if (node.firstToken.image.toLowerCase().endsWith("l")) {
return ClassDescriptorHelper.getClassDescriptor(long.class);
}
return ClassDescriptorHelper.getClassDescriptor(int.class);
case JavaParserConstants.CHARACTER_LITERAL:
return ClassDescriptorHelper.getClassDescriptor(char.class);
case JavaParserConstants.FLOATING_POINT_LITERAL:
if (node.firstToken.image.toLowerCase().endsWith("f")) {
return ClassDescriptorHelper.getClassDescriptor(float.class);
}
return ClassDescriptorHelper.getClassDescriptor(double.class);
case JavaParserConstants.STRING_LITERAL:
return ClassDescriptorHelper.getClassDescriptor(String.class);
default:
throw new RuntimeException("Expected literal token, found " + JavaParserConstants.tokenImage[nodeId]);
}
}
/**
* Scans through a compound symbol (foo.bar.baz) to identify and track all trackable pieces of it.
*
* @param symbol symbol to scan
* @param contextClass current class context
* @param isMethod flag to search a method
* @return the type of the symbol (or null if it could not be determined).
*/
private ClassDescriptor scanCompoundSymbol(String symbol, ClassDescriptor contextClass, boolean isMethod) {
String[] tokens = symbol.split("\\s*\\.\\s*");
if (log.isDebugEnabled()) {
log.debug("for symbol " + symbol + ", contextClass " + contextClass + ", isMethod " + isMethod);
log.debug("tokens " + Arrays.toString(tokens));
}
StringBuilder currentSymbol = new StringBuilder();
StringBuilder tokensSeenSoFar = new StringBuilder();
// if this ends up false, it means we weren't able to figure out
boolean accepted;
// which object the method is being invoked on
boolean recognizeClassNames = true;
for (int j = 0; j < tokens.length - (isMethod ? 1 : 0); j++) {
accepted = false;
if (tokensSeenSoFar.length() > 0) {
tokensSeenSoFar.append('.');
}
tokensSeenSoFar.append(tokens[j]);
if (currentSymbol.length() > 0) {
currentSymbol.append('.');
}
currentSymbol.append(tokens[j]);
if (log.isTraceEnabled()) {
log.trace("try to find type for " + currentSymbol);
}
if (currentSymbol.indexOf(".") == -1) {
String memberName = currentSymbol.toString();
CompiledObject object = compiler.getCompiledObject(memberName);
if (object != null) {
if (log.isTraceEnabled()) {
log.trace("detected an object " + object);
}
contextClass = object.getObjectClass();
currentSymbol.setLength(0);
accepted = true;
recognizeClassNames = false;
} else {
try {
FieldDescriptor field = contextClass.getFieldDescriptor(memberName);
if (log.isDebugEnabled()) {
log.debug("Will trackMemberIfPossible from field " + field.getName() + " with objectCode = " + tokensSeenSoFar.toString());
}
trackMemberIfPossible(tokensSeenSoFar.toString(), contextClass, field.getName(), false);
try {
contextClass = field.getType();
} catch (Exception e) {
log.warn("could not find type for field " + field);
throw new NoSuchFieldException(e.getMessage());
}
currentSymbol.setLength(0);
accepted = true;
recognizeClassNames = false;
} catch (NoSuchFieldException e) {
if (j == 0 || j == 1 && tokens[0].equals(compiler.getRootObject().getId())) {
// still in root context
FieldDescriptor newField = compiler.getScriptField(memberName);
if (newField != null) {
contextClass = newField.getType();
if (showLog()) {
log.info("Detect a dependency from a script field '" + newField + "'");
log.info("Try to add a listenenr [symbol:" + tokensSeenSoFar.toString() + ",objectCode:" + null + "]");
}
String eol = JAXXCompiler.getLineSeparator();
addListener(tokensSeenSoFar.toString(),
null,
"addPropertyChangeListener(\"" + memberName + "\", this);" + eol,
// "addPropertyChangeListener(\"" + memberName + "\", " + listenerId + ");" + eol,
"removePropertyChangeListener(\"" + memberName + "\", this);" + eol);
// "removePropertyChangeListener(\"" + memberName + "\", " + listenerId + ");" + eol);
assert contextClass != null : "script field '" + memberName + "' is defined, but has type null";
currentSymbol.setLength(0);
accepted = true;
recognizeClassNames = false;
}
}
}
}
}
if (currentSymbol.length() > 0 && recognizeClassNames) {
if (log.isDebugEnabled()) {
log.debug("Try to recognizeClassNames for symbol " + currentSymbol);
}
contextClass = TagManager.resolveClass(currentSymbol.toString(), compiler);
if (contextClass != null) {
currentSymbol.setLength(0);
//accepted = true;
//recognizeClassNames = false;
// TODO: for now we don't handle statics
return null;
}
}
if (!accepted) {
if (log.isDebugEnabled()) {
log.debug("symbol " + symbol + " was not accepted.");
}
return null;
}
}
return contextClass;
}
private void trackMemberIfPossible(String objectCode, ClassDescriptor objectClass, String memberName, boolean method) {
if (log.isDebugEnabled()) {
log.debug("for [objectCode:" + objectCode + ", objectClass:" + objectClass + ", memberName:" + memberName + ", isMethod:" + method);
}
DefaultObjectHandler handler = TagManager.getTagHandler(objectClass);
try {
if (handler.isMemberBound(memberName)) {
String bindingId = objectCode + "." + memberName + (method ? "()" : "");
if (showLog()) {
log.info("Detect a dependency from a event handler for memberName '" + memberName + "' for class " + objectClass);
log.info("Try to add a listener [symbol:" + bindingId + ", objectCode:" + objectCode + "]");
}
addListener(bindingId,
objectCode,
getAddMemberListenerCode(handler, objectCode, memberName, "this", compiler),
getRemoveMemberListenerCode(handler, objectCode, memberName, "this", compiler));
}
} catch (UnsupportedAttributeException e) {
// ignore -- this is thrown for methods like toString(), for which there is no tracking and
// no setting support
}
}
private void addListener(String dependencySymbol,
String objectCode,
String addCode,
String removeCode) {
if (objectCode != null) {
objectCode = objectCode.trim();
}
boolean needTest = objectCode != null &&
!compiler.getRootObject().getId().equals(objectCode);
if (!needTest) {
objectCode = null;
}
if (log.isDebugEnabled()) {
log.debug("try to add listener [dependencySymbol:" +
dependencySymbol + ", objectCode:" + objectCode +
", addCode:" + addCode + "]");
}
for (DataListener tracker : trackers) {
if (dependencySymbol.equals(tracker.getSymbol())) {
// listener already existing
return;
}
}
DataListener tracker = new DataListener(dependencySymbol,
objectCode,
addCode,
removeCode
);
if (log.isDebugEnabled()) {
log.debug("add tracker " + tracker);
}
trackers.add(tracker);
}
public boolean hasMethod(String methodName) {
for (JavaMethod method : methods) {
if (methodName.equals(method.getName())) {
return true;
}
}
return false;
}
/**
* Returns a snippet of Java code which will cause a PropertyChangeListener
to be notified
* when the member's value changes. The PropertyChangeListener
is provided in the form
* of a Java code snippet that evaluates to a listener object.
*
* For ordinary bound JavaBeans properties, the Java code returned is a simple call to
* addPropertyChangeListener
. Fields and methods which do not actually fire
* PropertyChangeEvents
when they change necessitate more complex code.
*
* @param handler Object handler (containts known events
* @param objectCode Java code which evaluates to the object to which to add the listener
* *@param dataBinding the name of the data binding this listener is a part of
* @param memberName the name of the field or method to listen to
* @param propertyChangeListenerCode Java code which evaluates to a PropertyChangeListener
* @param compiler the current JAXXCompiler
* @return Java code snippet which causes the listener to be added to the object
*/
public String getAddMemberListenerCode(DefaultObjectHandler handler,
String objectCode,
String memberName,
String propertyChangeListenerCode,
JAXXCompiler compiler) {
if ("getClass".equals(memberName)) {
return null;
}
DefaultObjectHandler.ProxyEventInfo eventInfo = handler.getEventInfo(memberName);
if (eventInfo != null) {
// a "proxied" event is one that doesn't fire PropertyChangeEvent, so we need to convert its native event type into PropertyChangeEvents
StringBuilder result = new StringBuilder();
String methodName = "$pr" + compiler.getUniqueId(propertyChangeListenerCode.equals("this") ? constantId : propertyChangeListenerCode);
boolean methodExists = hasMethod(methodName);
ClassDescriptor eventClass = DefaultObjectHandler.getEventClass(eventInfo.getListenerClass());
String type = compiler.getImportedType(JAXXCompiler.getCanonicalName(eventClass));
if (!methodExists) {
String code = JavaFileGenerator.addDebugLoggerInvocation(compiler, "event");
code += "propertyChange(null);";
JavaMethod method = JavaElementFactory.newMethod(
Modifier.PUBLIC,
JAXXCompilerFinalizer.TYPE_VOID,
methodName,
code,
false,
JavaElementFactory.newArgument(type, "event"));
methods.add(method);
}
String code = objectCode + (eventInfo.getModelName() != null ? ".get" + StringUtils.capitalize(eventInfo.getModelName()) + "()" : "");
result.append("$bindingSources.put(\"").append(code).append("\", ").append(code).append(");").append(JAXXCompiler.getLineSeparator());
//TC-20091105 JAXXUtil.getEventListener is generic, no more need cast and use simple listener name
ClassDescriptor listenerClass = eventInfo.getListenerClass();
String listenerType = compiler.getImportedType(listenerClass.getName());
String jaxxUtilPrefix = compiler.getImportedType(JAXXUtil.class);
result.append(code);
result.append('.');
result.append(eventInfo.getAddMethod());
result.append("( ").append(jaxxUtilPrefix).append(".getEventListener(");
result.append(listenerType);
result.append(".class, ");
result.append("this");
result.append(", ");
result.append(TypeManager.getJavaCode(methodName));
result.append("));");
result.append(JAXXCompiler.getLineSeparator());
if (eventInfo.getModelName() != null) {
String addCode = getAddMemberListenerCode(
handler,
objectCode,
"get" + StringUtils.capitalize(eventInfo.getModelName()),
jaxxUtilPrefix + ".getDataBindingUpdateListener(" + compiler.getOutputClassName() + ".this" + ", " + constantId + ")",
compiler
);
result.append(addCode);
}
return result.toString();
}
String propertyName = null;
if (memberName.startsWith("get")) {
propertyName = Introspector.decapitalize(memberName.substring(3));
} else if (memberName.startsWith("is")) {
propertyName = Introspector.decapitalize(memberName.substring(2));
} else {
try {
handler.getBeanClass().getFieldDescriptor(memberName);
propertyName = memberName;
} catch (NoSuchFieldException e) {
// ignore ?
}
}
if (propertyName != null) {
//TC-20091026 when on root object, do not prefix with objectCode
String prefix = objectCode.trim() + ".";
if (objectCode.equals(compiler.getRootObject().getJavaCode())) {
prefix = "";
}
//TC-20091203 : always use the property specific method, this is part of the javaBeans 1.1 norm
//TC-20091203 : if developpers do bad, shame on them...
return prefix + "addPropertyChangeListener(\"" + propertyName + "\", " + propertyChangeListenerCode + ");\n";
// try {
// // check for property-specific addPropertyChangeListener method
//// getBeanClass().getMethodDescriptor("addPropertyChangeListener", ClassDescriptorHelper.getClassDescriptor(String.class),
// handler.getBeanClass().getMethodDescriptor("addPropertyChangeListener", ClassDescriptorHelper.getClassDescriptor(String.class),
// ClassDescriptorHelper.getClassDescriptor(PropertyChangeListener.class));
// return prefix + "addPropertyChangeListener(\"" + propertyName + "\", " + propertyChangeListenerCode + ");\n";
// } catch (NoSuchMethodException e) {
// if (log.isInfoEnabled()) {
// log.info("Could not get named addPropertyChangeListener on class " + handler.getBeanClass());
// }
// // no property-specific method, use general one
// return prefix + "addPropertyChangeListener(" + propertyChangeListenerCode + ");\n";
// }
}
return null;
}
public String getRemoveMemberListenerCode(DefaultObjectHandler handler,
String objectCode,
String memberName,
String propertyChangeListenerCode,
JAXXCompiler compiler) {
if ("getClass".equals(memberName)) {
return null;
}
DefaultObjectHandler.ProxyEventInfo eventInfo = handler.getEventInfo(memberName);
if (eventInfo != null) {
// a "proxied" event is one that doesn't fire PropertyChangeEvent,
// so we need to convert its native event type into PropertyChangeEvents
StringBuilder result = new StringBuilder();
String methodName = "$pr" + compiler.getUniqueId(propertyChangeListenerCode.equals("this") ? constantId : propertyChangeListenerCode);
boolean methodExists = hasMethod(methodName);
if (!methodExists) {
ClassDescriptor eventClass = DefaultObjectHandler.getEventClass(eventInfo.getListenerClass());
String type = compiler.getImportedType(JAXXCompiler.getCanonicalName(eventClass));
String code = JavaFileGenerator.addDebugLoggerInvocation(compiler, "event");
code += "propertyChange(null);";
JavaMethod method = JavaElementFactory.newMethod(
Modifier.PUBLIC,
JAXXCompilerFinalizer.TYPE_VOID,
methodName,
code,
false,
JavaElementFactory.newArgument(type, "event"));
methods.add(method);
}
try {
String modelMemberName = eventInfo.getModelName() != null ? "get" + StringUtils.capitalize(eventInfo.getModelName()) : null;
String modelClassName = modelMemberName != null ? handler.getBeanClass().getMethodDescriptor(modelMemberName).getReturnType().getName() : JAXXCompiler.getCanonicalName(handler.getBeanClass());
String modelType = compiler.getImportedType(modelClassName);
String code = objectCode + (eventInfo.getModelName() != null ? "." + modelMemberName + "()" : "");
String eol = JAXXCompiler.getLineSeparator();
String jaxxUtilPrefix = compiler.getImportedType(JAXXUtil.class);
result.append(modelType).append(" $target = (").append(modelType).append(") $bindingSources.remove(\"").append(code).append("\");").append(eol);
//TC-20091105 test if $target is not null
result.append("if ($target != null) {").append(eol);
//TC-20091105 JAXXUtil.getEventListener is generic, no more need cast and use simple listener name
ClassDescriptor listenerClass = eventInfo.getListenerClass();
String listenerType = compiler.getImportedType(listenerClass.getName());
result.append(" $target.");
result.append(eventInfo.getRemoveMethod());
result.append("( ").append(jaxxUtilPrefix).append(".getEventListener(");
result.append(listenerType);
result.append(".class, ");
result.append("this");
result.append(", ");
result.append(TypeManager.getJavaCode(methodName));
result.append("));");
result.append(eol);
result.append("}").append(eol);
if (eventInfo.getModelName() != null) {
result.append(getRemoveMemberListenerCode(handler, objectCode, "get" + StringUtils.capitalize(eventInfo.getModelName()),
jaxxUtilPrefix + ".getDataBindingUpdateListener(" + compiler.getOutputClassName() + ".this, " + constantId + ")",
compiler));
}
return result.toString();
} catch (NoSuchMethodException e) {
throw new CompilerException("Internal error: " + e);
}
}
String propertyName = null;
if (memberName.startsWith("get")) {
propertyName = Introspector.decapitalize(memberName.substring("get".length()));
} else if (memberName.startsWith("is")) {
propertyName = Introspector.decapitalize(memberName.substring("is".length()));
} else {
try {
handler.getBeanClass().getFieldDescriptor(memberName);
propertyName = memberName;
} catch (NoSuchFieldException e) {
// ignore ?
}
}
if (propertyName == null) {
return null;
}
String prefix = objectCode.trim() + ".";
if (objectCode.equals(compiler.getRootObject().getJavaCode())) {
prefix = "";
}
//TC-20091203 : always use the property specific method, this is part of the javaBeans 1.1 norm
//TC-20091203 : if developpers do bad, shame on them...
return prefix + "removePropertyChangeListener(\"" + propertyName + "\", " + propertyChangeListenerCode + ");\n";
// try {
// // check for property-specific removePropertyChangeListener method
// handler.getBeanClass().getMethodDescriptor("removePropertyChangeListener", ClassDescriptorHelper.getClassDescriptor(String.class),
// ClassDescriptorHelper.getClassDescriptor(PropertyChangeListener.class));
// return prefix + "removePropertyChangeListener(\"" + propertyName + "\", " + propertyChangeListenerCode + ");\n";
// } catch (NoSuchMethodException e) {
// // no property-specific method, use general one
// return prefix + "removePropertyChangeListener(" + propertyChangeListenerCode + ");\n";
// }
}
/**
* Given a method from a given context class, try to obtain his method
* return type.
*
* Sometimes, the return type is unknown (generics can not be bind for
* example). As a fallback, we try if the context class is exactly the
* root context class of the compiler, replace it by the script method with
* same name on which we can have more chance to obtain a return type...
*
* @param contextClass the context class of the method
* @param method the method
* @return the method return type
* @since 2.4.2
*/
protected ClassDescriptor getMethodReturnType(ClassDescriptor contextClass,
MethodDescriptor method) {
ClassDescriptor returnType = method.getReturnType();
if (returnType == null &&
contextClass.equals(compiler.getRootObject().getObjectClass())) {
// special case to deal with generics (we need to
// have the concrete type)...
method = compiler.getScriptMethod(method.getName());
if (method != null) {
returnType = method.getReturnType();
}
}
return returnType;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy