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}