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..02023a3a9 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
@@ -30,6 +30,9 @@
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.Outline;
+import static com.sun.codemodel.JExpr._null;
+import static com.sun.codemodel.JExpr.lit;
+
/**
* Automatically generates the toString(), hashCode() and equals() methods
* using Jakarta's commons-lang.
@@ -62,11 +65,26 @@
*
* The default ToStringStyle adopted by this plugin is MULTI_LINE_STYLE.
*
+ * Equals options:
+ *
+ * -Xcommons-lang:equalsTestTransients=TRUE|FALSE (default: FALSE)
+ * -Xcommons-lang:equalsTestRecursive=TRUE|FALSE (default: FALSE)
+ *
+ * NOTE:
+ * When enabled equals passing when sub objects where last object POJO returns by ref check only but contents is equals
+ *
+ * Performance Cost: Deep reflection is significantly slower than standard equality checks.
+ *
+ *
* @author Hanson Char
*/
public class XjcCommonsLangPlugin extends Plugin
{
private static final String TOSTRING_STYLE_PARAM = "-Xcommons-lang:ToStringStyle=";
+ private static final String EQUALS_TEST_TRANSIENTS_PARAM = "-Xcommons-lang:equalsTestTransients=";
+ private static final String EQUALS_TEST_RECURSIVE_PARAM = "-Xcommons-lang:equalsTestRecursive=";
+
+ //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";
@@ -74,6 +92,9 @@ public class XjcCommonsLangPlugin extends Plugin
private String toStringStyle = "MULTI_LINE_STYLE";
private Class> customToStringStyle;
+ private boolean equalsTestTransients = false;
+ private boolean equalsTestRecursive = false;
+
@Override
public String getOptionName()
{
@@ -90,6 +111,13 @@ public String getUsage()
+ "| SHORT_PREFIX_STYLE\n\t"
+ "| SIMPLE_STYLE\n\t"
+ "| ]\n"
+ + "\n"
+ + " Equals options:\n"
+ + " -Xcommons-lang:equalsTestTransients=TRUE|FALSE (default: FALSE)\n"
+ + " -Xcommons-lang:equalsTestRecursive=TRUE|FALSE (default: FALSE)\n"
+ + " NOTE: \n"
+ + " Performance Cost: When equalsTestRecursive is TRUE, due to Deep reflection is significantly slower than standard equality checks.\n"
+ + "\n"
;
}
@@ -141,14 +169,27 @@ private void createEqualsMethod(JDefinedClass implClass)
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_CLASSNAME)
- .staticInvoke("reflectionEquals")
- .arg(JExpr._this())
- .arg(that)
- );
- return;
+ if (equalsTestTransients || equalsTestRecursive) {
+ //Since: 3.6
+ toStringMethod.body()._return(
+ codeModel.ref(EQUALSBUILDER_CLASSNAME)
+ .staticInvoke("reflectionEquals") // public static boolean reflectionEquals(
+ .arg(JExpr._this()) //final Object lhs,
+ .arg(that) // final Object rhs,
+ .arg(lit(equalsTestTransients)) // final boolean testTransients,
+ .arg(_null()) // final Class> reflectUpToClass,
+ .arg(lit(equalsTestRecursive)) // final boolean testRecursive,
+ .arg(_null()) // final String... excludeFields) {
+ );
+ } else {
+ // Invoke EqualsBuilder.reflectionEquals(Object,Object);
+ toStringMethod.body()._return(
+ codeModel.ref(EQUALSBUILDER_CLASSNAME)
+ .staticInvoke("reflectionEquals")
+ .arg(JExpr._this())
+ .arg(that)
+ );
+ }
}
private void createHashCodeMethod(JDefinedClass implClass)
@@ -191,6 +232,22 @@ public int parseArgument(Options opt, String[] args, int i)
}
return 1;
}
+
+ // eg. -Xcommons-lang:equalsTestTransients=TRUE
+ if (arg.startsWith(EQUALS_TEST_TRANSIENTS_PARAM))
+ {
+ String toStringBoolean = arg.substring(EQUALS_TEST_TRANSIENTS_PARAM.length());
+ equalsTestTransients = Boolean.parseBoolean(toStringBoolean);
+ return 1;
+ }
+
+ // eg. -Xcommons-lang:equalsTestRecursive=TRUE
+ if (arg.startsWith(EQUALS_TEST_RECURSIVE_PARAM))
+ {
+ String toStringBoolean = arg.substring(EQUALS_TEST_RECURSIVE_PARAM.length());
+ equalsTestRecursive = 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..4b6fb3a71
--- /dev/null
+++ b/jaxb-plugins-parent/jaxb-plugins/src/test/java/org/jvnet/jaxb/plugin/commons_lang/XjcCommonsLangPluginTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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" +
+ " [-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" +
+ " Equals options:\n" +
+ " -Xcommons-lang:equalsTestTransients=TRUE|FALSE (default: FALSE)\n" +
+ " -Xcommons-lang:equalsTestRecursive=TRUE|FALSE (default: FALSE)\n" +
+ " NOTE: \n" +
+ " Performance Cost: When equalsTestRecursive is TRUE, due to Deep reflection is significantly slower than standard equality checks.\n" +
+ "\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 testOnlyEqualsGeneratedWithTestTransients() throws Exception {
+ assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:equalsTestTransients=TRUE"}, 0));
+
+ plugin.run(outline, options, errorHandler);
+
+
+ assertTrue(hasMethod(implClass, "equals"), "equals() should be generated");
+
+ }
+
+ @Test
+ void testOnlyEqualsGeneratedWithRecursive() throws Exception {
+ assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:equalsTestRecursive=TRUE"}, 0));
+
+ plugin.run(outline, options, errorHandler);
+
+ assertTrue(hasMethod(implClass, "equals"), "equals() should be generated");
+
+ }
+
+ @Test
+ void testOnlyEqualsGeneratedWithTransiantAndRecursive() throws Exception {
+ assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:equalsTestTransients=TRUE"}, 0));
+ assertEquals(1, plugin.parseArgument(options, new String[]{"-Xcommons-lang:equalsTestRecursive=TRUE"}, 0));
+
+ plugin.run(outline, options, errorHandler);
+
+ 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.
+ }
+
+ @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_equals/pom.xml b/jaxb-plugins-parent/tests/commons_lang_custom_equals/pom.xml
new file mode 100644
index 000000000..9a8be6649
--- /dev/null
+++ b/jaxb-plugins-parent/tests/commons_lang_custom_equals/pom.xml
@@ -0,0 +1,48 @@
+
+ 4.0.0
+
+ org.jvnet.jaxb
+ jaxb-plugins-tests
+ 4.1.0-SNAPSHOT
+ ../pom.xml
+
+ jaxb-plugins-test-commons_lang_custom_equals
+ jar
+ JAXB Tools :: JAXB Plugins :: Test [commons_lang_custom_equals]
+
+
+ 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:equalsTestTransients=FALSE
+ -Xcommons-lang:equalsTestRecursive=TRUE
+
+
+
+ org.jvnet.jaxb
+ jaxb-plugins
+
+
+
+
+
+
+
diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/main/java/com/example/CustomToStringStyle.java b/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/main/java/com/example/CustomToStringStyle.java
new file mode 100644
index 000000000..cd04dd3b7
--- /dev/null
+++ b/jaxb-plugins-parent/tests/commons_lang_custom_equals/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_equals/src/main/resources/Person.xsd b/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/main/resources/Person.xsd
new file mode 100644
index 000000000..1ee143788
--- /dev/null
+++ b/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/main/resources/Person.xsd
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/AddressTest.java b/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/AddressTest.java
new file mode 100644
index 000000000..489d4f6f6
--- /dev/null
+++ b/jaxb-plugins-parent/tests/commons_lang_custom_equals/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 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(a, a);
+ }
+}
diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java b/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/test/java/org/jvnet/jaxb2_commons/tests/commons_lang/PersonTest.java
new file mode 100644
index 000000000..f0a4bf952
--- /dev/null
+++ b/jaxb-plugins-parent/tests/commons_lang_custom_equals/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 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();
+ Person p2 = new Person();
+ // No plugin default-value present, checking everything is null or default java value
+ Assertions.assertEquals(false, p.isMailingAddressIdentical());
+ Assertions.assertEquals(p, p2);
+
+ }
+}
diff --git a/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/test/resources/log4j.properties b/jaxb-plugins-parent/tests/commons_lang_custom_equals/src/test/resources/log4j.properties
new file mode 100644
index 000000000..ca4ee5e2c
--- /dev/null
+++ b/jaxb-plugins-parent/tests/commons_lang_custom_equals/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..43af9ff9a 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_equals
copyable
defaultvalue
elementwrapper