/**
 * Copyright (c) 2002-2006 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.mapping.impl;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.notify.impl.NotificationImpl;
import org.eclipse.emf.common.notify.impl.NotifyingListImpl;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.edit.provider.ChangeNotifier;
import org.eclipse.emf.edit.provider.Disposable;
import org.eclipse.emf.edit.provider.IDisposable;
import org.eclipse.emf.edit.provider.INotifyChangedListener;
import org.eclipse.emf.mapping.MappedObjectState;
import org.eclipse.emf.mapping.Mapping;
import org.eclipse.emf.mapping.MappingFactory;
import org.eclipse.emf.mapping.MappingPackage;
import org.eclipse.emf.mapping.MappingRoot;
import org.eclipse.emf.mapping.domain.MappingDomain;


/**
 * <!-- begin-user-doc -->
 * An implementation of the model object '<em><b>Root</b></em>'.
 * <!-- end-user-doc -->
 * <p>
 * The following features are implemented:
 * </p>
 * <ul>
 *   <li>{@link org.eclipse.emf.mapping.impl.MappingRootImpl#isOutputReadOnly <em>Output Read Only</em>}</li>
 *   <li>{@link org.eclipse.emf.mapping.impl.MappingRootImpl#isTopToBottom <em>Top To Bottom</em>}</li>
 *   <li>{@link org.eclipse.emf.mapping.impl.MappingRootImpl#getCommandStack <em>Command Stack</em>}</li>
 * </ul>
 *
 * @generated
 */
public class MappingRootImpl extends MappingImpl implements MappingRoot
{
  /**
   * The default value of the '{@link #isOutputReadOnly() <em>Output Read Only</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #isOutputReadOnly()
   * @generated
   * @ordered
   */
  protected static final boolean OUTPUT_READ_ONLY_EDEFAULT = false;

  /**
   * The cached value of the '{@link #isOutputReadOnly() <em>Output Read Only</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #isOutputReadOnly()
   * @generated
   * @ordered
   */
  protected boolean outputReadOnly = OUTPUT_READ_ONLY_EDEFAULT;

  /**
   * The default value of the '{@link #isTopToBottom() <em>Top To Bottom</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #isTopToBottom()
   * @generated
   * @ordered
   */
  protected static final boolean TOP_TO_BOTTOM_EDEFAULT = false;

  /**
   * The cached value of the '{@link #isTopToBottom() <em>Top To Bottom</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #isTopToBottom()
   * @generated
   * @ordered
   */
  protected boolean topToBottom = TOP_TO_BOTTOM_EDEFAULT;

  /**
   * The default value of the '{@link #getCommandStack() <em>Command Stack</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getCommandStack()
   * @generated
   * @ordered
   */
  protected static final String COMMAND_STACK_EDEFAULT = null;

  /**
   * The cached value of the '{@link #getCommandStack() <em>Command Stack</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getCommandStack()
   * @generated
   * @ordered
   */
  protected String commandStack = COMMAND_STACK_EDEFAULT;
  
  /**
   * This keeps track of the mapping domain that uses this mapping root.
   */
  protected MappingDomain domain;

  /**
   * This keeps track of whether the output has been modified.
   */
  protected boolean outputDirty = false;

  /**
   * This allows this listen for changes to inputs or outputs.
   */
  protected AdapterImpl mappedObjectListener;

  /**
   * This keeps track of the factory for creating the {@link org.eclipse.emf.mapping.MappedObjectState}.
   */
  protected AdapterFactory mappedObjectStateAdapterFactory;


  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   */
  protected MappingRootImpl() 
  {
    super();
    this.mappedObjectStateAdapterFactory = createMappedObjectStateAdapterFactory();
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  protected EClass eStaticClass()
  {
    return MappingPackage.Literals.MAPPING_ROOT;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public boolean isOutputReadOnly()
  {
    return outputReadOnly;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setOutputReadOnly(boolean newOutputReadOnly)
  {
    boolean oldOutputReadOnly = outputReadOnly;
    outputReadOnly = newOutputReadOnly;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, MappingPackage.MAPPING_ROOT__OUTPUT_READ_ONLY, oldOutputReadOnly, outputReadOnly));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public boolean isTopToBottom()
  {
    return topToBottom;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setTopToBottom(boolean newTopToBottom)
  {
    boolean oldTopToBottom = topToBottom;
    topToBottom = newTopToBottom;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, MappingPackage.MAPPING_ROOT__TOP_TO_BOTTOM, oldTopToBottom, topToBottom));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public String getCommandStack()
  {
    return commandStack;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setCommandStack(String newCommandStack)
  {
    String oldCommandStack = commandStack;
    commandStack = newCommandStack;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, MappingPackage.MAPPING_ROOT__COMMAND_STACK, oldCommandStack, commandStack));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public Object eGet(int featureID, boolean resolve, boolean coreType)
  {
    switch (featureID)
    {
      case MappingPackage.MAPPING_ROOT__OUTPUT_READ_ONLY:
        return isOutputReadOnly();
      case MappingPackage.MAPPING_ROOT__TOP_TO_BOTTOM:
        return isTopToBottom();
      case MappingPackage.MAPPING_ROOT__COMMAND_STACK:
        return getCommandStack();
    }
    return super.eGet(featureID, resolve, coreType);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public void eSet(int featureID, Object newValue)
  {
    switch (featureID)
    {
      case MappingPackage.MAPPING_ROOT__OUTPUT_READ_ONLY:
        setOutputReadOnly((Boolean)newValue);
        return;
      case MappingPackage.MAPPING_ROOT__TOP_TO_BOTTOM:
        setTopToBottom((Boolean)newValue);
        return;
      case MappingPackage.MAPPING_ROOT__COMMAND_STACK:
        setCommandStack((String)newValue);
        return;
    }
    super.eSet(featureID, newValue);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public void eUnset(int featureID)
  {
    switch (featureID)
    {
      case MappingPackage.MAPPING_ROOT__OUTPUT_READ_ONLY:
        setOutputReadOnly(OUTPUT_READ_ONLY_EDEFAULT);
        return;
      case MappingPackage.MAPPING_ROOT__TOP_TO_BOTTOM:
        setTopToBottom(TOP_TO_BOTTOM_EDEFAULT);
        return;
      case MappingPackage.MAPPING_ROOT__COMMAND_STACK:
        setCommandStack(COMMAND_STACK_EDEFAULT);
        return;
    }
    super.eUnset(featureID);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public boolean eIsSet(int featureID)
  {
    switch (featureID)
    {
      case MappingPackage.MAPPING_ROOT__OUTPUT_READ_ONLY:
        return outputReadOnly != OUTPUT_READ_ONLY_EDEFAULT;
      case MappingPackage.MAPPING_ROOT__TOP_TO_BOTTOM:
        return topToBottom != TOP_TO_BOTTOM_EDEFAULT;
      case MappingPackage.MAPPING_ROOT__COMMAND_STACK:
        return COMMAND_STACK_EDEFAULT == null ? commandStack != null : !COMMAND_STACK_EDEFAULT.equals(commandStack);
    }
    return super.eIsSet(featureID);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public String toString()
  {
    if (eIsProxy()) return super.toString();

    StringBuffer result = new StringBuffer(super.toString());
    result.append(" (outputReadOnly: ");
    result.append(outputReadOnly);
    result.append(", topToBottom: ");
    result.append(topToBottom);
    result.append(", commandStack: ");
    result.append(commandStack);
    result.append(')');
    return result.toString();
  }

  public MappingDomain getDomain() 
  {
    return domain;
  }

  public void setDomain(MappingDomain domain) 
  {
    if (this.domain != domain)
    {
      if (this.domain != null)
      {
        eAdapters.remove(mappedObjectListener);
      }

      this.domain = domain;
      domain.setMappingRoot(this);

      mappedObjectListener = 
        new AdapterImpl()
        {
          @Override
          public void notifyChanged(Notification notification)
          {
            Object feature = notification.getFeature();
            if (feature == MappingPackage.eINSTANCE.getMapping_Inputs() || feature == MappingPackage.eINSTANCE.getMapping_Outputs())
            {
              initializeMappedObjectStates();
            }
          }
        };
        eAdapters().add(mappedObjectListener);

      initializeMappedObjectStates();
    }
  }

  public void refreshMappedObjectStates(Mapping subtree)
  {
    for (Object input : subtree.getInputs())
    {
      for (Iterator<?> objects = domain.treeIterator(input); objects.hasNext(); )
      {
        Object object = objects.next();
        MappedObjectState mappedObjectState = getMappedObjectState(object);
        if (mappedObjectState != null)
        {
          mappedObjectState.setInput();
        }
      }
    }

    for (Object output : subtree.getOutputs())
    {
      for (Iterator<?> objects = domain.treeIterator(output); objects.hasNext(); )
      {
        MappedObjectState mappedObjectState = getMappedObjectState(objects.next());
        if (mappedObjectState != null)
        {
          mappedObjectState.setOutput();
        }
      }
    }

    for (Iterator<Mapping> mappings = subtree.treeIterator(); mappings.hasNext(); )
    {
      Mapping mapping = mappings.next();
      for (Object input : mapping.getInputs())
      {
        MappedObjectState mappedObjectState = getMappedObjectState(input);
        if (mappedObjectState != null)
        {
          mappedObjectState.getMappings().add(mapping);
        }
      }

      for (Object output : mapping.getOutputs())
      {
        MappedObjectState mappedObjectState = getMappedObjectState(output);
        if (mappedObjectState != null)
        {
          mappedObjectState.getMappings().add(mapping);
        }
      }
    }
  }

  protected void initializeMappedObjectStates()
  {
    refreshMappedObjectStates(this);

    if (getTypeMapping() instanceof MappingRootImpl)
    {
      ((MappingRootImpl)getTypeMapping()).initializeMappedObjectStates();
    }
  }

  public Mapping getParentMapping(Collection<?> collection)
  {
    // Barring a better result, this will be the result.
    //
    Mapping result = this;

    // Cache the tree path for each object.
    //
    final Collection<List<?>> allTreePaths = new ArrayList<List<?>>();
    for (Object object : collection)
    {
      allTreePaths.add(domain.getTreePath(object));
    }

    // Iterate over the mappings in the tree.
    //
    OuterLoop: for (TreeIterator<Mapping> mappings = treeIterator(); mappings.hasNext(); )
    {
      Mapping mapping = mappings.next();

      // Check to make sure that every object in the collection has an ancestor that is contained in this mapping.
      //
      for (Iterator<List<?>> treePaths = allTreePaths.iterator(); treePaths.hasNext(); )
      {
        List<?> treePath = treePaths.next();
        Collection<?> mappedObjects = mapping.getMappedObjects();
        mappedObjects.retainAll(treePath);

        // If the intersection is empty, i.e., no ancestor is in the mapping...
        //
        if (mappedObjects.isEmpty())
        {
          // If this mapping isn't a parent, it's children definitely won't be either.
          //
          mappings.prune();
          continue OuterLoop;
        }
      }

      // Make sure the collections aren't identical...
      //
      Collection<?> mappedObjects = mapping.getMappedObjects();
      if (!collection.containsAll(mappedObjects) || !mappedObjects.containsAll(collection))
      {
        result = mapping;
      }
    }

    return result;
  }

  public boolean isDirty() 
  {
    /**
    if (getRuleList() != null) 
    {
      return getRuleList().isDirty();
    }
    **/
    return false;
  }

  public boolean isOutputDirty() 
  {
    return outputDirty;
  }

  public void register(Mapping mapping)
  {
    for (Object input : mapping.getInputs())
    {
      MappedObjectState mappedObjectState = getMappedObjectState(input);
      if (mappedObjectState != null)
      {
        mappedObjectState.getMappings().add(mapping);
      }
    }

    for (Object output : mapping.getOutputs())
    {
      MappedObjectState mappedObjectState = getMappedObjectState(output);
      if (mappedObjectState != null)
      {
        mappedObjectState.getMappings().add(mapping);
      }
    }
  }

  public void deregister(Mapping mapping)
  {
    for (Object input : mapping.getInputs())
    {
      MappedObjectState mappedObjectState = getMappedObjectState(input);
      if (mappedObjectState != null)
      {
        mappedObjectState.getMappings().remove(mapping);
      }
    }

    for (Object output : mapping.getOutputs())
    {
      MappedObjectState mappedObjectState = getMappedObjectState(output);
      if (mappedObjectState != null)
      {
        mappedObjectState.getMappings().remove(mapping);
      }
    }
  }

  public boolean canCreateMapping(Collection<?> inputs, Collection<?> outputs, Mapping mapping)
  {
    if (domain == null)
    {
      return false;
    }
    int enablementFlags = domain.getMappingEnablementFlags();
    if ((enablementFlags & MappingDomain.ENABLE_EMPTY_INPUTS) == 0 && inputs.size() == 0 ||
        (enablementFlags & MappingDomain.ENABLE_EMPTY_OUTPUTS) == 0 && outputs.size() == 0 ||
        inputs.size() == 0 && outputs.size() == 0 ||
        (enablementFlags & MappingDomain.ENABLE_MULTIPLE_INPUTS) == 0 && inputs.size() > 1 ||
        (enablementFlags & MappingDomain.ENABLE_MULTIPLE_OUTPUTS) == 0 && outputs.size() > 1 ||
        (enablementFlags & MappingDomain.ENABLE_MULTIPLE_INPUT_MAPPINGS) == 0 && isMapped(inputs, mapping) ||
        (enablementFlags & MappingDomain.ENABLE_MULTIPLE_OUTPUT_MAPPINGS) == 0 && isMapped(outputs, mapping) ||
        (enablementFlags & MappingDomain.ENABLE_UNMAPPED_PARENTS) == 0 && !hasMappedParents(inputs, outputs) ||
        (enablementFlags & MappingDomain.ENABLE_INCOMPATIBLE_METAOBJECTS) == 0 && !hasCompatibleMetaObjects(inputs, outputs) ||
        (enablementFlags & MappingDomain.ENABLE_INCOMPATIBLE_TYPE_CLASSIFIERS) == 0 && !hasCompatibleTypes(inputs, outputs))
    {
      return false;
    }

    for (Object input : inputs)
    {
      if (!isAttachedObject(input))
      {
        return false;
      }
    }

    for (Object output :  outputs)
    {
      if (!isAttachedObject(output))
      {
        return false;
      }
    }

    return true;
  }

  public boolean canRemoveMapping(Mapping mapping)
  {
    int enablementFlags = domain.getMappingEnablementFlags();
    if (mapping.getNestedIn() == null ||
        (enablementFlags & MappingDomain.ENABLE_UNMAPPED_PARENTS) == 0 && hasMappedChildren(mapping))
    {
      return false;
    }

    return true;
  }

  protected boolean hasMappedChildren(Mapping mapping) 
  {
    return !mapping.getNested().isEmpty();
  }

  protected boolean hasMappedParents(Collection<?> inputs, Collection<?> outputs) 
  {
    Collection<Object> parents = new HashSet<Object>();
    for (Object input : inputs)
    {
      parents.add(domain.getParent(input));
    }
    for (Object output : outputs)
    {
      parents.add(domain.getParent(output));
    }
    return !getAllMappings(parents).isEmpty();
  }

  protected boolean isMapped(Collection<?> collection, Mapping mapping) 
  {
    for (Object object : collection)
    {
      Collection<? extends Mapping> mappings = getMappings(object);
      if (!mappings.isEmpty())
      {
        if (mapping == null || mappings.size() > 1 || !mappings.contains(mapping))
        {
          return true;
        }
      }
    }
    return false;
  }

  protected boolean hasCompatibleMetaObjects(Collection<?> inputs, Collection<?> outputs)
  {
    for (Object input : inputs)
    {
      EObject inputType = ((EObject)input).eClass();
      EObject convertedInputType = domain.getOutputMetaObject(inputType);
      for (Object output : outputs)
      {
        EObject outputType = ((EObject)output).eClass();
        if (convertedInputType != outputType)
        {
          return false;
        }
      }
    }
    return true;
  }

  protected boolean hasCompatibleTypes(Collection<?> inputs, Collection<?> outputs)
  {
    MappingRoot typeMappingRoot = getTypeMappingRoot();
    if (typeMappingRoot != null)
    {
      Collection<Object> inputTypes = getTypeClassifiers(inputs);
      Collection<Object> outputTypes = getTypeClassifiers(outputs);

      if (inputTypes.equals(outputTypes))
        return true;

      if (inputTypes.size() != inputs.size() || outputTypes.size() != outputs.size())
        return false;

      if (getTypeMappings(inputTypes, outputTypes).isEmpty() &&
          hasTypeMappings(inputTypes) && hasTypeMappings(outputTypes))
      {
        return false;
      }
    }
    return true;
  }

  protected Collection<?> getTypeMappings(Collection<?> inputTypes, Collection<?> outputTypes)
  {
    Collection<?> typeMappings;
    if (outputTypes.isEmpty())
    {
      typeMappings = getTypeMappingRoot().getAllMappings(inputTypes);
    }
    else
    {
      Collection<Object> allTypes = new ArrayList<Object>(inputTypes);
      allTypes.addAll(outputTypes);
      typeMappings = getTypeMappingRoot().getExactMappings(allTypes);
    }
    return typeMappings;
  }

  protected boolean hasTypeMappings(Collection<?> types)
  {
    return !getTypeMappingRoot().getAllMappings(types).isEmpty();
  }

  protected Collection<Object> getTypeClassifiers(Collection<?> collection)
  {
    Collection<Object> types = new ArrayList<Object>();
    for (Object object : collection)
    {
      Object type = domain.getTypeClassifier(object);
      if (type != null)
      {
        types.add(type);
      }
    }
    return types;
  }

  public Mapping createMapping(Collection<?> inputs, Collection<?> outputs)
  {
    Mapping newMapping = createMapping();
    initializeNewMapping(newMapping, inputs, outputs);
    return newMapping;
  }

  protected Mapping createMapping()
  {
    return MappingFactory.eINSTANCE.createMapping();
  }

  protected void initializeNewMapping(Mapping newMapping, Collection<?> inputs, Collection<?> outputs)
  {
    @SuppressWarnings("unchecked")
    Collection<EObject> eObjectInputs = (Collection<EObject>)inputs;
    @SuppressWarnings("unchecked")
    Collection<EObject> eObjectOutputs = (Collection<EObject>)outputs;
    newMapping.getInputs().addAll(eObjectInputs);
    newMapping.getOutputs().addAll(eObjectOutputs);

    if (getTypeMappingRoot() != null)
    {
      Collection<?> inputTypes = getTypeClassifiers(inputs);
      if (!inputTypes.isEmpty())
      {
        Collection<?> outputTypes = getTypeClassifiers(outputs);

        Collection<?> typeMappings = getTypeMappings(inputTypes, outputTypes);
        if (!typeMappings.isEmpty())
        {
          newMapping.setTypeMapping((Mapping)typeMappings.iterator().next());
        }
      }
    }
  }

  public void resetDirty() 
  {
    this.setOutputDirty(false);
  }

  public void setOutputDirty(boolean dirty) 
  {
    outputDirty = dirty;
  }

  public boolean isInputObject(Object object)
  {
    MappedObjectState mappedObjectState = (MappedObjectState)mappedObjectStateAdapterFactory.adapt(object, MappedObjectState.class);
    return 
      mappedObjectState != null  && mappedObjectState.isInput();
  }

  public boolean isOutputObject(Object object)
  {
    MappedObjectState mappedObjectState = (MappedObjectState)mappedObjectStateAdapterFactory.adapt(object, MappedObjectState.class);
    return 
      mappedObjectState != null  && mappedObjectState.isOutput();
  }

  public boolean isTopObject(Object object)
  {
    MappedObjectState mappedObjectState = (MappedObjectState)mappedObjectStateAdapterFactory.adapt(object, MappedObjectState.class);
    if (mappedObjectState != null)
      return isTopToBottom() ? mappedObjectState.isInput() : mappedObjectState.isOutput();
    return false;
  }

  public boolean isBottomObject(Object object)
  {
    MappedObjectState mappedObjectState = (MappedObjectState)mappedObjectStateAdapterFactory.adapt(object, MappedObjectState.class);
    if (mappedObjectState != null)
      return !isTopToBottom() ? mappedObjectState.isInput() : mappedObjectState.isOutput();
    return false;
  }

  public boolean isAttachedObject(Object object)
  {
    Object root = object;
    //FB used to check for parent != null.
    // Need to find the top most model object not including the resource.
    // parent instanceof Eobject only checks for trees in model object space.
    for (Object parent = domain.getParent(object); parent instanceof EObject; parent = domain.getParent(parent))
    {
      root = parent;
    }

    return getInputs().contains(root) || getOutputs().contains(root);
  }

  public Collection<? extends Mapping> getMappings(Object object) 
  {
    MappedObjectState mappedObjectState = getMappedObjectState(object);
    if (mappedObjectState == null)
    {
      return Collections.emptySet();
    }
    else
    {
      return  mappedObjectState.getMappings();
    }
  }

  public Collection<? extends Mapping> getAllMappings(Collection<?> collection) 
  {
    Iterator<?> objects = collection.iterator(); 
    if (objects.hasNext())
    {
      Collection<Mapping> result = new ArrayList<Mapping>(getMappings(objects.next()));
      while (objects.hasNext() && !result.isEmpty())
      {
        result.retainAll(getMappings(objects.next()));
      }
      return result;
    }
    else
    {
      return Collections.emptySet();
    }
  }

  public Collection<? extends Mapping> getExactMappings(Collection<?> collection)
  {
    Collection<Mapping> result = new ArrayList<Mapping>();
    for (Mapping mapping : getAllMappings(collection))
    {
      if (collection.containsAll(mapping.getMappedObjects()))
      {
        result.add(mapping);
      }
    }

    return result;
  }

  /**
   * This is a simple implementation of the basic information that needs to be maintained for any mapped object.
   */
  protected class MappedObjectStateAdapter extends AdapterImpl implements MappedObjectState, IDisposable
  {
    /**
     * This indicates whether the mapped object is an input.
     */
    protected boolean isInput;

    /**
     * This indicates whether the object is an output.
     */
    protected boolean isOutput;

    /**
     * This keeps track of the originating input of the mapped object, if any.
     */
    protected Object originatingInput;

    /**
     * This keeps track of all the mappings that involve the mapped object.
     */
    protected Collection<Mapping> mappings = 
      new NotifyingListImpl<Mapping>()
      {
        private static final long serialVersionUID = 1L;

        @Override
        public Object getNotifier()
        {
          return getTarget();
        }

        @Override
        protected boolean isNotificationRequired()
        {
          return true;
        }

        @Override
        protected NotificationImpl createNotification(int eventType, Object oldObject, Object newObject, int index, boolean wasSet)
        {
          Object object = oldObject == null ? newObject : oldObject;
          // Ensure that this is a touch notification so to resource aren't marked as dirty.
          //
          ENotificationImpl notification = 
            new ENotificationImpl
              ((InternalEObject)getTarget(),
               Notification.SET, 
               Notification.NO_FEATURE_ID - 1,
               object,
               object,
               Notification.NO_INDEX,
               wasSet);
          MappedObjectStateAdapter.this.fireNotifyChanged(notification);
          return notification;
        }

        @Override
        protected boolean isUnique()
        {
          return true;
        }
      };

    /**
     * This is where {@link org.eclipse.emf.edit.provider.IChangeNotifier} is delegated.
     */
    protected ChangeNotifier changeNotifier = new ChangeNotifier(); 
   
    /**
     * This returns when type is the {@link #mappedObjectStateAdapterFactory}.
     */
    @Override
    public boolean isAdapterForType(Object type)
    {
      return type == mappedObjectStateAdapterFactory;
    }

    public boolean isInput()
    {
      return isInput;
    }

    public void setInput()
    {
      isInput = true;
    }

    public boolean isOutput()
    {
      return isOutput;
    }

    public void setOutput()
    {
      isOutput = true;
      ENotificationImpl note = 
         new ENotificationImpl
           ((InternalEObject)getTarget(),
            Notification.SET, 
            Notification.NO_FEATURE_ID - 2,
            Boolean.TRUE,
            Boolean.TRUE,
            Notification.NO_INDEX);
      changeNotifier.fireNotifyChanged(note);
    }

    public Object getOriginatingInput()
    {
      return originatingInput;
    }

    public void setOriginatingInput(Object originatingInput)
    {
      setOutput();
      this.originatingInput = originatingInput;
    }

    public Collection<Mapping> getMappings()
    {
      return mappings;
    }

    public void addListener(INotifyChangedListener notifyChangedListener)
    {
      changeNotifier.addListener(notifyChangedListener);
    }
  
    public void removeListener(INotifyChangedListener notifyChangedListener)
    {
      changeNotifier.removeListener(notifyChangedListener);
    }

    public void fireNotifyChanged(Notification notification)
    {
      changeNotifier.fireNotifyChanged(notification);
    }

    public void dispose()
    {
      if (target != null)
      {
        target.eAdapters().remove(this);
      }
    }
  }

  /**
   * This uses the {@link #mappedObjectStateAdapterFactory} to get an adapter that implements this interface.
   */
  public MappedObjectState getMappedObjectState(Object object)
  {
    return (MappedObjectState)mappedObjectStateAdapterFactory.adapt(object, MappedObjectState.class);
  }

  public MappingRoot getTypeMappingRoot()
  {
    return (MappingRoot)getTypeMapping();
  }

  /**
   * By default, this creates an adapter factory that delegates {@link AdapterFactoryImpl#createAdapter(Notifier) createAdapter}
   * to {@link #createMappedObjectStateAdapter createMappedObjectStateAdapter}.
   */
  protected AdapterFactory createMappedObjectStateAdapterFactory()
  {
    return new MappedObjectStateAdapterFactory();
  }

  /**
   * This is the factory that creates adapters for the objects being mapped.
   * It must be disposed if the lifetime of the mapped objects is longer than the lifetime of the mapping root.
   */
  protected class MappedObjectStateAdapterFactory extends AdapterFactoryImpl implements IDisposable
  {
    protected Disposable disposable = new Disposable();

    public MappedObjectStateAdapterFactory()
    {
      super();
    }

    @Override
    public Adapter createAdapter(Notifier target) 
    {
      return createMappedObjectStateAdapter(target);
    }

    @Override
    public boolean isFactoryForType(Object type)
    {
      return super.isFactoryForType(type) || type == MappedObjectState.class;
    }

    @Override
    public Adapter adapt(Notifier notifier, Object type)
    {
      return super.adapt(notifier, this);
    }

    @Override
    public Object adapt(Object object, Object type)
    {
      Object result = super.adapt(object, type);
      return 
        result instanceof MappedObjectState ? result : null;
    }

    @Override
    public Adapter adaptNew(Notifier object, Object type)
    {
      Adapter result = super.adaptNew(object, type);
      disposable.add(result);
      return result;
    }

    public void dispose()
    {
      disposable.dispose();
    }
  }

  /**
   * By default, this creates a new instance of {@link MappedObjectStateAdapter}.
   */
  protected Adapter createMappedObjectStateAdapter(Notifier target)
  {
    return new MappedObjectStateAdapter();
  }

  public void dispose()
  {
    if (mappedObjectStateAdapterFactory instanceof IDisposable)
    {
      ((IDisposable)mappedObjectStateAdapterFactory).dispose();
    }

    MappingRoot typeMappingRoot = getTypeMappingRoot();
    if (typeMappingRoot != null)
    {
      typeMappingRoot.dispose();
    }

    // printAdapters();
  }

  protected void printAdapters()
  {
    walk(this);
    for (TreeIterator<Mapping> mappings = treeIterator(); mappings.hasNext(); )
    {
      for (EObject eObject : mappings.next().getMappedObjects())
      {
        walk(eObject);
      }
    }
  }

  protected void walk(EObject object)
  {
    for (EObject child : object.eContents())
    {
      Collection<Adapter> adapters = child.eAdapters();
      if (adapters != null)
      {
        boolean once = false;
        for (Object adapter : adapters)
        {
          if (adapter != null)
          {
            if (!once)
            {
              System.out.println("*** " + child);
              once = true;
            }
            System.out.println("  * " + adapter);
          }
        }
      }
      walk(child);
    }
  }

} //MappingRootImpl
