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}