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.set.UnmodifiableSet;
029import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
030import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator;
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 * @version $Id: SetUniqueList.html 972421 2015-11-14 20:00:04Z tn $
050 */
051public class SetUniqueList<E> extends AbstractSerializableListDecorator<E> {
052
053    /** Serialization version. */
054    private static final long serialVersionUID = 7196982186153478694L;
055
056    /** Internal Set to maintain uniqueness. */
057    private final Set<E> set;
058
059    /**
060     * Factory method to create a SetList using the supplied list to retain order.
061     * <p>
062     * If the list contains duplicates, these are removed (first indexed one
063     * kept). A <code>HashSet</code> is used for the set behaviour.
064     *
065     * @param <E>  the element type
066     * @param list  the list to decorate, must not be null
067     * @return a new {@link SetUniqueList}
068     * @throws IllegalArgumentException if list is null
069     * @since 4.0
070     */
071    public static <E> SetUniqueList<E> setUniqueList(final List<E> list) {
072        if (list == null) {
073            throw new IllegalArgumentException("List must not be null");
074        }
075        if (list.isEmpty()) {
076            return new SetUniqueList<E>(list, new HashSet<E>());
077        }
078        final List<E> temp = new ArrayList<E>(list);
079        list.clear();
080        final SetUniqueList<E> sl = new SetUniqueList<E>(list, new HashSet<E>());
081        sl.addAll(temp);
082        return sl;
083    }
084
085    // -----------------------------------------------------------------------
086    /**
087     * Constructor that wraps (not copies) the List and specifies the set to use.
088     * <p>
089     * The set and list must both be correctly initialised to the same elements.
090     *
091     * @param set  the set to decorate, must not be null
092     * @param list  the list to decorate, must not be null
093     * @throws IllegalArgumentException if set or list is null
094     */
095    protected SetUniqueList(final List<E> list, final Set<E> set) {
096        super(list);
097        if (set == null) {
098            throw new IllegalArgumentException("Set must not be null");
099        }
100        this.set = set;
101    }
102
103    // -----------------------------------------------------------------------
104    /**
105     * Gets an unmodifiable view as a Set.
106     *
107     * @return an unmodifiable set view
108     */
109    public Set<E> asSet() {
110        return UnmodifiableSet.unmodifiableSet(set);
111    }
112
113    // -----------------------------------------------------------------------
114    /**
115     * Adds an element to the list if it is not already present.
116     * <p>
117     * <i>(Violation)</i> The <code>List</code> interface requires that this
118     * method returns <code>true</code> always. However this class may return
119     * <code>false</code> because of the <code>Set</code> behaviour.
120     *
121     * @param object  the object to add
122     * @return true if object was added
123     */
124    @Override
125    public boolean add(final E object) {
126        // gets initial size
127        final int sizeBefore = size();
128
129        // adds element if unique
130        add(size(), object);
131
132        // compares sizes to detect if collection changed
133        return sizeBefore != size();
134    }
135
136    /**
137     * Adds an element to a specific index in the list if it is not already
138     * present.
139     * <p>
140     * <i>(Violation)</i> The <code>List</code> interface makes the assumption
141     * that the element is always inserted. This may not happen with this
142     * implementation.
143     *
144     * @param index  the index to insert at
145     * @param object  the object to add
146     */
147    @Override
148    public void add(final int index, final E object) {
149        // adds element if it is not contained already
150        if (set.contains(object) == false) {
151            super.add(index, object);
152            set.add(object);
153        }
154    }
155
156    /**
157     * Adds a collection of objects to the end of the list avoiding duplicates.
158     * <p>
159     * Only elements that are not already in this list will be added, and
160     * duplicates from the specified collection will be ignored.
161     * <p>
162     * <i>(Violation)</i> The <code>List</code> interface makes the assumption
163     * that the elements are always inserted. This may not happen with this
164     * implementation.
165     *
166     * @param coll  the collection to add in iterator order
167     * @return true if this collection changed
168     */
169    @Override
170    public boolean addAll(final Collection<? extends E> coll) {
171        return addAll(size(), coll);
172    }
173
174    /**
175     * Adds a collection of objects a specific index in the list avoiding
176     * duplicates.
177     * <p>
178     * Only elements that are not already in this list will be added, and
179     * duplicates from the specified collection will be ignored.
180     * <p>
181     * <i>(Violation)</i> The <code>List</code> interface makes the assumption
182     * that the elements are always inserted. This may not happen with this
183     * implementation.
184     *
185     * @param index  the index to insert at
186     * @param coll  the collection to add in iterator order
187     * @return true if this collection changed
188     */
189    @Override
190    public boolean addAll(final int index, final Collection<? extends E> coll) {
191        final List<E> temp = new ArrayList<E>();
192        for (final E e : coll) {
193            if (set.add(e)) {
194                temp.add(e);
195            }
196        }
197        return super.addAll(index, temp);
198    }
199
200    // -----------------------------------------------------------------------
201    /**
202     * Sets the value at the specified index avoiding duplicates.
203     * <p>
204     * The object is set into the specified index. Afterwards, any previous
205     * duplicate is removed. If the object is not already in the list then a
206     * normal set occurs. If it is present, then the old version is removed.
207     *
208     * @param index  the index to insert at
209     * @param object  the object to set
210     * @return the previous object
211     */
212    @Override
213    public E set(final int index, final E object) {
214        final int pos = indexOf(object);
215        final E removed = super.set(index, object);
216
217        if (pos != -1 && pos != index) {
218            // the object is already in the unique list
219            // (and it hasn't been swapped with itself)
220            super.remove(pos); // remove the duplicate by index
221        }
222
223        set.remove(removed); // remove the item deleted by the set
224        set.add(object); // add the new item to the unique set
225
226        return removed; // return the item deleted by the set
227    }
228
229    @Override
230    public boolean remove(final Object object) {
231        final boolean result = set.remove(object);
232        if (result) {
233            super.remove(object);
234        }
235        return result;
236    }
237
238    @Override
239    public E remove(final int index) {
240        final E result = super.remove(index);
241        set.remove(result);
242        return result;
243    }
244
245    @Override
246    public boolean removeAll(final Collection<?> coll) {
247        boolean result = false;
248        for (final Object name : coll) {
249            result |= remove(name);
250        }
251        return result;
252    }
253
254    @Override
255    public boolean retainAll(final Collection<?> coll) {
256        final Set<Object> setRetainAll = new HashSet<Object>();
257        for (final Object next : coll) {
258            if (set.contains(next)) {
259                setRetainAll.add(next);
260            }
261        }
262        if (setRetainAll.size() == set.size()) {
263            return false;
264        }
265        if (setRetainAll.size() == 0) {
266            clear();
267        } else {
268            for (final Iterator<E> it = iterator(); it.hasNext();) {
269                if (!setRetainAll.contains(it.next())) {
270                    it.remove();
271                }
272            }
273        }
274        return true;
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<E>(super.iterator(), set);
296    }
297
298    @Override
299    public ListIterator<E> listIterator() {
300        return new SetListListIterator<E>(super.listIterator(), set);
301    }
302
303    @Override
304    public ListIterator<E> listIterator(final int index) {
305        return new SetListListIterator<E>(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<E>(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<E>(list.size());
335        } else {
336            try {
337                subSet = set.getClass().newInstance();
338            } catch (final InstantiationException ie) {
339                subSet = new HashSet<E>();
340            } catch (final IllegalAccessException iae) {
341                subSet = new HashSet<E>();
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}