    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.collections4.list;
    018
    019import java.lang.reflect.InvocationTargetException;
    020import java.util.ArrayList;
    021import java.util.Collection;
    022import java.util.HashSet;
    023import java.util.Iterator;
    024import java.util.List;
    025import java.util.ListIterator;
    026import java.util.Objects;
    027import java.util.Set;
    028import java.util.function.Predicate;
    029
    030import org.apache.commons.collections4.ListUtils;
    031import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
    032import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator;
    033import org.apache.commons.collections4.set.UnmodifiableSet;
    034
    035/**
    036 * Decorates a {@code List} to ensure that no duplicates are present much
    037 * like a {@code Set}.
    038 * <p>
    039 * The {@code List} interface makes certain assumptions/requirements. This
    040 * implementation breaks these in certain ways, but this is merely the result of
    041 * rejecting duplicates. Each violation is explained in the method, but it
    042 * should not affect you. Bear in mind that Sets require immutable objects to
    043 * function correctly.
    044 * </p>
    045 * <p>
    046 * The {@link org.apache.commons.collections4.set.ListOrderedSet ListOrderedSet}
    047 * class provides an alternative approach, by wrapping an existing Set and
    048 * retaining insertion order in the iterator.
    049 * </p>
    050 * <p>
    051 * This class is Serializable from Commons Collections 3.1.
    052 * </p>
    053 *
    054 * @param <E> the type of the elements in the list.
    055 * @since 3.0
    056 */
    057public class SetUniqueList<E> extends AbstractSerializableListDecorator<E> {
    058
    059    /**
    060     * Inner class iterator.
    061     */
    062    static class SetListIterator<E> extends AbstractIteratorDecorator<E> {
    063
    064        private final Set<E> set;
    065        private E last;
    066
    067        protected SetListIterator(final Iterator<E> it, final Set<E> set) {
    068            super(it);
    069            this.set = set;
    070        }
    071
    072        @Override
    073        public E next() {
    074            last = super.next();
    075            return last;
    076        }
    077
    078        @Override
    079        public void remove() {
    080            super.remove();
    081            set.remove(last);
    082            last = null;
    083        }
    084    }
    085
    086    /**
    087     * Inner class iterator.
    088     */
    089    static class SetListListIterator<E> extends
    090            AbstractListIteratorDecorator<E> {
    091
    092        private final Set<E> set;
    093        private E last;
    094
    095        protected SetListListIterator(final ListIterator<E> it, final Set<E> set) {
    096            super(it);
    097            this.set = set;
    098        }
    099
    100        @Override
    101        public void add(final E object) {
    102            if (!set.contains(object)) {
    103                super.add(object);
    104                set.add(object);
    105            }
    106        }
    107
    108        @Override
    109        public E next() {
    110            last = super.next();
    111            return last;
    112        }
    113
    114        @Override
    115        public E previous() {
    116            last = super.previous();
    117            return last;
    118        }
    119
    120        @Override
    121        public void remove() {
    122            super.remove();
    123            set.remove(last);
    124            last = null;
    125        }
    126
    127        @Override
    128        public void set(final E object) {
    129            throw new UnsupportedOperationException("ListIterator does not support set");
    130        }
    131    }
    132
    133    /** Serialization version. */
    134    private static final long serialVersionUID = 7196982186153478694L;
    135
    136    /**
    137     * Factory method to create a SetList using the supplied list to retain order.
    138     * <p>
    139     * If the list contains duplicates, these are removed (first indexed one
    140     * kept). A {@code HashSet} is used for the set behavior.
    141     *
    142     * @param <E>  the element type
    143     * @param list  the list to decorate, must not be null
    144     * @return a new {@link SetUniqueList}
    145     * @throws NullPointerException if list is null
    146     * @since 4.0
    147     */
    148    public static <E> SetUniqueList<E> setUniqueList(final List<E> list) {
    149        Objects.requireNonNull(list, "list");
    150        if (list.isEmpty()) {
    151            return new SetUniqueList<>(list, new HashSet<>());
    152        }
    153        final List<E> temp = new ArrayList<>(list);
    154        list.clear();
    155        final SetUniqueList<E> sl = new SetUniqueList<>(list, new HashSet<>());
    156        sl.addAll(temp);
    157        return sl;
    158    }
    159
    160    /** Internal Set to maintain uniqueness. */
    161    private final Set<E> set;
    162
    163    /**
    164     * Constructor that wraps (not copies) the List and specifies the set to use.
    165     * <p>
    166     * The set and list must both be correctly initialized to the same elements.
    167     *
    168     * @param set  the set to decorate, must not be null
    169     * @param list  the list to decorate, must not be null
    170     * @throws NullPointerException if set or list is null
    171     */
    172    protected SetUniqueList(final List<E> list, final Set<E> set) {
    173        super(list);
    174        this.set = Objects.requireNonNull(set, "set");
    175    }
    176
    177    /**
    178     * Adds an element to the list if it is not already present.
    179     * <p>
    180     * <em>(Violation)</em> The {@code List} interface requires that this
    181     * method returns {@code true} always. However, this class may return
    182     * {@code false} because of the {@code Set} behavior.
    183     *
    184     * @param object  the object to add
    185     * @return true if object was added
    186     */
    187    @Override
    188    public boolean add(final E object) {
    189        // gets initial size
    190        final int sizeBefore = size();
    191
    192        // adds element if unique
    193        add(size(), object);
    194
    195        // compares sizes to detect if collection changed
    196        return sizeBefore != size();
    197    }
    198
    199    /**
    200     * Adds an element to a specific index in the list if it is not already
    201     * present.
    202     * <p>
    203     * <em>(Violation)</em> The {@code List} interface makes the assumption
    204     * that the element is always inserted. This may not happen with this
    205     * implementation.
    206     *
    207     * @param index  the index to insert at
    208     * @param object  the object to add
    209     */
    210    @Override
    211    public void add(final int index, final E object) {
    212        // adds element if it is not contained already
    213        if (!set.contains(object)) {
    214            set.add(object);
    215            super.add(index, object);
    216        }
    217    }
    218
    219    /**
    220     * Adds a collection of objects to the end of the list avoiding duplicates.
    221     * <p>
    222     * Only elements that are not already in this list will be added, and
    223     * duplicates from the specified collection will be ignored.
    224     * <p>
    225     * <em>(Violation)</em> The {@code List} interface makes the assumption
    226     * that the elements are always inserted. This may not happen with this
    227     * implementation.
    228     *
    229     * @param coll  the collection to add in iterator order
    230     * @return true if this collection changed
    231     */
    232    @Override
    233    public boolean addAll(final Collection<? extends E> coll) {
    234        return addAll(size(), coll);
    235    }
    236
    237    /**
    238     * Adds a collection of objects a specific index in the list avoiding
    239     * duplicates.
    240     * <p>
    241     * Only elements that are not already in this list will be added, and
    242     * duplicates from the specified collection will be ignored.
    243     * <p>
    244     * <em>(Violation)</em> The {@code List} interface makes the assumption
    245     * that the elements are always inserted. This may not happen with this
    246     * implementation.
    247     *
    248     * @param index  the index to insert at
    249     * @param coll  the collection to add in iterator order
    250     * @return true if this collection changed
    251     */
    252    @Override
    253    public boolean addAll(final int index, final Collection<? extends E> coll) {
    254        final List<E> temp = new ArrayList<>();
    255        for (final E e : coll) {
    256            if (set.add(e)) {
    257                temp.add(e);
    258            }
    259        }
    260        return super.addAll(index, temp);
    261    }
    262
    263    /**
    264     * Gets an unmodifiable view as a Set.
    265     *
    266     * @return an unmodifiable set view
    267     */
    268    public Set<E> asSet() {
    269        return UnmodifiableSet.unmodifiableSet(set);
    270    }
    271
    272    @Override
    273    public void clear() {
    274        super.clear();
    275        set.clear();
    276    }
    277
    278    @Override
    279    public boolean contains(final Object object) {
    280        return set.contains(object);
    281    }
    282
    283    @Override
    284    public boolean containsAll(final Collection<?> coll) {
    285        return set.containsAll(coll);
    286    }
    287
    288    /**
    289     * Create a new {@link Set} with the same type as the provided {@code set}
    290     * and populate it with all elements of {@code list}.
    291     *
    292     * @param set  the {@link Set} to be used as return type, must not be null
    293     * @param list  the {@link List} to populate the {@link Set}
    294     * @return a new {@link Set} populated with all elements of the provided
    295     *   {@link List}
    296     */
    297    protected Set<E> createSetBasedOnList(final Set<E> set, final List<E> list) {
    298        Set<E> subSet;
    299        if (set.getClass().equals(HashSet.class)) {
    300            subSet = new HashSet<>(list.size());
    301        } else {
    302            try {
    303                subSet = set.getClass().getDeclaredConstructor(set.getClass()).newInstance(set);
    304            } catch (final InstantiationException
    305                    | IllegalAccessException
    306                    | InvocationTargetException
    307                    | NoSuchMethodException ie) {
    308                subSet = new HashSet<>();
    309            }
    310        }
    311        subSet.addAll(list);
    312        return subSet;
    313    }
    314
    315    @Override
    316    public Iterator<E> iterator() {
    317        return new SetListIterator<>(super.iterator(), set);
    318    }
    319
    320    @Override
    321    public ListIterator<E> listIterator() {
    322        return new SetListListIterator<>(super.listIterator(), set);
    323    }
    324
    325    @Override
    326    public ListIterator<E> listIterator(final int index) {
    327        return new SetListListIterator<>(super.listIterator(index), set);
    328    }
    329
    330    @Override
    331    public E remove(final int index) {
    332        final E result = super.remove(index);
    333        set.remove(result);
    334        return result;
    335    }
    336
    337    @Override
    338    public boolean remove(final Object object) {
    339        final boolean result = set.remove(object);
    340        if (result) {
    341            super.remove(object);
    342        }
    343        return result;
    344    }
    345
    346    @Override
    347    public boolean removeAll(final Collection<?> coll) {
    348        boolean result = false;
    349        for (final Object name : coll) {
    350            result |= remove(name);
    351        }
    352        return result;
    353    }
    354
    355    /**
    356     * @since 4.4
    357     */
    358    @Override
    359    public boolean removeIf(final Predicate<? super E> filter) {
    360        final boolean result = super.removeIf(filter);
    361        set.removeIf(filter);
    362        return result;
    363    }
    364
    365    /**
    366     * {@inheritDoc}
    367     * <p>
    368     * This implementation iterates over the elements of this list, checking
    369     * each element in turn to see if it's contained in {@code coll}.
    370     * If it's not contained, it's removed from this list. As a consequence,
    371     * it is advised to use a collection type for {@code coll} that provides
    372     * a fast (for example O(1)) implementation of {@link Collection#contains(Object)}.
    373     */
    374    @Override
    375    public boolean retainAll(final Collection<?> coll) {
    376        final boolean result = set.retainAll(coll);
    377        if (!result) {
    378            return false;
    379        }
    380        if (set.isEmpty()) {
    381            super.clear();
    382        } else {
    383            // use the set as parameter for the call to retainAll to improve performance
    384            super.retainAll(set);
    385        }
    386        return result;
    387    }
    388
    389    /**
    390     * Sets the value at the specified index avoiding duplicates.
    391     * <p>
    392     * The object is set into the specified index. Afterwards, any previous
    393     * duplicate is removed. If the object is not already in the list then a
    394     * normal set occurs. If it is present, then the old version is removed.
    395     *
    396     * @param index  the index to insert at
    397     * @param object  the object to set
    398     * @return the previous object
    399     */
    400    @Override
    401    public E set(final int index, final E object) {
    402        final int pos = indexOf(object);
    403        final E removed = super.set(index, object);
    404
    405        if (pos != -1 && pos != index) {
    406            // the object is already in the unique list
    407            // (and it hasn't been swapped with itself)
    408            super.remove(pos); // remove the duplicate by index
    409        }
    410
    411        set.remove(removed); // remove the item deleted by the set
    412        set.add(object); // add the new item to the unique set
    413
    414        return removed; // return the item deleted by the set
    415    }
    416
    417    /**
    418     * {@inheritDoc}
    419     * <p>
    420     * NOTE: from 4.0, an unmodifiable list will be returned, as changes to the
    421     * subList can invalidate the parent list.
    422     */
    423    @Override
    424    public List<E> subList(final int fromIndex, final int toIndex) {
    425        final List<E> superSubList = super.subList(fromIndex, toIndex);
    426        final Set<E> subSet = createSetBasedOnList(set, superSubList);
    427        return ListUtils.unmodifiableList(new SetUniqueList<>(superSubList, subSet));
    428    }
    429
    430}