/* * Copyright 2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jvnet.jaxb2_commons.plugin.fluent_api; import static org.jvnet.jaxb2_commons.plugin.fluent_api.FluentMethodType.FLUENT_COLLECTION_SETTER; import static org.jvnet.jaxb2_commons.plugin.fluent_api.FluentMethodType.FLUENT_LIST_SETTER; import static org.jvnet.jaxb2_commons.plugin.fluent_api.FluentMethodType.FLUENT_SETTER; import static org.jvnet.jaxb2_commons.plugin.fluent_api.FluentMethodType.GETTER_METHOD_PREFIX; import static org.jvnet.jaxb2_commons.plugin.fluent_api.FluentMethodType.GETTER_METHOD_PREFIX_LEN; import static org.jvnet.jaxb2_commons.plugin.fluent_api.FluentMethodType.PARAMETERIZED_LIST_PREFIX; import static org.jvnet.jaxb2_commons.plugin.fluent_api.FluentMethodType.SETTER_METHOD_PREFIX; import static org.jvnet.jaxb2_commons.plugin.fluent_api.FluentMethodType.SETTER_METHOD_PREFIX_LEN; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.xml.sax.ErrorHandler; import com.sun.codemodel.JClass; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; import com.sun.codemodel.JType; import com.sun.codemodel.JVar; import com.sun.tools.xjc.Options; import com.sun.tools.xjc.Plugin; import com.sun.tools.xjc.outline.ClassOutline; import com.sun.tools.xjc.outline.Outline; /** * Support a fluent api in addition to the default (JavaBean) setter methods.<br> * <p> * The initial idea is simply to add a "with*" method to the generated class * for every "set*" method encountered, * with the only functional difference of returning the class instance, instead of void. * <p> * <strong>Enhancement on 11 June 2006:</strong><br> * Provide fluent setter api for Lists, with support of variable arguments. * * This enhancement was suggested by Kenny MacLeod <kennym@kizoom.com>, * and endorsed by Kohsuke Kawaguchi <Kohsuke.Kawaguchi@sun.com>. * Here is quoted from the original request: * <p> * By default, XJC represents Lists by generating a getter method, but no setter. * This is impossible to chain with fluent-api. * How about the plugin generates a withXYZ() method for List properties, * taking as it's parameters a vararg list. For example:<blockquote><pre> * // This method is generated by vanilla XJC * public List<OtherType> getMyList() { * if (myList == null) { * myList = new ArrayList<OtherType>(); * } * return myList; * } * * // This would be generated by fluent-api * public MyClass withMyList(OtherType... values) { * if (values!= null) { * for(OtherType value : values) { * getMyList().add(value); * } * } * return this; * } *</pre></blockquote> * <strong>Enhancement on 11 Oct 2008:</strong><br> * Provide fluent setter api for Lists, with support of Collection argument in addition to varargs arguments. * * This enhancement was suggested by Alex Wei <ozgwei@dev.java.net> with patch submitted. See * <a href="https://jaxb2-commons.dev.java.net/issues/show_bug.cgi?id=12">Jira Issue 12</a> for more details. *<p> * @author Hanson Char */ public class FluentApiPlugin extends Plugin { @Override public String getOptionName() { return "Xfluent-api"; } @Override public String getUsage() { return " -Xfluent-api : enable fluent api for generated code"; } @Override public boolean run(Outline outline, @SuppressWarnings("unused") Options opt, @SuppressWarnings("unused") ErrorHandler errorHandler) { final JType voidType = outline.getCodeModel().VOID; // Process every pojo class generated by jaxb for (ClassOutline classOutline : outline.getClasses()) { final JDefinedClass targetImplClass = classOutline.implClass; Collection<FluentMethodInfo> fluentMethodInfoList = new ArrayList<FluentMethodInfo>(); Set<String> methodNames = new HashSet<String>(); boolean isOverride = false; for (;;) { JDefinedClass implClass = classOutline.implClass; // Collect the methods we are interested in // but defer the respective fluent methods creation // to avoid ConcurrentModificationException for (JMethod jmethod : implClass.methods()) { if (methodNames.contains(jmethod.name())) continue; if (isSetterMethod(jmethod, voidType)) { fluentMethodInfoList.add(new FluentMethodInfo(jmethod, FLUENT_SETTER, isOverride)); methodNames.add(jmethod.name()); } else if (isListGetterMethod(jmethod)) { fluentMethodInfoList.add(new FluentMethodInfo(jmethod, FLUENT_LIST_SETTER, isOverride)); // Originally proposed by Alex Wei ozgwei@dev.java.net: // https://jaxb2-commons.dev.java.net/issues/show_bug.cgi?id=12 fluentMethodInfoList.add(new FluentMethodInfo(jmethod, FLUENT_COLLECTION_SETTER, isOverride)); methodNames.add(jmethod.name()); } } // Let's climb up the class hierarchy classOutline = classOutline.getSuperClass(); if (classOutline == null) break; isOverride = true; } // Generate a respective fluent method for each setter method for (FluentMethodInfo fluentMethodInfo : fluentMethodInfoList) fluentMethodInfo.createFluentMethod(targetImplClass); } return true; } /** * Returns true if the given method is a public non-static setter method that follows * the JavaBean convention; false otherwise. * The setter method can either be a simple property setter method or * an indexed property setter method. */ private boolean isSetterMethod(JMethod jmethod, final JType VOID) { // Return type of a setter method is expected to be void. if (jmethod.type() == VOID) { JVar[] jvars = jmethod.listParams(); switch(jvars.length) { case 2: // could be an indexed property setter method. // if so, the first argument must be the index (a primitive int). if (!isInt(jvars[0].type())) return false; // drop thru. case 1: // or could be a simple property setter method int mods = jmethod.mods().getValue(); if ((mods & JMod.STATIC) == 0 && (mods & JMod.PUBLIC) == 1) { String methodName = jmethod.name(); return methodName.length() > SETTER_METHOD_PREFIX_LEN && methodName.startsWith(SETTER_METHOD_PREFIX); } break; } } return false; } /** * Returns true if the given method is a public non-static getter method that returns * a List<T>; false otherwise. * * @param jmethod given method */ private boolean isListGetterMethod(JMethod jmethod) { int mods = jmethod.mods().getValue(); // check if it is a non-static public method if ((mods & JMod.STATIC) == 1 || (mods & JMod.PUBLIC) == 0) return false; String methodName = jmethod.name(); // See if the method name looks like a getter method if (methodName.length() <= GETTER_METHOD_PREFIX_LEN || !methodName.startsWith(GETTER_METHOD_PREFIX)) return false; // A list getter method will have no argument. if (jmethod.listParams().length > 0) return false; // See if the return type of the method // is a List<T> JType jtype = jmethod.type(); if (jtype instanceof JClass) { JClass jclass = JClass.class.cast(jtype); List<JClass> typeParams = jclass.getTypeParameters(); if (typeParams.size() != 1) return false; return jclass.fullName().startsWith(PARAMETERIZED_LIST_PREFIX); } return false; } /** Returns true if the given type is a primitive int; false otherwise. */ private boolean isInt(JType type) { JCodeModel codeModel = type.owner(); return type.isPrimitive() && codeModel.INT.equals( JType.parse(codeModel, type.name())); } }