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..260b14807 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 @@ -32,7 +32,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. @@ -62,18 +62,38 @@ * * The default ToStringStyle adopted by this plugin is MULTI_LINE_STYLE. * + * + * To disable one of the generated plugins if you wish to use another module use one of the following: + * + *
+ *  -Xcommons-lang:addToStringMethod=TRUE|FALSE (default: TRUE)
+ *  -Xcommons-lang:addHashCodeMethod=TRUE|FALSE (default: TRUE)
+ *  -Xcommons-lang:addEqualsMethod=TRUE|FALSE (default: TRUE)
+ * 
+ * * @author Hanson Char + * @author William Dutton (disable methods) */ public class XjcCommonsLangPlugin extends Plugin { private static final String TOSTRING_STYLE_PARAM = "-Xcommons-lang:ToStringStyle="; + private static final String TOSTRING_DISABLED_PARAM = "-Xcommons-lang:addToStringMethod="; + private static final String HASH_CODE_DISABLED_PARAM = "-Xcommons-lang:addHashCodeMethod="; + private static final String EQUALS_DISABLED_PARAM = "-Xcommons-lang:addEqualsMethod="; + + //Classes private static final String TOSTRINGSTYLE_CLASSNAME = "org.apache.commons.lang3.builder.ToStringStyle"; 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"; + private String toStringStyle = "MULTI_LINE_STYLE"; private Class customToStringStyle; + private boolean toStringEnabled = true; + private boolean equalsEnabled = true; + private boolean hashCodeEnabled = true; + @Override public String getOptionName() { @@ -83,13 +103,18 @@ public String getOptionName() @Override public String getUsage() { - return " -Xcommons-lang : generate toString(), hashCode() and equals() for generated code using Jakarta's common-lang\n" + return " -Xcommons-lang : generate toString(), hashCode() and equals() for generated code using Jakarta's common-lang " + " [-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" + + "\n" + + " To disable one of the generated plugins if you wish to use another module use one of the following:\n" + + " -Xcommons-lang:addToStringMethod=FALSE\n" + + " -Xcommons-lang:addHashCodeMethod=FALSE\n" + + " -Xcommons-lang:addEqualsMethod=FALSE\n" ; } @@ -110,6 +135,9 @@ public boolean run(Outline outline, private void createToStringMethod(JDefinedClass implClass) { + if (!toStringEnabled) { + return; + } JCodeModel codeModel = implClass.owner(); JMethod toStringMethod = implClass.method(JMod.PUBLIC, codeModel.ref(String.class), "toString"); @@ -135,6 +163,9 @@ private void createToStringMethod(JDefinedClass implClass) private void createEqualsMethod(JDefinedClass implClass) { + if (!equalsEnabled) { + return; + } JCodeModel codeModel = implClass.owner(); JMethod toStringMethod = implClass.method(JMod.PUBLIC, codeModel.BOOLEAN, "equals"); @@ -153,6 +184,9 @@ private void createEqualsMethod(JDefinedClass implClass) private void createHashCodeMethod(JDefinedClass implClass) { + if (!hashCodeEnabled) { + return; + } JCodeModel codeModel = implClass.owner(); JMethod toStringMethod = implClass.method(JMod.PUBLIC, codeModel.INT, "hashCode"); @@ -191,6 +225,30 @@ public int parseArgument(Options opt, String[] args, int i) } return 1; } + + // eg. -Xcommons-lang:addToStringMethod=TRUE + if (arg.startsWith(TOSTRING_DISABLED_PARAM)) + { + String toStringBoolean = arg.substring(TOSTRING_DISABLED_PARAM.length()); + toStringEnabled = Boolean.parseBoolean(toStringBoolean); + return 1; + } + + // eg. -Xcommons-lang:addEqualsMethod=TRUE + if (arg.startsWith(EQUALS_DISABLED_PARAM)) + { + String toStringBoolean = arg.substring(EQUALS_DISABLED_PARAM.length()); + equalsEnabled = Boolean.parseBoolean(toStringBoolean); + return 1; + } + + // eg. -Xcommons-lang:addHashCodeMethod=TRUE + if (arg.startsWith(HASH_CODE_DISABLED_PARAM)) + { + String toStringBoolean = arg.substring(HASH_CODE_DISABLED_PARAM.length()); + hashCodeEnabled = Boolean.parseBoolean(toStringBoolean); + 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..dc4dd3961 --- /dev/null +++ b/jaxb-plugins-parent/jaxb-plugins/src/test/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPluginTest.java @@ -0,0 +1,281 @@ +/* + * 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 " + + " [-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" + + "\n" + + " To disable one of the generated plugins if you wish to use another module use one of the following:\n" + + " -Xcommons-lang:addToStringMethod=FALSE\n" + + " -Xcommons-lang:addHashCodeMethod=FALSE\n" + + " -Xcommons-lang:addEqualsMethod=FALSE\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 testRunSkipsMethodsWhenDisabled() throws Exception { + // Set flags to disable generation + plugin.parseArgument(options, new String[]{"-Xcommons-lang:addToStringMethod=FALSE"}, 0); + plugin.parseArgument(options, new String[]{"-Xcommons-lang:addEqualsMethod=FALSE"}, 0); + plugin.parseArgument(options, new String[]{"-Xcommons-lang:addHashCodeMethod=FALSE"}, 0); + + plugin.run(outline, options, errorHandler); + + // Verify that the methods were NOT added to the real object + assertFalse(hasMethod(implClass, "toString"), "toString() should not be generated"); + assertFalse(hasMethod(implClass, "equals"), "equals() should not be generated"); + assertFalse(hasMethod(implClass, "hashCode"), "hashCode() should not be generated"); + } + + + @Test + void testOnlyToStringGenerated() throws Exception { + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addToStringMethod=TRUE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addEqualsMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addHashCodeMethod=FALSE"}, 0)); + + plugin.run(outline, options, errorHandler); + + assertTrue(hasMethod(implClass, "toString"), "toString() should be generated"); + assertFalse(hasMethod(implClass, "equals"), "equals() should be generated"); + assertFalse(hasMethod(implClass, "hashCode"), "hashCode() should be generated"); + } + + @Test + void testOnlyEqualsGenerated() throws Exception { + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addToStringMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addEqualsMethod=TRUE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addHashCodeMethod=FALSE"}, 0)); + + plugin.run(outline, options, errorHandler); + + assertFalse(hasMethod(implClass, "toString"), "toString() should be generated"); + assertTrue(hasMethod(implClass, "equals"), "equals() should be generated"); + JMethod method = getMethod(implClass, "equals").get(); + assertEquals(1, method.listParams().length); + assertEquals("that", Arrays.stream(method.listParams()).findFirst().get().name()); + assertEquals("java.lang.Object", Arrays.stream(method.listParams()).findFirst().get().type().fullName()); + assertFalse(hasMethod(implClass, "hashCode"), "hashCode() should be generated"); + } + + @Test + void testOnlyEqualsGeneratedWithTestTransients() throws Exception { + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addToStringMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addEqualsMethod=TRUE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addHashCodeMethod=FALSE"}, 0)); + + plugin.run(outline, options, errorHandler); + + assertFalse(hasMethod(implClass, "toString"), "toString() should be generated"); + assertTrue(hasMethod(implClass, "equals"), "equals() should be generated"); + assertFalse(hasMethod(implClass, "hashCode"), "hashCode() should be generated"); + } + + @Test + void testOnlyEqualsGeneratedWithRecursive() throws Exception { + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addToStringMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addEqualsMethod=TRUE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addHashCodeMethod=FALSE"}, 0)); + + plugin.run(outline, options, errorHandler); + + assertFalse(hasMethod(implClass, "toString"), "toString() should be generated"); + assertTrue(hasMethod(implClass, "equals"), "equals() should be generated"); + assertFalse(hasMethod(implClass, "hashCode"), "hashCode() should be generated"); + } + + @Test + void testOnlyEqualsGeneratedWithTransiantAndRecursive() throws Exception { + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addToStringMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addEqualsMethod=TRUE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addHashCodeMethod=FALSE"}, 0)); + + plugin.run(outline, options, errorHandler); + + assertFalse(hasMethod(implClass, "toString"), "toString() should be generated"); + assertTrue(hasMethod(implClass, "equals"), "equals() should be generated"); + JMethod method = getMethod(implClass, "equals").get(); + assertEquals(1, method.listParams().length); + assertEquals("that", Arrays.stream(method.listParams()).findFirst().get().name()); + assertEquals("java.lang.Object", Arrays.stream(method.listParams()).findFirst().get().type().fullName()); + //Unsure how to verify args inside since post jdk8 its now in a class that does hides its classes externally. + assertFalse(hasMethod(implClass, "hashCode"), "hashCode() should be generated"); + } + + @Test + void testOnlyHashCodeGenerated() throws Exception { + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addToStringMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addEqualsMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addHashCodeMethod=TRUE"}, 0)); + + plugin.run(outline, options, errorHandler); + + assertFalse(hasMethod(implClass, "toString"), "toString() should not be generated"); + assertFalse(hasMethod(implClass, "equals"), "equals() should not 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 testParseArgumentDisablingFlags() throws BadCommandLineException { + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addToStringMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addEqualsMethod=FALSE"}, 0)); + assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:addHashCodeMethod=FALSE"}, 0)); + + plugin.run(outline, options, errorHandler); + + assertFalse(hasMethod(implClass, "toString"), "toString() should not be generated"); + assertFalse(hasMethod(implClass, "equals"), "equals() should not be generated"); + assertFalse(hasMethod(implClass, "hashCode"), "hashCode() should not 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; + } + } +}