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