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
33package com.jme3.util;
34
35import 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 */
72public 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