001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.collections4.set;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashSet;
022import java.util.List;
023import java.util.ListIterator;
024import java.util.Objects;
025import java.util.Set;
026import java.util.function.Predicate;
027
028import org.apache.commons.collections4.CollectionUtils;
029import org.apache.commons.collections4.OrderedIterator;
030import org.apache.commons.collections4.functors.UniquePredicate;
031import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
032import org.apache.commons.collections4.list.UnmodifiableList;
033
034/**
035 * Decorates another {@code Set} to ensure that the order of addition is
036 * retained and used by the iterator.
037 * <p>
038 * If an object is added to the set for a second time, it will remain in the
039 * original position in the iteration. The order can be observed from the set
040 * via the iterator or toArray methods.
041 * </p>
042 * <p>
043 * The ListOrderedSet also has various useful direct methods. These include many
044 * from {@code List}, such as {@code get(int)},
045 * {@code remove(int)} and {@code indexOf(int)}. An unmodifiable
046 * {@code List} view of the set can be obtained via {@code asList()}.
047 * </p>
048 * <p>
049 * This class cannot implement the {@code List} interface directly as
050 * various interface methods (notably equals/hashCode) are incompatible with a
051 * set.
052 * </p>
053 * <p>
054 * This class is Serializable from Commons Collections 3.1.
055 * </p>
056 *
057 * @param <E> the type of the elements in this set
058 * @since 3.0
059 */
060public class ListOrderedSet<E>
061    extends AbstractSerializableSetDecorator<E> {
062
063    /**
064     * Internal iterator handle remove.
065     */
066    static class OrderedSetIterator<E>
067        extends AbstractIteratorDecorator<E>
068        implements OrderedIterator<E> {
069
070        /** Object we iterate on */
071        private final Collection<E> set;
072
073        /** Last object retrieved */
074        private E last;
075
076        private OrderedSetIterator(final ListIterator<E> iterator, final Collection<E> set) {
077            super(iterator);
078            this.set = set;
079        }
080
081        @Override
082        public boolean hasPrevious() {
083            return ((ListIterator<E>) getIterator()).hasPrevious();
084        }
085
086        @Override
087        public E next() {
088            last = getIterator().next();
089            return last;
090        }
091
092        @Override
093        public E previous() {
094            last = ((ListIterator<E>) getIterator()).previous();
095            return last;
096        }
097
098        @Override
099        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}