1 /* 2 * Copyright (c) 2009-2011 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.util; 34 35 import java.util.*; 36 37 /** 38 * <p>Provides a list with similar modification semantics to java.util.concurrent's 39 * CopyOnWriteArrayList except that it is not concurrent and also provides 40 * direct access to the current array. This List allows modification of the 41 * contents while iterating as any iterators will be looking at a snapshot of 42 * the list at the time they were created. Similarly, access the raw internal 43 * array is only presenting a snap shot and so can be safely iterated while 44 * the list is changing.</p> 45 * 46 * <p>All modifications, including set() operations will cause a copy of the 47 * data to be created that replaces the old version. Because this list is 48 * not designed for threading concurrency it further optimizes the "many modifications" 49 * case by buffering them as a normal ArrayList until the next time the contents 50 * are accessed.</p> 51 * 52 * <p>Normal list modification performance should be equal to ArrayList in a 53 * many situations and always better than CopyOnWriteArrayList. Optimum usage 54 * is when modifications are done infrequently or in batches... as is often the 55 * case in a scene graph. Read operations perform superior to all other methods 56 * as the array can be accessed directly.</p> 57 * 58 * <p>Important caveats over normal java.util.Lists:</p> 59 * <ul> 60 * <li>Even though this class supports modifying the list, the subList() method 61 * returns a read-only list. This technically breaks the List contract.</li> 62 * <li>The ListIterators returned by this class only support the remove() 63 * modification method. add() and set() are not supported on the iterator. 64 * Even after ListIterator.remove() or Iterator.remove() is called, this change 65 * is not reflected in the iterator instance as it is still refering to its 66 * original snapshot. 67 * </ul> 68 * 69 * @version $Revision: 8940 $ 70 * @author Paul Speed 71 */ 72 public class SafeArrayList<E> implements List<E> { 73 74 // Implementing List directly to avoid accidentally acquiring 75 // incorrect or non-optimal behavior from AbstractList. For 76 // example, the default iterator() method will not work for 77 // this list. 78 79 // Note: given the particular use-cases this was intended, 80 // it would make sense to nerf the public mutators and 81 // make this publicly act like a read-only list. 82 // SafeArrayList-specific methods could then be exposed 83 // for the classes like Node and Spatial to use to manage 84 // the list. This was the callers couldn't remove a child 85 // without it being detached properly, for example. 86 87 private Class<E> elementType; 88 private List<E> buffer; 89 private E[] backingArray; 90 private int size = 0; 91 92 public SafeArrayList(Class<E> elementType) { 93 this.elementType = elementType; 94 } 95 96 public SafeArrayList(Class<E> elementType, Collection<? extends E> c) { 97 this.elementType = elementType; 98 addAll(c); 99 } 100 101 protected final <T> T[] createArray(Class<T> type, int size) { 102 return (T[])java.lang.reflect.Array.newInstance(type, size); 103 } 104 105 protected final E[] createArray(int size) { 106 return createArray(elementType, size); 107 } 108 109 /** 110 * Returns a current snapshot of this List's backing array that 111 * is guaranteed not to change through further List manipulation. 112 * Changes to this array may or may not be reflected in the list and 113 * should be avoided. 114 */ 115 public final E[] getArray() { 116 if( backingArray != null ) 117 return backingArray; 118 119 if( buffer == null ) { 120 backingArray = createArray(0); 121 } else { 122 // Only keep the array or the buffer but never both at 123 // the same time. 1) it saves space, 2) it keeps the rest 124 // of the code safer. 125 backingArray = buffer.toArray( createArray(buffer.size()) ); 126 buffer = null; 127 } 128 return backingArray; 129 } 130 131 protected final List<E> getBuffer() { 132 if( buffer != null ) 133 return buffer; 134 135 if( backingArray == null ) { 136 buffer = new ArrayList(); 137 } else { 138 // Only keep the array or the buffer but never both at 139 // the same time. 1) it saves space, 2) it keeps the rest 140 // of the code safer. 141 buffer = new ArrayList( Arrays.asList(backingArray) ); 142 backingArray = null; 143 } 144 return buffer; 145 } 146 147 public final int size() { 148 return size; 149 } 150 151 public final boolean isEmpty() { 152 return size == 0; 153 } 154 155 public boolean contains(Object o) { 156 return indexOf(o) >= 0; 157 } 158 159 public Iterator<E> iterator() { 160 return listIterator(); 161 } 162 163 public Object[] toArray() { 164 return getArray(); 165 } 166 167 public <T> T[] toArray(T[] a) { 168 169 E[] array = getArray(); 170 if (a.length < array.length) { 171 return (T[])Arrays.copyOf(array, array.length, a.getClass()); 172 } 173 174 System.arraycopy( array, 0, a, 0, array.length ); 175 176 if (a.length > array.length) { 177 a[array.length] = null; 178 } 179 180 return a; 181 } 182 183 public boolean add(E e) { 184 boolean result = getBuffer().add(e); 185 size = getBuffer().size(); 186 return result; 187 } 188 189 public boolean remove(Object o) { 190 boolean result = getBuffer().remove(o); 191 size = getBuffer().size(); 192 return result; 193 } 194 195 public boolean containsAll(Collection<?> c) { 196 return Arrays.asList(getArray()).containsAll(c); 197 } 198 199 public boolean addAll(Collection<? extends E> c) { 200 boolean result = getBuffer().addAll(c); 201 size = getBuffer().size(); 202 return result; 203 } 204 205 public boolean addAll(int index, Collection<? extends E> c) { 206 boolean result = getBuffer().addAll(index, c); 207 size = getBuffer().size(); 208 return result; 209 } 210 211 public boolean removeAll(Collection<?> c) { 212 boolean result = getBuffer().removeAll(c); 213 size = getBuffer().size(); 214 return result; 215 } 216 217 public boolean retainAll(Collection<?> c) { 218 boolean result = getBuffer().retainAll(c); 219 size = getBuffer().size(); 220 return result; 221 } 222 223 public void clear() { 224 getBuffer().clear(); 225 size = 0; 226 } 227 228 public boolean equals(Object o) { 229 if( o == this ) 230 return true; 231 if( !(o instanceof List) ) //covers null too 232 return false; 233 List other = (List)o; 234 Iterator i1 = iterator(); 235 Iterator i2 = other.iterator(); 236 while( i1.hasNext() && i2.hasNext() ) { 237 Object o1 = i1.next(); 238 Object o2 = i2.next(); 239 if( o1 == o2 ) 240 continue; 241 if( o1 == null || !o1.equals(o2) ) 242 return false; 243 } 244 return !(i1.hasNext() || !i2.hasNext()); 245 } 246 247 public int hashCode() { 248 // Exactly the hash code described in the List interface, basically 249 E[] array = getArray(); 250 int result = 1; 251 for( E e : array ) { 252 result = 31 * result + (e == null ? 0 : e.hashCode()); 253 } 254 return result; 255 } 256 257 public final E get(int index) { 258 if( backingArray != null ) 259 return backingArray[index]; 260 if( buffer != null ) 261 return buffer.get(index); 262 throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" ); 263 } 264 265 public E set(int index, E element) { 266 return getBuffer().set(index, element); 267 } 268 269 public void add(int index, E element) { 270 getBuffer().add(index, element); 271 size = getBuffer().size(); 272 } 273 274 public E remove(int index) { 275 E result = getBuffer().remove(index); 276 size = getBuffer().size(); 277 return result; 278 } 279 280 public int indexOf(Object o) { 281 E[] array = getArray(); 282 for( int i = 0; i < array.length; i++ ) { 283 E element = array[i]; 284 if( element == o ) { 285 return i; 286 } 287 if( element != null && element.equals(o) ) { 288 return i; 289 } 290 } 291 return -1; 292 } 293 294 public int lastIndexOf(Object o) { 295 E[] array = getArray(); 296 for( int i = array.length - 1; i >= 0; i-- ) { 297 E element = array[i]; 298 if( element == o ) { 299 return i; 300 } 301 if( element != null && element.equals(o) ) { 302 return i; 303 } 304 } 305 return -1; 306 } 307 308 public ListIterator<E> listIterator() { 309 return new ArrayIterator<E>(getArray(), 0); 310 } 311 312 public ListIterator<E> listIterator(int index) { 313 return new ArrayIterator<E>(getArray(), index); 314 } 315 316 public List<E> subList(int fromIndex, int toIndex) { 317 318 // So far JME doesn't use subList that I can see so I'm nerfing it. 319 List<E> raw = Arrays.asList(getArray()).subList(fromIndex, toIndex); 320 return Collections.unmodifiableList(raw); 321 } 322 323 public String toString() { 324 325 E[] array = getArray(); 326 if( array.length == 0 ) { 327 return "[]"; 328 } 329 330 StringBuilder sb = new StringBuilder(); 331 sb.append('['); 332 for( int i = 0; i < array.length; i++ ) { 333 if( i > 0 ) 334 sb.append( ", " ); 335 E e = array[i]; 336 sb.append( e == this ? "(this Collection)" : e ); 337 } 338 sb.append(']'); 339 return sb.toString(); 340 } 341 342 protected class ArrayIterator<E> implements ListIterator<E> { 343 private E[] array; 344 private int next; 345 private int lastReturned; 346 347 protected ArrayIterator( E[] array, int index ) { 348 this.array = array; 349 this.next = index; 350 this.lastReturned = -1; 351 } 352 353 public boolean hasNext() { 354 return next != array.length; 355 } 356 357 public E next() { 358 if( !hasNext() ) 359 throw new NoSuchElementException(); 360 lastReturned = next++; 361 return array[lastReturned]; 362 } 363 364 public boolean hasPrevious() { 365 return next != 0; 366 } 367 368 public E previous() { 369 if( !hasPrevious() ) 370 throw new NoSuchElementException(); 371 lastReturned = --next; 372 return array[lastReturned]; 373 } 374 375 public int nextIndex() { 376 return next; 377 } 378 379 public int previousIndex() { 380 return next - 1; 381 } 382 383 public void remove() { 384 // This operation is not so easy to do but we will fake it. 385 // The issue is that the backing list could be completely 386 // different than the one this iterator is a snapshot of. 387 // We'll just remove(element) which in most cases will be 388 // correct. If the list had earlier .equals() equivalent 389 // elements then we'll remove one of those instead. Either 390 // way, none of those changes are reflected in this iterator. 391 SafeArrayList.this.remove( array[lastReturned] ); 392 } 393 394 public void set(E e) { 395 throw new UnsupportedOperationException(); 396 } 397 398 public void add(E e) { 399 throw new UnsupportedOperationException(); 400 } 401 } 402 } 403