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

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;

import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EGenericType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.ETypeParameter;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.Diagnostician;
import org.eclipse.emf.ecore.util.EObjectValidator;
import org.eclipse.emf.ecore.util.EcoreValidator;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
import org.junit.Before;
import org.junit.Test;


public class EcoreValidationTest
{
  protected static final boolean SYSOUT = false;

  @Before
  public void setUp()
  {
    EcorePackage.eINSTANCE.eClass();
  }

  @Test
  public void testEcoreValidator()
  {
    // Validate that the Ecore package instance itself is okay.
    {
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(EcorePackage.eINSTANCE);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // An annotation with an empty string for the source isn't valid.
    {
      EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
      eAnnotation.setSource("");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAnnotation);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_SOURCE_URI,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAnnotation, EcorePackage.Literals.EANNOTATION__SOURCE },
         diagnostic.getChildren().get(0));
    }

    // An annotation with a source containing a space isn't valid.
    {
      EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
      eAnnotation.setSource("a b");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAnnotation);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_SOURCE_URI,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAnnotation, EcorePackage.Literals.EANNOTATION__SOURCE },
         diagnostic.getChildren().get(0));
    }

    // Two map entries with the same key are not valid.
    //
    {
      EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
      {
        EObject entry = EcoreFactory.eINSTANCE.create(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY);
        entry.eSet(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY__KEY, "a");
        @SuppressWarnings("unchecked")
        Map.Entry<String, String> stringToStringMapEntry = (Map.Entry<String, String>)entry;
        eAnnotation.getDetails().add(stringToStringMapEntry);
      }
      {
        EObject entry = EcoreFactory.eINSTANCE.create(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY);
        entry.eSet(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY__KEY, "a");
        @SuppressWarnings("unchecked")
        Map.Entry<String, String> stringToStringMapEntry = (Map.Entry<String, String>)entry;
        eAnnotation.getDetails().add(stringToStringMapEntry);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAnnotation);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EObjectValidator.EOBJECT__EVERY_MAP_ENTRY_UNIQUE,
         EObjectValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAnnotation, EcorePackage.Literals.EANNOTATION__DETAILS, eAnnotation.getDetails().get(1), eAnnotation.getDetails().get(0) },
         diagnostic.getChildren().get(0));
    }

    // A named element without a name is invalid, unless strict element names are disabled.
    //
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_NAME,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, EcorePackage.Literals.ENAMED_ELEMENT__NAME },
         diagnostic.getChildren().get(0));

      diagnostic = Diagnostician.INSTANCE.validate(eClass, Collections.singletonMap(EcoreValidator.STRICT_NAMED_ELEMENT_NAMES, Boolean.FALSE));
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // An instance type name with type arguments is valid.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      eClass.setInstanceTypeName("_<_>");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // An instance type name that's just an empty string is not valid.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      eClass.setInstanceTypeName("");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_INSTANCE_TYPE_NAME,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, EcorePackage.Literals.ECLASSIFIER__INSTANCE_TYPE_NAME },
         diagnostic.getChildren().get(0));
    }

    // An instance type name with unbalanced "<" is not valid.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      eClass.setInstanceTypeName("_<_");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_INSTANCE_TYPE_NAME,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, EcorePackage.Literals.ECLASSIFIER__INSTANCE_TYPE_NAME },
         diagnostic.getChildren().get(0));
    }

    // An instance type name with unbalanced "[" is not valid.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      eClass.setInstanceTypeName("_[");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_INSTANCE_TYPE_NAME,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, EcorePackage.Literals.ECLASSIFIER__INSTANCE_TYPE_NAME },
         diagnostic.getChildren().get(0));
    }

    // A class that is an interface must also be abstract.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      eClass.setInterface(true);
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.INTERFACE_IS_ABSTRACT,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, EcorePackage.Literals.ECLASS__ABSTRACT },
         diagnostic.getChildren().get(0));
    }

    // A class can't have more than one attribute that is an ID.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("a");
        eAttribute.setID(true);
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
      }
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("b");
        eAttribute.setID(true);
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
      }
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("c");
        eAttribute.setID(true);
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.AT_MOST_ONE_ID,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, eClass.getEAttributes().get(0), eClass.getEAttributes().get(1), eClass.getEAllAttributes().get(2), EcorePackage.Literals.ECLASS__EALL_ATTRIBUTES},
         diagnostic.getChildren().get(0));
    }

    // A class can't have two attributes with the same name.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("a");
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
      }
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("a");
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_FEATURE_NAMES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, eClass.getEAttributes().get(0), eClass.getEAttributes().get(1), EcorePackage.Literals.ECLASS__EALL_STRUCTURAL_FEATURES },
         diagnostic.getChildren().get(0));
    }

    // A class can't have two attributes with the matching names.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("a");
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
      }
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("A_");
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.WARNING,
         EcoreValidator.UNIQUE_FEATURE_NAMES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, eClass.getEAttributes().get(0), eClass.getEAttributes().get(1), EcorePackage.Literals.ECLASS__EALL_STRUCTURAL_FEATURES },
         diagnostic.getChildren().get(0));
    }

    // A class can't have two operations with matching signatures.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
        eOperation.setName("a");
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eParameter.setEType(XMLTypePackage.Literals.HEX_BINARY);
          eOperation.getEParameters().add(eParameter);
        }
        eClass.getEOperations().add(eOperation);
      }
      {
        EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
        eOperation.setName("a");
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eParameter.setEType(XMLTypePackage.Literals.BASE64_BINARY);
          eOperation.getEParameters().add(eParameter);
        }
        eClass.getEOperations().add(eOperation);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_OPERATION_SIGNATURES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, eClass.getEOperations().get(0), eClass.getEOperations().get(1), EcorePackage.Literals.ECLASS__EALL_OPERATIONS },
         diagnostic.getChildren().get(0));
    }

    // A class can't have circular super types.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.getESuperTypes().add(eClass);
      eClass.setName("_");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.NO_CIRCULAR_SUPER_TYPES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, EcorePackage.Literals.ECLASS__EALL_SUPER_TYPES },
         diagnostic.getChildren().get(0));
    }

    // A map entry class must have a key feature and a value feature.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      eClass.setInstanceClassName("java.util.Map$Entry");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(2, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_MAP_ENTRY_CLASS,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, EcorePackage.Literals.ECLASS__EALL_STRUCTURAL_FEATURES },
         diagnostic.getChildren().get(0));
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_MAP_ENTRY_CLASS,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, EcorePackage.Literals.ECLASS__EALL_STRUCTURAL_FEATURES },
         diagnostic.getChildren().get(1));
    }

    // Two enum literals can't have the same name nor matching names.
    {
      EEnum eEnum = EcoreFactory.eINSTANCE.createEEnum();
      eEnum.setName("_");
      {
        EEnumLiteral eEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
        eEnumLiteral.setName("a");
        eEnum.getELiterals().add(eEnumLiteral);
      }
      {
        EEnumLiteral eEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
        eEnumLiteral.setName("a");
        eEnum.getELiterals().add(eEnumLiteral);
      }
      {
        EEnumLiteral eEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
        eEnumLiteral.setName("A");
        eEnum.getELiterals().add(eEnumLiteral);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eEnum);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_ENUMERATOR_NAMES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eEnum, eEnum.getELiterals().get(0), eEnum.getELiterals().get(1), eEnum.getELiterals().get(2), EcorePackage.Literals.EENUM__ELITERALS },
         diagnostic.getChildren().get(0));
    }

    // Two enum literals can't have the same literals.
    {
      EEnum eEnum = EcoreFactory.eINSTANCE.createEEnum();
      eEnum.setName("_");
      {
        EEnumLiteral eEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
        eEnumLiteral.setName("a");
        eEnumLiteral.setLiteral("x");
        eEnum.getELiterals().add(eEnumLiteral);
      }
      {
        EEnumLiteral eEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
        eEnumLiteral.setName("b");
        eEnumLiteral.setLiteral("x");
        eEnum.getELiterals().add(eEnumLiteral);
      }
      {
        EEnumLiteral eEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
        eEnumLiteral.setName("c");
        eEnumLiteral.setLiteral("x");
        eEnum.getELiterals().add(eEnumLiteral);
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eEnum);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_ENUMERATOR_LITERALS,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eEnum, eEnum.getELiterals().get(0), eEnum.getELiterals().get(1), eEnum.getELiterals().get(2), EcorePackage.Literals.EENUM__ELITERALS },
         diagnostic.getChildren().get(0));
    }

    // Two parameters cannot have the same name.
    {
      EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
      eOperation.setName("_");
      {
        EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
        eParameter.setName("a");
        eParameter.setEType(EcorePackage.Literals.ESTRING);
        eOperation.getEParameters().add(eParameter);
      }
      {
        EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
        eParameter.setName("a");
        eParameter.setEType(EcorePackage.Literals.ESTRING);
        eOperation.getEParameters().add(eParameter);
      }
      {
        EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
        eParameter.setName("a");
        eParameter.setEType(EcorePackage.Literals.ESTRING);
        eOperation.getEParameters().add(eParameter);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eOperation);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_PARAMETER_NAMES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eOperation, eOperation.getEParameters().get(0), eOperation.getEParameters().get(1), eOperation.getEParameters().get(2), EcorePackage.Literals.EOPERATION__EPARAMETERS },
         diagnostic.getChildren().get(0));
    }

    // Two type parameters cannot have the same name.
    {
      EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
      eOperation.setName("_");
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("A");
        eOperation.getETypeParameters().add(eTypeParameter);
      }
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("A");
        eOperation.getETypeParameters().add(eTypeParameter);
      }
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("A");
        eOperation.getETypeParameters().add(eTypeParameter);
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eOperation);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_TYPE_PARAMETER_NAMES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eOperation, eOperation.getETypeParameters().get(0), eOperation.getETypeParameters().get(1), eOperation.getETypeParameters().get(2), EcorePackage.Literals.EOPERATION__ETYPE_PARAMETERS },
         diagnostic.getChildren().get(0));
    }

    // Two type parameters cannot have the same name.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("A");
        eClass.getETypeParameters().add(eTypeParameter);
      }
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("A");
        eClass.getETypeParameters().add(eTypeParameter);
      }
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("A");
        eClass.getETypeParameters().add(eTypeParameter);
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_TYPE_PARAMETER_NAMES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, eClass.getETypeParameters().get(0), eClass.getETypeParameters().get(1), eClass.getETypeParameters().get(2), EcorePackage.Literals.ECLASSIFIER__ETYPE_PARAMETERS },
         diagnostic.getChildren().get(0));
    }

    // A void operation must have upper bound 1.
    {
      EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
      eOperation.setName("_");
      eOperation.setUpperBound(2);
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eOperation);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.NO_REPEATING_VOID,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eOperation, EcorePackage.Literals.ETYPED_ELEMENT__UPPER_BOUND },
         diagnostic.getChildren().get(0));
    }

    // A package must have an nsURI.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsPrefix("");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_NS_URI,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, EcorePackage.Literals.EPACKAGE__NS_URI },
         diagnostic.getChildren().get(0));
    }

    // A null string is not a well formed nsURI for a package.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("");
      ePackage.setNsPrefix("");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_NS_URI,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, EcorePackage.Literals.EPACKAGE__NS_URI },
         diagnostic.getChildren().get(0));
    }

    // A string with a space is not a well formed nsURI for a package.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("a b");
      ePackage.setNsPrefix("");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_NS_URI,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, EcorePackage.Literals.EPACKAGE__NS_URI },
         diagnostic.getChildren().get(0));
    }

    // A prefix that starts with "xml" is not a well formed nsPrefix for a package.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("_");
      ePackage.setNsPrefix("xml");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_NS_PREFIX,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, EcorePackage.Literals.EPACKAGE__NS_PREFIX },
         diagnostic.getChildren().get(0));
    }

    // A prefix that isn't an NCName because it contains a ":" is not a well formed nsPrefix for a package.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("_");
      ePackage.setNsPrefix("a:b");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_NS_PREFIX,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, EcorePackage.Literals.EPACKAGE__NS_PREFIX },
         diagnostic.getChildren().get(0));
    }

    // A null prefix that an NCName and is not a well formed nsPrefix for a package.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("_");
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.WELL_FORMED_NS_PREFIX,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, EcorePackage.Literals.EPACKAGE__NS_PREFIX },
         diagnostic.getChildren().get(0));
    }

    // Two subpackages cannot have the same name.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("_");
      ePackage.setNsPrefix("");
      {
        EPackage eSubpackage = EcoreFactory.eINSTANCE.createEPackage();
        eSubpackage.setName("a");
        eSubpackage.setNsURI("a");
        eSubpackage.setNsPrefix("");
        ePackage.getESubpackages().add(eSubpackage);
      }
      {
        EPackage eSubpackage = EcoreFactory.eINSTANCE.createEPackage();
        eSubpackage.setName("a");
        eSubpackage.setNsURI("b");
        eSubpackage.setNsPrefix("");
        ePackage.getESubpackages().add(eSubpackage);
      }
      {
        EPackage eSubpackage = EcoreFactory.eINSTANCE.createEPackage();
        eSubpackage.setName("a");
        eSubpackage.setNsURI("c");
        eSubpackage.setNsPrefix("");
        ePackage.getESubpackages().add(eSubpackage);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_SUBPACKAGE_NAMES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, ePackage.getESubpackages().get(0), ePackage.getESubpackages().get(1), ePackage.getESubpackages().get(2), EcorePackage.Literals.EPACKAGE__ESUBPACKAGES },
         diagnostic.getChildren().get(0));
    }

    // Two classifiers cannot have the same name nor matching names.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("_");
      ePackage.setNsPrefix("");
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("a");
        ePackage.getEClassifiers().add(eClass);
      }
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("a");
        ePackage.getEClassifiers().add(eClass);
      }
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("A");
        ePackage.getEClassifiers().add(eClass);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_CLASSIFIER_NAMES,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, ePackage.getEClassifiers().get(0), ePackage.getEClassifiers().get(1), ePackage.getEClassifiers().get(2), EcorePackage.Literals.EPACKAGE__ECLASSIFIERS },
         diagnostic.getChildren().get(0));
    }

    // Two packages cannot have the same namespace URI.
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("_");
      ePackage.setNsPrefix("");
      {
        EPackage eSubpackage = EcoreFactory.eINSTANCE.createEPackage();
        eSubpackage.setName("a");
        eSubpackage.setNsURI("_");
        eSubpackage.setNsPrefix("");
        ePackage.getESubpackages().add(eSubpackage);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
      assertEquals(2, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_NS_URIS,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage, ePackage.getESubpackages().get(0), EcorePackage.Literals.EPACKAGE__ESUBPACKAGES },
         diagnostic.getChildren().get(0));
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.UNIQUE_NS_URIS,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { ePackage.getESubpackages().get(0), ePackage, EcorePackage.Literals.EPACKAGE__ESUBPACKAGES },
         diagnostic.getChildren().get(1));
    }

    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("_");
      eAttribute.setEType(EcorePackage.Literals.ERESOURCE);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TRANSIENT,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute, EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT },
         diagnostic.getChildren().get(0));
    }

    // There are many ways for opposites to be inconsistent so we'll test them all.
    LOOP:
    for (int i = 0; i < 100; ++i)
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      EClass A;
      EReference a;
      EClass B;
      EReference b;
      ePackage.setName("_");
      ePackage.setNsURI("_");
      ePackage.setNsPrefix("");
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("A");
        ePackage.getEClassifiers().add(eClass);
        {
          EReference eReference = EcoreFactory.eINSTANCE.createEReference();
          eReference.setName("b");
          eClass.getEStructuralFeatures().add(eReference);
          b = eReference;
        }
        A = eClass;
      }
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("B");
        ePackage.getEClassifiers().add(eClass);
        {
          EReference eReference = EcoreFactory.eINSTANCE.createEReference();
          eReference.setName("a");
          eClass.getEStructuralFeatures().add(eReference);
          a = eReference;
        }
        B = eClass;
      }

      switch (i)
      {
        // Valid unidirectional references.
        case 0:
        {
          a.setEType(A);
          b.setEType(B);

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        // Valid bidirectional references.
        case 1:
        {
          a.setEType(A);
          a.setEOpposite(b);
          b.setEType(B);
          b.setEOpposite(a);

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        // The opposite must be a feature of the reference's type.
        case 2:
        {
          a.setEType(B); // <-- Bad type that doesn't have a b feature
          a.setEOpposite(b);
          b.setEType(B);
          b.setEOpposite(a);

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_OPPOSITE_NOT_FROM_TYPE,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object [] { a, b, B, EcorePackage.Literals.EREFERENCE__EOPPOSITE },
             diagnostic.getChildren().get(0));
          break;
        }
        // The opposite of the opposite must be the feature itself.
        case 3:
        {
          a.setEType(A);
          a.setEOpposite(b);
          b.setEType(B);
          //  b.setEOpposite(a); <-- No opposite for the opposite

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_OPPOSITE_NOT_MATCHING,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object [] { a, b, null, EcorePackage.Literals.EREFERENCE__EOPPOSITE },
             diagnostic.getChildren().get(0));
          break;
        }
        // Valid bidirectional container/containment references.
        case 4:
        {
          a.setEType(A);
          a.setEOpposite(b);
          a.setContainment(true);
          b.setEType(B);
          b.setEOpposite(a);

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        // A container reference must have upper bound 1.
        case 5:
        {
          a.setEType(A);
          a.setEOpposite(b);
          a.setContainment(true);
          b.setEType(B);
          b.setEOpposite(a);
          b.setUpperBound(2); // <-- Bad upper bound.

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.SINGLE_CONTAINER,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object [] { b, EcorePackage.Literals.ETYPED_ELEMENT__UPPER_BOUND },
             diagnostic.getChildren().get(0));
          break;
        }
        // Valid bidirectional references that are transient at both ends.
        case 6:
        {
          a.setEType(A);
          a.setEOpposite(b);
          a.setTransient(true);
          b.setEType(B);
          b.setEOpposite(a);
          b.setTransient(true);

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        // Valid bidirectional references with one transient end that doesn't resolve proxy.
        case 7:
        {
          a.setEType(A);
          a.setEOpposite(b);
          a.setTransient(true);
          b.setEType(B);
          b.setEOpposite(a);
          b.setResolveProxies(false);

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        // Valid bidirectional references with on transient end that's a container.
        case 8:
        {
          a.setEType(A);
          a.setEOpposite(b);
          a.setContainment(true);
          b.setEType(B);
          b.setEOpposite(a);
          b.setTransient(true);

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        // The opposite of a transient bidirectional reference must either be transient as, non-proxy resolving, or a container.
        case 9:
        {
          a.setEType(A);
          a.setEOpposite(b);
          a.setTransient(true);
          // a.setContainment(true); <-- Must be a containment for b to be a container and hence be valid.
          b.setEType(B);
          b.setEOpposite(a);
          // b.setTransient(true);  <-- Must be transient.
          // b.setResolveProxies(false); <-- Or must not resolve proxies.

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_OPPOSITE_BAD_TRANSIENT,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object [] { a, b, EcorePackage.Literals.EREFERENCE__EOPPOSITE, EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT },
             diagnostic.getChildren().get(0));
          break;
        }
        // Both sides can't be containment.
        case 10:
        {
          a.setEType(A);
          a.setEOpposite(b);
          a.setContainment(true);
          b.setEType(B);
          b.setEOpposite(a);
          b.setContainment(true);

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(2, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_OPPOSITE_BOTH_CONTAINMENT,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object [] { b, a, EcorePackage.Literals.EREFERENCE__EOPPOSITE, EcorePackage.Literals.EREFERENCE__CONTAINMENT },
             diagnostic.getChildren().get(0));
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_OPPOSITE_BOTH_CONTAINMENT,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object [] { a, b, EcorePackage.Literals.EREFERENCE__EOPPOSITE, EcorePackage.Literals.EREFERENCE__CONTAINMENT },
             diagnostic.getChildren().get(1));
          break;
        }
        default:
        {
          break LOOP;
        }
      }
    }

    // The lower bound of a typed element must be >= 0.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      eAttribute.setEType(EcorePackage.Literals.ESTRING);
      eAttribute.setLowerBound(-1);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.VALID_LOWER_BOUND,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute, EcorePackage.Literals.ETYPED_ELEMENT__LOWER_BOUND },
         diagnostic.getChildren().get(0));
    }

    // The upper bound of a typed elements must be -2, -1, or >= 1 not 3.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      eAttribute.setEType(EcorePackage.Literals.ESTRING);
      eAttribute.setUpperBound(-3);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.VALID_UPPER_BOUND,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute, EcorePackage.Literals.ETYPED_ELEMENT__UPPER_BOUND },
         diagnostic.getChildren().get(0));
    }

    // The upper bound of a typed element cannot be 0.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      eAttribute.setEType(EcorePackage.Literals.ESTRING);
      eAttribute.setUpperBound(0);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.VALID_UPPER_BOUND,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute, EcorePackage.Literals.ETYPED_ELEMENT__UPPER_BOUND  },
         diagnostic.getChildren().get(0));
    }

    // The lower bound of a typed element must be less than or equal to the upper bound, unless the upper bound is -1, or -2.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      eAttribute.setEType(EcorePackage.Literals.ESTRING);
      eAttribute.setLowerBound(2);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_BOUNDS,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute, EcorePackage.Literals.ETYPED_ELEMENT__LOWER_BOUND, EcorePackage.Literals.ETYPED_ELEMENT__UPPER_BOUND   },
         diagnostic.getChildren().get(0));
    }

    // An attribute must have a type that's a data type.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      eAttribute.setEType(EcorePackage.Literals.EOBJECT); // <-- Bad class type.

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(Diagnostic.ERROR, diagnostic.getSeverity());
      assertEquals(2, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EObjectValidator.EOBJECT__EVERY_MULTIPCITY_CONFORMS,
         EObjectValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute, EcorePackage.Literals.EATTRIBUTE__EATTRIBUTE_TYPE },
         diagnostic.getChildren().get(0));
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_CLASS_NOT_PERMITTED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute.getEGenericType(), EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER },
         diagnostic.getChildren().get(1));
    }

    // A generic type used as the type of an attribute must not refer to a class.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      eGenericType.setEClassifier(EcorePackage.Literals.EOBJECT); // <-- Bad class type.
      eAttribute.setEGenericType(eGenericType);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(Diagnostic.ERROR, diagnostic.getSeverity());
      assertEquals(2, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EObjectValidator.EOBJECT__EVERY_MULTIPCITY_CONFORMS,
         EObjectValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute, EcorePackage.Literals.EATTRIBUTE__EATTRIBUTE_TYPE },
         diagnostic.getChildren().get(0));
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_CLASS_NOT_PERMITTED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eGenericType, EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER },
         diagnostic.getChildren().get(1));
    }

    // A reference must have a type that's a class.
    {
      EReference eReference = EcoreFactory.eINSTANCE.createEReference();
      eReference.setName("r");
      eReference.setEType(EcorePackage.Literals.ESTRING);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eReference);
      assertEquals(2, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EObjectValidator.EOBJECT__EVERY_MULTIPCITY_CONFORMS,
         EObjectValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eReference, EcorePackage.Literals.EREFERENCE__EREFERENCE_TYPE },
         diagnostic.getChildren().get(0));
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_DATA_TYPE_NOT_PERMITTED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eReference.getEGenericType(), EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER },
         diagnostic.getChildren().get(1));
    }

    // A generic type used as the type of a reference must not refer to a data type.
    {
      EReference eReference = EcoreFactory.eINSTANCE.createEReference();
      eReference.setName("r");
      EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT);
      eReference.setEGenericType(eGenericType);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eReference);
      assertEquals(2, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EObjectValidator.EOBJECT__EVERY_MULTIPCITY_CONFORMS,
         EObjectValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eReference, EcorePackage.Literals.EREFERENCE__EREFERENCE_TYPE },
         diagnostic.getChildren().get(0));
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_DATA_TYPE_NOT_PERMITTED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eReference.getEGenericType(), EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER },
         diagnostic.getChildren().get(1));
    }


    // A reference with a valid key that's a feature of the reference's type.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");

      EAttribute a;
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("a");
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
        a = eAttribute;
      }

      {
        EReference eReference = EcoreFactory.eINSTANCE.createEReference();
        eReference.setName("r");
        eReference.setEType(eClass);
        eReference.getEKeys().add(a);
        eClass.getEStructuralFeatures().add(eReference);
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // Reference with a key that isn't a feature of the type.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");

      EAttribute a;
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("a");
        eAttribute.setEType(EcorePackage.Literals.ESTRING);
        eClass.getEStructuralFeatures().add(eAttribute);
        a = eAttribute;
      }

      EReference r;
      {
        EReference eReference = EcoreFactory.eINSTANCE.createEReference();
        eReference.setName("r");
        eReference.setEType(EcorePackage.Literals.EOBJECT); // <-- Reference's type doesn't have the key attribute as a feature.
        eReference.getEKeys().add(a);
        eClass.getEStructuralFeatures().add(eReference);
        r = eReference;
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_KEYS,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { r, a, EcorePackage.Literals.EREFERENCE__EKEYS },
         diagnostic.getChildren().get(0));
    }

    // A generic type can act as a wildcard only when used as the type argument of a generic type, not when used as the type of a type element.
    {
      EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
      eOperation.setName("_");
      EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      eOperation.setEGenericType(eGenericType);
      // eGenericType.setETypeParameter(...); // <-- Neither a type parameter
      // eGenericType.setEClassifier(...); // <-- Nor a classifier.

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eOperation);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_WILDCARD_NOT_PERMITTED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eGenericType, EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER, EcorePackage.Literals.EGENERIC_TYPE__ETYPE_PARAMETER },
         diagnostic.getChildren().get(0));
    }

    // A generic type must not reference both a classifier and a type parameter.
    {
      ETypeParameter T;
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("T");
        eClass.getETypeParameters().add(eTypeParameter);
        T = eTypeParameter;
      }
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("U");
        eClass.getETypeParameters().add(eTypeParameter);
        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setETypeParameter(T); // <-- Both a type parameter
          eGenericType.setEClassifier(EcorePackage.Literals.EOBJECT); // <-- And a classifier.
          eTypeParameter.getEBounds().add(eGenericType);
        }
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_NO_TYPE_PARAMETER_AND_CLASSIFIER,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass.getETypeParameters().get(1).getEBounds().get(0), EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER, EcorePackage.Literals.EGENERIC_TYPE__ETYPE_PARAMETER },
         diagnostic.getChildren().get(0));
    }

    // A generic super type must refer to a class.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT); // <-- Bad reference to data type.
        eClass.getEGenericSuperTypes().add(eGenericType);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_CLASS_REQUIRED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass.getEGenericSuperTypes().get(0), EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER },
         diagnostic.getChildren().get(0));
    }

    // No two generic super types may reference the same class.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EOBJECT); // <-- Both refer to EObject.
        eClass.getEGenericSuperTypes().add(eGenericType);
      }
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EOBJECT); // <-- Both refer to EObject.
        eClass.getEGenericSuperTypes().add(eGenericType);
      }
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_SUPER_TYPES_DUPLICATE,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass, eClass.getEGenericSuperTypes().get(0), eClass.getEGenericSuperTypes().get(1), EcorePackage.Literals.ECLASS__EGENERIC_SUPER_TYPES },
         diagnostic.getChildren().get(0));
    }

    // A generic type used as a type argument can be a wildcard.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_CLASS);
        eAttribute.setEGenericType(eGenericType);
        {
          EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.getETypeArguments().add(eGenericTypeArgument);
        }
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // A generic type used as a valid wildcard can have a lower bound.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_CLASS);
        eAttribute.setEGenericType(eGenericType);
        {
          EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
          {
            EGenericType eGenericTypeLowerBound = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeLowerBound.setEClassifier(EcorePackage.Literals.EOBJECT);
            eGenericTypeArgument.setEUpperBound(eGenericTypeLowerBound);
          }
          eGenericType.getETypeArguments().add(eGenericTypeArgument);
        }
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // A generic type used as a valid wildcard can have an upper bound.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_CLASS);
        eAttribute.setEGenericType(eGenericType);
        {
          EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
          {
            EGenericType eGenericTypeUpperBound = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeUpperBound.setEClassifier(EcorePackage.Literals.EOBJECT);
            eGenericTypeArgument.setEUpperBound(eGenericTypeUpperBound);
          }
          eGenericType.getETypeArguments().add(eGenericTypeArgument);
        }
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // A generic type can't have both an upper and lower bound.
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_CLASS);
        eAttribute.setEGenericType(eGenericType);
        {
          EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
          {
            EGenericType eGenericTypeLowerBound = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeLowerBound.setEClassifier(EcorePackage.Literals.EOBJECT);
            eGenericTypeArgument.setELowerBound(eGenericTypeLowerBound); // <-- A lower bound.
          }
          {
            EGenericType eGenericTypeUpperBound = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeUpperBound.setEClassifier(EcorePackage.Literals.EOBJECT);
            eGenericTypeArgument.setEUpperBound(eGenericTypeUpperBound); // <-- And an upper bound.
          }
          eGenericType.getETypeArguments().add(eGenericTypeArgument);
        }
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_BOUNDS_NO_LOWER_AND_UPPER,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute.getEGenericType().getETypeArguments().get(0), EcorePackage.Literals.EGENERIC_TYPE__ELOWER_BOUND, EcorePackage.Literals.EGENERIC_TYPE__EUPPER_BOUND },
         diagnostic.getChildren().get(0));
    }

    // A generic type can't have an upper bound except when used as a type argument.
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EOBJECT);
        {
          EGenericType eGenericTypeUpperBound = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericTypeUpperBound.setEClassifier(EcorePackage.Literals.EOBJECT);
          eGenericType.setEUpperBound(eGenericTypeUpperBound); // <-- Can't have bounds in this context.
        }
        eClass.getEGenericSuperTypes().add(eGenericType);
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_BOUNDS_NOT_ALLOWED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass.getEGenericSuperTypes().get(0), EcorePackage.Literals.EGENERIC_TYPE__EUPPER_BOUND },
         diagnostic.getChildren().get(0));
    }

    // A generic type with bounds can't also refer to a type parameter or classifier
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName("a");
      {
        EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_CLASS);
        eAttribute.setEGenericType(eGenericType);
        {
          EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericTypeArgument.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT); // <-- Can't have classifier with bounds.
          {
            EGenericType eGenericTypeUpperBound = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeUpperBound.setEClassifier(EcorePackage.Literals.EOBJECT);
            eGenericTypeArgument.setEUpperBound(eGenericTypeUpperBound); // <-- Can't have bounds with classifier.
          }
          eGenericType.getETypeArguments().add(eGenericTypeArgument);
        }
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eAttribute);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_BOUNDS_NO_BOUNDS_WITH_TYPE_PARAMETER_OR_CLASSIFIER,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eAttribute.getEGenericType().getETypeArguments().get(0), EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER },
         diagnostic.getChildren().get(0));
    }

    // Generic type with arguments must have a classifier.
    {
      EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      {
        EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericTypeArgument.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT);
        eGenericType.getETypeArguments().add(eGenericTypeArgument); // Can't have arguments because there is no classifier.
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eGenericType);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_ARGUMENTS_NONE_ALLOWED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eGenericType, EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS },
         diagnostic.getChildren().get(0));
    }

    // Generic type with classifier that specifies type parameters must have corresponding arguments,
    // but it's only a warning, analogous to a raw type warning, to have no arguments when there are type parameters.
    {
      EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_CLASS);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eGenericType);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.WARNING,
         EcoreValidator.CONSISTENT_ARGUMENTS_NONE,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eGenericType, EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS },
         diagnostic.getChildren().get(0));
    }

    // Generic type's classifier cannot specify a primitive type except as the generic type of a typed element.
    {
      EClass aClass = EcoreFactory.eINSTANCE.createEClass();
      aClass.setName("AClass");
      ETypeParameter e = EcoreFactory.eINSTANCE.createETypeParameter();
      e.setName("E");
      aClass.getETypeParameters().add(e);

      EClass bClass = EcoreFactory.eINSTANCE.createEClass();
      bClass.setName("BClass");
      EGenericType superType = EcoreFactory.eINSTANCE.createEGenericType();
      superType.setEClassifier(aClass);
      EGenericType typeArgument = EcoreFactory.eINSTANCE.createEGenericType();
      typeArgument.setEClassifier(EcorePackage.Literals.EINT);
      superType.getETypeArguments().add(typeArgument);
      bClass.getEGenericSuperTypes().add(superType);

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(bClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_PRIMITIVE_TYPE_NOT_PERMITTED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { typeArgument, EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER },
         diagnostic.getChildren().get(0));
    }

    // A generic type with classifier that specifies type parameters and that has arguments must have a matching number of them.
    {
      EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      eGenericType.setEClassifier(EcorePackage.Literals.EJAVA_CLASS);
      {
        EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericTypeArgument.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT);
        eGenericType.getETypeArguments().add(eGenericTypeArgument);
      }
      {
        EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericTypeArgument.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT);
        eGenericType.getETypeArguments().add(eGenericTypeArgument); // <-- A second argument is not allowed.
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eGenericType);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_ARGUMENTS_INCORRECT_NUMBER,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eGenericType, EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS },
         diagnostic.getChildren().get(0));
    }

    // Generic type must not reference an out of scope type parameter
    {
      ETypeParameter T;
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
        eOperation.setName("f");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eOperation.getETypeParameters().add(eTypeParameter);
          T = eTypeParameter;
        }
        eClass.getEOperations().add(eOperation);
      }
      {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName("a");
        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setETypeParameter(T);
          eAttribute.setEGenericType(eGenericType);
        }
        eClass.getEStructuralFeatures().add(eAttribute);
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_TYPE_PARAMETER_NOT_IN_SCOPE,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass.getEStructuralFeatures().get(0).getEGenericType(), EcorePackage.Literals.EGENERIC_TYPE__ETYPE_PARAMETER },
         diagnostic.getChildren().get(0));
    }

    // Generic type must not make a forward reference to a type parameter
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("T");
        eClass.getETypeParameters().add(eTypeParameter);
        {
          EGenericType eBound = EcoreFactory.eINSTANCE.createEGenericType();
          eBound.setETypeParameter(eTypeParameter);
          eTypeParameter.getEBounds().add(eBound);
        }
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_TYPE_PARAMETER_NOT_IN_SCOPE,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { eClass.getETypeParameters().get(0).getEBounds().get(0), EcorePackage.Literals.EGENERIC_TYPE__ETYPE_PARAMETER },
         diagnostic.getChildren().get(0));
    }

    // Type parameter can be used as the type argument of the
    // bound
    {
      EClass eClass = EcoreFactory.eINSTANCE.createEClass();
      eClass.setName("_");
      {
        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
        eTypeParameter.setName("T");
        eClass.getETypeParameters().add(eTypeParameter);
        {
          EGenericType eBound = EcoreFactory.eINSTANCE.createEGenericType();
          eBound.setEClassifier(eClass);
          eTypeParameter.getEBounds().add(eBound);

          EGenericType typeArgument = EcoreFactory.eINSTANCE.createEGenericType();
          typeArgument.setETypeParameter(eTypeParameter);
          eBound.getETypeArguments().add(typeArgument);
        }
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(eClass);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // A generic super type can validly use type arguments.
    {
      EClass A;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("A");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        A = eClass;
      }
      EClass B;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("B");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }

        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(A);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setETypeParameter(eClass.getETypeParameters().get(0));
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }

        B = eClass;
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(B);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // A generic super type must not have wildcard type arguments.
    {
      EClass A;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("A");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        A = eClass;
      }
      EClass B;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("B");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }

        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(A);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }

        B = eClass;
      }

      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(B);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_TYPE_WILDCARD_NOT_PERMITTED,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { B.getEGenericSuperTypes().get(0).getETypeArguments().get(0), EcorePackage.Literals.EGENERIC_TYPE__ECLASSIFIER, EcorePackage.Literals.EGENERIC_TYPE__ETYPE_PARAMETER },
         diagnostic.getChildren().get(0));
    }

    // Generic super types can pass template parameters in complex ways with redundant diamond inheritance.
    {
      EClass A;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("A");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        A = eClass;
      }
      EClass B;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("B");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }

        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(A);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setETypeParameter(eClass.getETypeParameters().get(0));
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }

        B = eClass;
      }

      EClass C;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("C");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }

        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(A);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setETypeParameter(eClass.getETypeParameters().get(0));
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }
        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(B);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setETypeParameter(eClass.getETypeParameters().get(0));
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }

        C = eClass;
      }

      assertEquals(2, C.getEAllGenericSuperTypes().size());
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(C);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // Generic super types can pass instantiate template parameters in complex ways with redundant diamond inheritance.
    {
      EClass A;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("A");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        A = eClass;
      }
      EClass B;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("B");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }

        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(A);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setETypeParameter(eClass.getETypeParameters().get(0));
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }

        B = eClass;
      }

      EClass C;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("C");

        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(A);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setEClassifier(EcorePackage.Literals.ESTRING); // <-- Use EString for A here.
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }
        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(B);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setEClassifier(EcorePackage.Literals.ESTRING); // <-- Both use EString for B and hence indirectly for A.
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }

        C = eClass;
      }

      assertEquals(2, C.getEAllGenericSuperTypes().size());
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(C);
      assertEquals(Diagnostic.OK, diagnostic.getSeverity());
    }

    // Generic super types can pass instantiate template parameters in complex ways with redundant diamond inheritance.
    {
      EClass A;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("A");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        A = eClass;
      }
      EClass B;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("B");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
        }

        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(A);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setETypeParameter(eClass.getETypeParameters().get(0));
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }

        B = eClass;
      }

      EClass C;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("C");

        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(A);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setEClassifier(EcorePackage.Literals.ESTRING); // <-- Use EString for A here.
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }
        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(B);
          eClass.getEGenericSuperTypes().add(eGenericType);
          {
            EGenericType eGenericTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericTypeArgument.setEClassifier(EcorePackage.Literals.EINTEGER_OBJECT); // <-- Both use EInteger for B and hence indirectly for A.
            eGenericType.getETypeArguments().add(eGenericTypeArgument);
          }
        }

        C = eClass;
      }

      assertEquals(3, C.getEAllGenericSuperTypes().size());
      Diagnostic diagnostic = Diagnostician.INSTANCE.validate(C);
      assertEquals(1, diagnostic.getChildren().size());
      assertDiagnostic
        (Diagnostic.ERROR,
         EcoreValidator.CONSISTENT_SUPER_TYPES_CONFLICT,
         EcoreValidator.DIAGNOSTIC_SOURCE,
         new Object [] { C, C.getEAllGenericSuperTypes().get(1), C.getEAllGenericSuperTypes().get(0), EcorePackage.Literals.ECLASS__EALL_GENERIC_SUPER_TYPES },
         diagnostic.getChildren().get(0));
    }

    LOOP:
    for (int i = 0; i < 100; ++i)
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("_");
      ePackage.setNsPrefix("");

      // interface X {}
      //
      EClass X;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("X");

        ePackage.getEClassifiers().add(eClass);
        X = eClass;
      }

      // interface Y extends X {}
      //
      EClass Y;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("Y");
        eClass.getESuperTypes().add(X);

        ePackage.getEClassifiers().add(eClass);
        Y = eClass;
      }

      // interface Container<E> {}
      //
      EClass Container;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("Container");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("E");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        ePackage.getEClassifiers().add(eClass);
        Container = eClass;
      }

      // interface A<T extends Container<?>> {}
      //
      EClass A;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("A");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");
          eClass.getETypeParameters().add(eTypeParameter);
          {
            EGenericType eBound = EcoreFactory.eINSTANCE.createEGenericType();
            eBound.setEClassifier(Container);
            {
              EGenericType eTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
              if (i == 2 || i == 3)
              {
                eTypeArgument.setEClassifier(X);  // <-- Container<?> or Container<X>
              }
              eBound.getETypeArguments().add(eTypeArgument);
            }
            eTypeParameter.getEBounds().add(eBound);
          }
        }
        ePackage.getEClassifiers().add(eClass);
        A = eClass;
      }

      // interface B<T extends Container<EString>> extends A<T> {}
      //
      EClass B;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("B");
        ETypeParameter T;
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("T");

          // <-- If we don't set a bound, that satisfies A's bound, which requires a Container,
          // then it won't be valid to pass T as the argument to A<T>.
          //
          if (i != 1)
          {
            EGenericType eBound = EcoreFactory.eINSTANCE.createEGenericType();
            eBound.setEClassifier(Container);
            {
              EGenericType eTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
              if (i == 2)
              {
                eTypeArgument.setEClassifier(X);   // <-- Container<X> is fine
              }
              else if (i == 3)
              {
                eTypeArgument.setEClassifier(Y); // <-- Container<Y> is an error
              }
              else
              {
                eTypeArgument.setEClassifier(EcorePackage.Literals.ESTRING);  // <-- Container<EString> is fine.
              }
              eBound.getETypeArguments().add(eTypeArgument);
            }
            eTypeParameter.getEBounds().add(eBound);
          }
          eClass.getETypeParameters().add(eTypeParameter);
          T = eTypeParameter;
        }
        {
          EGenericType eGenericSuperType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericSuperType.setEClassifier(A);
          {
            EGenericType eTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eTypeArgument.setETypeParameter(T);
            eGenericSuperType.getETypeArguments().add(eTypeArgument);
          }
          eClass.getEGenericSuperTypes().add(eGenericSuperType);
        }
        ePackage.getEClassifiers().add(eClass);
        B = eClass;
      }

      switch (i)
      {
        case 0:
        {
          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        case 1:
        {
          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_ARGUMENTS_INVALID_SUBSTITUTION,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object []
             {
               B.getEGenericSuperTypes().get(0),
               B.getEGenericSuperTypes().get(0).getETypeArguments().get(0),
               A.getETypeParameters().get(0),
               EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS
             },
             diagnostic.getChildren().get(0));
          break;
        }
        case 2:
        {
          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        case 3:
        {
          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_ARGUMENTS_INVALID_SUBSTITUTION,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object []
             {
               B.getEGenericSuperTypes().get(0),
               B.getEGenericSuperTypes().get(0).getETypeArguments().get(0),
               A.getETypeParameters().get(0),
               EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS
             },
             diagnostic.getChildren().get(0));
          break;
        }
        default:
        {
          break LOOP;
        }
      }
    }
  }

  interface X
  {
    //
  }

  interface Y extends X
  {
    //
  }

  interface Container<E>
  {
    //
  }

  interface DerivedContainer<E> extends Container<E>
  {
    //
  }

  interface Holder<E, F extends E, H extends Container<E>>
  {
    <E1, F1 extends E1, G1 extends Container<E1>> void foo();
  }

  @Test
  public void testMatching()
  {
    LOOP:
    for (int i = 0; i < 100; ++i)
    {
      EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
      ePackage.setName("_");
      ePackage.setNsURI("_");
      ePackage.setNsPrefix("");

      // interface X {}
      //
      EClass eClassX;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("X");

        ePackage.getEClassifiers().add(eClass);
        eClassX = eClass;
      }

      // interface Y extends X {}
      //
      // EClass eClassY;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("Y");
        eClass.getESuperTypes().add(eClassX);

        ePackage.getEClassifiers().add(eClass);
        // eClassY = eClass;
      }

      // interface Container<E> {}
      //
      EClass eClassContainer;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("Container");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("E");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        ePackage.getEClassifiers().add(eClass);
        eClassContainer = eClass;
      }

      // interface DerivedContainer<E>  extends Container<E> {}
      //
      // EClass eClassDerivedContainer;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("DerivedContainer");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("E");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        {
          EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(eClassContainer);
          {
            EGenericType eTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
            eTypeArgument.setETypeParameter(eClass.getETypeParameters().get(0));
            eGenericType.getETypeArguments().add(eTypeArgument);
          }
          eClass.getEGenericSuperTypes().add(eGenericType);
        }
        ePackage.getEClassifiers().add(eClass);
        // eClassDerivedContainer = eClass;
      }

      // interface Holder<E, F extends E, G extends Container<E>>
      // {
      //    <E1, F1 extends E1, G1 extends Container<E1>> void foo();
      // }
      //
      EClass eClassHolder;
      EOperation eOperationFoo;
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName("Holder");
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("E");
          eClass.getETypeParameters().add(eTypeParameter);
        }
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("F");
          {
            EGenericType eBound = EcoreFactory.eINSTANCE.createEGenericType();
            eBound.setETypeParameter(eClass.getETypeParameters().get(0));
            eTypeParameter.getEBounds().add(eBound);
          }
          eClass.getETypeParameters().add(eTypeParameter);
        }
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName("G");
          {
            EGenericType eBound = EcoreFactory.eINSTANCE.createEGenericType();
            eBound.setEClassifier(eClassContainer);
            if (i <= 5)   //  Container<E> or Container<? extends E> or <Container<? super E>>
            {
              EGenericType eTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeArgument.setETypeParameter(eClass.getETypeParameters().get(0));
              eBound.getETypeArguments().add(eTypeArgument);
            }
            else
            {
              EGenericType eTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
              if (i < 8)
              {
                EGenericType eUpperBound = EcoreFactory.eINSTANCE.createEGenericType();
                eUpperBound.setETypeParameter(eClass.getETypeParameters().get(0));
                eTypeArgument.setEUpperBound(eUpperBound);
              }
              else
              {
                EGenericType eLowerBound = EcoreFactory.eINSTANCE.createEGenericType();
                eLowerBound.setETypeParameter(eClass.getETypeParameters().get(0));
                eTypeArgument.setELowerBound(eLowerBound);
              }
              eBound.getETypeArguments().add(eTypeArgument);
            }
            eTypeParameter.getEBounds().add(eBound);
          }
          eClass.getETypeParameters().add(eTypeParameter);
        }
        ePackage.getEClassifiers().add(eClass);
        eClassHolder = eClass;

        {
          EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
          eOperation.setName("foo");
          {
            ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
            eTypeParameter.setName("E1");
            eOperation.getETypeParameters().add(eTypeParameter);
          }
          {
            ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
            eTypeParameter.setName("F1");
            {
              EGenericType eBound = EcoreFactory.eINSTANCE.createEGenericType();
              eBound.setETypeParameter(eOperation.getETypeParameters().get(0));
              eTypeParameter.getEBounds().add(eBound);
            }
            eOperation.getETypeParameters().add(eTypeParameter);
          }
          {
            ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
            eTypeParameter.setName("G1");
            {
              EGenericType eBound = EcoreFactory.eINSTANCE.createEGenericType();
              eBound.setEClassifier(eClassContainer);
              {
                EGenericType eTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
                eTypeArgument.setETypeParameter(eOperation.getETypeParameters().get(0));
                eBound.getETypeArguments().add(eTypeArgument);
              }
              eTypeParameter.getEBounds().add(eBound);
            }
            eOperation.getETypeParameters().add(eTypeParameter);
          }
          eClass.getEOperations().add(eOperation);
          eOperationFoo = eOperation;
        }
      }

      switch (i)
      {
        case 0:
        {
          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        case 1:
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eOperationFoo.getEParameters().add(eParameter);
          {
            EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassHolder);
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(0));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(1));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(2));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            eParameter.setEGenericType(eGenericType);
          }

          class HolderCase1<E, F extends E, H extends Container<E>>
          {
            @SuppressWarnings("unused")
            <E1, F1 extends E1, G1 extends Container<E1>> void foo(HolderCase1<E1, F1, G1> x)
            {
              this.foo(x);
            }
          }

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        case 2:
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eOperationFoo.getEParameters().add(eParameter);
          {
            EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassHolder);
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            eParameter.setEGenericType(eGenericType);
          }

          class HolderCase2<E, F extends E, H extends Container<E>>
          {
            @SuppressWarnings("unused")
            <E1, F1 extends E1, G1 extends Container<E1>> void foo(HolderCase2<?, ?, ?> x)
            {
              this.foo(x);
            }
          }

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        case 3:
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eOperationFoo.getEParameters().add(eParameter);
          {
            EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassHolder);
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              {
                EGenericType eUpperBound = EcoreFactory.eINSTANCE.createEGenericType();
                eUpperBound.setETypeParameter(eOperationFoo.getETypeParameters().get(1));  // <-- ? extends E1
                eTypeParameter.setEUpperBound(eUpperBound);
              }
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            eParameter.setEGenericType(eGenericType);
          }

          @SuppressWarnings("unused")
          class HolderCase3<E, F extends E, H extends Container<E>>
          {
            <E1, F1 extends E1, G1 extends Container<E1>> void foo(/* --> HolderCase3<?, ? extends E1, ?> x <-- */)
            {
              this.foo(/*x*/);
            }
          }

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_ARGUMENTS_INVALID_SUBSTITUTION,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object []
             {
               eOperationFoo.getEParameters().get(0).getEGenericType(),
               eOperationFoo.getEParameters().get(0).getEGenericType().getETypeArguments().get(1),
               eClassHolder.getETypeParameters().get(1),
               EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS
             },
             diagnostic.getChildren().get(0));
          break;
        }
        case 4:
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eOperationFoo.getEParameters().add(eParameter);
          {
            EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassHolder);
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(0));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(1));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setEClassifier(eClassContainer);  // <-- Container
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            eParameter.setEGenericType(eGenericType);
          }

          @SuppressWarnings("unused")
          class HolderCase4<E, F extends E, H extends Container<E>>
          {
            <E1, F1 extends E1, G1 extends Container<E1>> void foo(/* --> HolderCase4<E1, F1, Container> x <-- */)
            {
              this.foo(/*x*/);
            }
          }

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(2, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_ARGUMENTS_INVALID_SUBSTITUTION,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object []
             {
               eOperationFoo.getEParameters().get(0).getEGenericType(),
               eOperationFoo.getEParameters().get(0).getEGenericType().getETypeArguments().get(2),
               eClassHolder.getETypeParameters().get(2),
               EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS
             },
             diagnostic.getChildren().get(0));
          assertDiagnostic
            (Diagnostic.WARNING,
             EcoreValidator.CONSISTENT_ARGUMENTS_NONE,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object []
             {
               eOperationFoo.getEParameters().get(0).getEGenericType().getETypeArguments().get(2), EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS
             },
             diagnostic.getChildren().get(1));
          break;
        }
        case 5:
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eOperationFoo.getEParameters().add(eParameter);
          {
            EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassHolder);
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(0));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(1));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setEClassifier(eClassContainer);  // <-- Container
              {
                EGenericType eTypeParameterU1 = EcoreFactory.eINSTANCE.createEGenericType();
                eTypeParameterU1.setETypeParameter(eOperationFoo.getETypeParameters().get(1));  // <-- F1
                eTypeParameter.getETypeArguments().add(eTypeParameterU1);
              }
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            eParameter.setEGenericType(eGenericType);
          }

          @SuppressWarnings("unused")
          class HolderCase5<E, F extends E, H extends Container<E>>
          {
            <E1, F1 extends E1, G1 extends Container<E1>> void foo(/* --> HolderCase5<E1, F1, Container<F1>> x <-- */)
            {
              this.foo(/*x*/);
            }
          }

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_ARGUMENTS_INVALID_SUBSTITUTION,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object []
             {
               eOperationFoo.getEParameters().get(0).getEGenericType(),
               eOperationFoo.getEParameters().get(0).getEGenericType().getETypeArguments().get(2),
               eClassHolder.getETypeParameters().get(2),
               EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS
             },
             diagnostic.getChildren().get(0));
          break;
        }
        case 6:
        {
          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          break;
        }
        case 7:
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eOperationFoo.getEParameters().add(eParameter);
          {
            EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassHolder);
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(0));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(1));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setEClassifier(eClassContainer);  // <-- Container
              {
                EGenericType eTypeParameterU1 = EcoreFactory.eINSTANCE.createEGenericType();
                eTypeParameterU1.setETypeParameter(eOperationFoo.getETypeParameters().get(1));  // <-- F1
                eTypeParameter.getETypeArguments().add(eTypeParameterU1);
              }
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            eParameter.setEGenericType(eGenericType);
          }

          class HolderCase7<E, F extends E, H extends Container<? extends E>>
          {
            @SuppressWarnings("unused")
            <E1, F1 extends E1, G1 extends Container<E1>> void foo(HolderCase7<E1, F1, Container<F1>> x)
            {
              this.foo(x);
            }
          }

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(Diagnostic.OK, diagnostic.getSeverity());
          /*
          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_ARGUMENTS_INVALID_SUBSTITUTION,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object []
             {
               eOperationFoo.getEParameters().get(0).getEGenericType(),
               eOperationFoo.getEParameters().get(0).getEGenericType().getETypeArguments().get(2),
               eClassHolder.getETypeParameters().get(2)
             },
             diagnostic.getChildren().get(0));
             */
          break;
        }
        case 8:
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eParameter.setName("x");
          eOperationFoo.getEParameters().add(eParameter);
          {
            EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassHolder);
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(0));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setETypeParameter(eOperationFoo.getETypeParameters().get(1));
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            {
              EGenericType eTypeParameter = EcoreFactory.eINSTANCE.createEGenericType();
              eTypeParameter.setEClassifier(eClassContainer);  // <-- Container
              {
                EGenericType eTypeParameterU1 = EcoreFactory.eINSTANCE.createEGenericType();
                eTypeParameterU1.setETypeParameter(eOperationFoo.getETypeParameters().get(1));  // <-- F1
                eTypeParameter.getETypeArguments().add(eTypeParameterU1);
              }
              eGenericType.getETypeArguments().add(eTypeParameter);
            }
            eParameter.setEGenericType(eGenericType);
          }

          @SuppressWarnings("unused")
          class HolderCase8<E, F extends E, H extends Container<? super E>>
          {
            <E1, F1 extends E1, G1 extends Container<E1>> void foo(/* --> HolderCase8<E1, F1, Container<F1>> x <--*/)
            {
              this.foo(/*x*/);
            }
          }

          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          assertEquals(1, diagnostic.getChildren().size());
          assertDiagnostic
            (Diagnostic.ERROR,
             EcoreValidator.CONSISTENT_ARGUMENTS_INVALID_SUBSTITUTION,
             EcoreValidator.DIAGNOSTIC_SOURCE,
             new Object []
             {
               eOperationFoo.getEParameters().get(0).getEGenericType(),
               eOperationFoo.getEParameters().get(0).getEGenericType().getETypeArguments().get(2),
               eClassHolder.getETypeParameters().get(2),
               EcorePackage.Literals.EGENERIC_TYPE__ETYPE_ARGUMENTS
             },
             diagnostic.getChildren().get(0));
          break;
        }
        default:
        {
          break LOOP;
        }
      }
    }
  }

  public void validateAllRegisteredModels()
  {
    // Validate all registered models.
    //
    if (!"false".equals(System.getProperty(EcoreValidationTest.class.getName() + ".validateAllRegisteredModels", "false")))
    {
      for (String nsURI : new HashSet<String>(EPackage.Registry.INSTANCE.keySet()))
      {
        EPackage ePackage = null;
        try
        {
          ePackage = EPackage.Registry.INSTANCE.getEPackage(nsURI);
        }
        catch (Throwable exception)
        {
          // Ignore
        }
        if (ePackage != null)
        {
          Diagnostic diagnostic = Diagnostician.INSTANCE.validate(ePackage);
          System.err.println("Validating " + nsURI);
          for (Diagnostic child : diagnostic.getChildren())
          {
            switch (child.getSeverity())
            {
              case Diagnostic.ERROR:
              {
                System.err.print("error  : ");
                break;
              }
              case Diagnostic.WARNING:
              {
                System.err.print("warning: ");
                break;
              }
              default:
              {
                System.err.print("other  : ");
                break;
              }
            }
            System.err.println(child.getMessage());
          }
        }
      }
    }
  }

  public void assertDiagnostic(int severity, int code, String source, Object [] data, Diagnostic diagnostic)
  {
    if (SYSOUT) System.out.println(diagnostic.getMessage());
    assertEquals(severity, diagnostic.getSeverity());
    assertEquals(code, diagnostic.getCode());
    assertEquals(source, diagnostic.getSource());
    assertEquals(Arrays.asList(data), diagnostic.getData());
  }
}
