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 * https://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.io.IOException; 020import java.io.InvalidObjectException; 021import java.io.ObjectInputStream; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashSet; 025import java.util.List; 026import java.util.ListIterator; 027import java.util.Objects; 028import java.util.Set; 029import java.util.function.Predicate; 030 031import org.apache.commons.collections4.CollectionUtils; 032import org.apache.commons.collections4.OrderedIterator; 033import org.apache.commons.collections4.functors.UniquePredicate; 034import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; 035import org.apache.commons.collections4.list.UnmodifiableList; 036 037/** 038 * Decorates another {@code Set} to ensure that the order of addition is 039 * retained and used by the iterator. 040 * <p> 041 * If an object is added to the set for a second time, it will remain in the 042 * original position in the iteration. The order can be observed from the set 043 * via the iterator or toArray methods. 044 * </p> 045 * <p> 046 * The ListOrderedSet also has various useful direct methods. These include many 047 * from {@code List}, such as {@code get(int)}, 048 * {@code remove(int)} and {@code indexOf(int)}. An unmodifiable 049 * {@code List} view of the set can be obtained via {@code asList()}. 050 * </p> 051 * <p> 052 * This class cannot implement the {@code List} interface directly as 053 * various interface methods (notably equals/hashCode) are incompatible with a 054 * set. 055 * </p> 056 * <p> 057 * This class is Serializable from Commons Collections 3.1. 058 * </p> 059 * 060 * @param <E> The type of the elements in this set 061 * @since 3.0 062 */ 063public class ListOrderedSet<E> 064 extends AbstractSerializableSetDecorator<E> { 065 066 /** 067 * Internal iterator handle remove. 068 */ 069 static class OrderedSetIterator<E> 070 extends AbstractIteratorDecorator<E> 071 implements OrderedIterator<E> { 072 073 /** Object we iterate on */ 074 private final Collection<E> set; 075 076 /** Last object retrieved */ 077 private E last; 078 079 private OrderedSetIterator(final ListIterator<E> iterator, final Collection<E> set) { 080 super(iterator); 081 this.set = set; 082 } 083 084 @Override 085 public boolean hasPrevious() { 086 return ((ListIterator<E>) getIterator()).hasPrevious(); 087 } 088 089 @Override 090 public E next() { 091 last = getIterator().next(); 092 return last; 093 } 094 095 @Override 096 public E previous() { 097 last = ((ListIterator<E>) getIterator()).previous(); 098 return last; 099 } 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}