diff --git a/.github/workflows/maven-build-action.yaml b/.github/workflows/maven-build-action.yaml index 1bdb386f8..30d86d8d7 100644 --- a/.github/workflows/maven-build-action.yaml +++ b/.github/workflows/maven-build-action.yaml @@ -5,6 +5,8 @@ on: branches: [ master, jaxb-tools-4.0.x, jaxb-tools-3.x, jaxb-tools-2.x, 0.15.x ] pull_request: branches: [ master, jaxb-tools-4.0.x, jaxb-tools-3.x, jaxb-tools-2.x, 0.15.x ] + workflow_dispatch: + ## Allow's manual runs jobs: build_and_test: diff --git a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPlugin.java b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPlugin.java index 1af1ba23d..7afcb0d87 100644 --- a/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPlugin.java +++ b/jaxb-plugins-parent/jaxb-plugins/src/main/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPlugin.java @@ -15,6 +15,8 @@ */ package org.jvnet.jaxb.plugin.commons_lang; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.xml.sax.ErrorHandler; import com.sun.codemodel.JCodeModel; @@ -32,7 +34,7 @@ /** * Automatically generates the toString(), hashCode() and equals() methods - * using Jakarta's commons-lang. + * using org.apache.commons:commons-lang3. * * Supports the optional ToStringStyle command line parameter to specify * the style for use within the toString method. @@ -50,11 +52,11 @@ * Example 2: * * -Xcommons-lang - * -Xcommons-lang:ToStringStyle=my.CustomToStringStyle + * -Xcommons-lang:ToStringStyle=my.CustomToStringStyleClass * * to specify the use of * - * my.CustomToStringStyle, which must be a subclass of + * my.CustomToStringStyleClass, which must be a subclass of * * org.apache.commons.lang3.builder.ToStringStyle, and contains a public no-arg constructor. * @@ -71,8 +73,11 @@ public class XjcCommonsLangPlugin extends Plugin private static final String EQUALSBUILDER_CLASSNAME = "org.apache.commons.lang3.builder.EqualsBuilder"; private static final String HASHCODEBUILDER_CLASSNAME = "org.apache.commons.lang3.builder.HashCodeBuilder"; private static final String TOSTRINGBUILDER_CLASSNAME = "org.apache.commons.lang3.builder.ToStringBuilder"; + + protected Log logger = LogFactory.getLog(getClass()); + private String toStringStyle = "MULTI_LINE_STYLE"; - private Class customToStringStyle; + private String toStringClass = null; @Override public String getOptionName() @@ -83,13 +88,20 @@ public String getOptionName() @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" + return " -Xcommons-lang : generate toString(), hashCode() and equals() for generated code using Jakarta's common-lang " + + "[\n" + + "\t -Xcommons-lang:ToStringStyle=MULTI_LINE_STYLE\n" + + "\t| -Xcommons-lang:ToStringStyle=DEFAULT_STYLE\n" + + "\t| -Xcommons-lang:ToStringStyle=NO_FIELD_NAMES_STYLE\n" + + "\t| -Xcommons-lang:ToStringStyle=SHORT_PREFIX_STYLE\n" + + "\t| -Xcommons-lang:ToStringStyle=SIMPLE_STYLE\n" + + "\t| -Xcommons-lang:ToStringStyle=NO_CLASS_NAME_STYLE\n" + + "\t| -Xcommons-lang:ToStringStyle=JSON_STYLE\n" + + "\t| -Xcommons-lang:ToStringStyle=$\n" + + "\t| -Xcommons-lang:ToStringStyle=\n" + + "]\n" + + " Note: custom ToStringStyle class is needed if you wish to turn off setUseIdentityHashCode on top of MULTI_LINE_STYLE" + + "\n" ; } @@ -115,22 +127,25 @@ private void createToStringMethod(JDefinedClass implClass) implClass.method(JMod.PUBLIC, codeModel.ref(String.class), "toString"); // Annotate with @Override toStringMethod.annotate(Override.class); - final JExpression toStringStyleExpr = - customToStringStyle == null - ? codeModel.ref(TOSTRINGSTYLE_CLASSNAME) - .staticRef(toStringStyle) - : JExpr._new( - codeModel.ref(customToStringStyle)) - ; + + //Build body of method + final JExpression toStringStyleExpr; + if (toStringClass == null) { + // Call Static Reference i.e. ToStringStyle.JSON_STYLE; + toStringStyleExpr = codeModel.ref(TOSTRINGSTYLE_CLASSNAME).staticRef(toStringStyle); + } else { + //Direct class passed and calls new on it; + toStringStyleExpr = JExpr._new(codeModel.ref(toStringClass)); + } + // Invoke ToStringBuilder.reflectionToString(Object,StringStyle) toStringMethod.body() - ._return( - codeModel.ref(TOSTRINGBUILDER_CLASSNAME) - .staticInvoke("reflectionToString") - .arg(JExpr._this()) - .arg(toStringStyleExpr) - ); - return; + ._return( + codeModel.ref(TOSTRINGBUILDER_CLASSNAME) + .staticInvoke("reflectionToString") + .arg(JExpr._this()) + .arg(toStringStyleExpr) + ); } private void createEqualsMethod(JDefinedClass implClass) @@ -171,24 +186,31 @@ private void createHashCodeMethod(JDefinedClass implClass) public int parseArgument(Options opt, String[] args, int i) throws BadCommandLineException { - // eg. -Xcommons-lang ToStringStyle=SIMPLE_STYLE + // eg. + // -Xcommons-lang:ToStringStyle=SIMPLE_STYLE + // or + // -Xcommons-lang:ToStringStyle= String arg = args[i].trim(); if (arg.startsWith(TOSTRING_STYLE_PARAM)) { - toStringStyle = arg.substring(TOSTRING_STYLE_PARAM.length()); - try { - Class.forName(TOSTRINGSTYLE_CLASSNAME).getField(toStringStyle); + String value = arg.substring(TOSTRING_STYLE_PARAM.length()); + + if (value.matches("^[A-Z0-9_]+$")) { + try { + Class.forName(TOSTRINGSTYLE_CLASSNAME).getField(value); + } catch (NoSuchFieldException | ClassNotFoundException | SecurityException e) { + logger.error("Could not verify : '" + TOSTRINGSTYLE_CLASSNAME + "." + value + "'. Assume it's not visible to generator.", e); + } + toStringStyle = value; return 1; - } catch (ClassNotFoundException | SecurityException e) { - throw new BadCommandLineException(e.getMessage()); - } catch (NoSuchFieldException ignore) { } try { - customToStringStyle = Class.forName(toStringStyle); + Class.forName(value); } catch (ClassNotFoundException e) { - throw new BadCommandLineException(e.getMessage()); + logger.warn("Could not find class: '" + value + "'. Assume it's not visible to generator."); } + toStringClass = value; return 1; } return 0; diff --git a/jaxb-plugins-parent/jaxb-plugins/src/test/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPluginTest.java b/jaxb-plugins-parent/jaxb-plugins/src/test/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPluginTest.java new file mode 100644 index 000000000..23f303d6d --- /dev/null +++ b/jaxb-plugins-parent/jaxb-plugins/src/test/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPluginTest.java @@ -0,0 +1,164 @@ +/* + * 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.jaxb.plugin.commons_lang; + +import com.sun.codemodel.*; +import com.sun.tools.xjc.BadCommandLineException; +import com.sun.tools.xjc.Options; +import com.sun.tools.xjc.model.CClassInfo; +import com.sun.tools.xjc.outline.ClassOutline; +import com.sun.tools.xjc.outline.Outline; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.xml.sax.ErrorHandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +class XjcCommonsLangPluginTest { + + @Mock + private Outline outline; + @Mock + private Options options; + @Mock + private ErrorHandler errorHandler; + @Mock + private CClassInfo cClassInfo; + + private JDefinedClass implClass; + private XjcCommonsLangPlugin plugin; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + + JCodeModel codeModel = new JCodeModel(); + implClass = codeModel._class("TestGeneratedClass"); + + ClassOutline classOutline = new TestableClassOutline(cClassInfo, implClass); + when(outline.getClasses()).thenReturn((Collection) Collections.singletonList(classOutline)); + + plugin = new XjcCommonsLangPlugin(); + } + + @Test + void testGetOptionName() { + assertEquals("Xcommons-lang", plugin.getOptionName()); + } + + @Test + void testGetUsage() { + String usage = plugin.getUsage(); + assertNotNull(usage); + assertEquals(" -Xcommons-lang : generate toString(), hashCode() and equals() for generated code using Jakarta's common-lang [\n\t" + + " -Xcommons-lang:ToStringStyle=MULTI_LINE_STYLE\n\t" + + "| -Xcommons-lang:ToStringStyle=DEFAULT_STYLE\n\t" + + "| -Xcommons-lang:ToStringStyle=NO_FIELD_NAMES_STYLE\n\t" + + "| -Xcommons-lang:ToStringStyle=SHORT_PREFIX_STYLE\n\t" + + "| -Xcommons-lang:ToStringStyle=SIMPLE_STYLE\n\t" + + "| -Xcommons-lang:ToStringStyle=NO_CLASS_NAME_STYLE\n\t" + + "| -Xcommons-lang:ToStringStyle=JSON_STYLE\n\t" + + "| -Xcommons-lang:ToStringStyle=$\n\t" + + "| -Xcommons-lang:ToStringStyle=\n" + + "]\n" + + " Note: custom ToStringStyle class is needed if you wish to turn off setUseIdentityHashCode on top of MULTI_LINE_STYLE\n", usage); + } + + @Test + void testRunGeneratesAllMethods() throws Exception { + plugin.run(outline, options, errorHandler); + + assertTrue(hasMethod(implClass, "toString"), "toString() should be generated"); + assertTrue(hasMethod(implClass, "equals"), "equals() should be generated"); + assertTrue(hasMethod(implClass, "hashCode"), "hashCode() should be generated"); + } + + @Test + void testParseArgumentToStringStyleStandard() throws BadCommandLineException { + String arg = "-Xcommons-lang:ToStringStyle=SIMPLE_STYLE"; + assertEquals(1, plugin.parseArgument(options, new String[]{arg}, 0)); + + plugin.run(outline, options, errorHandler); + + assertTrue(hasMethod(implClass, "toString"), "toString() should be generated"); + } + + @Test + void testParseArgumentToStringStyleCustom() throws BadCommandLineException { + String arg = "-Xcommons-lang:ToStringStyle=java.lang.String"; + assertEquals(1, plugin.parseArgument(options, new String[]{arg}, 0)); + + plugin.run(outline, options, errorHandler); + + assertTrue(hasMethod(implClass, "toString"), "toString() should be generated"); + } + + @Test + void testParseArgumentUnknown() throws BadCommandLineException { + assertEquals(0, plugin.parseArgument(options, new String[]{"-Xunknown-param"}, 0)); + + plugin.run(outline, options, errorHandler); + + assertTrue(hasMethod(implClass, "toString"), "toString() should be generated"); + assertTrue(hasMethod(implClass, "equals"), "equals() should be generated"); + assertTrue(hasMethod(implClass, "hashCode"), "hashCode() should be generated"); + } + + + @Test + void testCreateToStringMethodWithCustomClassBranch() throws Exception { + String arg = "-Xcommons-lang:ToStringStyle=java.lang.String"; + plugin.parseArgument(options, new String[]{arg}, 0); + + assertTrue(plugin.run(outline, options, errorHandler)); + + assertTrue(hasMethod(implClass, "toString"), "toString() should be generated using custom class branch"); + } + + private boolean hasMethod(JDefinedClass clazz, String methodName) { + for (JMethod m : clazz.methods()) { + if (m.name().equals(methodName)) { + return true; + } + } + return false; + } + + private Optional getMethod(JDefinedClass clazz, String methodName) { + return clazz.methods().stream() + .filter(anno -> anno.name().equals(methodName)) + .findFirst(); + } + + public static class TestableClassOutline extends ClassOutline { + public TestableClassOutline(CClassInfo target, JDefinedClass implClass) { + super(target, null, null, implClass); + } + + @Override + public Outline parent() { + return null; + } + } +} diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_class/pom.xml b/jaxb-plugins-parent/tests/commons_lang_custom_class/pom.xml new file mode 100644 index 000000000..d03d063be --- /dev/null +++ b/jaxb-plugins-parent/tests/commons_lang_custom_class/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + org.jvnet.jaxb + jaxb-plugins-tests + 4.1.0-SNAPSHOT + ../pom.xml + + jaxb-plugins-test-commons_lang_custom_class + jar + JAXB Tools :: JAXB Plugins :: Test [commons_lang_custom_class] + + + org.jvnet.jaxb + jaxb-maven-plugin-testing + test + + + org.apache.commons + commons-lang3 + + + + test + + + org.jvnet.jaxb + jaxb-maven-plugin + + true + + -Xcommons-lang + -Xcommons-lang:ToStringStyle=com.example.CustomToStringStyle + + + + org.jvnet.jaxb + jaxb-plugins + + + + + + + diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_class/src/main/java/com/example/CustomToStringStyle.java b/jaxb-plugins-parent/tests/commons_lang_custom_class/src/main/java/com/example/CustomToStringStyle.java new file mode 100644 index 000000000..cd04dd3b7 --- /dev/null +++ b/jaxb-plugins-parent/tests/commons_lang_custom_class/src/main/java/com/example/CustomToStringStyle.java @@ -0,0 +1,33 @@ +package com.example; + +import org.apache.commons.lang3.builder.ToStringStyle; + +public class CustomToStringStyle extends ToStringStyle { + + public static final ToStringStyle CUSTOM_TO_STRING_STYLE = new CustomToStringStyle(); + + + private static final long serialVersionUID = 1L; + + /** + * Constructs a new instance. + * + *

Use the static constant rather than instantiating.

+ */ + public CustomToStringStyle() { + setUseIdentityHashCode(false); + setContentStart("["); + setFieldSeparator(System.lineSeparator() + " "); + setFieldSeparatorAtStart(true); + setContentEnd(System.lineSeparator() + "]"); + } + + /** + * Ensure Singleton after serialization. + * @return the singleton + */ + private Object readResolve() { + return CUSTOM_TO_STRING_STYLE; + } + +} diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_class/src/main/resources/Person.xsd b/jaxb-plugins-parent/tests/commons_lang_custom_class/src/main/resources/Person.xsd new file mode 100644 index 000000000..1ee143788 --- /dev/null +++ b/jaxb-plugins-parent/tests/commons_lang_custom_class/src/main/resources/Person.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_class/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/AddressTest.java b/jaxb-plugins-parent/tests/commons_lang_custom_class/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/AddressTest.java new file mode 100644 index 000000000..3fa2776af --- /dev/null +++ b/jaxb-plugins-parent/tests/commons_lang_custom_class/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 com.example.CustomToStringStyle; +import org.apache.commons.lang3.builder.ToStringBuilder; + +import generated.Address; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class AddressTest { + + @Test + public void testAddress() { + Address a = new Address(); + // No plugin default-value present, checking everything is null or default java value + Assertions.assertEquals(0, a.getNumber()); + Assertions.assertNull(a.getCareOf()); + Assertions.assertNull(a.getStreet()); + + Assertions.assertEquals(ToStringBuilder.reflectionToString(a, new CustomToStringStyle()), a.toString()); + } +} diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_class/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java b/jaxb-plugins-parent/tests/commons_lang_custom_class/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java new file mode 100644 index 000000000..be06f8bd9 --- /dev/null +++ b/jaxb-plugins-parent/tests/commons_lang_custom_class/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java @@ -0,0 +1,21 @@ +package org.jvnet.jaxb2_commons.tests.commons_lang; + +import com.example.CustomToStringStyle; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import generated.Person; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class PersonTest { + + @Test + public void testPerson() { + Person p = new Person(); + // No plugin default-value present, checking everything is null or default java value + Assertions.assertEquals(false, p.isMailingAddressIdentical()); + + Assertions.assertEquals(ToStringBuilder.reflectionToString(p, new CustomToStringStyle()), p.toString()); + } +} diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_class/src/test/resources/log4j.properties b/jaxb-plugins-parent/tests/commons_lang_custom_class/src/test/resources/log4j.properties new file mode 100644 index 000000000..ca4ee5e2c --- /dev/null +++ b/jaxb-plugins-parent/tests/commons_lang_custom_class/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 diff --git a/jaxb-plugins-parent/tests/pom.xml b/jaxb-plugins-parent/tests/pom.xml index 41b95f7cf..66cc70754 100644 --- a/jaxb-plugins-parent/tests/pom.xml +++ b/jaxb-plugins-parent/tests/pom.xml @@ -19,6 +19,7 @@ camelcase commons_lang commons_lang_custom + commons_lang_custom_class copyable defaultvalue elementwrapper