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