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      * <p>
115      * NOTE: If the list contains duplicates, the duplicates are removed,
116      * altering the specified list.
117      * </p>
118      *
119      * @param <E> the element type
120      * @param list the list to decorate, must not be null
121      * @return a new ordered set
122      * @throws NullPointerException if list is null
123      * @since 4.0
124      */
125     public static <E> ListOrderedSet<E> listOrderedSet(final List<E> list) {
126         Objects.requireNonNull(list, "list");
127         CollectionUtils.filter(list, UniquePredicate.uniquePredicate());
128         final Set<E> set = new HashSet<>(list);
129 
130         return new ListOrderedSet<>(set, list);
131     }
132 
133     /**
134      * Factory method to create an ordered set.
135      * <p>
136      * An {@code ArrayList} is used to retain order.
137      * </p>
138      *
139      * @param <E> the element type
140      * @param set the set to decorate, must not be null
141      * @return a new ordered set
142      * @throws NullPointerException if set is null
143      * @since 4.0
144      */
145     public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set) {
146         return new ListOrderedSet<>(set);
147     }
148 
149     /**
150      * Factory method to create an ordered set specifying the list and set to use.
151      * <p>
152      * The list and set must both be empty.
153      * </p>
154      *
155      * @param <E> the element type
156      * @param set the set to decorate, must be empty and not null
157      * @param list the list to decorate, must be empty and not null
158      * @return a new ordered set
159      * @throws NullPointerException if set or list is null
160      * @throws IllegalArgumentException if either the set or list is not empty
161      * @since 4.0
162      */
163     public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set, final List<E> list) {
164         Objects.requireNonNull(set, "set");
165         Objects.requireNonNull(list, "list");
166         if (!set.isEmpty() || !list.isEmpty()) {
167             throw new IllegalArgumentException("Set and List must be empty");
168         }
169         return new ListOrderedSet<>(set, list);
170     }
171 
172     /** Internal list to hold the sequence of objects */
173     private final List<E> setOrder;
174 
175     /**
176      * Constructs a new empty {@code ListOrderedSet} using a
177      * {@code HashSet} and an {@code ArrayList} internally.
178      *
179      * @since 3.1
180      */
181     public ListOrderedSet() {
182         super(new HashSet<>());
183         setOrder = new ArrayList<>();
184     }
185 
186     /**
187      * Constructor that wraps (not copies).
188      *
189      * @param set the set to decorate, must not be null
190      * @throws NullPointerException if set is null
191      */
192     protected ListOrderedSet(final Set<E> set) {
193         super(set);
194         setOrder = new ArrayList<>(set);
195     }
196 
197     /**
198      * Constructor that wraps (not copies) the Set and specifies the list to
199      * use.
200      * <p>
201      * The set and list must both be correctly initialized to the same elements.
202      * </p>
203      *
204      * @param set the set to decorate, must not be null
205      * @param list the list to decorate, must not be null
206      * @throws NullPointerException if set or list is null
207      */
208     protected ListOrderedSet(final Set<E> set, final List<E> list) {
209         super(set);
210         setOrder = Objects.requireNonNull(list, "list");
211     }
212 
213     @Override
214     public boolean add(final E object) {
215         if (decorated().add(object)) {
216             setOrder.add(object);
217             return true;
218         }
219         return false;
220     }
221 
222     /**
223      * Inserts the specified element at the specified position if it is not yet
224      * contained in this ordered set (optional operation). Shifts the element
225      * currently at this position and any subsequent elements to the right.
226      *
227      * @param index the index at which the element is to be inserted
228      * @param object the element to be inserted
229      * @see List#add(int, Object)
230      */
231     public void add(final int index, final E object) {
232         if (!contains(object)) {
233             decorated().add(object);
234             setOrder.add(index, object);
235         }
236     }
237 
238     @Override
239     public boolean addAll(final Collection<? extends E> coll) {
240         boolean result = false;
241         for (final E e : coll) {
242             result |= add(e);
243         }
244         return result;
245     }
246 
247     /**
248      * Inserts all elements in the specified collection not yet contained in the
249      * ordered set at the specified position (optional operation). Shifts the
250      * element currently at the position and all subsequent elements to the
251      * right.
252      *
253      * @param index the position to insert the elements
254      * @param coll the collection containing the elements to be inserted
255      * @return {@code true} if this ordered set changed as a result of the call
256      * @see List#addAll(int, Collection)
257      */
258     public boolean addAll(final int index, final Collection<? extends E> coll) {
259         boolean changed = false;
260         // collect all elements to be added for performance reasons
261         final List<E> toAdd = new ArrayList<>();
262         for (final E e : coll) {
263             if (contains(e)) {
264                 continue;
265             }
266             decorated().add(e);
267             toAdd.add(e);
268             changed = true;
269         }
270 
271         if (changed) {
272             setOrder.addAll(index, toAdd);
273         }
274 
275         return changed;
276     }
277 
278     /**
279      * Gets an unmodifiable view of the order of the Set.
280      *
281      * @return an unmodifiable list view
282      */
283     public List<E> asList() {
284         return UnmodifiableList.unmodifiableList(setOrder);
285     }
286 
287     @Override
288     public void clear() {
289         decorated().clear();
290         setOrder.clear();
291     }
292 
293     /**
294      * Gets the element at the specified position in this ordered set.
295      *
296      * @param index the position of the element in the ordered {@link Set}.
297      * @return the element at position {@code index}
298      * @see List#get(int)
299      */
300     public E get(final int index) {
301         return setOrder.get(index);
302     }
303 
304     /**
305      * Returns the index of the first occurrence of the specified element in
306      * ordered set.
307      *
308      * @param object the element to search for
309      * @return the index of the first occurrence of the object, or {@code -1} if
310      *         this ordered set does not contain this object
311      * @see List#indexOf(Object)
312      */
313     public int indexOf(final Object object) {
314         return setOrder.indexOf(object);
315     }
316 
317     @Override
318     public OrderedIterator<E> iterator() {
319         return new OrderedSetIterator<>(setOrder.listIterator(), decorated());
320     }
321 
322     /**
323      * Removes the element at the specified position from the ordered set.
324      * Shifts any subsequent elements to the left.
325      *
326      * @param index the index of the element to be removed
327      * @return the element that has been remove from the ordered set
328      * @see List#remove(int)
329      */
330     public E remove(final int index) {
331         final E obj = setOrder.remove(index);
332         remove(obj);
333         return obj;
334     }
335 
336     @Override
337     public boolean remove(final Object object) {
338         final boolean result = decorated().remove(object);
339         if (result) {
340             setOrder.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         if (Objects.isNull(filter)) {
360             return false;
361         }
362         final boolean result = decorated().removeIf(filter);
363         if (result) {
364             setOrder.removeIf(filter);
365         }
366         return result;
367     }
368 
369     /**
370      * {@inheritDoc}
371      * <p>
372      * This implementation iterates over the elements of this set, checking
373      * each element in turn to see if it's contained in {@code coll}.
374      * If it's not contained, it's removed from this set. As a consequence,
375      * it is advised to use a collection type for {@code coll} that provides
376      * a fast (for example O(1)) implementation of {@link Collection#contains(Object)}.
377      * </p>
378      */
379     @Override
380     public boolean retainAll(final Collection<?> coll) {
381         final boolean result = decorated().retainAll(coll);
382         if (!result) {
383             return false;
384         }
385         if (decorated().isEmpty()) {
386             setOrder.clear();
387         } else {
388             setOrder.removeIf(e -> !decorated().contains(e));
389         }
390         return result;
391     }
392 
393     @Override
394     public Object[] toArray() {
395         return setOrder.toArray();
396     }
397 
398     @Override
399     public <T> T[] toArray(final T[] a) {
400         return setOrder.toArray(a);
401     }
402 
403     /**
404      * Uses the underlying List's toString so that order is achieved. This means
405      * that the decorated Set's toString is not used, so any custom toStrings
406      * will be ignored.
407      *
408      * @return a string representation of the ordered set
409      */
410     // Fortunately List.toString and Set.toString look the same
411     @Override
412     public String toString() {
413         return setOrder.toString();
414     }
415 
416 }