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