/**
 * 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 v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.xsd.impl;


import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

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.UniqueEList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.xsd.XSDComponent;
import org.eclipse.xsd.XSDCompositor;
import org.eclipse.xsd.XSDConcreteComponent;
import org.eclipse.xsd.XSDConstraint;
import org.eclipse.xsd.XSDDiagnostic;
import org.eclipse.xsd.XSDDiagnosticSeverity;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDFactory;
import org.eclipse.xsd.XSDModelGroup;
import org.eclipse.xsd.XSDModelGroupDefinition;
import org.eclipse.xsd.XSDPackage;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDParticleContent;
import org.eclipse.xsd.XSDPlugin;
import org.eclipse.xsd.XSDSimpleTypeDefinition;
import org.eclipse.xsd.XSDTerm;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.XSDWildcard;
import org.eclipse.xsd.util.XSDConstants;
import org.eclipse.xsd.util.XSDSwitch;
import org.w3c.dom.Element;
import org.w3c.dom.Node;


/**
 * <!-- begin-user-doc -->
 * An implementation of the model object '<em><b>Particle</b></em>'.
 * <!-- end-user-doc -->
 * <p>
 * The following features are implemented:
 * <ul>
 *   <li>{@link org.eclipse.xsd.impl.XSDParticleImpl#getMinOccurs <em>Min Occurs</em>}</li>
 *   <li>{@link org.eclipse.xsd.impl.XSDParticleImpl#getMaxOccurs <em>Max Occurs</em>}</li>
 *   <li>{@link org.eclipse.xsd.impl.XSDParticleImpl#getContent <em>Content</em>}</li>
 *   <li>{@link org.eclipse.xsd.impl.XSDParticleImpl#getTerm <em>Term</em>}</li>
 * </ul>
 * </p>
 *
 * @generated
 */
public class XSDParticleImpl 
  extends XSDComplexTypeContentImpl 
  implements XSDParticle
{
  protected static final int MAXIMUM_STATES = 10000;

  /**
   * The default value of the '{@link #getMinOccurs() <em>Min Occurs</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getMinOccurs()
   * @generated
   * @ordered
   */
  protected static final int MIN_OCCURS_EDEFAULT = 1;

  /**
   * The cached value of the '{@link #getMinOccurs() <em>Min Occurs</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getMinOccurs()
   * @generated
   * @ordered
   */
  protected int minOccurs = MIN_OCCURS_EDEFAULT;

  /**
   * The flag representing whether the Min Occurs attribute has been set.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   * @ordered
   */
  protected static final int MIN_OCCURS_ESETFLAG = 1 << 8;

  /**
   * The default value of the '{@link #getMaxOccurs() <em>Max Occurs</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getMaxOccurs()
   * @generated
   * @ordered
   */
  protected static final int MAX_OCCURS_EDEFAULT = 1;

  /**
   * The cached value of the '{@link #getMaxOccurs() <em>Max Occurs</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getMaxOccurs()
   * @generated
   * @ordered
   */
  protected int maxOccurs = MAX_OCCURS_EDEFAULT;

  /**
   * The flag representing whether the Max Occurs attribute has been set.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   * @ordered
   */
  protected static final int MAX_OCCURS_ESETFLAG = 1 << 9;

  /**
   * The cached value of the '{@link #getContent() <em>Content</em>}' containment reference.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getContent()
   * @generated
   * @ordered
   */
  protected XSDParticleContent content;

  /**
   * The cached value of the '{@link #getTerm() <em>Term</em>}' reference.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getTerm()
   * @generated
   * @ordered
   */
  protected XSDTerm term;

  public static XSDParticle createParticle(Node node)
  {
    XSDParticleContent xsdParticleContent = XSDParticleContentImpl.createParticleContent(node);
    if (xsdParticleContent != null)
    {
      XSDParticle xsdParticle = XSDFactory.eINSTANCE.createXSDParticle();
      xsdParticle.setElement((Element)node);
      xsdParticle.setContent(xsdParticleContent);
      return xsdParticle;
    }

    return null;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  protected XSDParticleImpl()
  {
    super();
  }

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

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public int getMinOccurs()
  {
    return minOccurs;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setMinOccurs(int newMinOccurs)
  {
    int oldMinOccurs = minOccurs;
    minOccurs = newMinOccurs;
    boolean oldMinOccursESet = (eFlags & MIN_OCCURS_ESETFLAG) != 0;
    eFlags |= MIN_OCCURS_ESETFLAG;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, XSDPackage.XSD_PARTICLE__MIN_OCCURS, oldMinOccurs, minOccurs, !oldMinOccursESet));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void unsetMinOccurs()
  {
    int oldMinOccurs = minOccurs;
    boolean oldMinOccursESet = (eFlags & MIN_OCCURS_ESETFLAG) != 0;
    minOccurs = MIN_OCCURS_EDEFAULT;
    eFlags &= ~MIN_OCCURS_ESETFLAG;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.UNSET, XSDPackage.XSD_PARTICLE__MIN_OCCURS, oldMinOccurs, MIN_OCCURS_EDEFAULT, oldMinOccursESet));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public boolean isSetMinOccurs()
  {
    return (eFlags & MIN_OCCURS_ESETFLAG) != 0;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public int getMaxOccurs()
  {
    return maxOccurs;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setMaxOccurs(int newMaxOccurs)
  {
    int oldMaxOccurs = maxOccurs;
    maxOccurs = newMaxOccurs;
    boolean oldMaxOccursESet = (eFlags & MAX_OCCURS_ESETFLAG) != 0;
    eFlags |= MAX_OCCURS_ESETFLAG;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, XSDPackage.XSD_PARTICLE__MAX_OCCURS, oldMaxOccurs, maxOccurs, !oldMaxOccursESet));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void unsetMaxOccurs()
  {
    int oldMaxOccurs = maxOccurs;
    boolean oldMaxOccursESet = (eFlags & MAX_OCCURS_ESETFLAG) != 0;
    maxOccurs = MAX_OCCURS_EDEFAULT;
    eFlags &= ~MAX_OCCURS_ESETFLAG;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.UNSET, XSDPackage.XSD_PARTICLE__MAX_OCCURS, oldMaxOccurs, MAX_OCCURS_EDEFAULT, oldMaxOccursESet));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public boolean isSetMaxOccurs()
  {
    return (eFlags & MAX_OCCURS_ESETFLAG) != 0;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public XSDParticleContent getContent()
  {
    return content;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setContent(XSDParticleContent newContent)
  {
    if (newContent != content)
    {
      NotificationChain msgs = null;
      if (content != null)
        msgs = ((InternalEObject)content).eInverseRemove(this, EOPPOSITE_FEATURE_BASE - XSDPackage.XSD_PARTICLE__CONTENT, null, msgs);
      if (newContent != null)
        msgs = ((InternalEObject)newContent).eInverseAdd(this, EOPPOSITE_FEATURE_BASE - XSDPackage.XSD_PARTICLE__CONTENT, null, msgs);
      msgs = basicSetContent(newContent, msgs);
      if (msgs != null) msgs.dispatch();
    }
    else if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, XSDPackage.XSD_PARTICLE__CONTENT, newContent, newContent));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public NotificationChain basicSetContent(XSDParticleContent newContent, NotificationChain msgs)
  {
    XSDParticleContent oldContent = content;
    content = newContent;
    if (eNotificationRequired())
    {
      ENotificationImpl notification = new ENotificationImpl(this, Notification.SET, XSDPackage.XSD_PARTICLE__CONTENT, oldContent, newContent);
      if (msgs == null) msgs = notification; else msgs.add(notification);
    }
    return msgs;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public XSDTerm getTerm()
  {
    return term;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setTerm(XSDTerm newTerm)
  {
    XSDTerm oldTerm = term;
    term = newTerm;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, XSDPackage.XSD_PARTICLE__TERM, oldTerm, term));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public NotificationChain eInverseRemove(InternalEObject otherEnd, int featureID, NotificationChain msgs)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_PARTICLE__CONTENT:
        return basicSetContent(null, msgs);
    }
    return super.eInverseRemove(otherEnd, featureID, msgs);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public Object eGet(int featureID, boolean resolve, boolean coreType)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_PARTICLE__MIN_OCCURS:
        return getMinOccurs();
      case XSDPackage.XSD_PARTICLE__MAX_OCCURS:
        return getMaxOccurs();
      case XSDPackage.XSD_PARTICLE__CONTENT:
        return getContent();
      case XSDPackage.XSD_PARTICLE__TERM:
        return getTerm();
    }
    return super.eGet(featureID, resolve, coreType);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public void eSet(int featureID, Object newValue)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_PARTICLE__MIN_OCCURS:
        setMinOccurs((Integer)newValue);
        return;
      case XSDPackage.XSD_PARTICLE__MAX_OCCURS:
        setMaxOccurs((Integer)newValue);
        return;
      case XSDPackage.XSD_PARTICLE__CONTENT:
        setContent((XSDParticleContent)newValue);
        return;
      case XSDPackage.XSD_PARTICLE__TERM:
        setTerm((XSDTerm)newValue);
        return;
    }
    super.eSet(featureID, newValue);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public void eUnset(int featureID)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_PARTICLE__MIN_OCCURS:
        unsetMinOccurs();
        return;
      case XSDPackage.XSD_PARTICLE__MAX_OCCURS:
        unsetMaxOccurs();
        return;
      case XSDPackage.XSD_PARTICLE__CONTENT:
        setContent((XSDParticleContent)null);
        return;
      case XSDPackage.XSD_PARTICLE__TERM:
        setTerm((XSDTerm)null);
        return;
    }
    super.eUnset(featureID);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public boolean eIsSet(int featureID)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_PARTICLE__MIN_OCCURS:
        return isSetMinOccurs();
      case XSDPackage.XSD_PARTICLE__MAX_OCCURS:
        return isSetMaxOccurs();
      case XSDPackage.XSD_PARTICLE__CONTENT:
        return content != null;
      case XSDPackage.XSD_PARTICLE__TERM:
        return term != null;
    }
    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(" (minOccurs: ");
    if ((eFlags & MIN_OCCURS_ESETFLAG) != 0) result.append(minOccurs); else result.append("<unset>");
    result.append(", maxOccurs: ");
    if ((eFlags & MAX_OCCURS_ESETFLAG) != 0) result.append(maxOccurs); else result.append("<unset>");
    result.append(')');
    return result.toString();
  }

  @Override
  public Element createElement()
  {
    Element newElement = null;
    XSDParticleContent theContent = getContent();
    if (theContent != null)
    {
      newElement = ((XSDConcreteComponentImpl)theContent).createElement();
      setElement(newElement);
    }
    return newElement;
  }

  @Override
  protected void patch()
  {
    super.patch();
    patchHelper();
  }

  protected void patchHelper()
  {
    XSDParticleContent theContent =  getContent();
    XSDTerm newTerm = null;
    if (theContent instanceof XSDModelGroupDefinition)
    {
      XSDModelGroupDefinition xsdModelGroupDefinition = (XSDModelGroupDefinition)theContent;
      newTerm = xsdModelGroupDefinition.getResolvedModelGroupDefinition().getModelGroup();
    }
    else if (theContent instanceof XSDElementDeclaration)
    {
      XSDElementDeclaration xsdElementDeclaration = (XSDElementDeclaration)theContent;
      newTerm = xsdElementDeclaration.getResolvedElementDeclaration();
    }
    else
    {
      newTerm = (XSDTerm)theContent;
    }

    if (getTerm() != newTerm)
    {
      setTerm(newTerm);
    }
  }

  @Override
  public boolean analyze()
  {
    xsdNFA = null;
    return super.analyze();
  }

  @Override
  public void validate()
  {
    super.validate();

    if (getMaxOccurs() != -1)
    {
      if (getMinOccurs() > getMaxOccurs())
      {
        createDiagnostic(XSDDiagnosticSeverity.ERROR_LITERAL, "coss-particle.2.1");
      }
    }
  }

  @Override
  protected Collection<Element> getContentNodes(Element changedElement)
  {
    return Collections.singleton(getElement());
  }

  @Override
  protected void reconcileAttributes(Element changedElement)
  {
    super.reconcileAttributes(changedElement);

    if (changedElement == getElement())
    {
      if (changedElement.hasAttributeNS(null, XSDConstants.MINOCCURS_ATTRIBUTE))
      {
        String newMinOccurs = changedElement.getAttributeNS(null, XSDConstants.MINOCCURS_ATTRIBUTE);
        try
        {
          int newMinOccursValue = Integer.parseInt(newMinOccurs);
          if (!isSetMinOccurs() || newMinOccursValue != getMinOccurs())
          {
            setMinOccurs(newMinOccursValue);
          }
        }
        catch (NumberFormatException exception)
        {
          // Ignore
        }
      }
      else if (isSetMinOccurs())
      {
        unsetMinOccurs();
      }

      if (changedElement.hasAttributeNS(null, XSDConstants.MAXOCCURS_ATTRIBUTE))
      {
        String newMaxOccurs = changedElement.getAttributeNS(null, XSDConstants.MAXOCCURS_ATTRIBUTE);
        try
        {
          int newMaxOccursValue = "unbounded".equals(newMaxOccurs) ?  -1 : Integer.parseInt(newMaxOccurs);
          if (!isSetMaxOccurs() || newMaxOccursValue != getMaxOccurs())
          {
            setMaxOccurs(newMaxOccursValue);
          }
        }
        catch (NumberFormatException exception)
        {
          // Ignore
        }
      }
      else if (isSetMaxOccurs())
      {
        unsetMaxOccurs();
      }

      XSDParticleContent theContent = getContent();
      if (theContent != null)
      {
        theContent.elementAttributesChanged(changedElement);
      }
    }
  }

  @Override
  protected void reconcileContents(Element changedElement)
  {
    super.reconcileContents(changedElement);
    XSDParticleContent theContent = getContent();
    if (theContent != null)
    {
      theContent.elementContentsChanged(changedElement);

      XSDTerm newTerm =
        (XSDTerm)
          new XSDSwitch<Object>()
          {
            @Override
            public Object caseXSDElementDeclaration(XSDElementDeclaration xsdElementDeclaration)
            {
              return xsdElementDeclaration.getResolvedElementDeclaration();
            }
            @Override
            public Object caseXSDModelGroupDefinition(XSDModelGroupDefinition xsdModelGroupDefinition)
            {
              return xsdModelGroupDefinition.getResolvedModelGroupDefinition().getModelGroup();
            }
            @Override
            public Object caseXSDWildcard(XSDWildcard xsdWildcard)
            {
              return xsdWildcard;
            }
            @Override
            public Object caseXSDModelGroup(XSDModelGroup xsdModelGroup)
            {
              return xsdModelGroup;
            }
          }.doSwitch(theContent);

      if (newTerm != getTerm())
      {
        setTerm(newTerm);
      }
    }
  }

  @Override
  protected void changeAttribute(EAttribute eAttribute)
  {
    if (isReconciling)
    {
      return;
    }

    super.changeAttribute(eAttribute);
    Element theElement = getElement();
    if (theElement != null)
    {
      if (eAttribute == null || eAttribute == XSDPackage.Literals.XSD_PARTICLE__MIN_OCCURS)
      {
        niceSetAttribute
          (theElement, 
           XSDConstants.MINOCCURS_ATTRIBUTE, 
           isSetMinOccurs() ? Integer.toString(getMinOccurs()) : null);
      }
      if (eAttribute == null || eAttribute == XSDPackage.Literals.XSD_PARTICLE__MAX_OCCURS)
      {
        niceSetAttribute
          (theElement, 
           XSDConstants.MAXOCCURS_ATTRIBUTE, 
           isSetMaxOccurs() ? getMaxOccurs() == -1 ? "unbounded" : Integer.toString(getMaxOccurs()) : null);
      }
    }
  }

  @Override
  protected void adoptContent(EReference eReference, XSDConcreteComponent xsdConcreteComponent)
  {
    super.adoptContent(eReference, xsdConcreteComponent);
    if (eReference == XSDPackage.Literals.XSD_PARTICLE__CONTENT)
    {
      patchHelper();
      traverseToRootForPatching();
    }
  }

  @Override
  protected void orphanContent(EReference eReference, XSDConcreteComponent xsdConcreteComponent)
  {
    super.orphanContent(eReference, xsdConcreteComponent);
    if (eReference == XSDPackage.Literals.XSD_PARTICLE__CONTENT)
    {
      patchHelper();
      traverseToRootForPatching();
    }
  }

  public static class XSDNFA implements XSDParticle.DFA
  {
    private static final class StateList extends BasicEList<State>
    {
      private static final long serialVersionUID = 1L;
      
      public StateList()
      {
        super();
      }

      public StateList(Collection<State> states)
      {
        super(states);
      }

      @Override
      protected Object [] newData(int capacity)
      {
        return new StateImpl [capacity];
      }
      
      @Override
      protected final boolean useEquals()
      {
        return false;
      }

      @Override
      public boolean contains(Object object)
      {
        for (int i = 0; i < size; ++i)
        {
          if (data[i] == object)
          {
            return true;
          }
        }

        return false;
      }
      
      @Override
      public void clear()
      {
        Object [] oldData = data;
        super.clear();
        data = oldData;
      }

      @SuppressWarnings("unchecked")
      @Override
      public <T> T[] toArray(T[] array) 
      {
        if (array == EMPTY_STATE_ARRAY)
        {
          return (T[])data;
        }
        else
        {
          return super.toArray(array);
        }
      }
    }

    private static final class TransitionList extends UniqueEList<Transition>
    {
      private static final long serialVersionUID = 1L;

      Set<XSDParticle.DFA.Transition> set = new HashSet<XSDParticle.DFA.Transition>();

      @Override
      protected Object[] newData(int capacity)
      {
        return new TransitionImpl [capacity];
      }

      @Override
      public boolean addAll(Collection<? extends Transition> collection)
      {
        boolean result = false;
        Transition [] transitions = collection.toArray(EMPTY_TRANSITION_ARRAY);
        int collectionSize = collection.size();
        grow(size + collectionSize);  
        for (int i = 0; i < collectionSize; ++i)
        {
          Transition transition = transitions[i];
          if (set.add(transition))
          {
            data[size++] = transition;
            result = true;
          }
        }
        return result;
      }

      @Override
      protected void didAdd(int index, Transition newObject)
      {
        set.add(newObject);
      }

      @Override
      protected void didRemove(int index, Transition oldObject)
      {
        set.remove(oldObject);
      }

      @Override
      protected void didClear(int size, Object[] oldObjects)
      {
        set.clear();
      }

      @Override
      public boolean contains(Object object)
      {
        return set.contains(object);
      }

      @Override
      public void clear()
      {
        Object [] oldData = data;
        super.clear();
        data = oldData;
      }

      @SuppressWarnings("unchecked")
      @Override
      public <T> T[] toArray(T[] array) 
      {
        if (array == EMPTY_TRANSITION_ARRAY)
        {
          return (T[])data;
        }
        else
        {
          return super.toArray(array);
        }
      }
    }

    public static class StateImpl implements XSDParticle.DFA.State
    {
      protected UniqueEList<XSDParticle.DFA.Transition> transitions;
      protected boolean isAccepting;

      public StateImpl()
      {
        transitions = new TransitionList();
      }

      public List<XSDParticle.DFA.Transition> getTransitions()
      {
        return transitions;
      }

      public boolean isAccepting()
      {
        return isAccepting;
      }

      public void setAccepting(boolean isAccepting)
      {
        this.isAccepting = isAccepting;
      }

      protected TransitionImpl createdTransition;

      public XSDParticle.DFA.Transition createTransition(XSDParticle xsdParticle, XSDParticle.DFA.State state)
      {
        if (createdTransition == null)
        {
          createdTransition = new TransitionImpl(xsdParticle, state);
        }
        else
        {
          createdTransition.setParticle(xsdParticle);
          createdTransition.setState(state);
        }

        if (transitions.add(createdTransition))
        {
          XSDParticle.DFA.Transition result = createdTransition;
          createdTransition = null;
          return result;
        }
        else
        {
          return null;
        }
      }

      public XSDParticle.DFA.Transition accept(String namespaceURI, String localName)
      {
        for (Transition transition : getTransitions())
        {
          XSDParticle xsdParticle = transition.getParticle();
          XSDTerm xsdTerm = xsdParticle.getTerm();
          if (xsdTerm instanceof XSDElementDeclaration)
          {
            XSDElementDeclaration xsdElementDeclaration = (XSDElementDeclaration)xsdTerm;
            if ((namespaceURI == null ? 
                   xsdElementDeclaration.getTargetNamespace() == null : 
                   namespaceURI.equals(xsdElementDeclaration.getTargetNamespace())) &&
                  localName.equals(xsdElementDeclaration.getName()))
            {
              return transition;
            }
          }
          else if (xsdTerm instanceof XSDWildcard)
          {
            XSDWildcard xsdWildcard = (XSDWildcard)xsdTerm;
            if (xsdWildcard.allows(namespaceURI))
            {
              return transition;
            }
          }
        }

        return null;
      }
    }

    public static class TransitionImpl implements XSDParticle.DFA.Transition
    {
      protected XSDParticle xsdParticle;
      protected XSDParticle.DFA.State state;
      protected int hashCode;

      public TransitionImpl(XSDParticle xsdParticle, XSDParticle.DFA.State state)
      {
        this.xsdParticle = xsdParticle;
        this.state = state;
        hashCode = -1;
      }

      public final XSDParticle.DFA.State getState()
      {
        return state;
      }

      public void setState(XSDParticle.DFA.State state)
      {
        this.state = state;
        hashCode = -1;
      }

      public final XSDParticle getParticle()
      {
        return xsdParticle;
      }

      public void setParticle(XSDParticle xsdParticle)
      {
        this.xsdParticle = xsdParticle;
        hashCode = -1;
      }

      @Override
      public int hashCode()
      {
        if (hashCode == -1)
        {
          hashCode = (xsdParticle == null ? 0 : xsdParticle.hashCode()) ^ (state == null ? 0 : state.hashCode());
        }
        return hashCode;
      }

      @Override
      public boolean equals(Object that)
      {
        if (that instanceof TransitionImpl)
        {
          TransitionImpl otherTransition = (TransitionImpl)that;
          return 
            xsdParticle == otherTransition.xsdParticle &&
            state == otherTransition.state;
        }
        else
        {
          return false;
        }
      }
    }

    private static final StateImpl[] EMPTY_STATE_ARRAY = new StateImpl [0];
    private static final TransitionImpl[] EMPTY_TRANSITION_ARRAY = new TransitionImpl [0];

    protected List<State> states = new StateList();

    protected StateImpl initialState;
    protected StateImpl finalState;
    protected XSDParticle xsdParticle;
    protected StateImpl currentState;
    protected Set<XSDModelGroup> visitedModelGroups;
    protected Collection<XSDDiagnostic> diagnostics;
    protected boolean isApproximate;

    protected XSDNFA(boolean isApproximate)
    {
      this.isApproximate = isApproximate;
      currentState = initialState = createState(null);
      finalState = createState(null);
    }

    protected XSDNFA(Set<XSDModelGroup> visitedModelGroups, XSDParticle xsdParticle, XSDTerm xsdTerm, boolean isApproximate)
    {
      this.isApproximate = isApproximate;
      this.visitedModelGroups = visitedModelGroups;
      this.xsdParticle = xsdParticle;
      currentState = initialState = createState(null);
      finalState = createState(xsdTerm);
      if (!visitedModelGroups.contains(xsdTerm))
      {
        initialize(xsdTerm);
      }
    }

    public XSDNFA(Set<XSDModelGroup> visitedModelGroups, XSDParticle xsdParticle, boolean isApproximate)
    {
      this.isApproximate = isApproximate;
      this.visitedModelGroups = visitedModelGroups;
      this.xsdParticle = xsdParticle;
      currentState = initialState = createState(null);
      finalState = createState(xsdParticle);
      initialize(xsdParticle);
    }

    public XSDNFA(XSDParticle xsdParticle, boolean isApproximate)
    {
      this.isApproximate = isApproximate;
      this.visitedModelGroups = new HashSet<XSDModelGroup>();
      this.xsdParticle = xsdParticle;
      currentState = initialState = createState(null);
      finalState = createState(xsdParticle);
      initialize(xsdParticle);
    }

    protected void checkBadTransitions()
    {
      for (State state : getStates())
      {
        for (Transition transition : state.getTransitions())
        {
          if (!getStates().contains(transition.getState()))
          {
            Thread.dumpStack();
          }
        }
      }
    }

    protected XSDNFA(XSDNFA original, boolean isApproximate)
    {
      this.isApproximate = isApproximate;
      xsdParticle = original.getParticle();

      Map<State, State> stateCloneMap = new HashMap<State, State>();
      List<State> originalStates = original.getStates();
      for (int i = 0, size = originalStates.size(); i < size; ++i)
      {
        State originalState = originalStates.get(i); 
        StateImpl clonedState = createState(null);
        stateCloneMap.put(originalState, clonedState);
        clonedState.setAccepting(originalState.isAccepting());
      }

      currentState = initialState = (StateImpl)stateCloneMap.get(original.getInitialState());
      finalState = (StateImpl)stateCloneMap.get(original.getFinalState());

      for (int i = 0, size = originalStates.size(); i < size; ++i)
      {
        State originalState = originalStates.get(i); 
        List<Transition> originalTransitions = originalState.getTransitions();
        for (int j = 0, transitionSize = originalTransitions.size(); j < transitionSize; ++j)
        {
          Transition originalTransition = originalTransitions.get(j); 
          ((StateImpl)stateCloneMap.get(originalState)).createTransition
            (originalTransition.getParticle(), stateCloneMap.get(originalTransition.getState()));
        }
      }
    }

    protected StateImpl createState(XSDComponent associatedComponent)
    {
      StateImpl result = new StateImpl();
      states.add(result);
      return result;
    }

    protected void initialize(XSDComponent xsdComponent)
    {
      if (xsdComponent instanceof XSDElementDeclaration)
      {
        XSDElementDeclaration xsdElementDeclaration = (XSDElementDeclaration)xsdComponent;
        for (XSDElementDeclaration substititonGroupMember : xsdElementDeclaration.getSubstitutionGroup())
        {
          if (substititonGroupMember == xsdElementDeclaration)
          {
            initialState.createTransition(xsdParticle, finalState);
          }
          else
          {
            XSDParticle substititonGroupMemberParticle = ((XSDParticleImpl)xsdParticle).getXSDFactory().createXSDParticle();
            substititonGroupMemberParticle.setTerm(substititonGroupMember);
            initialState.createTransition(substititonGroupMemberParticle, finalState);
          }
        }
      }
      else if (xsdComponent instanceof XSDWildcard)
      {
        initialState.createTransition(xsdParticle, finalState);
      }
      else if (xsdComponent instanceof XSDModelGroup)
      {
        XSDModelGroup xsdModelGroup = (XSDModelGroup)xsdComponent;
        visitedModelGroups.add(xsdModelGroup);
        switch (xsdModelGroup.getCompositor().getValue())
        {
          case XSDCompositor.SEQUENCE:
          {
            StateImpl previousState = initialState;
            for (XSDParticle xsdParticle : xsdModelGroup.getParticles())
            {
              XSDNFA particleNFA = new XSDNFA(visitedModelGroups, xsdParticle, isApproximate);
              propagateStates(particleNFA);
              previousState.createTransition(null, particleNFA.getInitialState());
              previousState = (StateImpl)particleNFA.getFinalState();
              if (getStates().size() > MAXIMUM_STATES)
              {
                break;
              }
            }
            previousState.createTransition(null, finalState);
            break;
          }
          case XSDCompositor.CHOICE:
          {
            for (XSDParticle xsdParticle : xsdModelGroup.getParticles())
            {
              XSDNFA particleNFA = new XSDNFA(visitedModelGroups, xsdParticle, isApproximate);
              propagateStates(particleNFA);
              initialState.createTransition(null, particleNFA.getInitialState());
              ((StateImpl)particleNFA.getFinalState()).createTransition(null, finalState);
              if (getStates().size() > MAXIMUM_STATES)
              {
                break;
              }
            }
            break;
          }
          case XSDCompositor.ALL:
          default:
          {
            if (visitedModelGroups.size() > 1)
            {
              if (diagnostics == null)
              {
                diagnostics = new ArrayList<XSDDiagnostic>();
              }

              XSDDiagnostic xsdDiagnostic = XSDFactory.eINSTANCE.createXSDDiagnostic();
              xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.ERROR_LITERAL);
              xsdDiagnostic.setMessage
                (XSDPlugin.INSTANCE.getString
                   ("_UI_XSDError_message", 
                       new Object [] 
                       {
                         populateDiagnostic(xsdDiagnostic, "cos-all-limited.1", null)
                       }));

              diagnostics.add(xsdDiagnostic);
            }

            if (xsdModelGroup.getParticles().size() == 0)
            {
              initialState.createTransition(null, finalState);
            }
            else
            {
              List<XSDNFA> particleNFAs = new ArrayList<XSDNFA>(xsdModelGroup.getParticles().size());
              for (XSDParticle xsdParticle : xsdModelGroup.getParticles())
              {
                XSDNFA newParticleNFA = new XSDNFA(visitedModelGroups, xsdParticle, isApproximate);
                particleNFAs.add(newParticleNFA);
                if (getStates().size() > MAXIMUM_STATES)
                {
                  break;
                }
              }

              if (xsdModelGroup.getParticles().size() > 4)
              {
                // diagnostics = new ArrayList();
                // XSDDiagnostic xsdDiagnostic = XSDFactory.eINSTANCE.createXSDDiagnostic();
                // xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.ERROR_LITERAL);
                // xsdDiagnostic.setMessage
                //   (XSDPlugin.INSTANCE.getString
                //      ("_UI_XSDError_message", 
                //       new Object [] 
                //       {
                //         XSDPlugin.INSTANCE.getString
                //           ("coss-particle.0.2", new Object [] { xsdModelGroup.getParticles().size() })
                //       }));
                // diagnostics.add(xsdDiagnostic);

                isApproximate = true;

                // Treat it like a repeating choice.
                //
                for (XSDParticle xsdParticle : xsdModelGroup.getParticles())
                {
                  XSDNFA particleNFA = new XSDNFA(visitedModelGroups, xsdParticle, isApproximate);
                  propagateStates(particleNFA);
                  initialState.createTransition(null, particleNFA.getInitialState());
                  ((StateImpl)particleNFA.getFinalState()).createTransition(null, finalState);
                  ((StateImpl)particleNFA.getFinalState()).createTransition(null, initialState);
                  if (getStates().size() > MAXIMUM_STATES)
                  {
                    break;
                  }
                }
              }
              else
              {
                XSDNFA xsdNFA = createPermutations(xsdModelGroup.getParticles(), particleNFAs, new HashMap<List<XSDParticle>, XSDNFA>());

                propagateStates(xsdNFA);

                initialState.createTransition(null, xsdNFA.getInitialState());
                ((StateImpl)xsdNFA.getFinalState()).createTransition(null, finalState);
              }
            }
            break;
          }
        }
        visitedModelGroups.remove(xsdModelGroup);
      }
      else if (xsdComponent instanceof XSDParticle)
      {
        XSDParticle xsdParticle = (XSDParticle)xsdComponent;
        int minOccurs = xsdParticle.getMinOccurs();
        int maxOccurs = xsdParticle.getMaxOccurs();
        int count = maxOccurs == -1 || isApproximate ? minOccurs : maxOccurs;
        if (minOccurs <= count)
        {
          List<XSDNFA> termNFAs = new ArrayList<XSDNFA>(count);
          XSDNFA firstTermNFA = new XSDNFA(visitedModelGroups, xsdParticle, xsdParticle.getTerm(), isApproximate);
          propagateStates(firstTermNFA);
          termNFAs.add(firstTermNFA);
          for (int i = 2; i <= count; ++i)
          {
            XSDNFA clonedTermNFA = new XSDNFA(firstTermNFA, isApproximate);
            propagateStates(clonedTermNFA);
            termNFAs.add(clonedTermNFA);
            if (getStates().size() > MAXIMUM_STATES)
            {
              break;
            }
          }

          //XSDNFA previousTermNFA = firstTermNFA;
          StateImpl previousState = initialState;
          int i;
          if (minOccurs == 0)
          {
            initialState.createTransition(null, firstTermNFA.getInitialState());
            initialState.createTransition(null, firstTermNFA.getFinalState());
            previousState = (StateImpl)firstTermNFA.getFinalState();
            i = 2;
          }
          else
          {
            for (i = 1; i <= minOccurs; ++i)
            {
              XSDNFA termNFA = termNFAs.get(i - 1);
              previousState.createTransition(null, termNFA.getInitialState());
              previousState = (StateImpl)termNFA.getFinalState();
              if (getStates().size() > MAXIMUM_STATES)
              {
                break;
              }
            }
          }

          if (maxOccurs == -1 || isApproximate)
          {
            previousState.createTransition(null, firstTermNFA.getInitialState());
          }
          else
          {
            for ( ; i <= maxOccurs; ++i)
            {
              XSDNFA termNFA = termNFAs.get(i - 1);
              previousState.createTransition(null, termNFA.getInitialState());
              ((StateImpl)termNFA.getInitialState()).createTransition(null, finalState);
              previousState = (StateImpl)termNFA.getFinalState();
              if (getStates().size() > MAXIMUM_STATES)
              {
                break;
              }
            }
          }
          previousState.createTransition(null, finalState);
        }
      }
    }

    protected XSDNFA createPermutations(List<XSDParticle> particles, List<XSDNFA> particleNFAs, Map<List<XSDParticle>, XSDNFA> particlesToNFAMap)
    {
      XSDNFA result = particlesToNFAMap.get(particles);
      if (result == null)
      {
        int size = particleNFAs.size();
        if (size == 1)
        {
          result = new XSDNFA(particleNFAs.get(0), isApproximate);
          particlesToNFAMap.put(particles, result);
        }
        else
        {
          result = new XSDNFA(isApproximate);
          List<XSDNFA> otherParticleNFAs = new ArrayList<XSDNFA>(size - 1);
          List<XSDParticle> otherParticles = new ArrayList<XSDParticle>(size - 1);
          for (int i = 0; i < size; ++i) 
          {
            XSDNFA particleNFA = particleNFAs.get(i);
            XSDParticle particle = particles.get(i);
            otherParticleNFAs.clear();
            otherParticles.clear();
            for (int j = 0; j < size; ++j) 
            {
              XSDParticle otherParticle = particles.get(j);
              if (particle != otherParticle)
              {
                XSDNFA otherParticleNFA = particleNFAs.get(j);
                otherParticleNFAs.add(otherParticleNFA);
                otherParticles.add(otherParticle);
              }
            }

            XSDNFA clonedParticleNFA = new XSDNFA(particleNFA, isApproximate);
            result.propagateStates(clonedParticleNFA);
            ((StateImpl)result.getInitialState()).createTransition(null, clonedParticleNFA.getInitialState());

            XSDNFA combinationXSDNFA = createPermutations
              (otherParticles, 
               otherParticleNFAs, 
               particlesToNFAMap);
            result.propagateStates(combinationXSDNFA);

            ((StateImpl)clonedParticleNFA.getFinalState()).createTransition(null, combinationXSDNFA.getInitialState());
            ((StateImpl)combinationXSDNFA.getFinalState()).createTransition(null, result.getFinalState());
          }

          if (result.getStates().size() > 50)
          {
            result.epsilonClosure();
            result.minimize();
            result.determinize();
            result.minimize();
            result.createFinalState();
          }
        }
        particlesToNFAMap.put(particles, result);
      }

      return result;
    }

    public boolean isApproximate()
    {
      return isApproximate;
    }

    public List<State> getStates()
    {
      return states;
    }

    public XSDParticle.DFA.State getInitialState()
    {
      return initialState;
    }

    public XSDParticle.DFA.State getFinalState()
    {
      return finalState;
    }

    public XSDParticle getParticle()
    {
      return xsdParticle;
    }

    public static String getComponentLabel(Collection<XSDComponent> xsdComponents)
    {
      StringBuffer result = new StringBuffer();
      result.append("{");
      for (Iterator<XSDComponent> i = xsdComponents.iterator(); i.hasNext(); )
      {
        XSDComponent xsdComponent = i.next();
        result.append(getComponentLabel(xsdComponent));
        if (i.hasNext())
        {
          result.append(" ");
        }
      }
      result.append("}");
      return result.toString();
    }

    public static String getComponentLabel(XSDComponent xsdComponent)
    {
      if (xsdComponent == null)
      {
        return "-";
      }
      else if (xsdComponent instanceof XSDElementDeclaration)
      {
        XSDElementDeclaration xsdElementDeclaration = (XSDElementDeclaration)xsdComponent;
        return xsdElementDeclaration.getName();
      }
      else if (xsdComponent instanceof XSDWildcard)
      {
        XSDWildcard xsdWildcard = (XSDWildcard)xsdComponent;
        return xsdWildcard.getStringNamespaceConstraint();
      }
      else if (xsdComponent instanceof XSDModelGroup)
      {
        XSDModelGroup xsdModelGroup = (XSDModelGroup)xsdComponent;
        switch (xsdModelGroup.getCompositor().getValue())
        {
          case XSDCompositor.SEQUENCE:
          {
            return "<sequence/>";
          }
          case XSDCompositor.CHOICE:
          {
            return "<choice/>";
          }
          case XSDCompositor.ALL:
          default:
          {
            return "<all/>";
          }
        }
      }
      else if (xsdComponent instanceof XSDParticle)
      {
        XSDParticle xsdParticle = (XSDParticle)xsdComponent;
        return getComponentLabel(xsdParticle.getTerm()) + "[" + xsdParticle.getMinOccurs() + "," + xsdParticle.getMaxOccurs() + "]";
      }
      else
      {
        return xsdComponent.toString();
      }
    }

    public void dump(PrintStream out)
    {
      List<State> stateList = new StateList(getStates());
      stateList.remove(initialState);
      stateList.add(0, initialState);
      if (finalState != null)
      {
        stateList.remove(finalState);
        stateList.add(finalState);
      }

      out.println
        ("Component: " + getComponentLabel(xsdParticle) + 
           " [" + stateList.indexOf(initialState) + ", " + 
           stateList.indexOf(finalState) + "]"); 
      for (State state : stateList)
      {
        out.println
          ("    State: " + stateList.indexOf(state) + (state.isAccepting() ? " *" : ""));

        for (Transition transition : state.getTransitions())
        {
          out.println
            ("     --> : " + getComponentLabel(transition.getParticle()) +  " -> " + stateList.indexOf(transition.getState())); 
        }
      }
    }

    public void epsilonClosure()
    {
      finalState.setAccepting(true);
      boolean closed;
      
      StateImpl[] statesArray = states.toArray(EMPTY_STATE_ARRAY);
      int statesSize = states.size();
      Set<Transition> nullTransitions = new HashSet<Transition>();
      do
      {
        closed = true;
        for (int i = 0; i < statesSize; ++i) 
        {
          StateImpl state = statesArray[i];
          List<Transition> stateTransitions = state.getTransitions();
          TransitionImpl [] stateTransitionsArray = stateTransitions.toArray(EMPTY_TRANSITION_ARRAY);
          for (int j = 0; j < stateTransitions.size(); ++j)
          {
            TransitionImpl transition = stateTransitionsArray[j];
            if (transition.getParticle() == null)
            {
              stateTransitions.remove(j);
              --j;
              closed = false;
              State otherState = transition.getState();
              if (state != otherState && nullTransitions.add(transition))
              {
                if (otherState.isAccepting())
                {
                  state.setAccepting(true);
                }
                stateTransitions.addAll(otherState.getTransitions());
              }
              stateTransitionsArray = stateTransitions.toArray(EMPTY_TRANSITION_ARRAY);
            }
          }
          nullTransitions.clear();
        }
      }
      while (!closed);
    }

    public void createFinalState()
    {
      finalState = createState(null);
      for (int i = 0, iSize = states.size(); i < iSize; ++i) 
      {
        StateImpl state = (StateImpl)states.get(i);
        if (state.isAccepting())
        {
          state.setAccepting(false);
          state.createTransition(null, finalState);
        }
      }
    }

    protected TransitionImpl testTransition = new TransitionImpl(null, null);

    public boolean isEquivalent(XSDParticle.DFA.State s1, XSDParticle.DFA.State s2)
    {
      if (s1.isAccepting() != s2.isAccepting() || s1.getTransitions().size() != s2.getTransitions().size())
      {
        return false;
      }
      else
      {
        List<Transition> stateTransitions = s1.getTransitions();
        TransitionImpl[] stateTransitionsArray = stateTransitions.toArray(EMPTY_TRANSITION_ARRAY);
        for (int i = 0, iSize = stateTransitions.size(); i < iSize; ++i)
        {
          TransitionImpl transition = stateTransitionsArray[i];
          testTransition.setParticle(transition.getParticle());
          testTransition.setState(transition.getState());

          if (!s2.getTransitions().contains(testTransition))
          {
            return false;
          }
        }

        return true;
      }
    }

    public void minimize()
    {
      int size = states.size();
      StateImpl [] theStates = states.toArray(new StateImpl[size]);
      StateList equivalentStates = new StateList();
      equivalentStates.grow(size);
      boolean minimal;
      do
      {
        minimal = true;
        for (int i = 0; i < theStates.length; ++i)
        {
          StateImpl state = theStates[i];
          if (state != null)
          {
            for (int j = i + 1; j < theStates.length; ++j)
            {
              State otherState = theStates[j];
              if (otherState != null && isEquivalent(state, otherState))
              {
                equivalentStates.add(otherState);
                theStates[j] = null;
              }
            }
            if (!equivalentStates.isEmpty())
            {
              for (int j = 0; j < theStates.length; ++j)
              {
                State otherState = theStates[j];
                if (otherState != null)
                {
                  List<Transition> theTransitions = otherState.getTransitions();
                  TransitionImpl[] theTransitionsArray = theTransitions.toArray(EMPTY_TRANSITION_ARRAY);
                  if (equivalentStates.contains(otherState))
                  {
                    for (int k = 0, kSize = theTransitions.size(); k < kSize; ++k)
                    {
                      TransitionImpl transition = theTransitionsArray[k];
                      State outgoingState = transition.getState();
                      if (equivalentStates.contains(outgoingState))
                      {
                        state.createTransition(transition.getParticle(), state);
                      }
                      else
                      {
                        state.createTransition(transition.getParticle(), outgoingState);
                      }
                    }
                  }
                  else 
                  {
                    for (int k = theTransitions.size(); --k >= 0; )
                    {
                      TransitionImpl transition = theTransitionsArray[k];
                      State outgoingState = transition.getState();
                      if (equivalentStates.contains(outgoingState))
                      {
                        theTransitions.remove(k);
                        theTransitions.add(new TransitionImpl(transition.getParticle(), state));
                      }
                    }
                  }
                }
              }

              states.removeAll(equivalentStates);
              equivalentStates.clear();
              minimal = false;
            }
          }
        }
      }
      while (!minimal);
    }

    protected XSDElementDeclaration [] xsdElementDeclarations;

    public int checksum()
    {
      int result = 0;
      if (xsdElementDeclarations != null)
      {
        for (int i = 0, length = xsdElementDeclarations.length; i < length; ++i)
        {
          result += xsdElementDeclarations[i].getSubstitutionGroup().hashCode();
        }
      }
      else
      {
        List<XSDElementDeclaration> elements = new UniqueEList.FastCompare<XSDElementDeclaration>();
        StateImpl[] statesArray = states.toArray(EMPTY_STATE_ARRAY);
        for (int i = 0, iSize = states.size(); i < iSize; ++i)
        {
          StateImpl state = statesArray[i];
          List<Transition> theTransitions = state.getTransitions();
          TransitionImpl[] transitionsArray = theTransitions.toArray(EMPTY_TRANSITION_ARRAY);
          for (int j = 0, jSize = theTransitions.size(); j < jSize; ++j)
          {
            TransitionImpl transition = transitionsArray[j];
            XSDTerm xsdTerm = transition.getParticle().getTerm();
            if (xsdTerm instanceof XSDElementDeclaration)
            {
              XSDElementDeclaration xsdElementDeclaration = (XSDElementDeclaration)xsdTerm;
              if (elements.add(xsdElementDeclaration))
              {
                result += xsdElementDeclaration.getSubstitutionGroup().hashCode();
              }
            }
          }
        }
        xsdElementDeclarations = new XSDElementDeclaration[elements.size()];
        elements.toArray(xsdElementDeclarations);
      }
      return result;
    }

    public void determinize()
    {
      Set<Set<State>> stateSubsets = new HashSet<Set<State>>();
      State originalInitialState = initialState;
      Set<State> initialStateSubset = Collections.<State>singleton(initialState);
      stateSubsets.add(initialStateSubset);

      states.clear();
      currentState = initialState = createState(null);
      initialState.setAccepting(originalInitialState.isAccepting());
      finalState = null;
      xsdParticle = null;

      Map<Set<State>, State> stateSubsetToStateMap = new HashMap<Set<State>, State>();
      stateSubsetToStateMap.put(initialStateSubset, initialState);
      List<Transition> transitions = new TransitionList();
      do
      {
        Iterator<Set<State>> s = stateSubsets.iterator();
        Set<State> stateSubset = s.next();
        s.remove();

        StateImpl newState = (StateImpl)stateSubsetToStateMap.get(stateSubset);

        transitions.clear();
        for (State originalState : stateSubset)
        {
          transitions.addAll(originalState.getTransitions());
        }

        TransitionImpl [] transitionsArray = transitions.toArray(EMPTY_TRANSITION_ARRAY);
        int count = transitions.size();
        while (count > 0)
        {
          TransitionImpl originalTransition = transitionsArray[0];
          int moveIndex = 0;
          int index = 1;
          XSDParticle particle = originalTransition.getParticle();
          State orginalTransitionState = originalTransition.getState();
          Set<State> newStateSubset = new HashSet<State>();
          newStateSubset.add(orginalTransitionState);
          boolean isAccepting = orginalTransitionState.isAccepting();
          while (index < count)
          {
            TransitionImpl otherOriginalTransition = transitionsArray[index++];
            if (otherOriginalTransition.getParticle() == particle)
            {
              State otherOriginalTransitionState = otherOriginalTransition.getState();
              newStateSubset.add(otherOriginalTransitionState);
              if (otherOriginalTransitionState.isAccepting())
              {
                isAccepting = true;
              }
            }
            else
            {
              // Shift it forward to the next unused slot for subsequent processing.
              //
              transitionsArray[moveIndex++] = otherOriginalTransition;
            }
          }
          count = moveIndex;
          StateImpl newNextState = (StateImpl)stateSubsetToStateMap.get(newStateSubset);
          if (newNextState == null)
          {
            newNextState = createState(null);
            if (isAccepting)
            {
              newNextState.setAccepting(true);
            }
            stateSubsetToStateMap.put(newStateSubset, newNextState);
            stateSubsets.add(newStateSubset);
          }
          else
          {
            if (newNextState.isAccepting() != isAccepting)
            {
              Thread.dumpStack();
            }
          }

          newState.createTransition(particle, newNextState);
        }
      }
      while (!stateSubsets.isEmpty());
    }

    public XSDDiagnostic checkOverlap(XSDTerm xsdComponent1, XSDTerm xsdComponent2)
    {
      if (xsdComponent1 instanceof XSDElementDeclaration)
      {
        XSDElementDeclaration xsdElementDeclaration1 = (XSDElementDeclaration)xsdComponent1;
        return checkOverlap(xsdElementDeclaration1, xsdComponent2);
      }
      else if (xsdComponent1 instanceof XSDWildcard)
      {
        XSDWildcard xsdWildcard1 = (XSDWildcard)xsdComponent1;
        return checkOverlap(xsdWildcard1, xsdComponent2);
      }
      else
      {
        return null;
      }
    }

    protected XSDDiagnostic checkOverlap(XSDElementDeclaration xsdElementDeclaration1, XSDTerm xsdComponent2)
    {
      if (xsdComponent2 instanceof XSDElementDeclaration)
      {
        XSDElementDeclaration xsdElementDeclaration2 = (XSDElementDeclaration)xsdComponent2;
        if (xsdElementDeclaration1.hasSameNameAndTargetNamespace(xsdElementDeclaration2))
        {
          XSDDiagnostic xsdDiagnostic = XSDFactory.eINSTANCE.createXSDDiagnostic();
          xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.ERROR_LITERAL);
          xsdDiagnostic.setMessage
            (XSDPlugin.INSTANCE.getString
               ("_UI_XSDError_message", 
                new Object [] 
                {
                  populateDiagnostic
                    (xsdDiagnostic, "key-overlap.1", new Object [] { xsdElementDeclaration1.getURI() })
                }));
          return xsdDiagnostic;
        }
      }
      else if (xsdComponent2 instanceof XSDWildcard)
      {
        XSDWildcard xsdWildcard2 = (XSDWildcard)xsdComponent2;
        if (xsdWildcard2.allows(xsdElementDeclaration1.getTargetNamespace()))
        {
          XSDDiagnostic xsdDiagnostic = XSDFactory.eINSTANCE.createXSDDiagnostic();
          xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.ERROR_LITERAL);
          xsdDiagnostic.setMessage
            (XSDPlugin.INSTANCE.getString
               ("_UI_XSDError_message", 
                new Object [] 
                { 
                  populateDiagnostic(xsdDiagnostic, "key-overlap.2", new Object [] { xsdElementDeclaration1.getURI() }) 
                }));
          return xsdDiagnostic;
        }
      }
      return null;
    }

    protected XSDDiagnostic checkOverlap(XSDWildcard xsdWildcard1, XSDTerm xsdComponent2)
    {
      if (xsdComponent2 instanceof XSDElementDeclaration)
      {
        XSDElementDeclaration xsdElementDeclaration2 = (XSDElementDeclaration)xsdComponent2;
        if (xsdWildcard1.allows(xsdElementDeclaration2.getTargetNamespace()))
        {
          XSDDiagnostic xsdDiagnostic = XSDFactory.eINSTANCE.createXSDDiagnostic();
          xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.ERROR_LITERAL);
          xsdDiagnostic.setMessage
            (XSDPlugin.INSTANCE.getString
               ("_UI_XSDError_message", 
                new Object [] 
                { 
                  populateDiagnostic(xsdDiagnostic, "key-overlap.2", new Object [] { xsdElementDeclaration2.getURI() })
                }));
          return xsdDiagnostic;
        }
      }
      else if (xsdComponent2 instanceof XSDWildcard)
      {
        XSDWildcard xsdWildcard2 = (XSDWildcard)xsdComponent2;
        XSDWildcard intersection = xsdWildcard1.attributeWildcardIntersection(xsdWildcard2);
        if (intersection != null && !intersection.getNamespaceConstraint().isEmpty())
        {
          XSDDiagnostic xsdDiagnostic = XSDFactory.eINSTANCE.createXSDDiagnostic();
          xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.ERROR_LITERAL);
          xsdDiagnostic.setMessage
            (XSDPlugin.INSTANCE.getString
               ("_UI_XSDError_message", 
                new Object [] 
                {
                  populateDiagnostic(xsdDiagnostic, "key-overlap.3", new Object [] { intersection.getNamespaceConstraint().get(0) })
                }));
          return xsdDiagnostic;
        }
      }
      return null;
    }

    public void propagateStates(XSDNFA xsdNFA)
    {
      states.addAll(xsdNFA.getStates());
      if (xsdNFA.diagnostics != null)
      {
        if (diagnostics == null)
        {
          diagnostics = new ArrayList<XSDDiagnostic>();
        }
        diagnostics.addAll(xsdNFA.diagnostics);
      }
    }

    public Collection<XSDDiagnostic> getDiagnostics()
    {
      if (diagnostics == null)
      {
        diagnostics = new ArrayList<XSDDiagnostic>();
        Map<String, XSDTypeDefinition> elementURIToTypeMap = new HashMap<String, XSDTypeDefinition>();
        int iSize = states.size();
        if (iSize > MAXIMUM_STATES)
        {
          XSDDiagnostic xsdDiagnostic = XSDFactory.eINSTANCE.createXSDDiagnostic();
          xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.WARNING_LITERAL);
          xsdDiagnostic.setMessage
            (XSDPlugin.INSTANCE.getString
               ("_UI_XSDError_message", 
                new Object [] 
                { 
                  populateDiagnostic(xsdDiagnostic, "coss-particle.0.1", new Object [] { getStates().size() })
                }));
          diagnostics.add(xsdDiagnostic);
        }
        else
        {
          StateImpl [] statesArray = states.toArray(EMPTY_STATE_ARRAY);
          Map<String, XSDElementDeclaration> uriToElementMap = new HashMap<String, XSDElementDeclaration>();
          for (int i = 0; i < iSize; ++i)
          {
            StateImpl state = statesArray[i];
            List<Transition> transitions = state.getTransitions();
            TransitionImpl[] transitionsArray = transitions.toArray(EMPTY_TRANSITION_ARRAY);
            int jSize = transitions.size();
            uriToElementMap.clear();
            for (int j = 0; j < jSize; ++j)
            {
              TransitionImpl transition = transitionsArray[j];
              XSDTerm xsdTerm = transition.getParticle().getTerm();
              if (xsdTerm instanceof XSDElementDeclaration)
              {
                XSDElementDeclaration xsdElementDeclaration = (XSDElementDeclaration)xsdTerm;
                XSDTypeDefinition xsdTypeDefinition = xsdElementDeclaration.getTypeDefinition();
                Object otherXSDTypeDefinition = elementURIToTypeMap.put(xsdElementDeclaration.getURI(), xsdTypeDefinition);
                if (otherXSDTypeDefinition != null && otherXSDTypeDefinition != xsdTypeDefinition)
                {
                  XSDDiagnostic xsdDiagnostic = XSDFactory.eINSTANCE.createXSDDiagnostic();
                  xsdDiagnostic.setSeverity(XSDDiagnosticSeverity.ERROR_LITERAL);
                  xsdDiagnostic.setMessage
                    (XSDPlugin.INSTANCE.getString
                       ("_UI_XSDError_message", 
                        new Object [] 
                        {
                          populateDiagnostic
                            (xsdDiagnostic,
                             "cos-element-consistent", 
                             new Object [] 
                             { 
                               xsdElementDeclaration.getURI(), 
                               xsdTypeDefinition.getURI(), 
                               ((XSDTypeDefinition)otherXSDTypeDefinition).getURI()
                             })
                         }));
                  diagnostics.add(xsdDiagnostic);
                }

                if (jSize > 1)
                {
                  XSDElementDeclaration otherXSDElementDeclaration = uriToElementMap.put(xsdElementDeclaration.getURI(), xsdElementDeclaration);
                  if (otherXSDElementDeclaration != null)
                  {
                    XSDDiagnostic xsdDiagnostic = checkOverlap(xsdElementDeclaration, otherXSDElementDeclaration);
                    if (xsdDiagnostic != null)
                    {
                      diagnostics.add(xsdDiagnostic);
                    }
                  }
                }
              }
            }
            // Only if there are wildcards do the check for overlapping wildcards.
            //
            if (jSize > 1 && uriToElementMap.size() <= jSize)
            {
              for (int j = 0; j < jSize; ++j)
              {
                TransitionImpl transition = transitionsArray[j];
                XSDTerm xsdTerm = transition.getParticle().getTerm();
                if (xsdTerm instanceof XSDWildcard)
                {
                  XSDWildcard xsdWildcard = (XSDWildcard)xsdTerm;
                  for (int k = j + 1; k < jSize; ++k)
                  {
                    TransitionImpl otherTransition = transitionsArray[k];
                    XSDDiagnostic xsdDiagnostic = checkOverlap(xsdWildcard, otherTransition.getParticle().getTerm());
                    if (xsdDiagnostic != null)
                    {
                      diagnostics.add(xsdDiagnostic);
                    }
                  }
                }
              }
            }
          }
        }
      }

      return diagnostics;
    }

    public XSDParticle.DFA.State getCurrentState()
    {
      return currentState;
    }

    public void reset()
    {
      currentState = initialState;
    }

    public XSDParticle.DFA cloneDFA()
    {
      return new XSDNFA(this, isApproximate);
    }
  }

  public static final boolean debug = false;

  protected XSDNFA xsdNFA;
  protected int xsdNFACheckSum;

  public XSDParticle.DFA getDFA()
  {
    if (xsdNFA == null || (!getSchema().getSchemaForSchemaNamespace().equals(getSchema().getTargetNamespace()) && xsdNFACheckSum != xsdNFA.checksum()))
    {
      XSDNFA localNFA = new XSDNFA(this, false);
      if (localNFA.getStates().size() > MAXIMUM_STATES)
      {
        localNFA = new XSDNFA(this, true);
      }
      if (localNFA.getStates().size() < MAXIMUM_STATES)
      {
        if (debug)
        {
          System.out.println("-- NFA initial --");
          localNFA.dump(System.out);
          System.out.println("-- NFA no-epsilon --");
        }
        localNFA.epsilonClosure();
        if (debug)
        {
          localNFA.dump(System.out);
          System.out.println("-- NFA minimal --");
        }
        localNFA.minimize();
        if (debug)
        {
          localNFA.dump(System.out);
          System.out.println("-- DFA --");
        }
        localNFA.determinize();
        if (debug)
        {
          localNFA.dump(System.out);
          System.out.println("-- DFA minimal--");
        }
        localNFA.minimize();
        if (debug)
        {
          localNFA.dump(System.out);
        }
      }
      else
      {
        // Clean up bad transitions.
        //
        for (DFA.State state : localNFA.getStates())
        {
          for (Iterator<DFA.Transition> j = state.getTransitions().iterator(); j.hasNext(); )
          {
            DFA.Transition transition = j.next();
            if (transition.getParticle() == null)
            {
              j.remove();
            }
          }
        }
      }
      xsdNFA = localNFA;
    }
    return xsdNFA;
  }

  public boolean isEmptiable()
  {
    return getDFA().getInitialState().isAccepting();
  }

  public boolean isSubset(XSDParticle otherParticle)
  {
    return isSubset(otherParticle, false);
  }

  public boolean isSubset(XSDParticle otherParticle, boolean diagnose)
  {
    XSDNFA subset = (XSDNFA)getDFA();
    XSDNFA superSet = (XSDNFA)otherParticle.getDFA();

    if (!subset.getDiagnostics().isEmpty() || !superSet.getDiagnostics().isEmpty())
    {
      return true;
    }

    final Set<List<DFA.State>> states = new HashSet<List<DFA.State>>();
    Map<DFA.State, Object> map = 
      new HashMap<DFA.State, Object>()
      {
        private static final long serialVersionUID = 1L;

        @Override
        public Object put(DFA.State key, Object value)
        {
          DFA.State s1 = key;
          DFA.State s2 = (DFA.State)value;
          if (s1.isAccepting() && !s2.isAccepting())
          {
            return null;
          }

          @SuppressWarnings("unchecked") Set<DFA.State> set = (Set<DFA.State>)super.get(key);
          if (set == null)
          {
            set = new HashSet<DFA.State>();
            super.put(key, set);
          }
          if (set.add(s2))
          {
            List<DFA.State> pair = new ArrayList<DFA.State>(2);
            pair.add(s1);
            pair.add(s2);
            states.add(pair);
          }
          return set;
        }
      };

    boolean result = false;
    if (map.put(subset.getInitialState(), superSet.getInitialState()) != null)
    {
      Map<XSDElementDeclaration, XSDElementDeclaration> elementMap = new HashMap<XSDElementDeclaration, XSDElementDeclaration>();

      result = true;
      LOOP: while (!states.isEmpty())
      {
        Iterator<List<DFA.State>> iterator = states.iterator();
        List<DFA.State> pair = iterator.next();
        iterator.remove();

        DFA.State substate = pair.get(0);
        DFA.State superState = pair.get(1);
        for (DFA.Transition subtransition : substate.getTransitions())
        {
          XSDTerm subXSDTerm = subtransition.getParticle().getTerm();
          boolean transitionResult = false;
          if (subXSDTerm instanceof XSDElementDeclaration)
          {
            XSDElementDeclaration subXSDElementDeclaration = (XSDElementDeclaration)subXSDTerm;
            for (DFA.Transition superTransition : superState.getTransitions())
            {
              XSDTerm superXSDTerm = superTransition.getParticle().getTerm();
              if (superXSDTerm instanceof XSDElementDeclaration)
              {
                XSDElementDeclaration superXSDElementDeclaration = (XSDElementDeclaration)superXSDTerm;
                if (superXSDElementDeclaration.hasSameNameAndTargetNamespace(subXSDElementDeclaration))
                {
                  if (map.put(subtransition.getState(), superTransition.getState()) != null)
                  {
                    elementMap.put(subXSDElementDeclaration, superXSDElementDeclaration);
                    transitionResult = true;
                    break;
                  }
                  else
                  {
                    result = false;
                    break LOOP;
                  }
                }
              }
              else
              {
                XSDWildcard superXSDWildcard = (XSDWildcard)superXSDTerm;
                if (superXSDWildcard.allows(subXSDElementDeclaration.getTargetNamespace()))
                {
                  if (map.put(subtransition.getState(), superTransition.getState()) != null)
                  {
                    transitionResult = true;
                    break;
                  }
                  else
                  {
                    result = false;
                    break LOOP;
                  }
                }
              }
            }
          }
          else
          {
            XSDWildcard subXSDWildcard = (XSDWildcard)subXSDTerm;
            for (DFA.Transition superTransition : superState.getTransitions())
            {
              XSDTerm superXSDTerm = superTransition.getParticle().getTerm();
              if (superXSDTerm instanceof XSDWildcard)
              {
                XSDWildcard superXSDWildcard = (XSDWildcard)superXSDTerm;
                if (subXSDWildcard.isWildcardSubset(superXSDWildcard))
                {
                  if (map.put(subtransition.getState(), superTransition.getState()) != null)
                  {
                    transitionResult = true;
                    break;
                  }
                  else
                  {
                    result = false;
                    break LOOP;
                  }
                }
              }
            }
          }

          if (!transitionResult)
          {
            result = false;
            break LOOP;
          }
        }
      }

      if (result)
      {
        for (Map.Entry<XSDElementDeclaration, XSDElementDeclaration> entry : elementMap.entrySet())
        {
          XSDElementDeclaration subXSDElementDeclaration = entry.getKey();
          XSDElementDeclaration superXSDElementDeclaration = entry.getValue();

          if (superXSDElementDeclaration.isNillable() && !subXSDElementDeclaration.isNillable())
          {
            result = false;
            if (diagnose)
            {
              getDiagnosticTarget(subXSDElementDeclaration).createDiagnostic
                (XSDDiagnosticSeverity.ERROR_LITERAL,
                 "rcase-NameAndTypeOK.2",
                 subXSDElementDeclaration.getURI());
            }
            else
            {
             break;
            }
          }

          if (superXSDElementDeclaration.getTypeDefinition() instanceof XSDSimpleTypeDefinition)
          {
            if (superXSDElementDeclaration.getConstraint() == XSDConstraint.FIXED_LITERAL &&
                  superXSDElementDeclaration.getLexicalValue() != null &&
                  (subXSDElementDeclaration.getConstraint() != XSDConstraint.FIXED_LITERAL || 
                    !((XSDSimpleTypeDefinition)superXSDElementDeclaration.getTypeDefinition()).equalLiterals
                       (superXSDElementDeclaration.getElement(), superXSDElementDeclaration.getLexicalValue(), subXSDElementDeclaration.getElement(), subXSDElementDeclaration.getLexicalValue())))
            {
              result = false;
              if (diagnose)
              {
                getDiagnosticTarget(subXSDElementDeclaration).createDiagnostic
                  (XSDDiagnosticSeverity.ERROR_LITERAL, 
                   "rcase-NameAndTypeOK.4", 
                   subXSDElementDeclaration.getLexicalValue(), 
                   subXSDElementDeclaration.getURI());
              }
              else
              {
                break;
              }
            }
          }
    
          if (!subXSDElementDeclaration.getDisallowedSubstitutions().containsAll
                 (superXSDElementDeclaration.getDisallowedSubstitutions()))
          {
            result = false;
            if (diagnose)
            {
              getDiagnosticTarget(subXSDElementDeclaration).createDiagnostic
                (XSDDiagnosticSeverity.ERROR_LITERAL, 
                 "rcase-NameAndTypeOK.6", 
                 subXSDElementDeclaration.getURI());
            }
            else
            {
              break;
            }
          }

          XSDTypeDefinition badType =
            subXSDElementDeclaration.getTypeDefinition().getBadTypeDerivation
              (superXSDElementDeclaration.getTypeDefinition(), false, true);
          if (badType != null)
          {
            result = false;
            if (diagnose)
            {
              getDiagnosticTarget(subXSDElementDeclaration).createDiagnostic
                (XSDDiagnosticSeverity.ERROR_LITERAL,
                 "rcase-NameAndTypeOK.7",
                 subXSDElementDeclaration.getURI(),
                 superXSDElementDeclaration.getTypeDefinition().getURI());
            }
            else
            {
              break;
            }
          }
        }
      }
    }

    // if (!result)
    // {
    //   printDFA(subset);
    //   printDFA(superSet);
    // }

    return result;
  }

  @Override
  public XSDConcreteComponent cloneConcreteComponent(boolean deep, boolean shareDOM)
  {
    XSDParticleImpl clonedParticle =
      (XSDParticleImpl)getXSDFactory().createXSDParticle();
    clonedParticle.isReconciling = true;

    if (isSetMinOccurs())
    {
      clonedParticle.setMinOccurs(getMinOccurs());
    }
    if (isSetMaxOccurs())
    {
      clonedParticle.setMaxOccurs(getMaxOccurs());
    }

    clonedParticle.setContent((XSDParticleContent)getContent().cloneConcreteComponent(deep, shareDOM));

    if (shareDOM && getElement() != null)
    {
      clonedParticle.setElement(getElement());
    }

    clonedParticle.isReconciling = shareDOM;
    return clonedParticle;
  }
} 
