From 9c78f6df7f5438a2a8ef3d8f7f0653b779d713a9 Mon Sep 17 00:00:00 2001 From: Laurent SCHOELENS Date: Fri, 12 May 2023 13:51:16 +0200 Subject: [PATCH] GH-160 : take back remaining jaxb_commons plugins --- .gitignore | 1 + .../commons_lang/XjcCommonsLangPlugin.java | 197 ++++++++++ .../DefaultValuePlugin.java | 324 +++++++++++++++++ .../plugin/fluent_api/Customizations.java | 11 + .../plugin/fluent_api/FluentApiPlugin.java | 234 ++++++++++++ .../plugin/fluent_api/FluentMethodInfo.java | 55 +++ .../plugin/fluent_api/FluentMethodType.java | 172 +++++++++ .../NamespacePrefixPlugin.java | 337 ++++++++++++++++++ .../value_constructor/Customizations.java | 11 + .../ValueConstructorPlugin.java | 153 ++++++++ .../services/com.sun.tools.xjc.Plugin | 7 +- tests/commons_lang/pom.xml | 46 +++ .../src/main/resources/Person.xsd | 28 ++ .../tests/commons_lang/AddressTest.java | 22 ++ .../tests/commons_lang/PersonTest.java | 20 ++ .../src/test/resources/log4j.properties | 5 + tests/defaultvalue/pom.xml | 45 +++ .../src/main/resources/Person.xsd | 28 ++ .../tests/defaultvalue/AddressTest.java | 18 + .../tests/defaultvalue/PersonTest.java | 15 + .../src/test/resources/log4j.properties | 5 + tests/namespace/pom.xml | 45 +++ tests/namespace/src/main/resources/a.xsd | 27 ++ tests/namespace/src/main/resources/b.xsd | 47 +++ .../namespace/src/main/resources/binding.xjb | 27 ++ .../tests/namespace/RunNamespacePlugin.java | 26 ++ .../src/test/resources/log4j.properties | 5 + tests/pom.xml | 3 + 28 files changed, 1913 insertions(+), 1 deletion(-) create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/commons_lang/XjcCommonsLangPlugin.java create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/defaultvalueplugin/DefaultValuePlugin.java create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/Customizations.java create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentApiPlugin.java create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentMethodInfo.java create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentMethodType.java create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/namespace_prefix/NamespacePrefixPlugin.java create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/value_constructor/Customizations.java create mode 100644 basic/src/main/java/org/jvnet/jaxb2_commons/plugin/value_constructor/ValueConstructorPlugin.java create mode 100644 tests/commons_lang/pom.xml create mode 100644 tests/commons_lang/src/main/resources/Person.xsd create mode 100644 tests/commons_lang/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/AddressTest.java create mode 100644 tests/commons_lang/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java create mode 100644 tests/commons_lang/src/test/resources/log4j.properties create mode 100644 tests/defaultvalue/pom.xml create mode 100644 tests/defaultvalue/src/main/resources/Person.xsd create mode 100644 tests/defaultvalue/src/test/java/org/jvnet/jaxb2_commons/tests/defaultvalue/AddressTest.java create mode 100644 tests/defaultvalue/src/test/java/org/jvnet/jaxb2_commons/tests/defaultvalue/PersonTest.java create mode 100644 tests/defaultvalue/src/test/resources/log4j.properties create mode 100644 tests/namespace/pom.xml create mode 100644 tests/namespace/src/main/resources/a.xsd create mode 100644 tests/namespace/src/main/resources/b.xsd create mode 100644 tests/namespace/src/main/resources/binding.xjb create mode 100644 tests/namespace/src/test/java/org/jvnet/jaxb2_commons/tests/namespace/RunNamespacePlugin.java create mode 100644 tests/namespace/src/test/resources/log4j.properties diff --git a/.gitignore b/.gitignore index 4a6acd1e..529be156 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +bin /err /std target diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/commons_lang/XjcCommonsLangPlugin.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/commons_lang/XjcCommonsLangPlugin.java new file mode 100644 index 00000000..8c37d298 --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/commons_lang/XjcCommonsLangPlugin.java @@ -0,0 +1,197 @@ +/* + * 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.commons_lang; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.xml.sax.ErrorHandler; + +import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JExpression; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JVar; +import com.sun.tools.xjc.BadCommandLineException; +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; + +/** + * Automatically generates the toString(), hashCode() and equals() methods + * using Jakarta's commons-lang. + *

+ * Supports the optional ToStringStyle command line parameter to specify + * the style for use within the toString method. + *

+ *

+ * Example 1:
+ *  
+ *     -Xcommons-lang
+ *     -Xcommons-lang:ToStringStyle=SIMPLE_STYLE
+ *     
+ *     to specify the use of 
+ * 
+ *     org.apache.commons.lang3.builder.ToStringStyle.SIMPLE_STYLE
+ *     
+ * Example 2:
+ *  
+ *     -Xcommons-lang
+ *     -Xcommons-lang:ToStringStyle=my.CustomToStringStyle
+ *     
+ *     to specify the use of 
+ * 
+ *     my.CustomToStringStyle, which must be a subclass of 
+ *     
+ *     org.apache.commons.lang3.builder.ToStringStyle, and contains a public no-arg constructor.
+ *     
+ * 
+ *

+ * The default ToStringStyle adopted by this plugin is MULTI_LINE_STYLE. + * + * @see org.apache.commons.lang.builder.ToStringStyle + * @author Hanson Char + */ +public class XjcCommonsLangPlugin extends Plugin +{ + private static final String TOSTRING_STYLE_PARAM = "-Xcommons-lang:ToStringStyle="; + private String toStringStyle = "MULTI_LINE_STYLE"; + private Class customToStringStyle; + + @Override + public String getOptionName() + { + return "Xcommons-lang"; + } + + @Override + public String getUsage() + { + return " -Xcommons-lang : generate toString(), hashCode() and equals() for generated code using Jakarta's common-lang\n" + + " [-Xcommons-lang:ToStringStyle=MULTI_LINE_STYLE\n\t" + + "| DEFAULT_STYLE\n\t" + + "| NO_FIELD_NAMES_STYLE\n\t" + + "| SHORT_PREFIX_STYLE\n\t" + + "| SIMPLE_STYLE\n\t" + + "| ]\n" + ; + } + + @Override + public boolean run(Outline outline, + @SuppressWarnings("unused") Options opt, + @SuppressWarnings("unused") ErrorHandler errorHandler) + { + // Process every pojo class generated by jaxb + for (ClassOutline classOutline : outline.getClasses()) { + JDefinedClass implClass = classOutline.implClass; + this.createToStringMethod(implClass); + this.createEqualsMethod(implClass); + this.createHashCodeMethod(implClass); + } + return true; + } + + private void createToStringMethod(JDefinedClass implClass) + { + JCodeModel codeModel = implClass.owner(); + JMethod toStringMethod = + implClass.method(JMod.PUBLIC, codeModel.ref(String.class), "toString"); + // Annotate with @Override + toStringMethod.annotate(Override.class); + final JExpression toStringStyleExpr = + customToStringStyle == null + ? codeModel.ref(ToStringStyle.class) + .staticRef(toStringStyle) + : JExpr._new( + codeModel.ref(customToStringStyle)) + ; + // Invoke ToStringBuilder.reflectionToString(Object,StringStyle) + toStringMethod.body() + ._return( + codeModel.ref(ToStringBuilder.class) + .staticInvoke("reflectionToString") + .arg(JExpr._this()) + .arg(toStringStyleExpr) + ); + return; + } + + private void createEqualsMethod(JDefinedClass implClass) + { + JCodeModel codeModel = implClass.owner(); + JMethod toStringMethod = + implClass.method(JMod.PUBLIC, codeModel.BOOLEAN, "equals"); + JVar that = toStringMethod.param(Object.class, "that"); + // Annotate with @Override + toStringMethod.annotate(Override.class); + // Invoke EqualsBuilder.reflectionEquals(Object,Object); + toStringMethod.body()._return( + codeModel.ref(EqualsBuilder.class) + .staticInvoke("reflectionEquals") + .arg(JExpr._this()) + .arg(that) + ); + return; + } + + private void createHashCodeMethod(JDefinedClass implClass) + { + JCodeModel codeModel = implClass.owner(); + JMethod toStringMethod = + implClass.method(JMod.PUBLIC, codeModel.INT, "hashCode"); + // Annotate with @Override + toStringMethod.annotate(Override.class); + // Invoke EqualsBuilder.reflectionHashCode(Object); + toStringMethod.body()._return( + codeModel.ref(HashCodeBuilder.class) + .staticInvoke("reflectionHashCode") + .arg(JExpr._this()) + ); + return; + } + + @Override + public int parseArgument(Options opt, String[] args, int i) + throws BadCommandLineException + { + // eg. -Xcommons-lang ToStringStyle=SIMPLE_STYLE + String arg = args[i].trim(); + + if (arg.startsWith(TOSTRING_STYLE_PARAM)) + { + toStringStyle = arg.substring(TOSTRING_STYLE_PARAM.length()); + try { + ToStringStyle.class.getField(toStringStyle); + return 1; + } catch (SecurityException e) { + throw new BadCommandLineException(e.getMessage()); + } catch (NoSuchFieldException ignore) { + } + try { + customToStringStyle = Class.forName(toStringStyle); + } catch (ClassNotFoundException e) { + throw new BadCommandLineException(e.getMessage()); + } + return 1; + } + return 0; + } +} \ No newline at end of file diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/defaultvalueplugin/DefaultValuePlugin.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/defaultvalueplugin/DefaultValuePlugin.java new file mode 100644 index 00000000..dd0ef898 --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/defaultvalueplugin/DefaultValuePlugin.java @@ -0,0 +1,324 @@ +package org.jvnet.jaxb2_commons.plugin.defaultvalueplugin; + +import java.util.Map; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + +import org.xml.sax.ErrorHandler; + +import com.sun.codemodel.ClassType; +import com.sun.codemodel.JBlock; +import com.sun.codemodel.JCatchBlock; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JEnumConstant; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JTryBlock; +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.model.CPropertyInfo; +import com.sun.tools.xjc.outline.ClassOutline; +import com.sun.tools.xjc.outline.EnumConstantOutline; +import com.sun.tools.xjc.outline.EnumOutline; +import com.sun.tools.xjc.outline.FieldOutline; +import com.sun.tools.xjc.outline.Outline; +import com.sun.xml.xsom.XSElementDecl; +import com.sun.xml.xsom.XSParticle; +import com.sun.xml.xsom.XSTerm; + +/** + * Modifies the JAXB code model to set default values to the schema "default" attribute. + * Currently, the following field types can be initialized: + *

+ * + * Created: Mon Apr 24 22:04:25 2006 + * + * @author Hari Selvarajan + * @author J�rgen Lukasczyk + * @version 1.1 + */ +public class DefaultValuePlugin + extends Plugin +{ + + /** + * Name of Option to enable this plugin + */ + static private final String OPTION_NAME = "Xdefault-value"; + + + /** + * Creates a new DefaultValuePlugin instance. + * + */ + public DefaultValuePlugin() { + } + + + /** + * DefaultValuePlugin uses "-Xdefault-value" as the command-line + * argument + * + */ + public String getOptionName() { + return OPTION_NAME; + } + + + /** + * Return usage information for plugin + * + */ + public String getUsage() + { + return " -"+OPTION_NAME+" : enable rewriting of classes to set default values for fields as specified in XML schema"; + } + + /** + * Run the plugin. We perform the following steps: + * + * + * + */ + public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) + { + // For all Classes generated + for (ClassOutline co : outline.getClasses()) { + + // Some conversions may have to add class level code + JFieldVar dtf = null; // Helper code: DatatypeFactory + + // check all Fields in Class + for (FieldOutline f : co.getDeclaredFields()) { + CPropertyInfo fieldInfo = f.getPropertyInfo(); + + // Do nothing if Field is not created from an xsd particle + if (!(fieldInfo.getSchemaComponent() instanceof XSParticle)) { + continue; + } + XSTerm term = ((XSParticle) fieldInfo.getSchemaComponent()).getTerm(); + + // Default values only necessary for fields derived from an xsd:element + if (!term.isElementDecl()) { + continue; + } + XSElementDecl element = term.asElementDecl(); + + // Do nothing if no default value + if (element.getDefaultValue() == null) { + continue; + } + String defaultValue = element.getDefaultValue().value; + + // Get handle to JModel representing the field + Map fields = co.implClass.fields(); + JFieldVar var = fields.get(fieldInfo.getName(false)); + + // Handle primitive types via boxed representation (treat boolean as java.lang.Boolean) + JType type = f.getRawType(); + if (type.isPrimitive()) + type = type.boxify(); + String typeFullName = type.fullName(); + + // Create an appropriate default expression depending on type + if ("java.lang.String".equals(typeFullName)) { + var.init(JExpr.lit(defaultValue)); + if (opt.verbose) + System.out.println("[INFO] Initializing String variable " + +fieldInfo.displayName() + +" to \""+defaultValue+"\""); + } + + else if ("java.lang.Boolean".equals(typeFullName)) { + var.init(JExpr.lit(Boolean.valueOf(defaultValue))); + if (opt.verbose) + System.out.println("[INFO] Initializing Boolean variable " + +fieldInfo.displayName() + +" to "+defaultValue+""); + } + + else if ( ("java.lang.Byte".equals(typeFullName)) + || ("java.lang.Short".equals(typeFullName)) + || ("java.lang.Integer".equals(typeFullName)) + ) { + // CodeModel does not distinguish between Byte, Short and Integer literals + var.init(JExpr.lit(Integer.valueOf(defaultValue))); + if (opt.verbose) + System.out.println("[INFO] Initializing Integer variable " + +fieldInfo.displayName() + +" to "+defaultValue+""); + } + + else if ("java.lang.Long".equals(typeFullName)) { + var.init(JExpr.lit(Long.valueOf(defaultValue))); + if (opt.verbose) + System.out.println("[INFO] Initializing Long variable " + +fieldInfo.displayName() + +" to "+defaultValue+""); + } + + else if ("java.lang.Float".equals(typeFullName)) { + var.init(JExpr.lit(Float.valueOf(defaultValue))); + if (opt.verbose) + System.out.println("[INFO] Initializing Float variable " + +fieldInfo.displayName() + +" to "+defaultValue+""); + } + + else if ( ("java.lang.Single".equals(typeFullName)) + || ("java.lang.Double".equals(typeFullName)) + ) { + // CodeModel does not distinguish between Single and Double literals + var.init(JExpr.lit(Double.valueOf(defaultValue))); + if (opt.verbose) + System.out.println("[INFO] Initializing Double variable " + +fieldInfo.displayName() + +" to "+defaultValue+""); + } + + else if ("javax.xml.datatype.XMLGregorianCalendar".equals(typeFullName)) { + // XMLGregorianCalender is constructed by DatatypeFactory, so we have to have + // an instance of that once per class + if (dtf == null) { + dtf = installDtF(co.implClass); + if (dtf == null) continue; + } + // Use our DtF instance to generate the initialization expression + var.init(JExpr.invoke(dtf, "newXMLGregorianCalendar") + .arg(defaultValue)); + if (opt.verbose) + System.out.println("[INFO] Initializing XMLGregorianCalendar variable " + +fieldInfo.displayName() + +" with value of "+defaultValue); + } + + else if ( (type instanceof JDefinedClass) + && (((JDefinedClass) type).getClassType() == ClassType.ENUM) ) { + // Find Enum constant + JEnumConstant constant = findEnumConstant(type, defaultValue, outline); + if (constant != null) { + var.init(constant); + if (opt.verbose) + System.out.println("[INFO] Initializing enum variable " + + fieldInfo.displayName() + " with constant " + constant.getName()); + } + } + + // Don't know how to create default for this type + else { + System.out.println("[WARN] Did not create default value for field " + + fieldInfo.displayName() + + ". Don't know how to create default value expression for fields of type " + + typeFullName + + ". Default value of \""+defaultValue+"\" specified in schema" + ); + } + + } // for FieldOutline + + } // for ClassOutline + + return true; + } + + + /** + * Retrieve the enum constant that correlates to the string value. + * @param enumType Type identifying an Enum in the code model + * @param enumStringValue Lexical value of the constant to search + * @param outline Outline of the code model + * @return The matching Constant from the enum type or NULL if not found + */ + private JEnumConstant findEnumConstant(JType enumType, String enumStringValue, Outline outline) + { + // Search all Enums generated + for (EnumOutline eo : outline.getEnums()) { + // Is it the type of my variable? + if (eo.clazz == enumType) { + // Search all Constants of that enum + for (EnumConstantOutline eco : eo.constants) { + // Is the enum generated from the XML defaut value string? + if (eco.target.getLexicalValue().equals(enumStringValue)) { + return eco.constRef; + } + } // for Constants + // Did not find the constant??? + System.out.println("[WARN] Could not find EnumConstant for value: "+enumStringValue); + return null; + } + } + // Did not find the type?? + System.out.println("[WARN] Could not find Enum class for type: "+enumType.fullName()); + return null; + } + + + /** + * Enhance the CodeModel of a Class to include a {@link DatatypeFactory} as a static private field. + * The factory is needed to construct {@link XMLGregorianCalendar} from String representation. + * @param parentClass Class where the DatatypeFactory will be created + * @return Reference to the created static field + */ + private JFieldVar installDtF(final JDefinedClass parentClass) + { + try { + JCodeModel cm = parentClass.owner(); + // Create a static variable of type DatatypeFactory + JClass dtfClass = cm.ref(DatatypeFactory.class); + JFieldVar dtf = parentClass.field(JMod.STATIC | JMod.FINAL | JMod.PRIVATE, + dtfClass, "DATATYPE_FACTORY"); + // Initialize variable in static block + JBlock si = parentClass.init(); + JTryBlock tryBlock = si._try(); + tryBlock.body().assign(dtf, dtfClass.staticInvoke("newInstance")); + // Catch exception & rethrow as unchecked Exception + JCatchBlock catchBlock = tryBlock._catch(cm.ref(DatatypeConfigurationException.class)); + JVar ex = catchBlock.param("ex"); + JClass runtimeException = cm.ref(RuntimeException.class); + catchBlock.body()._throw(JExpr._new(runtimeException) + .arg("Unable to initialize DatatypeFactory") + .arg(ex)); + // Return reference to initialized static field + return dtf; + } catch (Exception e) { + // We don't want JAXB to break of any plugin error + System.out.println("[ERROR] Failed to create code"); + e.printStackTrace(); + return null; + } + } + + +} \ No newline at end of file diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/Customizations.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/Customizations.java new file mode 100644 index 00000000..cd8b15f7 --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/Customizations.java @@ -0,0 +1,11 @@ +package org.jvnet.jaxb2_commons.plugin.fluent_api; + +import javax.xml.namespace.QName; + +public class Customizations { + + public static String NAMESPACE_URI = "http://jaxb2-commons.dev.java.net/basic/fluent-api"; + + public static QName IGNORED_ELEMENT_NAME = new QName(NAMESPACE_URI, "ignored"); + +} \ No newline at end of file diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentApiPlugin.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentApiPlugin.java new file mode 100644 index 00000000..38f8beba --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentApiPlugin.java @@ -0,0 +1,234 @@ +/* + * 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.Plugin; +import com.sun.tools.xjc.Options; +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.
+ *

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

+ * Enhancement on 11 June 2006:
+ * Provide fluent setter api for Lists, with support of variable arguments. + * + * This enhancement was suggested by Kenny MacLeod , + * and endorsed by Kohsuke Kawaguchi . + * Here is quoted from the original request: + *

+ * 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:

+ * // 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;
+ * }
+ *
+ * Enhancement on 11 Oct 2008:
+ * Provide fluent setter api for Lists, with support of Collection argument in addition to varargs arguments. + * + * This enhancement was suggested by Alex Wei with patch submitted. See + * Jira Issue 12 for more details. + *

+ * @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 fluentMethodInfoList = new ArrayList(); + Set methodNames = new HashSet(); + 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; 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 + JType jtype = jmethod.type(); + + if (jtype instanceof JClass) + { + JClass jclass = JClass.class.cast(jtype); + List 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())); + } +} \ No newline at end of file diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentMethodInfo.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentMethodInfo.java new file mode 100644 index 00000000..897888af --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentMethodInfo.java @@ -0,0 +1,55 @@ +/* + * 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 com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JMethod; + +/** + * Information used to generate a fluent API method. + * + * @author Hanson Char + */ +public class FluentMethodInfo { + // Original method to used to derive the fluent API method. + private final JMethod jmethod; + // True if this method will be overriding a parent method; false otherwise. + private final boolean isOverride; + // Type of fluent API method to be generated. + private final FluentMethodType fluentMethodType; + + public FluentMethodInfo(JMethod jmethod, FluentMethodType fluentMethodType, boolean isOverride) + { + this.jmethod = jmethod; + this.fluentMethodType = fluentMethodType; + this.isOverride = isOverride; + } + + /** Creates a fluent API method in the given class. */ + public void createFluentMethod(JDefinedClass implClass) { + fluentMethodType.createFluentMethod(implClass, this); + } + + /** Returns true if the fluent API method is one overriding a parent method. */ + public boolean isOverride() { + return isOverride; + } + + /** Returns the original method for which a fluent API method will be generated. */ + public JMethod getJmethod() { + return jmethod; + } +} \ No newline at end of file diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentMethodType.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentMethodType.java new file mode 100644 index 00000000..a25b87be --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/fluent_api/FluentMethodType.java @@ -0,0 +1,172 @@ +/* + * 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 java.util.Collection; +import java.util.List; + +import com.sun.codemodel.JBlock; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JConditional; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JForEach; +import com.sun.codemodel.JInvocation; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JType; +import com.sun.codemodel.JVar; + +/** + * @author Hanson Char + */ +public enum FluentMethodType { + FLUENT_SETTER + { + /** + * Adds a fluent api method, which invokes the given setter method, to the given class. + * This applies to both simple property setter method and + * indexed property setter method. + */ + @Override + public void createFluentMethod(JDefinedClass implClass, FluentMethodInfo fluentMethodInfo) + { + JMethod setterMethod = fluentMethodInfo.getJmethod(); + String name = setterMethod.name(); + // Create a with* method for the respective set* method. + int mods = JMod.PUBLIC | setterMethod.mods().getValue() & JMod.FINAL; + JMethod fluentMethod = implClass.method(mods, implClass, + FLUENT_SETTER_METHOD_PREFIX + name.substring(SETTER_METHOD_PREFIX_LEN)); + if (fluentMethodInfo.isOverride()) + fluentMethod.annotate(Override.class); + JVar[] jvars = setterMethod.listParams(); + // jvars.length == 1 means simple property setter method. + // jvars.length == 2 means indexed property setter method. + assert jvars.length == 1 || jvars.length == 2; + // with the same parameter(s) as the set* method + for (JVar jvar : jvars) + fluentMethod.param(jvar.mods().getValue(), jvar.type(), jvar.name()); + JBlock jblock = fluentMethod.body(); + // The with* method in turn invoke the setter method + JInvocation jinvocation = jblock.invoke(setterMethod); + // passing the same list of arguments + for (JVar jvar : jvars) + jinvocation.arg(jvar); + // and return "this" + jblock._return(JExpr._this()); + return; + } + }, + FLUENT_LIST_SETTER + { + /** + * Create a fluent setter method for List, with support of variable arguments. + */ + @Override + public void createFluentMethod(JDefinedClass implClass, FluentMethodInfo fluentMethodInfo) + { + JMethod listGetterMethod = fluentMethodInfo.getJmethod(); + String name = listGetterMethod.name(); + // Create a with* method for the respective List get* method. + int mods = JMod.PUBLIC | listGetterMethod.mods().getValue() & JMod.FINAL; + JMethod fluentMethod = implClass.method(mods, implClass, + FLUENT_SETTER_METHOD_PREFIX + name.substring(GETTER_METHOD_PREFIX_LEN)); + if (fluentMethodInfo.isOverride()) + fluentMethod.annotate(Override.class); + JType returnJType = listGetterMethod.type(); + // As is already checked in isListGetterMethod(JMethod): + // 1) the return type must be a subtype of JClass; and + // 2) the number of type parameters must be 1 + JClass returnJClass = JClass.class.cast(returnJType); + List typeParams = returnJClass.getTypeParameters(); + assert typeParams.size() == 1; + JClass typeParam = typeParams.get(0); + // Support variable arguments + JVar jvarParam = fluentMethod.varParam(typeParam, VALUES); + JBlock body = fluentMethod.body(); + JConditional cond = body._if( + jvarParam.ne( + JExpr._null())); + JForEach forEach = cond._then() + .forEach( + typeParam, VALUE, JExpr.ref(VALUES)); + JInvocation addInvocation = forEach.body() + .invoke( + JExpr.invoke(listGetterMethod), "add"); + addInvocation.arg( + JExpr.ref(VALUE)); + // and return "this" + body._return( + JExpr._this()); + return; + } + }, + FLUENT_COLLECTION_SETTER + { + // Originally proposed by Alex Wei ozgwei@dev.java.net: + // https://jaxb2-commons.dev.java.net/issues/show_bug.cgi?id=12 + /** + * Create a fluent setter method for List, with support of a java.util.Collection argument. + */ + @Override + public void createFluentMethod(JDefinedClass implClass, FluentMethodInfo fluentMethodInfo) + { + JMethod listGetterMethod = fluentMethodInfo.getJmethod(); + String name = listGetterMethod.name(); + // Create a with* method for the respective List get* method. + int mods = JMod.PUBLIC | listGetterMethod.mods().getValue() & JMod.FINAL; + JMethod fluentMethod = implClass.method(mods, implClass, + FLUENT_SETTER_METHOD_PREFIX + name.substring(GETTER_METHOD_PREFIX_LEN)); + if (fluentMethodInfo.isOverride()) + fluentMethod.annotate(Override.class); + JType returnJType = listGetterMethod.type(); + // As is already checked in isListGetterMethod(JMethod): + // 1) the return type must be a subtype of JClass; and + // 2) the number of type parameters must be 1 + JClass returnJClass = JClass.class.cast(returnJType); + List typeParams = returnJClass.getTypeParameters(); + assert typeParams.size() == 1; + JClass typeParam = typeParams.get(0); + // Support Collection with type parameter + JClass narrowedCollectionJClass = implClass.owner().ref(Collection.class).narrow(typeParam); + JVar jvarParam = fluentMethod.param(narrowedCollectionJClass, VALUES); + JBlock body = fluentMethod.body(); + JConditional cond = body._if( + jvarParam.ne( + JExpr._null())); + JInvocation addInvocation = cond._then() + .invoke( + JExpr.invoke(listGetterMethod), "addAll"); + addInvocation.arg(jvarParam); + // and return "this" + body._return( + JExpr._this()); + return; + } + } + ; + private static final String VALUE = "value"; + private static final String VALUES = "values"; + public static final String GETTER_METHOD_PREFIX = "get"; + public static final String SETTER_METHOD_PREFIX = "set"; + public static final String FLUENT_SETTER_METHOD_PREFIX = "with"; + public static final String PARAMETERIZED_LIST_PREFIX = List.class.getName() + "<"; + + public static final int SETTER_METHOD_PREFIX_LEN = SETTER_METHOD_PREFIX.length(); + public static final int GETTER_METHOD_PREFIX_LEN = GETTER_METHOD_PREFIX.length(); + + public abstract void createFluentMethod(JDefinedClass implClass, FluentMethodInfo fluentMethodInfo); +} \ No newline at end of file diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/namespace_prefix/NamespacePrefixPlugin.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/namespace_prefix/NamespacePrefixPlugin.java new file mode 100644 index 00000000..ed324841 --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/namespace_prefix/NamespacePrefixPlugin.java @@ -0,0 +1,337 @@ +package org.jvnet.jaxb2_commons.plugin.namespace_prefix; + +import javax.xml.bind.annotation.XmlNs; +import javax.xml.bind.annotation.XmlSchema; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.sun.codemodel.JAnnotationArrayMember; +import com.sun.codemodel.JAnnotationUse; +import com.sun.codemodel.JAnnotationValue; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JPackage; +import com.sun.codemodel.JStringLiteral; +import com.sun.tools.xjc.Options; +import com.sun.tools.xjc.Plugin; +import com.sun.tools.xjc.generator.bean.PackageOutlineImpl; +import com.sun.tools.xjc.model.CCustomizations; +import com.sun.tools.xjc.model.CPluginCustomization; +import com.sun.tools.xjc.model.Model; +import com.sun.tools.xjc.outline.Outline; +import com.sun.tools.xjc.outline.PackageOutline; +import com.sun.tools.xjc.reader.xmlschema.bindinfo.BIDeclaration; +import com.sun.tools.xjc.reader.xmlschema.bindinfo.BIXPluginCustomization; +import com.sun.tools.xjc.reader.xmlschema.bindinfo.BindInfo; +import com.sun.xml.xsom.XSAnnotation; +import com.sun.xml.xsom.XSSchema; +import com.sun.xml.xsom.impl.SchemaImpl; +import org.xml.sax.ErrorHandler; + +/** + * This plugin adds {@link jakarta.xml.bind.annotation.XmlNs} annotations to package-info.java files. Those annotations tells Jaxb to generate XML schema's instances with specific namespaces + * prefixes, instead of the auto-generated (ns1, ns2, ...) prefixes. Definition of thoses prefixes is done in the bindings.xml file. + *

+ * Bindings.xml file example: + *

+ *  <?xml version="1.0"?>
+ *  <jxb:bindings version="3.0"
+ *      xmlns:jxb="https://jakarta.ee/xml/ns/jaxb"
+ *      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ *      xmlns:namespace="http://jaxb2-commons.dev.java.net/basic/namespace-prefix">
+ *
+ *      <jxb:bindings schemaLocation="unireg-common-1.xsd">
+ *          <jxb:schemaBindings>
+ *              <jxb:package name="ch.vd.unireg.xml.common.v1" />
+ *          </jxb:schemaBindings>
+ *          <jxb:bindings>
+ *              <namespace:prefix name="common-1" />
+ *          </jxb:bindings>
+ *      </jxb:bindings>
+ *
+ *  </jxb:bindings>
+ * 
+ * + * @author Manuel Siggen (c) 2012 Etat-de-Vaud (www.vd.ch) + */ +@SuppressWarnings("UnusedDeclaration") +public class NamespacePrefixPlugin extends Plugin { + + private static final String NAMESPACE_URI = "http://jaxb2-commons.dev.java.net/basic/namespace-prefix"; + + @Override + public String getOptionName() { + return "Xnamespace-prefix"; + } + + @Override + public String getUsage() { + return "-Xnamespace-prefix : activate namespaces prefix customizations"; + } + + @Override + public List getCustomizationURIs() { + return Arrays.asList(NAMESPACE_URI); + } + + @Override + public boolean isCustomizationTagName(String nsUri, String localName) { + return NAMESPACE_URI.equals(nsUri) && "prefix".equals(localName); + } + + @Override + public boolean run(final Outline outline, final Options options, final ErrorHandler errorHandler) { + + final JClass xmlNsClass = outline.getCodeModel().ref(XmlNs.class); + final JClass xmlSchemaClass = outline.getCodeModel().ref(XmlSchema.class); + + for (PackageOutline packageOutline : outline.getAllPackageContexts()) { + final JPackage p = packageOutline._package(); + + // get the target namespaces of all schemas that bind to the current package + final Set packageNamespaces = getPackageNamespace(packageOutline); + + // is there any prefix binding defined for the current package ? + final Model packageModel = getPackageModel((PackageOutlineImpl) packageOutline); + final List list = getPrefixBinding(packageModel, packageNamespaces); + acknowledgePrefixAnnotations(packageModel); + + if (list == null || list.isEmpty()) { + // no prefix binding, nothing to do + continue; + } + + // add XML namespace prefix annotations + final JAnnotationUse xmlSchemaAnnotation = getOrAddXmlSchemaAnnotation(p, xmlSchemaClass); + if (xmlSchemaAnnotation == null) { + throw new RuntimeException("Unable to get/add 'XmlSchema' annotation to package [" + p.name() + "]"); + } + + final JAnnotationArrayMember members = xmlSchemaAnnotation.paramArray("xmlns"); + for (Pair pair : list) { + addNamespacePrefix(xmlNsClass, members, pair.getNamespace(), pair.getPrefix()); + } + } + + return true; + } + + private static Set getPackageNamespace(PackageOutline packageOutline) { + final Map map = getUriCountMap(packageOutline); + return map == null ? Collections.emptySet() : map.keySet(); + } + + /** + * Make sure the prefix annotations have been acknowledged. + * + * @param packageModel the package model + */ + private void acknowledgePrefixAnnotations(Model packageModel) { + final CCustomizations customizations = packageModel.getCustomizations(); + if (customizations != null) { + for (CPluginCustomization customization : customizations) { + if (customization.element.getNamespaceURI().equals(NAMESPACE_URI)) { + if (!customization.element.getLocalName().equals("prefix")) { + throw new RuntimeException("Unrecognized element [" + customization.element.getLocalName() + "]"); + } + customization.markAsAcknowledged(); + } + } + } + } + + /** + * This method detects prefixes for a given package as specified in the bindings file. Usually, there is only one namespace per package, but there may be more. + * + * @param packageModel the package model + * @param packageNamespace the target namespace for the package + * @return the prefix annotations + */ + private static List getPrefixBinding(Model packageModel, Set packageNamespace) { + + final List list = new ArrayList(); + + // loop on existing schemas (XSD files) + for (XSSchema schema : packageModel.schemaComponent.getSchemas()) { + + final SchemaImpl s = (SchemaImpl) schema; + final XSAnnotation annotation = s.getAnnotation(); + if (annotation == null) { + continue; + } + + final Object anno = annotation.getAnnotation(); + if (anno == null || !(anno instanceof BindInfo)) { + continue; + } + + final BindInfo b = (BindInfo) anno; + final String targetNS = b.getOwner().getOwnerSchema().getTargetNamespace(); + + if (!packageNamespace.contains(targetNS)) { // only consider schemas that bind the current package + continue; + } + + // get the prefix's name + String prefix = ""; + for (BIDeclaration declaration : b.getDecls()) { + if (declaration instanceof BIXPluginCustomization) { + final BIXPluginCustomization customization = (BIXPluginCustomization) declaration; + if (customization.element.getNamespaceURI().equals(NAMESPACE_URI)) { + if (!customization.element.getLocalName().equals("prefix")) { + throw new RuntimeException("Unrecognized element [" + customization.element.getLocalName() + "]"); + } + prefix = customization.element.getAttribute("name"); + customization.markAsAcknowledged(); + break; + } + } + } + + list.add(new Pair(targetNS, prefix)); + } + + return list; + } + + private static void addNamespacePrefix(JClass xmlNsClass, JAnnotationArrayMember members, String namespace, String prefix) { + final JAnnotationUse ns = members.annotate(xmlNsClass); + ns.param("namespaceURI", namespace); + ns.param("prefix", prefix); + } + + @SuppressWarnings("unchecked") + private static JAnnotationUse getOrAddXmlSchemaAnnotation(JPackage p, JClass xmlSchemaClass) { + + JAnnotationUse xmlAnn = null; + + final List annotations = getAnnotations(p); + if (annotations != null) { + for (JAnnotationUse annotation : annotations) { + final JClass clazz = getAnnotationJClass(annotation); + if (clazz == xmlSchemaClass) { + xmlAnn = annotation; + break; + } + } + } + + if (xmlAnn == null) { + // XmlSchema annotation not found, let's add one + xmlAnn = p.annotate(xmlSchemaClass); + } + + return xmlAnn; + } + + @SuppressWarnings("unchecked") + private static Map getUriCountMap(PackageOutline packageOutline) { + try { + final Field field = PackageOutlineImpl.class.getDeclaredField("uriCountMap"); + field.setAccessible(true); + return (Map) field.get(packageOutline); + } + catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to access 'uriCountMap' field for package outline [" + packageOutline._package().name() + "] : " + e.getMessage(), e); + } + catch (IllegalAccessException e) { + throw new RuntimeException("Unable to find 'uriCountMap' field for package outline [" + packageOutline._package().name() + "] : " + e.getMessage(), e); + } + } + + private static Model getPackageModel(PackageOutlineImpl packageOutline) { + try { + final Field field = PackageOutlineImpl.class.getDeclaredField("_model"); + field.setAccessible(true); + return (Model) field.get(packageOutline); + } + catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to access '_model' field for package outline [" + packageOutline._package().name() + "] : " + e.getMessage(), e); + } + catch (IllegalAccessException e) { + throw new RuntimeException("Unable to find '_model' field for package outline [" + packageOutline._package().name() + "] : " + e.getMessage(), e); + } + } + + @SuppressWarnings("unchecked") + private static List getAnnotations(JPackage p) { + // TODO bump jaxb-xjc dependency to version >= 2.2.2 and use the annotations() method instead. + try { + final Field annotationsField = JPackage.class.getDeclaredField("annotations"); + annotationsField.setAccessible(true); + return (List) annotationsField.get(p); + } + catch (IllegalAccessException e) { + throw new RuntimeException("Unable to access 'annotation' field for package [" + p.name() + "] : " + e.getMessage(), e); + } + catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to find 'annotation' field for package [" + p.name() + "] : " + e.getMessage(), e); + } + } + + private static JClass getAnnotationJClass(JAnnotationUse annotation) { + try { + final Field clazzField = JAnnotationUse.class.getDeclaredField("clazz"); + clazzField.setAccessible(true); + return (JClass) clazzField.get(annotation); + } + catch (IllegalAccessException e) { + throw new RuntimeException("Unable to access 'clazz' field for class [JAnnotationUse] : " + e.getMessage(), e); + } + catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to find 'annotation' field for class [JAnnotationUse] : " + e.getMessage(), e); + } + } + + @SuppressWarnings("unchecked") + private static Map getAnnotationMemberValues(JAnnotationUse annotation) { + try { + final Field clazzField = JAnnotationUse.class.getDeclaredField("memberValues"); + clazzField.setAccessible(true); + return (Map) clazzField.get(annotation); + } + catch (IllegalAccessException e) { + throw new RuntimeException("Unable to access 'memberValues' field for class [JAnnotationUse] : " + e.getMessage(), e); + } + catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to find 'memberValues' field for class [JAnnotationUse] : " + e.getMessage(), e); + } + } + + private static String getStringAnnotationValue(JAnnotationValue val) { + try { + final Field clazzField = val.getClass().getDeclaredField("value"); + clazzField.setAccessible(true); + final JStringLiteral j = (JStringLiteral) clazzField.get(val); + return j == null ? null : j.str; + } + catch (IllegalAccessException e) { + throw new RuntimeException("Unable to access 'value' field for class [" + val.getClass() + "] : " + e.getMessage(), e); + } + catch (NoSuchFieldException e) { + throw new RuntimeException("Unable to find 'value' field for class [" + val.getClass() + "] : " + e.getMessage(), e); + } + } + + private static class Pair { + private final String namespace; + private final String prefix; + + private Pair(String namespace, String prefix) { + this.namespace = namespace; + this.prefix = prefix; + } + + public String getNamespace() { + return namespace; + } + + public String getPrefix() { + return prefix; + } + } +} \ No newline at end of file diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/value_constructor/Customizations.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/value_constructor/Customizations.java new file mode 100644 index 00000000..cbe23b34 --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/value_constructor/Customizations.java @@ -0,0 +1,11 @@ +package org.jvnet.jaxb2_commons.plugin.value_constructor; + +import javax.xml.namespace.QName; + +public class Customizations { + + public static String NAMESPACE_URI = "http://jaxb2-commons.dev.java.net/basic/value-constructor"; + + public static QName IGNORED_ELEMENT_NAME = new QName(NAMESPACE_URI, "ignored"); + +} \ No newline at end of file diff --git a/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/value_constructor/ValueConstructorPlugin.java b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/value_constructor/ValueConstructorPlugin.java new file mode 100644 index 00000000..7508f840 --- /dev/null +++ b/basic/src/main/java/org/jvnet/jaxb2_commons/plugin/value_constructor/ValueConstructorPlugin.java @@ -0,0 +1,153 @@ +/* + * 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.value_constructor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +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.codemodel.JVar; +import com.sun.tools.xjc.Plugin; +import com.sun.tools.xjc.Options; +import com.sun.tools.xjc.outline.ClassOutline; +import com.sun.tools.xjc.outline.Outline; + +/** + * Generate two constructors for each generated class, one of which is a default constructor, + * the other takes an argument for each field in the class and initialises the field with the + * argument value. + * + * Without this plugin, XJC will not generate any explicit constructors. + * + * @author Kenny MacLeod + * $Id: XjcValueConstructorPlugin.java,v 1.7 2007-11-26 18:35:27 skaffman Exp $ + */ +public class ValueConstructorPlugin extends Plugin +{ + @Override + public String getOptionName() + { + return "Xvalue-constructor"; + } + + @Override + public String getUsage() + { + return " -Xvalue-constructor : enable generation of value constructors"; + } + + @Override + public boolean run(final Outline outline, final Options options, final ErrorHandler errorHandler) { + // For each defined class + for (final ClassOutline classOutline : outline.getClasses()) { + final JDefinedClass implClass = classOutline.implClass; + + // Create the default, no-arg constructor + @SuppressWarnings("unused") + final JMethod defaultConstructor = implClass.constructor(JMod.PUBLIC); + defaultConstructor.javadoc().add("Default no-arg constructor"); + defaultConstructor.body().invoke("super"); + + final Collection superClassInstanceFields = getInstanceFields(getSuperclassFields(implClass)); + final Collection thisClassInstanceFields = getInstanceFields(implClass.fields().values()); + + final boolean doGenerateValueConstructor = !superClassInstanceFields.isEmpty() || !thisClassInstanceFields.isEmpty(); + + // If the class or its (generated) superclass has fields, then generate a value constructor + if (doGenerateValueConstructor) { + + // Create the skeleton of the value constructor + final JMethod valueConstructor = implClass.constructor(JMod.PUBLIC); + valueConstructor.javadoc().add("Fully-initialising value constructor"); + + // If our superclass is also being generated, then we can assume it will also have + // its own value constructor, so we add an invocation of that constructor. + if (implClass._extends() instanceof JDefinedClass) { + + final JInvocation superInvocation = valueConstructor.body().invoke("super"); + + // Add each argument to the super constructor. + for (JFieldVar superClassField : superClassInstanceFields) { + if (generateConstructorParameter(superClassField)) { + final JVar arg = valueConstructor.param(JMod.FINAL, superClassField.type(), superClassField.name()); + superInvocation.arg(arg); + } + } + } + + // Now add constructor parameters for each field in "this" class, and assign them to + // our fields. + for (final JFieldVar field : thisClassInstanceFields) { + if (generateConstructorParameter(field)) { + final JVar arg = valueConstructor.param(JMod.FINAL, field.type(), field.name()); + valueConstructor.body().assign(JExpr.refthis(field.name()), arg); + } + } + } + } + + return true; + } + + /** + * Takes a collection of fields, and returns a new collection containing only the instance + * (i.e. non-static) fields. + */ + protected Collection getInstanceFields(final Collection fields) { + final List instanceFields = new ArrayList(); + for (final JFieldVar fieldVar : fields) { + final boolean isStaticField = (fieldVar.mods().getValue() & JMod.STATIC) != 0; + if (!isStaticField) { + instanceFields.add(fieldVar); + } + } + return instanceFields; + } + + /** + * Whether or not to generate a constructor parameter for the given field. + */ + protected boolean generateConstructorParameter(final JFieldVar field) { + final boolean isStaticField = (field.mods().getValue() & JMod.STATIC) > 0; + return !isStaticField; + } + + /** + * Retrieve a List of the fields of each ancestor class. I walk up the class hierarchy + * until I reach a class that isn't being generated by JAXB. + */ + protected List getSuperclassFields(final JDefinedClass implClass) { + final List fieldList = new LinkedList(); + + JClass superclass = implClass._extends(); + while (superclass instanceof JDefinedClass) { + fieldList.addAll(0, ((JDefinedClass)superclass).fields().values()); + superclass = superclass._extends(); + } + + return fieldList; + } +} \ No newline at end of file diff --git a/basic/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin b/basic/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin index 9c97afad..d5915081 100644 --- a/basic/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin +++ b/basic/src/main/resources/META-INF/services/com.sun.tools.xjc.Plugin @@ -14,4 +14,9 @@ org.jvnet.jaxb2_commons.plugin.simplehashcode.SimpleHashCodePlugin org.jvnet.jaxb2_commons.plugin.simpleequals.SimpleEqualsPlugin org.jvnet.jaxb2_commons.plugin.enumvalue.EnumValuePlugin org.jvnet.jaxb2_commons.plugin.fixjaxb1058.FixJAXB1058Plugin -org.jvnet.jaxb2_commons.plugin.customizations.CustomizationsPlugin \ No newline at end of file +org.jvnet.jaxb2_commons.plugin.customizations.CustomizationsPlugin +org.jvnet.jaxb2_commons.plugin.value_constructor.ValueConstructorPlugin +org.jvnet.jaxb2_commons.plugin.fluent_api.FluentApiPlugin +org.jvnet.jaxb2_commons.plugin.namespace_prefix.NamespacePrefixPlugin +org.jvnet.jaxb2_commons.plugin.defaultvalueplugin.DefaultValuePlugin +org.jvnet.jaxb2_commons.plugin.commons_lang.XjcCommonsLangPlugin \ No newline at end of file diff --git a/tests/commons_lang/pom.xml b/tests/commons_lang/pom.xml new file mode 100644 index 00000000..aaebaf8a --- /dev/null +++ b/tests/commons_lang/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + + org.jvnet.jaxb2_commons + jaxb2-basics-tests + 2.3.6-SNAPSHOT + + jaxb2-basics-test-commonslang + jar + JAXB2 Basics - Test [commons_lang] + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin-testing + test + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + test + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin + + true + + -Xcommons-lang + + + + org.jvnet.jaxb2_commons + jaxb2-basics + + + + + + + diff --git a/tests/commons_lang/src/main/resources/Person.xsd b/tests/commons_lang/src/main/resources/Person.xsd new file mode 100644 index 00000000..13f760a5 --- /dev/null +++ b/tests/commons_lang/src/main/resources/Person.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/commons_lang/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/AddressTest.java b/tests/commons_lang/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/AddressTest.java new file mode 100644 index 00000000..54b92e35 --- /dev/null +++ b/tests/commons_lang/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/AddressTest.java @@ -0,0 +1,22 @@ +package org.jvnet.jaxb2_commons.tests.commons_lang; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.junit.Assert; +import org.junit.Test; + +import generated.Address; + +public class AddressTest { + + @Test + public void testAddress() { + Address a = new Address(); + // No plugin default-value present, checking everything is null or default java value + Assert.assertEquals(0, a.getNumber()); + Assert.assertNull(a.getCareOf()); + Assert.assertNull(a.getStreet()); + + Assert.assertEquals(ToStringBuilder.reflectionToString(a, ToStringStyle.MULTI_LINE_STYLE), a.toString()); + } +} \ No newline at end of file diff --git a/tests/commons_lang/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java b/tests/commons_lang/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java new file mode 100644 index 00000000..4a6b3da3 --- /dev/null +++ b/tests/commons_lang/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java @@ -0,0 +1,20 @@ +package org.jvnet.jaxb2_commons.tests.commons_lang; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.junit.Assert; +import org.junit.Test; + +import generated.Person; + +public class PersonTest { + + @Test + public void testPerson() { + Person p = new Person(); + // No plugin default-value present, checking everything is null or default java value + Assert.assertEquals(false, p.isMailingAddressIdentical()); + + Assert.assertEquals(ToStringBuilder.reflectionToString(p, ToStringStyle.MULTI_LINE_STYLE), p.toString()); + } +} \ No newline at end of file diff --git a/tests/commons_lang/src/test/resources/log4j.properties b/tests/commons_lang/src/test/resources/log4j.properties new file mode 100644 index 00000000..9c985635 --- /dev/null +++ b/tests/commons_lang/src/test/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootCategory=DEBUG, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.target=system.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/tests/defaultvalue/pom.xml b/tests/defaultvalue/pom.xml new file mode 100644 index 00000000..16db54b0 --- /dev/null +++ b/tests/defaultvalue/pom.xml @@ -0,0 +1,45 @@ + + 4.0.0 + + org.jvnet.jaxb2_commons + jaxb2-basics-tests + 2.3.6-SNAPSHOT + + jaxb2-basics-test-defaultvalue + jar + JAXB2 Basics - Test [default-value] + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin-testing + test + + + org.jvnet.jaxb2_commons + jaxb2-basics-runtime + + + + test + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin + + true + + -Xdefault-value + + + + org.jvnet.jaxb2_commons + jaxb2-basics + + + + + + + diff --git a/tests/defaultvalue/src/main/resources/Person.xsd b/tests/defaultvalue/src/main/resources/Person.xsd new file mode 100644 index 00000000..13f760a5 --- /dev/null +++ b/tests/defaultvalue/src/main/resources/Person.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/defaultvalue/src/test/java/org/jvnet/jaxb2_commons/tests/defaultvalue/AddressTest.java b/tests/defaultvalue/src/test/java/org/jvnet/jaxb2_commons/tests/defaultvalue/AddressTest.java new file mode 100644 index 00000000..43792d5f --- /dev/null +++ b/tests/defaultvalue/src/test/java/org/jvnet/jaxb2_commons/tests/defaultvalue/AddressTest.java @@ -0,0 +1,18 @@ +package org.jvnet.jaxb2_commons.tests.defaultvalue; + +import org.junit.Assert; +import org.junit.Test; + +import generated.Address; + +public class AddressTest { + + @Test + public void testAddress() { + Address a = new Address(); + Assert.assertEquals(42, a.getNumber()); + Assert.assertEquals("none", a.getCareOf()); + // no default value for street + Assert.assertNull(a.getStreet()); + } +} diff --git a/tests/defaultvalue/src/test/java/org/jvnet/jaxb2_commons/tests/defaultvalue/PersonTest.java b/tests/defaultvalue/src/test/java/org/jvnet/jaxb2_commons/tests/defaultvalue/PersonTest.java new file mode 100644 index 00000000..5c61ed9f --- /dev/null +++ b/tests/defaultvalue/src/test/java/org/jvnet/jaxb2_commons/tests/defaultvalue/PersonTest.java @@ -0,0 +1,15 @@ +package org.jvnet.jaxb2_commons.tests.defaultvalue; + +import org.junit.Assert; +import org.junit.Test; + +import generated.Person; + +public class PersonTest { + + @Test + public void testPerson() { + Person p = new Person(); + Assert.assertEquals(true, p.isMailingAddressIdentical()); + } +} diff --git a/tests/defaultvalue/src/test/resources/log4j.properties b/tests/defaultvalue/src/test/resources/log4j.properties new file mode 100644 index 00000000..9c985635 --- /dev/null +++ b/tests/defaultvalue/src/test/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootCategory=DEBUG, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.target=system.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/tests/namespace/pom.xml b/tests/namespace/pom.xml new file mode 100644 index 00000000..3b95a045 --- /dev/null +++ b/tests/namespace/pom.xml @@ -0,0 +1,45 @@ + + 4.0.0 + + org.jvnet.jaxb2_commons + jaxb2-basics-tests + 2.3.6-SNAPSHOT + + jaxb2-basics-test-namespace + jar + JAXB2 Basics - Test [namespace] + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin-testing + test + + + org.jvnet.jaxb2_commons + jaxb2-basics-runtime + + + + test + + + org.jvnet.jaxb2.maven2 + maven-jaxb2-plugin + + true + + -Xnamespace-prefix + + + + org.jvnet.jaxb2_commons + jaxb2-basics + + + + + + + diff --git a/tests/namespace/src/main/resources/a.xsd b/tests/namespace/src/main/resources/a.xsd new file mode 100644 index 00000000..51d4ee39 --- /dev/null +++ b/tests/namespace/src/main/resources/a.xsd @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/namespace/src/main/resources/b.xsd b/tests/namespace/src/main/resources/b.xsd new file mode 100644 index 00000000..0b45601e --- /dev/null +++ b/tests/namespace/src/main/resources/b.xsd @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/namespace/src/main/resources/binding.xjb b/tests/namespace/src/main/resources/binding.xjb new file mode 100644 index 00000000..83eca62f --- /dev/null +++ b/tests/namespace/src/main/resources/binding.xjb @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/tests/namespace/src/test/java/org/jvnet/jaxb2_commons/tests/namespace/RunNamespacePlugin.java b/tests/namespace/src/test/java/org/jvnet/jaxb2_commons/tests/namespace/RunNamespacePlugin.java new file mode 100644 index 00000000..36eca2c6 --- /dev/null +++ b/tests/namespace/src/test/java/org/jvnet/jaxb2_commons/tests/namespace/RunNamespacePlugin.java @@ -0,0 +1,26 @@ +package org.jvnet.jaxb2_commons.tests.namespace; + +import java.util.ArrayList; +import java.util.List; + +import org.jvnet.jaxb2.maven2.AbstractXJC2Mojo; +import org.jvnet.jaxb2.maven2.test.RunXJC2Mojo; + +public class RunNamespacePlugin extends RunXJC2Mojo { + + @Override + protected void configureMojo(AbstractXJC2Mojo mojo) { + super.configureMojo(mojo); + mojo.setExtension(true); + mojo.setForceRegenerate(true); + mojo.setDebug(false); + } + + @Override + public List getArgs() { + final List args = new ArrayList(super.getArgs()); + args.add("-Xnamespace-prefix"); + return args; + } + +} \ No newline at end of file diff --git a/tests/namespace/src/test/resources/log4j.properties b/tests/namespace/src/test/resources/log4j.properties new file mode 100644 index 00000000..9c985635 --- /dev/null +++ b/tests/namespace/src/test/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootCategory=DEBUG, stdout +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.target=system.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n \ No newline at end of file diff --git a/tests/pom.xml b/tests/pom.xml index 60766bfd..1c2522ca 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -11,10 +11,13 @@ 2.3.6-SNAPSHOT + commons_lang + defaultvalue episodes ignoring issues JAXB-1058 + namespace one simple-hashCode-equals-01 simplify-01