/**
 * Copyright (c) 2002-2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.common.notify.impl;


import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.NotifyingList;
import org.eclipse.emf.common.util.BasicEList;


/**
 * An extensible implementation of a notifying list.
 */
public class NotifyingListImpl<E> extends BasicEList<E> implements NotifyingList<E>
{
  private static final long serialVersionUID = 1L;

  /**
   * Creates an empty instance.
   */
  public NotifyingListImpl()
  {
    super();
  }

  /**
   * Creates an empty instance with the given capacity.
   * @param initialCapacity the initial capacity of the list before it must grow.
   */
  public NotifyingListImpl(int initialCapacity)
  {
    super(initialCapacity);
  }

  /**
   * Creates an instance that is a copy of the collection.
   * @param collection the initial contents of the list.
   */
  public NotifyingListImpl(Collection<? extends E> collection)
  {
    super(collection);
  }

  /**
   * Returns <code>null</code>.
   * @return <code>null</code>.
   */
  public Object getNotifier()
  {
    return null;
  }

  /**
   * Returns <code>null</code>.
   * @return <code>null</code>.
   */
  public Object getFeature()
  {
    return null;
  }

  /**
   * Returns {@link org.eclipse.emf.common.notify.Notification#NO_FEATURE_ID}.
   * @return <code>Notification.NO_FEATURE_ID</code>.
   */
  public int getFeatureID()
  {
    return Notification.NO_FEATURE_ID;
  }

  /**
   * Returns the result of calling {@link #getFeatureID()}.
   * @param expectedClass the class to which the ID is relative.
   * @return <code>getFeatureID()</code>.
   */
  protected int getFeatureID(Class<?> expectedClass)
  {
    return getFeatureID();
  }

  /**
   * Returns whether the list is considered set, i.e., whether it's not empty.
   * A derived implementation may model this state directly.
   * @return whether the list is considered set.
   */
  protected boolean isSet()
  {
    return !isEmpty();
  }

  /**
   * Returns <code>false</code>.
   * @return <code>false</code>.
   */
  protected boolean hasInverse()
  {
    return false;
  }

  /**
   * Returns <code>!{@link #hasInverse()}</code>.
   * @return <code>!hasInverse</code>.
   */
  @Override
  protected boolean canContainNull()
  {
    return !hasInverse();
  }

  /**
   * Returns <code>false</code>.
   * @return <code>false</code>.
   */
  protected boolean isNotificationRequired()
  {
    return false;
  }

  /**
   * Returns <code>false</code>.
   * @return <code>false</code>.
   */
  protected boolean hasShadow()
  {
    return false;
  }

  /**
   * Does nothing and returns the <code>notifications</code>.
   * Clients can override this to update the inverse of a bidirectional relation.
   * @param object the object that's been added to the list.
   * @param notifications the chain of accumulating notifications.
   * @return the <code>notifications</code>.
   */
  protected NotificationChain shadowAdd(E object, NotificationChain notifications)
  {
    return notifications;
  }

  /**
   * Does nothing and returns the <code>notifications</code>.
   * Clients can override this to update the inverse of a bidirectional relation.
   * @param object the object that's been remove from the list.
   * @param notifications the chain of accumulating notifications.
   * @return the <code>notifications</code>.
   */
  protected NotificationChain shadowRemove(E object, NotificationChain notifications)
  {
    return notifications;
  }

  /**
   * Does nothing and returns the <code>notifications</code>.
   * Clients can override this to update the inverse of a bidirectional relation.
   * @param oldObject the object that's been removed from the list.
   * @param newObject the object that's been added to the list.
   * @param notifications the chain of accumulating notifications.
   * @return the <code>notifications</code>.
   */
  protected NotificationChain shadowSet(E oldObject, E newObject, NotificationChain notifications)
  {
    return notifications;
  }

  /**
   * Does nothing and returns the <code>notifications</code>.
   * Clients can override this to update the inverse of a bidirectional relation.
   * @param object the object that's been added to the list.
   * @param notifications the chain of accumulating notifications.
   * @return the <code>notifications</code>.
   */
  protected NotificationChain inverseAdd(E object, NotificationChain notifications)
  {
    return notifications;
  }

  /**
   * Does nothing and returns the <code>notifications</code>.
   * Clients can override this to update the inverse of a bidirectional relation.
   * @param object the object that's been remove from the list.
   * @param notifications the chain of accumulating notifications.
   * @return the <code>notifications</code>.
   */
  protected NotificationChain inverseRemove(E object, NotificationChain notifications)
  {
    return notifications;
  }

  /*
   * @deprecated
   */
  protected NotificationImpl createNotification(int eventType, Object oldObject, Object newObject, int index)
  {
    throw new UnsupportedOperationException("Please change your code to call new five argument version of this method");
  }

  /**
   * Creates a notification.
   * @param eventType the type of change that has occurred.
   * @param oldObject the value of the notifier's feature before the change occurred.
   * @param newObject the value of the notifier's feature after the change occurred.
   * @param index the position at which the change occurred.
   * @return a new notification.
   */
  protected NotificationImpl createNotification(int eventType, Object oldObject, Object newObject, int index, boolean wasSet)
  {
    return 
      new NotificationImpl(eventType, oldObject, newObject, index, wasSet)
      {
        @Override
        public Object getNotifier()
        {
          return NotifyingListImpl.this.getNotifier();
        }

        @Override
        public Object getFeature()
        {
          return NotifyingListImpl.this.getFeature();
        }

        @Override
        public int getFeatureID(Class<?> expectedClass)
        {
          return NotifyingListImpl.this.getFeatureID(expectedClass);
        }
      };
  }

  /**
   * Creates a notification chain, if the expected capacity exceeds the threshold 
   * at which a list is better than chaining individual notification instances.
   */
  protected NotificationChain createNotificationChain(int capacity)
  {
    return capacity < 100 ? null: new NotificationChainImpl(capacity);
  }

  /**
   * Dispatches a notification to the notifier of the list.
   * @param notification the notification to dispatch.
   */
  protected void dispatchNotification(Notification notification)
  {
    ((Notifier)getNotifier()).eNotify(notification);
  }

  /**
   * Adds the object at the end of the list;
   * it does no uniqueness checking.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required} 
   * and delegates to {@link #inverseAdd inverseAdd} as {@link #hasInverse required}.
   * @param object the object to be added.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseAdd
   */
  @Override
  public void addUnique(E object)
  {
    if (isNotificationRequired())
    {
      int index = size;
      boolean oldIsSet = isSet();
      doAddUnique(object);
      NotificationImpl notification = createNotification(Notification.ADD, null, object, index, oldIsSet);
      if (hasInverse())
      {
        NotificationChain notifications = inverseAdd(object, null);
        if (hasShadow())
        {
          notifications = shadowAdd(object, notifications);
        }

        if (notifications == null)
        {
          dispatchNotification(notification);
        }
        else
        {
          notifications.add(notification);
          notifications.dispatch();
        }
      }
      else
      {
        dispatchNotification(notification);
      }
    }
    else
    {
      doAddUnique(object);
      if (hasInverse())
      {
        NotificationChain notifications = inverseAdd(object, null);
        if (notifications != null) notifications.dispatch();
      }
    }
  }

  /**
   * Adds the object at the end of the list;
   * it does no uniqueness checking, inverse updating, or notification.
   * @param object the object to be added.
   */
  protected void doAddUnique(E object)
  {
    super.addUnique(object);
  }

  /**
   * Adds the object at the given index in the list;
   * it does no ranging checking or uniqueness checking.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required} 
   * and delegates to {@link #inverseAdd inverseAdd} as {@link #hasInverse required}.
   * @param object the object to be added.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseAdd
   */
  @Override
  public void addUnique(int index, E object)
  {
    if (isNotificationRequired())
    {
      boolean oldIsSet = isSet();
      doAddUnique(index, object);
      NotificationImpl notification = createNotification(Notification.ADD, null, object, index, oldIsSet);
      if (hasInverse())
      {
        NotificationChain notifications = inverseAdd(object, null);
        if (hasShadow())
        {
          notifications = shadowAdd(object, notifications);
        }
        if (notifications == null)
        {
          dispatchNotification(notification);
        }
        else
        {
          notifications.add(notification);
          notifications.dispatch();
        }
      }
      else
      {
        dispatchNotification(notification);
      }
    }
    else
    {
      doAddUnique(index, object);
      if (hasInverse())
      {
        NotificationChain notifications = inverseAdd(object, null);
        if (notifications != null) notifications.dispatch();
      }
    }
  }

  /**
   * Adds the object at the given index in the list;
   * it does no range checking, uniqueness checking, inverse updating, or notification.
   * @param object the object to be added.
   */
  protected void doAddUnique(int index, E object)
  {
    super.addUnique(index, object);
  }

  /**
   * Adds each object of the collection to the end of the list;
   * it does no uniqueness checking.
   * This implementation delegates to {@link #addAllUnique(int, Collection) addAllUnique(int, Collection)}.
   * @param collection the collection of objects to be added.
   * @see #inverseAdd
   */
  @Override
  public boolean addAllUnique(Collection<? extends E> collection)
  {
    return addAllUnique(size, collection);
  }

  /**
   * Adds each object of the collection to the end of the list;
   * it does no uniqueness checking, inverse updating, or notification.
   * @param collection the collection of objects to be added.
   */
  protected boolean doAddAllUnique(Collection<? extends E> collection)
  {
    return super.addAllUnique(collection);
  }

  /**
   * Adds each object of the collection at each successive index in the list
   * and returns whether any objects were added;
   * it does no ranging checking or uniqueness checking.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required} 
   * and delegates to {@link #inverseAdd inverseAdd} as {@link #hasInverse required}.
   * @param index the index at which to add.
   * @param collection the collection of objects to be added.
   * @return whether any objects were added.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseAdd
   */
  @Override
  public boolean addAllUnique(int index, Collection<? extends E> collection)
  {
    int collectionSize = collection.size();
    if (collectionSize == 0)
    {
      return false;
    }
    else
    {
      if (isNotificationRequired())
      {
        boolean oldIsSet = isSet();
        doAddAllUnique(index, collection);
        NotificationImpl notification =
          collectionSize == 1 ?
            createNotification(Notification.ADD, null, collection.iterator().next(), index, oldIsSet) :
            createNotification(Notification.ADD_MANY, null, collection, index, oldIsSet);
        if (hasInverse())
        {
          NotificationChain notifications = createNotificationChain(collectionSize);
          int lastIndex = index + collectionSize;
          for (int i = index; i < lastIndex; ++i)
          {            
            @SuppressWarnings("unchecked") E value = (E)data[i];
            notifications = inverseAdd(value, notifications);
            notifications = shadowAdd(value, notifications);
          }
          if (notifications == null)
          {
            dispatchNotification(notification);
          }
          else
          {
            notifications.add(notification);
            notifications.dispatch();
          }
        }
        else
        {
          dispatchNotification(notification);
        }
      }
      else
      {
        doAddAllUnique(index, collection);
        if (hasInverse())
        {
          NotificationChain notifications = createNotificationChain(collectionSize);
          int lastIndex = index + collectionSize;
          for (int i = index; i < lastIndex; ++i)
          {            
            @SuppressWarnings("unchecked") E object = (E)data[i];
            notifications = inverseAdd(object, notifications);
          }
          if (notifications != null) notifications.dispatch();
        }
      }

      return true;
    }
  }

  /**
   * Adds each object of the collection at each successive index in the list
   * and returns whether any objects were added;
   * it does no range checking, uniqueness checking, inverse updating, or notification.
   * @param index the index at which to add.
   * @param collection the collection of objects to be added.
   * @return whether any objects were added.
   */
  protected boolean doAddAllUnique(int index, Collection<? extends E> collection)
  {
    return super.addAllUnique(index, collection);
  }

  /**
   * Adds each object from start to end of the array to the end of the list
   * and returns whether any objects were added;
   * it does no uniqueness checking.
   * This implementation delegates to {@link #addAllUnique(int, Object[], int, int) addAllUnique(int, Object[], int, int)}.
   * @param objects the objects to be added.
   * @param start the index of first object to be added.
   * @param end the index past the last object to be added.
   * @return whether any objects were added.
   * @see #inverseAdd
   */
  @Override
  public boolean addAllUnique(Object [] objects, int start, int end)
  {
    return addAllUnique(size, objects, start, end);
  }

  /**
   * Adds each object from start to end of the array to the end of the list
   * and returns whether any objects were added;
   * it does no ranging checking, uniqueness checking, inverse updating, or notification.
   * @param objects the objects to be added.
   * @param start the index of first object to be added.
   * @param end the index past the last object to be added.
   * @return whether any objects were added.
   */
  protected boolean doAddAllUnique(Object [] objects, int start, int end)
  {
    return super.addAllUnique(objects, start, end);
  }

  /**
   * Adds each object from start to end of the array at each successive index in the list 
   * and returns whether any objects were added;
   * it does no ranging checking or uniqueness checking.
   * This implementation delegates to {@link #assign(int, Object) assign},
   * {@link #didAdd(int, Object) didAdd}, and {@link #didChange() didChange}.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required} 
   * and delegates to {@link #inverseAdd inverseAdd} as {@link #hasInverse required}.
   * @param index the index at which to add.
   * @param objects the objects to be added.
   * @param start the index of first object to be added.
   * @param end the index past the last object to be added.
   * @return whether any objects were added.
   * @see #addAllUnique(int, Collection)
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseAdd
   * @see #assign(int, Object)
   */
  @Override
  public boolean addAllUnique(int index, Object [] objects, int start, int end)
  {
    int collectionSize = end - start;
    if (collectionSize == 0)
    {
      return false;
    }
    else
    {
      if (isNotificationRequired())
      {
        boolean oldIsSet = isSet();
        doAddAllUnique(index, objects, start, end);
        NotificationImpl notification;
        if (collectionSize == 1)
        {
          notification = createNotification(Notification.ADD, null, objects[0], index, oldIsSet);
        }
        else
        {
          if (start != 0 || end != objects.length)
          {
            Object [] actualObjects = new Object [collectionSize];
            for (int i = 0, j = start; j < end; ++i, ++j)
            {
              actualObjects[i] = objects[j];
            }
            notification = createNotification(Notification.ADD_MANY, null, Arrays.asList(actualObjects), index, oldIsSet);
          }
          else
          {
            notification =  createNotification(Notification.ADD_MANY, null, Arrays.asList(objects), index, oldIsSet);
          }
        }
        if (hasInverse())
        {
          NotificationChain notifications = createNotificationChain(collectionSize);
          int lastIndex = index + collectionSize;
          for (int i = index; i < lastIndex; ++i)
          {            
            @SuppressWarnings("unchecked") E value = (E)data[i];
            notifications = inverseAdd(value, notifications);
            notifications = shadowAdd(value, notifications);
          }
          if (notifications == null)
          {
            dispatchNotification(notification);
          }
          else
          {
            notifications.add(notification);
            notifications.dispatch();
          }
        }
        else
        {
          dispatchNotification(notification);
        }
      }
      else
      {
        doAddAllUnique(index, objects, start, end);
        if (hasInverse())
        {
          NotificationChain notifications = createNotificationChain(collectionSize);
          int lastIndex = index + collectionSize;
          for (int i = index; i < lastIndex; ++i)
          {            
            @SuppressWarnings("unchecked") E object = (E)data[i];
            notifications = inverseAdd(object, notifications);
          }
          if (notifications != null) notifications.dispatch();
        }
      }

      return true;
    }
  }

  /**
   * Adds each object from start to end of the array at each successive index in the list 
   * and returns whether any objects were added;
   * it does no ranging checking, uniqueness checking, inverse updating, or notification.
   * @param index the index at which to add.
   * @param objects the objects to be added.
   * @param start the index of first object to be added.
   * @param end the index past the last object to be added.
   * @return whether any objects were added.
   */
  protected boolean doAddAllUnique(int index, Object [] objects, int start, int end)
  {
    return super.addAllUnique(index, objects, start, end);
  }
  
  /**
   * Adds the object at the end of the list and returns the potentially updated notification chain;
   * it does no {@link #inverseAdd inverse} updating.
   * This implementation generates notifications as {@link #isNotificationRequired required}.
   * @param object the object to be added.
   * @return the notification chain.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseAdd
   */
  public NotificationChain basicAdd(E object, NotificationChain notifications)
  {
    if (isNotificationRequired())
    {
      int index = size;
      boolean oldIsSet = isSet();
      doAddUnique(index, object);
      NotificationImpl notification = createNotification(Notification.ADD, null, object, index, oldIsSet);
      if (notifications == null)
      {
        notifications = notification;
      }
      else
      {
        notifications.add(notification);
      }
    }
    else
    {
      doAddUnique(size, object);
    }
    return notifications;
  }

  /**
   * Removes the object at the index from the list and returns it.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required} 
   * and delegates to {@link #inverseRemove inverseRemove} as {@link #hasInverse required}.
   * @param index the position of the object to remove.
   * @return the removed object.
   * @exception IndexOutOfBoundsException if the index isn't within the size range.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseRemove
   */
  @Override
  public E remove(int index)
  {
    if (isNotificationRequired())
    {
      NotificationChain notifications = null;
      boolean oldIsSet = isSet();
      if (hasShadow())
      {
        notifications = shadowRemove(basicGet(index), null);
      }
      E oldObject;
      NotificationImpl notification = createNotification(Notification.REMOVE, oldObject = doRemove(index), null, index, oldIsSet);
      if (hasInverse() && oldObject != null)
      {
        notifications = inverseRemove(oldObject, notifications);
        if (notifications == null)
        {
          dispatchNotification(notification);
        }
        else
        {
          notifications.add(notification);
          notifications.dispatch();
        }
      }
      else
      {
        if (notifications == null)
        {
          dispatchNotification(notification);
        }
        else
        {
          notifications.add(notification);
          notifications.dispatch();
        }
      }
      return oldObject;
    }
    else
    {
      E oldObject = doRemove(index);
      if (hasInverse() && oldObject != null)
      {
        NotificationChain notifications = inverseRemove(oldObject, null);
        if (notifications != null) notifications.dispatch();
      }
      return oldObject;
    }
  }

  /**
   * Removes the object at the index from the list and returns it;
   * it does no inverse updating, or notification.
   * @param index the position of the object to remove.
   * @return the removed object.
   * @exception IndexOutOfBoundsException if the index isn't within the size range.
   */
  protected E doRemove(int index)
  {
    return super.remove(index);
  }

  /**
   * Removes each object of the collection from the list and returns whether any object was actually contained by the list.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required} 
   * and delegates to {@link #inverseRemove inverseRemove} as {@link #hasInverse required}.
   * @param collection the collection of objects to be removed.
   * @return whether any object was actually contained by the list.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseRemove
   */
  @Override
  public boolean removeAll(Collection<?> collection)
  {
    boolean oldIsSet = isSet();

    boolean result = false;
    int [] positions = null;
    NotificationChain notifications = null;
    if (isNotificationRequired())
    {
      int listSize = collection.size();
      if (listSize > 0)
      {
        // Copy to a list and allocate positions.
        //
        BasicEList<Object> list = new BasicEList<Object>(collection);
        Object[] objects = list.data();
        positions = new int [listSize];
        int count = 0;

        if (isUnique())
        {
          // Count up the objects that will be removed.
          // The objects are exchanged to produce this list's order
          //
          for (int i = 0; i < size; ++i)
          {
            @SuppressWarnings("unchecked") E initialObject = (E)data[i];
            E object = initialObject;
            LOOP:
            for (int repeat = 0; repeat < 2; ++repeat)
            {
              for (int j = listSize; --j >= 0; )
              {
                if (equalObjects(object, objects[j]))
                {
                  if (count != j)
                  {
                    Object x = objects[count];
                    objects[count] = objects[j];
                    objects[j] = x;
                  }
                  positions[count++] = i;
                  break LOOP;
                }
              }
              object = resolve(object);
              if (object == initialObject)
              {
                break;
              }
            }
          }
        }
        else
        {
          BasicEList<Object> resultList = new BasicEList<Object>(listSize);
          
          // Count up the objects that will be removed.
          // The objects are exchanged to produce this list's order
          //
          for (int i = 0; i < size; ++i)
          {
            @SuppressWarnings("unchecked") E initialObject = (E)data[i];
            E object = initialObject;
            LOOP:
            for (int repeat = 0; repeat < 2; ++repeat)
            {
              for (int j = listSize; --j >= 0; )
              {
                if (equalObjects(object, objects[j]))
                {
                  if (positions.length <= count)
                  {
                    int [] oldPositions = positions;
                    positions = new int [2 * positions.length];
                    System.arraycopy(oldPositions, 0, positions, 0, count);
                  }
                  positions[count++] = i;
                  resultList.add(objects[j]);
                  break LOOP;
                }
              }
              object = resolve(object);
              if (object == initialObject)
              {
                break;
              }
            }
          }
          
          list = resultList;
          objects = resultList.data();
          listSize = count;
          
          if (count > positions.length)
          {
            int [] oldPositions = positions;
            positions = new int [count];
            System.arraycopy(oldPositions, 0, positions, 0, count);
          }
        }

        // If any objects are matched.
        //
        if (count > 0)
        {
          result = true;

          if (hasShadow())
          {
            notifications = createNotificationChain(count);

            // Remove from by position in reverse order.
            //
            for (int i = 0; i < count; ++i)
            {
              @SuppressWarnings("unchecked") E object = (E)objects[i];
              notifications = shadowRemove(object, notifications);
            }
          }

          // Remove from by position in reverse order.
          //
          for (int i = count; --i >= 0;)
          {
            doRemove(positions[i]);
          }

          // Compact the results to remove unmatched objects
          //
          if (count != listSize)
          {
            for (int i = listSize; --i >= count; )
            {
              list.remove(i);
            }
            int [] oldPositions = positions;
            positions = new int [count];
            System.arraycopy(oldPositions, 0, positions, 0, count);
          }

          collection = list;
        }
      }
    }
    else
    {
      collection = getDuplicates(collection);

      for (int i = size; --i >= 0; )
      {
        if (collection.contains(data[i]))
        {
          doRemove(i);
          result = true;
        }
      }
    }

    if (result)
    {
      if (positions != null)
      {
        int collectionSize = collection.size();
        NotificationImpl notification =
          (collectionSize == 1 ?
            createNotification(Notification.REMOVE, collection.iterator().next(), null, positions[0], oldIsSet) :
            createNotification(Notification.REMOVE_MANY, collection, positions, positions[0], oldIsSet));

        if (notifications == null)
        {
          notifications = createNotificationChain(collectionSize);
        }

        if (hasInverse())
        {
          for (Iterator<?> i = collection.iterator(); i.hasNext(); )
          {
            @SuppressWarnings("unchecked") E object = (E)i.next();
            notifications = inverseRemove(object, notifications);
          }
          if (notifications == null)
          {
            dispatchNotification(notification);
          }
          else
          {
            notifications.add(notification);
            notifications.dispatch();
          }
        }
        else
        {
          if (notifications == null)
          {
            dispatchNotification(notification);
          }
          else
          {
            notifications.add(notification);
            notifications.dispatch();
          }
        }
      }
      else if (hasInverse())
      {
        if (notifications == null)
        {
          notifications = createNotificationChain(collection.size());
        }

        for (Iterator<?> i = collection.iterator(); i.hasNext(); )
        {
          @SuppressWarnings("unchecked") E object = (E)i.next();
          notifications = inverseRemove(object, notifications);
        }
        if (notifications != null) notifications.dispatch();
      }
      return true;
    }
    else
    {
      return false;
    }
  }
  
  /**
   * Returns the resolved object from this list for the purpose of testing whether {@link #removeAll(Collection)} applies to it.
   * @param object the object to be resolved.
   * @return the resolved object from this list for the purpose of testing whether removeAll applies to it.
   */
  protected E resolve(E object)
  {
    return object;
  }

  /**
   * Removes each object of the collection from the list and returns whether any object was actually contained by the list;
   * it does no inverse updating, or notification.
   * @param collection the collection of objects to be removed.
   * @return whether any object was actually contained by the list.
   */
  protected boolean doRemoveAll(Collection<?> collection)
  {
    return super.removeAll(collection);
  }

  /**
   * Removes the object from the list and returns the potentially updated notification chain;
   * it does no {@link #inverseRemove inverse} updating.
   * This implementation generates notifications as {@link #isNotificationRequired required}.
   * @param object the object to be removed.
   * @return the notification chain.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseRemove
   */
  public NotificationChain basicRemove(Object object, NotificationChain notifications)
  {
    int index = indexOf(object);
    if (index != -1)
    {
      if (isNotificationRequired())
      {
        boolean oldIsSet = isSet();
        Object oldObject = doRemove(index);
        NotificationImpl notification = createNotification(Notification.REMOVE, oldObject, null, index, oldIsSet);
        if (notifications == null) 
        {
          notifications = notification;
        }
        else
        {
          notifications.add(notification);
        }
      }
      else
      {
        doRemove(index);
      }
    }
    return notifications;
  }

  /**
   * Clears the list of all objects.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required} 
   * and delegates to {@link #inverseRemove inverseRemove} as {@link #hasInverse required}.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseRemove
   */
  @Override
  public void clear()
  {
    if (isNotificationRequired())
    {
      boolean oldIsSet = isSet();
      if (size > 0)
      {
        List<E> collection = new UnmodifiableEList<E>(size, data);
        int collectionSize = size;

        NotificationChain notifications = createNotificationChain(collectionSize);
        if (hasShadow())
        {
          for (int i = 0; i < size; ++i)
          {
            @SuppressWarnings("unchecked") E object = (E)data[i];
            notifications = shadowRemove(object, notifications);
          }
        }

        doClear();
        Notification notification =
          (collectionSize == 1 ?
            createNotification(Notification.REMOVE, collection.get(0), null, 0, oldIsSet) :
            createNotification(Notification.REMOVE_MANY, collection, null, Notification.NO_INDEX, oldIsSet));

        if (hasInverse())
        {
          for (Iterator<E> i = collection.iterator(); i.hasNext(); )
          {
            notifications = inverseRemove(i.next(), notifications);
          }
          if (notifications == null)
          {
            dispatchNotification(notification);
          }
          else
          {
            notifications.add(notification);
            notifications.dispatch();
          }
        }
        else
        {
          if (notifications == null)
          {
            dispatchNotification(notification);
          }
          else
          {
            notifications.add(notification);
            notifications.dispatch();
          }
        }
      }
      else
      {
        doClear();
        dispatchNotification(createNotification(Notification.REMOVE_MANY, Collections.EMPTY_LIST, null, Notification.NO_INDEX, oldIsSet));
      }
    }
    else if (hasInverse())
    {
      if (size > 0)
      {
        Object [] oldData = data;
        int oldSize = size;
        doClear();
        NotificationChain notifications = createNotificationChain(oldSize);
        for (int i = 0; i < oldSize; ++i)
        {
          @SuppressWarnings("unchecked") E object = (E)oldData[i];
          notifications = inverseRemove(object, notifications);
        }
        if (notifications != null) notifications.dispatch();
      }
      else 
      {
        doClear();
      }
    }
    else
    {
      doClear();
    }
  }

  /**
   * Clears the list of all objects;
   * it does no {@link #inverseRemove inverse} updating.
   */
  protected void doClear()
  {
    super.clear();
  }

  /**
   * Sets the object at the index
   * and returns the old object at the index;
   * it does no ranging checking or uniqueness checking.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required} 
   * and delegates to {@link #inverseAdd inverseAdd} and {@link #inverseRemove inverseRemove} as {@link #hasInverse required}.
   * @param index the position in question.
   * @param object the object to set.
   * @return the old object at the index.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseAdd
   * @see #inverseRemove
   */
  @Override
  public E setUnique(int index, E object)
  {
    if (isNotificationRequired())
    {
      NotificationChain notifications = null;
      boolean oldIsSet = isSet();
      E oldObject;
      Notification notification = createNotification(Notification.SET, oldObject = doSetUnique(index, object), object, index, oldIsSet);
      if (hasInverse() && !equalObjects(oldObject, object))
      {
        if (oldObject != null)
        {
          notifications = inverseRemove(oldObject, notifications);
        }

        notifications = inverseAdd(object, notifications);

        if (hasShadow())
        {
          notifications = shadowSet(oldObject, object, notifications);
        }

        if (notifications == null)
        {
          dispatchNotification(notification);
        }
        else
        {
          notifications.add(notification);
          notifications.dispatch();
        }
      }
      else
      {
        if (hasShadow())
        {
          notifications = shadowSet(oldObject, object, notifications);
        }

        if (notifications == null)
        {
          dispatchNotification(notification);
        }
        else
        {
          notifications.add(notification);
          notifications.dispatch();
        }
      }

      return oldObject;
    }
    else
    {
      E oldObject = doSetUnique(index, object);
      if (hasInverse() && !equalObjects(oldObject, object))
      {
        NotificationChain notifications = null;
        if (oldObject != null)
        {
          notifications = inverseRemove(oldObject, null);
        }
        notifications = inverseAdd(object, notifications);
        if (notifications != null) notifications.dispatch();
      }
      return oldObject;
    }
  }

  /**
   * Sets the object at the index
   * and returns the old object at the index;
   * it does no ranging checking, uniqueness checking, inverse updating or notification.
   * @param index the position in question.
   * @param object the object to set.
   * @return the old object at the index.
   */
  protected E doSetUnique(int index, E object)
  {
    return super.setUnique(index, object);
  }

  /**
   * Sets the object at the index
   * and returns the potentially updated notification chain;
   * it does no {@link #hasInverse inverse} updating.
   * This implementation generates notifications as {@link #isNotificationRequired required}.
   * @param index the position in question.
   * @param object the object to set.
   * @return the notification chain.
   * @see #isNotificationRequired
   * @see #hasInverse
   * @see #inverseAdd
   * @see #inverseRemove
   */
  public NotificationChain basicSet(int index, E object, NotificationChain notifications)
  {
    if (isNotificationRequired())
    {
      boolean oldIsSet = isSet();
      NotificationImpl notification = createNotification(Notification.SET, doSetUnique(index, object), object, index, oldIsSet);
      if (notifications == null) 
      {
        notifications = notification;
      }
      else
      {
        notifications.add(notification);
      }
    }
    else
    {
      doSetUnique(index, object);
    }
    return notifications;
  }

  /**
   * Moves the object at the source index of the list to the target index of the list
   * and returns the moved object.
   * In addition to the normal effects, 
   * this override implementation generates notifications as {@link #isNotificationRequired required}.
   * @param targetIndex the new position for the object in the list.
   * @param sourceIndex the old position of the object in the list.
   * @return the moved object.
   * @exception IndexOutOfBoundsException if either index isn't within the size range.
   * @see #isNotificationRequired
   */
  @Override
  public E move(int targetIndex, int sourceIndex)
  {
    if (isNotificationRequired())
    {
      boolean oldIsSet = isSet();
      E object = doMove(targetIndex, sourceIndex);
      dispatchNotification
        (createNotification
           (Notification.MOVE, 
            sourceIndex,
            object, 
            targetIndex,
            oldIsSet));
      return object;
    }
    else
    {
      return doMove(targetIndex, sourceIndex);
    }
  }

  /**
   * Moves the object at the source index of the list to the target index of the list
   * and returns the moved object;
   * it does no notification.
   * @param targetIndex the new position for the object in the list.
   * @param sourceIndex the old position of the object in the list.
   * @return the moved object.
   * @exception IndexOutOfBoundsException if either index isn't within the size range.
   */
  protected E doMove(int targetIndex, int sourceIndex)
  {
    return super.move(targetIndex, sourceIndex);
  }
}
