users@jaxb.java.net

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

From: Kohsuke Kawaguchi <Kohsuke.Kawaguchi_at_Sun.COM>
Date: Fri, 12 Jan 2007 10:34:24 -0800

Marcos wrote:
> 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 ;-)

Let me know your java.net ID.


> 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.

Good.

>> 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):

That's OK.

But it's hard to see the source file in e-mail content. If it contains
changes you'd like to be incorporated, perhaps you want to commit it in
a branch or something so that all interested parties can see?

>
> /*
> * 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?
>>>
>>
>>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe_at_jaxb.dev.java.net
> For additional commands, e-mail: users-help_at_jaxb.dev.java.net
>
>


-- 
Kohsuke Kawaguchi
Sun Microsystems                   kohsuke.kawaguchi_at_sun.com