/**
 * Copyright (c) 2003-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.ecore.util;


import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.util.FeatureMap.Entry;
import org.eclipse.emf.ecore.xml.type.ProcessingInstruction;
import org.eclipse.emf.ecore.xml.type.XMLTypeFactory;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;


public final class FeatureMapUtil
{
  protected static final Class<Validator> VALIDATOR_CLASS = Validator.class;

  private FeatureMapUtil()
  {
    super();
  }

  public static void addText(FeatureMap featureMap, String text)
  {
    featureMap.add(XMLTypeFeatures.TEXT, text);
  }

  public static void addText(FeatureMap featureMap, int index, String text)
  {
    featureMap.add(index, XMLTypeFeatures.TEXT, text);
  }

  public static boolean isText(FeatureMap.Entry entry)
  {
    return entry.getEStructuralFeature() == XMLTypeFeatures.TEXT;
  }

  public static boolean isText(EStructuralFeature eStructuralFeature)
  {
    return eStructuralFeature == XMLTypeFeatures.TEXT;
  }

  public static void addCDATA(FeatureMap featureMap, String cdata)
  {
    featureMap.add(XMLTypeFeatures.CDATA, cdata);
  }

  public static void addCDATA(FeatureMap featureMap, int index, String cdata)
  {
    featureMap.add(index, XMLTypeFeatures.CDATA, cdata);
  }

  public static boolean isCDATA(FeatureMap.Entry entry)
  {
    return entry.getEStructuralFeature() == XMLTypeFeatures.CDATA;
  }

  public static boolean isCDATA(EStructuralFeature eStructuralFeature)
  {
    return eStructuralFeature == XMLTypeFeatures.CDATA;
  }

  public static void addComment(FeatureMap featureMap, String comment)
  {
    featureMap.add(XMLTypeFeatures.COMMENT, comment);
  }

  public static void addComment(FeatureMap featureMap, int index, String comment)
  {
    featureMap.add(index, XMLTypeFeatures.COMMENT, comment);
  }

  public static boolean isComment(FeatureMap.Entry entry)
  {
    return entry.getEStructuralFeature() == XMLTypeFeatures.COMMENT;
  }

  public static boolean isComment(EStructuralFeature eStructuralFeature)
  {
    return eStructuralFeature == XMLTypeFeatures.COMMENT;
  }

  public static void addProcessingInstruction(FeatureMap featureMap, String target, String data)
  {
    ProcessingInstruction processingInstruction = XMLTypeFactory.eINSTANCE.createProcessingInstruction();
    processingInstruction.setTarget(target);
    processingInstruction.setData(data);
    featureMap.add(XMLTypeFeatures.PROCESSING_INSTRUCTION, processingInstruction);
  }

  public static void addProcessingInstruction(FeatureMap featureMap, int index, String target, String data)
  {
    ProcessingInstruction processingInstruction = XMLTypeFactory.eINSTANCE.createProcessingInstruction();
    processingInstruction.setTarget(target);
    processingInstruction.setData(data);
    featureMap.add(index, XMLTypeFeatures.PROCESSING_INSTRUCTION, processingInstruction);
  }

  public static boolean isProcessingInstruction(FeatureMap.Entry entry)
  {
    return entry.getEStructuralFeature() == XMLTypeFeatures.PROCESSING_INSTRUCTION;
  }

  public static boolean isProcessingInstruction(EStructuralFeature eStructuralFeature)
  {
    return eStructuralFeature == XMLTypeFeatures.PROCESSING_INSTRUCTION;
  }

  public static boolean isFeatureMap(EStructuralFeature eStructuralFeature)
  {
    return ((EStructuralFeature.Internal)eStructuralFeature).isFeatureMap();
  }

  public static boolean isFeatureMapEntry(EClassifier eClassifier)
  {
    return eClassifier.getInstanceClassName() == "org.eclipse.emf.ecore.util.FeatureMap$Entry";
  }

  public static FeatureMap.Entry createTextEntry(String value)
  {
    return XMLTypeFeatures.TEXT_PROTOTYPE.createEntry(value);
  }

  public static FeatureMap.Entry createCDATAEntry(String value)
  {
    return XMLTypeFeatures.CDATA_PROTOTYPE.createEntry(value);
  }

  public static FeatureMap.Entry createCommentEntry(String value)
  {
    return XMLTypeFeatures.COMMENT_PROTOTYPE.createEntry(value);
  }

  public static FeatureMap.Entry createProcessingInstructionEntry(String target, String data)
  {
    return createRawProcessingInstructionEntry(target, data);
  }

  public static FeatureMap.Entry createEntry(EStructuralFeature eStructuralFeature, Object value)
  {
    FeatureMap.Entry.Internal prototype = ((EStructuralFeature.Internal)eStructuralFeature).getFeatureMapEntryPrototype();
    prototype.validate(value);
    return prototype.createEntry(value);
  }

  public static FeatureMap.Entry.Internal createRawEntry(EStructuralFeature eStructuralFeature, Object value)
  {
    FeatureMap.Entry.Internal prototype = ((EStructuralFeature.Internal)eStructuralFeature).getFeatureMapEntryPrototype();
    return prototype.createEntry(value);
  }

  public static FeatureMap.Entry.Internal createRawTextEntry(String value)
  {
    return XMLTypeFeatures.TEXT_PROTOTYPE.createEntry(value);
  }

  public static FeatureMap.Entry.Internal createRawCDATAEntry(String value)
  {
    return XMLTypeFeatures.CDATA_PROTOTYPE.createEntry(value);
  }

  public static FeatureMap.Entry.Internal createRawCommentEntry(String value)
  {
    return XMLTypeFeatures.COMMENT_PROTOTYPE.createEntry(value);
  }

  public static FeatureMap.Entry.Internal createRawProcessingInstructionEntry(String target, String data)
  {
    ProcessingInstruction processingInstruction = XMLTypeFactory.eINSTANCE.createProcessingInstruction();
    processingInstruction.setTarget(target);
    processingInstruction.setData(data);
    return XMLTypeFeatures.PROCESSING_INSTRUCTION_PROTOTYPE.createEntry(processingInstruction);
  }

  public static class EntryImpl implements FeatureMap.Entry
  {
    protected final EStructuralFeature eStructuralFeature;
    protected final Object value;

    public EntryImpl(EStructuralFeature eStructuralFeature, Object value)
    {
      this.eStructuralFeature = eStructuralFeature;
      this.value = value;
      validate();
    }

    protected void validate()
    {
      if (value != null && !eStructuralFeature.getEType().isInstance(value))
      {
        String valueClass = value instanceof EObject ? ((EObject)value).eClass().getName() : value.getClass().getName();
        throw 
          new ClassCastException
            ("The feature '" + eStructuralFeature.getName()  + "'s type '" + 
                eStructuralFeature.getEType().getName() + "' does not permit a value of type '" + valueClass + "'");
      }
    }

    public EStructuralFeature getEStructuralFeature()
    {
      return eStructuralFeature;
    }

    public Object getValue()
    {
      return value;
    }

    @Override
    public boolean equals(Object that)
    {
      if (this == that)
      {
        return true;
      }
      else if (!(that instanceof FeatureMap.Entry))
      {
        return false;
      }
      else
      {
        FeatureMap.Entry entry = (FeatureMap.Entry)that;
        return 
          entry.getEStructuralFeature() == eStructuralFeature &&
          (value == null ? entry.getValue() == null : value.equals(entry.getValue()));
      }
    }

    @Override
    public int hashCode()
    {
     return eStructuralFeature.hashCode() ^ (value == null ? 0 : value.hashCode());
    }

    @Override
    public String toString()
    {
      String prefix = eStructuralFeature.getEContainingClass().getEPackage().getNsPrefix();
      eStructuralFeature.getName();
      return 
         (prefix != null && prefix.length() != 0 ? 
            prefix + ":" + eStructuralFeature.getName() : 
            eStructuralFeature.getName()) + 
           "=" + value;
/*
      StringBuffer result = new StringBuffer(super.toString());
      result.append(" (feature: ");
      result.append(eStructuralFeature.getName());
      result.append(", value: ");
      result.append(value);
      result.append(")");
      return result.toString();
*/
    }
  }

  public static abstract class BasicFeatureEIterator<E> implements ListIterator<E>
  {
    protected final EStructuralFeature eStructuralFeature;
    protected final FeatureMap.Internal featureMap;

    protected int entryCursor;
    protected int cursor;
    protected int prepared;
    protected E preparedResult;
    protected int expectedModCount;
    protected int lastCursor;
    protected boolean isFeatureMap;
    protected Validator validator;

    public BasicFeatureEIterator(EStructuralFeature eStructuralFeature, FeatureMap.Internal featureMap)
    {
      this.eStructuralFeature = eStructuralFeature;
      this.featureMap = featureMap;
      expectedModCount = featureMap.getModCount();
      isFeatureMap = isFeatureMap(eStructuralFeature);
      validator = getValidator(featureMap.getEObject().eClass(), eStructuralFeature);
    }

    protected boolean resolve()
    {
      return false;
    }

    @SuppressWarnings("unchecked")
    protected E extractValue(FeatureMap.Entry entry)
    {
      return isFeatureMap ? (E)entry : (E)entry.getValue();
    }

    public boolean hasNext()
    {
      switch (prepared)
      {
        case 2:
        {
          return true;
        }
        case 1:
        {
          return false;
        }
        case -1:
        {
          ++entryCursor;
        }
        default:
        {
          return scanNext();
        }
      }
    }

    protected abstract boolean scanNext();
/*
    {
      FeatureMap.Entry [] entries = (FeatureMap.Entry [])featureMap.data;
      while (entryCursor < size)
      {
        FeatureMap.Entry entry = entries[entryCursor];
        if (entry.getEStructuralFeature() == eStructuralFeature)
        {
          preparedResult = entry.getValue();
          prepared = 2;
          return true;
        }
        ++entryCursor;
      }

      prepared = 1;
      lastCursor = -1;
      return false;

    }
*/

    public E next()
    {
      if (hasNext())
      {
        checkModCount();

        if (resolve())
        {
          @SuppressWarnings("unchecked") E newPreparedResult = (E)featureMap.resolveProxy(eStructuralFeature, entryCursor, cursor, preparedResult);
          preparedResult = newPreparedResult;
        }

        lastCursor = cursor;
        ++cursor;

        ++entryCursor;
        prepared = 0;
        return preparedResult;
      }
      else
      {
        throw new NoSuchElementException();
      }
    }

    public int nextIndex()
    {
      return cursor;
    }

    public boolean hasPrevious()
    {
      switch (prepared)
      {
        case -2:
        {
          return true;
        }
        case -1:
        {
          return false;
        }
        case 1:
        {
          --entryCursor;
        }
        default:
        {
          return scanPrevious();
        }
      }
    }

    protected abstract boolean scanPrevious();
/*
    {
      FeatureMap.Entry [] entries = (FeatureMap.Entry [])featureMap.data;
      while (--entryCursor >= 0)
      {
        FeatureMap.Entry entry = entries[entryCursor];
        if (entry.getEStructuralFeature() == eStructuralFeature)
        {
          preparedResult = entry.getValue();
          prepared = -2;
          return true;
        }
      }

      prepared = -1;
      lastCursor = -1;
      return false;
    }
*/

    public E previous()
    {
      if (hasPrevious())
      {
        checkModCount();
        lastCursor = --cursor;
        if (resolve())
        {
          @SuppressWarnings("unchecked") E newPreparedResult = (E)featureMap.resolveProxy(eStructuralFeature, entryCursor, cursor, preparedResult);
          preparedResult = newPreparedResult;
        }
        // --entryCursor;
        prepared = 0;
        return preparedResult;
      }
      else
      {
        throw new NoSuchElementException();
      }
    }

    public int previousIndex()
    {
      return cursor - 1;
    }

    public void add(Object o)
    {
      if (lastCursor == -1)
      {
        throw new IllegalStateException();
      }
      checkModCount();

      try
      {
        featureMap.add(eStructuralFeature, cursor, o);
        expectedModCount = featureMap.getModCount();
        next();
/*

        featureMap.add(eStructuralFeature, cursor++, o);
        expectedModCount = featureMap.getModCount();

        ++entryCursor;

        ++lastCursor;
        // lastCursor = -1;
        // prepared = 0;
*/
      }
      catch (IndexOutOfBoundsException exception)
      {
        throw new ConcurrentModificationException();
      }
    }

    public void remove()
    {
      if (lastCursor == -1)
      {
        throw new IllegalStateException();
      }
      checkModCount();

      try
      {
        featureMap.remove(eStructuralFeature, lastCursor);
        expectedModCount = featureMap.getModCount();
        if (lastCursor < cursor)
        {
          --cursor;
          --entryCursor;
        }

        --lastCursor;
        //lastCursor = -1;
        //prepared = 0;
      }
      catch (IndexOutOfBoundsException exception)
      {
        throw new ConcurrentModificationException();
      }
    }

    public void set(Object o)
    {
      if (lastCursor == -1)
      {
        throw new IllegalStateException();
      }
      checkModCount();

      try
      {
        featureMap.set(eStructuralFeature, lastCursor, o);
        expectedModCount = featureMap.getModCount();
      }
      catch (IndexOutOfBoundsException exception)
      {
        throw new ConcurrentModificationException();
      }
    }

    /**
     * Checks that the modification count is as expected.
     * @exception ConcurrentModificationException if the modification count is not as expected.
     */
    protected void checkModCount()
    {
      if (featureMap.getModCount() != expectedModCount)
      {
        throw new ConcurrentModificationException();
      }
    }
  }

  public static class FeatureEList<E>
    extends AbstractList<E>
    implements InternalEList.Unsettable<E>, EStructuralFeature.Setting
  {
    public static class Basic<E> extends FeatureEList<E>
    {
      public Basic(EStructuralFeature feature, FeatureMap.Internal featureMap)
      {
        super(feature, featureMap);
      }

      @Override
      public Iterator<E> iterator()
      {
        return this.basicIterator();
      }

      @Override
      public ListIterator<E> listIterator()
      {
        return this.basicListIterator();
      }

      @Override
      public ListIterator<E> listIterator(int index)
      {
        return this.basicListIterator(index);
      }

      @Override
      public List<E> basicList()
      {
        return this;
      }
    }

    protected EStructuralFeature feature;
    protected FeatureMap.Internal featureMap;

    public FeatureEList(EStructuralFeature feature, FeatureMap.Internal featureMap)
    {
      this.feature= feature;
      this.featureMap = featureMap;
    }

    @Override
    public int size()
    {
      return featureMap.size(getEStructuralFeature());
    }

    @Override
    public boolean isEmpty()
    {
      return featureMap.isEmpty(getEStructuralFeature());
    }

    @Override
    public boolean contains(Object object)
    {
      return featureMap.contains(getEStructuralFeature(), object);
    }

    @Override
    public int indexOf(Object object)
    {
      return featureMap.indexOf(getEStructuralFeature(), object);
    }

    @Override
    public int lastIndexOf(Object object)
    {
      return featureMap.lastIndexOf(getEStructuralFeature(), object);
    }

    @Override
    public boolean containsAll(Collection<?> collection)
    {
      return featureMap.containsAll(getEStructuralFeature(), collection);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Iterator<E> iterator()
    {
      return (Iterator<E>)featureMap.iterator(getEStructuralFeature());
    }

    @SuppressWarnings("unchecked")
    @Override
    public ListIterator<E> listIterator()
    {
      return (ListIterator<E>)featureMap.listIterator(getEStructuralFeature());
    }

    @SuppressWarnings("unchecked")
    @Override
    public ListIterator<E> listIterator(int index)
    {
      return (ListIterator<E>)featureMap.listIterator(getEStructuralFeature(), index);
    }

/*
    public List subList(int from, int to)
    {
      return featureMap.subList(getEStructuralFeature(), from, to);
    }
*/

    @SuppressWarnings("unchecked")
    public E basicGet(int index)
    {
      return (E)featureMap.get(getEStructuralFeature(), index, false);
    }

    @SuppressWarnings("unchecked")
    public List<E> basicList()
    {
      return (List<E>)featureMap.basicList(getEStructuralFeature());
    }

    @SuppressWarnings("unchecked")
    public Iterator<E> basicIterator()
    {
      return (Iterator<E>)featureMap.basicIterator(getEStructuralFeature());
    }

    @SuppressWarnings("unchecked")
    public ListIterator<E> basicListIterator()
    {
      return (ListIterator<E>)featureMap.basicListIterator(getEStructuralFeature());
    }

    @SuppressWarnings("unchecked")
    public ListIterator<E> basicListIterator(int index)
    {
      return (ListIterator<E>)featureMap.basicListIterator(getEStructuralFeature(), index);
    }

    @Override
    public Object[] toArray()
    {
      return featureMap.toArray(getEStructuralFeature());
    }

    @Override
    public <T>  T [] toArray(T[] array)
    {
      return featureMap.toArray(getEStructuralFeature(), array);
    }

    public boolean basicContains(Object object)
    {
      return featureMap.basicContains(getEStructuralFeature(), object);
    }

    public boolean basicContainsAll(Collection<?> collection)
    {
      return featureMap.basicContainsAll(getEStructuralFeature(), collection);
    }

    public int basicIndexOf(Object object)
    {
      return featureMap.basicIndexOf(getEStructuralFeature(), object);
    }

    public int basicLastIndexOf(Object object)
    {
      return featureMap.basicLastIndexOf(getEStructuralFeature(), object);
    }

    public Object[] basicToArray()
    {
      return featureMap.basicToArray(getEStructuralFeature());
    }

    public <T> T[] basicToArray(T[] array)
    {
      return featureMap.basicToArray(getEStructuralFeature(), array);
    }

    @Override
    public boolean add(Object object)
    {
      return featureMap.add(getEStructuralFeature(), object);
    }

    @Override
    public void add(int index, Object object)
    {
      featureMap.add(getEStructuralFeature(), index, object);
    }

    @Override
    public boolean addAll(Collection<? extends E> collection)
    {
      return featureMap.addAll(getEStructuralFeature(), collection);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> collection)
    {
      return featureMap.addAll(getEStructuralFeature(), index, collection);
    }

    public void addUnique(Object object)
    {
      featureMap.addUnique(getEStructuralFeature(), object);
    }

    public void addUnique(int index, Object object)
    {
      featureMap.addUnique(getEStructuralFeature(), index, object);
    }
    
    @SuppressWarnings("unchecked")
    public boolean addAllUnique(Collection<? extends E> collection)
    {
      modCount = -1;
      return featureMap.addAllUnique((Collection<? extends Entry>)collection);
    }

    @SuppressWarnings("unchecked")
    public boolean addAllUnique(int index, Collection<? extends E> collection)
    {
      modCount = -1;
      return featureMap.addAllUnique(index, (Collection<? extends Entry>)collection);
    }
    
    public void addUnique(Entry.Internal entry)
    {
      modCount = -1;
      featureMap.addUnique(entry);
    }

    public boolean addAllUnique(Entry.Internal [] entries, int start, int end)
    {
      return addAllUnique(size(), entries, start, end);
    }

    public boolean addAllUnique(int index, Entry.Internal [] entries, int start, int end)
    {
      modCount = -1;
      BasicEList<Entry.Internal> collection = new BasicEList<Entry.Internal>();
      if (start == 0)
      {
        collection.setData(end, entries);
      }
      else
      {
        collection.grow(end - start);
        for (int i = start; i < end; ++i)
        {
          collection.add(entries[i]);
        }
      }
      return featureMap.addAllUnique(index, collection);
    }

    public NotificationChain basicAdd(E object, NotificationChain notifications)
    {
      return featureMap.basicAdd(getEStructuralFeature(), object, notifications);
    }

    @Override
    public boolean remove(Object object)
    {
      return featureMap.remove(getEStructuralFeature(), object);
    }

    @SuppressWarnings("unchecked")
    @Override
    public E remove(int index)
    {
      return (E)featureMap.remove(getEStructuralFeature(), index);
    }

    public NotificationChain basicRemove(Object object, NotificationChain notifications)
    {
      return featureMap.basicRemove(getEStructuralFeature(), object, notifications);
    }

    @Override
    public boolean removeAll(Collection<?> collection)
    {
      return featureMap.removeAll(getEStructuralFeature(), collection);
    }

    @Override
    public boolean retainAll(Collection<?> collection)
    {
      return featureMap.retainAll(getEStructuralFeature(), collection);
    }

    @Override
    public void clear()
    {
      featureMap.clear(getEStructuralFeature());
    }

    public void move(int index, Object object)
    {
      featureMap.move(getEStructuralFeature(), index, object);
    }

    @SuppressWarnings("unchecked")
    public E move(int targetIndex, int sourceIndex)
    {
      return (E)featureMap.move(getEStructuralFeature(), targetIndex, sourceIndex);
    }

    @SuppressWarnings("unchecked")
    @Override
    public E get(int index)
    {
      return (E)featureMap.get(getEStructuralFeature(), index, true);
    }

    @SuppressWarnings("unchecked")
    @Override
    public E set(int index, E object)
    {
      return (E)featureMap.set(getEStructuralFeature(), index, object);
    }

    @SuppressWarnings("unchecked")
    public E setUnique(int index, E object)
    {
      return (E)featureMap.setUnique(getEStructuralFeature(), index, object);
    }

    public Object get(boolean resolve)
    {
      return this;
    }

    @SuppressWarnings("unchecked")
    public void set(Object newValue)
    {
      clear();
      addAll((List<? extends E>)newValue);
    }

    public boolean isSet()
    {
      return !isEmpty();
    }

    public void unset()
    {
      clear();
    }

    public Object getFeature()
    {
      return getEStructuralFeature();
    }

    public int getFeatureID()
    {
      return featureMap.getEObject().eClass().getFeatureID(getEStructuralFeature());
    }

    public EStructuralFeature getEStructuralFeature()
    {
      return feature;
    }

    public EObject getEObject()
    {
      return featureMap.getEObject();
    }

    @Override
    public String toString()
    {
      StringBuilder stringBuffer = new StringBuilder();
      stringBuffer.append("[");
      for (Iterator<E> i = basicIterator(); i.hasNext(); )
      {
        stringBuffer.append(String.valueOf(i.next()));
        if (i.hasNext())
        {
          stringBuffer.append(", ");
        }
      }
      stringBuffer.append("]");
      return stringBuffer.toString();
    }
  }

  public static class FeatureFeatureMap extends FeatureEList<FeatureMap.Entry> implements FeatureMap.Internal, FeatureMap.Internal.Wrapper
  {
    protected FeatureMap.Internal.Wrapper wrapper = this;
    
    public FeatureFeatureMap(EStructuralFeature feature, FeatureMap.Internal featureMap)
    {
      super(feature, featureMap);
    }
    
    public FeatureMap.ValueListIterator<Object> valueListIterator()
    {
      return featureMap.valueListIterator();
    }
    
    public FeatureMap.ValueListIterator<Object> valueListIterator(int index)
    {
      return featureMap.valueListIterator(index);
    }

    public <T> EList<T> list(EStructuralFeature feature)
    {
      return featureMap.list(feature);
    }

    public EStructuralFeature getEStructuralFeature(int index)
    {
      return ((Entry)featureMap.get(getEStructuralFeature(), index, false)).getEStructuralFeature();
    }

    public Object getValue(int index)
    {
      return ((Entry)featureMap.get(getEStructuralFeature(), index, false)).getValue();
    }

    public Object setValue(int index, Object value)
    {
      Entry entry = (Entry)featureMap.get(getEStructuralFeature(), index, false);
      set(index, createEntry(entry.getEStructuralFeature(), value));
      return entry.getValue();
    }

    public boolean add(EStructuralFeature feature, Object value)
    {
      return featureMap.add(feature, value);
    }

    public void add(int index, EStructuralFeature feature, Object value)
    {
      add(index, isFeatureMap(feature) ? value : createEntry(feature, value));
    }

    public void add(EStructuralFeature feature, int index, Object value)
    {
      featureMap.add(feature, index, value);
    }

    public boolean addAll(EStructuralFeature feature, Collection<?> values)
    {
      return featureMap.addAll(feature, values);
    }

    public boolean addAll(int index, EStructuralFeature feature, Collection<?> values)
    {
      if (isFeatureMap(feature))
      {
        @SuppressWarnings("unchecked") Collection<? extends Entry> entryValues = (Collection<? extends Entry>)values;
        return addAll(index, entryValues);
      }
      else
      {
        Collection<Entry> entries = new ArrayList<Entry>(values.size());
        for (Object value : values)
        {
          entries.add(createEntry(feature, value));
        }
        return addAll(index, entries);
      }
    }

    public boolean addAll(EStructuralFeature feature, int index, Collection<?> values)
    {
      return featureMap.addAll(feature, index, values);
    }

    public int getModCount()
    {
      return featureMap.getModCount();
    }

    @Override
    public EObject getEObject()
    {
      return featureMap.getEObject();
    }

    public Object resolveProxy(EStructuralFeature feature, int entryIndex, int index, Object object)
    {
      return featureMap.resolveProxy(feature, entryIndex, index, object);
    }

    public int size(EStructuralFeature feature)
    {
      return featureMap.size(feature);
    }

    public boolean isEmpty(EStructuralFeature feature)
    {
      return featureMap.isEmpty(feature);
    }

    public boolean contains(EStructuralFeature feature, Object object)
    {
      return featureMap.contains(feature, object);
    }

    public boolean containsAll(EStructuralFeature feature, Collection<?> collection)
    {
      return featureMap.containsAll(feature, collection);
    }

    public int indexOf(EStructuralFeature feature, Object object)
    {
      return featureMap.indexOf(feature, object);
    }

    public int lastIndexOf(EStructuralFeature feature, Object object)
    {
      return featureMap.lastIndexOf(feature, object);
    }

    public Iterator<Object> iterator(EStructuralFeature feature)
    {
      return featureMap.iterator(feature);
    }

    public ListIterator<Object> listIterator(EStructuralFeature feature)
    {
      return featureMap.listIterator(feature);
    }

    public ListIterator<Object> listIterator(EStructuralFeature feature, int index)
    {
      return featureMap.listIterator(feature, index);
    }

    // List subList(EStructuralFeature feature, int from, int to);
    // EList list(EStructuralFeature feature);
    public EStructuralFeature.Setting setting(EStructuralFeature feature)
    {
      return featureMap.setting(feature);
    }

    public List<Object> basicList(EStructuralFeature feature)
    {
      return featureMap.basicList(feature);
    }

    public Iterator<Object> basicIterator(EStructuralFeature feature)
    {
      return featureMap.basicIterator(feature);
    }

    public ListIterator<Object> basicListIterator(EStructuralFeature feature)
    {
      return featureMap.basicListIterator(feature);
    }

    public ListIterator<Object> basicListIterator(EStructuralFeature feature, int index)
    {
      return featureMap.basicListIterator(feature, index);
    }

    public Object[] toArray(EStructuralFeature feature)
    {
      return featureMap.toArray(feature);
    }

    public <T> T[] toArray(EStructuralFeature feature, T [] array)
    {
      return featureMap.toArray(feature, array);
    }

    public boolean basicContains(EStructuralFeature feature, Object object)
    {
      return featureMap.basicContains(feature, object);
    }

    public boolean basicContainsAll(EStructuralFeature feature, Collection<?> collection)
    {
      return featureMap.basicContainsAll(feature, collection);
    }

    public int basicIndexOf(EStructuralFeature feature, Object object)
    {
      return featureMap.basicIndexOf(feature, object);
    }

    public int basicLastIndexOf(EStructuralFeature feature, Object object)
    {
      return featureMap.basicLastIndexOf(feature, object);
    }

    public Object[] basicToArray(EStructuralFeature feature)
    {
      return featureMap.basicToArray(feature);
    }

    public <T> T[] basicToArray(EStructuralFeature feature, T[] array)
    {
      return featureMap.basicToArray(feature, array);
    }

/*
    public boolean add(EStructuralFeature feature, Object object)
    {
      return featureMap.add(feature, object);
    }

    public void add(EStructuralFeature feature, int index, Object object)
    {
      featureMap.add(feature, index, object);
    }

    public boolean addAll(EStructuralFeature feature, Collection collection)
    {
      return featureMap.addAll(feature, collection);
    }

    boolean addAll(EStructuralFeature feature, int index, Collection collection)
    {
      return featureMap.addAll(feature, index, collection);
    }
*/

    public void addUnique(EStructuralFeature feature, Object object)
    {
      featureMap.addUnique(feature, object);
    }

    public void addUnique(EStructuralFeature feature, int index, Object object)
    {
      featureMap.addUnique(feature, index, object);
    }

    public NotificationChain basicAdd(EStructuralFeature feature, Object object, NotificationChain notifications)
    {
      return featureMap.basicAdd(feature, object, notifications);
    }

    public boolean remove(EStructuralFeature feature, Object object)
    {
      return featureMap.remove(feature, object);
    }

    public Object remove(EStructuralFeature feature, int index)
    {
      return featureMap.remove(feature, index);
    }

    public boolean removeAll(EStructuralFeature feature, Collection<?> collection)
    {
      return featureMap.removeAll(feature, collection);
    }

    public NotificationChain basicRemove(EStructuralFeature feature, Object object, NotificationChain notifications)
    {
      return featureMap.basicRemove(feature, object, notifications);
    }

    public boolean retainAll(EStructuralFeature feature, Collection<?> collection)
    {
      return featureMap.retainAll(feature, collection);
    }

    public void clear(EStructuralFeature feature)
    {
      featureMap.clear(feature);
    }

    public void move(EStructuralFeature feature, int index, Object object)
    {
      featureMap.move(feature, index, object);
    }

    public Object move(EStructuralFeature feature, int targetIndex, int sourceIndex)
    {
      return featureMap.move(feature, targetIndex, sourceIndex);
    }

    public Object get(EStructuralFeature feature, boolean resolve)
    {
      return featureMap.get(feature, resolve);
    }

    public Object get(EStructuralFeature feature, int index, boolean resolve)
    {
      return featureMap.get(feature, index, resolve);
    }

    public void set(EStructuralFeature feature, Object object)
    {
      featureMap.set(feature, object);
    }

    public Object set(EStructuralFeature feature, int index, Object object)
    {
      return featureMap.set(feature, index, object);
    }

    public Object setUnique(EStructuralFeature feature, int index, Object object)
    {
      return featureMap.setUnique(feature, index, object);
    }

    public boolean isSet(EStructuralFeature feature)
    {
      return featureMap.isSet(feature);
    }

    public void unset(EStructuralFeature feature)
    {
      featureMap.unset(feature);
    }
    
    public Wrapper getWrapper()
    {
      return wrapper;
    }

    public void setWrapper(Wrapper wrapper)
    {
      this.wrapper = wrapper;
    }

    public FeatureMap featureMap()
    {
      return this;
    }
  }

  public static class FeatureValue implements EStructuralFeature.Setting
  {
    protected EStructuralFeature feature;
    protected FeatureMap.Internal featureMap;

    public FeatureValue(EStructuralFeature feature, FeatureMap.Internal featureMap)
    {
      this.feature = feature;
      this.featureMap = featureMap;
    }

    public Object get(boolean resolve)
    {
      return featureMap.get(getEStructuralFeature(), -1, resolve);
    }

    public void set(Object newValue)
    {
      featureMap.set(getEStructuralFeature(), newValue);
    }

    public boolean isSet()
    {
      return !featureMap.isEmpty(getEStructuralFeature());
    }

    public void unset()
    {
      featureMap.clear(getEStructuralFeature());
    }

    public Object getFeature()
    {
      return getEStructuralFeature();
    }

    public int getFeatureID()
    {
      return featureMap.getEObject().eClass().getFeatureID(getEStructuralFeature());
    }

    public EStructuralFeature getEStructuralFeature()
    {
      return feature;
    }

    public EObject getEObject()
    {
      return featureMap.getEObject();
    }
  }

  public static class FeatureENotificationImpl extends ENotificationImpl
  {
    public FeatureENotificationImpl
      (InternalEObject owner, int eventType, EStructuralFeature feature, Object oldObject, Object newObject, int index)
    {
      super(owner, eventType, feature, oldObject, newObject, index);
    }

    public FeatureENotificationImpl
      (InternalEObject owner, int eventType, EStructuralFeature feature, Object oldObject, Object newObject, int index, boolean wasSet)
    {
      super(owner, eventType, feature, oldObject, newObject, index, wasSet);
    }
    
    @Override
    public int getFeatureID(Class<?> expectedClass)
    {
      if (featureID == NO_FEATURE_ID && feature != null)
      {
        Class<?> containerClass = feature.getContainerClass();
        featureID = containerClass == null ? 
          notifier.eClass().getFeatureID(feature) : 
          notifier.eDerivedStructuralFeatureID(feature.getFeatureID(), containerClass);
      }
      return notifier.eBaseStructuralFeatureID(featureID, expectedClass);
    }

    @Override
    public boolean merge(Notification notification)
    {
      switch (eventType)
      {
        case Notification.SET:
        case Notification.UNSET:
        {
          Object notificationNotifier = notification.getNotifier();
          if (notificationNotifier == getNotifier() && getFeatureID(null) == notification.getFeatureID(null))
          {
            newValue = notification.getNewValue();
            if (notification.getEventType() == Notification.SET)
            {
              eventType = Notification.SET;
            }
            return true;
          }
          break;
        }
        case Notification.ADD:
        {
          int notificationEventType = notification.getEventType();
          switch (notificationEventType)
          {
            case Notification.ADD:
            {
              Object notificationNotifier = notification.getNotifier();
              if (notificationNotifier == getNotifier() && getFeatureID(null) == notification.getFeatureID(null))
              {
                eventType = Notification.ADD_MANY;
                BasicEList<Object> addedValues = new BasicEList<Object>(2);
                addedValues.add(newValue);
                addedValues.add(notification.getNewValue());
                newValue = addedValues;
                return true;
              }
              break;
            }
          }
          break;
        }
        case Notification.ADD_MANY:
        {
          int notificationEventType = notification.getEventType();
          switch (notificationEventType)
          {
            case Notification.ADD:
            {
              Object notificationNotifier = notification.getNotifier();
              if (notificationNotifier == getNotifier() && getFeatureID(null) == notification.getFeatureID(null))
              {
                @SuppressWarnings("unchecked") Collection<Object> collection = (Collection<Object>)newValue;
                collection.add(notification.getNewValue());
                return true;
              }
              break;
            }
          }
          break;
        }
        case Notification.REMOVE:
        {
          int notificationEventType = notification.getEventType();
          switch (notificationEventType)
          {
            case Notification.ADD:
            {
              Object notificationNotifier = notification.getNotifier();
              if (notificationNotifier == getNotifier() && getFeatureID(null) == notification.getFeatureID(null))
              {
                eventType = Notification.SET;
                newValue = notification.getNewValue();
                return true;
              }
              break;
            }
            case Notification.REMOVE:
            {
              Object notificationNotifier = notification.getNotifier();
              if (notificationNotifier == getNotifier() && getFeatureID(null) == notification.getFeatureID(null))
              {
                eventType = Notification.REMOVE_MANY;
                BasicEList<Object> removedValues = new BasicEList<Object>(2);
                removedValues.add(oldValue);
                removedValues.add(notification.getOldValue());
                oldValue = removedValues;

                int [] positions = new int [] { position, notification.getPosition() };
                newValue = positions;
                return true;
              }
              break;
            }
          }
          break;
        }
        case Notification.REMOVE_MANY:
        {
          int notificationEventType = notification.getEventType();
          switch (notificationEventType)
          {
            case Notification.REMOVE:
            {
              Object notificationNotifier = notification.getNotifier();
              if (notificationNotifier == getNotifier() && getFeatureID(null) == notification.getFeatureID(null))
              {
                @SuppressWarnings("unchecked") Collection<Object> collection = ((Collection<Object>)oldValue);
                collection.add(notification.getOldValue());

                int [] positions = (int [])newValue;
                int [] newPositions = new int [positions.length + 1];

                System.arraycopy(positions, 0, newPositions, 0, positions.length);
                newPositions[positions.length] = notification.getPosition();
                newValue = newPositions;

                return true;
              }
              break;
            }
          }
          break;
        }
      }

      return false;
    }
  }

  public interface Validator
  {
    boolean isValid(EStructuralFeature feature);
  }

  public static class BasicValidator implements Validator
  {
    protected static final List<String> ANY_WILDCARD = Collections.singletonList("##any");

    protected EClass containingClass;
    protected EStructuralFeature eStructuralFeature;
    protected List<EStructuralFeature> groupMembers;
    protected List<String> wildcards;
    protected String name;
    protected boolean isElement;
    
    protected class Cache extends WeakHashMap<Object, Object>
    {
      public Boolean get(EStructuralFeature eStructuralFeature)
      {
        return (Boolean)get((Object)eStructuralFeature);
      }

      public void put(EStructuralFeature eStructuralFeature, Boolean isValid)
      {
        Cache newCache = new Cache();
        newCache.putAll(cache);
        newCache.put((Object)eStructuralFeature, (Object)isValid);
        cache = newCache;
      }
   }

    protected Cache cache = new Cache();

    public BasicValidator(EClass containingClass, EStructuralFeature eStructuralFeature)
    {
      this.containingClass = containingClass;
      this.eStructuralFeature = eStructuralFeature;

      wildcards = ExtendedMetaData.INSTANCE.getWildcards(eStructuralFeature);
      EStructuralFeature mixedFeature;
      if (!wildcards.isEmpty())
      {
        isElement = ExtendedMetaData.INSTANCE.getFeatureKind(eStructuralFeature) == ExtendedMetaData.ELEMENT_WILDCARD_FEATURE;
        if (wildcards.equals(ANY_WILDCARD))
        {
          wildcards = ANY_WILDCARD;
        }
      }
      else if ((mixedFeature = ExtendedMetaData.INSTANCE.getMixedFeature(containingClass)) == eStructuralFeature)
      {
        isElement = true;
        groupMembers = new ArrayList<EStructuralFeature>();
        wildcards = new UniqueEList<String>();
        wildcards.add(XMLTypePackage.eNS_URI);
        if (ExtendedMetaData.INSTANCE.getDocumentRoot(containingClass.getEPackage()) == containingClass)
        {
          wildcards.add(ExtendedMetaData.INSTANCE.getNamespace(containingClass));
        }
        for (EStructuralFeature feature : ExtendedMetaData.INSTANCE.getAllElements(containingClass))
        {
          switch (ExtendedMetaData.INSTANCE.getFeatureKind(feature))
          {
            case ExtendedMetaData.ELEMENT_FEATURE:
            {
              groupMembers.add(feature);
              break;
            }
            case ExtendedMetaData.ELEMENT_WILDCARD_FEATURE:
            {
              wildcards.addAll(ExtendedMetaData.INSTANCE.getWildcards(feature));
              break;
            }
          }
        }
      }
      else if (isFeatureMap(eStructuralFeature))
      {
        isElement = true;
        wildcards = null;
        groupMembers = new ArrayList<EStructuralFeature>();
        for (int i = 0, size = containingClass.getFeatureCount(); i < size; ++i)
        {
          EStructuralFeature feature = containingClass.getEStructuralFeature(i);
          for (EStructuralFeature group = ExtendedMetaData.INSTANCE.getGroup(feature); 
               group != null; 
               group = ExtendedMetaData.INSTANCE.getGroup(group))
          {
            if (group == eStructuralFeature)
            {
              groupMembers.add(feature);
            }
          }
        }
      }
      else if (ExtendedMetaData.INSTANCE.getFeatureKind(eStructuralFeature) == ExtendedMetaData.SIMPLE_FEATURE &&
                 mixedFeature != null)
      {
        wildcards = null;
        groupMembers = XMLTypeFeatures.TEXTUAL_FEATURES;
      }
      else
      {
        wildcards = null;
        isElement = true;
        groupMembers = Collections.singletonList(eStructuralFeature);
      }
    }

    public boolean isValid(EStructuralFeature feature)
    {
      if (eStructuralFeature == feature) return true;

      Boolean result = cache.get(feature);
      if (result == null)
      {
        if (isIncluded(feature))
        {
          cache.put(feature, Boolean.TRUE);
          return true;
        }
        else
        {
          cache.put(feature, Boolean.FALSE);
          return false;
        }
      }
      else
      {
        return result == Boolean.TRUE;
      }
    }

    public boolean isIncluded(EStructuralFeature feature)
    {
      if (wildcards == ANY_WILDCARD)
      {
        int featureKind = ExtendedMetaData.INSTANCE.getFeatureKind(feature);
        return 
          isElement ? 
            featureKind == ExtendedMetaData.ELEMENT_FEATURE && 
              feature != XMLTypeFeatures.TEXT && feature != XMLTypeFeatures.CDATA && feature != XMLTypeFeatures.COMMENT && feature != XMLTypeFeatures.PROCESSING_INSTRUCTION :
            featureKind == ExtendedMetaData.ATTRIBUTE_FEATURE;
      }

      if (groupMembers != null &&
            (groupMembers.contains(feature) ||
               groupMembers.contains(ExtendedMetaData.INSTANCE.getGroup(feature)) ||
               groupMembers.contains(ExtendedMetaData.INSTANCE.getAffiliation(containingClass, feature))))
      {
        return true;
      }

      if (wildcards != null)
      {
        if (ExtendedMetaData.INSTANCE.matches(wildcards, ExtendedMetaData.INSTANCE.getNamespace(feature)))
        {
          int featureKind = ExtendedMetaData.INSTANCE.getFeatureKind(feature);
          return isElement ? featureKind == ExtendedMetaData.ELEMENT_FEATURE : featureKind == ExtendedMetaData.ATTRIBUTE_FEATURE;
        }
      }

      return false;
    }
  }

  protected static Validator NULL_VALIDATOR = 
    new Validator()
    {
      public boolean isValid(EStructuralFeature eStructuralFeature)
      {
        return true;
      }
    };

  public static Validator getValidator(EClass containingClass, EStructuralFeature eStructuralFeature)
  {
    if (eStructuralFeature == null)
    {
      return NULL_VALIDATOR;
    }
    else if (eStructuralFeature == XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__MIXED ||
              (eStructuralFeature == XMLTypePackage.Literals.ANY_TYPE__MIXED ||
                 eStructuralFeature == XMLTypePackage.Literals.ANY_TYPE__ANY ||
                 eStructuralFeature == XMLTypePackage.Literals.ANY_TYPE__ANY_ATTRIBUTE) && containingClass != XMLTypePackage.Literals.ANY_TYPE)
    {
      // Don't cache this one as it will cause leaks because to dynamic demand created document roots and demand created EClasses.
      //
      return new BasicValidator(containingClass, eStructuralFeature);
    }
    else
    {
      BasicExtendedMetaData.EStructuralFeatureExtendedMetaData.Holder holder = 
        (BasicExtendedMetaData.EStructuralFeatureExtendedMetaData.Holder)eStructuralFeature;
      BasicExtendedMetaData.EStructuralFeatureExtendedMetaData extendedMetaData = holder.getExtendedMetaData();
      if (extendedMetaData == null)
      {
        // For the extended meta data to be created.
        //
        ExtendedMetaData.INSTANCE.getName(eStructuralFeature);
        extendedMetaData = holder.getExtendedMetaData();
      }
      Map<EClass, Validator> validatorMap = extendedMetaData.getValidatorMap();
      Validator result = validatorMap.get(containingClass);
      if (result == null)
      {
        validatorMap.put(containingClass, result = new BasicValidator(containingClass, eStructuralFeature));
      }
      return result;
    }
  }

  public static boolean isMany(EObject owner, EStructuralFeature feature)
  {
    if (feature.isMany())
    {
      return true;
    }
    else if (feature.getUpperBound() == ETypedElement.UNSPECIFIED_MULTIPLICITY)
    {
      if (feature == XMLTypeFeatures.TEXT ||
            feature == XMLTypeFeatures.CDATA ||
            feature == XMLTypeFeatures.COMMENT ||
            feature == XMLTypeFeatures.PROCESSING_INSTRUCTION)
      {
        return true;
      }
      else
      {
        EClass eClass = owner.eClass();
        if (eClass.getFeatureID(feature) >= 0)
        {
          return false;
        }
        else
        {
          EStructuralFeature affiliation = ExtendedMetaData.INSTANCE.getAffiliation(eClass, feature);
          if (affiliation == null)
          {
            return true;
          }
          else
          {
            int affiliationUpperBound = affiliation.getUpperBound();
            return 
              (affiliationUpperBound > 1 || affiliationUpperBound == ETypedElement.UNBOUNDED_MULTIPLICITY) && 
                ExtendedMetaData.INSTANCE.getFeatureKind(affiliation) != ExtendedMetaData.ATTRIBUTE_WILDCARD_FEATURE;
          }
        }
      }
    }
    else
    {
      return false;
    }
  }
}

final class XMLTypeFeatures
{
  public static final EStructuralFeature TEXT = XMLTypePackage.eINSTANCE.getXMLTypeDocumentRoot_Text();
  public static final EStructuralFeature CDATA = XMLTypePackage.eINSTANCE.getXMLTypeDocumentRoot_CDATA();
  public static final EStructuralFeature COMMENT = XMLTypePackage.eINSTANCE.getXMLTypeDocumentRoot_Comment();
  public static final EStructuralFeature PROCESSING_INSTRUCTION = XMLTypePackage.eINSTANCE.getXMLTypeDocumentRoot_ProcessingInstruction();
  public static final FeatureMap.Entry.Internal TEXT_PROTOTYPE = ((EStructuralFeature.Internal)TEXT).getFeatureMapEntryPrototype();
  public static final FeatureMap.Entry.Internal CDATA_PROTOTYPE = ((EStructuralFeature.Internal)CDATA).getFeatureMapEntryPrototype();
  public static final FeatureMap.Entry.Internal COMMENT_PROTOTYPE = ((EStructuralFeature.Internal)COMMENT).getFeatureMapEntryPrototype();
  public static final FeatureMap.Entry.Internal PROCESSING_INSTRUCTION_PROTOTYPE = ((EStructuralFeature.Internal)PROCESSING_INSTRUCTION).getFeatureMapEntryPrototype();

  public static final List<EStructuralFeature> TEXTUAL_FEATURES = Arrays.asList(new EStructuralFeature [] {TEXT, CDATA});
}

