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