diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c86190789..28ad20f22 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,9 @@ + + "AbstractCollectionDecorator" doesn't forward equals and hashCode anymore. + Documented runtime complexity of "CollectionUtils#retainAll(Collection, Collection). diff --git a/src/main/java/org/apache/commons/collections4/bag/AbstractBagDecorator.java b/src/main/java/org/apache/commons/collections4/bag/AbstractBagDecorator.java index b01d44ee6..819632ec5 100644 --- a/src/main/java/org/apache/commons/collections4/bag/AbstractBagDecorator.java +++ b/src/main/java/org/apache/commons/collections4/bag/AbstractBagDecorator.java @@ -63,6 +63,16 @@ public abstract class AbstractBagDecorator return (Bag) super.decorated(); } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + //----------------------------------------------------------------------- public int getCount(final Object object) { diff --git a/src/main/java/org/apache/commons/collections4/bag/PredicatedBag.java b/src/main/java/org/apache/commons/collections4/bag/PredicatedBag.java index 44796d73c..f3a14d481 100644 --- a/src/main/java/org/apache/commons/collections4/bag/PredicatedBag.java +++ b/src/main/java/org/apache/commons/collections4/bag/PredicatedBag.java @@ -87,6 +87,16 @@ public class PredicatedBag extends PredicatedCollection implements Bag return (Bag) super.decorated(); } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + //----------------------------------------------------------------------- public boolean add(final E object, final int count) { diff --git a/src/main/java/org/apache/commons/collections4/bag/SynchronizedBag.java b/src/main/java/org/apache/commons/collections4/bag/SynchronizedBag.java index 6c3e0bc75..00b0a1612 100644 --- a/src/main/java/org/apache/commons/collections4/bag/SynchronizedBag.java +++ b/src/main/java/org/apache/commons/collections4/bag/SynchronizedBag.java @@ -82,6 +82,23 @@ public class SynchronizedBag extends SynchronizedCollection implements Bag return (Bag) decorated(); } + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + synchronized (lock) { + return getBag().equals(object); + } + } + + @Override + public int hashCode() { + synchronized (lock) { + return getBag().hashCode(); + } + } + //----------------------------------------------------------------------- public boolean add(final E object, final int count) { diff --git a/src/main/java/org/apache/commons/collections4/bag/TransformedBag.java b/src/main/java/org/apache/commons/collections4/bag/TransformedBag.java index 52a01cffa..21d01c785 100644 --- a/src/main/java/org/apache/commons/collections4/bag/TransformedBag.java +++ b/src/main/java/org/apache/commons/collections4/bag/TransformedBag.java @@ -18,10 +18,10 @@ package org.apache.commons.collections4.bag; import java.util.Set; -import org.apache.commons.collections4.set.TransformedSet; import org.apache.commons.collections4.Bag; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.collection.TransformedCollection; +import org.apache.commons.collections4.set.TransformedSet; /** * Decorates another {@link Bag} to transform objects that are added. @@ -110,6 +110,16 @@ public class TransformedBag extends TransformedCollection implements Bag) decorated(); } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + //----------------------------------------------------------------------- public int getCount(final Object object) { diff --git a/src/main/java/org/apache/commons/collections4/bidimap/AbstractDualBidiMap.java b/src/main/java/org/apache/commons/collections4/bidimap/AbstractDualBidiMap.java index 3a8c3fbac..4bffdb136 100644 --- a/src/main/java/org/apache/commons/collections4/bidimap/AbstractDualBidiMap.java +++ b/src/main/java/org/apache/commons/collections4/bidimap/AbstractDualBidiMap.java @@ -342,6 +342,16 @@ public abstract class AbstractDualBidiMap implements BidiMap { this.parent = parent; } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + @Override public boolean removeAll(final Collection coll) { if (parent.isEmpty() || coll.isEmpty()) { diff --git a/src/main/java/org/apache/commons/collections4/collection/AbstractCollectionDecorator.java b/src/main/java/org/apache/commons/collections4/collection/AbstractCollectionDecorator.java index de1d484c2..961f4e464 100644 --- a/src/main/java/org/apache/commons/collections4/collection/AbstractCollectionDecorator.java +++ b/src/main/java/org/apache/commons/collections4/collection/AbstractCollectionDecorator.java @@ -34,6 +34,19 @@ import java.util.Iterator; * {@link #iterator()}. Instead it simply returns the value from the * wrapped collection. This may be undesirable, for example if you are trying * to write an unmodifiable implementation it might provide a loophole. + *

+ * This implementation does not forward the hashCode and equals methods through + * to the backing object, but relies on Object's implementation. This is necessary + * to preserve the symmetry of equals. Custom definitions of equality are usually + * based on an interface, such as Set or List, so that the implementation of equals + * can cast the object being tested for equality to the custom interface. + * AbstractCollectionDecorator does not implement such custom interfaces directly; + * they are implemented only in subclasses. Therefore, forwarding equals would break + * symmetry, as the forwarding object might consider itself equal to the object being + * tested, but the reverse could not be true. This behavior is consistent with the + * JDK's collection wrappers, such as {@link java.util.Collections#unmodifiableCollection(Collection)}. + * Use an interface-specific subclass of AbstractCollectionDecorator, such as + * AbstractListDecorator, to preserve equality behavior, or override equals directly. * * @param the type of the elements in the collection * @since 3.0 @@ -144,16 +157,6 @@ public abstract class AbstractCollectionDecorator return decorated().retainAll(coll); } - @Override - public boolean equals(final Object object) { - return object == this || decorated().equals(object); - } - - @Override - public int hashCode() { - return decorated().hashCode(); - } - @Override public String toString() { return decorated().toString(); diff --git a/src/main/java/org/apache/commons/collections4/list/AbstractListDecorator.java b/src/main/java/org/apache/commons/collections4/list/AbstractListDecorator.java index d7a5bc463..67b892878 100644 --- a/src/main/java/org/apache/commons/collections4/list/AbstractListDecorator.java +++ b/src/main/java/org/apache/commons/collections4/list/AbstractListDecorator.java @@ -65,6 +65,16 @@ public abstract class AbstractListDecorator extends AbstractCollectionDecorat return (List) super.decorated(); } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + //----------------------------------------------------------------------- public void add(final int index, final E object) { diff --git a/src/main/java/org/apache/commons/collections4/list/PredicatedList.java b/src/main/java/org/apache/commons/collections4/list/PredicatedList.java index 4dc9bdb18..5d51b6001 100644 --- a/src/main/java/org/apache/commons/collections4/list/PredicatedList.java +++ b/src/main/java/org/apache/commons/collections4/list/PredicatedList.java @@ -94,6 +94,16 @@ public class PredicatedList extends PredicatedCollection implements List) super.decorated(); } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + //----------------------------------------------------------------------- public E get(final int index) { diff --git a/src/main/java/org/apache/commons/collections4/list/TransformedList.java b/src/main/java/org/apache/commons/collections4/list/TransformedList.java index adf19d8f7..6a0ce29a2 100644 --- a/src/main/java/org/apache/commons/collections4/list/TransformedList.java +++ b/src/main/java/org/apache/commons/collections4/list/TransformedList.java @@ -114,6 +114,16 @@ public class TransformedList extends TransformedCollection implements List return (List) decorated(); } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + //----------------------------------------------------------------------- public E get(final int index) { diff --git a/src/main/java/org/apache/commons/collections4/queue/AbstractQueueDecorator.java b/src/main/java/org/apache/commons/collections4/queue/AbstractQueueDecorator.java index 440fa3292..eabee4504 100644 --- a/src/main/java/org/apache/commons/collections4/queue/AbstractQueueDecorator.java +++ b/src/main/java/org/apache/commons/collections4/queue/AbstractQueueDecorator.java @@ -24,6 +24,12 @@ import org.apache.commons.collections4.collection.AbstractCollectionDecorator; * Decorates another {@link Queue} to provide additional behaviour. *

* Methods are forwarded directly to the decorated queue. + *

+ * This implementation does not forward the hashCode and equals methods through + * to the backing object, but relies on Object's implementation. This is + * necessary as some Queue implementations, e.g. LinkedList, have custom a + * equals implementation for which symmetry can not be preserved. + * See class javadoc of AbstractCollectionDecorator for more information. * * @param the type of the elements in the queue * @since 4.0 diff --git a/src/main/java/org/apache/commons/collections4/set/AbstractSetDecorator.java b/src/main/java/org/apache/commons/collections4/set/AbstractSetDecorator.java index c664043f1..1be4a1232 100644 --- a/src/main/java/org/apache/commons/collections4/set/AbstractSetDecorator.java +++ b/src/main/java/org/apache/commons/collections4/set/AbstractSetDecorator.java @@ -63,4 +63,14 @@ public abstract class AbstractSetDecorator extends AbstractCollectionDecorato return (Set) super.decorated(); } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + } diff --git a/src/main/java/org/apache/commons/collections4/set/PredicatedSet.java b/src/main/java/org/apache/commons/collections4/set/PredicatedSet.java index af048f9e6..472a13176 100644 --- a/src/main/java/org/apache/commons/collections4/set/PredicatedSet.java +++ b/src/main/java/org/apache/commons/collections4/set/PredicatedSet.java @@ -86,4 +86,14 @@ public class PredicatedSet extends PredicatedCollection implements Set return (Set) super.decorated(); } + @Override + public boolean equals(final Object object) { + return object == this || decorated().equals(object); + } + + @Override + public int hashCode() { + return decorated().hashCode(); + } + } diff --git a/src/main/java/org/apache/commons/collections4/set/TransformedSet.java b/src/main/java/org/apache/commons/collections4/set/TransformedSet.java index cf5db63a7..234fcb296 100644 --- a/src/main/java/org/apache/commons/collections4/set/TransformedSet.java +++ b/src/main/java/org/apache/commons/collections4/set/TransformedSet.java @@ -101,4 +101,14 @@ public class TransformedSet extends TransformedCollection implements Set extends AbstractObjectTes (MultiValuedMap) readExternalFormFromDisk(getCanonicalFullCollectionName(map)); assertEquals("Map is the right size", map.size(), map2.size()); for (final Object key : map.keySet()) { - assertEquals("Map had inequal elements", map.get(key), map2.get(key)); + assertTrue("Map had inequal elements", CollectionUtils.isEqualCollection(map.get(key), map2.get(key))); if (isRemoveSupported()) { map2.remove(key); } @@ -1071,10 +1072,12 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes return AbstractMultiValuedMapTest.this.makeObject().asMap(); } + @Override public Map> makeFullMap() { return AbstractMultiValuedMapTest.this.makeFullMap().asMap(); } + @Override @SuppressWarnings("unchecked") public K[] getSampleKeys() { K[] samplekeys = AbstractMultiValuedMapTest.this.getSampleKeys(); @@ -1085,6 +1088,7 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes return (K[]) finalKeys; } + @Override @SuppressWarnings("unchecked") public Collection[] getSampleValues() { V[] sampleValues = AbstractMultiValuedMapTest.this.getSampleValues(); @@ -1095,6 +1099,7 @@ public abstract class AbstractMultiValuedMapTest extends AbstractObjectTes return colArr; } + @Override @SuppressWarnings("unchecked") public Collection[] getNewSampleValues() { Object[] sampleValues = { "ein", "ek", "zwei", "duey", "drei", "teen" };