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