/*
* 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.property_listener_injector;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import org.xml.sax.ErrorHandler;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.tools.xjc.BadCommandLineException;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
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;
/**
* This Plugin will generate property change events on each setXXX.
* <p>
* See the javadoc of {@link Plugin} for what those methods mean.
* <p>
* Usage in schema:<br>
* Add either <br>
* <code><uri:listener>java.beans.VetoableChangeSupport</uri:listener></code><br>
* or<br>
* <code><uri:listener>java.beans.PropertyChangeSupport</uri:listener></code><br>
* to either global or class bindings element <code><appinfo><code>.
*
* @author Jerome Dochez
* @author Martin Pecka
*/
public class PluginImpl extends Plugin
{
protected String boundInterface = null;
protected String constrainedInterface = null;
protected Class<?> boundSupportClass = PropertyChangeSupport.class;
protected Class<?> constrainedSupportClass = VetoableChangeSupport.class;
/** If false, don't generate the PropertyChangeSupport field and its delegate methods. */
protected boolean generateSupport = true;
public String getOptionName()
{
return "Xinject-listener-code";
}
@Override
public List<String> getCustomizationURIs()
{
return Collections.singletonList(Const.NS);
}
@Override
public boolean isCustomizationTagName(String nsUri, String localName)
{
return nsUri.equals(Const.NS) && localName.equals("listener");
}
@Override
public String getUsage()
{
return " -Xinject-listener-code\t: inject property change event support to setter methods\n"
+ " -Xinject-listener-code-dont-generate-support\t: don't generate the PropertyChangeSupport field and its delegate methods"
+ " -Xinject-listener-code-interface-bound fully.qualified.interface.name\t: tag classes containing bound properties with this interface\n"
+ " -Xinject-listener-code-interface-constrained fully.qualified.interface.name\t: tag classes containing constrained properties with this interface\n"
+ " -Xinject-listener-code-supportClass-bound fully.qualified.interface.name\t: name of the class to be used as PropertyChangeSupport implementation (must extend this class)\n"
+ " -Xinject-listener-code-supportClass-constrained fully.qualified.interface.name\t: name of the class to be used as VetoableChangeSupport implementation (must extend this class)\n"
+ "\n Note: Even if you provide an implementation class for either bound/constrained property support, you still have to use the base class names in the schema (eg. java.beans.PropertyChangeSupport)";
}
@Override
public void onActivated(Options opt) throws BadCommandLineException
{
FieldRendererFactory frf = new FieldRendererFactory(opt.getFieldRendererFactory());
opt.setFieldRendererFactory(frf, this);
}
@Override
public int parseArgument(Options opt, String[] args, int i) throws BadCommandLineException, IOException
{
int numTokens = super.parseArgument(opt, args, i);
if (numTokens > 0)
return numTokens;
String arg = args[i];
if (arg.startsWith("-Xinject-listener-code-interface-")) {
if (args.length <= i)
throw new BadCommandLineException("Option " + arg + " needs an argument - an interface FQname.");
String _interface = args[i + 1];
if (arg.endsWith("bound")) {
boundInterface = _interface;
return 2;
} else if (arg.endsWith("constrained")) {
constrainedInterface = _interface;
return 2;
}
} else if (arg.startsWith("-Xinject-listener-code-supportClass-")) {
if (args.length <= i)
throw new BadCommandLineException("Option " + arg + " needs an argument - a class' FQname.");
String support = args[i + 1];
Class<?> supportClass;
try {
supportClass = Class.forName(support);
} catch (ClassNotFoundException e) {
throw new IOException("The specified property support class " + support
+ " isn't on XJC classpath! Try to re-run the build, maybe you've dynamically "
+ "created the class file and Ant hasn't noticed it.", e);
}
if (arg.endsWith("bound")) {
if (!PropertyChangeSupport.class.isAssignableFrom(supportClass)) {
throw new BadCommandLineException("The specified bound property support class "
+ supportClass.getName() + " doesn't extend java.beans.PropertyChangeSupport!");
}
boundSupportClass = supportClass;
return 2;
} else if (arg.endsWith("constrained")) {
if (!VetoableChangeSupport.class.isAssignableFrom(supportClass)) {
throw new BadCommandLineException("The specified constrained property support class "
+ supportClass.getName() + " doesn't extend java.beans.VetoableChangeSupport!");
}
constrainedSupportClass = supportClass;
return 2;
}
} else if (arg.equals("-Xinject-listener-code-dont-generate-support")) {
generateSupport = false;
return 1;
}
return 0;
}
// meat of the processing
@Override
public boolean run(Outline model, Options opt, ErrorHandler errorHandler)
{
CPluginCustomization listener = model.getModel().getCustomizations().find(Const.NS, "listener");
String globalInterfaceSetting = null;
if (listener != null) {
listener.markAsAcknowledged();
globalInterfaceSetting = DOMUtils.getElementText(listener.element);
}
for (ClassOutline co : model.getClasses()) {
CPluginCustomization c = co.target.getCustomizations().find(Const.NS, "listener");
String interfaceName;
if (c != null) {
c.markAsAcknowledged();
interfaceName = DOMUtils.getElementText(c.element);
} else {
interfaceName = globalInterfaceSetting;
}
if (generateSupport) {
if (VetoableChangeListener.class.getName().equals(interfaceName)) {
addSupport(VetoableChangeListener.class, constrainedSupportClass, co.implClass,
constrainedInterface);
}
if (PropertyChangeListener.class.getName().equals(interfaceName)) {
addSupport(PropertyChangeListener.class, boundSupportClass, co.implClass, boundInterface);
}
}
}
return true;
}
private void addSupport(Class<?> listener, Class<?> support, JDefinedClass target, String _interface)
{
JClass clazz = target;
boolean foundSupportField = false;
while (clazz != null) {
if (!(clazz instanceof JDefinedClass)) {
clazz = clazz._extends();
continue;
}
if (((JDefinedClass) clazz).fields().get("support") != null) {
foundSupportField = true;
break;
}
clazz = clazz._extends();
}
if (foundSupportField)
return;
// add the support field.
// JFieldVar field = target.field(JMod.PRIVATE|JMod.TRANSIENT, support, "support");
JFieldVar field = target.field(JMod.PROTECTED | JMod.TRANSIENT, support, "support");
// and initialize it....
field.init(JExpr.direct("new " + support.getSimpleName() + "(this)"));
if (_interface != null) {
target._implements(target.owner().ref(_interface));
}
// 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);
break;
}
}
}
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);
break;
}
}
}
}
}
}
private void addMethod(Method method, JDefinedClass target)
{
JMethod addListener;
if (method.getGenericReturnType() instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) method.getGenericReturnType();
JClass returnType = target.owner().ref(method.getReturnType()).erasure();
for (Type t : pType.getActualTypeArguments()) {
if (t instanceof Class<?>)
returnType = returnType.narrow(target.owner().ref((Class<?>) t));
}
addListener = target.method(JMod.PUBLIC, returnType, method.getName());
} else {
addListener = target.method(JMod.PUBLIC, method.getReturnType(), method.getName());
}
Type types[] = method.getGenericParameterTypes();
Class<?>[] params = method.getParameterTypes();
JClass[] jParams = new JClass[params.length];
for (int i = 0; i < params.length; i++) {
jParams[i] = target.owner().ref(params[i]);
if (types[i] instanceof ParameterizedType) {
for (Type t : ((ParameterizedType) types[i]).getActualTypeArguments()) {
if (t instanceof Class<?>)
jParams[i] = jParams[i].narrow(target.owner().ref((Class<?>) t));
}
}
}
JInvocation inv;
if (!method.getReturnType().equals(Void.TYPE))
addListener.body()._return(inv = JExpr._this().ref("support").invoke(method.getName()));
else
inv = addListener.body().invoke(JExpr._this().ref("support"), method.getName());
for (int i = 0; i < params.length; i++) {
inv.arg(addListener.param(params[i], "param" + i));
}
}
}