View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.list;
18  
19  import java.lang.reflect.InvocationTargetException;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.ListIterator;
26  import java.util.Objects;
27  import java.util.Set;
28  import java.util.function.Predicate;
29  
30  import org.apache.commons.collections4.ListUtils;
31  import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
32  import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator;
33  import org.apache.commons.collections4.set.UnmodifiableSet;
34  
35  /**
36   * Decorates a {@code List} to ensure that no duplicates are present much
37   * like a {@code Set}.
38   * <p>
39   * The {@code List} interface makes certain assumptions/requirements. This
40   * implementation breaks these in certain ways, but this is merely the result of
41   * rejecting duplicates. Each violation is explained in the method, but it
42   * should not affect you. Bear in mind that Sets require immutable objects to
43   * function correctly.
44   * </p>
45   * <p>
46   * The {@link org.apache.commons.collections4.set.ListOrderedSet ListOrderedSet}
47   * class provides an alternative approach, by wrapping an existing Set and
48   * retaining insertion order in the iterator.
49   * </p>
50   * <p>
51   * This class is Serializable from Commons Collections 3.1.
52   * </p>
53   *
54   * @since 3.0
55   */
56  public class SetUniqueList<E> extends AbstractSerializableListDecorator<E> {
57  
58      /**
59       * Inner class iterator.
60       */
61      static class SetListIterator<E> extends AbstractIteratorDecorator<E> {
62  
63          private final Set<E> set;
64          private E last;
65  
66          protected SetListIterator(final Iterator<E> it, final Set<E> set) {
67              super(it);
68              this.set = set;
69          }
70  
71          @Override
72          public E next() {
73              last = super.next();
74              return last;
75          }
76  
77          @Override
78          public void remove() {
79              super.remove();
80              set.remove(last);
81              last = null;
82          }
83      }
84  
85      /**
86       * Inner class iterator.
87       */
88      static class SetListListIterator<E> extends
89              AbstractListIteratorDecorator<E> {
90  
91          private final Set<E> set;
92          private E last;
93  
94          protected SetListListIterator(final ListIterator<E> it, final Set<E> set) {
95              super(it);
96              this.set = set;
97          }
98  
99          @Override
100         public void add(final E object) {
101             if (!set.contains(object)) {
102                 super.add(object);
103                 set.add(object);
104             }
105         }
106 
107         @Override
108         public E next() {
109             last = super.next();
110             return last;
111         }
112 
113         @Override
114         public E previous() {
115             last = super.previous();
116             return last;
117         }
118 
119         @Override
120         public void remove() {
121             super.remove();
122             set.remove(last);
123             last = null;
124         }
125 
126         @Override
127         public void set(final E object) {
128             throw new UnsupportedOperationException("ListIterator does not support set");
129         }
130     }
131 
132     /** Serialization version. */
133     private static final long serialVersionUID = 7196982186153478694L;
134 
135     /**
136      * Factory method to create a SetList using the supplied list to retain order.
137      * <p>
138      * If the list contains duplicates, these are removed (first indexed one
139      * kept). A {@code HashSet} is used for the set behavior.
140      *
141      * @param <E>  the element type
142      * @param list  the list to decorate, must not be null
143      * @return a new {@link SetUniqueList}
144      * @throws NullPointerException if list is null
145      * @since 4.0
146      */
147     public static <E> SetUniqueList<E> setUniqueList(final List<E> list) {
148         Objects.requireNonNull(list, "list");
149         if (list.isEmpty()) {
150             return new SetUniqueList<>(list, new HashSet<>());
151         }
152         final List<E> temp = new ArrayList<>(list);
153         list.clear();
154         final SetUniqueList<E> sl = new SetUniqueList<>(list, new HashSet<>());
155         sl.addAll(temp);
156         return sl;
157     }
158 
159     /** Internal Set to maintain uniqueness. */
160     private final Set<E> set;
161 
162     /**
163      * Constructor that wraps (not copies) the List and specifies the set to use.
164      * <p>
165      * The set and list must both be correctly initialized to the same elements.
166      *
167      * @param set  the set to decorate, must not be null
168      * @param list  the list to decorate, must not be null
169      * @throws NullPointerException if set or list is null
170      */
171     protected SetUniqueList(final List<E> list, final Set<E> set) {
172         super(list);
173         this.set = Objects.requireNonNull(set, "set");
174     }
175 
176     /**
177      * Adds an element to the list if it is not already present.
178      * <p>
179      * <i>(Violation)</i> The {@code List} interface requires that this
180      * method returns {@code true} always. However, this class may return
181      * {@code false} because of the {@code Set} behavior.
182      *
183      * @param object  the object to add
184      * @return true if object was added
185      */
186     @Override
187     public boolean add(final E object) {
188         // gets initial size
189         final int sizeBefore = size();
190 
191         // adds element if unique
192         add(size(), object);
193 
194         // compares sizes to detect if collection changed
195         return sizeBefore != size();
196     }
197 
198     /**
199      * Adds an element to a specific index in the list if it is not already
200      * present.
201      * <p>
202      * <i>(Violation)</i> The {@code List} interface makes the assumption
203      * that the element is always inserted. This may not happen with this
204      * implementation.
205      *
206      * @param index  the index to insert at
207      * @param object  the object to add
208      */
209     @Override
210     public void add(final int index, final E object) {
211         // adds element if it is not contained already
212         if (!set.contains(object)) {
213             set.add(object);
214             super.add(index, object);
215         }
216     }
217 
218     /**
219      * Adds a collection of objects to the end of the list avoiding duplicates.
220      * <p>
221      * Only elements that are not already in this list will be added, and
222      * duplicates from the specified collection will be ignored.
223      * <p>
224      * <i>(Violation)</i> The {@code List} interface makes the assumption
225      * that the elements are always inserted. This may not happen with this
226      * implementation.
227      *
228      * @param coll  the collection to add in iterator order
229      * @return true if this collection changed
230      */
231     @Override
232     public boolean addAll(final Collection<? extends E> coll) {
233         return addAll(size(), coll);
234     }
235 
236     /**
237      * Adds a collection of objects a specific index in the list avoiding
238      * duplicates.
239      * <p>
240      * Only elements that are not already in this list will be added, and
241      * duplicates from the specified collection will be ignored.
242      * <p>
243      * <i>(Violation)</i> The {@code List} interface makes the assumption
244      * that the elements are always inserted. This may not happen with this
245      * implementation.
246      *
247      * @param index  the index to insert at
248      * @param coll  the collection to add in iterator order
249      * @return true if this collection changed
250      */
251     @Override
252     public boolean addAll(final int index, final Collection<? extends E> coll) {
253         final List<E> temp = new ArrayList<>();
254         for (final E e : coll) {
255             if (set.add(e)) {
256                 temp.add(e);
257             }
258         }
259         return super.addAll(index, temp);
260     }
261 
262     /**
263      * Gets an unmodifiable view as a Set.
264      *
265      * @return an unmodifiable set view
266      */
267     public Set<E> asSet() {
268         return UnmodifiableSet.unmodifiableSet(set);
269     }
270 
271     @Override
272     public void clear() {
273         super.clear();
274         set.clear();
275     }
276 
277     @Override
278     public boolean contains(final Object object) {
279         return set.contains(object);
280     }
281 
282     @Override
283     public boolean containsAll(final Collection<?> coll) {
284         return set.containsAll(coll);
285     }
286 
287     /**
288      * Create a new {@link Set} with the same type as the provided {@code set}
289      * and populate it with all elements of {@code list}.
290      *
291      * @param set  the {@link Set} to be used as return type, must not be null
292      * @param list  the {@link List} to populate the {@link Set}
293      * @return a new {@link Set} populated with all elements of the provided
294      *   {@link List}
295      */
296     protected Set<E> createSetBasedOnList(final Set<E> set, final List<E> list) {
297         Set<E> subSet;
298         if (set.getClass().equals(HashSet.class)) {
299             subSet = new HashSet<>(list.size());
300         } else {
301             try {
302                 subSet = set.getClass().getDeclaredConstructor(set.getClass()).newInstance(set);
303             } catch (final InstantiationException
304                     | IllegalAccessException
305                     | InvocationTargetException
306                     | NoSuchMethodException ie) {
307                 subSet = new HashSet<>();
308             }
309         }
310         subSet.addAll(list);
311         return subSet;
312     }
313 
314     @Override
315     public Iterator<E> iterator() {
316         return new SetListIterator<>(super.iterator(), set);
317     }
318 
319     @Override
320     public ListIterator<E> listIterator() {
321         return new SetListListIterator<>(super.listIterator(), set);
322     }
323 
324     @Override
325     public ListIterator<E> listIterator(final int index) {
326         return new SetListListIterator<>(super.listIterator(index), set);
327     }
328 
329     @Override
330     public E remove(final int index) {
331         final E result = super.remove(index);
332         set.remove(result);
333         return result;
334     }
335 
336     @Override
337     public boolean remove(final Object object) {
338         final boolean result = set.remove(object);
339         if (result) {
340             super.remove(object);
341         }
342         return result;
343     }
344 
345     @Override
346     public boolean removeAll(final Collection<?> coll) {
347         boolean result = false;
348         for (final Object name : coll) {
349             result |= remove(name);
350         }
351         return result;
352     }
353 
354     /**
355      * @since 4.4
356      */
357     @Override
358     public boolean removeIf(final Predicate<? super E> filter) {
359         final boolean result = super.removeIf(filter);
360         set.removeIf(filter);
361         return result;
362     }
363 
364     /**
365      * {@inheritDoc}
366      * <p>
367      * This implementation iterates over the elements of this list, checking
368      * each element in turn to see if it's contained in {@code coll}.
369      * If it's not contained, it's removed from this list. As a consequence,
370      * it is advised to use a collection type for {@code coll} that provides
371      * a fast (e.g. O(1)) implementation of {@link Collection#contains(Object)}.
372      */
373     @Override
374     public boolean retainAll(final Collection<?> coll) {
375         final boolean result = set.retainAll(coll);
376         if (!result) {
377             return false;
378         }
379         if (set.isEmpty()) {
380             super.clear();
381         } else {
382             // use the set as parameter for the call to retainAll to improve performance
383             super.retainAll(set);
384         }
385         return result;
386     }
387 
388     /**
389      * Sets the value at the specified index avoiding duplicates.
390      * <p>
391      * The object is set into the specified index. Afterwards, any previous
392      * duplicate is removed. If the object is not already in the list then a
393      * normal set occurs. If it is present, then the old version is removed.
394      *
395      * @param index  the index to insert at
396      * @param object  the object to set
397      * @return the previous object
398      */
399     @Override
400     public E set(final int index, final E object) {
401         final int pos = indexOf(object);
402         final E removed = super.set(index, object);
403 
404         if (pos != -1 && pos != index) {
405             // the object is already in the unique list
406             // (and it hasn't been swapped with itself)
407             super.remove(pos); // remove the duplicate by index
408         }
409 
410         set.remove(removed); // remove the item deleted by the set
411         set.add(object); // add the new item to the unique set
412 
413         return removed; // return the item deleted by the set
414     }
415 
416     /**
417      * {@inheritDoc}
418      * <p>
419      * NOTE: from 4.0, an unmodifiable list will be returned, as changes to the
420      * subList can invalidate the parent list.
421      */
422     @Override
423     public List<E> subList(final int fromIndex, final int toIndex) {
424         final List<E> superSubList = super.subList(fromIndex, toIndex);
425         final Set<E> subSet = createSetBasedOnList(set, superSubList);
426         return ListUtils.unmodifiableList(new SetUniqueList<>(superSubList, subSet));
427     }
428 
429 }