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.CollectionUtils; 028import org.apache.commons.collections4.OrderedIterator; 029import org.apache.commons.collections4.functors.UniquePredicate; 030import org.apache.commons.collections4.iterators.AbstractIteratorDecorator; 031import org.apache.commons.collections4.list.UnmodifiableList; 032 033/** 034 * Decorates another <code>Set</code> to ensure that the order of addition is 035 * retained and used by the iterator. 036 * <p> 037 * If an object is added to the set for a second time, it will remain in the 038 * original position in the iteration. The order can be observed from the set 039 * via the iterator or toArray methods. 040 * <p> 041 * The ListOrderedSet also has various useful direct methods. These include many 042 * from <code>List</code>, such as <code>get(int)</code>, 043 * <code>remove(int)</code> and <code>indexOf(int)</code>. An unmodifiable 044 * <code>List</code> view of the set can be obtained via <code>asList()</code>. 045 * <p> 046 * This class cannot implement the <code>List</code> interface directly as 047 * various interface methods (notably equals/hashCode) are incompatible with a 048 * set. 049 * <p> 050 * This class is Serializable from Commons Collections 3.1. 051 * 052 * @param <E> the type of the elements in this set 053 * @since 3.0 054 */ 055public class ListOrderedSet<E> 056 extends AbstractSerializableSetDecorator<E> { 057 058 /** Serialization version */ 059 private static final long serialVersionUID = -228664372470420141L; 060 061 /** Internal list to hold the sequence of objects */ 062 private final List<E> setOrder; 063 064 /** 065 * Factory method to create an ordered set specifying the list and set to use. 066 * <p> 067 * The list and set must both be empty. 068 * 069 * @param <E> the element type 070 * @param set the set to decorate, must be empty and not null 071 * @param list the list to decorate, must be empty and not null 072 * @return a new ordered set 073 * @throws NullPointerException if set or list is null 074 * @throws IllegalArgumentException if either the set or list is not empty 075 * @since 4.0 076 */ 077 public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set, final List<E> list) { 078 if (set == null) { 079 throw new NullPointerException("Set must not be null"); 080 } 081 if (list == null) { 082 throw new NullPointerException("List must not be null"); 083 } 084 if (set.size() > 0 || list.size() > 0) { 085 throw new IllegalArgumentException("Set and List must be empty"); 086 } 087 return new ListOrderedSet<>(set, list); 088 } 089 090 /** 091 * Factory method to create an ordered set. 092 * <p> 093 * An <code>ArrayList</code> is used to retain order. 094 * 095 * @param <E> the element type 096 * @param set the set to decorate, must not be null 097 * @return a new ordered set 098 * @throws NullPointerException if set is null 099 * @since 4.0 100 */ 101 public static <E> ListOrderedSet<E> listOrderedSet(final Set<E> set) { 102 return new ListOrderedSet<>(set); 103 } 104 105 /** 106 * Factory method to create an ordered set using the supplied list to retain 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 NullPointerException 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 NullPointerException("List must not be null"); 122 } 123 CollectionUtils.filter(list, UniquePredicate.uniquePredicate()); 124 final Set<E> set = new HashSet<>(list); 125 126 return new ListOrderedSet<>(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<>(); 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<>(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 NullPointerException 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 NullPointerException("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<>(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 /** 229 * {@inheritDoc} 230 * <p> 231 * This implementation iterates over the elements of this set, checking 232 * each element in turn to see if it's contained in <code>coll</code>. 233 * If it's not contained, it's removed from this set. As a consequence, 234 * it is advised to use a collection type for <code>coll</code> that provides 235 * a fast (e.g. O(1)) implementation of {@link Collection#contains(Object)}. 236 */ 237 @Override 238 public boolean retainAll(final Collection<?> coll) { 239 final boolean result = decorated().retainAll(coll); 240 if (result == false) { 241 return false; 242 } 243 if (decorated().size() == 0) { 244 setOrder.clear(); 245 } else { 246 for (final Iterator<E> it = setOrder.iterator(); it.hasNext();) { 247 if (!decorated().contains(it.next())) { 248 it.remove(); 249 } 250 } 251 } 252 return result; 253 } 254 255 @Override 256 public Object[] toArray() { 257 return setOrder.toArray(); 258 } 259 260 @Override 261 public <T> T[] toArray(final T a[]) { 262 return setOrder.toArray(a); 263 } 264 265 // ----------------------------------------------------------------------- 266 // Additional methods that comply to the {@link List} interface 267 // ----------------------------------------------------------------------- 268 269 /** 270 * Returns the element at the specified position in this ordered set. 271 * 272 * @param index the position of the element in the ordered {@link Set}. 273 * @return the element at position {@code index} 274 * @see List#get(int) 275 */ 276 public E get(final int index) { 277 return setOrder.get(index); 278 } 279 280 /** 281 * Returns the index of the first occurrence of the specified element in 282 * ordered set. 283 * 284 * @param object the element to search for 285 * @return the index of the first occurrence of the object, or {@code -1} if 286 * this ordered set does not contain this object 287 * @see List#indexOf(Object) 288 */ 289 public int indexOf(final Object object) { 290 return setOrder.indexOf(object); 291 } 292 293 /** 294 * Inserts the specified element at the specified position if it is not yet 295 * contained in this ordered set (optional operation). Shifts the element 296 * currently at this position and any subsequent elements to the right. 297 * 298 * @param index the index at which the element is to be inserted 299 * @param object the element to be inserted 300 * @see List#add(int, Object) 301 */ 302 public void add(final int index, final E object) { 303 if (!contains(object)) { 304 decorated().add(object); 305 setOrder.add(index, object); 306 } 307 } 308 309 /** 310 * Inserts all elements in the specified collection not yet contained in the 311 * ordered set at the specified position (optional operation). Shifts the 312 * element currently at the position and all subsequent elements to the 313 * right. 314 * 315 * @param index the position to insert the elements 316 * @param coll the collection containing the elements to be inserted 317 * @return {@code true} if this ordered set changed as a result of the call 318 * @see List#addAll(int, Collection) 319 */ 320 public boolean addAll(final int index, final Collection<? extends E> coll) { 321 boolean changed = false; 322 // collect all elements to be added for performance reasons 323 final List<E> toAdd = new ArrayList<>(); 324 for (final E e : coll) { 325 if (contains(e)) { 326 continue; 327 } 328 decorated().add(e); 329 toAdd.add(e); 330 changed = true; 331 } 332 333 if (changed) { 334 setOrder.addAll(index, toAdd); 335 } 336 337 return changed; 338 } 339 340 /** 341 * Removes the element at the specified position from the ordered set. 342 * Shifts any subsequent elements to the left. 343 * 344 * @param index the index of the element to be removed 345 * @return the element that has been remove from the ordered set 346 * @see List#remove(int) 347 */ 348 public E remove(final int index) { 349 final E obj = setOrder.remove(index); 350 remove(obj); 351 return obj; 352 } 353 354 /** 355 * Uses the underlying List's toString so that order is achieved. This means 356 * that the decorated Set's toString is not used, so any custom toStrings 357 * will be ignored. 358 * 359 * @return a string representation of the ordered set 360 */ 361 // Fortunately List.toString and Set.toString look the same 362 @Override 363 public String toString() { 364 return setOrder.toString(); 365 } 366 367 // ----------------------------------------------------------------------- 368 /** 369 * Internal iterator handle remove. 370 */ 371 static class OrderedSetIterator<E> 372 extends AbstractIteratorDecorator<E> 373 implements OrderedIterator<E> { 374 375 /** Object we iterate on */ 376 private final Collection<E> set; 377 378 /** Last object retrieved */ 379 private E last; 380 381 private OrderedSetIterator(final ListIterator<E> iterator, final Collection<E> set) { 382 super(iterator); 383 this.set = set; 384 } 385 386 @Override 387 public E next() { 388 last = getIterator().next(); 389 return last; 390 } 391 392 @Override 393 public void remove() { 394 set.remove(last); 395 getIterator().remove(); 396 last = null; 397 } 398 399 @Override 400 public boolean hasPrevious() { 401 return ((ListIterator<E>) getIterator()).hasPrevious(); 402 } 403 404 @Override 405 public E previous() { 406 last = ((ListIterator<E>) getIterator()).previous(); 407 return last; 408 } 409 } 410 411}