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.set;
18  
19  import java.io.IOException;
20  import java.io.InvalidObjectException;
21  import java.io.ObjectInputStream;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.HashSet;
25  import java.util.List;
26  import java.util.ListIterator;
27  import java.util.Objects;
28  import java.util.Set;
29  import java.util.function.Predicate;
30  
31  import org.apache.commons.collections4.CollectionUtils;
32  import org.apache.commons.collections4.OrderedIterator;
33  import org.apache.commons.collections4.functors.UniquePredicate;
34  import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
35  import org.apache.commons.collections4.list.UnmodifiableList;
36  
37  /**
38   * Decorates another {@code Set} to ensure that the order of addition is
39   * retained and used by the iterator.
40   * <p>
41   * If an object is added to the set for a second time, it will remain in the
42   * original position in the iteration. The order can be observed from the set
43   * via the iterator or toArray methods.
44   * </p>
45   * <p>
46   * The ListOrderedSet also has various useful direct methods. These include many
47   * from {@code List}, such as {@code get(int)},
48   * {@code remove(int)} and {@code indexOf(int)}. An unmodifiable
49   * {@code List} view of the set can be obtained via {@code asList()}.
50   * </p>
51   * <p>
52   * This class cannot implement the {@code List} interface directly as
53   * various interface methods (notably equals/hashCode) are incompatible with a
54   * set.
55   * </p>
56   * <p>
57   * This class is Serializable from Commons Collections 3.1.
58   * </p>
59   *
60   * @param <E> The type of the elements in this set
61   * @since 3.0
62   */
63  public class ListOrderedSet<E>
64      extends AbstractSerializableSetDecorator<E> {
65  
66      /**
67       * Internal iterator handle remove.
68       */
69      static class OrderedSetIterator<E>
70          extends AbstractIteratorDecorator<E>
71          implements OrderedIterator<E> {
72  
73          /** Object we iterate on */
74          private final Collection<E> set;
75  
76          /** Last object retrieved */
77          private E last;
78  
79          private OrderedSetIterator(final ListIterator<E> iterator, final Collection<E> set) {
80              super(iterator);
81              this.set = set;
82          }
83  
84          @Override
85          public boolean hasPrevious() {
86              return ((ListIterator<E>) getIterator()).hasPrevious();
87          }
88  
89          @Override
90          public E next() {
91              last = getIterator().next();
92              return last;
93          }
94  
95          @Override
96          public E previous() {
97              last = ((ListIterator<E>) getIterator()).previous();
98              return last;
99          }
100 
101         @Override
102         public void remove() {
103             set.remove(last);
104             getIterator().remove();
105             last = null;
106         }
107     }
108 
109     /** Serialization version */
110     private static final long serialVersionUID = -228664372470420141L;
111 
112     /**
113      * Factory method to create an ordered set using the supplied list to retain order.
114      * <p>
115      * A {@code HashSet} is used for the set behavior.
116      * </p>
117      * <p>
118      * NOTE: If the list contains duplicates, the duplicates are removed,
119      * altering the specified list.
120      * </p>
121      *
122      * @param <E> The element type
123      * @param list The list to decorate, must not be null
124      * @return A new ordered set
125      * @throws NullPointerException if list is null
126      * @since 4.0
127      */
128     public static <E> ListOrderedSet<E> listOrderedSet(final List<E> list) {
129         Objects.requireNonNull(list, "list");
130         CollectionUtils.filter(list, UniquePredicate.uniquePredicate());
131         final Set<E> set = new HashSet<>(list);
132 
133         return new ListOrderedSet<>(set, list);
134     }
135 
136     /**
137      * Factory method to create an ordered set.
138      * <p>
139      * An {@code ArrayList} is used to retain order.
140      * </p>
141      *
142      * @param <E> The element type
143      * @param set The set to decorate, must not be null
144      * @return A new ordered set
145      * @throws NullPointerException if set is null
146      * @since 4.0
147      */
148     public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set) {
149         return new ListOrderedSet<>(set);
150     }
151 
152     /**
153      * Factory method to create an ordered set specifying the list and set to use.
154      * <p>
155      * The list and set must both be empty.
156      * </p>
157      *
158      * @param <E> The element type
159      * @param set The set to decorate, must be empty and not null
160      * @param list The list to decorate, must be empty and not null
161      * @return A new ordered set
162      * @throws NullPointerException if set or list is null
163      * @throws IllegalArgumentException if either the set or list is not empty
164      * @since 4.0
165      */
166     public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set, final List<E> list) {
167         Objects.requireNonNull(set, "set");
168         Objects.requireNonNull(list, "list");
169         if (!set.isEmpty() || !list.isEmpty()) {
170             throw new IllegalArgumentException("Set and List must be empty");
171         }
172         return new ListOrderedSet<>(set, list);
173     }
174 
175     /** Internal list to hold the sequence of objects */
176     private final List<E> setOrder;
177 
178     /**
179      * Constructs a new empty {@code ListOrderedSet} using a
180      * {@code HashSet} and an {@code ArrayList} internally.
181      *
182      * @since 3.1
183      */
184     public ListOrderedSet() {
185         super(new HashSet<>());
186         setOrder = new ArrayList<>();
187     }
188 
189     /**
190      * Constructor that wraps (not copies).
191      *
192      * @param set The set to decorate, must not be null
193      * @throws NullPointerException if set is null
194      */
195     protected ListOrderedSet(final Set<E> set) {
196         super(set);
197         setOrder = new ArrayList<>(set);
198     }
199 
200     /**
201      * Constructor that wraps (not copies) the Set and specifies the list to
202      * use.
203      * <p>
204      * The set and list must both be correctly initialized to the same elements.
205      * </p>
206      *
207      * @param set The set to decorate, must not be null
208      * @param list The list to decorate, must not be null
209      * @throws NullPointerException if set or list is null
210      */
211     protected ListOrderedSet(final Set<E> set, final List<E> list) {
212         super(set);
213         setOrder = Objects.requireNonNull(list, "list");
214     }
215 
216     @Override
217     public boolean add(final E object) {
218         if (decorated().add(object)) {
219             setOrder.add(object);
220             return true;
221         }
222         return false;
223     }
224 
225     /**
226      * Inserts the specified element at the specified position if it is not yet
227      * contained in this ordered set (optional operation). Shifts the element
228      * currently at this position and any subsequent elements to the right.
229      *
230      * @param index The index at which the element is to be inserted
231      * @param object The element to be inserted
232      * @see List#add(int, Object)
233      */
234     public void add(final int index, final E object) {
235         if (index < 0 || index > setOrder.size()) {
236             throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + setOrder.size());
237         }
238         if (!contains(object)) {
239             decorated().add(object);
240             setOrder.add(index, object);
241         }
242     }
243 
244     @Override
245     public boolean addAll(final Collection<? extends E> coll) {
246         boolean result = false;
247         for (final E e : coll) {
248             result |= add(e);
249         }
250         return result;
251     }
252 
253     /**
254      * Inserts all elements in the specified collection not yet contained in the
255      * ordered set at the specified position (optional operation). Shifts the
256      * element currently at the position and all subsequent elements to the
257      * right.
258      *
259      * @param index The position to insert the elements
260      * @param coll The collection containing the elements to be inserted
261      * @return {@code true} if this ordered set changed as a result of the call
262      * @see List#addAll(int, Collection)
263      */
264     public boolean addAll(final int index, final Collection<? extends E> coll) {
265         if (index < 0 || index > setOrder.size()) {
266             throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + setOrder.size());
267         }
268         boolean changed = false;
269         // collect all elements to be added for performance reasons
270         final List<E> toAdd = new ArrayList<>();
271         for (final E e : coll) {
272             if (contains(e)) {
273                 continue;
274             }
275             decorated().add(e);
276             toAdd.add(e);
277             changed = true;
278         }
279 
280         if (changed) {
281             setOrder.addAll(index, toAdd);
282         }
283 
284         return changed;
285     }
286 
287     /**
288      * Gets an unmodifiable view of the order of the Set.
289      *
290      * @return An unmodifiable list view
291      */
292     public List<E> asList() {
293         return UnmodifiableList.unmodifiableList(setOrder);
294     }
295 
296     @Override
297     public void clear() {
298         decorated().clear();
299         setOrder.clear();
300     }
301 
302     /**
303      * Gets the element at the specified position in this ordered set.
304      *
305      * @param index The position of the element in the ordered {@link Set}.
306      * @return The element at position {@code index}
307      * @see List#get(int)
308      */
309     public E get(final int index) {
310         return setOrder.get(index);
311     }
312 
313     /**
314      * Returns the index of the first occurrence of the specified element in
315      * ordered set.
316      *
317      * @param object The element to search for
318      * @return The index of the first occurrence of the object, or {@code -1} if
319      *         this ordered set does not contain this object
320      * @see List#indexOf(Object)
321      */
322     public int indexOf(final Object object) {
323         return setOrder.indexOf(object);
324     }
325 
326     @Override
327     public OrderedIterator<E> iterator() {
328         return new OrderedSetIterator<>(setOrder.listIterator(), decorated());
329     }
330 
331     /**
332      * Deserializes the set and re-checks that the iteration order matches the
333      * decorated set, as the constructors guarantee.
334      *
335      * @param in  The input stream
336      * @throws IOException Thrown if an error occurs while reading from the stream
337      * @throws ClassNotFoundException if a class read from the stream cannot be loaded
338      */
339     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
340         in.defaultReadObject();
341         if (setOrder.size() != size() || !new HashSet<>(setOrder).equals(decorated())) {
342             throw new InvalidObjectException("Inconsistent ListOrderedSet deserialized: iteration order does not match the set");
343         }
344     }
345 
346     /**
347      * Removes the element at the specified position from the ordered set.
348      * Shifts any subsequent elements to the left.
349      *
350      * @param index The index of the element to be removed
351      * @return The element that has been remove from the ordered set
352      * @see List#remove(int)
353      */
354     public E remove(final int index) {
355         final E obj = setOrder.remove(index);
356         remove(obj);
357         return obj;
358     }
359 
360     @Override
361     public boolean remove(final Object object) {
362         final boolean result = decorated().remove(object);
363         if (result) {
364             setOrder.remove(object);
365         }
366         return result;
367     }
368 
369     @Override
370     public boolean removeAll(final Collection<?> coll) {
371         boolean result = false;
372         for (final Object name : coll) {
373             result |= remove(name);
374         }
375         return result;
376     }
377 
378     /**
379      * @since 4.4
380      */
381     @Override
382     public boolean removeIf(final Predicate<? super E> filter) {
383         if (Objects.isNull(filter)) {
384             return false;
385         }
386         final boolean result = decorated().removeIf(filter);
387         if (result) {
388             setOrder.removeIf(filter);
389         }
390         return result;
391     }
392 
393     /**
394      * {@inheritDoc}
395      * <p>
396      * This implementation iterates over the elements of this set, checking
397      * each element in turn to see if it's contained in {@code coll}.
398      * If it's not contained, it's removed from this set. As a consequence,
399      * it is advised to use a collection type for {@code coll} that provides
400      * a fast (for example O(1)) implementation of {@link Collection#contains(Object)}.
401      * </p>
402      */
403     @Override
404     public boolean retainAll(final Collection<?> coll) {
405         final boolean result = decorated().retainAll(coll);
406         if (!result) {
407             return false;
408         }
409         if (decorated().isEmpty()) {
410             setOrder.clear();
411         } else {
412             setOrder.removeIf(e -> !decorated().contains(e));
413         }
414         return result;
415     }
416 
417     @Override
418     public Object[] toArray() {
419         return setOrder.toArray();
420     }
421 
422     @Override
423     public <T> T[] toArray(final T[] a) {
424         return setOrder.toArray(a);
425     }
426 
427     /**
428      * Uses the underlying List's toString so that order is achieved. This means
429      * that the decorated Set's toString is not used, so any custom toStrings
430      * will be ignored.
431      *
432      * @return A string representation of the ordered set
433      */
434     // Fortunately List.toString and Set.toString look the same
435     @Override
436     public String toString() {
437         return setOrder.toString();
438     }
439 
440 }