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