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..1226195cfca 100644 --- a/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java @@ -195,10 +195,10 @@ private static void unregister(final Object lhs, final Object rhs) { } /** - * If the fields tested are equals. + * If the fields tested are equal * The default value is {@code true}. */ - private boolean isEquals = true; + protected 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 we should interrupt the comparison during an appending. + * + * @return true if the equality is false + */ + protected boolean interruptEqualityCheck() { + 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 (interruptEqualityCheck()) { return this; } if (lhs == rhs) { @@ -583,7 +592,7 @@ private void reflectionAppend( * @since 2.0 */ public EqualsBuilder appendSuper(final boolean superEquals) { - if (!isEquals) { + if (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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 (interruptEqualityCheck()) { 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..cacc0f77317 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/TypedEqualsBuilder.java @@ -0,0 +1,82 @@ +/* + * 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.apache.commons.lang3.function.FailableFunction; + +import java.util.Objects; +import java.util.function.Function; + +/** + * An extension of {@link EqualsBuilder} that is typed and meant to append field getter functions. + * it does the nullity and class equality checks before checking the appended values. + * + *

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 + protected boolean interruptEqualityCheck() { + return sameReference || super.interruptEqualityCheck(); + } + + public TypedEqualsBuilder append(FailableFunction extractor) { + if (interruptEqualityCheck()) { + return this; + } + try { + super.append(extractor.apply(currentInstance), extractor.apply(other)); + } catch (Exception e) { + throw new RuntimeException(e); + } + 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..95328f4e675 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/TypedEqualsBuilderTest.java @@ -0,0 +1,97 @@ +/* + * 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); + + assertTrue( + new TypedEqualsBuilder<>(testObject1, testObject2) + .append(TestObject::getA) + .isEquals()); + assertTrue( + new TypedEqualsBuilder<>(testObject1, testObject3) + .append(TestObject::getA) + .append(TestObject::getB) + .isEquals()); + assertFalse( + new TypedEqualsBuilder<>(testObject1, testObject2) + .append(TestObject::getA) + .append(TestObject::getB) + .isEquals()); + assertFalse( + new TypedEqualsBuilder<>(testObject2, testObject1) + .append(TestObject::getA) + .append(TestObject::getB) + .isEquals()); + assertFalse( + new TypedEqualsBuilder<>(testObject1, null) + .append(TestObject::getA) + .isEquals()); + assertFalse( + new TypedEqualsBuilder<>(testObject1, "") + .append(TestObject::getA) + .isEquals()); + assertThrows(RuntimeException.class, + () -> new TypedEqualsBuilder<>(testObject1, testObject2) + .append(TestObject::getSomethingWithException) + .isEquals()); + } + + private 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; + } + + public int getSomethingWithException() throws Exception { + throw new Exception("something went wrong"); + } + + @Override + public boolean equals(Object obj) { + return new TypedEqualsBuilder<>(this, obj) + .append(TestObject::getA) + .append(TestObject::getB) + .isEquals(); + } + } +} \ No newline at end of file