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.list; 018 019import java.io.IOException; 020import java.io.InvalidObjectException; 021import java.io.ObjectInputStream; 022import java.lang.reflect.InvocationTargetException; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.List; 028import java.util.ListIterator; 029import java.util.Objects; 030import java.util.Set; 031import java.util.function.Predicate; 032 033import org.apache.commons.collections4.ListUtils; 034import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; 035import org.apache.commons.collections4.iterators.AbstractListIteratorDecorator; 036import org.apache.commons.collections4.set.ListOrderedSet; 037import org.apache.commons.collections4.set.UnmodifiableSet; 038 039/** 040 * Decorates a {@code List} to ensure that no duplicates are present much 041 * like a {@code Set}. 042 * <p> 043 * The {@code List} interface makes certain assumptions/requirements. This 044 * implementation breaks these in certain ways, but this is merely the result of 045 * rejecting duplicates. Each violation is explained in the method, but it 046 * should not affect you. Bear in mind that Sets require immutable objects to 047 * function correctly. 048 * </p> 049 * <p> 050 * The {@link ListOrderedSet ListOrderedSet} 051 * class provides an alternative approach, by wrapping an existing Set and 052 * retaining insertion order in the iterator. 053 * </p> 054 * <p> 055 * This class is Serializable from Commons Collections 3.1. 056 * </p> 057 * 058 * @param <E> The type of the elements in the list. 059 * @since 3.0 060 */ 061public class SetUniqueList<E> extends AbstractSerializableListDecorator<E> { 062 063 /** 064 * Inner class iterator. 065 */ 066 static class SetListIterator<E> extends AbstractIteratorDecorator<E> { 067 068 private final Set<E> set; 069 private E last; 070 071 protected SetListIterator(final Iterator<E> it, final Set<E> set) { 072 super(it); 073 this.set = set; 074 } 075 076 @Override 077 public E next() { 078 last = super.next(); 079 return last; 080 } 081 082 @Override 083 public void remove() { 084 super.remove(); 085 set.remove(last); 086 last = null; 087 } 088 } 089 090 /** 091 * Inner class iterator. 092 */ 093 static class SetListListIterator<E> extends 094 AbstractListIteratorDecorator<E> { 095 096 private final Set<E> set; 097 private E last; 098 099 protected SetListListIterator(final ListIterator<E> it, final Set<E> set) { 100 super(it); 101 this.set = set; 102 } 103 104 @Override 105 public void add(final E object) { 106 if (!set.contains(object)) { 107 super.add(object); 108 set.add(object); 109 } 110 } 111 112 @Override 113 public E next() { 114 last = super.next(); 115 return last; 116 } 117 118 @Override 119 public E previous() { 120 last = super.previous(); 121 return last; 122 } 123 124 @Override 125 public void remove() { 126 super.remove(); 127 set.remove(last); 128 last = null; 129 } 130 131 /** 132 * Always throws {@link UnsupportedOperationException}. 133 * 134 * @param object Ignored. 135 * @throws UnsupportedOperationException Always thrown. 136 */ 137 @Override 138 public void set(final E object) { 139 throw new UnsupportedOperationException("ListIterator does not support set"); 140 } 141 } 142 143 /** Serialization version. */ 144 private static final long serialVersionUID = 7196982186153478694L; 145 146 /** 147 * Factory method to create a SetList using the supplied list to retain order. 148 * <p> 149 * If the list contains duplicates, these are removed (first indexed one 150 * kept). A {@code HashSet} is used for the set behavior. 151 * 152 * @param <E> the element type 153 * @param list The list to decorate, must not be null 154 * @return A new {@link SetUniqueList} 155 * @throws NullPointerException if list is null 156 * @since 4.0 157 */ 158 public static <E> SetUniqueList<E> setUniqueList(final List<E> list) { 159 Objects.requireNonNull(list, "list"); 160 if (list.isEmpty()) { 161 return new SetUniqueList<>(list, new HashSet<>()); 162 } 163 final List<E> temp = new ArrayList<>(list); 164 list.clear(); 165 final SetUniqueList<E> sl = new SetUniqueList<>(list, new HashSet<>()); 166 sl.addAll(temp); 167 return sl; 168 } 169 170 /** Internal Set to maintain uniqueness. */ 171 private final Set<E> set; 172 173 /** 174 * Constructor that wraps (not copies) the List and specifies the set to use. 175 * <p> 176 * The set and list must both be correctly initialized to the same elements. 177 * 178 * @param set The set to decorate, must not be null 179 * @param list The list to decorate, must not be null 180 * @throws NullPointerException if set or list is null 181 */ 182 protected SetUniqueList(final List<E> list, final Set<E> set) { 183 super(list); 184 this.set = Objects.requireNonNull(set, "set"); 185 } 186 187 /** 188 * Adds an element to the list if it is not already present. 189 * <p> 190 * <em>(Violation)</em> The {@code List} interface requires that this 191 * method returns {@code true} always. However, this class may return 192 * {@code false} because of the {@code Set} behavior. 193 * 194 * @param object The object to add 195 * @return true if object was added 196 */ 197 @Override 198 public boolean add(final E object) { 199 // gets initial size 200 final int sizeBefore = size(); 201 202 // adds element if unique 203 add(size(), object); 204 205 // compares sizes to detect if collection changed 206 return sizeBefore != size(); 207 } 208 209 /** 210 * Adds an element to a specific index in the list if it is not already 211 * present. 212 * <p> 213 * <em>(Violation)</em> The {@code List} interface makes the assumption 214 * that the element is always inserted. This may not happen with this 215 * implementation. 216 * 217 * @param index The index to insert at 218 * @param object The object to add 219 */ 220 @Override 221 public void add(final int index, final E object) { 222 if (index < 0 || index > size()) { 223 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size()); 224 } 225 // adds element if it is not contained already 226 if (!set.contains(object)) { 227 set.add(object); 228 super.add(index, object); 229 } 230 } 231 232 /** 233 * Adds a collection of objects to the end of the list avoiding duplicates. 234 * <p> 235 * Only elements that are not already in this list will be added, and 236 * duplicates from the specified collection will be ignored. 237 * <p> 238 * <em>(Violation)</em> The {@code List} interface makes the assumption 239 * that the elements are always inserted. This may not happen with this 240 * implementation. 241 * 242 * @param coll The collection to add in iterator order 243 * @return true if this collection changed 244 */ 245 @Override 246 public boolean addAll(final Collection<? extends E> coll) { 247 return addAll(size(), coll); 248 } 249 250 /** 251 * Adds a collection of objects a specific index in the list avoiding 252 * duplicates. 253 * <p> 254 * Only elements that are not already in this list will be added, and 255 * duplicates from the specified collection will be ignored. 256 * <p> 257 * <em>(Violation)</em> The {@code List} interface makes the assumption 258 * that the elements are always inserted. This may not happen with this 259 * implementation. 260 * 261 * @param index The index to insert at 262 * @param coll The collection to add in iterator order 263 * @return true if this collection changed 264 */ 265 @Override 266 public boolean addAll(final int index, final Collection<? extends E> coll) { 267 if (index < 0 || index > size()) { 268 throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size()); 269 } 270 final List<E> temp = new ArrayList<>(); 271 for (final E e : coll) { 272 if (set.add(e)) { 273 temp.add(e); 274 } 275 } 276 return super.addAll(index, temp); 277 } 278 279 /** 280 * Gets an unmodifiable view as a Set. 281 * 282 * @return An unmodifiable set view 283 */ 284 public Set<E> asSet() { 285 return UnmodifiableSet.unmodifiableSet(set); 286 } 287 288 @Override 289 public void clear() { 290 super.clear(); 291 set.clear(); 292 } 293 294 @Override 295 public boolean contains(final Object object) { 296 return set.contains(object); 297 } 298 299 @Override 300 public boolean containsAll(final Collection<?> coll) { 301 return set.containsAll(coll); 302 } 303 304 /** 305 * Create a new {@link Set} with the same type as the provided {@code set} 306 * and populate it with all elements of {@code list}. 307 * 308 * @param set The {@link Set} to be used as return type, must not be null 309 * @param list The {@link List} to populate the {@link Set} 310 * @return A new {@link Set} populated with all elements of the provided 311 * {@link List} 312 */ 313 protected Set<E> createSetBasedOnList(final Set<E> set, final List<E> list) { 314 Set<E> subSet; 315 if (set.getClass().equals(HashSet.class)) { 316 subSet = new HashSet<>(list.size()); 317 } else { 318 try { 319 subSet = set.getClass().getDeclaredConstructor(set.getClass()).newInstance(set); 320 } catch (final InstantiationException 321 | IllegalAccessException 322 | InvocationTargetException 323 | NoSuchMethodException ie) { 324 subSet = new HashSet<>(); 325 } 326 } 327 subSet.addAll(list); 328 return subSet; 329 } 330 331 @Override 332 public Iterator<E> iterator() { 333 return new SetListIterator<>(super.iterator(), set); 334 } 335 336 @Override 337 public ListIterator<E> listIterator() { 338 return new SetListListIterator<>(super.listIterator(), set); 339 } 340 341 @Override 342 public ListIterator<E> listIterator(final int index) { 343 return new SetListListIterator<>(super.listIterator(index), set); 344 } 345 346 /** 347 * Deserializes the list and re-checks the no-duplicate invariant the 348 * constructors guarantee. 349 * 350 * @param in The input stream 351 * @throws IOException Thrown if an error occurs while reading from the stream 352 * @throws ClassNotFoundException if a class read from the stream cannot be loaded 353 */ 354 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 355 in.defaultReadObject(); 356 if (set.size() != size() || !new HashSet<>(decorated()).equals(set)) { 357 throw new InvalidObjectException("Inconsistent SetUniqueList deserialized: backing list does not match the uniqueness set"); 358 } 359 } 360 361 @Override 362 public E remove(final int index) { 363 final E result = super.remove(index); 364 set.remove(result); 365 return result; 366 } 367 368 @Override 369 public boolean remove(final Object object) { 370 final boolean result = set.remove(object); 371 if (result) { 372 super.remove(object); 373 } 374 return result; 375 } 376 377 @Override 378 public boolean removeAll(final Collection<?> coll) { 379 boolean result = false; 380 for (final Object name : coll) { 381 result |= remove(name); 382 } 383 return result; 384 } 385 386 /** 387 * @since 4.4 388 */ 389 @Override 390 public boolean removeIf(final Predicate<? super E> filter) { 391 final boolean result = super.removeIf(filter); 392 set.removeIf(filter); 393 return result; 394 } 395 396 /** 397 * {@inheritDoc} 398 * <p> 399 * This implementation iterates over the elements of this list, checking 400 * each element in turn to see if it's contained in {@code coll}. 401 * If it's not contained, it's removed from this list. As a consequence, 402 * it is advised to use a collection type for {@code coll} that provides 403 * a fast (for example O(1)) implementation of {@link Collection#contains(Object)}. 404 */ 405 @Override 406 public boolean retainAll(final Collection<?> coll) { 407 final boolean result = set.retainAll(coll); 408 if (!result) { 409 return false; 410 } 411 if (set.isEmpty()) { 412 super.clear(); 413 } else { 414 // use the set as parameter for the call to retainAll to improve performance 415 super.retainAll(set); 416 } 417 return result; 418 } 419 420 /** 421 * Sets the value at the specified index avoiding duplicates. 422 * <p> 423 * The object is set into the specified index. Afterwards, any previous 424 * duplicate is removed. If the object is not already in the list then a 425 * normal set occurs. If it is present, then the old version is removed. 426 * 427 * @param index The index to insert at 428 * @param object The object to set 429 * @return The previous object 430 */ 431 @Override 432 public E set(final int index, final E object) { 433 final int pos = indexOf(object); 434 final E removed = super.set(index, object); 435 436 if (pos != -1 && pos != index) { 437 // the object is already in the unique list 438 // (and it hasn't been swapped with itself) 439 super.remove(pos); // remove the duplicate by index 440 } 441 442 set.remove(removed); // remove the item deleted by the set 443 set.add(object); // add the new item to the unique set 444 445 return removed; // return the item deleted by the set 446 } 447 448 /** 449 * {@inheritDoc} 450 * <p> 451 * NOTE: from 4.0, an unmodifiable list will be returned, as changes to the 452 * subList can invalidate the parent list. 453 */ 454 @Override 455 public List<E> subList(final int fromIndex, final int toIndex) { 456 final List<E> superSubList = super.subList(fromIndex, toIndex); 457 final Set<E> subSet = createSetBasedOnList(set, superSubList); 458 return ListUtils.unmodifiableList(new SetUniqueList<>(superSubList, subSet)); 459 } 460 461}