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