diff --git a/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java b/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java index b34e9d39056..f4ec11332c6 100644 --- a/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java @@ -198,7 +198,7 @@ private static void unregister(final Object lhs, final Object rhs) { * If the fields tested are equals. * The default value is {@code true}. */ - private boolean isEquals = true; + boolean isEquals = true; private boolean testTransients; private boolean testRecursive; @@ -450,6 +450,15 @@ public static boolean reflectionEquals(final Object lhs, final Object rhs, final .isEquals(); } + /** + * Indicates if an early append method leave should be done + * + * @return true if the equality is false + */ + boolean shouldLeaveEarly() { + return !isEquals; + } + /** * Tests if two {@code objects} by using reflection. * @@ -478,7 +487,7 @@ public static boolean reflectionEquals(final Object lhs, final Object rhs, final * @return this */ public EqualsBuilder reflectionAppend(final Object lhs, final Object rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -583,7 +592,7 @@ private void reflectionAppend( * @since 2.0 */ public EqualsBuilder appendSuper(final boolean superEquals) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } isEquals = superEquals; @@ -602,7 +611,7 @@ public EqualsBuilder appendSuper(final boolean superEquals) { * @return this */ public EqualsBuilder append(final Object lhs, final Object rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -670,7 +679,7 @@ private void appendArray(final Object lhs, final Object rhs) { * @return this */ public EqualsBuilder append(final long lhs, final long rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } isEquals = lhs == rhs; @@ -685,7 +694,7 @@ public EqualsBuilder append(final long lhs, final long rhs) { * @return this */ public EqualsBuilder append(final int lhs, final int rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } isEquals = lhs == rhs; @@ -700,7 +709,7 @@ public EqualsBuilder append(final int lhs, final int rhs) { * @return this */ public EqualsBuilder append(final short lhs, final short rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } isEquals = lhs == rhs; @@ -715,7 +724,7 @@ public EqualsBuilder append(final short lhs, final short rhs) { * @return this */ public EqualsBuilder append(final char lhs, final char rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } isEquals = lhs == rhs; @@ -730,7 +739,7 @@ public EqualsBuilder append(final char lhs, final char rhs) { * @return this */ public EqualsBuilder append(final byte lhs, final byte rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } isEquals = lhs == rhs; @@ -751,7 +760,7 @@ public EqualsBuilder append(final byte lhs, final byte rhs) { * @return this */ public EqualsBuilder append(final double lhs, final double rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); @@ -771,7 +780,7 @@ public EqualsBuilder append(final double lhs, final double rhs) { * @return this */ public EqualsBuilder append(final float lhs, final float rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); @@ -785,7 +794,7 @@ public EqualsBuilder append(final float lhs, final float rhs) { * @return this */ public EqualsBuilder append(final boolean lhs, final boolean rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } isEquals = lhs == rhs; @@ -806,7 +815,7 @@ public EqualsBuilder append(final boolean lhs, final boolean rhs) { * @return this */ public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -837,7 +846,7 @@ public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { * @return this */ public EqualsBuilder append(final long[] lhs, final long[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -868,7 +877,7 @@ public EqualsBuilder append(final long[] lhs, final long[] rhs) { * @return this */ public EqualsBuilder append(final int[] lhs, final int[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -899,7 +908,7 @@ public EqualsBuilder append(final int[] lhs, final int[] rhs) { * @return this */ public EqualsBuilder append(final short[] lhs, final short[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -930,7 +939,7 @@ public EqualsBuilder append(final short[] lhs, final short[] rhs) { * @return this */ public EqualsBuilder append(final char[] lhs, final char[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -961,7 +970,7 @@ public EqualsBuilder append(final char[] lhs, final char[] rhs) { * @return this */ public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -992,7 +1001,7 @@ public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { * @return this */ public EqualsBuilder append(final double[] lhs, final double[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -1023,7 +1032,7 @@ public EqualsBuilder append(final double[] lhs, final double[] rhs) { * @return this */ public EqualsBuilder append(final float[] lhs, final float[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { @@ -1054,7 +1063,7 @@ public EqualsBuilder append(final float[] lhs, final float[] rhs) { * @return this */ public EqualsBuilder append(final boolean[] lhs, final boolean[] rhs) { - if (!isEquals) { + if (shouldLeaveEarly()) { return this; } if (lhs == rhs) { diff --git a/src/main/java/org/apache/commons/lang3/builder/TypedEqualsBuilder.java b/src/main/java/org/apache/commons/lang3/builder/TypedEqualsBuilder.java new file mode 100644 index 00000000000..f854abd32bd --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/TypedEqualsBuilder.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.lang3.builder; + +import java.util.Objects; +import java.util.function.Function; + +/** + * An extension of {@link EqualsBuilder} aimed to perform additionally the base objects and class equals checks. + * + *

It then offers the possibility to append field extractors.

+ * + *

Typical use for the code is as follows:

+ *
+ * public boolean equals(Object obj) {
+ *     return new TypedEqualsBuilder<>(this)
+ *         .appendBaseObject(obj)
+ *         .append(TestObject::getA)
+ *         .append(TestObject::getB)
+ *         .isEquals();
+ *     }
+ * 
+ * + * @param the type of the compared object. + * + * @since 3.14.0 + */ +public class TypedEqualsBuilder extends EqualsBuilder { + + private final T currentInstance; + + private boolean sameReference = false; + private T other; + + @SuppressWarnings("unchecked") + public TypedEqualsBuilder(T currentInstance, Object other) { + Objects.requireNonNull(currentInstance); + this.currentInstance = currentInstance; + if (currentInstance == other) { + sameReference = true; + return; + } + Class currentInstanceClass = (Class) currentInstance.getClass(); + if (other == null || currentInstanceClass != other.getClass()) { + isEquals = false; + return; + } + this.other = (T) other; + } + + @Override + boolean shouldLeaveEarly() { + return sameReference || super.shouldLeaveEarly(); + } + + public TypedEqualsBuilder append(Function extractor) { + if (shouldLeaveEarly()) { + return this; + } + super.append(extractor.apply(currentInstance), extractor.apply(other)); + return this; + } +} diff --git a/src/test/java/org/apache/commons/lang3/builder/TypedEqualsBuilderTest.java b/src/test/java/org/apache/commons/lang3/builder/TypedEqualsBuilderTest.java new file mode 100644 index 00000000000..796a35e172c --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/TypedEqualsBuilderTest.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.lang3.builder; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TypedEqualsBuilderTest { + + @Test + void test_complete_equals() { + TestObject testObject1 = new TestObject(1, 2); + TestObject testObject2 = new TestObject(1, 3); + TestObject testObject3 = new TestObject(1, 2); + + assertEquals(testObject1, testObject1); + assertNotEquals(testObject1, testObject2); + assertNotEquals(testObject2, testObject1); + + assertEquals(Boolean.TRUE, + new TypedEqualsBuilder<>(testObject1, testObject2) + .append(TestObject::getA) + .isEquals()); + assertEquals(Boolean.TRUE, + new TypedEqualsBuilder<>(testObject1, testObject3) + .append(TestObject::getA) + .append(TestObject::getB) + .isEquals()); + assertEquals(Boolean.FALSE, + new TypedEqualsBuilder<>(testObject1, testObject2) + .append(TestObject::getA) + .append(TestObject::getB) + .isEquals()); + assertEquals(Boolean.FALSE, + new TypedEqualsBuilder<>(testObject2, testObject1) + .append(TestObject::getA) + .append(TestObject::getB) + .isEquals()); + assertEquals(Boolean.FALSE, + new TypedEqualsBuilder<>(testObject1, null) + .append(TestObject::getA) + .isEquals()); + assertEquals(Boolean.FALSE, + new TypedEqualsBuilder<>(testObject1, "") + .append(TestObject::getA) + .isEquals()); + } + + static class TestObject { + int a; + int b; + + public TestObject(int a, int b) { + this.a = a; + this.b = b; + } + + public int getA() { + return a; + } + + public int getB() { + return b; + } + + @Override + public boolean equals(Object obj) { + return new TypedEqualsBuilder<>(this, obj) + .append(TestObject::getA) + .append(TestObject::getB) + .isEquals(); + } + } +} \ No newline at end of file