/**
 * Copyright (c) 2002-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.importer.java.builder;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.osgi.util.ManifestElement;

import org.eclipse.emf.codegen.ecore.CodeGenEcorePlugin;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
import org.eclipse.emf.codegen.ecore.genmodel.util.GenModelUtil;
import org.eclipse.emf.codegen.merge.java.JMerger;
import org.eclipse.emf.codegen.merge.java.facade.FacadeHelper;
import org.eclipse.emf.codegen.merge.java.facade.JAbstractType;
import org.eclipse.emf.codegen.merge.java.facade.JAnnotationType;
import org.eclipse.emf.codegen.merge.java.facade.JCompilationUnit;
import org.eclipse.emf.codegen.merge.java.facade.JEnum;
import org.eclipse.emf.codegen.merge.java.facade.JEnumConstant;
import org.eclipse.emf.codegen.merge.java.facade.JField;
import org.eclipse.emf.codegen.merge.java.facade.JImport;
import org.eclipse.emf.codegen.merge.java.facade.JMember;
import org.eclipse.emf.codegen.merge.java.facade.JMethod;
import org.eclipse.emf.codegen.merge.java.facade.JNode;
import org.eclipse.emf.codegen.merge.java.facade.JPackage;
import org.eclipse.emf.codegen.merge.java.facade.JType;
import org.eclipse.emf.codegen.util.CodeGenUtil;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EGenericType;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.ENamedElement;
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.EStructuralFeature;
import org.eclipse.emf.ecore.ETypeParameter;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.plugin.EcorePlugin;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreValidator;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.xml.namespace.XMLNamespacePackage;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
import org.eclipse.emf.importer.ModelImporter;
import org.eclipse.emf.importer.java.JavaImporterPlugin;


public class JavaEcoreBuilder
{
  /**
   * The factory used to create JNodes.
   */
  protected static FacadeHelper facadeHelper = CodeGenUtil.instantiateFacadeHelper(JMerger.DEFAULT_FACADE_HELPER_CLASS);

  /**
   * The file being generated.
   */
  protected IFile genModelFile;

  /**
   * The GenModel being generated.
   */
  protected GenModel genModel;

  /**
   * The map from a package name to the corresponding package.
   * These are populated from the GenModels of required projects.
   */
  protected Map<String, EPackage> externalPackageNameToEPackageMap = new LinkedHashMap<String, EPackage>();

  /**
   * The map from a package name to the corresponding package.
   * These are populated on demand during traversal as modeled class and enums are discovered.
   */
  protected Map<String, EPackage> packageNameToEPackageMap = new LinkedHashMap<String, EPackage>();

  /**
   * The map from package to the map of ordering constants.
   * These are populated as the package interface is traversed.
   */
  protected Map<EPackage, Map<Object, Integer>> ePackageToOrderingMap = new LinkedHashMap<EPackage, Map<Object, Integer>>();

  /**
   * The map from package to the it's prefix.
   * These are populated as the package interfaces are traversed.
   */
  protected Map<EPackage, String> ePackageToPrefixMap = new LinkedHashMap<EPackage, String>();

  /**
   * The map from a model element to the corresponding JNode.
   * These are populated during traversal as each model element is created.
   */
  protected Map<EModelElement, JNode> eModelElementToJNodeMap = new LinkedHashMap<EModelElement, JNode>();

  /**
   * The features handled during the traversal.
   * These are populated during traversal as each feature is created.
   * They must be patched after traversal is completed to ensure they have resolved to the right type of classifier.
   */
  protected Set<EStructuralFeature> eStructuralFeatures = new LinkedHashSet<EStructuralFeature>();

  /**
   * The attributes that must be treated as attributes because the are explicitly declared to be of that kind.
   */
  protected Set<EAttribute> eAttributes = new LinkedHashSet<EAttribute>();

  /**
   * A map from a generic type to the possibly null generic type that represents its proper Java type.
   */
  protected Map<EGenericType, EGenericType> ecoreEGenericTypeToJavaEGenericTypeMap = new LinkedHashMap<EGenericType, EGenericType>();

  /**
   * The set of classifiers that were demand created.
   */
  protected Set<EClassifier> demandCreatedEClassifiers = new LinkedHashSet<EClassifier>();

  /**
   * The map from a reference to the name of its opposite.
   * These are populated during traversal as each reference with an opposite is created.
   * They must be patched after traversal and patching of typed elements is completed.
   * The opposite found as a feature of the type.
   */
  protected Map<EReference, String> eReferenceToOppositeNameMap = new LinkedHashMap<EReference, String>();

  /**
   * The set of references that have opposites and have been explicitly marked as being transient.
   */
  protected Set<EReference> transientEReferenceWithOpposite = new HashSet<EReference>();

  /**
   * The map from a reference to the names of its keys.
   * These are populated during traversal as each reference with keys is created.
   * They must be patched after traversal and patching of typed elements is completed.
   * The key is found as an attribute of the type.
   */
  protected Map<EReference, List<String>> eReferenceToKeyNamesMap = new LinkedHashMap<EReference, List<String>>();

  /**
   * All the external GenModels from all required projects.
   */
  protected Collection<GenModel> externalGenModels = new UniqueEList<GenModel>();

  /**
   * All the used GenPackages.
   */
  protected Collection<GenPackage> usedGenPackages = new ArrayList<GenPackage>();

  /**
   * A collection of the problems encountered during the processing.
   */
  protected BasicDiagnostic basicDiagnostic;

  /**
   * Set to true when a compilation unit is processed and hence indicates if anything to process has been found.
   */
  protected boolean foundJava;

  /**
   * The old version to against which to reconcile.
   */
  protected GenModel oldGenModelVersion;

  /**
   * The utility used to convert a generate type to a fully qualified Java instance type.
   */
  protected EcoreUtil.EGenericTypeConverter eGenericTypeConverter =
     new EcoreUtil.EGenericTypeConverter()
     {
       @Override
       protected String getInstanceTypeName(EClassifier eClassifier)
       {
         String result = super.getInstanceTypeName(eClassifier);
         if (result == null)
         {
           EPackage ePackage = eClassifier.getEPackage();
           for (Map.Entry<String, EPackage> entry : packageNameToEPackageMap.entrySet())
           {
             if (entry.getValue() == ePackage)
             {
               return entry.getKey() + '.' + eClassifier.getName();
             }
           }
           for (Map.Entry<String, EPackage> entry : externalPackageNameToEPackageMap.entrySet())
           {
             if (entry.getValue() == ePackage)
             {
               return entry.getKey() + '.' + eClassifier.getName();
             }
           }
         }
         return result;
       }
     };

  protected static final String MAP_ENTRY_CLASS_CONTAINER_ANNOTATION_SOURCE = EcorePackage.eNS_URI + "#MapEntryClassContainer";
  
  /**
   * Creates a builder for the given file.
   */
  public JavaEcoreBuilder(IFile genModelFile)
  {
    this.genModelFile = genModelFile;

    basicDiagnostic = new BasicDiagnostic(
      JavaImporterPlugin.getPlugin().getBundle().getSymbolicName(),
      0,
      CodeGenEcorePlugin.INSTANCE.getString("_UI_ErrorsWereDetectedJava_message"),
      null);
  }

  public JavaEcoreBuilder(IFile genModelFile, GenModel oldGenModelVersion)
  {
    this(genModelFile);
    this.oldGenModelVersion = oldGenModelVersion;
  }

  public JavaEcoreBuilder(IFile genModelFile, GenModel oldGenModelVersion, GenModel genModel)
  {
    this(genModelFile, oldGenModelVersion);
    this.genModel = genModel;
  }

  protected ResourceSet createResourceSet()
  {
    ResourceSet result = new ResourceSetImpl();
    result.getURIConverter().getURIMap().putAll(EcorePlugin.computePlatformURIMap(true));
    return result;
  }

  protected IPath analyzeProject(IProject project) throws Exception
  {
    // Walk the project looking for .java files to analyze.
    //
    IJavaProject javaProject = JavaCore.create(project);
    IPackageFragmentRoot[] packageFragmentRoots = javaProject.getPackageFragmentRoots();
    Set<IResource> visited = new HashSet<IResource>();
    for (int i = 0; i < packageFragmentRoots.length; ++i)
    {
      if (packageFragmentRoots[i].getKind() == IPackageFragmentRoot.K_SOURCE)
      {
        traverse((IContainer)packageFragmentRoots[i].getUnderlyingResource(), visited);
      }
    }

    for (Map.Entry<EGenericType, EGenericType> entry : ecoreEGenericTypeToJavaEGenericTypeMap.entrySet())
    {
      EGenericType ecoreEGenericType = entry.getKey();
      EGenericType javaEGenericType = entry.getValue();
      EModelElement eModelElement = null;
      for (EObject eObject = ecoreEGenericType.eContainer(); eObject != null; eObject = eObject.eContainer())
      {
        if (eObject instanceof EModelElement)
        {
          eModelElement = (EModelElement)eObject;
          break;
        }
      }
      RequiredClassifierType requiredClassifierType =
         ecoreEGenericType.eContainer() instanceof EClass || ecoreEGenericType.eContainer() instanceof EReference ?
           RequiredClassifierType.CLASS :
           eAttributes.contains(ecoreEGenericType.eContainer()) ?
             RequiredClassifierType.DATA_TYPE :
             RequiredClassifierType.NONE;
      resolve(eModelElement, ecoreEGenericType, requiredClassifierType);
      if (javaEGenericType != null)
      {
        resolve(eModelElement, javaEGenericType, requiredClassifierType);
      }

      if (javaEGenericType != null)
      {
        resolve(eModelElement, ecoreEGenericType, javaEGenericType);
      }
    }

    for (Map.Entry<EGenericType, EGenericType> entry : ecoreEGenericTypeToJavaEGenericTypeMap.entrySet())
    {
      EGenericType ecoreEGenericType = entry.getKey();
      EGenericType javaEGenericType = entry.getValue();
      EModelElement eModelElement = null;
      for (EObject eObject = ecoreEGenericType.eContainer(); eObject != null; eObject = eObject.eContainer())
      {
        if (eObject instanceof EModelElement)
        {
          eModelElement = (EModelElement)eObject;
          break;
        }
      }

      if (javaEGenericType == null)
      {
        resolve(eModelElement, ecoreEGenericType, javaEGenericType);
      }
      used(ecoreEGenericType);
    }

    for (EStructuralFeature eStructuralFeature : eStructuralFeatures)
    {
      EGenericType eGenericType = eStructuralFeature.getEGenericType();
      EClassifier eClassifier = eGenericType.getERawType();

      // If we have resolved to an EClass but we have an EAttribute, we can change it to be an EReference.
      //
      if (eClassifier instanceof EClass && eStructuralFeature instanceof EAttribute)
      {
        EAttribute eAttribute = (EAttribute)eStructuralFeature;
        EClass container = eAttribute.getEContainingClass();
        EReference eReference = EcoreFactory.eINSTANCE.createEReference();
        eReference.setChangeable(eAttribute.isChangeable());
        eReference.setVolatile(eAttribute.isVolatile());
        eReference.setTransient(eAttribute.isTransient());
        eReference.setDerived(eAttribute.isDerived());
        eReference.setUnsettable(eAttribute.isUnsettable());
        eReference.setLowerBound(eAttribute.getLowerBound());
        eReference.setUpperBound(eAttribute.getUpperBound());
        eReference.setName(eStructuralFeature.getName());
        eReference.setEGenericType(eGenericType);
        eReference.getEAnnotations().addAll(eStructuralFeature.getEAnnotations());
        container.getEStructuralFeatures().add(container.getEStructuralFeatures().indexOf(eStructuralFeature), eReference);
        container.getEStructuralFeatures().remove(eStructuralFeature);
        eStructuralFeature = eReference;
      }
      else if (eClassifier instanceof EDataType && eStructuralFeature instanceof EReference)
      {
        EReference eReference = (EReference)eStructuralFeature;
        EClass container = eReference.getEContainingClass();
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setChangeable(eReference.isChangeable());
        eAttribute.setVolatile(eReference.isVolatile());
        eAttribute.setTransient(eReference.isTransient());
        eAttribute.setDerived(eReference.isDerived());
        eAttribute.setUnsettable(eReference.isUnsettable());
        eAttribute.setLowerBound(eReference.getLowerBound());
        eAttribute.setUpperBound(eReference.getUpperBound());
        eAttribute.setName(eStructuralFeature.getName());
        eAttribute.setEGenericType(eGenericType);
        eAttribute.getEAnnotations().addAll(eStructuralFeature.getEAnnotations());
        container.getEStructuralFeatures().remove(eStructuralFeature);
        eStructuralFeature = eAttribute;
        eReferenceToOppositeNameMap.remove(eReference);
      }

      if (eClassifier.getEPackage() == null)
      {
        error(CodeGenEcorePlugin.INSTANCE.getString("_UI_TheTypeDoesNotResolveCorrectly_message", new Object []{ eClassifier.getInstanceTypeName() }));

        eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        eGenericType.setEClassifier(eClassifier instanceof EClass ? EcorePackage.Literals.EOBJECT : EcorePackage.Literals.EJAVA_OBJECT);
      }
    }

    // Now we need to hook up opposites by finding the named feature in the type.
    //
    for (Map.Entry<EReference, String> entry : eReferenceToOppositeNameMap.entrySet())
    {
      EReference eReference = entry.getKey();
      String oppositeName = entry.getValue();
      EClass eClass = (EClass)eReference.getEType();
      // TODO handle class cast exception better.
      EReference eOpposite = (EReference)eClass.getEStructuralFeature(oppositeName);
      if (eOpposite == null)
      {
        error(CodeGenEcorePlugin.INSTANCE.getString("_UI_TheAttributeIsNotAMemberOf_message", new Object []{ oppositeName, eClass.getName() }));
      }
      else if (eOpposite.getEOpposite() != eReference && eOpposite.getEOpposite() != null)
      {
        error
          (CodeGenEcorePlugin.INSTANCE.getString
             ("_UI_TheOppositeAlreadyHasOpposite_message",
              new Object []{ oppositeName, eOpposite.getEOpposite().getName(), eOpposite.getEOpposite().getEContainingClass().getName() }));
      }
      else
      {
        eReference.setEOpposite(eOpposite);
        eOpposite.setEOpposite(eReference);

        used(eOpposite);

        // Containers are transient by default unless explicitly annotated otherwise.
        //
        if (eOpposite.isContainment() && !transientEReferenceWithOpposite.contains(eReference))
        {
          eReference.setTransient(true);
        }
      }
    }

    // Now we need to hook up keys by finding the named feature in the type.
    //
    for (Map.Entry<EReference, List<String>> entry : eReferenceToKeyNamesMap.entrySet())
    {
      EReference eReference = entry.getKey();
      EClass eClass = (EClass)eReference.getEType();
      for (String keyName : entry.getValue())
      {
        EStructuralFeature eKey = eClass.getEStructuralFeature(keyName);
        if (eKey == null)
        {
          error(CodeGenEcorePlugin.INSTANCE.getString("_UI_TheAttributeIsNotAMemberOf_message", new Object []{ keyName, eClass.getName() }));
        }
        else if (!(eKey instanceof EAttribute))
        {
          // TODO Ignore for now.
        }
        else
        {
          eReference.getEKeys().add((EAttribute)eKey);
          used(eKey);
        }
      }
    }

    // Clean up the temporary container annotations for holding map entry classes until they are for sure needed.
    //
    for (EPackage ePackage : packageNameToEPackageMap.values())
    {
      EAnnotation eAnnotation = ePackage.getEAnnotation(MAP_ENTRY_CLASS_CONTAINER_ANNOTATION_SOURCE);
      if (eAnnotation != null)
      { 
        EcoreUtil.remove(eAnnotation);
      }
    }

    // Now we should sort.
    //
    for (Map.Entry<EPackage, Map<Object, Integer>> entry : ePackageToOrderingMap.entrySet())
    {
      EPackage ePackage = entry.getKey();
      Map<Object, Integer> nameToIDMap = entry.getValue();

      sort(ePackage.getEClassifiers(), nameToIDMap);
      for (EClassifier eClassifier : ePackage.getEClassifiers())
      {
        if (eClassifier instanceof EClass)
        {
          EClass eClass = (EClass)eClassifier;
          sort(eClass.getEStructuralFeatures(), nameToIDMap);
        }
      }
    }

    // Find the fragment root so that we can generate to the right location (by default).
    //
    IPath targetFragmentRoot = project.getFullPath();
    for (int i = 0; i < packageFragmentRoots.length; ++i)
    {
      if (packageFragmentRoots[i].getKind() == IPackageFragmentRoot.K_SOURCE)
      {
        IPath path = packageFragmentRoots[i].getUnderlyingResource().getFullPath();
        if (targetFragmentRoot.isPrefixOf(path))
        {
          targetFragmentRoot = path;
          break;
        }
      }
    }

    facadeHelper.reset();
    return targetFragmentRoot;
  }

  public void computeEPackages(Monitor monitor, ModelImporter modelImporter) throws Exception
  {
    IProject project = genModelFile.getProject();
    project.open(BasicMonitor.toIProgressMonitor(monitor));
    if (!project.hasNature(JavaCore.NATURE_ID) && oldGenModelVersion.getModelDirectory() != null)
    {
      project = project.getWorkspace().getRoot().getFolder(new Path(oldGenModelVersion.getModelDirectory())).getProject();
    }

    Collection<IFile> allGenModelFiles = new ArrayList<IFile>();
    Collection<IProject> allReferencedProjects = new ArrayList<IProject>();
    getAllReferencedProjects(allReferencedProjects, project.getDescription().getReferencedProjects());
    getAllReferencedProjects(allReferencedProjects, project.getDescription().getDynamicReferences());
    for (IProject referencedProject : allReferencedProjects)
    {
      getAllGenModelFiles(allGenModelFiles, referencedProject);
    }

    ResourceSet resourceSet = modelImporter.createResourceSet();
    for (IFile file : allGenModelFiles)
    {
      Resource resource = resourceSet.getResource(modelImporter.createFileURI(file.getFullPath().toString()), true);
      GenModel genModel = GenModelUtil.getGenModel(resource);
      externalGenModels.add(genModel);
      for (GenPackage genPackage : genModel.getGenPackages())
      {
        determineExternalPackages(genPackage, modelImporter);
      }
    }

    // Iterate over all projects to look at the manifests.
    //
    List<String> allReferencedPluginIDs = new UniqueEList<String>();
    allReferencedProjects.add(project);
    for (IProject referencedProject : allReferencedProjects)
    {
      try
      {
        // Determine the required plugins.
        //
        URI manifestURI = URI.createURI(referencedProject.getFullPath() + "/META-INF/MANIFEST.MF");
        Manifest manifest = new Manifest(resourceSet.getURIConverter().createInputStream(manifestURI));
        String requires =  manifest.getMainAttributes().getValue("Require-Bundle");
        if (requires != null)
        {
          ManifestElement[] elements = ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, requires);
          for (int j = 0; j < elements.length; ++j)
          {
            // Include each required plugin of the starting project and the exported ones of all the required projects.
            //
            ManifestElement element = elements[j];
            if (project == referencedProject || "reexport".equals(element.getDirective("visibility")))
            {
              String pluginID = element.getValue();
              allReferencedPluginIDs.add(pluginID);
            }
          }
        }
      }
      catch (IOException exception)
      {
        // Ignore
      }
    }

    // Iterate over all the plugin IDs to determine the dependency closure.
    // The list grows as the plugins are visited.
    //
    for (int i = 0; i < allReferencedPluginIDs.size(); ++i)
    {
      // Determine the required plugins.
      //
      String pluginID = allReferencedPluginIDs.get(i);
      Bundle bundle = Platform.getBundle(pluginID);
      if (bundle != null)
      {
        String requires = (String)bundle.getHeaders().get(Constants.REQUIRE_BUNDLE);
        if (requires != null)
        {
          ManifestElement[] elements = ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, requires);
          for (int j = 0; j < elements.length; ++j)
          {
            // Also add each required plugin for consideration in the loop.
            //
            ManifestElement element = elements[j];
            String value = element.getValue();
            if ("reexport".equals(element.getDirective("visibility")))
            {
              allReferencedPluginIDs.add(value);
            }
          }
        }
      }
    }

    // Determine the inverse map from plugin IDs to their registered GenModel locations.
    //
    Map<String, List<URI>> allPluginsWithGenModels = new HashMap<String, List<URI>>();
    for (Map.Entry<String, URI> entry : EcorePlugin.getEPackageNsURIToGenModelLocationMap(true).entrySet())
    {
      // If it's a platform plugin URI, include it in the map.
      //
      URI genModelLocation = entry.getValue();
      if (genModelLocation.isPlatformResource())
      {
        List<URI> uris = allPluginsWithGenModels.get(genModelLocation.segment(1));
        if (uris == null)
        {
          uris = new UniqueEList<URI>();
        }
        uris.add(genModelLocation);
        allPluginsWithGenModels.put(genModelLocation.segment(1), uris);
      }
    }

    // Keep only the plugins that have GenModels for consideration.
    //
    allReferencedPluginIDs.retainAll(allPluginsWithGenModels.keySet());
    for (String pluginID : allReferencedPluginIDs)
    {
      // Consider each GenModel location URI for each required plugin.
      //
      for (URI uri : allPluginsWithGenModels.get(pluginID))
      {
        // Load the model and if it's not one already considered, e.g., a local version in the workspace, process its GenPackages.
        //
        Resource resource = resourceSet.getResource(uri, true);
        GenModel genModel = GenModelUtil.getGenModel(resource);
        if (externalGenModels.add(genModel))
        {
          for (GenPackage genPackage : genModel.getGenPackages())
          {
            determineExternalPackages(genPackage, modelImporter);
          }
        }
      }
    }

    IPath targetFragmentRoot = analyzeProject(project);
    modelImporter.setModelPluginDirectory(targetFragmentRoot.toString());

    if (packageNameToEPackageMap.isEmpty())
    {
      error(JavaImporterPlugin.INSTANCE.getString(foundJava ? "_UI_NoModelElementsInJava_message" : "_UI_NoModelElements_message"));
    }

    for (EPackage ePackage : packageNameToEPackageMap.values())
    {
      modelImporter.getEPackages().add(ePackage);

      ModelImporter.EPackageImportInfo ePackageInfo = modelImporter.getEPackageImportInfo(ePackage);
      ePackageInfo.setPrefix(ePackageToPrefixMap.get(ePackage));
      for (Map.Entry<String, EPackage> entry : packageNameToEPackageMap.entrySet())
      {
        if (entry.getValue() == ePackage)
        {
          String qualifiedPackageName = entry.getKey();
          if (qualifiedPackageName != null)
          {
            int index = qualifiedPackageName.lastIndexOf(".");
            if (index != -1)
            {
              ePackageInfo.setBasePackage(qualifiedPackageName.substring(0, index));
            }
          }
          break;
        }
      }
    }
  }

  public void used(EGenericType eGenericType)
  {
    if (eGenericType != null)
    {
      EClassifier eClassifier = eGenericType.getEClassifier();
      if (eClassifier != null)
      {
        used(eClassifier);
      }
      used(eGenericType.getEUpperBound());
      used(eGenericType.getELowerBound());
      for (EGenericType eTypeArgument : eGenericType.getETypeArguments())
      {
        used(eTypeArgument);
      }
    }
  }

  public void used(EModelElement modelElement)
  {
    EPackage ePackage = (EPackage)EcoreUtil.getRootContainer(modelElement);
    if (ePackage != EcorePackage.eINSTANCE)
    {
      for (GenModel genModel : externalGenModels)
      {
        GenPackage genPackage = genModel.findGenPackage(ePackage);
        if (genPackage != null)
        {
          if (!getUsedGenPackages().contains(genPackage) && genPackage.eResource() != null)
          {
            getUsedGenPackages().add(genPackage);

            // Compute the closure.
            //
            for (Iterator<EObject> j = ePackage.eAllContents(); j.hasNext();)
            {
              for (Iterator<EObject> k = j.next().eCrossReferences().iterator(); k.hasNext();)
              {
                EObject o = k.next();
                if (o instanceof EModelElement)
                {
                  used((EModelElement)o);
                }
              }
            }
          }
          break;
        }
      }
    }
  }

  public void determineExternalPackages(GenPackage genPackage)
  {
    determineExternalPackages(genPackage, null);
  }

  protected void determineExternalPackages(GenPackage genPackage, ModelImporter modelImporter)
  {
    if (modelImporter != null)
    {
      modelImporter.getReferencedGenPackages().add(genPackage);
    }
    EPackage ePackage = genPackage.getEcorePackage();
    externalPackageNameToEPackageMap.put(genPackage.getInterfacePackageName(), ePackage);
    for (GenPackage nestedGenPackage : genPackage.getNestedGenPackages())
    {
      determineExternalPackages(nestedGenPackage);
    }
  }

  /**
   * Walks the projects recursively.
   */
  public void getAllReferencedProjects(Collection<IProject> result, IProject[] projects) throws CoreException
  {
    for (int i = 0; i < projects.length; ++i)
    {
      IProject project = projects[i];
      if (!result.contains(project) && project.exists() && project.isOpen())
      {
        result.add(project);
        getAllReferencedProjects(result, project.getDescription().getReferencedProjects());
        getAllReferencedProjects(result, project.getDescription().getDynamicReferences());
      }
    }
  }

  /**
   * Walks the container recursively.
   */
  public void getAllGenModelFiles(Collection<IFile> result, IContainer container) throws CoreException
  {
    IResource[] contents = container.members();
    for (int i = 0; i < contents.length; ++i)
    {
      IResource resource = contents[i];
      if (resource.getType() == IResource.FILE)
      {
        getAllGenModelFiles(result, (IFile)resource);
      }
      else
      {
        getAllGenModelFiles(result, (IContainer)resource);
      }
    }
  }

  /**
   * Walks the container recursively.
   */
  public void getAllGenModelFiles(Collection<IFile> result, IFile file) throws CoreException
  {
    if (file.getName().endsWith(".genmodel"))
    {
      IProject project = file.getProject();
      IJavaProject javaProject = JavaCore.create(project);
      try
      {
        IPath outputLocation = javaProject.getOutputLocation();
        if (project == project.getWorkspace().getRoot().findMember(javaProject.getOutputLocation())
          || !outputLocation.isPrefixOf(file.getFullPath()))
        {
          result.add(file);
        }
      }
      catch (JavaModelException exception)
      {
        JavaImporterPlugin.INSTANCE.log(exception);
      }
    }
  }

  /**
   * Walks the container recursively.
   */
  public void traverse(IContainer container, Set<IResource> visited) throws CoreException
  {
    IResource[] contents = container.members();
    for (int i = 0; i < contents.length; ++i)
    {
      IResource resource = contents[i];
      if (visited.add(resource))
      {
        if (resource.getType() == IResource.FILE)
        {
          traverse((IFile)resource);
        }
        else
        {
          traverse((IContainer)resource, visited);
        }
      }
    }
  }

  /**
   * Analyzes .java files as compilation units.
   */
  public void traverse(IFile file) throws CoreException
  {
    if ("java".equalsIgnoreCase(file.getProjectRelativePath().getFileExtension()))
    {
      try
      {
        BufferedInputStream bufferedInputStream = new BufferedInputStream(file.getContents(true));
        byte[] input = new byte [bufferedInputStream.available()];
        bufferedInputStream.read(input);
        bufferedInputStream.close();

        String encoding = null;
        try
        {
          encoding = file.getCharset();
        }
        catch (CoreException ce)
        {
          // use no encoding
        }
        String contents = encoding == null ? new String(input) : new String(input, encoding);
        JCompilationUnit compilationUnit = facadeHelper.createCompilationUnit("NAME", contents);
        analyzeCompilationUnit(compilationUnit);
      }
      catch (IOException exception)
      {
        JavaImporterPlugin.INSTANCE.log(exception);
      }
    }
  }

  /**
   * Walks the compilation unit to analyze the type.
   */
  protected void analyzeCompilationUnit(JCompilationUnit compilationUnit)
  {
    foundJava = true;
    for (JAbstractType abstractType : facadeHelper.getChildren(compilationUnit, JAbstractType.class))
    {
      if (abstractType instanceof JEnum)
      {
        analyzeEnum((JEnum)abstractType);
      }
      else if (abstractType instanceof JType)
      {
        analyzeType((JType)abstractType);
      }
      break;
    }
  }

  protected void analyzeEnum(JEnum enumeration)
  {
    String modelAnnotation = getModelAnnotation(enumeration.getComment());
    if (modelAnnotation != null)
    {
      EEnum eEnum = EcoreFactory.eINSTANCE.createEEnum();
      eModelElementToJNodeMap.put(eEnum, enumeration);
      eEnum.setName(enumeration.getName());
      getEPackage(enumeration).getEClassifiers().add(eEnum);
      eEnum.getEAnnotations().addAll(extractEAnnotations(modelAnnotation));
      EcoreUtil.setDocumentation(eEnum, getModelDocumentation(enumeration.getComment()));

      // Walk the fields.
      //
      boolean hasEnumLiteral = false;
      for (JEnumConstant enumConstant : facadeHelper.getChildren(enumeration, JEnumConstant.class))
      {
        hasEnumLiteral |= analyzeEnumLiteral(eEnum, enumConstant);
      }

      if (!hasEnumLiteral)
      {
        for (JField field : facadeHelper.getChildren(enumeration, JField.class))
        {
          hasEnumLiteral |= analyzeEnumLiteral(eEnum, field);
        }
      }
    }
  }

  protected EPackage getEPackage(JNode node)
  {
    JPackage jPackage = facadeHelper.getPackage(node);
    String qualifiedPackageName = jPackage != null ?
      jPackage.getQualifiedName()
      : null;

    EPackage ePackage = packageNameToEPackageMap.get(qualifiedPackageName);
    if (ePackage == null)
    {
      // Create the EPackage on demand.
      //
      ePackage = EcoreFactory.eINSTANCE.createEPackage();
      int index = qualifiedPackageName == null ? -1 : qualifiedPackageName.lastIndexOf(".");
      String packageName = index == -1 ? qualifiedPackageName : qualifiedPackageName.substring(index + 1);
      ePackage.setName(packageName);
      ePackage.setNsURI("http:///" + (qualifiedPackageName == null ? "null" : qualifiedPackageName.replace('.', '/')) + ".ecore");
      ePackage.setNsPrefix(qualifiedPackageName == null ? "null" : qualifiedPackageName);
      packageNameToEPackageMap.put(qualifiedPackageName, ePackage);

      if (packageName != null)
      {
        String prefix = Character.toUpperCase(packageName.charAt(0)) + packageName.substring(1);
        ePackageToPrefixMap.put(ePackage, prefix);
      }
    }

    return ePackage;
  }

  private static final String[] EMPTY_STRING_ARRAY = new String[0];

  /**
   * Walks the type either as an EClass or an ENum to analyze either the methods or the fields.
   */
  protected void analyzeType(JType type)
  {
    // Check whether this has @model annotation contents.
    // If not, it might be a package interface, for backwards compatibility.
    //
    String modelAnnotation = getModelAnnotation(type.getComment());
    boolean isEClassifier = false;
    String kind = null;

    if (modelAnnotation != null)
    {
      kind = getModelAnnotationAttribute(modelAnnotation, "kind");
      isEClassifier = !"package".equals(kind);
    }

    if (isEClassifier)
    {
      // Get the package name and see if there's an EPackage for it.
      //
      EPackage ePackage = getEPackage(type);

      // If it's an interface, then it will be treated as an EClass
      //
      if ((type.getFlags() & Flags.AccInterface) != 0 || "class".equals(kind))
      {
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eModelElementToJNodeMap.put(eClass, type);
        eClass.setName(type.getName());
        ePackage.getEClassifiers().add(eClass);
        eClass.getEAnnotations().addAll(extractEAnnotations(modelAnnotation));
        EcoreUtil.setDocumentation(eClass, getModelDocumentation(type.getComment()));

        analyzeTypeParameters(eClass.getETypeParameters(), type.getTypeParameters(), modelAnnotation);

        String[] superInterfaces = type.getSuperInterfaces();
        String extend = getExtendsAnnotation(type.getComment());
        if (extend != null)
        {
          List<String> superInterfaceList = new ArrayList<String>(Arrays.asList(superInterfaces));
          Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseTypeArgumentList(extend);
          if (diagnostic.getSeverity() == Diagnostic.OK)
          {
            @SuppressWarnings("unchecked")
            List<EGenericType> eTypeArguments = (List<EGenericType>)diagnostic.getData().get(0);
            for (EGenericType eGenericType : eTypeArguments)
            {
              superInterfaceList.remove(EcoreUtil.toJavaInstanceTypeName(eGenericType));
            }
          }
          superInterfaces = new String [superInterfaceList.size()];
          superInterfaceList.toArray(superInterfaces);
        }

        // Create a generic super type with an EClass as the classifier for each super interface in the Java representation.
        //
        List<EGenericType> javaEGenericSuperTypes = new ArrayList<EGenericType>();
        for (String superType : superInterfaces)
        {
          Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(superType);
          if (diagnostic.getSeverity() == Diagnostic.OK)
          {
            EGenericType eGenericSuperType = (EGenericType)diagnostic.getData().get(0);
            EClass eSuperClass = EcoreFactory.eINSTANCE.createEClass();
            eSuperClass.setAbstract(true);
            eSuperClass.setInterface(true);
            eSuperClass.setInstanceTypeName(eGenericSuperType.getEClassifier().getInstanceTypeName());
            eGenericSuperType.setEClassifier(eSuperClass);
            javaEGenericSuperTypes.add(eGenericSuperType);
          }
        }

        // Create a generic super type with an EClass as the classifier for each super interface in the @model representation
        //
        List<EGenericType> ecoreEGenericSuperTypes = null;
        String superTypes = getModelAnnotationAttribute(modelAnnotation, "superTypes");
        if (superTypes != null)
        {
          ecoreEGenericSuperTypes = new ArrayList<EGenericType>();
          for (EGenericType ecoreEGenericSuperType : analyzeEGenericTypes(superTypes))
          {
            EClass eSuperClass = EcoreFactory.eINSTANCE.createEClass();
            eSuperClass.setAbstract(true);
            eSuperClass.setInterface(true);
            eSuperClass.setInstanceTypeName(ecoreEGenericSuperType.getEClassifier().getInstanceTypeName());
            ecoreEGenericSuperType.setEClassifier(eSuperClass);
            ecoreEGenericSuperTypes.add(ecoreEGenericSuperType);
          }
        }

        // Filter out explicit EObject from super types, except in the Ecore package itself, or if it appears in the @model superTypes.
        //
        if (!javaEGenericSuperTypes.isEmpty() &&
              javaEGenericSuperTypes.get(0).getERawType().getInstanceTypeName().endsWith("EObject") &&
              (ecoreEGenericSuperTypes == null ||
                !ecoreEGenericSuperTypes.isEmpty() && !ecoreEGenericSuperTypes.get(0).getERawType().getInstanceTypeName().endsWith("EObject")) &&
              !EcorePackage.eNS_URI.equals(ePackage.getNsURI()))
        {
           EGenericType superType = resolve(eClass, javaEGenericSuperTypes.get(0).getERawType().getInstanceTypeName(), RequiredClassifierType.CLASS, false);
           EClassifier superClass = superType.getEClassifier();
           if (superClass.getEPackage() != null &&
                 EcorePackage.eNS_URI.equals(superClass.getEPackage().getNsURI()) &&
                 "EObject".equals(superClass.getName()))
           {
             javaEGenericSuperTypes.remove(0);
           }
        }

        // Match them and accumulate the appropriate result.
        //
        match(eClass.getEGenericSuperTypes(), javaEGenericSuperTypes, ecoreEGenericSuperTypes);

        String isInterface = getModelAnnotationAttribute(modelAnnotation, "interface");
        eClass.setInterface("true".equals(isInterface));

        String isAbstract = getModelAnnotationAttribute(modelAnnotation, "abstract");
        eClass.setAbstract("true".equals(isAbstract) || isAbstract == null && eClass.isInterface());

        // Walk the methods.
        //
        for (JMethod method : facadeHelper.getChildren(type, JMethod.class))
        {
          analyzeMethod(eClass, method);
        }

        // Additional attributes and references may be defined directly on the interface in order to allow the
        // get accessor method to have suppressed visibility.
        //
        String features = getModelAnnotationAttribute(modelAnnotation, "features");
        if (features != null)
        {
          for (StringTokenizer stringTokenizer = new StringTokenizer(features, " "); stringTokenizer.hasMoreTokens();)
          {
            String feature = stringTokenizer.nextToken();
            if (eClass.getEStructuralFeature(feature) == null)
            {
              analyzeMethod
                (eClass,
                 getFilteredModelAnnotations(modelAnnotation, feature),
                 "get" + Character.toUpperCase(feature.charAt(0)) + feature.substring(1),
                 "java.lang.Object",
                 EMPTY_STRING_ARRAY,
                 EMPTY_STRING_ARRAY,
                 EMPTY_STRING_ARRAY,
                 EMPTY_STRING_ARRAY);
            }
            else
            {
              warning(CodeGenEcorePlugin.INSTANCE.getString("_UI_DuplicateFeature_message", new Object []{ feature, eClass.getName() }));
            }
          }
        }
      }
      // Otherwise it's treated as an EEnum
      //
      else
      {
        EEnum eEnum = EcoreFactory.eINSTANCE.createEEnum();
        eModelElementToJNodeMap.put(eEnum, type);
        eEnum.setName(type.getName());
        ePackage.getEClassifiers().add(eEnum);
        eEnum.getEAnnotations().addAll(extractEAnnotations(modelAnnotation));
        EcoreUtil.setDocumentation(eEnum, getModelDocumentation(type.getComment()));

        // Walk the fields.
        //
        for (JField field : facadeHelper.getChildren(type, JField.class))
        {
          analyzeEnumLiteral(eEnum, field);
        }
      }
    }
    // Find Packages and Factories
    else
    {
      JPackage jPackage = facadeHelper.getPackage(type);
      String qualifiedPackageName = jPackage != null ? jPackage.getQualifiedName() : null;

      String typeName = type.getName();
      boolean isEPackage = false;
      if (typeName.endsWith("Package") && typeName.length() > 7)
      {
        String packagePrefix = typeName.substring(0, typeName.length() - 7);

        // It's definitely a package if it was declared as such.
        //
        if ("package".equals(kind))
        {
          isEPackage = true;
        }

        int index = qualifiedPackageName == null ? -1 : qualifiedPackageName.lastIndexOf(".");
        String name = index == -1 ? qualifiedPackageName : qualifiedPackageName.substring(index + 1);
        String nsURI = "http:///" + (qualifiedPackageName == null ? "null" : qualifiedPackageName.replace('.', '/')) + ".ecore";
        String nsPrefix = qualifiedPackageName == null ? "null" : qualifiedPackageName;

        List<EClass> eClasses = new ArrayList<EClass>();
        List<EDataType> eDataTypes = new ArrayList<EDataType>();
        Map<Object, Integer> ordering = new HashMap<Object, Integer>();

        // It's definitely a package if expected constants eNAME, eNS_PREFIX, or eNS_URI appear.
        //
        for (JNode node : type.getChildren())
        {
          if (node instanceof JField)
          {
            JField field = (JField)node;
            String fieldName = field.getName();
            String fieldType = field.getType();
            if ("eNAME".equals(fieldName))
            {
              isEPackage = true;
              name = field.getInitializer();
              name = name.substring(1, name.length() - 1);
            }
            else if ("eNS_URI".equals(fieldName))
            {
              isEPackage = true;
              nsURI = field.getInitializer();
              nsURI = nsURI.substring(1, nsURI.length() - 1);
            }
            else if ("eNS_PREFIX".equals(fieldName))
            {
              isEPackage = true;
              nsPrefix = field.getInitializer();
              nsPrefix = nsPrefix.substring(1, nsPrefix.length() - 1);
            }
            else if ("int".equals(fieldType) && !fieldName.endsWith("FEATURE_COUNT"))
            {
              try
              {
                String initializer = field.getInitializer();
                int plusIndex = initializer.lastIndexOf("+");
                if (plusIndex != -1)
                {
                  initializer = initializer.substring(plusIndex + 1);
                }
                initializer = initializer.trim();
                int value = Integer.parseInt(initializer);
                ordering.put(fieldName, value);
              }
              catch (NumberFormatException exception)
              {
                // This will catch inherited features, or additional things we don't want to worry about.
              }
            }
          }
          else if (node instanceof JMethod)
          {
            JMethod method = (JMethod)node;
            String methodAnnotation = getModelAnnotation(method.getComment());
            if (methodAnnotation != null)
            {
              String returnType = method.getReturnType();
              if (returnType != null)
              {
                if (returnType.endsWith("EDataType"))
                {
                  EDataType eDataType = EcoreFactory.eINSTANCE.createEDataType();
                  eDataType.setInstanceTypeName(getModelAnnotationAttribute(methodAnnotation, "instanceClass"));
                  eDataType.setName(method.getName().substring(3));
                  String isSerializable = getModelAnnotationAttribute(methodAnnotation, "serializable");
                  if ("false".equals(isSerializable))
                  {
                    eDataType.setSerializable(false);
                  }

                  String typeParameterNames = getModelAnnotationAttribute(methodAnnotation, "typeParameters");
                  if (typeParameterNames != null)
                  {
                    for (StringTokenizer stringTokenizer = new StringTokenizer(typeParameterNames, " "); stringTokenizer.hasMoreTokens();)
                    {
                      String typeParameterName = stringTokenizer.nextToken();
                      ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
                      eTypeParameter.setName(typeParameterName);
                      String typeParameterModelAnnotation = getFilteredModelAnnotations(methodAnnotation, typeParameterName);
                      String bounds = getModelAnnotationAttribute(typeParameterModelAnnotation, "bounds");
                      if (bounds != null)
                      {
                        eTypeParameter.getEBounds().addAll(analyzeEGenericTypes(bounds));
                        for (EGenericType eBound : eTypeParameter.getEBounds())
                        {
                          ecoreEGenericTypeToJavaEGenericTypeMap.put(eBound, null);
                        }
                      }

                      eDataType.getETypeParameters().add(eTypeParameter);
                    }
                  }

                  eDataTypes.add(eDataType);
                  eDataType.getEAnnotations().addAll(extractEAnnotations(methodAnnotation));
                  EcoreUtil.setDocumentation(eDataType, getModelDocumentation(method.getComment()));
                }
                else if (returnType.endsWith("EClass"))
                {
                  EClass eClass = EcoreFactory.eINSTANCE.createEClass();
                  String instanceClass = getModelAnnotationAttribute(methodAnnotation, "instanceClass");
                  if (instanceClass != null)
                  {
                    eClass.setInterface(true);
                    eClass.setAbstract(true);
                    eClass.setInstanceTypeName(instanceClass);
                    eClass.setName(method.getName().substring(3));

                    String typeParameterNames = getModelAnnotationAttribute(methodAnnotation, "typeParameters");
                    if (typeParameterNames != null)
                    {
                      for (StringTokenizer stringTokenizer = new StringTokenizer(typeParameterNames, " "); stringTokenizer.hasMoreTokens();)
                      {
                        String typeParameterName = stringTokenizer.nextToken();
                        ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
                        eTypeParameter.setName(typeParameterName);
                        String typeParameterModelAnnotation = getFilteredModelAnnotations(methodAnnotation, typeParameterName);
                        String bounds = getModelAnnotationAttribute(typeParameterModelAnnotation, "bounds");
                        if (bounds != null)
                        {
                          eTypeParameter.getEBounds().addAll(analyzeEGenericTypes(bounds));
                          for (EGenericType eBound : eTypeParameter.getEBounds())
                          {
                            ecoreEGenericTypeToJavaEGenericTypeMap.put(eBound, null);
                          }
                        }
  
                        eClass.getETypeParameters().add(eTypeParameter);
                      }
                    }

                    eClasses.add(eClass);
                  }
                  else
                  {
                    eClass.setInstanceTypeName("java.util.Map$Entry");
                    eClass.setName(method.getName().substring(3));
                    eClasses.add(eClass);

                    String features = getModelAnnotationAttribute(methodAnnotation, "features");
                    if (features != null)
                    {
                      for (StringTokenizer stringTokenizer = new StringTokenizer(features, " "); stringTokenizer.hasMoreTokens();)
                      {
                        String feature = stringTokenizer.nextToken();
                        analyzeMethod
                          (eClass,
                           getFilteredModelAnnotations(methodAnnotation, feature),
                           "get" + Character.toUpperCase(feature.charAt(0)) + feature.substring(1),
                           "java.lang.Object",
                           EMPTY_STRING_ARRAY,
                           EMPTY_STRING_ARRAY,
                           EMPTY_STRING_ARRAY,
                           EMPTY_STRING_ARRAY);
                      }
                    }
                    else
                    {
                      analyzeMethod
                        (eClass,
                         getFilteredModelAnnotations(methodAnnotation, "key"),
                         "getKey",
                         "java.lang.Object",
                         EMPTY_STRING_ARRAY,
                         EMPTY_STRING_ARRAY,
                         EMPTY_STRING_ARRAY,
                         EMPTY_STRING_ARRAY);

                      analyzeMethod
                        (eClass,
                         getFilteredModelAnnotations(methodAnnotation, "value"),
                         "getValue",
                         "java.lang.Object",
                         EMPTY_STRING_ARRAY,
                         EMPTY_STRING_ARRAY,
                         EMPTY_STRING_ARRAY,
                         EMPTY_STRING_ARRAY);
                    }
                  }
                  eClass.getEAnnotations().addAll(extractEAnnotations(methodAnnotation));
                  EcoreUtil.setDocumentation(eClass, getModelDocumentation(method.getComment()));
                }
              }
            }
          }
        }

        if (isEPackage || !eClasses.isEmpty() || !eDataTypes.isEmpty())
        {
          EPackage ePackage = EcoreFactory.eINSTANCE.createEPackage();
          ePackageToOrderingMap.put(ePackage, ordering);
          eModelElementToJNodeMap.put(ePackage, type);
          ePackage.setNsURI(nsURI);
          ePackage.setNsPrefix(nsPrefix);
          ePackage.setName(name);
          ePackage.getEClassifiers().addAll(eClasses);
          ePackage.getEClassifiers().addAll(eDataTypes);
          if (modelAnnotation != null)
          {
            ePackage.getEAnnotations().addAll(extractEAnnotations(modelAnnotation));
          }
          EcoreUtil.setDocumentation(ePackage, getModelDocumentation(type.getComment()));

          ePackageToPrefixMap.put(ePackage, packagePrefix);

          EPackage existingEPackage = packageNameToEPackageMap.get(qualifiedPackageName);
          if (existingEPackage != null)
          {
            ePackage.getEClassifiers().addAll(existingEPackage.getEClassifiers());
            ePackage.getEAnnotations().addAll(existingEPackage.getEAnnotations());
          }

          packageNameToEPackageMap.put(qualifiedPackageName, ePackage);
        }
      }
    }
  }

  /**
   * Creates an EOperation, EAttribute, or EReference as appropriate.
   */
  protected void analyzeMethod(EClass eClass, JMethod method)
  {
    // Check whether this has @model annotation contents.
    //
    String modelAnnotation = getModelAnnotation(method.getComment());
    if (modelAnnotation != null && method.getName() != null)
    {
      String methodName = method.getName();
      String returnType = method.getReturnType();
      String[] parameterNames = method.getParameterNames();
      String[] parameterTypes = method.getFullParameterTypes();
      String[] exceptionTypes = method.getExceptions();
      String[] typeParameters = method.getTypeParameters();

      ETypedElement eTypedElement = analyzeMethod(eClass, modelAnnotation, methodName, returnType, parameterNames, parameterTypes, exceptionTypes, typeParameters);
      if (eTypedElement != null)
      {
        EcoreUtil.setDocumentation(eTypedElement, getModelDocumentation(method.getComment()));
      }

      eModelElementToJNodeMap.put(eTypedElement, method);
      if (eTypedElement instanceof EOperation)
      {
        EOperation eOperation = (EOperation)eTypedElement;
        for (EParameter eParameter : eOperation.getEParameters())
        {
          eModelElementToJNodeMap.put(eParameter, method);
        }
      }
    }
  }

  protected ETypedElement analyzeMethod(
    EClass eClass,
    String modelAnnotation,
    String methodName,
    String returnType,
    String[] parameterNames,
    String[] parameterTypes,
    String[] exceptionTypes,
    String[] typeParameters)
  {
    // We will create an EAttribute, EReference, or an EOperation.
    //
    ETypedElement eTypedElement = null;
    String featureName = methodName;
    String parameters = getModelAnnotationAttribute(modelAnnotation, "parameters");
    String kind = getModelAnnotationAttribute(modelAnnotation, "kind");

    // An operation is declared via the kind property or, for backwards compatibility, by specifying parameters
    // (though attribute or reference kind takes precedence).
    //
    boolean declaredEOperation =
        "operation".equals(kind) ||
          (parameters != null && !"attribute".equals(kind) && !"reference".equals(kind));

    // Check whether there are parameters; the special attribute and reference rules only apply for the case of no arguments.
    //
    if (parameterNames.length == 0 &&
         !declaredEOperation &&
         methodName.startsWith("get") &&
         methodName.length() > 3 &&
         Character.isUpperCase(methodName.charAt(3)) &&
         !"boolean".equals(returnType) &&
         !"void".equals(returnType))
    {
      // The feature name is extracted lower cased.
      //
      featureName = CodeGenUtil.uncapName(methodName.substring(3));
    }
    else if (parameterNames.length == 0 &&
              !declaredEOperation &&
              methodName.startsWith("is") &&
              methodName.length() > 2 &&
              Character.isUpperCase(methodName.charAt(2)) &&
              "boolean".equals(returnType))
    {
      // The name is extracted and lower cased.
      //
      featureName = CodeGenUtil.uncapName(methodName.substring(2));
    }
    else
    {
      // The method is not a structural feature, so it's modeled as an operation.
      //
      EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
      eTypedElement = eOperation;
      eClass.getEOperations().add(eOperation);

      analyzeTypeParameters(eOperation.getETypeParameters(), typeParameters, modelAnnotation);

      handleETypedElement(eOperation, methodName, returnType, modelAnnotation, eClass.getName() + "." + methodName);

      if (parameterTypes.length > 0)
      {
        // Each token in parameters will specify a dataType for the corresponding parameter, but can be overridden by a
        // parameter-name-prefixed dataType property.
        //
        StringTokenizer stringTokenizer = new StringTokenizer(parameters == null ? "" : parameters);
        for (int i = 0; i < parameterNames.length; ++i)
        {
          EParameter eParameter = EcoreFactory.eINSTANCE.createEParameter();
          eOperation.getEParameters().add(eParameter);
          String parameterName = parameterNames[i];
          String parameterType = parameterTypes[i];
          String parameterModelAnnotation = getFilteredModelAnnotations(modelAnnotation, parameterName);

          if (stringTokenizer.hasMoreTokens())
          {
            String dataType = stringTokenizer.nextToken();
            if (!"-".equals(dataType))
            {
              StringBuilder sb = new StringBuilder(parameterModelAnnotation);
              sb.append("dataType=\"");
              sb.append(dataType);
              sb.append("\" ");
              parameterModelAnnotation = sb.toString();
            }
          }

          StringBuilder identifierName = new StringBuilder(eClass.getName());
          identifierName.append('.');
          identifierName.append(methodName);
          identifierName.append('(');
          identifierName.append(parameterName);
          identifierName.append(')');
          handleETypedElement(eParameter, parameterName, parameterType, parameterModelAnnotation, identifierName.toString());
          eParameter.getEAnnotations().addAll(extractEAnnotations(parameterModelAnnotation));
        }
      }

      if (exceptionTypes.length > 0)
      {
        // Each token in exceptions will specify a data type for the corresponding exception.
        //
        List<EGenericType> ecoreEGenericExceptions = new ArrayList<EGenericType>();
        String exceptions = getModelAnnotationAttribute(modelAnnotation, "exceptions");
        if (exceptions != null)
        {
          ecoreEGenericExceptions.addAll(analyzeEGenericTypes(exceptions));
        }
        List<EGenericType> javaEGenericExceptions = new ArrayList<EGenericType>();
        for (String exception : exceptionTypes)
        {
          Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(exception);
          if (diagnostic.getSeverity() == Diagnostic.OK)
          {
            javaEGenericExceptions.add((EGenericType)diagnostic.getData().get(0));
          }
        }
        match(eOperation.getEGenericExceptions(), javaEGenericExceptions, ecoreEGenericExceptions);
      }
    }

    // If we aren't doing an operation...
    //
    if (eTypedElement == null)
    {
      // We'll create one of these.
      //
      EStructuralFeature eStructuralFeature = null;

      // If any of these attributes appear, this must be a reference.
      //
      String opposite = getModelAnnotationAttribute(modelAnnotation, "opposite");
      String keys = getModelAnnotationAttribute(modelAnnotation, "keys");
      String containment = getModelAnnotationAttribute(modelAnnotation, "containment");
      String resolveProxies = getModelAnnotationAttribute(modelAnnotation, "resolveProxies");
      String mapType = getModelAnnotationAttribute(modelAnnotation, "mapType");
      String keyType = getModelAnnotationAttribute(modelAnnotation, "keyType");
      String valueType = getModelAnnotationAttribute(modelAnnotation, "valueType");

      if ("reference".equals(kind) ||
            opposite != null ||
            keys != null ||
            containment != null ||
            resolveProxies != null ||
            mapType != null ||
            keyType != null && valueType != null)
      {
        EReference eReference = EcoreFactory.eINSTANCE.createEReference();
        eTypedElement = eStructuralFeature = eReference;
        eClass.getEStructuralFeatures().add(eReference);

        // Set the EReference attributes.
        //
        eReference.setContainment
          ("true".equals(containment) ||
              mapType != null && !stripTypeArguments(returnType).endsWith("Entry") ||
              keyType != null && valueType != null);
        eReference.setResolveProxies(eReference.isContainment() ? "true".equals(resolveProxies) : !"false".equals(resolveProxies));
        eReference.setUnsettable("true".equals(getModelAnnotationAttribute(modelAnnotation, "unsettable")));

        // Defer the handling of the opposite.
        //
        if (opposite != null)
        {
          eReferenceToOppositeNameMap.put(eReference, opposite);
          if ("false".equals(getModelAnnotationAttribute(modelAnnotation, "transient")))
          {
            transientEReferenceWithOpposite.add(eReference);
          }
        }
        
        if (keys != null)
        {
          List<String> keyNames = new ArrayList<String>();
          for (StringTokenizer stringTokenizer = new StringTokenizer(keys, " ,\t\n\r\f"); stringTokenizer.hasMoreTokens();)
          {
            keyNames.add(stringTokenizer.nextToken());
          }
          eReferenceToKeyNamesMap.put(eReference, keyNames);
        }
      }
      else
      {
        // Assume that it's an attribute for now.
        // It will/could become a reference if the type resolves to an EClass.
        //
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        if ("attribute".equals(kind))
        {
          eAttributes.add(eAttribute);
        }
        eTypedElement = eStructuralFeature = eAttribute;
        eClass.getEStructuralFeatures().add(eAttribute);

        // Set the EAttribute attributes.
        //
        eAttribute.setUnsettable("true".equals(getModelAnnotationAttribute(modelAnnotation, "unsettable")));
        eAttribute.setID("true".equals(getModelAnnotationAttribute(modelAnnotation, "id")));
        String defaultValueLiteral = getModelAnnotationAttribute(modelAnnotation, "defaultValue");
        if (defaultValueLiteral == null)
        {
          defaultValueLiteral = getModelAnnotationAttribute(modelAnnotation, "default");
        }
        eStructuralFeature.setDefaultValueLiteral(defaultValueLiteral);
      }

      // Handle the type, multiplicity and other ETypedElement attributes.
      //
      handleETypedElement(eStructuralFeature, featureName, returnType, modelAnnotation, eClass.getName() + "." + methodName);

      // Set the EStructuralFeature attributes.
      //
      eStructuralFeature.setChangeable(!"false".equals(getModelAnnotationAttribute(modelAnnotation, "changeable")));
      eStructuralFeature.setDerived("true".equals(getModelAnnotationAttribute(modelAnnotation, "derived")));
      eStructuralFeature.setVolatile("true".equals(getModelAnnotationAttribute(modelAnnotation, "volatile")));
      eStructuralFeature.setTransient("true".equals(getModelAnnotationAttribute(modelAnnotation, "transient")));

      // Set the visibility annotations for the EstructuralFeature.
      //
      EcoreUtil.setSuppressedVisibility
        (eStructuralFeature, EcoreUtil.GET, "true".equals(getModelAnnotationAttribute(modelAnnotation, "suppressedGetVisibility")));
      EcoreUtil.setSuppressedVisibility
        (eStructuralFeature, EcoreUtil.SET, "true".equals(getModelAnnotationAttribute(modelAnnotation, "suppressedSetVisibility")));
      EcoreUtil.setSuppressedVisibility
        (eStructuralFeature, EcoreUtil.IS_SET, "true".equals(getModelAnnotationAttribute(modelAnnotation, "suppressedIsSetVisibility")));
      EcoreUtil.setSuppressedVisibility
        (eStructuralFeature, EcoreUtil.UNSET, "true".equals(getModelAnnotationAttribute(modelAnnotation, "suppressedUnsetVisibility")));
    }

    if (eTypedElement != null)
    {
      // Process the annotations.
      //
      eTypedElement.getEAnnotations().addAll(extractEAnnotations(modelAnnotation));
      if (eTypedElement instanceof EStructuralFeature)
      {
        eStructuralFeatures.add((EStructuralFeature)eTypedElement);
      }
    }

    return eTypedElement;
  }

  protected List<EGenericType> analyzeEGenericTypes(String genericTypes)
  {
    List<EGenericType> result = new ArrayList<EGenericType>();
    int depth = 0;
    int start = -1;
    int length = genericTypes.length();
    for (int i = 0; i < length; i = Character.offsetByCodePoints(genericTypes, i, 1))
    {
      int codePoint = genericTypes.codePointAt(i);
      if (codePoint == '<')
      {
        ++depth;
      }
      else if (codePoint == '>')
      {
        --depth;
      }
      else if (Character.isWhitespace(codePoint))
      {
        if (depth == 0 && start != -1)
        {
          EGenericType eGenericType = EcoreValidator.EGenericTypeBuilder.INSTANCE.buildEGenericType(genericTypes.substring(start, i));
          result.add(eGenericType);
          start = -1;
        }
      }
      else if (start == -1)
      {
        start = i;
      }
    }
    if (start != -1)
    {
      EGenericType eGenericType = EcoreValidator.EGenericTypeBuilder.INSTANCE.buildEGenericType(genericTypes.substring(start));
      result.add(eGenericType);
    }
    return result;
  }

  void analyzeTypeParameters(List<ETypeParameter> eTypeParameters, String[] typeParameters, String modelAnnotation)
  {
    if (typeParameters.length > 0)
    {
      for (String typeParameter : typeParameters)
      {
        Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseTypeParameter(typeParameter);
        if (diagnostic.getSeverity() == Diagnostic.OK)
        {
          ETypeParameter eTypeParameter = (ETypeParameter)diagnostic.getData().get(0);
          List<EGenericType> javaEBounds = new ArrayList<EGenericType>(eTypeParameter.getEBounds());
          eTypeParameter.getEBounds().clear();
          List<EGenericType> ecoreEBounds =  null;
          String bounds = getModelAnnotationAttribute(getFilteredModelAnnotations(modelAnnotation, eTypeParameter.getName()), "bounds");
          if (bounds != null)
          {
            ecoreEBounds = analyzeEGenericTypes(bounds);
          }
          match(eTypeParameter.getEBounds(), javaEBounds, ecoreEBounds);
          eTypeParameters.add(eTypeParameter);
        }
      }
    }
  }

  protected boolean isListType(String type)
  {
    type = stripTypeArguments(type);
    return "EList".equals(type) || "org.eclipse.emf.common.util.EList".equals(type) || "List".equals(type) || "java.util.List".equals(type);
  }

  protected boolean isMapType(String type)
  {
    type = stripTypeArguments(type);
    return "EMap".equals(type) || "org.eclipse.emf.common.util.EMap".equals(type) || "Map".equals(type) || "java.util.Map".equals(type);
  }

  protected void handleETypedElement(ETypedElement eTypedElement, String name, String type, String modelAnnotation, String identifierName)
  {
    eTypedElement.setName(name);
    if ("void".equals(type)) return;

    String mapType = getModelAnnotationAttribute(modelAnnotation, "mapType");
    String dataType = getModelAnnotationAttribute(modelAnnotation, "dataType");
    String modelType = getModelAnnotationAttribute(modelAnnotation, "type");
    String keyType = getModelAnnotationAttribute(modelAnnotation, "keyType");
    String valueType = getModelAnnotationAttribute(modelAnnotation, "valueType");
    String many = getModelAnnotationAttribute(modelAnnotation, "many");

    // For lists, maps, and feature maps, the default is many-valued, which can be overridden by an upper-bound declaration.
    //
    if (isListType(type) && (dataType == null || modelType != null && !isListType(modelType)))
    {
      eTypedElement.setUpperBound(-1);
      Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(type);
      if (diagnostic.getSeverity() == Diagnostic.OK)
      {
        EGenericType eGenericType = (EGenericType)diagnostic.getData().get(0);
        if (eGenericType.getETypeArguments().size() == 1)
        {
          EGenericType typeArgument = eGenericType.getETypeArguments().get(0);
          if (typeArgument.getEClassifier() != null || typeArgument.getETypeParameter() != null)
          {
            type = eGenericTypeConverter.toJavaInstanceTypeName(typeArgument);
            if (modelType == null)
            {
              modelType = type;
            }
          }  
          else
          {
            eTypedElement.setUpperBound(1);
          }
        }
      }
      if (modelType == null && !"false".equals(many) && eTypedElement.getUpperBound() != 1)
      {
        error(CodeGenEcorePlugin.INSTANCE.getString("_UI_TheTypeMustBeSpecifiedFor_message", new Object [] { identifierName }));
        modelType = "java.lang.Object";
      }
    }
    else if (mapType != null ||
              keyType != null && valueType != null ||
              "FeatureMap".equals(type) ||
              "org.eclipse.emf.common.util.FeatureMap".equals(type))
    {
      eTypedElement.setUpperBound(-1);
    }

    if (many != null)
    {
      eTypedElement.setUpperBound("true".equals(many) ? -1 : 1);
    }

    eTypedElement.setLowerBound("true".equals(getModelAnnotationAttribute(modelAnnotation, "required")) ? 1 : 0);

    String lowerBound = getModelAnnotationAttribute(modelAnnotation, "lowerBound");
    if (lowerBound == null)
    {
      lowerBound = getModelAnnotationAttribute(modelAnnotation, "lower");
    }
    if (lowerBound != null)
    {
      try
      {
        eTypedElement.setLowerBound(Integer.parseInt(lowerBound));
      }
      catch (NumberFormatException exception)
      {
        warning(JavaImporterPlugin.INSTANCE.getString("_UI_ValueOfPropertyIsBad_message", new Object [] { lowerBound, "lower", identifierName }));
      }
    }
    String upperBound = getModelAnnotationAttribute(modelAnnotation, "upperBound");
    if (upperBound == null)
    {
      upperBound = getModelAnnotationAttribute(modelAnnotation, "upper");
    }
    if (upperBound != null)
    {
      try
      {
        eTypedElement.setUpperBound(Integer.parseInt(upperBound));
      }
      catch (NumberFormatException exception)
      {
        warning(JavaImporterPlugin.INSTANCE.getString("_UI_ValueOfPropertyIsBad_message", new Object [] { upperBound, "upper", identifierName }));
      }
    }

    EGenericType ecoreEGenericType = null;
    EGenericType javaEGenericType = null;

    // The type can be augmented by specifying the it explicitly in the annotation.
    // This mostly makes sense only for many-valued typed elements, where the Java
    // type is a list and the item type needs to be specified.
    //
    if (modelType != null)
    {
      Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(modelType);
      if (diagnostic.getSeverity() == Diagnostic.OK)
      {
        javaEGenericType = (EGenericType)diagnostic.getData().get(0);
      }
      type = modelType;
    }

    if (mapType != null || keyType != null && valueType != null)
    {
      EPackage ePackage = (EPackage)EcoreUtil.getRootContainer(eTypedElement);
      EGenericType ecoreKeyEGenericType = null;
      EGenericType javaKeyEGenericType = null;
      EGenericType ecoreValueEGenericType = null;
      EGenericType javaValueEGenericType = null;
      if (mapType != null)
      {
        Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(mapType);
        if (diagnostic.getSeverity() == Diagnostic.OK)
        {
          ecoreEGenericType = (EGenericType)diagnostic.getData().get(0);
          resolve(eTypedElement, ecoreEGenericType, RequiredClassifierType.CLASS);
          List<EGenericType> mapETypeArguments = ecoreEGenericType.getETypeArguments();
          if (mapETypeArguments.size() == 2)
          {
            ecoreKeyEGenericType = mapETypeArguments.get(0);
            ecoreValueEGenericType = mapETypeArguments.get(1);
          }
          mapETypeArguments.clear();
        }
        else
        {
          ecoreEGenericType = resolve(eTypedElement, mapType, RequiredClassifierType.CLASS, false);
        }
        if (ecoreEGenericType.getERawType().getEPackage() == null)
        {
          if (keyType == null || valueType == null && isMapType(type))
          {
            diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(type);
            if (diagnostic.getSeverity() == Diagnostic.OK)
            {
              EGenericType eGenericType = (EGenericType)diagnostic.getData().get(0);
              if (eGenericType.getETypeArguments().size() == 2)
              {
                javaKeyEGenericType = eGenericType.getETypeArguments().get(0);
                javaValueEGenericType = eGenericType.getETypeArguments().get(1);
              }
            }
          }
          if (javaKeyEGenericType == null)
          {
            javaKeyEGenericType = resolve(eTypedElement, keyType == null ? "java.lang.Object" : keyType, RequiredClassifierType.NONE, false);
          }
          if (javaValueEGenericType == null)
          {
            javaValueEGenericType = resolve(eTypedElement, valueType == null ? "java.lang.Object" : valueType, RequiredClassifierType.NONE, false);
          }
        }
      }
      else
      {
        javaKeyEGenericType = resolve(eTypedElement, keyType, RequiredClassifierType.NONE, false);
        javaValueEGenericType = resolve(eTypedElement, valueType, RequiredClassifierType.NONE, false);
        ecoreEGenericType = resolveMapEntry(ePackage, javaKeyEGenericType, javaValueEGenericType);
        if (ecoreEGenericType == null)
        {
          ecoreEGenericType = 
            resolve
              (eTypedElement, 
               javaKeyEGenericType.getERawType().getName() + "To" + javaValueEGenericType.getERawType().getName() + "MapEntry", 
               RequiredClassifierType.CLASS, 
               false);
        }
      }
      
      EClass mapEClass = (EClass)ecoreEGenericType.getEClassifier();
      if (mapEClass.getEPackage() == null && mapEClass.getEStructuralFeatures().isEmpty())
      {
        eModelElementToJNodeMap.put(mapEClass, getJCompilationUnit(eTypedElement));
        mapEClass.setInstanceTypeName("java.util.Map$Entry");
        mapEClass.setAbstract(false);
        mapEClass.setInterface(false);
        if (ecoreKeyEGenericType == null)
        {
          createFeature(mapEClass, "key", javaKeyEGenericType);
          ecoreEGenericTypeToJavaEGenericTypeMap.put(javaKeyEGenericType, null);
        }
        else
        {
          createFeature(mapEClass, "key", ecoreKeyEGenericType);
          ecoreEGenericTypeToJavaEGenericTypeMap.put(ecoreKeyEGenericType, javaKeyEGenericType);
        }
        if (ecoreValueEGenericType == null)
        {
          createFeature(mapEClass, "value", javaValueEGenericType);
          ecoreEGenericTypeToJavaEGenericTypeMap.put(javaValueEGenericType, null);
        }
        else
        {
          createFeature(mapEClass, "value", ecoreValueEGenericType);
          ecoreEGenericTypeToJavaEGenericTypeMap.put(ecoreValueEGenericType, javaValueEGenericType);
        }
        EAnnotation container = ePackage.getEAnnotation(MAP_ENTRY_CLASS_CONTAINER_ANNOTATION_SOURCE);
        if (container == null)
        {
           container = EcoreFactory.eINSTANCE.createEAnnotation();
           container.setSource(MAP_ENTRY_CLASS_CONTAINER_ANNOTATION_SOURCE);
           ePackage.getEAnnotations().add(container);
        }
        container.getContents().add(mapEClass);
      }
    }
    else if (dataType != null)
    {
      Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(dataType);
      if (diagnostic.getSeverity() == Diagnostic.OK)
      {
        ecoreEGenericType = (EGenericType)diagnostic.getData().get(0);
        diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(type);
        if (diagnostic.getSeverity() == Diagnostic.OK)
        {
          javaEGenericType = (EGenericType)diagnostic.getData().get(0);
        }
      }
      else
      {
        ecoreEGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        ecoreEGenericType.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT);
        basicDiagnostic.add(diagnostic);
      }
    }
    else
    {
      Diagnostic diagnostic = EcoreValidator.EGenericTypeBuilder.INSTANCE.parseInstanceTypeName(type);
      if (diagnostic.getSeverity() == Diagnostic.OK)
      {
        javaEGenericType = (EGenericType)diagnostic.getData().get(0);
      }
      else
      {
        ecoreEGenericType = EcoreFactory.eINSTANCE.createEGenericType();
        ecoreEGenericType.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT);
        basicDiagnostic.add(diagnostic);
      }
    }

    if (ecoreEGenericType == null)
    {
      eTypedElement.setEGenericType(javaEGenericType);
      ecoreEGenericTypeToJavaEGenericTypeMap.put(javaEGenericType, null);
    }
    else
    {
      eTypedElement.setEGenericType(ecoreEGenericType);
      ecoreEGenericTypeToJavaEGenericTypeMap.put(ecoreEGenericType, javaEGenericType);
    }

    eTypedElement.setUnique(!"false".equals(getModelAnnotationAttribute(modelAnnotation, "unique")));
    eTypedElement.setOrdered(!"false".equals(getModelAnnotationAttribute(modelAnnotation, "ordered")));
  }

  protected JCompilationUnit getJCompilationUnit(EModelElement eModelElement)
  {
    for (; eModelElement != null; eModelElement = (EModelElement)eModelElement.eContainer())
    {
      JCompilationUnit compilationUnit = facadeHelper.getCompilationUnit(eModelElementToJNodeMap.get(eModelElement));
      if (compilationUnit != null)
      {
        return compilationUnit;
      }
    }
    return null;
  }

  /**
   * Strips the type arguments from the type, if it has any.
   */
  protected String stripTypeArguments(String type)
  {
    int start = type.indexOf('<');
    if (start > 0)
    {
      int end = type.lastIndexOf('>');
      if (end > start+1)
      {
        return type.substring(0, start).trim();
      }
    }
    return type;
  }

  protected EStructuralFeature createFeature(EClass eClass, String name, EGenericType eGenericType)
  {
    EClassifier eClassifier = eGenericType.getERawType();
    if (eClassifier instanceof EClass)
    {
      EReference eReference = EcoreFactory.eINSTANCE.createEReference();
      eReference.setName(name);
      eReference.setEGenericType(eGenericType);
      eClass.getEStructuralFeatures().add(eReference);
      eStructuralFeatures.add(eReference);
      return eReference;
    }
    else
    {
      EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
      eAttribute.setName(name);
      eAttribute.setEGenericType(eGenericType);
      eClass.getEStructuralFeatures().add(eAttribute);
      eStructuralFeatures.add(eAttribute);
      return eAttribute;
    }
  }

  /**
   * Creates EEnumLiteral as appropriate.
   * @return <code>true</code> when the method has created an EnumLiteral after
   * analyzing the JMember
   */
  protected boolean analyzeEnumLiteral(EEnum eEnum, JMember member)
  {
    String modelAnnotation = getModelAnnotation(member.getComment());
    if (modelAnnotation != null)
    {
      String fieldName = member.getName();

      EEnumLiteral eEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
      eModelElementToJNodeMap.put(eEnumLiteral, member);
      eEnumLiteral.getEAnnotations().addAll(extractEAnnotations(modelAnnotation));
      EcoreUtil.setDocumentation(eEnumLiteral, getModelDocumentation(member.getComment()));

      // Allow the value to be defined by an annotation or by the field's initializer
      //
      String value = getModelAnnotationAttribute(modelAnnotation, "value");
      if (value == null)
      {
        if (member instanceof JField)
        {
          String initializer = ((JField)member).getInitializer();
          if (initializer != null)
          {
            value = initializer.trim();
          }
        }
      }

      if (value != null)
      {
        try
        {
          int intValue = Integer.parseInt(value);
          eEnumLiteral.setValue(intValue);
        }
        catch (NumberFormatException exception)
        {
          warning
            (JavaImporterPlugin.INSTANCE.getString
               ("_UI_InvalidLiteralValueForField",
                new Object [] { value, eEnum.getName() + "." + fieldName }));
          eEnumLiteral.setValue(eEnum.getELiterals().size());
        }
      }
      else
      {
        eEnumLiteral.setValue(eEnum.getELiterals().size());
      }

      // Allow a mixed case version of the name to be provided.
      //
      String name = getModelAnnotationAttribute(modelAnnotation, "name");

      if (fieldName.endsWith("_VALUE") && (name == null || !name.toLowerCase().endsWith("value")))
      {
        fieldName = fieldName.substring(0, fieldName.length()-"_VALUE".length());
      }

      // But, if name doesn't expand into field name, ignore it.
      //
      if (name == null || !CodeGenUtil.format(name, '_', null, false, true).toUpperCase().equals(fieldName))
      {
          if (name != null)
          {
            warning
              (JavaImporterPlugin.INSTANCE.getString
                 ("_UI_InvalidLiteralNameForField", new Object[] { name, eEnum.getName() + "." + fieldName }));
          }
          name = fieldName;
      }
      eEnumLiteral.setName(name);

      // Allow a distinct literal value to be provided, too.
      //
      String literal = getModelAnnotationAttribute(modelAnnotation, "literal");
      if (literal != null)
      {
        eEnumLiteral.setLiteral(literal);
      }

      return eEnum.getELiterals().add(eEnumLiteral);
    }
    return false;
  }

  /**
   * The pattern for extracting the model documentation.
   */
  protected static Pattern modelDocExpression =
    Pattern.compile
      ("<!--\\s*begin-model-doc\\s*-->[ \\f\\n\\r\\t]*\\*\\s?(.*?)<!--\\s*end-model-doc\\s*-->",
       Pattern.MULTILINE | Pattern.DOTALL);

  /**
   * Returns the model documentation, or null.
   */
  protected String getModelDocumentation(String comment)
  {
    if (comment != null)
    {
      Matcher matcher = modelDocExpression.matcher(comment);
      if (matcher.find())
      {
        return comment.substring(matcher.start(1), matcher.end(1)).replaceAll("[\\n\\r]*\\s*\\*[\\s]?", "\n").replaceAll("\\s*$", "");
      }
    }

    return null;
  }

  /**
   * The pattern for extracting the @model annotations.
   */
  protected static Pattern modelAnnotationExpression =
    Pattern.compile
      ("@[ \\f\\n\\r\\t*]*model[ \\f\\n\\r\\t*]*((\\w*\\s*=\\s*(['\"])(?>\\\\.|.)*?\\3[ \\f\\n\\r\\t*]*)*)",
       Pattern.MULTILINE);

  /**
   * Returns the @model annotation contents, or null.
   */
  protected String getModelAnnotation(String comment)
  {
    if (comment != null)
    {
      Matcher matcher = modelAnnotationExpression.matcher(comment);
      if (matcher.find())
      {
        return CodeGenUtil.xmlEscapeDecode(comment.substring(matcher.start(1), matcher.end(1)));
      }
    }

    return null;
  }

  /**
   * The pattern for extracting the @extends annotations.
   */
  protected static Pattern extendsAnnotationExpression = Pattern.compile("@\\s*extends\\s*([.\\w<>,?\\s]+)", Pattern.MULTILINE);

  /**
   * The pattern for extracting the @implements annotations.
   */
  protected static Pattern implementsAnnotationExpression = Pattern.compile("@\\s*implements\\s*([.\\w<>,?\\s]+)", Pattern.MULTILINE);

  /**
   * Returns the @extends/@implements annotation contents, or null.
   */
  protected String getExtendsAnnotation(String comment)
  {
    if (comment != null)
    {
      StringBuilder result = new StringBuilder();
      Matcher extendsMatcher = extendsAnnotationExpression.matcher(comment);
      while (extendsMatcher.find())
      {
        if (result.length() != 0)
        {
          result.append(',');
        }
        result.append(comment.substring(extendsMatcher.start(1), extendsMatcher.end(1)));
      }

      Matcher implementsMatcher = implementsAnnotationExpression.matcher(comment);
      while (implementsMatcher.find())
      {
        if (result.length() != 0)
        {
          result.append(',');
        }
        result.append(comment.substring(implementsMatcher.start(1), implementsMatcher.end(1)));
      }

      return result.length() == 0 ? null : result.toString();
    }

    return null;
  }

  /**
   * Returns the unquoted value of attribute-name="value" or of attribute-name='value', or null.
   */
  protected String getModelAnnotationAttribute(String modelAnnotation, String attributeName)
  {
    Pattern modelAnnotationAttributeExpressionDoubleQuote =
      Pattern.compile
        ("\\b" + attributeName + "\\s*=\\s*([\"'])((?>\\\\.|.)*?)\\1",
         Pattern.MULTILINE);
    Matcher matcher = modelAnnotationAttributeExpressionDoubleQuote.matcher(modelAnnotation);
    if (matcher.find())
    {
      return modelAnnotation.substring(matcher.start(2), matcher.end(2));
    }
    else
    {
      return null;
    }
  }

  /**
   * Returns the space separated concatenation of the unquoted value of each attribute-name="value" or attribute-name='value' repeat, or null.
   */
  protected String getModelAnnotationAttributes(String modelAnnotation, String attributeName)
  {
    StringBuilder result = null;
    Pattern modelAnnotationAttributeExpressionDoubleQuote =
      Pattern.compile
        ("\\b" + attributeName + "\\s*=\\s*([\"'])((?>\\\\.|.)*?)\\1",
         Pattern.MULTILINE);
    for (Matcher matcher = modelAnnotationAttributeExpressionDoubleQuote.matcher(modelAnnotation); matcher.find();)
    {
      if (result == null)
      {
        result = new StringBuilder();
      }
      else
      {
        result.append(' ');
      }
      result.append(modelAnnotation.subSequence(matcher.start(2), matcher.end(2)));
    }

    return result == null ? null : result.toString();
  }

  protected static Pattern eAnnotationExpression = Pattern.compile("\\G\\s*((?>\\\\.|\\S)+)((?:\\s+(?>\\\\.|\\S)+\\s*+=\\s*(['\"])((?>\\\\.|.)*?)\\3)*)");

  protected static Pattern eAnnotationDetailExpression = Pattern.compile("\\s+((?>\\\\.|\\S)+)\\s*+=\\s*((['\"])((?>\\\\.|.)*?)\\3)");

  protected List<EAnnotation> extractEAnnotations(String modelAnnotation)
  {
    List<EAnnotation> result = Collections.emptyList();
    String annotations = getModelAnnotationAttributes(modelAnnotation, "annotation");
    if (annotations != null)
    {
      for (Matcher matcher = eAnnotationExpression.matcher(annotations); matcher.find();)
      {
        if (result == Collections.EMPTY_LIST)
        {
          result = new ArrayList<EAnnotation>();
        }
        EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
        result.add(eAnnotation);
        eAnnotation.setSource(parseString(matcher.group(1)));
        EMap<String, String> details = eAnnotation.getDetails();
        for (Matcher detailMatcher = eAnnotationDetailExpression.matcher(matcher.group(2)); detailMatcher.find();)
        {
          details.put(parseString(detailMatcher.group(1)), parseString(detailMatcher.group(4)));
        }
      }
    }

    String extendedMetaDataAnnotations = getModelAnnotationAttributes(modelAnnotation, "extendedMetaData");
    if (extendedMetaDataAnnotations != null)
    {
      if (result == Collections.EMPTY_LIST)
      {
        result = new ArrayList<EAnnotation>();
      }
      EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
      result.add(eAnnotation);
      eAnnotation.setSource(ExtendedMetaData.ANNOTATION_URI);
      EMap<String, String> details = eAnnotation.getDetails();
      for (Matcher detailMatcher = eAnnotationDetailExpression.matcher(" " + extendedMetaDataAnnotations); detailMatcher.find();)
      {
        details.put(parseString(detailMatcher.group(1)), parseString(detailMatcher.group(4)));
      }
    }

    return result;
  }

  private static String parseString(String stringLiteralBody)
  {
    return CodeGenUtil.parseString(stringLiteralBody);
  }

  /**
   * Returns the filtered matches xyzAttribute-name="value" or of xyxAttribute-name='value', or null.
   */
  protected String getFilteredModelAnnotations(String modelAnnotation, String filter)
  {
    StringBuilder result = new StringBuilder();
    Pattern modelAnnotationAttributeExpressionDoubleQuote =
      Pattern.compile
        ("\\b" + filter + "([A-Z]\\w*\\s*=\\s*([\"'])((?>\\\\.|.)*?)\\2)",
         Pattern.MULTILINE);
    int start = 0;
    int end = modelAnnotation.length();
    Matcher matcher;
    while ((matcher = modelAnnotationAttributeExpressionDoubleQuote.matcher(modelAnnotation.subSequence(start, end))).find())
    {
      result.append(modelAnnotation.substring(start + matcher.start(1), start + matcher.start(1) + 1).toLowerCase());
      result.append(modelAnnotation.substring(start + matcher.start(1) + 1, start + matcher.end(1)));
      result.append(' ');
      start += matcher.end(0);
    }
    return result.toString();
  }

  protected void match(List<EGenericType> target, List<EGenericType> javaEGenericTypes, List<EGenericType> ecoreEGenericTypes)
  {
    for (Iterator<EGenericType> i = javaEGenericTypes.iterator(), j = ecoreEGenericTypes == null ? null : ecoreEGenericTypes.iterator(); i.hasNext(); )
    {
      EGenericType javaEGenericType = i.next();
      EGenericType ecoreEGenericType = j != null && j.hasNext() ? j.next() : null;

      // If there is no Ecore representation or it's invalid, e.g., when - is used to omit a specification, then just use the Java representation.
      //
      if (ecoreEGenericType == null || ecoreEGenericType.getEClassifier() == null)
      {
        ecoreEGenericTypeToJavaEGenericTypeMap.put(javaEGenericType, null);
        target.add(javaEGenericType);
      }
      else
      {
        ecoreEGenericTypeToJavaEGenericTypeMap.put(ecoreEGenericType, javaEGenericType);
        target.add(ecoreEGenericType);
      }
    }
  }

  protected void resolve(EModelElement eModelElement, EGenericType ecoreEGenericType, EGenericType javaEGenericType)
  {
    EClassifier eClassifier = ecoreEGenericType.getERawType();
    if (eClassifier.getEPackage() == null)
    {
      if (javaEGenericType != null)
      {
        ETypeParameter eTypeParameter = javaEGenericType.getETypeParameter();
        if (eTypeParameter != null)
        {
          eClassifier = null;
          ecoreEGenericType.setEClassifier(null);
          ecoreEGenericType.setETypeParameter(eTypeParameter);
        }
        else
        {
          eClassifier.setInstanceTypeName
            (ecoreEGenericType.getETypeArguments().isEmpty() ?
               eGenericTypeConverter.toJavaInstanceTypeName(javaEGenericType) :
               javaEGenericType.getEClassifier().getInstanceTypeName());
        }
      }

      if (eClassifier != null)
      {
        EPackage ePackage = (EPackage)EcoreUtil.getRootContainer(eModelElement);
        addToPackage(ePackage, eClassifier);
        if (eClassifier instanceof EClass)
        {
          for (EStructuralFeature eStructuralFeature : ((EClass)eClassifier).getEStructuralFeatures())
          {
            resolve(eStructuralFeature, eStructuralFeature.getEGenericType(), (EGenericType)null);
          }
        }
      }
    }
    EGenericType ecoreEUpperBound = ecoreEGenericType.getEUpperBound();
    if (ecoreEUpperBound != null)
    {
      resolve(eModelElement, ecoreEUpperBound, javaEGenericType == null ? null : javaEGenericType.getEUpperBound());
    }
    else
    {
      EGenericType ecoreELowerBound = ecoreEGenericType.getELowerBound();
      if (ecoreELowerBound != null)
      {
        resolve(eModelElement, ecoreELowerBound, javaEGenericType == null ? null : javaEGenericType.getELowerBound());
      }
    }

    if (eClassifier != null)
    {
      int index = 0;
      for (Iterator<EGenericType>
             i = ecoreEGenericType.getETypeArguments().iterator(),
             j = javaEGenericType == null ? null : javaEGenericType.getETypeArguments().iterator();
           i.hasNext();
           ++index)
      {
        EGenericType eTypeArgument = i.next();
        EGenericType eTypeArgumentInstance = j != null && j.hasNext() ? j.next() : null;
        if (demandCreatedEClassifiers.contains(eClassifier) && eClassifier.getETypeParameters().size() <= index)
        {
          ETypeParameter eTypeParameter = EcoreFactory.eINSTANCE.createETypeParameter();
          eTypeParameter.setName(index == 0? "T"  : "T" + index);
          eClassifier.getETypeParameters().add(eTypeParameter);
        }
        resolve(eModelElement, eTypeArgument, eTypeArgumentInstance);
      }
    }
  }

  protected void resolve(EModelElement eModelElement, EGenericType eGenericType, RequiredClassifierType requiredClassifierType)
  {
    EClassifier eClassifier = eGenericType.getEClassifier();
    if (eClassifier != null)
    {
      if (eClassifier.eContainer() == null)
      {
        EGenericType resolvedEGenericType = resolve(eModelElement, eClassifier.getInstanceTypeName(), requiredClassifierType, false);
        eGenericType.setETypeParameter(resolvedEGenericType.getETypeParameter());
        eGenericType.setEClassifier(resolvedEGenericType.getEClassifier());
      }
      else if (eClassifier.getEPackage() == null)
      {
        EGenericType resolvedEGenericType = resolve(eModelElement, eClassifier.getName(), requiredClassifierType, false);
        if (resolvedEGenericType.getERawType().getEPackage() == null)
        {
          EPackage ePackage = (EPackage)EcoreUtil.getRootContainer(eClassifier);
          addToPackage(ePackage, eClassifier);
        }
        else
        {
          eGenericType.setETypeParameter(null);
          eGenericType.setEClassifier(resolvedEGenericType.getEClassifier());
        }
      }
      for (EGenericType eTypeArgument : eGenericType.getETypeArguments())
      {
        resolve(eModelElement, eTypeArgument, RequiredClassifierType.NONE);
      }
    }
    else
    {
      EGenericType eUpperBound = eGenericType.getEUpperBound();
      if (eUpperBound != null)
      {
        resolve(eModelElement, eUpperBound, RequiredClassifierType.NONE);
      }
      else
      {
        EGenericType eLowerBound = eGenericType.getELowerBound();
        if (eLowerBound != null)
        {
          resolve(eModelElement, eLowerBound, RequiredClassifierType.NONE);
        }
      }
    }
  }

  protected enum RequiredClassifierType
  {
    NONE
    {
      @Override
      boolean isValid(EClassifier eClassifier)
      {
        return eClassifier != null;
      }
    },
    CLASS
    {
      @Override
      boolean isValid(EClassifier eClassifier)
      {
        return eClassifier instanceof EClass;
      }
    },
    DATA_TYPE
    {
      @Override
      boolean isValid(EClassifier eClassifier)
      {
        return eClassifier instanceof EDataType;
      }
    };

    abstract boolean isValid(EClassifier eClassifier);
  }

  protected EGenericType resolve(EModelElement eModelElement, String typeName, RequiredClassifierType requiredClassifierType, boolean recordDemandCreatedEClassifier)
  {
    EPackage ePackage = (EPackage)EcoreUtil.getRootContainer(eModelElement);

    // We want to resolve to this.
    //
    EGenericType eGenericType = null;

    // Check if it is exactly the name of a type parameter that's in scope.
    //
    for (EObject eObject = eModelElement; eObject != null; eObject = eObject.eContainer())
    {
      EList<ETypeParameter> eTypeParameters = null;
      if (eObject instanceof EOperation)
      {
        eTypeParameters = ((EOperation)eObject).getETypeParameters();
      }
      else if (eObject instanceof EClass)
      {
        eTypeParameters = ((EClass)eObject).getETypeParameters();
      }
      // If we've found thing with type parameters, search them for a match.
      //
      if (eTypeParameters != null)
      {
        for (ETypeParameter localETypeParameter : eTypeParameters)
        {
          if (typeName.equals(localETypeParameter.getName()))
          {
            // If we find a match, make this generic type be for this type parameter,
            // and then stop the whole process.
            eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setETypeParameter(localETypeParameter);
            return eGenericType;
          }
        }
      }
    }

    // Check if the name is qualified
    //
    String baseName = typeName;
    String packageName = "";
    int index = baseName.lastIndexOf(".");

    // For arrays, consider the element type.
    //
    int i = baseName.indexOf('[');
    String elementTypeName = i == -1 ? baseName : baseName.substring(0, i);

    if (index == -1)
    {
      // Look through the imports of the containing compilation unit.
      //
      JCompilationUnit compilationUnit = getJCompilationUnit(eModelElement);
      if (compilationUnit != null)
      {
        boolean firstType = true;
        LOOP:
        for (JType jType : facadeHelper.getChildren(compilationUnit, JType.class))
        {
          String name = jType.getName();
          if (elementTypeName.equals(name))
          {
            packageName = facadeHelper.getPackage(compilationUnit).getQualifiedName();
            typeName = packageName + "." + baseName;
            break;
          }
          else if (firstType)
          {
            for (JAbstractType jAbstractType : facadeHelper.getChildren(jType, JAbstractType.class))
            {
              if (!(jAbstractType instanceof JAnnotationType))
              {
                String nestedName = jAbstractType.getName();
                if (elementTypeName.equals(nestedName))
                {
                  packageName = facadeHelper.getPackage(compilationUnit).getQualifiedName();
                  typeName = packageName + "." + name + '$' + baseName;
                  break LOOP;
                }
              }
            }
            firstType = false;
          }
        }

        // Find an explicit import or the first wildcard import.
        //
        if (packageName.length() == 0)
        {
          boolean firstWildcard = true;
          for (JImport jImport : facadeHelper.getChildren(compilationUnit, JImport.class))
          {
            String importName = jImport.getName();
            if (importName.endsWith("." + elementTypeName))
            {
              int importIndex = importName.lastIndexOf(".");
              packageName = importName.substring(0, importIndex);
              typeName = packageName + "." + baseName;
              break;
            }
            else if (firstWildcard && importName.endsWith(".*"))
            {
              int importIndex = importName.lastIndexOf(".");
              packageName = importName.substring(0, importIndex);
              typeName = packageName + "." + baseName;
              firstWildcard = false;
            }
          }
        }

        // Find the modeled package for the import and look up the name there.
        //
        EPackage otherEPackage = packageNameToEPackageMap.get(packageName);
        if (otherEPackage == null)
        {
          otherEPackage = externalPackageNameToEPackageMap.get(packageName);
        }
        if (otherEPackage != null)
        {
          EClassifier eClassifier = otherEPackage.getEClassifier(baseName);
          if (requiredClassifierType.isValid(eClassifier))
          {
            eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassifier);
          }
        }
      }

      // If we can't find it, try the simple name in the package...
      //
      if (eGenericType == null && "".equals(packageName))
      {
        EClassifier eClassifier = ePackage.getEClassifier(baseName);
        if (requiredClassifierType.isValid(eClassifier))
        {
          eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(eClassifier);
        }
        else if (i != -1 && CodeGenUtil.isJavaLangType(elementTypeName))
        {
          packageName = "java.lang";
          typeName = packageName + "." + baseName;
        }
      }
    }
    else
    {
      // Find the modeled package for the name and look up the name there.
      //
      packageName = baseName.substring(0, index);
      if (eModelElement.eContainer() instanceof EClassifier &&
            packageName.equals(((EClassifier)eModelElement.eContainer()).getName()))
      {
        JCompilationUnit compilationUnit = getJCompilationUnit(eModelElement);
        if (compilationUnit != null)
        {
          typeName = packageName;
          packageName = facadeHelper.getPackage(compilationUnit).getQualifiedName();
          typeName = packageName + "." + typeName + "$" + baseName.substring(index + 1);
        }
      }
      else
      {
        baseName = baseName.substring(index + 1);
        EPackage otherEPackage = packageNameToEPackageMap.get(packageName);
        if (otherEPackage == null)
        {
          otherEPackage = externalPackageNameToEPackageMap.get(packageName);
          if (otherEPackage == null)
          {
            if ("org.eclipse.emf.ecore".equals(packageName))
            {
              otherEPackage = EcorePackage.eINSTANCE;
            }
            else if ("org.eclipse.emf.ecore.xml.type".equals(packageName))
            {
              otherEPackage = XMLTypePackage.eINSTANCE;
            }
            else if ("org.eclipse.emf.ecore.xml.namespace".equals(packageName))
            {
              otherEPackage = XMLNamespacePackage.eINSTANCE;
            }
          }
        }
        if (otherEPackage != null)
        {
          EClassifier eClassifier = otherEPackage.getEClassifier(baseName);
          if (eClassifier != null)
          {
            eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(eClassifier);
          }
        }
      }
    }

    // If we still don't have one, we'll have to settle for an EDataType or EClass with an instance class name.
    //
    if (eGenericType == null)
    {
      // See if we already have the EDataType.
      //
      String modifiedName = typeName.replace('$', '.');
      for (EClassifier ePackageClassifier : ePackage.getEClassifiers())
      {
        String name = ePackageClassifier.getInstanceTypeName();
        if (name != null &&
              name.replace('$', '.').equals(modifiedName) &&
              requiredClassifierType.isValid(ePackageClassifier))
        {
          eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
          eGenericType.setEClassifier(ePackageClassifier);
          break;
        }
      }
    }

    if (eGenericType == null &&
          EcorePackage.Literals.EOBJECT.getInstanceTypeName().equals(typeName) &&
          requiredClassifierType != RequiredClassifierType.DATA_TYPE)
    {
      eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      eGenericType.setEClassifier(EcorePackage.Literals.EOBJECT);
    }

    // Just to be helpful, we'll recognize a type of org.eclipse.emf.ecore.util.FeatureMap and convert it to EFeatureMapEntry.
    // This way a dataType need not be specified. But, we won't do this if recordDemandCreateEDataType is false, so we don't
    // change the instanceClass of a new EDataType that's implicitly being defined for FeatureMap.
    //
    if (eGenericType == null &&
          EcorePackage.Literals.EFEATURE_MAP.getInstanceTypeName().equals(typeName) &&
          requiredClassifierType != RequiredClassifierType.CLASS)
    {
      eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      eGenericType.setEClassifier(EcorePackage.Literals.EFEATURE_MAP_ENTRY);
    }

    // If we don't have one yet, maybe it's one of the special types...
    //
    if (eGenericType == null &&
          requiredClassifierType != RequiredClassifierType.CLASS &&
          (packageName.length() == 0 ||
             packageName.equals("java.lang") ||
             packageName.equals("java.math") ||
             packageName.equals("java.util")))
    {
      for (EClassifier ecoreEClassifier : EcorePackage.eINSTANCE.getEClassifiers())
      {
        if (ecoreEClassifier instanceof EDataType)
        {
          String instanceClassName = ecoreEClassifier.getInstanceClassName();
          if ((instanceClassName.equals(typeName) || instanceClassName.equals("java.lang." + typeName)) && 
                (ecoreEClassifier.getETypeParameters().isEmpty() ||
                    ecoreEClassifier == EcorePackage.Literals.EJAVA_CLASS))
          {
            eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
            eGenericType.setEClassifier(ecoreEClassifier);
            break;
          }
        }
      }
    }

    // If we still don't have one, we'll have to settle for a demand created EDataType or EClass,
    // so create a new EClassifier with a nice unique name.
    //
    if (eGenericType == null)
    {
      // If the name isn't qualified, it might be a primitive or from java.lang. Otherwise, assume it's in the current
      // package and use the nsPrefix for the qualified package name.
      //
      boolean primitive = false;
      String instanceTypeName = typeName;
      if (packageName.length() == 0)
      {
        if (CodeGenUtil.isJavaPrimitiveType(elementTypeName))
        {
          primitive = true;
        }
        else if (CodeGenUtil.isJavaLangType(elementTypeName))
        {
          packageName = "java.lang";
          instanceTypeName = packageName + "." + instanceTypeName;
        }
        else
        {
          packageName = ePackage.getNsPrefix();
          instanceTypeName = packageName + '.' + instanceTypeName;
        }
      }

      EClassifier eClassifier = null;
      if (!"java.util.Map$Entry".equals(instanceTypeName))
      {
        for (EClassifier demandCreatedEClassifier : demandCreatedEClassifiers)
        {
          if (requiredClassifierType.isValid(demandCreatedEClassifier) && 
                instanceTypeName.equals(demandCreatedEClassifier.getInstanceTypeName()))
          {
            eClassifier = demandCreatedEClassifier;
          }
        }
      }

      if (eClassifier == null)
      {
        if (requiredClassifierType == RequiredClassifierType.CLASS)
        {
          EClass eClass = EcoreFactory.eINSTANCE.createEClass();
          eClass.setAbstract(true);
          eClass.setInterface(true);
          eClassifier = eClass;
        }
        else
        {
          eClassifier = EcoreFactory.eINSTANCE.createEDataType();
        }
        demandCreatedEClassifiers.add(eClassifier);
  
        eClassifier.setInstanceTypeName(instanceTypeName);
  
        // Even primitives should be represented by a data type with a conventional (i.e. capitalized) name.
        //
        String name = baseName;
        if (primitive && name.length() > 0)
        {
          name = CodeGenUtil.capName(name);
        }
  
        // Make array names legal.
        //
        while (name.endsWith("[]"))
        {
          name = name.substring(0, name.length() - 2) + "Array";
        }
        
        eClassifier.setName(name);

        if (recordDemandCreatedEClassifier)
        {
          addToPackage(ePackage, eClassifier);
        }
      }

      eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
      eGenericType.setEClassifier(eClassifier);
    }

    return eGenericType;
  }
  
  protected void addToPackage(EPackage ePackage, EClassifier eClassifier)
  {
    String baseName = eClassifier.getName();
    String name = baseName;
    
    // Avoid classifier name collisions.
    //
    for (int j = 1; ePackage.getEClassifier(name) != null; ++j)
    {
      name = baseName + "_" + j;
    }
    
    eClassifier.setName(name);
    ePackage.getEClassifiers().add(eClassifier);
  }

  protected EGenericType resolveMapEntry(EPackage ePackage, EGenericType keyEGenericType, EGenericType valueEGenericType)
  {
    for (EClassifier ePackageClassifier : ePackage.getEClassifiers())
    {
      if (ePackageClassifier instanceof EClass &&
            ("java.util.Map.Entry".equals(ePackageClassifier.getInstanceClassName()) || "java.util.Map$Entry".equals(ePackageClassifier.getInstanceClassName())))
      {
        EClass mapEntryInterface = (EClass)ePackageClassifier;
        EStructuralFeature keyFeature = mapEntryInterface.getEStructuralFeature("key");
        if (keyFeature != null && EcoreUtil.equals(keyFeature.getEGenericType(), keyEGenericType) && !keyFeature.isMany())
        {
          EStructuralFeature valueFeature = mapEntryInterface.getEStructuralFeature("value");
          if (valueFeature != null && EcoreUtil.equals(valueFeature.getEGenericType(), valueEGenericType) && !valueFeature.isMany())
          {
            EGenericType result = EcoreFactory.eINSTANCE.createEGenericType();
            result.setEClassifier(mapEntryInterface);
            return result;
          }
        }
      }
    }

    return null;
  }

  protected <T extends ENamedElement> void sort(EList<T> namedElements, final Map<Object, Integer> nameToIDMap)
  {
    Collection<T> ordered = new TreeSet<T>(new Comparator<T>()
      {
        @Override
        public boolean equals(Object object)
        {
          return object == this;
        }

        public int compare(T firstObject, T secondObject)
        {
          int firstValue = getOrderingValue(firstObject, nameToIDMap);
          int secondValue = getOrderingValue(secondObject, nameToIDMap);
          return firstValue - secondValue;
        }
      });
    ordered.addAll(namedElements);
    int index = 0;
    for (T eNamedElement : ordered)
    {
      namedElements.move(index++, eNamedElement);
    }
  }

  protected int getOrderingValue(ENamedElement eNamedElement, Map<Object, Integer> nameToIDMap)
  {
    Integer result = nameToIDMap.get(eNamedElement);
    if (result == null)
    {
      if (eNamedElement instanceof EClassifier)
      {
        String prefix = ePackageToPrefixMap.get(eNamedElement.eContainer());
        String name = eNamedElement.getName();
        String id = CodeGenUtil.format(name, '_', prefix, true, true).toUpperCase();
        result = nameToIDMap.get(id);
      }
      else
      {
        String prefix = ePackageToPrefixMap.get(eNamedElement.eContainer().eContainer());
        String eClassName = ((ENamedElement)eNamedElement.eContainer()).getName();
        String eFeatureName = eNamedElement.getName();
        String id = CodeGenUtil.format(eClassName, '_', prefix, true, true).toUpperCase() + "__"
          + CodeGenUtil.format(eFeatureName, '_', prefix, true, false).toUpperCase();
        result = nameToIDMap.get(id);
      }
      if (result != null)
      {
        nameToIDMap.put(eNamedElement, result);
      }
    }

    if (result != null)
    {
      return result;
    }

    return Integer.MAX_VALUE;
  }

  protected <T extends ENamedElement> T getNamedElement(List<T> namedElements, String name)
  {
    if (name != null)
    {
      for (T namedElement : namedElements)
      {
        if (name.equals(namedElement.getName()))
        {
          return namedElement;
        }
      }
    }
    return null;
  }

  /**
   * Returns the diagnostic.
   * @return the status.
   */
  public Diagnostic getDiagnostic()
  {
    return basicDiagnostic;
  }

  /**
   * Returns the generator model.
   * @return the generator model.
   */
  public GenModel getGenModel()
  {
    return genModel;
  }

  /**
   * Returns the list of used GenPackages.
   * @return the list of used GenPackages.
   */
  public Collection<GenPackage> getUsedGenPackages()
  {
    return usedGenPackages;
  }

  /**
   * Produces another IStatus in the MultiStatus.
   * @param message a description of the error.
   */
  protected void error(String message)
  {
    System.err.println("-->Error: " + message);
    basicDiagnostic.add(new BasicDiagnostic(Diagnostic.ERROR, JavaImporterPlugin.getPlugin().getBundle().getSymbolicName(), 0, message, null));
  }

  /**
   * Produces another IStatus in the MultiStatus, with warning severity.
   * @param message a description of the error.
   */
  protected void warning(String message)
  {
    System.err.println("-->Warning: " + message);
    basicDiagnostic.add(new BasicDiagnostic(Diagnostic.WARNING, JavaImporterPlugin.getPlugin().getBundle().getSymbolicName(), 0, message, null));
  }
}
