/**
 * Copyright (c) 2004-2009 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import java.lang.reflect.Array;
import java.math.BigDecimal;

import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticChain;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.ResourceLocator;

import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.InternalEObject;

import org.eclipse.emf.ecore.plugin.EcorePlugin;

import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;

import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
import org.eclipse.emf.ecore.xml.type.util.XMLTypeUtil;


/**
 * A validity checker for basic EObject constraints.
 */
public class EObjectValidator implements EValidator
{
  public static final EObjectValidator INSTANCE = new EObjectValidator();

  public static final String DIAGNOSTIC_SOURCE = "org.eclipse.emf.ecore";

  public static final int EOBJECT__EVERY_MULTIPCITY_CONFORMS = 1;
  public static final int EOBJECT__EVERY_DATA_VALUE_CONFORMS = 2;
  public static final int EOBJECT__EVERY_REFERENCE_IS_CONTAINED = 3;
  public static final int EOBJECT__EVERY_PROXY_RESOLVES = 4;
  public static final int DATA_VALUE__VALUE_IN_RANGE = 5;
  public static final int DATA_VALUE__LENGTH_IN_RANGE = 6;
  public static final int DATA_VALUE__TYPE_CORRECT = 7;
  public static final int DATA_VALUE__VALUE_IN_ENUMERATION = 8;
  public static final int DATA_VALUE__MATCHES_PATTERN = 9;
  public static final int DATA_VALUE__TOTAL_DIGITS_IN_RANGE = 10;
  public static final int DATA_VALUE__FRACTION_DIGITS_IN_RANGE = 11;
  public static final int EOBJECT__UNIQUE_ID = 12;
  public static final int EOBJECT__EVERY_KEY_UNIQUE = 13;
  public static final int EOBJECT__EVERY_MAP_ENTRY_UNIQUE = 14;
  public static final int EOBJECT__NO_CIRCULAR_CONTAINMENT = 15;
  public static final int EOBJECT__EVERY_BIDIRECTIONAL_REFERENCE_IS_PAIRED = 16;
  
  static final int EOBJECT_DIAGNOSTIC_CODE_COUNT = EOBJECT__EVERY_BIDIRECTIONAL_REFERENCE_IS_PAIRED;
  
  /**
   * A key to be used in <code>context</code> maps to indicate the root object at which validation started.
   * It's used to detect {@link #EOBJECT__NO_CIRCULAR_CONTAINMENT circular containment}
   * and to prevent {@link Diagnostician#validate(EClass, EObject, DiagnosticChain, Map) infinite recursion}.
   * The value of the entry must be the root {@link EObject}.
   * @see EValidator#validate(EObject, DiagnosticChain, Map)
   * @see #validate_NoCircularContainment(EObject, DiagnosticChain, Map)
   * @since 2.5
   */
  public static final String ROOT_OBJECT = "org.eclipse.emf.ecore.EObject_NoCircularContainment";

  /**
   * @since 2.1.0
   */
  public static String getObjectLabel(EObject eObject, Map<Object, Object> context)
  {
    if (context != null)
    {
      SubstitutionLabelProvider substitutionlabelProvider = (SubstitutionLabelProvider)context.get(SubstitutionLabelProvider.class);
      if (substitutionlabelProvider != null)
      {
        return substitutionlabelProvider.getObjectLabel(eObject);
      }
    }
    return EcoreUtil.getIdentification(eObject);
  }

  /**
   * @since 2.1.0
   */
  public static String getFeatureLabel(EStructuralFeature eStructuralFeature, Map<Object, Object> context)
  {
    if (context != null)
    {
      SubstitutionLabelProvider substitutionlabelProvider = (SubstitutionLabelProvider)context.get(SubstitutionLabelProvider.class);
      if (substitutionlabelProvider != null)
      {
        return substitutionlabelProvider.getFeatureLabel(eStructuralFeature);
      }
    }
    return eStructuralFeature.getName();
  }

  /**
   * @since 2.1.0
   */
  public static String getValueLabel(EDataType eDataType, Object value, Map<Object, Object> context)
  {
    if (context != null)
    {
      SubstitutionLabelProvider substitutionlabelProvider = (SubstitutionLabelProvider)context.get(SubstitutionLabelProvider.class);
      if (substitutionlabelProvider != null)
      {
        return substitutionlabelProvider.getValueLabel(eDataType, value);
      }
    }
    return EcoreUtil.convertToString(eDataType, value);
  }

  public EObjectValidator()
  {
    super();
  }

  protected EPackage getEPackage()
  {
    return EcorePackage.eINSTANCE;
  }

  protected EValidator getRootEValidator(Map<Object, Object> context)
  {
    if (context != null)
    {
      EValidator result = (EValidator)context.get(EValidator.class);
      if (result != null)
      {
        return result;
      }
    }

    return Diagnostician.INSTANCE;
  }

  /**
   * @since 2.6
   */
  protected static EValidator.ValidationDelegate.Registry getValidationDelegateRegistry(Map<Object, Object> context)
  {
    if (context != null)
    {
      EValidator.ValidationDelegate.Registry result = (EValidator.ValidationDelegate.Registry)context.get(EValidator.ValidationDelegate.Registry.class);
      if (result != null)
      {
        return result;
      }
    }

    return EValidator.ValidationDelegate.Registry.INSTANCE;
  }

  /**
   * Delegates evaluation of the given invariant expression against the object in the given context.
   * @return the result of the expression evaluation.
   * @since 2.6
   */
  public static boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context, String validationDelegate, EOperation invariant, String expression, int severity, String source, int code)
  {
    ValidationDelegate delegate = getValidationDelegateRegistry(context).getValidationDelegate(validationDelegate);
    if (delegate != null)
    {
      try
      {
        if (!delegate.validate(eClass, eObject, context, invariant, expression))
        {
          if (diagnostics != null)
            reportInvariantDelegateViolation(eClass, eObject, diagnostics, context, invariant, severity, source, code);
          return false;
        }
      }
      catch (Throwable throwable)
      {
        if (diagnostics != null)
          reportInvariantDelegateException(eClass, eObject, diagnostics, context, invariant, severity, source, code, throwable);
      }
    }
    else
    {
      if (diagnostics != null)
        reportInvariantDelegateNotFound(eClass, eObject, diagnostics, context, invariant, severity, source, code, validationDelegate);
    }
    return true;
  }

  /**
   * Delegates evaluation of the given constraint expression against the object in the given context.
   * @return the result of the expression evaluation.
   * @since 2.6
   */
  public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context, String validationDelegate, String constraint, String expression, int severity, String source, int code)
  {
    ValidationDelegate delegate = getValidationDelegateRegistry(context).getValidationDelegate(validationDelegate);
    if (delegate != null)
    {
      try
      {
        if (!delegate.validate(eClass, eObject, context, constraint, expression))
        {
          if (diagnostics != null)
            reportConstraintDelegateViolation(eClass, eObject, diagnostics, context, constraint, severity, source, code);
          return false;
        }
      }
      catch (Throwable throwable)
      {
        if (diagnostics != null)
          reportConstraintDelegateException(eClass, eObject, diagnostics, context, constraint, severity, source, code, throwable);
      }
    }
    else
    {
      if (diagnostics != null)
        reportConstraintDelegateNotFound(eClass, eObject, diagnostics, context, constraint, severity, source, code, validationDelegate);
    }
    return true;
  }

  /**
   * Delegates evaluation of the given constraint expression against the value in the given context.
   * @return the result of the expression evaluation.
   * @since 2.6
   */
  public boolean validate(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context, String validationDelegate, String constraint, String expression, int severity, String source, int code)
  {
    ValidationDelegate delegate = getValidationDelegateRegistry(context).getValidationDelegate(validationDelegate);
    if (delegate != null)
    {
      try
      {
        if (!delegate.validate(eDataType, value, context, constraint, expression))
        {
          if (diagnostics != null)
            reportConstraintDelegateViolation(eDataType, value, diagnostics, context, constraint, severity, source, code);
          return false;
        }
      }
      catch (Throwable throwable)
      {
        if (diagnostics != null)
          reportConstraintDelegateException(eDataType, value, diagnostics, context, constraint, severity, source, code, throwable);
      }
    }
    else
    {
      if (diagnostics != null)
        reportConstraintDelegateNotFound(eDataType, value, diagnostics, context, constraint, severity, source, code, validationDelegate);
    }
    return true;
  }

  /**
   * Validates the object in the given context, optionally producing diagnostics.
   * @param diagnostics a place to accumulate diagnostics; if it's <code>null</code>, no diagnostics should be produced.
   * @param context a place to cache information, if it's <code>null</code>, no cache is supported.
   * @return whether the object is valid.
   */
  public boolean validate(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    return validate(eObject.eClass(), eObject, diagnostics, context);
  }

  public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
	if (eObject.eIsProxy())
    {
      if (context != null && context.get(ROOT_OBJECT) != null)
      {
        if (diagnostics != null)
        {
          diagnostics.add
            (createDiagnostic
              (Diagnostic.ERROR,
               DIAGNOSTIC_SOURCE,
               EOBJECT__EVERY_PROXY_RESOLVES,
               "_UI_UnresolvedProxy_diagnostic",
               new Object []
               {
                 getFeatureLabel(eObject.eContainmentFeature(), context),
                 getObjectLabel(eObject.eContainer(), context),
                 getObjectLabel(eObject, context)
               },
               new Object [] { eObject.eContainer(), eObject.eContainmentFeature(), eObject },
               context));
        }
        return false;
      }
      else
      {
        return true;
      }
    }
	else if (eClass.eContainer() == getEPackage())
    {
      return validate(eClass.getClassifierID(), eObject, diagnostics, context);
    }
    else
    {
      return
        new DynamicEClassValidator()
        {
          // Ensure that the class loader for this class will be used downstream.
          //
        }.validate(eClass, eObject, diagnostics, context);
    }
  }

  protected boolean validate(int classifierID, Object object, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    return classifierID != EcorePackage.EOBJECT || validate_EveryDefaultConstraint((EObject)object, diagnostics, context);
  }

  public boolean validate_EveryDefaultConstraint(EObject object, DiagnosticChain theDiagnostics, Map<Object, Object> context)
  {
    if (!validate_NoCircularContainment(object, theDiagnostics, context))
    {
      return false;
    }
    boolean result = validate_EveryMultiplicityConforms(object, theDiagnostics, context);
    if (result || theDiagnostics != null)
    {
      result &= validate_EveryProxyResolves(object, theDiagnostics, context);
    }
    if (result || theDiagnostics != null)
    {
      result &= validate_EveryReferenceIsContained(object, theDiagnostics, context);
    }
    if (result || theDiagnostics != null)
    {
      result &= validate_EveryBidirectionalReferenceIsPaired(object, theDiagnostics, context);
    }
    if (result || theDiagnostics != null)
    {
      result &= validate_EveryDataValueConforms(object, theDiagnostics, context);
    }
    if (result || theDiagnostics != null)
    {
      result &= validate_UniqueID(object, theDiagnostics, context);
    }
    if (result || theDiagnostics != null)
    {
      result &= validate_EveryKeyUnique(object, theDiagnostics, context);
    }
    if (result || theDiagnostics != null)
    {
      result &= validate_EveryMapEntryUnique(object, theDiagnostics, context);
    }
    return result;
  }

  public boolean validate_NoCircularContainment(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    if (context != null)
    {
      Object root = context.get(ROOT_OBJECT);
      if (root == null)
      {
        context.put(ROOT_OBJECT, eObject);
      }
      else if (root == eObject)
      {
        if (diagnostics != null)
        {
          diagnostics.add
            (createDiagnostic
              (Diagnostic.ERROR,
               DIAGNOSTIC_SOURCE,
               EOBJECT__NO_CIRCULAR_CONTAINMENT,
               "_UI_CircularContainment_diagnostic",
                new Object []
                {
                  getObjectLabel(eObject, context),
                },
               new Object [] { eObject },
               context));
        }
        return false;
      }
    }
    return true;
  }

  public boolean validate_EveryBidirectionalReferenceIsPaired(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    for (EReference eReference : eObject.eClass().getEAllReferences())
    {
      if (eReference.isResolveProxies())
      {
        EReference eOpposite = eReference.getEOpposite();
        if (eOpposite != null)
        {
          result &= validate_BidirectionalReferenceIsPaired(eObject, eReference, eOpposite, diagnostics, context);
          if (!result && diagnostics == null)
          {
            return false;
          }
        }
      }
    }
    return result;
  }

  public boolean validate_BidirectionalReferenceIsPaired(EObject eObject, EReference eReference, EReference eOpposite, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    Object value = eObject.eGet(eReference);
    if (eReference.isMany())
    {
      @SuppressWarnings("unchecked")
      List<EObject> values = (List<EObject>)value;
      if (eOpposite.isMany())
      {
        for (EObject oppositeEObject : values)
        {
          @SuppressWarnings("unchecked")
          List<EObject> oppositeValues = (List<EObject>)oppositeEObject.eGet(eOpposite);
          if (!oppositeValues.contains(eObject))
          {
            result = false;
            if (diagnostics != null)
            {
              // TODO
              diagnostics.add
                (createDiagnostic
                  (Diagnostic.ERROR,
                   DIAGNOSTIC_SOURCE,
                   EOBJECT__EVERY_BIDIRECTIONAL_REFERENCE_IS_PAIRED,
                   "_UI_UnpairedBidirectionalReference_diagnostic",
                    new Object []
                    {
                      getFeatureLabel(eReference, context),
                      getObjectLabel(eObject, context),
                      getFeatureLabel(eOpposite, context),
                      getObjectLabel(oppositeEObject, context),
                    },
                   new Object [] { eObject, eReference, oppositeEObject, eOpposite },
                   context));
            }
            else
            {
              break;
            }
          }
        }
      }
      else
      {
        for (EObject oppositeEObject : values)
        {
          if (oppositeEObject.eGet(eOpposite) != eObject)
          {
            result = false; 
            if (diagnostics != null)
            {
              // TODO
              diagnostics.add
                (createDiagnostic
                  (Diagnostic.ERROR,
                   DIAGNOSTIC_SOURCE,
                   EOBJECT__EVERY_BIDIRECTIONAL_REFERENCE_IS_PAIRED,
                   "_UI_UnpairedBidirectionalReference_diagnostic",
                    new Object []
                    {
                      getFeatureLabel(eReference, context),
                      getObjectLabel(eObject, context),
                      getFeatureLabel(eOpposite, context),
                      getObjectLabel(oppositeEObject, context),
                    },
                   new Object [] { eObject, eReference, oppositeEObject, eOpposite },
                   context));
            }
            else
            {
              break;
            }
          }
        }
      }
    }
    else
    {
      EObject oppositeEObject = (EObject)value;
      if (oppositeEObject != null)
      {
        if (eOpposite.isMany())
        {
          @SuppressWarnings("unchecked")
          List<EObject> oppositeValues = (List<EObject>)oppositeEObject.eGet(eOpposite);
          if (!oppositeValues.contains(eObject))
          {
            result = false;
            if (diagnostics != null)
            {
              // TODO
              diagnostics.add
                (createDiagnostic
                  (Diagnostic.ERROR,
                   DIAGNOSTIC_SOURCE,
                   EOBJECT__EVERY_BIDIRECTIONAL_REFERENCE_IS_PAIRED,
                   "_UI_UnpairedBidirectionalReference_diagnostic",
                    new Object []
                    {
                      getFeatureLabel(eReference, context),
                      getObjectLabel(eObject, context),
                      getFeatureLabel(eOpposite, context),
                      getObjectLabel(oppositeEObject, context),
                    },
                   new Object [] { eObject, eReference, oppositeEObject, eOpposite },
                   context));
            }
          }
        }
        else
        {
          if (oppositeEObject.eGet(eOpposite) != eObject)
          {
            result = false;
            if (diagnostics != null)
            {
              diagnostics.add
                (createDiagnostic
                  (Diagnostic.ERROR,
                   DIAGNOSTIC_SOURCE,
                   EOBJECT__EVERY_BIDIRECTIONAL_REFERENCE_IS_PAIRED,
                   "_UI_UnpairedBidirectionalReference_diagnostic",
                    new Object []
                    {
                      getFeatureLabel(eReference, context),
                      getObjectLabel(eObject, context),
                      getFeatureLabel(eOpposite, context),
                      getObjectLabel(oppositeEObject, context),
                    },
                   new Object [] { eObject, eReference, oppositeEObject, eOpposite },
                   context));
            }
          }
        }
      }
    }
    return result;
  }

  public boolean validate_EveryMultiplicityConforms(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    EClass eClass = eObject.eClass();
    for (int i = 0, size = eClass.getFeatureCount(); i < size; ++i)
    {
      result &= validate_MultiplicityConforms(eObject, eClass.getEStructuralFeature(i), diagnostics, context);
      if (!result && diagnostics == null)
      {
        return false;
      }
    }
    return result;
  }
  
  protected boolean validate_MultiplicityConforms
    (EObject eObject, EStructuralFeature eStructuralFeature, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    if (eStructuralFeature.isMany())
    {
      if (FeatureMapUtil.isFeatureMap(eStructuralFeature) &&  ExtendedMetaData.INSTANCE.isDocumentRoot(eObject.eClass()))
      {
        FeatureMap featureMap = (FeatureMap)eObject.eGet(eStructuralFeature);
        int count = 0;
        for (int i = 0, size = featureMap.size(); i < size; ++i)
        {
          EStructuralFeature feature = featureMap.getEStructuralFeature(i);
          int kind = ExtendedMetaData.INSTANCE.getFeatureKind(feature);
          if (kind == ExtendedMetaData.ELEMENT_FEATURE && 
                feature != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__CDATA &&
                feature != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__COMMENT &&
                feature != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__TEXT &&
                feature != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT__PROCESSING_INSTRUCTION &&
                ++count > 1)
          {
            result = false;
            break;
          }
        }
        if (count != 1)
        {
          result = false;
          if (diagnostics != null)
          {
            diagnostics.add
              (createDiagnostic
                (Diagnostic.ERROR,
                 DIAGNOSTIC_SOURCE,
                 EOBJECT__EVERY_MULTIPCITY_CONFORMS,
                 "_UI_DocumentRootMustHaveOneElement_diagnostic",
                  new Object []
                  {
                    getFeatureLabel(eStructuralFeature, context),
                    getObjectLabel(eObject, context),
                    count
                  },
                 new Object [] { eObject, eStructuralFeature },
                 context));
          }
        }
      }
      else
      {
        int lowerBound = eStructuralFeature.getLowerBound();
        if (lowerBound > 0)
        {
          int size = ((List<?>)eObject.eGet(eStructuralFeature)).size();
          if (size < lowerBound)
          {
            result = false;
            if (diagnostics != null)
            {
              diagnostics.add
                (createDiagnostic
                  (Diagnostic.ERROR,
                   DIAGNOSTIC_SOURCE,
                   EOBJECT__EVERY_MULTIPCITY_CONFORMS,
                   "_UI_FeatureHasTooFewValues_diagnostic",
                    new Object []
                    {
                      getFeatureLabel(eStructuralFeature, context),
                      getObjectLabel(eObject, context),
                      size,
                      lowerBound
                    },
                   new Object [] { eObject, eStructuralFeature },
                   context));
            }
          }
          int upperBound = eStructuralFeature.getUpperBound();
          if (upperBound > 0 && size > upperBound)
          {
            result = false;
            if (diagnostics != null)
            {
              diagnostics.add
                (createDiagnostic
                  (Diagnostic.ERROR,
                   DIAGNOSTIC_SOURCE,
                   EOBJECT__EVERY_MULTIPCITY_CONFORMS,
                   "_UI_FeatureHasTooManyValues_diagnostic",
                   new Object []
                   {
                     getFeatureLabel(eStructuralFeature, context),
                     getObjectLabel(eObject, context),
                     size,
                     upperBound
                   },
                   new Object [] { eObject, eStructuralFeature },
                   context));
            }
          }
        }
        else
        {
          int upperBound = eStructuralFeature.getUpperBound();
          if (upperBound > 0)
          {
            int size = ((List<?>)eObject.eGet(eStructuralFeature)).size();
            if (size > upperBound)
            {
              result = false;
              if (diagnostics != null)
              {
                diagnostics.add
                  (createDiagnostic
                    (Diagnostic.ERROR,
                     DIAGNOSTIC_SOURCE,
                     EOBJECT__EVERY_MULTIPCITY_CONFORMS,
                     "_UI_FeatureHasTooManyValues_diagnostic",
                     new Object []
                     {
                       getFeatureLabel(eStructuralFeature, context),
                       getObjectLabel(eObject, context),
                       size,
                       upperBound
                     },
                     new Object [] { eObject, eStructuralFeature },
                     context));
              }
            }
          }
        }
      }
    }
    else if (eStructuralFeature.isRequired())
    {
      if (eStructuralFeature.isUnsettable() ? !eObject.eIsSet(eStructuralFeature) : eObject.eGet(eStructuralFeature, false) == null)
      {
        result = false;
        if (diagnostics != null)
        {
          diagnostics.add
            (createDiagnostic
              (Diagnostic.ERROR,
               DIAGNOSTIC_SOURCE,
               EOBJECT__EVERY_MULTIPCITY_CONFORMS,
               "_UI_RequiredFeatureMustBeSet_diagnostic",
               new Object [] { getFeatureLabel(eStructuralFeature, context), getObjectLabel(eObject, context) },
               new Object [] { eObject, eStructuralFeature },
               context));
        }
      }
    }

    return result;
  }

  public boolean validate_EveryProxyResolves(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    for (EContentsEList.FeatureIterator<EObject> i = (EContentsEList.FeatureIterator<EObject>)eObject.eCrossReferences().iterator(); i.hasNext(); )
    {
      EObject eCrossReferenceObject = i.next();
      if (eCrossReferenceObject.eIsProxy())
      {
        result = false;
        if (diagnostics != null)
        {
          diagnostics.add
            (createDiagnostic
              (Diagnostic.ERROR,
               DIAGNOSTIC_SOURCE,
               EOBJECT__EVERY_PROXY_RESOLVES,
               "_UI_UnresolvedProxy_diagnostic",
               new Object []
               {
                 getFeatureLabel(i.feature(), context),
                 getObjectLabel(eObject, context),
                 getObjectLabel(eCrossReferenceObject, context)
               },
               new Object [] { eObject, i.feature(), eCrossReferenceObject },
               context));
        }
        else
        {
          break;
        }
      }
    }
    return result;
  }

  public boolean validate_EveryReferenceIsContained(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    if (eObject.eResource() != null)
    {
      for (EContentsEList.FeatureIterator<EObject> i = (EContentsEList.FeatureIterator<EObject>)eObject.eCrossReferences().iterator(); i.hasNext(); )
      {
        EObject eCrossReferenceObject = i.next();
        if (eCrossReferenceObject.eResource() == null && !eCrossReferenceObject.eIsProxy() && !i.feature().isTransient())
        {
          result = false;
          if (diagnostics != null)
          {
            diagnostics.add
              (createDiagnostic
                (Diagnostic.ERROR,
                 DIAGNOSTIC_SOURCE,
                 EOBJECT__EVERY_REFERENCE_IS_CONTAINED,
                 "_UI_DanglingReference_diagnostic",
                 new Object []
                 {
                   getFeatureLabel(i.feature(), context),
                   getObjectLabel(eObject, context),
                   getObjectLabel(eCrossReferenceObject, context)
                 },
                 new Object [] { eObject, i.feature(), eCrossReferenceObject },
                 context));
          }
          else
          {
            break;
          }
        }
      }
    }
    return result;
  }

  public boolean validate_EveryDataValueConforms(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    for (EAttribute eAttribute : eObject.eClass().getEAllAttributes())
    {
      result &= validate_DataValueConforms(eObject, eAttribute, diagnostics, context);
      if (!result && diagnostics == null)
      {
        return false;
      }
    }
    return result;
  }

  protected boolean validate_DataValueConforms
    (EObject eObject, EAttribute eAttribute, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    if (!eObject.eIsSet(eAttribute))
    {
      return true;
    }
    boolean result = true;
    EDataType eDataType = eAttribute.getEAttributeType();
    EValidator rootValidator = getRootEValidator(context);
    Object value = eObject.eGet(eAttribute);
    if (FeatureMapUtil.isFeatureMap(eAttribute))
    {
      @SuppressWarnings("unchecked") Collection<FeatureMap.Entry> featureMap = (Collection<FeatureMap.Entry>)value;
      EClass eClass = eObject.eClass();
      Map<EStructuralFeature, DiagnosticChain> entryFeatureToDiagnosticChainMap = null;
      for (Iterator<FeatureMap.Entry> i = featureMap.iterator(); i.hasNext() && (result || diagnostics != null); )
      {
        FeatureMap.Entry entry = i.next();
        EStructuralFeature entryFeature = entry.getEStructuralFeature();
        if (entryFeature instanceof EAttribute &&
              ExtendedMetaData.INSTANCE.getAffiliation(eClass, entryFeature) == eAttribute)
        {
          EDataType entryType = (EDataType)entryFeature.getEType();
          Object entryValue = entry.getValue();
          boolean entryIsValid = rootValidator.validate(entryType, entryValue, null, context);
          if (!entryIsValid)
          {
            result = false;
            if (diagnostics != null)
            {
              if (entryFeatureToDiagnosticChainMap == null)
              {
                entryFeatureToDiagnosticChainMap = new HashMap<EStructuralFeature, DiagnosticChain>();
              }
              DiagnosticChain entryFeatureDiagnostic = entryFeatureToDiagnosticChainMap.get(entryFeature);
              if (entryFeatureDiagnostic == null)
              {
                entryFeatureDiagnostic = createBadDataValueDiagnostic(eObject, (EAttribute)entryFeature, diagnostics, context);
                entryFeatureToDiagnosticChainMap.put(entryFeature, entryFeatureDiagnostic);
              }
              rootValidator.validate(entryType, entryValue, entryFeatureDiagnostic, context);
            }
          }
        }
      }
    }
    else if (eAttribute.isMany())
    {
      for (Iterator<?> i = ((List<?>)value).iterator(); i.hasNext() && result; )
      {
        result &= rootValidator.validate(eDataType, i.next(), null, context);
      }

      if (!result && diagnostics != null)
      {
        DiagnosticChain diagnostic = createBadDataValueDiagnostic(eObject, eAttribute, diagnostics, context);
        for (Iterator<?> i = ((List<?>)value).iterator(); i.hasNext(); )
        {
          rootValidator.validate(eDataType, i.next(), diagnostic, context);
        }
      }
    }
    else if (value != null)
    {
      result = rootValidator.validate(eDataType, value, null, context);
      if (!result && diagnostics != null)
      {
        DiagnosticChain diagnostic = createBadDataValueDiagnostic(eObject, eAttribute, diagnostics, context);
        rootValidator.validate(eDataType, value, diagnostic, context);
      }
    }

    return result;
  }

  protected DiagnosticChain createBadDataValueDiagnostic
    (EObject eObject, EAttribute eAttribute, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    BasicDiagnostic diagnostic =
      createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         EOBJECT__EVERY_DATA_VALUE_CONFORMS,
         "_UI_BadDataValue_diagnostic",
         new Object []
         {
           getFeatureLabel(eAttribute, context),
           getObjectLabel(eObject, context)
         },
         new Object [] { eObject, eAttribute },
         context);
    diagnostics.add(diagnostic);
    return diagnostic;
  }

  protected boolean validatePattern
    (EDataType eDataType, Object value, PatternMatcher [][] patterns, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    String literal = EcoreUtil.convertToString(eDataType, value);
    for (int i = 0; i < patterns.length; ++i)
    {
      PatternMatcher [] children = patterns[i];
      boolean matches = false;
      for (int j = 0; j < children.length; ++j)
      {
        if (children[j].matches(literal))
        {
          matches = true;
          break;
        }
      }
      if (!matches)
      {
        if (diagnostics != null)
        {
          reportDataValuePatternViolation(eDataType, value, children, diagnostics, context);
        }
        return false;
      }
    }
    return true;
  }

  public class DynamicEDataTypeValidator
  {
    protected List<Object> effectiveEnumeration;
    protected PatternMatcher [][] effectivePattern;
    protected int effectiveTotalDigits = -1;
    protected int effectiveFractionDigits = -1;
    protected int effectiveMinLength = -1;
    protected int effectiveMaxLength = -1;
    protected Object effectiveMin;
    protected boolean effectiveMinIsInclusive;
    protected int effectiveTotalDigitsMin = -1;
    protected Object effectiveMax;
    protected boolean effectiveMaxIsInclusive;
    protected int effectiveTotalDigitsMax = -1;
    protected EDataType builtinType;
    protected EDataType itemType;
    protected List<EDataType> memberTypes;

    public DynamicEDataTypeValidator(EDataType eDataType)
    {
      ExtendedMetaData extendedMetaData = ExtendedMetaData.INSTANCE;
      Resource resource = eDataType.eResource();
      if (resource != null)
      {
        ResourceSet resourceSet = resource.getResourceSet();
        if (resourceSet != null)
        {
          extendedMetaData = new BasicExtendedMetaData(resourceSet.getPackageRegistry());
        }
      }

      List<PatternMatcher[]> patterns = null;

      for (;;)
      {
        if (effectiveEnumeration == null)
        {
          List<String> enumeration = extendedMetaData.getEnumerationFacet(eDataType);
          if (!enumeration.isEmpty())
          {
            effectiveEnumeration = new ArrayList<Object>();
            for (String enumerator : enumeration)
            {
              effectiveEnumeration.add(EcoreUtil.createFromString(eDataType, enumerator));
            }
          }
        }

        List<String> pattern = extendedMetaData.getPatternFacet(eDataType);
        if (!pattern.isEmpty())
        {
          if (patterns == null)
          {
            patterns = new ArrayList<PatternMatcher[]>();
          }
          PatternMatcher [] children = new PatternMatcher [pattern.size()];
          patterns.add(children);
          for (ListIterator<String> i = pattern.listIterator(); i.hasNext(); )
          {
            PatternMatcher patternMatcher = XMLTypeUtil.createPatternMatcher(i.next());
            children[i.previousIndex()] = patternMatcher;
          }
        }

        if (effectiveTotalDigits == -1)
        {
          effectiveTotalDigits = extendedMetaData.getTotalDigitsFacet(eDataType);
        }
        if (effectiveFractionDigits == -1)
        {
          effectiveFractionDigits = extendedMetaData.getFractionDigitsFacet(eDataType);
        }
        if (effectiveMinLength == -1)
        {
          effectiveMinLength = extendedMetaData.getLengthFacet(eDataType);
          if (effectiveMinLength == -1)
          {
            effectiveMinLength = extendedMetaData.getMinLengthFacet(eDataType);
          }
        }
        if (effectiveMaxLength == -1)
        {
          effectiveMaxLength = extendedMetaData.getLengthFacet(eDataType);
          if (effectiveMaxLength == -1)
          {
            effectiveMaxLength = extendedMetaData.getMaxLengthFacet(eDataType);
          }
        }
        if (effectiveMin == null)
        {
          effectiveMin = extendedMetaData.getMinExclusiveFacet(eDataType);
          if (effectiveMin == null)
          {
            effectiveMin = extendedMetaData.getMinInclusiveFacet(eDataType);
            if (effectiveMin != null)
            {
              effectiveMin = EcoreUtil.createFromString(eDataType, (String)effectiveMin);
              effectiveMinIsInclusive = true;
            }
          }
          else
          {
            effectiveMin = EcoreUtil.createFromString(eDataType, (String)effectiveMin);
            effectiveMinIsInclusive = false;
          }
        }
        if (effectiveMax == null)
        {
          effectiveMax = extendedMetaData.getMaxExclusiveFacet(eDataType);
          if (effectiveMax == null)
          {
            effectiveMax = extendedMetaData.getMaxInclusiveFacet(eDataType);
            if (effectiveMax != null)
            {
              effectiveMax = EcoreUtil.createFromString(eDataType, (String)effectiveMax);
              effectiveMaxIsInclusive = true;
            }
          }
          else
          {
            effectiveMax = EcoreUtil.createFromString(eDataType, (String)effectiveMax);
            effectiveMaxIsInclusive = false;
          }
        }

        EDataType baseType = extendedMetaData.getBaseType(eDataType);
        if (baseType != null)
        {
          eDataType = baseType;
          if (eDataType.getEPackage() == XMLTypePackage.eINSTANCE && eDataType.getInstanceClassName() == "javax.xml.datatype.XMLGregorianCalendar")
          {
            builtinType = eDataType;
            itemType = null;
            memberTypes = Collections.emptyList();
            break;
          }
          else
          {
            continue;
          }
        }
        else
        {
          itemType = extendedMetaData.getItemType(eDataType);
          memberTypes = extendedMetaData.getMemberTypes(eDataType);
          break;
        }
      }

      if (patterns != null)
      {
        effectivePattern = new PatternMatcher [patterns.size()][];
        patterns.toArray(effectivePattern);
      }

      if (effectiveTotalDigits != -1 && eDataType.getInstanceClassName() != "java.math.BigDecimal")
      {
        StringBuilder digits = new StringBuilder("1");
        for (int i = effectiveTotalDigits; i > 0; --i)
        {
          digits.append("0");
        }
        
        try
        {
          Object lowerBound = EcoreUtil.createFromString(eDataType, "-" + digits.toString());
          @SuppressWarnings("unchecked") boolean lowerBounded = effectiveMin == null ||
                (effectiveMinIsInclusive ?
                   ((Comparable<Object>)effectiveMin).compareTo(lowerBound) <= 0:
                   ((Comparable<Object>)effectiveMin).compareTo(lowerBound) < 0);
          if (lowerBounded)
          {
            effectiveMinIsInclusive = false;
            effectiveMin = lowerBound;
            effectiveTotalDigitsMin = effectiveTotalDigits;
          }
        }
        catch (NumberFormatException exception) 
        {
          // Ignore the bound if the value is too big.
        }

        try
        {
          Object upperBound = EcoreUtil.createFromString(eDataType, digits.toString());
          @SuppressWarnings("unchecked") boolean upperBounded = effectiveMax == null ||
                (effectiveMaxIsInclusive ?
                   ((Comparable<Object>)effectiveMax).compareTo(upperBound) >= 0:
                   ((Comparable<Object>)effectiveMax).compareTo(upperBound) > 0);
          if (upperBounded)
          {
            effectiveMaxIsInclusive = false;
            effectiveMax = upperBound;
            effectiveTotalDigitsMax = effectiveTotalDigits;
          }
        }
        catch (NumberFormatException exception) 
        {
          // Ignore the bound if the value is too big.
        }

        effectiveTotalDigits = -1;
      }

      if (effectiveFractionDigits != -1 && eDataType.getInstanceClassName() != "java.math.BigDecimal")
      {
        effectiveFractionDigits = -1;
      }
    }

    protected boolean validateDelegatedConstraints(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context)
    {
      boolean result = true;
      List<String> validationDelegates = EcoreUtil.getValidationDelegates(eDataType.getEPackage());

      if (!validationDelegates.isEmpty())
      {
        CONSTRAINTS: for (String constraint : EcoreUtil.getConstraints(eDataType))
        {
          for (String validationDelegate : validationDelegates)
          {
            String expression = EcoreUtil.getAnnotation(eDataType, validationDelegate, constraint);
            if (expression != null)
            {
              result &= EObjectValidator.this.validate(eDataType, value, diagnostics, context, validationDelegate, constraint, expression, Diagnostic.ERROR, DIAGNOSTIC_SOURCE, 0);
              if (!result && diagnostics == null)
                break CONSTRAINTS;
            }
          }
        }
      }

      return result;
    }

    protected boolean validateSchemaConstraints(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context)
    {
      boolean result = true;

      if (effectiveEnumeration != null)
      {
        if (!effectiveEnumeration.contains(value))
        {
          if (diagnostics != null)
            reportEnumerationViolation(eDataType, value, effectiveEnumeration, diagnostics, context);
          result = false;
        }
      }

      if (effectivePattern != null)
      {
        result = validatePattern(eDataType, value, effectivePattern, diagnostics, context);
      }

      if (effectiveMin != null)
      {
        @SuppressWarnings("unchecked")
        Comparable<Object> comparableObject = (Comparable<Object>)effectiveMin;
        if (effectiveMinIsInclusive ? comparableObject.compareTo(value) > 0 : comparableObject.compareTo(value) >= 0)
        {
          if (diagnostics != null)
          {
            if (effectiveTotalDigitsMin != -1)
            {
              reportTotalDigitsViolation(eDataType, value, effectiveTotalDigitsMin, diagnostics, context);
            }
            else
            {
              reportMinViolation(eDataType, value, effectiveMin, effectiveMinIsInclusive, diagnostics, context);
            }
          }
          result = false;
        }
      }

      if (effectiveMax != null)
      {
        @SuppressWarnings("unchecked")
        Comparable<Object> comparableObject = (Comparable<Object>)effectiveMax;
        if (effectiveMaxIsInclusive ? comparableObject.compareTo(value) < 0 : comparableObject.compareTo(value) <= 0)
        {
          if (diagnostics != null)
          {
            if (effectiveTotalDigitsMax != -1)
            {
              reportTotalDigitsViolation(eDataType, value, effectiveTotalDigitsMax, diagnostics, context);
            }
            else
            {
              reportMaxViolation(eDataType, value, effectiveMax, effectiveMaxIsInclusive, diagnostics, context);
            }
          }
          result = false;
        }
      }

      if (effectiveMinLength != -1)
      {
        int length =
          value instanceof String ?
            ((String)value).length() :
             value instanceof Collection<?> ?
               ((Collection<?>)value).size() :
               Array.getLength(value);
        if (length < effectiveMinLength)
        {
          if (diagnostics != null) reportMinLengthViolation(eDataType, value, length, effectiveMinLength, diagnostics, context);
          result = false;
        }
      }

      if (effectiveMaxLength != -1)
      {
        int length =
          value instanceof String ?
            ((String)value).length() :
             value instanceof Collection<?> ?
               ((Collection<?>)value).size() :
               Array.getLength(value);
        if (length > effectiveMaxLength)
        {
          if (diagnostics != null) reportMaxLengthViolation(eDataType, value, length, effectiveMaxLength, diagnostics, context);
          result = false;
        }
      }

      if (effectiveTotalDigits != -1)
      {
        if (value instanceof BigDecimal)
        {
          BigDecimal bigDecimal = (BigDecimal)value;
          int scale = bigDecimal.scale();
          int totalDigits = scale < 0 ? bigDecimal.precision() - scale : bigDecimal.precision();
          if (totalDigits > effectiveTotalDigits)
          {
            if (diagnostics != null) reportTotalDigitsViolation(eDataType, value, effectiveTotalDigits, diagnostics, context);
            result = false;
          }
        }
      }

      if (effectiveFractionDigits != -1)
      {
        if (value instanceof BigDecimal && ((BigDecimal)value).scale() > effectiveFractionDigits)
        {
          if (diagnostics != null) reportFractionDigitsViolation(eDataType, value, effectiveFractionDigits, diagnostics, context);
          result = false;
        }
      }
      
      if (builtinType != null)
      {
        EValidator rootValidator = getRootEValidator(context);
        result &= rootValidator.validate(builtinType, value, diagnostics, context);
      }

      return result;
    }

    public boolean validate(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context)
    {
      boolean result = validateDelegatedConstraints(eDataType, value, diagnostics, context);

      if (result || diagnostics != null)
      {
        result &= validateSchemaConstraints(eDataType, value, diagnostics, context);

        if (itemType != null)
        {
          EValidator rootValidator = getRootEValidator(context);
          for (Iterator< ? > i = ((List< ? >)value).iterator(); i.hasNext() && (result || diagnostics != null);)
          {
            result &= rootValidator.validate(itemType, i.next(), diagnostics, context);
          }
          return result;
        }
        else if (!memberTypes.isEmpty())
        {
          EValidator rootValidator = getRootEValidator(context);

          for (EDataType memberType : memberTypes)
          {
            if (rootValidator.validate(memberType, value, null, context))
            {
              return true;
            }
          }
          for (EDataType memberType : memberTypes)
          {
            if (memberType.isInstance(value))
            {
              return rootValidator.validate(memberType, value, diagnostics, context);
            }
          }
          return false;
        }
        else
        {
          return result;
        }
      }

      return result;
    }
  }

  public class DynamicEClassValidator
  {
    protected boolean validateDelegatedInvariants(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
    {
      boolean result = true;
      List<String> validationDelegates = EcoreUtil.getValidationDelegates(eClass.getEPackage());

      if (!validationDelegates.isEmpty())
      {
        INVARIANTS: for (EOperation eOperation : eClass.getEOperations())
        {
          if (EcoreUtil.isInvariant(eOperation))
          {
            for (String validationDelegate : validationDelegates)
            {
              String expression = EcoreUtil.getAnnotation(eOperation, validationDelegate, "body");
              if (expression != null)
              {
                result &= EObjectValidator.validate(eClass, eObject, diagnostics, context, validationDelegate, eOperation, expression, Diagnostic.ERROR, DIAGNOSTIC_SOURCE, 0);
                if (!result && diagnostics == null)
                  break INVARIANTS;
              }
            }
          }
        }
      }

      return result;
    }
    
    protected boolean validateDelegatedConstraints(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
    {
      boolean result = true;
      List<String> validationDelegates = EcoreUtil.getValidationDelegates(eClass.getEPackage());

      if (!validationDelegates.isEmpty())
      {
        CONSTRAINTS: for (String constraint : EcoreUtil.getConstraints(eClass))
        {
          for (String validationDelegate : validationDelegates)
          {
            String expression = EcoreUtil.getAnnotation(eClass, validationDelegate, constraint);
            if (expression != null)
            {
              result &= EObjectValidator.this.validate(eClass, eObject, diagnostics, context, validationDelegate, constraint, expression, Diagnostic.ERROR, DIAGNOSTIC_SOURCE, 0);
              if (!result && diagnostics == null)
                break CONSTRAINTS;
            }
          }
        }
      }

      return result;
    }
    
    public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
    {
      boolean result = validateDelegatedInvariants(eClass, eObject, diagnostics, context);

      if (result || diagnostics != null)
      {
        result &= validateDelegatedConstraints(eClass, eObject, diagnostics, context);
        
        if (result || diagnostics != null)
        {
          List<EClass> eSuperTypes = eClass.getESuperTypes();
          result &= eSuperTypes.isEmpty() ?
            validate_EveryDefaultConstraint(eObject, diagnostics, context) :
              eClass.eContainer() == getEPackage() ?
                EObjectValidator.this.validate(eClass.getClassifierID(), eObject, diagnostics, context) :
                validate(eSuperTypes.get(0), eObject, diagnostics, context);
        }
      }

      return result;
    }
  }

  public boolean validate(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    if (!eDataType.isInstance(value))
    {
      if (value == null)
      {
        return true;
      }
      else
      {
        if (diagnostics != null) reportDataValueTypeViolation(eDataType, value, diagnostics, context);
        return false;
      }
    }

    if (eDataType.eContainer() == getEPackage())
    {
      return validate(eDataType.getClassifierID(), value, diagnostics, context);
    }
    else
    {
      return
        new DynamicEDataTypeValidator(eDataType)
        {
          // Ensure that the class loader for this class will be used downstream.
          //
        }.validate(eDataType, value, diagnostics, context);
    }
  }

  protected void reportMinViolation
    (EDataType eDataType, Object value, Object bound, boolean isInclusive, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__VALUE_IN_RANGE,
         isInclusive ? "_UI_MinInclusiveConstraint_diagnostic" : "_UI_MinExclusiveConstraint_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           isInclusive ? ">=" : ">",
           getValueLabel(eDataType, bound, context)
         },
         new Object [] { value, bound, isInclusive ? Boolean.TRUE : Boolean.FALSE },
         context));
  }

  protected void reportMaxViolation
    (EDataType eDataType, Object value, Object bound, boolean isInclusive, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__VALUE_IN_RANGE,
         isInclusive ? "_UI_MaxInclusiveConstraint_diagnostic" : "_UI_MaxExclusiveConstraint_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           "<",
           getValueLabel(eDataType, bound, context)
         },
         new Object [] { value, bound, isInclusive ? Boolean.TRUE : Boolean.FALSE },
         context));
  }

  protected void reportMinLengthViolation
    (EDataType eDataType, Object value, int length, int bound, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__LENGTH_IN_RANGE,
         "_UI_MinLengthConstraint_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           Integer.toString(length),
           Integer.toString(bound)
         },
         new Object [] { value, eDataType, length, bound },
         context));
  }

  protected void reportMaxLengthViolation
    (EDataType eDataType, Object value, int length, int bound, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__LENGTH_IN_RANGE,
         "_UI_MaxLengthConstraint_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           Integer.toString(length),
           Integer.toString(bound)
         },
         new Object [] { value, eDataType, length, bound },
         context));
  }

  protected void reportTotalDigitsViolation
    (EDataType eDataType, Object value, int totalDigits, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__TOTAL_DIGITS_IN_RANGE,
         "_UI_TotalDigitsConstraint_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           totalDigits
         },
         new Object [] { value, eDataType, totalDigits },
         context));
  }

  protected void reportFractionDigitsViolation
    (EDataType eDataType, Object value, int fractionDigits, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__FRACTION_DIGITS_IN_RANGE,
         "_UI_FractionDigitsConstraint_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           fractionDigits
         },
         new Object [] { value, eDataType, fractionDigits },
         context));
  }

  protected void reportEnumerationViolation
    (EDataType eDataType, Object value, Collection<?> values, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    String valueLiterals = "";
    Iterator<?> i = values.iterator();
    if (i.hasNext())
    {
      valueLiterals =
        getEcoreResourceLocator().getString("_UI_ListHead_composition", new Object [] { getValueLabel(eDataType, i.next(), context) });
      while (i.hasNext())
      {
        valueLiterals =
          getEcoreResourceLocator().getString
            ("_UI_ListTail_composition",
             new Object [] { valueLiterals, getValueLabel(eDataType, i.next(), context) });
      }
    }
    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__VALUE_IN_ENUMERATION,
         "_UI_EnumerationConstraint_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           valueLiterals
         },
         new Object [] { value, eDataType, values },
         context));
  }

  protected void reportDataValuePatternViolation
    (EDataType eDataType, Object value, PatternMatcher [] patterns, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    String patternLiterals = "";
    if (patterns.length > 0)
    {
      patternLiterals = getEcoreResourceLocator().getString("_UI_ListHead_composition", new Object [] { patterns[0] });
      for (int i = 1; i < patterns.length; ++i)
      {
        patternLiterals = getEcoreResourceLocator().getString("_UI_ListTail_composition", new Object [] { patternLiterals, patterns[i] });
      }
    }

    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__MATCHES_PATTERN,
         "_UI_PatternConstraint_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           patternLiterals
         },
         new Object [] { value, eDataType, patterns },
         context));
  }

  protected void reportDataValueTypeViolation
    (EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    diagnostics.add
      (createDiagnostic
        (Diagnostic.ERROR,
         DIAGNOSTIC_SOURCE,
         DATA_VALUE__TYPE_CORRECT,
         "_UI_BadDataValueType_diagnostic",
         new Object []
         {
           getValueLabel(eDataType, value, context),
           value == null ? "<null>" : value.getClass().getName(),
           eDataType.getInstanceClassName()
         },
         new Object [] { value, eDataType },
         context));
  }

  /**
   * @since 2.6
   */
  protected void reportConstraintDelegateViolation(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context, String constraint, int severity, String source, int code)
  {
    diagnostics.add
      (new BasicDiagnostic
        (severity,
         source,
         code,
         getString("_UI_GenericConstraint_diagnostic", new Object[] { constraint, getValueLabel(eDataType, value, context) }),
         new Object [] { value }));
  }

  /**
   * @since 2.6
   */
  protected void reportConstraintDelegateException(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context, String constraint, int severity, String source, int code, Throwable throwable)
  {
    diagnostics.add
      (new BasicDiagnostic
        (severity,
         source,
         code,
         getString("_UI_ConstraintDelegateException_diagnostic", new Object[] { constraint, getValueLabel(eDataType, value, context), throwable.getClass().getName() + ": " + throwable.getLocalizedMessage() }),
         new Object [] { value, throwable }));
  }

  /**
   * @since 2.6
   */
  protected void reportConstraintDelegateNotFound(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context, String constraint, int severity, String source, int code, String validationDelegate)
  {
    diagnostics.add
      (new BasicDiagnostic
        (severity,
         source,
         code,
         getString("_UI_ConstraintDelegateNotFound_diagnostic", new Object[] { constraint, getValueLabel(eDataType, value, context), validationDelegate }),
         new Object [] { value }));
  }

  /**
   * @since 2.6
   */
  protected void reportConstraintDelegateViolation(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context, String constraint, int severity, String source, int code)
  {
    diagnostics.add
      (new BasicDiagnostic
        (severity,
         source,
         code,
         getString("_UI_GenericConstraint_diagnostic", new Object[] { constraint, getObjectLabel(eObject, context) }),
         new Object [] { eObject }));
  }

  /**
   * @since 2.6
   */
  protected void reportConstraintDelegateException(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context, String constraint, int severity, String source, int code, Throwable throwable)
  {
    diagnostics.add
      (new BasicDiagnostic
        (severity,
         source,
         code,
         getString("_UI_ConstraintDelegateException_diagnostic", new Object[] { constraint, getObjectLabel(eObject, context), throwable.getClass().getName() + ": " + throwable.getLocalizedMessage() }),
         new Object [] { eObject, throwable }));
  }

  /**
   * @since 2.6
   */
  protected void reportConstraintDelegateNotFound(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context, String constraint, int severity, String source, int code, String validationDelegate)
  {
    diagnostics.add
      (new BasicDiagnostic
        (severity,
         source,
         code,
         getString("_UI_ConstraintDelegateNotFound_diagnostic", new Object[] { constraint, getObjectLabel(eObject, context), validationDelegate }),
         new Object [] { eObject }));
  }

  /**
   * @since 2.6
   */
  protected static void reportInvariantDelegateViolation(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context, EOperation invariant, int severity, String source, int code)
  {
    diagnostics.add
      (new BasicDiagnostic
        (severity,
         source,
         code,
         EcorePlugin.INSTANCE.getString("_UI_GenericInvariant_diagnostic", new Object[] { invariant.getName(), getObjectLabel(eObject, context) }),
         new Object [] { eObject }));
  }

  /**
   * @since 2.6
   */
  protected static void reportInvariantDelegateException(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context, EOperation invariant, int severity, String source, int code, Throwable throwable)
  {
    diagnostics.add
      (new BasicDiagnostic
        (severity,
         source,
         code,
         EcorePlugin.INSTANCE.getString("_UI_InvariantDelegateException_diagnostic", new Object[] { invariant.getName(), getObjectLabel(eObject, context), throwable.getClass().getName() + ": " + throwable.getLocalizedMessage() }),
         new Object [] { eObject, throwable }));
  }

  /**
   * @since 2.6
   */
  protected static void reportInvariantDelegateNotFound(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context, EOperation invariant, int severity, String source, int code, String validationDelegate)
  {
    diagnostics.add
    (new BasicDiagnostic
      (severity,
       source,
       code,
       EcorePlugin.INSTANCE.getString("_UI_InvariantDelegateNotFound_diagnostic", new Object[] { invariant.getName(), getObjectLabel(eObject, context), validationDelegate }),
       new Object [] { eObject }));
  }

  protected static Collection<Object> wrapEnumerationValues(Object [] values)
  {
    return java.util.Arrays.asList(values);
  }
  
  /**
   * @since 2.2
   */
  public boolean validate_UniqueID(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    String id = EcoreUtil.getID(eObject);
    if (id != null)
    {
      Resource resource = eObject.eResource();
      if (resource != null)
      {
        EObject otherEObject = resource.getEObject(id);
        if (eObject != otherEObject && otherEObject != null)
        {
          result = false;
          if (diagnostics != null)
          {
            diagnostics.add
              (createDiagnostic
                (Diagnostic.ERROR,
                 DIAGNOSTIC_SOURCE,
                 EOBJECT__UNIQUE_ID,
                 "_UI_DuplicateID_diagnostic",
                 new Object []
                 {
                   id,
                   getObjectLabel(eObject, context),
                   getObjectLabel(otherEObject, context)
                 },
                 new Object [] { eObject, otherEObject, id },
                 context));
          }
        }
      }
    }
    return result;
  }

  /**
   * @since 2.3
   * @param eObject
   * @param diagnostics
   * @param context
   * @return whether every key is unique.
   */
  public boolean validate_EveryKeyUnique(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    EClass eClass = eObject.eClass();
    for (int i = 0, size = eClass.getFeatureCount(); i < size; ++i)
    {
      EStructuralFeature eStructuralFeature = eClass.getEStructuralFeature(i);
      if (eStructuralFeature instanceof EReference)
      {
        EReference eReference = (EReference)eStructuralFeature;
        if (eReference.isMany() && !eReference.getEKeys().isEmpty())
        {
          result &= validate_KeyUnique(eObject, eReference, diagnostics, context);
          if (!result && diagnostics == null)
          {
            return false;
          }
        }
      }
    }
    return result;
  }
  
  /**
   * @since 2.3
   * @param eObject
   * @param eReference
   * @param diagnostics
   * @param context
   * @return whether every key is unique.
   */
  protected boolean validate_KeyUnique
    (EObject eObject, EReference eReference, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    Map<List<Object>, EObject> keys = new HashMap<List<Object>, EObject>();
    EAttribute [] eAttributes = (EAttribute[])((BasicEList<?>)eReference.getEKeys()).data();
    @SuppressWarnings("unchecked")
    List<EObject> values = (List<EObject>)eObject.eGet(eReference);
    for (EObject value : values)
    {
      ArrayList<Object> key = new ArrayList<Object>(); 
      for (int i = 0, size = eAttributes.length; i < size; ++i)
      {
        EAttribute eAttribute = eAttributes[i];
        if (eAttribute == null)
        {
          break;
        }
        else 
        {
          key.add(value.eGet(eAttribute));
        }
      }
      EObject otherValue = keys.put(key, value);
      if (otherValue != null)
      {
        result = false;
        if (diagnostics == null)
        {
          break;
        }
        else
        {
          String uriFragmentSegment = ((InternalEObject)eObject).eURIFragmentSegment(eReference, value);
          int index = uriFragmentSegment.indexOf('[', 0);
          if (index != -1)
          {
            uriFragmentSegment = uriFragmentSegment.substring(index);
          }
          diagnostics.add
            (createDiagnostic
              (Diagnostic.ERROR,
               DIAGNOSTIC_SOURCE,
               EOBJECT__EVERY_KEY_UNIQUE,
               "_UI_DuplicateKey_diagnostic",
               new Object []
               {
                 getFeatureLabel(eReference, context),
                 uriFragmentSegment,
                 getObjectLabel(value, context),
                 getObjectLabel(otherValue, context)
               },
               new Object [] { eObject, eReference, value, otherValue },
               context));
        }
      }
    }

    return result;
  }

  /**
   * @since 2.3
   * @param eObject
   * @param diagnostics
   * @param context
   * @return whether every map entry is unique.
   */
  public boolean validate_EveryMapEntryUnique(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    EClass eClass = eObject.eClass();
    for (int i = 0, size = eClass.getFeatureCount(); i < size; ++i)
    {
      EStructuralFeature eStructuralFeature = eClass.getEStructuralFeature(i);
      if (eStructuralFeature.getEType().getInstanceClassName() == "java.util.Map$Entry" && eStructuralFeature instanceof EReference)
      {
        EReference eReference = (EReference)eStructuralFeature;
        result &= validate_MapEntryUnique(eObject, eReference, diagnostics, context);
        if (!result && diagnostics == null)
        {
          return false;
        }
      }
    }
    return result;
  }

  /**
   * @since 2.3
   * @param eObject
   * @param eReference
   * @param diagnostics
   * @param context
   * @return whether every map entry is unique.
   */
  protected boolean validate_MapEntryUnique
    (EObject eObject, EReference eReference, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    boolean result = true;
    Object value = eObject.eGet(eReference);
    if (value instanceof EMap<?, ?>)
    {
      EMap<?, ?> eMap = (EMap<?, ?>)value;
      for (int i = 0, size = eMap.size(); i < size; ++i)
      {
        Map.Entry<?, ?> entry = eMap.get(i);
        Object key = entry.getKey();
        int index =  eMap.indexOfKey(key);
        if (index != i)
        {
          result = false;
          if (diagnostics == null)
          {
            break;
          }
          else
          {
            diagnostics.add
              (createDiagnostic
                (Diagnostic.ERROR,
                 DIAGNOSTIC_SOURCE,
                 EOBJECT__EVERY_MAP_ENTRY_UNIQUE,
                 "_UI_DuplicateMapEntry_diagnostic",
                 new Object []
                 {
                   getFeatureLabel(eReference, context),
                   i,
                   index
                 },
                 new Object [] { eObject, eReference, entry, eMap.get(index) },
                 context));
          }
        }
      }
    }

    return result;
  }

  /**
   * Creates a new {@link BasicDiagnostic#BasicDiagnostic(int, String, int, String, Object[]) basic diagnostic}.
   * If the source is {@link #DIAGNOSTIC_SOURCE "org.eclipse.emf.ecore"}, 
   * it calls {@link #getEcoreString(String, Object[])};
   * otherwise it calls {@link #getString(String, Object[])}.
   * @param severity an indicator of the severity of the problem.
   * @param source the unique identifier of the source.
   * @param code the source-specific identity code.
   * @param messageKey the key of the message.
   * @param messageSubstitutions the substitutions for the key; <code>null</code> if there are no substitutions.
   * @param data the data associated with the diagnostic
   * @param context a place to cache information, if it's <code>null</code>, no cache is supported.
   * @return a new diagnostic.
   * @see BasicDiagnostic#BasicDiagnostic(int, String, int, String, Object[])
   * @since 2.4
   */
  protected BasicDiagnostic createDiagnostic
    (int severity, String source, int code, String messageKey, Object[] messageSubstitutions, Object[] data, Map<Object, Object> context)
  {
    String message =
      DIAGNOSTIC_SOURCE.equals(source) ?
        getEcoreString(messageKey, messageSubstitutions) :
        getString(messageKey, messageSubstitutions);
    return new BasicDiagnostic(severity, source, code, message, data);
  }

  /**
   * Returns a translated message with the given substitutions.
   * The {@link #getEcoreResourceLocator() Ecore resource locator} is used.
   * @param key the key for the message.
   * @param substitutions the substitutions for the key; <code>null</code> if there are no substitutions.
   * @return the message.
   * @since 2.4
   */
  protected String getEcoreString(String key, Object [] substitutions)
  {
    return getString(getEcoreResourceLocator(), key, substitutions);
  }

  /**
   * Returns the resource locator for {@link #getEcoreString(String, Object[]) fetching} Ecore-specific messages.
   * @return the resource locator for fetching Ecore-specific messages.
   * @since 2.2
   */
  protected ResourceLocator getEcoreResourceLocator()
  {
    return EcorePlugin.INSTANCE;
  }

  /**
   * @since 2.6
   */
  protected boolean isEcoreString(String key)
  {
    return "_UI_GenericConstraint_diagnostic".equals(key) || "_UI_GenericInvariant_diagnostic".equals(key)
      || "_UI_ConstraintDelegateException_diagnostic".equals(key) || "_UI_InvariantDelegateException_diagnostic".equals(key)
      || "_UI_ConstraintDelegateNotFound_diagnostic".equals(key) || "_UI_InvariantDelegateNotFound_diagnostic".equals(key);
  }

  /**
   * Returns a translated message with the given substitutions.
   * The {@link #getResourceLocator() resource locator} is used.
   * @param key the key for the message.
   * @param substitutions the substitutions for the key; <code>null</code> if there are no substitutions.
   * @return the message.
   * @since 2.4
   */
  protected String getString(String key, Object [] substitutions)
  {
    return getString(isEcoreString(key) ? getEcoreResourceLocator() : getResourceLocator(), key, substitutions);
  }

  /**
   * Returns the resource locator for {@link #getString(String, Object[]) fetching} model-specific messages.
   * This implementation returns the {@link #getEcoreResourceLocator() Ecore resource locator};
   * derived validators <b>must</b> override this to return the resource locator for their model.
   * @return the resource locator for fetching model-specific messages.
   * @since 2.4
   */
  protected ResourceLocator getResourceLocator()
  {
    return getEcoreResourceLocator();
  }

  private String getString(ResourceLocator resourceLocator, String key, Object [] substitutions)
  {
    return substitutions == null ? resourceLocator.getString(key) : resourceLocator.getString(key, substitutions);
  }

  /**
   * Composes a localized message from the collection of choices
   * with each choice value surrounded by the given quote character, except if the choice value is {@code null}.
   * Each choice value will be {@link String#valueOf(Object) converted} to a string, for {@code Class} value, it will use the class {@link Class#getName() name}.
   * <p>
   * Example usage with results for the default locale:
   * </p>
   * <pre>
   *   ({}, true, "'", Integer.MAX_INT)                 &rarr;   none available
   *   ({"a", "b"}, true, "'", Integer.MAX_INT)         &rarr;   'a' and 'b'
   *   ({"a", "b", "c"}, false, "", Integer.MAX_INT)    &rarr;   a, b, or c
   *   ({"a", "b", "c", "d"}, false, "", 3)    ->       &rarr;   a, b, c, &hellip;
   * </pre>
   * @param choices a collection of choice values.
   * @param inclusive whether this an inclusive choice, i.e., one or more possible, e.g., {@code 'and'}, or an exclusive choice, i.e., only one possible, e.g., {@code 'or'}.
   * @param quote the quotation characters used to bracket each choice value; use the empty string for no quotations.
   * @param limit the maximum number to compose before terminating with an ellipsis.
   * @return a localized messages for the collection of choices.
   * @since 2.14
   */
  public String getAvailableChoices(Collection<?> choices, boolean inclusive, String quote, int limit)
  {
    if (choices.isEmpty())
    {
      return "none available";
    }
    else if (choices.size() == 1)
    {
      return quote(choices.iterator().next(), quote);
    }
    else
    {
      Iterator<?> i = choices.iterator();
      String result = quote(i.next(), quote);
      ResourceLocator ecoreResourceLocator = getEcoreResourceLocator();
      if (choices.size() == 2)
      {
        return getString(ecoreResourceLocator, "_UI_SimpleListTrivialOr_composition", new Object[] { result, quote(i.next(), quote) });
      }

      for (int count = 2; i.hasNext(); ++count)
      {
        String next = quote(i.next(), quote);
        if (i.hasNext())
        {
          result = getString(ecoreResourceLocator, "_UI_SimpleListIntermediate_composition", new Object[] { result, next });
        }
        else
        {
          result = getString(ecoreResourceLocator, inclusive ? "_UI_SimpleListTailAnd_composition" : "_UI_SimpleListTailOr_composition", new Object[] { result, next });
        }

        if (count == limit)
        {
          result = getString(ecoreResourceLocator, "_UI_SimpleListTailLimited_composition", new Object[] { result });
          break;
        }
      }
      return result;
    }
  }

  private static String quote(Object value, String quote)
  {
    return value == null ? "null" : quote + (value instanceof Class<?> ? ((Class<?>)value).getName() : value) + quote;
  }
}
