ListOrderedSet.java

  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. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.HashSet;
  21. import java.util.List;
  22. import java.util.ListIterator;
  23. import java.util.Objects;
  24. import java.util.Set;
  25. import java.util.function.Predicate;

  26. import org.apache.commons.collections4.CollectionUtils;
  27. import org.apache.commons.collections4.OrderedIterator;
  28. import org.apache.commons.collections4.functors.UniquePredicate;
  29. import org.apache.commons.collections4.iterators.AbstractIteratorDecorator;
  30. import org.apache.commons.collections4.list.UnmodifiableList;

  31. /**
  32.  * Decorates another {@code Set} to ensure that the order of addition is
  33.  * retained and used by the iterator.
  34.  * <p>
  35.  * If an object is added to the set for a second time, it will remain in the
  36.  * original position in the iteration. The order can be observed from the set
  37.  * via the iterator or toArray methods.
  38.  * </p>
  39.  * <p>
  40.  * The ListOrderedSet also has various useful direct methods. These include many
  41.  * from {@code List}, such as {@code get(int)},
  42.  * {@code remove(int)} and {@code indexOf(int)}. An unmodifiable
  43.  * {@code List} view of the set can be obtained via {@code asList()}.
  44.  * </p>
  45.  * <p>
  46.  * This class cannot implement the {@code List} interface directly as
  47.  * various interface methods (notably equals/hashCode) are incompatible with a
  48.  * set.
  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 this set
  55.  * @since 3.0
  56.  */
  57. public class ListOrderedSet<E>
  58.     extends AbstractSerializableSetDecorator<E> {

  59.     /**
  60.      * Internal iterator handle remove.
  61.      */
  62.     static class OrderedSetIterator<E>
  63.         extends AbstractIteratorDecorator<E>
  64.         implements OrderedIterator<E> {

  65.         /** Object we iterate on */
  66.         private final Collection<E> set;

  67.         /** Last object retrieved */
  68.         private E last;

  69.         private OrderedSetIterator(final ListIterator<E> iterator, final Collection<E> set) {
  70.             super(iterator);
  71.             this.set = set;
  72.         }

  73.         @Override
  74.         public boolean hasPrevious() {
  75.             return ((ListIterator<E>) getIterator()).hasPrevious();
  76.         }

  77.         @Override
  78.         public E next() {
  79.             last = getIterator().next();
  80.             return last;
  81.         }

  82.         @Override
  83.         public E previous() {
  84.             last = ((ListIterator<E>) getIterator()).previous();
  85.             return last;
  86.         }

  87.         @Override
  88.         public void remove() {
  89.             set.remove(last);
  90.             getIterator().remove();
  91.             last = null;
  92.         }
  93.     }

  94.     /** Serialization version */
  95.     private static final long serialVersionUID = -228664372470420141L;

  96.     /**
  97.      * Factory method to create an ordered set using the supplied list to retain order.
  98.      * <p>
  99.      * A {@code HashSet} is used for the set behavior.
  100.      * </p>
  101.      * <p>
  102.      * NOTE: If the list contains duplicates, the duplicates are removed,
  103.      * altering the specified list.
  104.      * </p>
  105.      *
  106.      * @param <E> the element type
  107.      * @param list the list to decorate, must not be null
  108.      * @return a new ordered set
  109.      * @throws NullPointerException if list is null
  110.      * @since 4.0
  111.      */
  112.     public static <E> ListOrderedSet<E> listOrderedSet(final List<E> list) {
  113.         Objects.requireNonNull(list, "list");
  114.         CollectionUtils.filter(list, UniquePredicate.uniquePredicate());
  115.         final Set<E> set = new HashSet<>(list);

  116.         return new ListOrderedSet<>(set, list);
  117.     }

  118.     /**
  119.      * Factory method to create an ordered set.
  120.      * <p>
  121.      * An {@code ArrayList} is used to retain order.
  122.      * </p>
  123.      *
  124.      * @param <E> the element type
  125.      * @param set the set to decorate, must not be null
  126.      * @return a new ordered set
  127.      * @throws NullPointerException if set is null
  128.      * @since 4.0
  129.      */
  130.     public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set) {
  131.         return new ListOrderedSet<>(set);
  132.     }

  133.     /**
  134.      * Factory method to create an ordered set specifying the list and set to use.
  135.      * <p>
  136.      * The list and set must both be empty.
  137.      * </p>
  138.      *
  139.      * @param <E> the element type
  140.      * @param set the set to decorate, must be empty and not null
  141.      * @param list the list to decorate, must be empty and not null
  142.      * @return a new ordered set
  143.      * @throws NullPointerException if set or list is null
  144.      * @throws IllegalArgumentException if either the set or list is not empty
  145.      * @since 4.0
  146.      */
  147.     public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set, final List<E> list) {
  148.         Objects.requireNonNull(set, "set");
  149.         Objects.requireNonNull(list, "list");
  150.         if (!set.isEmpty() || !list.isEmpty()) {
  151.             throw new IllegalArgumentException("Set and List must be empty");
  152.         }
  153.         return new ListOrderedSet<>(set, list);
  154.     }

  155.     /** Internal list to hold the sequence of objects */
  156.     private final List<E> setOrder;

  157.     /**
  158.      * Constructs a new empty {@code ListOrderedSet} using a
  159.      * {@code HashSet} and an {@code ArrayList} internally.
  160.      *
  161.      * @since 3.1
  162.      */
  163.     public ListOrderedSet() {
  164.         super(new HashSet<>());
  165.         setOrder = new ArrayList<>();
  166.     }

  167.     /**
  168.      * Constructor that wraps (not copies).
  169.      *
  170.      * @param set the set to decorate, must not be null
  171.      * @throws NullPointerException if set is null
  172.      */
  173.     protected ListOrderedSet(final Set<E> set) {
  174.         super(set);
  175.         setOrder = new ArrayList<>(set);
  176.     }

  177.     /**
  178.      * Constructor that wraps (not copies) the Set and specifies the list to
  179.      * use.
  180.      * <p>
  181.      * The set and list must both be correctly initialized to the same elements.
  182.      * </p>
  183.      *
  184.      * @param set the set to decorate, must not be null
  185.      * @param list the list to decorate, must not be null
  186.      * @throws NullPointerException if set or list is null
  187.      */
  188.     protected ListOrderedSet(final Set<E> set, final List<E> list) {
  189.         super(set);
  190.         setOrder = Objects.requireNonNull(list, "list");
  191.     }

  192.     @Override
  193.     public boolean add(final E object) {
  194.         if (decorated().add(object)) {
  195.             setOrder.add(object);
  196.             return true;
  197.         }
  198.         return false;
  199.     }

  200.     /**
  201.      * Inserts the specified element at the specified position if it is not yet
  202.      * contained in this ordered set (optional operation). Shifts the element
  203.      * currently at this position and any subsequent elements to the right.
  204.      *
  205.      * @param index the index at which the element is to be inserted
  206.      * @param object the element to be inserted
  207.      * @see List#add(int, Object)
  208.      */
  209.     public void add(final int index, final E object) {
  210.         if (!contains(object)) {
  211.             decorated().add(object);
  212.             setOrder.add(index, object);
  213.         }
  214.     }

  215.     @Override
  216.     public boolean addAll(final Collection<? extends E> coll) {
  217.         boolean result = false;
  218.         for (final E e : coll) {
  219.             result |= add(e);
  220.         }
  221.         return result;
  222.     }

  223.     /**
  224.      * Inserts all elements in the specified collection not yet contained in the
  225.      * ordered set at the specified position (optional operation). Shifts the
  226.      * element currently at the position and all subsequent elements to the
  227.      * right.
  228.      *
  229.      * @param index the position to insert the elements
  230.      * @param coll the collection containing the elements to be inserted
  231.      * @return {@code true} if this ordered set changed as a result of the call
  232.      * @see List#addAll(int, Collection)
  233.      */
  234.     public boolean addAll(final int index, final Collection<? extends E> coll) {
  235.         boolean changed = false;
  236.         // collect all elements to be added for performance reasons
  237.         final List<E> toAdd = new ArrayList<>();
  238.         for (final E e : coll) {
  239.             if (contains(e)) {
  240.                 continue;
  241.             }
  242.             decorated().add(e);
  243.             toAdd.add(e);
  244.             changed = true;
  245.         }

  246.         if (changed) {
  247.             setOrder.addAll(index, toAdd);
  248.         }

  249.         return changed;
  250.     }

  251.     /**
  252.      * Gets an unmodifiable view of the order of the Set.
  253.      *
  254.      * @return an unmodifiable list view
  255.      */
  256.     public List<E> asList() {
  257.         return UnmodifiableList.unmodifiableList(setOrder);
  258.     }

  259.     @Override
  260.     public void clear() {
  261.         decorated().clear();
  262.         setOrder.clear();
  263.     }

  264.     /**
  265.      * Gets the element at the specified position in this ordered set.
  266.      *
  267.      * @param index the position of the element in the ordered {@link Set}.
  268.      * @return the element at position {@code index}
  269.      * @see List#get(int)
  270.      */
  271.     public E get(final int index) {
  272.         return setOrder.get(index);
  273.     }

  274.     /**
  275.      * Returns the index of the first occurrence of the specified element in
  276.      * ordered set.
  277.      *
  278.      * @param object the element to search for
  279.      * @return the index of the first occurrence of the object, or {@code -1} if
  280.      *         this ordered set does not contain this object
  281.      * @see List#indexOf(Object)
  282.      */
  283.     public int indexOf(final Object object) {
  284.         return setOrder.indexOf(object);
  285.     }

  286.     @Override
  287.     public OrderedIterator<E> iterator() {
  288.         return new OrderedSetIterator<>(setOrder.listIterator(), decorated());
  289.     }

  290.     /**
  291.      * Removes the element at the specified position from the ordered set.
  292.      * Shifts any subsequent elements to the left.
  293.      *
  294.      * @param index the index of the element to be removed
  295.      * @return the element that has been remove from the ordered set
  296.      * @see List#remove(int)
  297.      */
  298.     public E remove(final int index) {
  299.         final E obj = setOrder.remove(index);
  300.         remove(obj);
  301.         return obj;
  302.     }

  303.     @Override
  304.     public boolean remove(final Object object) {
  305.         final boolean result = decorated().remove(object);
  306.         if (result) {
  307.             setOrder.remove(object);
  308.         }
  309.         return result;
  310.     }

  311.     @Override
  312.     public boolean removeAll(final Collection<?> coll) {
  313.         boolean result = false;
  314.         for (final Object name : coll) {
  315.             result |= remove(name);
  316.         }
  317.         return result;
  318.     }

  319.     /**
  320.      * @since 4.4
  321.      */
  322.     @Override
  323.     public boolean removeIf(final Predicate<? super E> filter) {
  324.         if (Objects.isNull(filter)) {
  325.             return false;
  326.         }
  327.         final boolean result = decorated().removeIf(filter);
  328.         if (result) {
  329.             setOrder.removeIf(filter);
  330.         }
  331.         return result;
  332.     }

  333.     /**
  334.      * {@inheritDoc}
  335.      * <p>
  336.      * This implementation iterates over the elements of this set, checking
  337.      * each element in turn to see if it's contained in {@code coll}.
  338.      * If it's not contained, it's removed from this set. As a consequence,
  339.      * it is advised to use a collection type for {@code coll} that provides
  340.      * a fast (for example O(1)) implementation of {@link Collection#contains(Object)}.
  341.      * </p>
  342.      */
  343.     @Override
  344.     public boolean retainAll(final Collection<?> coll) {
  345.         final boolean result = decorated().retainAll(coll);
  346.         if (!result) {
  347.             return false;
  348.         }
  349.         if (decorated().isEmpty()) {
  350.             setOrder.clear();
  351.         } else {
  352.             setOrder.removeIf(e -> !decorated().contains(e));
  353.         }
  354.         return result;
  355.     }

  356.     @Override
  357.     public Object[] toArray() {
  358.         return setOrder.toArray();
  359.     }

  360.     @Override
  361.     public <T> T[] toArray(final T[] a) {
  362.         return setOrder.toArray(a);
  363.     }

  364.     /**
  365.      * Uses the underlying List's toString so that order is achieved. This means
  366.      * that the decorated Set's toString is not used, so any custom toStrings
  367.      * will be ignored.
  368.      *
  369.      * @return a string representation of the ordered set
  370.      */
  371.     // Fortunately List.toString and Set.toString look the same
  372.     @Override
  373.     public String toString() {
  374.         return setOrder.toString();
  375.     }

  376. }