/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.commons.collections;

import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/**
 * <p>A customized implementation of <code>java.util.ArrayList</code> designed
 * to operate in a multithreaded environment where the large majority of
 * method calls are read-only, instead of structural changes.  When operating
 * in "fast" mode, read calls are non-synchronized and write calls perform the
 * following steps:</p>
 * <ul>
 * <li>Clone the existing collection
 * <li>Perform the modification on the clone
 * <li>Replace the existing collection with the (modified) clone
 * </ul>
 * <p>When first created, objects of this class default to "slow" mode, where
 * all accesses of any type are synchronized but no cloning takes place.  This
 * is appropriate for initially populating the collection, followed by a switch
 * to "fast" mode (by calling <code>setFast(true)</code>) after initialization
 * is complete.</p>
 *
 * <p><strong>NOTE</strong>: If you are creating and accessing an
 * <code>ArrayList</code> only within a single thread, you should use
 * <code>java.util.ArrayList</code> directly (with no synchronization), for
 * maximum performance.</p>
 *
 * <p><strong>NOTE</strong>: <i>This class is not cross-platform.
 * Using it may cause unexpected failures on some architectures.</i>
 * It suffers from the same problems as the double-checked locking idiom.  
 * In particular, the instruction that clones the internal collection and the 
 * instruction that sets the internal reference to the clone can be executed 
 * or perceived out-of-order.  This means that any read operation might fail 
 * unexpectedly, as it may be reading the state of the internal collection
 * before the internal collection is fully formed.
 * For more information on the double-checked locking idiom, see the
 * <a href="http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html">
 * Double-Checked Locking Idiom Is Broken Declaration</a>.</p>
 *
 * @since Commons Collections 1.0
 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
 * 
 * @author Craig R. McClanahan
 * @author Stephen Colebourne
 */
public class FastArrayList extends ArrayList {


    // ----------------------------------------------------------- Constructors


    /**
     * Construct a an empty list.
     */
    public FastArrayList() {

        super();
        this.list = new ArrayList();

    }


    /**
     * Construct an empty list with the specified capacity.
     *
     * @param capacity The initial capacity of the empty list
     */
    public FastArrayList(int capacity) {

        super();
        this.list = new ArrayList(capacity);

    }


    /**
     * Construct a list containing the elements of the specified collection,
     * in the order they are returned by the collection's iterator.
     *
     * @param collection The collection whose elements initialize the contents
     *  of this list
     */
    public FastArrayList(Collection collection) {

        super();
        this.list = new ArrayList(collection);

    }


    // ----------------------------------------------------- Instance Variables


    /**
     * The underlying list we are managing.
     */
    protected ArrayList list = null;


    // ------------------------------------------------------------- Properties


    /**
     * Are we operating in "fast" mode?
     */
    protected boolean fast = false;


    /**
     *  Returns true if this list is operating in fast mode.
     *
     *  @return true if this list is operating in fast mode
     */
    public boolean getFast() {
        return (this.fast);
    }

    /**
     *  Sets whether this list will operate in fast mode.
     *
     *  @param fast true if the list should operate in fast mode
     */
    public void setFast(boolean fast) {
        this.fast = fast;
    }


    // --------------------------------------------------------- Public Methods


    /**
     * Appends the specified element to the end of this list.
     *
     * @param element The element to be appended
     */
    public boolean add(Object element) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                boolean result = temp.add(element);
                list = temp;
                return (result);
            }
        } else {
            synchronized (list) {
                return (list.add(element));
            }
        }

    }


    /**
     * Insert the specified element at the specified position in this list,
     * and shift all remaining elements up one position.
     *
     * @param index Index at which to insert this element
     * @param element The element to be inserted
     *
     * @exception IndexOutOfBoundsException if the index is out of range
     */
    public void add(int index, Object element) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                temp.add(index, element);
                list = temp;
            }
        } else {
            synchronized (list) {
                list.add(index, element);
            }
        }

    }


    /**
     * Append all of the elements in the specified Collection to the end
     * of this list, in the order that they are returned by the specified
     * Collection's Iterator.
     *
     * @param collection The collection to be appended
     */
    public boolean addAll(Collection collection) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                boolean result = temp.addAll(collection);
                list = temp;
                return (result);
            }
        } else {
            synchronized (list) {
                return (list.addAll(collection));
            }
        }

    }


    /**
     * Insert all of the elements in the specified Collection at the specified
     * position in this list, and shift any previous elements upwards as
     * needed.
     *
     * @param index Index at which insertion takes place
     * @param collection The collection to be added
     *
     * @exception IndexOutOfBoundsException if the index is out of range
     */
    public boolean addAll(int index, Collection collection) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                boolean result = temp.addAll(index, collection);
                list = temp;
                return (result);
            }
        } else {
            synchronized (list) {
                return (list.addAll(index, collection));
            }
        }

    }


    /**
     * Remove all of the elements from this list.  The list will be empty
     * after this call returns.
     *
     * @exception UnsupportedOperationException if <code>clear()</code>
     *  is not supported by this list
     */
    public void clear() {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                temp.clear();
                list = temp;
            }
        } else {
            synchronized (list) {
                list.clear();
            }
        }

    }


    /**
     * Return a shallow copy of this <code>FastArrayList</code> instance.
     * The elements themselves are not copied.
     */
    public Object clone() {

        FastArrayList results = null;
        if (fast) {
            results = new FastArrayList(list);
        } else {
            synchronized (list) {
                results = new FastArrayList(list);
            }
        }
        results.setFast(getFast());
        return (results);

    }


    /**
     * Return <code>true</code> if this list contains the specified element.
     *
     * @param element The element to test for
     */
    public boolean contains(Object element) {

        if (fast) {
            return (list.contains(element));
        } else {
            synchronized (list) {
                return (list.contains(element));
            }
        }

    }


    /**
     * Return <code>true</code> if this list contains all of the elements
     * in the specified Collection.
     *
     * @param collection Collection whose elements are to be checked
     */
    public boolean containsAll(Collection collection) {

        if (fast) {
            return (list.containsAll(collection));
        } else {
            synchronized (list) {
                return (list.containsAll(collection));
            }
        }

    }


    /**
     * Increase the capacity of this <code>ArrayList</code> instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param capacity The new minimum capacity
     */
    public void ensureCapacity(int capacity) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                temp.ensureCapacity(capacity);
                list = temp;
            }
        } else {
            synchronized (list) {
                list.ensureCapacity(capacity);
            }
        }

    }


    /**
     * Compare the specified object with this list for equality.  This
     * implementation uses exactly the code that is used to define the
     * list equals function in the documentation for the
     * <code>List.equals</code> method.
     *
     * @param o Object to be compared to this list
     */
    public boolean equals(Object o) {

        // Simple tests that require no synchronization
        if (o == this)
            return (true);
        else if (!(o instanceof List))
            return (false);
        List lo = (List) o;

        // Compare the sets of elements for equality
        if (fast) {
            ListIterator li1 = list.listIterator();
            ListIterator li2 = lo.listIterator();
            while (li1.hasNext() && li2.hasNext()) {
                Object o1 = li1.next();
                Object o2 = li2.next();
                if (!(o1 == null ? o2 == null : o1.equals(o2)))
                    return (false);
            }
            return (!(li1.hasNext() || li2.hasNext()));
        } else {
            synchronized (list) {
                ListIterator li1 = list.listIterator();
                ListIterator li2 = lo.listIterator();
                while (li1.hasNext() && li2.hasNext()) {
                    Object o1 = li1.next();
                    Object o2 = li2.next();
                    if (!(o1 == null ? o2 == null : o1.equals(o2)))
                        return (false);
                }
                return (!(li1.hasNext() || li2.hasNext()));
            }
        }

    }


    /**
     * Return the element at the specified position in the list.
     *
     * @param index The index of the element to return
     *
     * @exception IndexOutOfBoundsException if the index is out of range
     */
    public Object get(int index) {

        if (fast) {
            return (list.get(index));
        } else {
            synchronized (list) {
                return (list.get(index));
            }
        }

    }


    /**
     * Return the hash code value for this list.  This implementation uses
     * exactly the code that is used to define the list hash function in the
     * documentation for the <code>List.hashCode</code> method.
     */
    public int hashCode() {

        if (fast) {
            int hashCode = 1;
            java.util.Iterator i = list.iterator();
            while (i.hasNext()) {
                Object o = i.next();
                hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode());
            }
            return (hashCode);
        } else {
            synchronized (list) {
                int hashCode = 1;
                java.util.Iterator i = list.iterator();
                while (i.hasNext()) {
                    Object o = i.next();
                    hashCode = 31 * hashCode + (o == null ? 0 : o.hashCode());
                }
                return (hashCode);
            }
        }

    }


    /**
     * Search for the first occurrence of the given argument, testing
     * for equality using the <code>equals()</code> method, and return
     * the corresponding index, or -1 if the object is not found.
     *
     * @param element The element to search for
     */
    public int indexOf(Object element) {

        if (fast) {
            return (list.indexOf(element));
        } else {
            synchronized (list) {
                return (list.indexOf(element));
            }
        }

    }


    /**
     * Test if this list has no elements.
     */
    public boolean isEmpty() {

        if (fast) {
            return (list.isEmpty());
        } else {
            synchronized (list) {
                return (list.isEmpty());
            }
        }

    }


    /**
     * Return an iterator over the elements in this list in proper sequence.
     * <p>
     * <b>Thread safety</b><br />
     * The iterator returned is thread-safe ONLY in FAST mode.
     * In slow mode there is no way to synchronize, or make the iterator thread-safe.
     * <p>
     * In fast mode iteration and modification may occur in parallel on different threads,
     * however there is a restriction. Modification must be EITHER via the Iterator
     * interface methods OR the List interface. If a mixture of modification
     * methods is used a ConcurrentModificationException is thrown from the iterator
     * modification method. If the List modification methods are used the changes are
     * NOT visible in the iterator (it shows the list contents at the time the iterator
     * was created).
     * 
     * @return the iterator
     */
    public Iterator iterator() {
        if (fast) {
            return new ListIter(0);
        } else {
            return list.iterator();
        }
    }


    /**
     * Search for the last occurrence of the given argument, testing
     * for equality using the <code>equals()</code> method, and return
     * the corresponding index, or -1 if the object is not found.
     *
     * @param element The element to search for
     */
    public int lastIndexOf(Object element) {

        if (fast) {
            return (list.lastIndexOf(element));
        } else {
            synchronized (list) {
                return (list.lastIndexOf(element));
            }
        }

    }


    /**
     * Return an iterator of the elements of this list, in proper sequence.
     * <p>
     * <b>Thread safety</b><br />
     * The iterator returned is thread-safe ONLY in FAST mode.
     * In slow mode there is no way to synchronize, or make the iterator thread-safe.
     * <p>
     * In fast mode iteration and modification may occur in parallel on different threads,
     * however there is a restriction. Modification must be EITHER via the Iterator
     * interface methods OR the List interface. If a mixture of modification
     * methods is used a ConcurrentModificationException is thrown from the iterator
     * modification method. If the List modification methods are used the changes are
     * NOT visible in the iterator (it shows the list contents at the time the iterator
     * was created).
     * 
     * @return the list iterator
     */
    public ListIterator listIterator() {
        if (fast) {
            return new ListIter(0);
        } else {
            return list.listIterator();
        }
    }


    /**
     * Return an iterator of the elements of this list, in proper sequence,
     * starting at the specified position.
     * <p>
     * <b>Thread safety</b><br />
     * The iterator returned is thread-safe ONLY in FAST mode.
     * In slow mode there is no way to synchronize, or make the iterator thread-safe.
     * <p>
     * In fast mode iteration and modification may occur in parallel on different threads,
     * however there is a restriction. Modification must be EITHER via the Iterator
     * interface methods OR the List interface. If a mixture of modification
     * methods is used a ConcurrentModificationException is thrown from the iterator
     * modification method. If the List modification methods are used the changes are
     * NOT visible in the iterator (it shows the list contents at the time the iterator
     * was created).
     *
     * @param index The starting position of the iterator to return
     * @return the list iterator
     * @exception IndexOutOfBoundsException if the index is out of range
     */
    public ListIterator listIterator(int index) {
        if (fast) {
            return new ListIter(index);
        } else {
            return list.listIterator(index);
        }
    }


    /**
     * Remove the element at the specified position in the list, and shift
     * any subsequent elements down one position.
     *
     * @param index Index of the element to be removed
     *
     * @exception IndexOutOfBoundsException if the index is out of range
     */
    public Object remove(int index) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                Object result = temp.remove(index);
                list = temp;
                return (result);
            }
        } else {
            synchronized (list) {
                return (list.remove(index));
            }
        }

    }


    /**
     * Remove the first occurrence of the specified element from the list,
     * and shift any subsequent elements down one position.
     *
     * @param element Element to be removed
     */
    public boolean remove(Object element) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                boolean result = temp.remove(element);
                list = temp;
                return (result);
            }
        } else {
            synchronized (list) {
                return (list.remove(element));
            }
        }

    }


    /**
     * Remove from this collection all of its elements that are contained
     * in the specified collection.
     *
     * @param collection Collection containing elements to be removed
     *
     * @exception UnsupportedOperationException if this optional operation
     *  is not supported by this list
     */
    public boolean removeAll(Collection collection) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                boolean result = temp.removeAll(collection);
                list = temp;
                return (result);
            }
        } else {
            synchronized (list) {
                return (list.removeAll(collection));
            }
        }

    }


    /**
     * Remove from this collection all of its elements except those that are
     * contained in the specified collection.
     *
     * @param collection Collection containing elements to be retained
     *
     * @exception UnsupportedOperationException if this optional operation
     *  is not supported by this list
     */
    public boolean retainAll(Collection collection) {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                boolean result = temp.retainAll(collection);
                list = temp;
                return (result);
            }
        } else {
            synchronized (list) {
                return (list.retainAll(collection));
            }
        }

    }


    /**
     * Replace the element at the specified position in this list with
     * the specified element.  Returns the previous object at that position.
     * <br><br>
     * <strong>IMPLEMENTATION NOTE</strong> - This operation is specifically
     * documented to not be a structural change, so it is safe to be performed
     * without cloning.
     *
     * @param index Index of the element to replace
     * @param element The new element to be stored
     *
     * @exception IndexOutOfBoundsException if the index is out of range
     */
    public Object set(int index, Object element) {

        if (fast) {
            return (list.set(index, element));
        } else {
            synchronized (list) {
                return (list.set(index, element));
            }
        }

    }


    /**
     * Return the number of elements in this list.
     */
    public int size() {

        if (fast) {
            return (list.size());
        } else {
            synchronized (list) {
                return (list.size());
            }
        }

    }


    /**
     * Return a view of the portion of this list between fromIndex
     * (inclusive) and toIndex (exclusive).  The returned list is backed
     * by this list, so non-structural changes in the returned list are
     * reflected in this list.  The returned list supports
     * all of the optional list operations supported by this list.
     *
     * @param fromIndex The starting index of the sublist view
     * @param toIndex The index after the end of the sublist view
     *
     * @exception IndexOutOfBoundsException if an index is out of range
     */
    public List subList(int fromIndex, int toIndex) {
        if (fast) {
            return new SubList(fromIndex, toIndex);
        } else {
            return list.subList(fromIndex, toIndex);
        }
    }


    /**
     * Return an array containing all of the elements in this list in the
     * correct order.
     */
    public Object[] toArray() {

        if (fast) {
            return (list.toArray());
        } else {
            synchronized (list) {
                return (list.toArray());
            }
        }

    }


    /**
     * Return an array containing all of the elements in this list in the
     * correct order.  The runtime type of the returned array is that of
     * the specified array.  If the list fits in the specified array, it is
     * returned therein.  Otherwise, a new array is allocated with the
     * runtime type of the specified array, and the size of this list.
     *
     * @param array Array defining the element type of the returned list
     *
     * @exception ArrayStoreException if the runtime type of <code>array</code>
     *  is not a supertype of the runtime type of every element in this list
     */
    public Object[] toArray(Object array[]) {

        if (fast) {
            return (list.toArray(array));
        } else {
            synchronized (list) {
                return (list.toArray(array));
            }
        }

    }


    /**
     * Return a String representation of this object.
     */
    public String toString() {

        StringBuffer sb = new StringBuffer("FastArrayList[");
        sb.append(list.toString());
        sb.append("]");
        return (sb.toString());

    }


    /**
     * Trim the capacity of this <code>ArrayList</code> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <code>ArrayList</code> instance.
     */
    public void trimToSize() {

        if (fast) {
            synchronized (this) {
                ArrayList temp = (ArrayList) list.clone();
                temp.trimToSize();
                list = temp;
            }
        } else {
            synchronized (list) {
                list.trimToSize();
            }
        }

    }



    private class SubList implements List {

        private int first;
        private int last;
        private List expected;


        public SubList(int first, int last) {
            this.first = first;
            this.last = last;
            this.expected = list;
        }

        private List get(List l) {
            if (list != expected) {
                throw new ConcurrentModificationException();
            }
            return l.subList(first, last);
        }

        public void clear() {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    get(temp).clear();
                    last = first;
                    list = temp;
                    expected = temp;
                }
            } else {
                synchronized (list) {
                    get(expected).clear();
                }
            }
        }

        public boolean remove(Object o) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    boolean r = get(temp).remove(o);
                    if (r) last--;
                    list = temp;
                    expected = temp;
                    return r;
                }
            } else {
                synchronized (list) {
                    return get(expected).remove(o);
                }
            }
        }

        public boolean removeAll(Collection o) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    List sub = get(temp);
                    boolean r = sub.removeAll(o);
                    if (r) last = first + sub.size();
                    list = temp;
                    expected = temp;
                    return r;
                }
            } else {
                synchronized (list) {
                    return get(expected).removeAll(o);
                }
            }
        }

        public boolean retainAll(Collection o) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    List sub = get(temp);
                    boolean r = sub.retainAll(o);
                    if (r) last = first + sub.size();
                    list = temp;
                    expected = temp;
                    return r;
                }
            } else {
                synchronized (list) {
                    return get(expected).retainAll(o);
                }
            }
        }

        public int size() {
            if (fast) {
                return get(expected).size();
            } else {
                synchronized (list) {
                    return get(expected).size();
                }
            }
        }


        public boolean isEmpty() {
            if (fast) {
                return get(expected).isEmpty();
            } else {
                synchronized (list) {
                    return get(expected).isEmpty();
                }
            }
        }

        public boolean contains(Object o) {
            if (fast) {
                return get(expected).contains(o);
            } else {
                synchronized (list) {
                    return get(expected).contains(o);
                }
            }
        }

        public boolean containsAll(Collection o) {
            if (fast) {
                return get(expected).containsAll(o);
            } else {
                synchronized (list) {
                    return get(expected).containsAll(o);
                }
            }
        }

        public Object[] toArray(Object[] o) {
            if (fast) {
                return get(expected).toArray(o);
            } else {
                synchronized (list) {
                    return get(expected).toArray(o);
                }
            }
        }

        public Object[] toArray() {
            if (fast) {
                return get(expected).toArray();
            } else {
                synchronized (list) {
                    return get(expected).toArray();
                }
            }
        }


        public boolean equals(Object o) {
            if (o == this) return true;
            if (fast) {
                return get(expected).equals(o);
            } else {
                synchronized (list) {
                    return get(expected).equals(o);
                }
            }
        }

        public int hashCode() {
            if (fast) {
                return get(expected).hashCode();
            } else {
                synchronized (list) {
                    return get(expected).hashCode();
                }
            }
        }

        public boolean add(Object o) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    boolean r = get(temp).add(o);
                    if (r) last++;
                    list = temp;
                    expected = temp;
                    return r;
                }
            } else {
                synchronized (list) {
                    return get(expected).add(o);
                }
            }
        }

        public boolean addAll(Collection o) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    boolean r = get(temp).addAll(o);
                    if (r) last += o.size();
                    list = temp;
                    expected = temp;
                    return r;
                }
            } else {
                synchronized (list) {
                    return get(expected).addAll(o);
                }
            }
        }

        public void add(int i, Object o) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    get(temp).add(i, o);
                    last++;
                    list = temp;
                    expected = temp;
                }
            } else {
                synchronized (list) {
                    get(expected).add(i, o);
                }
            }
        }

        public boolean addAll(int i, Collection o) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    boolean r = get(temp).addAll(i, o);
                    list = temp;
                    if (r) last += o.size();
                    expected = temp;
                    return r;
                }
            } else {
                synchronized (list) {
                    return get(expected).addAll(i, o);
                }
            }
        }

        public Object remove(int i) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    Object o = get(temp).remove(i);
                    last--;
                    list = temp;
                    expected = temp;
                    return o;
                }
            } else {
                synchronized (list) {
                    return get(expected).remove(i);
                }
            }
        }

        public Object set(int i, Object a) {
            if (fast) {
                synchronized (FastArrayList.this) {
                    ArrayList temp = (ArrayList) list.clone();
                    Object o = get(temp).set(i, a);
                    list = temp;
                    expected = temp;
                    return o;
                }
            } else {
                synchronized (list) {
                    return get(expected).set(i, a);
                }
            }
        }


        public Iterator iterator() {
            return new SubListIter(0);
        }

        public ListIterator listIterator() {
            return new SubListIter(0);
        }

        public ListIterator listIterator(int i) {
            return new SubListIter(i);
        }


        public Object get(int i) {
            if (fast) {
                return get(expected).get(i);
            } else {
                synchronized (list) {
                    return get(expected).get(i);
                }
            }
        }

        public int indexOf(Object o) {
            if (fast) {
                return get(expected).indexOf(o);
            } else {
                synchronized (list) {
                    return get(expected).indexOf(o);
                }
            }
        }


        public int lastIndexOf(Object o) {
            if (fast) {
                return get(expected).lastIndexOf(o);
            } else {
                synchronized (list) {
                    return get(expected).lastIndexOf(o);
                }
            }
        }


        public List subList(int f, int l) {
            if (list != expected) {
                throw new ConcurrentModificationException();
            }
            return new SubList(first + f, f + l);
        }


    private class SubListIter implements ListIterator {

        private List expected;
        private ListIterator iter;
        private int lastReturnedIndex = -1;


        public SubListIter(int i) {
            this.expected = list;
            this.iter = SubList.this.get(expected).listIterator(i);
        }

        private void checkMod() {
            if (list != expected) {
                throw new ConcurrentModificationException();
            }
        }

        List get() {
            return SubList.this.get(expected);
        }

        public boolean hasNext() {
            checkMod();
            return iter.hasNext();     
        }

        public Object next() {
            checkMod();
            lastReturnedIndex = iter.nextIndex();
            return iter.next();
        }

        public boolean hasPrevious() {
            checkMod();
            return iter.hasPrevious();
        }

        public Object previous() {
            checkMod();
            lastReturnedIndex = iter.previousIndex();
            return iter.previous();
        }

        public int previousIndex() {
            checkMod();
            return iter.previousIndex();
        }

        public int nextIndex() {
            checkMod();
            return iter.nextIndex();
        }

        public void remove() {
            checkMod();
            if (lastReturnedIndex < 0) {
                throw new IllegalStateException();
            }
            get().remove(lastReturnedIndex);
            last--;
            expected = list;
            iter = get().listIterator(lastReturnedIndex);
            lastReturnedIndex = -1;
        }

        public void set(Object o) {
            checkMod();
            if (lastReturnedIndex < 0) {
                throw new IllegalStateException();
            }
            get().set(lastReturnedIndex, o);
            expected = list;
            iter = get().listIterator(previousIndex() + 1);
        } 

        public void add(Object o) {
            checkMod();
            int i = nextIndex();
            get().add(i, o);
            last++;
            expected = list;
            iter = get().listIterator(i + 1);
            lastReturnedIndex = -1;
        }

   }


    }



    private class ListIter implements ListIterator {

        private List expected;
        private ListIterator iter;
        private int lastReturnedIndex = -1;


        public ListIter(int i) {
            this.expected = list;
            this.iter = get().listIterator(i);
        }

        private void checkMod() {
            if (list != expected) {
                throw new ConcurrentModificationException();
            }
        }

        List get() {
            return expected;
        }

        public boolean hasNext() {
            return iter.hasNext();     
        }

        public Object next() {
            lastReturnedIndex = iter.nextIndex();
            return iter.next();
        }

        public boolean hasPrevious() {
            return iter.hasPrevious();
        }

        public Object previous() {
            lastReturnedIndex = iter.previousIndex();
            return iter.previous();
        }

        public int previousIndex() {
            return iter.previousIndex();
        }

        public int nextIndex() {
            return iter.nextIndex();
        }

        public void remove() {
            checkMod();
            if (lastReturnedIndex < 0) {
                throw new IllegalStateException();
            }
            get().remove(lastReturnedIndex);
            expected = list;
            iter = get().listIterator(lastReturnedIndex);
            lastReturnedIndex = -1;
        }

        public void set(Object o) {
            checkMod();
            if (lastReturnedIndex < 0) {
                throw new IllegalStateException();
            }
            get().set(lastReturnedIndex, o);
            expected = list;
            iter = get().listIterator(previousIndex() + 1);
        } 

        public void add(Object o) {
            checkMod();
            int i = nextIndex();
            get().add(i, o);
            expected = list;
            iter = get().listIterator(i + 1);
            lastReturnedIndex = -1;
        }

   }
}
