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.Iterator; 023import java.util.List; 024import java.util.ListIterator; 025import java.util.Set; 026 027import org.apache.commons.collections4.OrderedIterator; 028import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; 029import org.apache.commons.collections4.list.UnmodifiableList; 030 031/** 032 * Decorates another <code>Set</code> to ensure that the order of addition is 033 * retained and used by the iterator. 034 * <p> 035 * If an object is added to the set for a second time, it will remain in the 036 * original position in the iteration. The order can be observed from the set 037 * via the iterator or toArray methods. 038 * <p> 039 * The ListOrderedSet also has various useful direct methods. These include many 040 * from <code>List</code>, such as <code>get(int)</code>, 041 * <code>remove(int)</code> and <code>indexOf(int)</code>. An unmodifiable 042 * <code>List</code> view of the set can be obtained via <code>asList()</code>. 043 * <p> 044 * This class cannot implement the <code>List</code> interface directly as 045 * various interface methods (notably equals/hashCode) are incompatible with a 046 * set. 047 * <p> 048 * This class is Serializable from Commons Collections 3.1. 049 * 050 * @since 3.0 051 * @version $Id: ListOrderedSet.html 972421 2015-11-14 20:00:04Z tn $ 052 */ 053public class ListOrderedSet<E> 054 extends AbstractSerializableSetDecorator<E> { 055 056 /** Serialization version */ 057 private static final long serialVersionUID = -228664372470420141L; 058 059 /** Internal list to hold the sequence of objects */ 060 private final List<E> setOrder; 061 062 /** 063 * Factory method to create an ordered set specifying the list and set to 064 * use. 065 * <p> 066 * The list and set must both be empty. 067 * 068 * @param <E> the element type 069 * @param set the set to decorate, must be empty and not null 070 * @param list the list to decorate, must be empty and not null 071 * @return a new ordered set 072 * @throws IllegalArgumentException if set or list is null 073 * @throws IllegalArgumentException if either the set or list is not empty 074 * @since 4.0 075 */ 076 public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set, final List<E> list) { 077 if (set == null) { 078 throw new IllegalArgumentException("Set must not be null"); 079 } 080 if (list == null) { 081 throw new IllegalArgumentException("List must not be null"); 082 } 083 if (set.size() > 0 || list.size() > 0) { 084 throw new IllegalArgumentException("Set and List must be empty"); 085 } 086 return new ListOrderedSet<E>(set, list); 087 } 088 089 /** 090 * Factory method to create an ordered set. 091 * <p> 092 * An <code>ArrayList</code> is used to retain order. 093 * 094 * @param <E> the element type 095 * @param set the set to decorate, must not be null 096 * @return a new ordered set 097 * @throws IllegalArgumentException if set is null 098 * @since 4.0 099 */ 100 public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set) { 101 return new ListOrderedSet<E>(set); 102 } 103 104 /** 105 * Factory method to create an ordered set using the supplied list to retain 106 * order. 107 * <p> 108 * A <code>HashSet</code> is used for the set behaviour. 109 * <p> 110 * NOTE: If the list contains duplicates, the duplicates are removed, 111 * altering the specified list. 112 * 113 * @param <E> the element type 114 * @param list the list to decorate, must not be null 115 * @return a new ordered set 116 * @throws IllegalArgumentException if list is null 117 * @since 4.0 118 */ 119 public static <E> ListOrderedSet<E> listOrderedSet(final List<E> list) { 120 if (list == null) { 121 throw new IllegalArgumentException("List must not be null"); 122 } 123 final Set<E> set = new HashSet<E>(list); 124 list.retainAll(set); 125 126 return new ListOrderedSet<E>(set, list); 127 } 128 129 // ----------------------------------------------------------------------- 130 /** 131 * Constructs a new empty <code>ListOrderedSet</code> using a 132 * <code>HashSet</code> and an <code>ArrayList</code> internally. 133 * 134 * @since 3.1 135 */ 136 public ListOrderedSet() { 137 super(new HashSet<E>()); 138 setOrder = new ArrayList<E>(); 139 } 140 141 /** 142 * Constructor that wraps (not copies). 143 * 144 * @param set the set to decorate, must not be null 145 * @throws IllegalArgumentException if set is null 146 */ 147 protected ListOrderedSet(final Set<E> set) { 148 super(set); 149 setOrder = new ArrayList<E>(set); 150 } 151 152 /** 153 * Constructor that wraps (not copies) the Set and specifies the list to 154 * use. 155 * <p> 156 * The set and list must both be correctly initialised to the same elements. 157 * 158 * @param set the set to decorate, must not be null 159 * @param list the list to decorate, must not be null 160 * @throws IllegalArgumentException if set or list is null 161 */ 162 protected ListOrderedSet(final Set<E> set, final List<E> list) { 163 super(set); 164 if (list == null) { 165 throw new IllegalArgumentException("List must not be null"); 166 } 167 setOrder = list; 168 } 169 170 // ----------------------------------------------------------------------- 171 /** 172 * Gets an unmodifiable view of the order of the Set. 173 * 174 * @return an unmodifiable list view 175 */ 176 public List<E> asList() { 177 return UnmodifiableList.unmodifiableList(setOrder); 178 } 179 180 // ----------------------------------------------------------------------- 181 @Override 182 public void clear() { 183 decorated().clear(); 184 setOrder.clear(); 185 } 186 187 @Override 188 public OrderedIterator<E> iterator() { 189 return new OrderedSetIterator<E>(setOrder.listIterator(), decorated()); 190 } 191 192 @Override 193 public boolean add(final E object) { 194 if (decorated().add(object)) { 195 setOrder.add(object); 196 return true; 197 } 198 return false; 199 } 200 201 @Override 202 public boolean addAll(final Collection<? extends E> coll) { 203 boolean result = false; 204 for (final E e : coll) { 205 result |= add(e); 206 } 207 return result; 208 } 209 210 @Override 211 public boolean remove(final Object object) { 212 final boolean result = decorated().remove(object); 213 if (result) { 214 setOrder.remove(object); 215 } 216 return result; 217 } 218 219 @Override 220 public boolean removeAll(final Collection<?> coll) { 221 boolean result = false; 222 for (final Object name : coll) { 223 result |= remove(name); 224 } 225 return result; 226 } 227 228 @Override 229 public boolean retainAll(final Collection<?> coll) { 230 final Set<Object> collectionRetainAll = new HashSet<Object>(); 231 for (final Object next : coll) { 232 if (decorated().contains(next)) { 233 collectionRetainAll.add(next); 234 } 235 } 236 if (collectionRetainAll.size() == decorated().size()) { 237 return false; 238 } 239 if (collectionRetainAll.size() == 0) { 240 clear(); 241 } else { 242 for (final Iterator<E> it = iterator(); it.hasNext();) { 243 if (!collectionRetainAll.contains(it.next())) { 244 it.remove(); 245 } 246 } 247 } 248 return true; 249 } 250 251 @Override 252 public Object[] toArray() { 253 return setOrder.toArray(); 254 } 255 256 @Override 257 public <T> T[] toArray(final T a[]) { 258 return setOrder.toArray(a); 259 } 260 261 // ----------------------------------------------------------------------- 262 // Additional methods that comply to the {@link List} interface 263 // ----------------------------------------------------------------------- 264 265 /** 266 * Returns the element at the specified position in this ordered set. 267 * 268 * @param index the position of the element in the ordered {@link Set}. 269 * @return the element at position {@code index} 270 * @see List#get(int) 271 */ 272 public E get(final int index) { 273 return setOrder.get(index); 274 } 275 276 /** 277 * Returns the index of the first occurrence of the specified element in 278 * ordered set. 279 * 280 * @param object the element to search for 281 * @return the index of the first occurrence of the object, or {@code -1} if 282 * this ordered set does not contain this object 283 * @see List#indexOf(Object) 284 */ 285 public int indexOf(final Object object) { 286 return setOrder.indexOf(object); 287 } 288 289 /** 290 * Inserts the specified element at the specified position if it is not yet 291 * contained in this ordered set (optional operation). Shifts the element 292 * currently at this position and any subsequent elements to the right. 293 * 294 * @param index the index at which the element is to be inserted 295 * @param object the element to be inserted 296 * @see List#add(int, Object) 297 */ 298 public void add(final int index, final E object) { 299 if (!contains(object)) { 300 decorated().add(object); 301 setOrder.add(index, object); 302 } 303 } 304 305 /** 306 * Inserts all elements in the specified collection not yet contained in the 307 * ordered set at the specified position (optional operation). Shifts the 308 * element currently at the position and all subsequent elements to the 309 * right. 310 * 311 * @param index the position to insert the elements 312 * @param coll the collection containing the elements to be inserted 313 * @return {@code true} if this ordered set changed as a result of the call 314 * @see List#addAll(int, Collection) 315 */ 316 public boolean addAll(final int index, final Collection<? extends E> coll) { 317 boolean changed = false; 318 // collect all elements to be added for performance reasons 319 final List<E> toAdd = new ArrayList<E>(); 320 for (final E e : coll) { 321 if (contains(e)) { 322 continue; 323 } 324 decorated().add(e); 325 toAdd.add(e); 326 changed = true; 327 } 328 329 if (changed) { 330 setOrder.addAll(index, toAdd); 331 } 332 333 return changed; 334 } 335 336 /** 337 * Removes the element at the specified position from the ordered set. 338 * Shifts any subsequent elements to the left. 339 * 340 * @param index the index of the element to be removed 341 * @return the element that has been remove from the ordered set 342 * @see List#remove(int) 343 */ 344 public Object remove(final int index) { 345 final Object obj = setOrder.remove(index); 346 remove(obj); 347 return obj; 348 } 349 350 /** 351 * Uses the underlying List's toString so that order is achieved. This means 352 * that the decorated Set's toString is not used, so any custom toStrings 353 * will be ignored. 354 * 355 * @return a string representation of the ordered set 356 */ 357 // Fortunately List.toString and Set.toString look the same 358 @Override 359 public String toString() { 360 return setOrder.toString(); 361 } 362 363 // ----------------------------------------------------------------------- 364 /** 365 * Internal iterator handle remove. 366 */ 367 static class OrderedSetIterator<E> 368 extends AbstractIteratorDecorator<E> 369 implements OrderedIterator<E> { 370 371 /** Object we iterate on */ 372 private final Collection<E> set; 373 374 /** Last object retrieved */ 375 private E last; 376 377 private OrderedSetIterator(final ListIterator<E> iterator, final Collection<E> set) { 378 super(iterator); 379 this.set = set; 380 } 381 382 @Override 383 public E next() { 384 last = getIterator().next(); 385 return last; 386 } 387 388 @Override 389 public void remove() { 390 set.remove(last); 391 getIterator().remove(); 392 last = null; 393 } 394 395 public boolean hasPrevious() { 396 return ((ListIterator<E>) getIterator()).hasPrevious(); 397 } 398 399 public E previous() { 400 last = ((ListIterator<E>) getIterator()).previous(); 401 return last; 402 } 403 } 404 405}