Thursday, March 19, 2009

Unordered collections

Suppose we have domain classes with fields of type Collection. We also need to implement equals on these classes, which can be easily done using org.apache.commons.lang.builder.EqualsBuilder.
However, the EqualsBuilder is not smart enough to treat our data as an unordered collection (unordered is the lowest common denominator when the implemented interface is a Collection and not a List). This means that we should wrap our collections into UnorderedCollection
when comparing, which can be done like so:

public static <T, E extends T> Collection<T> asCollection(final Collection<E> ts) {
return new UnorderedCollection<T>() {

@Override
public Iterator<T> iterator() {
return new UnmodifiableIterator<T>() {
private Iterator<E> iterator = ts.iterator();

public boolean hasNext() {
return iterator.hasNext();
}

public T next() {
return iterator.next();
}

};
}

@Override
public int size() {
return ts.size();
}

@Override
public String toString() {
return ts.toString();
}
};
}


private static abstract class UnorderedCollection<T> extends AbstractCollection<T> {
/**
* Taken from {@link AbstractSet#equals} implementation.
* <p>
* <b>Important!</b> not associative when compared with ordered
* {@link Collection} (such as {@link List}).
* </p><p>
* E.g.
* <code>CollectionUtils.asCollection().equals(new ArrayList()) == true</code>

* but
* <code>(new ArrayList()).equals(CollectionUtils.asCollection()) != true</code>

*
* @see java.lang.Object#equals(java.lang.Object)
* @see java.util.collection.AbstractSet#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}

if (!(o instanceof Collection)) {
return false;
}
Collection c = (Collection) o;
if (c.size() != size()) {
return false;
}
try {
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
}

The implementation of equals was adopted from AbstractSet as the comments state.

private static abstract class UnmodifiableIterator<T> implements Iterator<T> {
public void remove() {
throw new UnsupportedOperationException(
"Collection was created using CollectionUtils#asCollection which does not support element removal.");
}
}

This way we can wrap any Collection with an UnorderedCollection and enjoy the equality between
Arrays.asList(1, 2, 3)

and
Arrays.asList(3, 2, 1)

No comments: