package com.laytonsmith.PureUtilities;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* A LinkedComparatorSet works like a {@link LinkedHashSet}, but the comparison can
* be given with a custom comparator, instead of forcing use of the hashCode or equals
* method. In a normal LinkedHashSet, there is no way to provide a custom comparator,
* so if you wanted a case insensitive LinkedHashSet, you couldn't do this because there
* is no way to override the comparison to check if "A" were equal to "a". You could do
* this with a TreeSet, by providing a custom comparator, but if ordering is also important,
* then you can't use it either. This provides the best of both worlds by providing insertion
* order guarantees, while still allowing you to override the comparison mechanism.
*/
public class LinkedComparatorSet<T> extends AbstractSet<T> implements Set<T> {
final EqualsComparator comparator;
final List<T> list = new ArrayList<T>();
/**
* Creates an empty {@link LinkedComparatorSet} with the given comparator.
* @param comparator
*/
public LinkedComparatorSet(EqualsComparator comparator) {
this(null, comparator);
}
/**
* Creates a new LinkedComparatorSet, based on the given collection. The
* comparator, if not null is used to do the comparison of equality. This constructor
* has better runtime performance than doing an {@link #addAll(java.util.Collection)}
* operation, <code>n log(n)</code> instead of <code>n<sup>2</sup></code>.
* @param c The collection to start off with
* @param comparator The comparator to use in place of the equals method on the underlying
* objects, or null if a simple {@link Object#equals(java.lang.Object)} check is sufficient.
*/
public LinkedComparatorSet(Collection c, EqualsComparator comparator) {
this.comparator = comparator;
if (c != null && comparator != null) {
Set<Integer> skip = new HashSet<Integer>();
List<T> array = new ArrayList<T>(c);
for (int i = 0; i < c.size(); i++) {
if (skip.contains(i)) {
continue;
}
boolean foundMatch = false;
T item1 = array.get(i);
for (int j = i + 1; j < array.size(); j++) {
if (skip.contains(j)) {
continue;
}
T item2 = array.get(j);
if (comparator.checkIfEquals(item1, item2)) {
skip.add(j);
if (!foundMatch) {
list.add(item1);
}
foundMatch = true;
}
}
if (!foundMatch) {
list.add(item1);
}
}
} else if(c != null){
addAll(c);
}
}
@Override
public Iterator<T> iterator() {
return list.iterator();
}
@Override
public int size() {
return list.size();
}
@Override
public boolean add(T e) {
if (!contains(e)) {
list.add(e);
return true;
} else {
return false;
}
}
/**
* {@inheritDoc}
*
* This implementation uses the custom comparator if provided to check for
* equality, as opposed to the underlying object's equals methods.
* @param o
* @return
*/
@Override
public boolean contains(Object o) {
if (comparator == null) {
return super.contains(o);
} else {
Iterator<T> e = iterator();
if (o == null) {
while (e.hasNext()) {
if (e.next() == null) {
return true;
}
}
} else {
while (e.hasNext()) {
if (comparator.checkIfEquals(o, e.next())) {
return true;
}
}
}
return false;
}
}
/**
* {@inheritDoc}
*
* This implementation uses the custom comparator if provided to check
* for equality, as opposed to the underlying object's equals methods.
* @param o
* @return
*/
@Override
public boolean remove(Object o) {
if(comparator == null){
return super.remove(o);
} else {
Iterator<T> e = iterator();
if (o==null) {
while (e.hasNext()) {
if (e.next()==null) {
e.remove();
return true;
}
}
} else {
while (e.hasNext()) {
if (comparator.checkIfEquals(o, e.next())) {
e.remove();
return true;
}
}
}
return false;
}
}
/**
* This can be passed in to the constructor of {@link LinkedComparatorSet} to
* "override" the equals contract of the sub elements, and that will be used instead.
*/
public static interface EqualsComparator<T> {
/**
* Should return true if val1 and val2 are "equals" according to your custom
* contract.
* @param val1
* @param val2
* @return
*/
boolean checkIfEquals(T val1, T val2);
}
}