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

jaxx.compiler.binding.DataSource Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-6
Show newest version
/*
 * #%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.21/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