users@jaxb.java.net

Re: (JAXB Plugin) Customize the body of a setter method to fire bound properties

From: Marcos <marcos_at_softingsystems.com.br>
Date: Fri, 12 Jan 2007 10:26:27 -0300

Kohsuke Kawaguchi escreveu:

>
> I moved the plugin from Glassfish workspace to jaxb2-commons.
>
> See https://jaxb2-commons.dev.java.net/property-listener-injector/
>
> This is mostly the same code that Jerome had.
>
> Marcos, it looks like you found a bug in it. If you are interested in
> looking at the code,

Yes I'm interested ;-)

> I'd be happy to make you a committer.

I would be very glad to be a committer ;-)

Actually I've made a check out from the sources found at the repository
location "jaxb2-commons/property-listener-injector"
and this version will do the trick for me at this moment.

> Otherwise, if you can send me a test case, I'd be happy to take a look.

Ok I've been working on the plugin below, but after I saw the sources at
jaxb2-commons/property-listener-injector.
I must confess that I'm a little embarrassed about my attempt to rewrite
this plugin ;-(
I think it's because this is my first week working with JAXB and I'm
still studying the API
Anyway below is the class that I was working on yesterday (it was not
finished):

/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
package com.sun.tools.xjc.addon.bound_property_injector;

import com.sun.codemodel.JAssignment;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JStatement;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.addon.code_injector.Const;
import com.sun.tools.xjc.generator.bean.field.FieldRendererFactory;
import com.sun.tools.xjc.model.CPluginCustomization;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.Outline;
import com.sun.tools.xjc.util.DOMUtils;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.bind.annotation.XmlTransient;

import org.xml.sax.ErrorHandler;

/**
 * This Plugin will generate property change events on each setXXX.
 *
 * See the javadoc of {_at_link Plugin} for what those methods mean.
 *
 * @author Jerome Dochez, Marcos
 */
public class PluginImpl extends Plugin {
   
    public String getOptionName() {
        return "Xinject-listener-code";
    }
   
    public List<String> getCustomizationURIs() {
        return Collections.singletonList(Const.NS);
    }
   
    public boolean isCustomizationTagName(String nsUri, String localName) {
        return nsUri.equals(Const.NS) && localName.equals("listener");
    }
   
    public String getUsage() {
        return " -Xinject-listener-code\t: inject property change
event support to setter methods";
    }
   
    public void onActivated(Options opt) {
        try { //At
this point I didn't find the
                                                                //
FieldRendererFactory at GlassFish ;-(
            FieldRendererFactory frf = new
FieldRendererFactory(/*opt.getFieldRendererFactory()*/);
            opt.setFieldRendererFactory(frf, this);
        } catch(Exception e) {
            e.printStackTrace();
        }
       
    }
   
    // meat of the processing
    public boolean run(Outline model, Options opt, ErrorHandler
errorHandler) {
       
        for( ClassOutline co : model.getClasses() ) {
            CPluginCustomization c =
co.target.getCustomizations().find(Const.NS,"bound");
            //if(c==null)
            // continue; // no customization --- nothing to inject here
           
            //c.markAsAcknowledged();
            // TODO: ideally you should validate this DOM element to
make sure
            // that there's no typo/etc. JAXP 1.3 can do this very easily.
            String interfaceName;
            if (c!=null) {
                interfaceName = DOMUtils.getElementText(c.element);
            } else {
                interfaceName = VetoableChangeListener.class.getName();
            }
            if
(VetoableChangeListener.class.getName().equals(interfaceName)) {
                addSupport(model, VetoableChangeListener.class,
VetoableChangeSupport.class, co.implClass);
            }
           
            /*
             * --marcos My attempt to improve the plugin started here ...
             * but I didn't finish
             * Start of injection of fireXXX statement inside of setters.
             */
            addBoundPropertiesSupport(model, opt, errorHandler,
co.implClass);
        }
       
        return true;
    }
   
    private void addSupport(Outline model, Class listener, Class
support, JDefinedClass target) {
       
        // add the support field.
        // JFieldVar field =
target.field(JMod.PRIVATE|JMod.TRANSIENT, support, "support");
        JFieldVar field = target.field(JMod.PRIVATE, support, "support");
        field.annotate(XmlTransient.class);
       
        // and initialize it....
        field.init(JExpr.direct("new " + support.getSimpleName() +
"(this)"));
       
        // we need to hadd
        for (Method method : support.getMethods()) {
            if (Modifier.isPublic(method.getModifiers())) {
                if (method.getName().startsWith("add")) {
                    // look if one of the parameters in the listnener
class...
                    for (Class param : method.getParameterTypes()) {
                        if (param.getName().equals(listener.getName())) {
                            addMethod(method, target);
                            continue;
                        }
                    }
                }
                if (method.getName().startsWith("remove")) {
                    // look if one of the parameters in the listnener
class...
                    for (Class param : method.getParameterTypes()) {
                        if (param.getName().equals(listener.getName())) {
                            addMethod(method, target);
                            continue;
                        }
                    }
                }
            }
        }
    }
   
    private void addMethod(Method method, JDefinedClass target) {
        JMethod addListener = target.method(JMod.PUBLIC,
method.getReturnType(), method.getName());
        Class params[] = method.getParameterTypes();
        String body = "support." + method.getName() + "(";
        for (int i=0;i<params.length;i++) {
            addListener.param(params[i], "param"+i);
            body = body + "param"+i;
            if (i+1<params.length) {
                body = body + ",";
            }
        }
        body = body + ");";
        addListener.body().directStatement(body);
       
    }
   
    // --marcos ----------
    /**
     * Add bound property support to methods found inside the related
     * <tt>JDefinedClass</tt>. This basically consists in the insertion
     * of code inside of the setter methods.
     * <p>
     * <pre>
     * <b>// Before.: (Without the plugin)</b>
     * public void setReturnCode(String value) {
     * this.returnCode = value;
     * }
     *
     * <b>// After.: (Using the plugin)</b>
     * public void setReturnCode(String value) {
     * String oldReturnCode = this.returnCode;
     * this.returnCode = value;
     * support.firePropertyChange ("returnCode", oldReturnCode,
returnCode);
     * }
     * </pre>
     * @param model <tt>Outline</tt>
     * @param opt <tt>Options</tt>
     * @param errorHandler <tt>Errorhandler</tt>
     * @param target <tt>JDefinedClass</tt> that will receive the
     * injection of the bound code inside of its setters
     */
    private void addBoundPropertiesSupport(Outline model, Options opt,
            ErrorHandler errorHandler, JDefinedClass target) {
       
        JBlock block = null;
       
        /* Validator that checks if the method is a
         * typical setter. (Such functionality
         * wasn't found in the JAXB RI API, tried to
         * find a utility class with static methods
         * but until now cannot figure out another
         * way to perform that)
         */
        Validator validator = new SetterValidator();
       
        for (JMethod method : target.methods()){
            // If method has at least one JBlock inside.
            if (null != (block = method.body())){
                // If method is a valid setter, its body is not empty
                // and it has at maximum one assignment.
                if (validator.isValid(method) &&
                        !(block.getContents()).isEmpty() &&
                        hasOneAssignment(method)){
                    addFirePropertyChange(method);
                }
            }
        }
       
    }
   
    /**
     * Checks if the method passed in has one assignment
     * statetment inside its body.
     * @param method <tt>JMethod</tt> to be checked
     * @return <tt>true</tt> if the method has one assignment
     * inside <tt>false</tt> if it doesn't
     */
    private boolean hasOneAssignment(JMethod method){

        boolean ret = false;
        JBlock block = null;
        List contents = null;
       
        if (null != (block = method.body())){
            if (!(contents = block.getContents()).isEmpty()){
                // Iterate over the method contents
                for (Iterator it = contents.iterator(); it.hasNext();) {
                    Object elem = (Object) it.next();
                   
                    // If the block element is a JAssignment
                    // we can invoke the proper method to inject
                    // the "fireXXX" part and continue at the
                    // next method
                    if (elem instanceof JAssignment &&
                            contents.size() == 1){
                        ret = true;
                        break;
                    }
                }
            }
        }
       
        return ret;
       
    }

    // Some lines commented because it was not finished
    /**
     * Injects <tt>fireXXX</tt> statement to method's block.
     * @param method <tt>JMethod</tt> that will receive the
     * injection
     */
    private void addFirePropertyChange(JMethod method){

        JBlock block = method.body();

        /* Initial position we'll insert our
         * customized statements, such as.:
         */
        int pos = 0;

        /* Old JStatement saved to be the second
         * line in the customized method block.
         */
        JStatement oldAssign = null;


        for (Iterator it = block.getContents().iterator(); it.hasNext();) {
            Object elem = (Object) it.next();
            oldAssign = (JStatement) elem;
            break;
        }

        /* Reset block position.*/
// block.pos(pos);

        /* Adds the oldBound declaration.*/
        addOldBoundDecl(); // Not finished
// block.pos(++pos);

// /* Adds the oldAssign declaration.*/
// block.add(oldAssign);
// block.pos(++pos);

        /* Adds the fireXXX statement.*/
        addSupportFirePropertyChange(); // Not finished
       
    }

    /**
     * Adds the declaration to save the old value of
     * the bound property.
     * <pre>
     * public void setReturnCode(String value) {
     * <b>String oldReturnCode = this.returnCode;</b>
     * this.returnCode = value;
     * support.firePropertyChange ("returnCode", oldReturnCode,
returnCode);
     * }
     * </pre>
     */
    private void addOldBoundDecl(){


    }

    /**
     * Adds the declaration to trigger the method
     * of the support.
     * <pre>
     * public void setReturnCode(String value) {
     * String oldReturnCode = this.returnCode;
     * this.returnCode = value;
     * <b>support.firePropertyChange ("returnCode",
oldReturnCode, returnCode);</b>
     * }
     * </pre>
     */
    private void addSupportFirePropertyChange(){

    }


}

// Validator for setters ----------
/**
 * Marker interface implemented by all classes that are able to
 * <tt>validate</tt> some parts found inside the code model.
 */
interface Validator {
   
    /**
     * Validates the <tt>Object</tt> received.
     * @param o <tt>Object</tt> to be validated
     * @return <tt>true</tt> if valid
     * <tt>false</tt> if not valid
     */
    boolean isValid(Object o);
   
}

/**
 * <tt>Validator</tt> implementation that verifies
 * if method is acknowledged as a <tt>setter</tt>.
 */
class SetterValidator implements Validator {
   
    /**
     * Constant that maintains the method prefix <tt>set</tt>.
     */
    public static final String SET = "set";
   
    /**
     * Check if the received <tt>Object</tt> is a
     * valid <tt>setter</tt> method with only one
     * parameter (typically the bound property)
     * @param o <tt>Object</tt> to be validated
     * @return <tt>true</tt> if valid
     * <tt>false</tt> if not valid
     */
    public boolean isValid(Object o) {
       
        boolean ret = false;
       
        JMethod method = null;
       
        if (o instanceof JMethod){
            method = (JMethod) o;
            if (null != method.listParams() &&
                    method.listParams().length == 1 &&
                    method.name().startsWith(SET)){
                ret = true;
            }
        }
       
        return ret;
       
    }
   
}

Thank you very much
Marcos

>
>
> Kohsuke Kawaguchi wrote:
>
>> Marcos wrote:
>>
>>> Hi all,
>>>
>>> I'm trying to write a plugin based on the one developed by Mr.
>>> Jerome Dochez, found at.:
>>> http://fisheye5.cenqua.com/browse/glassfish/admin-core/xjc-plugin/src/com/sun/tools/xjc/addon/property_listener_injector/PluginImpl.java?r1=1.1&r2=1.1.2.1
>>>
>>
>>
>> We are just talking about moving this to jaxb2-commons.
>>
>>> JAXB's binding compiler generate setter methods for all elements
>>> found in the .xsd file
>>> but I would like to replace some parts to suit my needs .... as
>>> follows.:
>>>
>>> Before.: (Without the plugin)
>>>
>>> /**
>>> * Sets the value of the returnCode property.
>>> *
>>> * @param value
>>> * allowed object is
>>> * {_at_link String }
>>> * */
>>> public void setReturnCode(String value) {
>>> this.returnCode = value;
>>> }
>>>
>>> After.: (Using the plugin)
>>>
>>> /* PropertyChangeSupport inserted by my own plugin.*/
>>> private PropertyChangeSupport changes = new
>>> PropertyChangeSupport(this);
>>>
>>> /**
>>> * Sets the value of the returnCode property.
>>> *
>>> * @param value
>>> * allowed object is
>>> * {_at_link String }
>>> * */
>>> public void setReturnCode(String value) {
>>> String oldReturnCode = this.returnCode; // This must be
>>> inserted by my plugin
>>> this.returnCode = value; // I'm
>>> not sure if I can invoke the default implementation of JAXB to
>>> retrieve this line of code ...
>>> changes.firePropertyChange ("returnCode", oldReturnCode,
>>> returnCode); // This must be inserted by my plugin
>>> }
>>>
>>> I know that each plugin has 02 interesting hook methods.:
>>> onActivated (Options opt)
>>> run (Outline model, Options opt, ErrorHandler)
>>>
>>> How can I use these methods and JAXB RI API to inject the piece of
>>> code that I need ?
>>
>>
>> It really depends on what kind of code you want to inject. Why don't
>> you start by looking at Jerome's code and modify it to fit your needs?
>>
>
>