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 (e.g. 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}