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