SetUniqueList.java
/*
* 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.collections4.list;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator;
import org.apache.commons.collections4.set.UnmodifiableSet;
/**
* Decorates a {@code List} to ensure that no duplicates are present much
* like a {@code Set}.
* <p>
* The {@code List} interface makes certain assumptions/requirements. This
* implementation breaks these in certain ways, but this is merely the result of
* rejecting duplicates. Each violation is explained in the method, but it
* should not affect you. Bear in mind that Sets require immutable objects to
* function correctly.
* </p>
* <p>
* The {@link org.apache.commons.collections4.set.ListOrderedSet ListOrderedSet}
* class provides an alternative approach, by wrapping an existing Set and
* retaining insertion order in the iterator.
* </p>
* <p>
* This class is Serializable from Commons Collections 3.1.
* </p>
*
* @param <E> the type of the elements in the list.
* @since 3.0
*/
public class SetUniqueList<E> extends AbstractSerializableListDecorator<E> {
/**
* Inner class iterator.
*/
static class SetListIterator<E> extends AbstractIteratorDecorator<E> {
private final Set<E> set;
private E last;
protected SetListIterator(final Iterator<E> it, final Set<E> set) {
super(it);
this.set = set;
}
@Override
public E next() {
last = super.next();
return last;
}
@Override
public void remove() {
super.remove();
set.remove(last);
last = null;
}
}
/**
* Inner class iterator.
*/
static class SetListListIterator<E> extends
AbstractListIteratorDecorator<E> {
private final Set<E> set;
private E last;
protected SetListListIterator(final ListIterator<E> it, final Set<E> set) {
super(it);
this.set = set;
}
@Override
public void add(final E object) {
if (!set.contains(object)) {
super.add(object);
set.add(object);
}
}
@Override
public E next() {
last = super.next();
return last;
}
@Override
public E previous() {
last = super.previous();
return last;
}
@Override
public void remove() {
super.remove();
set.remove(last);
last = null;
}
@Override
public void set(final E object) {
throw new UnsupportedOperationException("ListIterator does not support set");
}
}
/** Serialization version. */
private static final long serialVersionUID = 7196982186153478694L;
/**
* Factory method to create a SetList using the supplied list to retain order.
* <p>
* If the list contains duplicates, these are removed (first indexed one
* kept). A {@code HashSet} is used for the set behavior.
*
* @param <E> the element type
* @param list the list to decorate, must not be null
* @return a new {@link SetUniqueList}
* @throws NullPointerException if list is null
* @since 4.0
*/
public static <E> SetUniqueList<E> setUniqueList(final List<E> list) {
Objects.requireNonNull(list, "list");
if (list.isEmpty()) {
return new SetUniqueList<>(list, new HashSet<>());
}
final List<E> temp = new ArrayList<>(list);
list.clear();
final SetUniqueList<E> sl = new SetUniqueList<>(list, new HashSet<>());
sl.addAll(temp);
return sl;
}
/** Internal Set to maintain uniqueness. */
private final Set<E> set;
/**
* Constructor that wraps (not copies) the List and specifies the set to use.
* <p>
* The set and list must both be correctly initialized to the same elements.
*
* @param set the set to decorate, must not be null
* @param list the list to decorate, must not be null
* @throws NullPointerException if set or list is null
*/
protected SetUniqueList(final List<E> list, final Set<E> set) {
super(list);
this.set = Objects.requireNonNull(set, "set");
}
/**
* Adds an element to the list if it is not already present.
* <p>
* <em>(Violation)</em> The {@code List} interface requires that this
* method returns {@code true} always. However, this class may return
* {@code false} because of the {@code Set} behavior.
*
* @param object the object to add
* @return true if object was added
*/
@Override
public boolean add(final E object) {
// gets initial size
final int sizeBefore = size();
// adds element if unique
add(size(), object);
// compares sizes to detect if collection changed
return sizeBefore != size();
}
/**
* Adds an element to a specific index in the list if it is not already
* present.
* <p>
* <em>(Violation)</em> The {@code List} interface makes the assumption
* that the element is always inserted. This may not happen with this
* implementation.
*
* @param index the index to insert at
* @param object the object to add
*/
@Override
public void add(final int index, final E object) {
// adds element if it is not contained already
if (!set.contains(object)) {
set.add(object);
super.add(index, object);
}
}
/**
* Adds a collection of objects to the end of the list avoiding duplicates.
* <p>
* Only elements that are not already in this list will be added, and
* duplicates from the specified collection will be ignored.
* <p>
* <em>(Violation)</em> The {@code List} interface makes the assumption
* that the elements are always inserted. This may not happen with this
* implementation.
*
* @param coll the collection to add in iterator order
* @return true if this collection changed
*/
@Override
public boolean addAll(final Collection<? extends E> coll) {
return addAll(size(), coll);
}
/**
* Adds a collection of objects a specific index in the list avoiding
* duplicates.
* <p>
* Only elements that are not already in this list will be added, and
* duplicates from the specified collection will be ignored.
* <p>
* <em>(Violation)</em> The {@code List} interface makes the assumption
* that the elements are always inserted. This may not happen with this
* implementation.
*
* @param index the index to insert at
* @param coll the collection to add in iterator order
* @return true if this collection changed
*/
@Override
public boolean addAll(final int index, final Collection<? extends E> coll) {
final List<E> temp = new ArrayList<>();
for (final E e : coll) {
if (set.add(e)) {
temp.add(e);
}
}
return super.addAll(index, temp);
}
/**
* Gets an unmodifiable view as a Set.
*
* @return an unmodifiable set view
*/
public Set<E> asSet() {
return UnmodifiableSet.unmodifiableSet(set);
}
@Override
public void clear() {
super.clear();
set.clear();
}
@Override
public boolean contains(final Object object) {
return set.contains(object);
}
@Override
public boolean containsAll(final Collection<?> coll) {
return set.containsAll(coll);
}
/**
* Create a new {@link Set} with the same type as the provided {@code set}
* and populate it with all elements of {@code list}.
*
* @param set the {@link Set} to be used as return type, must not be null
* @param list the {@link List} to populate the {@link Set}
* @return a new {@link Set} populated with all elements of the provided
* {@link List}
*/
protected Set<E> createSetBasedOnList(final Set<E> set, final List<E> list) {
Set<E> subSet;
if (set.getClass().equals(HashSet.class)) {
subSet = new HashSet<>(list.size());
} else {
try {
subSet = set.getClass().getDeclaredConstructor(set.getClass()).newInstance(set);
} catch (final InstantiationException
| IllegalAccessException
| InvocationTargetException
| NoSuchMethodException ie) {
subSet = new HashSet<>();
}
}
subSet.addAll(list);
return subSet;
}
@Override
public Iterator<E> iterator() {
return new SetListIterator<>(super.iterator(), set);
}
@Override
public ListIterator<E> listIterator() {
return new SetListListIterator<>(super.listIterator(), set);
}
@Override
public ListIterator<E> listIterator(final int index) {
return new SetListListIterator<>(super.listIterator(index), set);
}
@Override
public E remove(final int index) {
final E result = super.remove(index);
set.remove(result);
return result;
}
@Override
public boolean remove(final Object object) {
final boolean result = set.remove(object);
if (result) {
super.remove(object);
}
return result;
}
@Override
public boolean removeAll(final Collection<?> coll) {
boolean result = false;
for (final Object name : coll) {
result |= remove(name);
}
return result;
}
/**
* @since 4.4
*/
@Override
public boolean removeIf(final Predicate<? super E> filter) {
final boolean result = super.removeIf(filter);
set.removeIf(filter);
return result;
}
/**
* {@inheritDoc}
* <p>
* This implementation iterates over the elements of this list, checking
* each element in turn to see if it's contained in {@code coll}.
* If it's not contained, it's removed from this list. As a consequence,
* it is advised to use a collection type for {@code coll} that provides
* a fast (e.g. O(1)) implementation of {@link Collection#contains(Object)}.
*/
@Override
public boolean retainAll(final Collection<?> coll) {
final boolean result = set.retainAll(coll);
if (!result) {
return false;
}
if (set.isEmpty()) {
super.clear();
} else {
// use the set as parameter for the call to retainAll to improve performance
super.retainAll(set);
}
return result;
}
/**
* Sets the value at the specified index avoiding duplicates.
* <p>
* The object is set into the specified index. Afterwards, any previous
* duplicate is removed. If the object is not already in the list then a
* normal set occurs. If it is present, then the old version is removed.
*
* @param index the index to insert at
* @param object the object to set
* @return the previous object
*/
@Override
public E set(final int index, final E object) {
final int pos = indexOf(object);
final E removed = super.set(index, object);
if (pos != -1 && pos != index) {
// the object is already in the unique list
// (and it hasn't been swapped with itself)
super.remove(pos); // remove the duplicate by index
}
set.remove(removed); // remove the item deleted by the set
set.add(object); // add the new item to the unique set
return removed; // return the item deleted by the set
}
/**
* {@inheritDoc}
* <p>
* NOTE: from 4.0, an unmodifiable list will be returned, as changes to the
* subList can invalidate the parent list.
*/
@Override
public List<E> subList(final int fromIndex, final int toIndex) {
final List<E> superSubList = super.subList(fromIndex, toIndex);
final Set<E> subSet = createSetBasedOnList(set, superSubList);
return ListUtils.unmodifiableList(new SetUniqueList<>(superSubList, subSet));
}
}