/**
 * Copyright (c) 2006 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 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.codegen.ecore.generator;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.codegen.ecore.CodeGenEcorePlugin;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.util.GenModelUtil;
import org.eclipse.emf.codegen.merge.java.JControlModel;
import org.eclipse.emf.codegen.merge.java.facade.FacadeHelper;
import org.eclipse.emf.codegen.util.CodeGenUtil;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.plugin.EcorePlugin;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;


/**
 * An extensible code generator that delegates its work to model-specific {@link GeneratorAdapter adapters} created
 * provided by an {@link GeneratorAdapterFactory adapter factory}.
 *
 * <p>Adapter factories are obtained for model objects from a particular EMF or Java package by consulting a 
 * {@link GeneratorAdapterFactory.Descriptor.Registry registry}, which can be local to the generator or globally
 * shared. Local registries typically {@link GeneratorAdapterFactory.Descriptor.DelegatingRegistry delegate} to the
 * global registry, overriding the adapter factories returned for certain packages. Global registration is usually
 * done via the <code>org.eclipse.emf.codegen.ecore.generatorAdapters</code> extension point.
 * 
 * <p>Here is a typical usage example, where we generate code for a standard GenModel-decorated Ecore model:
 * 
 * <pre>
 *   // Globally register the default generator adapter factory for GenModel
 *   // elements (only needed in stand-alone).
 *   // 
 *   GeneratorAdapterFactory.Descriptor.Registry.INSTANCE.addDescriptor
 *     (GenModelPackage.eNS_URI, GenModelGeneratorAdapterFactory.DESCRIPTOR);
 * 
 *   // Create the generator and set the model-level input object.
 *   // 
 *   Generator generator = new Generator();
 *   generator.setInput(genModel);
 * 
 *   // Generator model code.
 *   //
 *   generator.generate
 *     (genModel, GenBaseGeneratorAdapter.MODEL_PROJECT_TYPE,
 *      new BasicMonitor.Printing(System.out));
 * </pre>
 * 
 * <p>
 * The adapter factories for the input object handle initializing the generator's {@link #getOptions() options}, and the
 * generator walks the tree of objects defined by the relevant adapters, invoking code generation for each one.
 *
 * @since 2.2.0
 */
public class Generator
{
  private static final boolean SYSOUT_BEGIN_END = false;
  
  /**
   * A set of code generation options that should be shared among the generator, adapter factories and adapters.
   * Additional options may be added to this class in the future.
   */
  public static class Options
  {
    /**
     * A filename redirection pattern for generated files.
     */
    public String redirectionPattern;

    /**
     * Whether to overwrite read-only files.
     */
    public boolean forceOverwrite;

    /**
     * Whether to try to use dynamically compiled templates, in place of supplied static templates.
     */
    public boolean dynamicTemplates;

    /**
     * A path for dynamic templates: ordered list of URIs corresponding to base locations under which to find templates.
     * A single path for all code generation is actually insufficient. This path needs to specified and extended on a
     * per-adapter basis, so this field should not be used.
     * @deprecated org.eclipse.emf.codegen.ecore 2.2.2 Override {@link AbstractGeneratorAdapter#addBaseTemplatePathEntries(List)} instead. 
     * @see AbstractGeneratorAdapter#addBaseTemplatePathEntries(List)
     */
    @Deprecated
    public String[] templatePath;

    /**
     * The name of the {@link org.eclipse.emf.codegen.merge.java.facade.FacadeHelper facade helper} class to be used in
     * Java merging.
     * @see org.eclipse.emf.codegen.merge.java.facade.FacadeHelper
     */
    public String mergerFacadeHelperClass;

    /**
     * The URI of the JMerge rules file.
     */
    public String mergeRulesURI;

    /**
     * Whether Eclipse JDT code formatting should be applied to generated Java code.
     */
    public boolean codeFormatting;

    /**
     * Whether Eclipse JDT code formatting should be applied to comments.
     * @since 2.8
     */
    public boolean commentFormatting;

    /**
     * Whether Eclipse JDT unused import checking should be used to remove unused imports from generated Java code.
     * @since 2.9
     */
    public boolean importOrganizing;

    /**
     * Whether registered cleanup actions should be applied to the generated source code.
     * @since 2.10
     */
    public boolean cleanup;

    /**
     * Code formatter options to be used instead of the defaults for Java code formatting.
     */
    public Map<?, ?> codeFormatterOptions;

    /**
     * The resource set containing the input, from which a URI converter, package registry, resource factory registry,
     * etc can be obtained.
     */  
    public ResourceSet resourceSet;

    /**
     * A list of strings of the form [&lt;variable-name>=]&lt;plugin-id>
     * that will be used by {@link AbstractGeneratorAdapter#addClasspathEntries(org.eclipse.emf.codegen.jet.JETEmitter)}
     * to add classpath entries to each JETEmitter.
     * @see AbstractGeneratorAdapter#addClasspathEntries(org.eclipse.emf.codegen.jet.JETEmitter)
     */
    public List<String> templateClasspath;

    /**
     * Arbitrary data for extensibility.
     */
    public Object[] data;

    public Options()
    {
      super();
    }
  }

  /**
   * The local registry from which generator adapter factories are created.
   */
  protected GeneratorAdapterFactory.Descriptor.Registry adapterFactoryDescriptorRegistry;

  /**
   * The cached set of generator adapter factories for this generator, keyed by package ID.
   */
  protected Map<String, Collection<GeneratorAdapterFactory>> packageIDToAdapterFactories;

  protected Object input;
  protected Options options;

  /**
   * Whether the adapter factories for the input need to be called to initialize before generating code.
   * 
   * @see #requestInitialize()
   */
  protected boolean initializeNeeded = true;

  protected JControlModel jControlModel;

  /**
   * @since 2.10
   */
  protected Set<URI> generatedOutputs = new LinkedHashSet<URI>();

  /**
   * Creates a generator that delegates directly to the {@link GeneratorAdapterFactory.Descriptor.Registry#INSTANCE global}
   * adapter factory descriptor registry.
   * 
   * @see GeneratorAdapterFactory.Descriptor.Registry#INSTANCE
   */
  public Generator()
  {
    super();
  }

  /**
   * Creates a generator that delegates to the given adapter factory descriptor registry.
   */
  public Generator(GeneratorAdapterFactory.Descriptor.Registry adapterFactoryDescriptorRegistry)
  {
    this.adapterFactoryDescriptorRegistry = adapterFactoryDescriptorRegistry;
  }

  /**
   * Returns the current model-level input object.
   * 
   * @see #setInput(Object)
   */
  public Object getInput()
  {
    return input;
  }

  /**
   * Sets the model-level input object, and invokes {@link GeneratorAdapterFactory#initialize(Object) initialize(Object)}
   * on any of its adapter factories. This initialization will also be repeated in the next
   * {@link #generate(Object, Object, Monitor)} or {@link #generate(Object, Object, String, Monitor)}, when the full
   * tree of objects and adapters has been discovered.
   * 
   * @see #getInput()
   * @see #generate(Object, Object, Monitor)
   * @see #generate(Object, Object, String, Monitor)
   * @see GeneratorAdapterFactory#initialize(Object)
   */
  public void setInput(Object input)
  {
    this.input = input;
    initialize();
    initializeNeeded = true;
  }

  /**
   * Signals that {@link GeneratorAdapterFactory#initialize(Object) initialize(object)} should be called on any adapter
   * factories for the {@link #getInput() input} object during the next {@link #generate(Object, Object, Monitor)} or
   * {@link #generate(Object, Object, String, Monitor)}, when the full tree of objects and adapters has been discovered.
   * 
   * @see #getInput()
   * @see #generate(Object, Object, Monitor)
   * @see #generate(Object, Object, String, Monitor)
   * @see GeneratorAdapterFactory#initialize(Object)
   */
  public void requestInitialize()
  {
    initializeNeeded = true;
  }

  /**
   * Invokes {@link GeneratorAdapterFactory#initialize(Object) initialize(object)} on each adapter factory for the
   * {@link #getInput() input} object. Note that adapter factory order is non-deterministic, so if more than
   * one tries to set the same options, the result is undefined.
   * 
   * @see #getInput()
   * @see GeneratorAdapterFactory#initialize(Object)
   */
  protected void initialize()
  {
    for (GeneratorAdapterFactory adapterFactory : getAdapterFactories(input))
    {
      adapterFactory.initialize(input);
    }
  }

  /**
   * Returns the {@link Options} for this generator. Note that these are accessible to (and can be set by) all of the
   * adapter factories and adapters supporting this generator and by clients of the generator itself.
   */
  public Options getOptions()
  {
    if (options == null)
    {
      options = new Options();
    }
    return options;
  }

  protected Set<String> badFacadeHelperClasses = new HashSet<String>();

  /**
   * Returns a {@link org.eclipse.emf.codegen.merge.java.JControlModel JControlModel} that the generator's adapters can
   * use for merging. It is initialized with the {@link Options#mergerFacadeHelperClass facade helper class} and
   * {@link Options#mergeRulesURI merge rules URI} specified in the {@link #getOptions() options}.
   * 
   * @see #getOptions()
   * @see Options#mergerFacadeHelperClass
   * @see Options#mergeRulesURI
   */
  public JControlModel getJControlModel()
  {
    if (jControlModel == null)
    {
      jControlModel = new JControlModel();
    }

    String facadeHelperClass = options.mergerFacadeHelperClass;
    if (!badFacadeHelperClasses.contains(facadeHelperClass) && 
          (jControlModel.getFacadeHelper() == null || !jControlModel.getFacadeHelper().getClass().getName().equals(facadeHelperClass)))
    {
      FacadeHelper facadeHelper = CodeGenUtil.instantiateFacadeHelper(facadeHelperClass); 
      if (facadeHelper == null)
      {
        badFacadeHelperClasses.add(facadeHelperClass);
      }
      else 
      {
        if (input instanceof GenModel)
        {
          GenModel genModel = (GenModel)input;
          switch (genModel.getComplianceLevel())
          {
            case JDK14_LITERAL:
            {
              facadeHelper.setCompilerCompliance(JavaCore.VERSION_1_4);
              break;
            }
            case JDK50_LITERAL:
            {
              facadeHelper.setCompilerCompliance(JavaCore.VERSION_1_5); 
              break;
            }
            case JDK60_LITERAL: 
            {
              facadeHelper.setCompilerCompliance(JavaCore.VERSION_1_6); 
              break;
            }
            case JDK70_LITERAL: 
            {
              facadeHelper.setCompilerCompliance(JavaCore.VERSION_1_7); 
              break;
            }
            case JDK80_LITERAL:
            {
              facadeHelper.setCompilerCompliance("1.8"); 
              break;
            }
            case JDK90_LITERAL:
            {
              facadeHelper.setCompilerCompliance("9"); 
              break;
            }
            case JDK100_LITERAL:
            {
              facadeHelper.setCompilerCompliance("10"); 
              break;
            }
            case JDK110_LITERAL:
            {
              facadeHelper.setCompilerCompliance("11"); 
              break;
            }
            case JDK120_LITERAL:
            {
              facadeHelper.setCompilerCompliance("12"); 
              break;
            }
            case JDK130_LITERAL:
            {
              facadeHelper.setCompilerCompliance("13"); 
              break;
            }
            case JDK140_LITERAL:
            {
              facadeHelper.setCompilerCompliance("14"); 
              break;
            }
            case JDK150_LITERAL:
            {
              facadeHelper.setCompilerCompliance("15"); 
              break;
            }
          }

          if (genModel.isCodeFormatting())
          {
            jControlModel.setLeadingTabReplacement(null);
            jControlModel.setConvertToStandardBraceStyle(false);
          }
          else
          {
            Map<String, String> options = GenModelUtil.getJavaOptions(genModel);
            String tabSize = options.get(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE);
            String braceStyle = options.get(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_TYPE_DECLARATION);
            String tabCharacter = options.get(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR);
            if (JavaCore.TAB.equals(tabCharacter))
            {
              jControlModel.setLeadingTabReplacement("\t");
            }
            else
            {
              String spaces = "";
              for (int i = Integer.parseInt(tabSize); i > 0; --i)
              {
                spaces += " ";
              }
              jControlModel.setLeadingTabReplacement(spaces);
            }
            jControlModel.setConvertToStandardBraceStyle(DefaultCodeFormatterConstants.END_OF_LINE.equals(braceStyle));
          }
        }
      }
      jControlModel.initialize(facadeHelper, options.mergeRulesURI);
    }
    return jControlModel;
  }

  /**
   * Returns the generator's adapter factory descriptor registry.
   */
  public GeneratorAdapterFactory.Descriptor.Registry getAdapterFactoryDescriptorRegistry()
  {
    if (adapterFactoryDescriptorRegistry == null)
    {
      adapterFactoryDescriptorRegistry =
        new GeneratorAdapterFactory.Descriptor.DelegatingRegistry(GeneratorAdapterFactory.Descriptor.Registry.INSTANCE);
    }
    return adapterFactoryDescriptorRegistry;
  }

  /**
   * Returns the generator adapter factories for a given object. The {@link #getAdapterFactoryDescriptorRegistry() registry}
   * for the generator is used to obtain the {@link GeneratorAdapterFactory.Descriptor descriptors} for the object's
   * {@link #getPackageID(Object) package ID}, and those descriptors are used to create the adapter factories, which are
   * cached.
   * 
   * @see #getAdapterFactoryDescriptorRegistry()
   * @see #getPackageID(Object)
   * @see GeneratorAdapterFactory.Descriptor
   */
  protected Collection<GeneratorAdapterFactory> getAdapterFactories(Object object)
  {
    if (packageIDToAdapterFactories == null)
    {
      packageIDToAdapterFactories = new HashMap<String, Collection<GeneratorAdapterFactory>>();
    }

    String packageID = getPackageID(object);
    Collection<GeneratorAdapterFactory> result = packageIDToAdapterFactories.get(packageID);
    if (result == null)
    {
      Collection<GeneratorAdapterFactory.Descriptor> descriptors = getAdapterFactoryDescriptorRegistry().getDescriptors(packageID);
      result = new ArrayList<GeneratorAdapterFactory>(descriptors.size());
      for (GeneratorAdapterFactory.Descriptor descriptor : descriptors)
      {
        GeneratorAdapterFactory adapterFactory = descriptor.createAdapterFactory();
        adapterFactory.setGenerator(this);
        result.add(adapterFactory);
      }
      packageIDToAdapterFactories.put(packageID, result);
    }
    return result;
  }

  /**
   * Returns the package ID for the given object.
   *
   * <p>This implementation returns the {@link org.eclipse.emf.ecore.EPackage#getNsURI() EPackage nsURI} for an
   * {@link org.eclipse.emf.ecore.EObject}, and the Java {@link java.lang.Package#getName() package name} for any other
   * object.
   * 
   * @see org.eclipse.emf.ecore.EPackage#getNsURI()
   * @see java.lang.Package#getName()
   */
  protected String getPackageID(Object object)
  {
    return object instanceof EObject ? ((EObject)object).eClass().getEPackage().getNsURI() : object.getClass().getPackage().getName();
  }

  /**
   * Returns the generator adapters for the given object. The adapter factories are obtained from
   * {@link #getAdapterFactories(Object)}, and each may provide one adapter of type
   * {@link GeneratorAdapter GeneratorAdapter.class}.
   * 
   * @see #getAdapterFactories(Object)
   * @see GeneratorAdapter
   */
  protected Collection<GeneratorAdapter> getAdapters(Object object)
  {
    Collection<GeneratorAdapterFactory> adapterFactories = getAdapterFactories(object);
    List<GeneratorAdapter> result = new ArrayList<GeneratorAdapter>(adapterFactories.size());

    for (AdapterFactory adapterFactory : adapterFactories)
    {
      if (adapterFactory.isFactoryForType(GeneratorAdapter.class))
      {
        Object adapter = adapterFactory.adapt(object, GeneratorAdapter.class);
        if (adapter != null)
        {
          result.add((GeneratorAdapter)adapter);
        }
      }
    }
    return result;
  }

  private static class GeneratorData
  {
    public Object object;
    public GeneratorAdapter adapter;

    public GeneratorData(Object object, GeneratorAdapter adapter)
    {
      this.object = object;
      this.adapter = adapter;
    }
  }

  private GeneratorData[] getGeneratorData(Object object, Object projectType, boolean forGenerate)
  {
    // Since we're invoking plugged-in code, we must be defensive against cycles.
    //
    Set<Object> objects = new HashSet<Object>();

    // Compute the GeneratorData for the given object and its children, then for the parents of the given object.
    //

    List<GeneratorData> childrenData = getGeneratorData(object, projectType, forGenerate, true, false, objects);
    List<GeneratorData> parentsData = getGeneratorData(object, projectType, forGenerate, false, true, objects);

    // Combine the two lists.
    //
    List<GeneratorData> result = new ArrayList<GeneratorData>(parentsData.size() + childrenData.size());
    Collections.reverse(parentsData);
    result.addAll(parentsData);
    result.addAll(childrenData);
    return result.toArray(new GeneratorData[result.size()]);
  }

  private List<GeneratorData> getGeneratorData(Object object, Object projectType, boolean forGenerate, boolean forChildren, boolean skipFirst, Set<Object> objects)
  {
    List<Object> result  = new ArrayList<Object>();
    result.add(object);

    for (int i = 0; i < result.size(); skipFirst = false)
    {
      Object o = result.get(i);

      Collection<GeneratorAdapter> adapters = getAdapters(o);
      result.remove(i);
      if (!adapters.isEmpty())
      {
        for (GeneratorAdapter adapter : adapters)
        {
          if (forChildren)
          {
            Collection<?> children = forGenerate ? adapter.getGenerateChildren(o, projectType) : adapter.getCanGenerateChildren(o, projectType);
            for (Object child : children)
            {
              if (objects.add(child))
              {
                result.add(child);
              }
            }
          }
          else
          {
            Object parent = forGenerate ? adapter.getGenerateParent(o, projectType) : adapter.getCanGenerateParent(o, projectType);
            if (parent != null && objects.add(parent))
            {
              result.add(parent);
            }
          }

          if (!skipFirst)
          {
            result.add(i++, new GeneratorData(o, adapter));
          }
        }
      }
    }
    
    @SuppressWarnings("unchecked")
    List<GeneratorData> list = (List<GeneratorData>)(List<?>)result;
    return list;
  }

  /**
   * Returns whether code can be generated for the given object and project type.
   * A project type is represented by an arbitrary object that is meaningful to the generator adapters for the relevant
   * objects.
   *
   * <p>This result is obtained as follows:
   * 
   * <ol>
   * <li>The {@link GeneratorAdapter adapters} for the object are obtained.
   * <li>A complete collection of objects to be considered is formed by iteratively invoking the
   *     {@link GeneratorAdapter#getCanGenerateParent(Object, Object) getCanGenerateParent(Object, Object)} and
   *     {@link GeneratorAdapter#getCanGenerateChildren(Object, Object) getCanGenerateChildren(Object, Object)} methods
   *     on the adapters for the object, the adapters for the object's parent and children, and so on. It is the
   *     adapters' responsibility to determine the relevant objects through their implementations of these methods.
   * <li>The {@link GeneratorAdapter#canGenerate(Object, Object) canGenerate(Object, Object)} method is invoked on all
   *     the adapters for every object in the set formed in step 2. If any adapter returns <code>true</code> for any
   *     object, this method returns <code>true</code>.
   * <li>Otherwise, <code>false</code> is returned.
   * </ol>
   * 
   * @see GeneratorAdapter#getCanGenerateParent(Object, Object)
   * @see GeneratorAdapter#getCanGenerateChildren(Object, Object)
   * @see GeneratorAdapter#canGenerate(Object, Object)
   */
  public boolean canGenerate(Object object, Object projectType)
  {
    GeneratorData[] data = getGeneratorData(object, projectType, false);
    for (int i = 0; i < data.length; i++)
    {
      if (data[i].adapter.canGenerate(data[i].object, projectType))
      {
        return true;
      }
    }
    return false;
  }

  /**
   * Performs code generation for the given object and project type. A project type is represented by an arbitrary object
   * that is meaningful to the generator adapters for the relevant objects. Since this is a long-running operation, it
   * reports progress using the given <code>Monitor</code>. Its final status is returned as a <code>Diagnostic</code>.
   * 
   * <p>This method operates exactly as {@link #generate(Object, Object, String, Monitor)} does when null is specified
   * as the <code>projectTypeName</code>.
   * 
   * <p>It is not necessary to call {@link #canGenerate(Object, Object)} before this method. If that method would 
   * return <code>false</code>, this method should generate nothing when invoked with the same arguments.
   * 
   * @see #generate(Object, Object, String, Monitor)
   * @see #canGenerate(Object, Object)
   */
  public Diagnostic generate(Object object, Object projectType, Monitor monitor)
  {
    return generate(object, projectType, null, monitor);
  }

  /**
   * Performs code generation for the given object and project type. A project type is represented by an arbitrary object
   * that is meaningful to the generator adapters for the relevant objects. Since this is a long-running operation, it
   * reports progress using the given <code>Monitor</code>. Its final status is returned as a <code>Diagnostic</code>.
   * The <code>projectTypeName</code>, if non-null, is used only to provide to the <code>Monitor</code> a more specific
   * message for the task.
   * 
   * <p>It is not necessary to call {@link #canGenerate(Object, Object)} before this method. If that method would 
   * return <code>false</code>, this method should generate nothing when invoked with the same arguments.
   * 
   * <p>Code generation is performed as follows:
   * 
   * <ol>
   * <li>The {@link GeneratorAdapter adapters} for the object are obtained.
   * <li>A complete collection of objects to be considered is formed by iteratively invoking the
   *     {@link GeneratorAdapter#getGenerateParent(Object, Object) getGenerateParent(Object, Object)} and
   *     {@link GeneratorAdapter#getGenerateChildren(Object, Object) getGenerateChildren(Object, Object)} methods on the
   *     adapters for the object, the adapters for the object's parent and children, and so on. It is the adapters'
   *     responsibility to determine the relevant objects through their implementations of these methods.
   * <li>If this is the first invocation of {@link #generate(Object, Object, String, Monitor)} or initialization has
   *     been {@link #requestInitialize() requested}, {@link GeneratorAdapterFactory#initialize(Object) initialize(Object)}
   *     is invoked on each adapter factory for the {@link #getInput() input} object.
   * <li>The {@link GeneratorAdapter#preGenerate(Object, Object) preGenerate(Object, Object)} method is invoked on all
   *     the adapters for every object in the set formed in step 2, giving the adapters a chance to perform setup
   *     before any code is generated.
   * <li>The {@link GeneratorAdapter#generate(Object, Object, Monitor) generate(Object, Object, Monitor)} method is
   *     invoked on all the adapters for every object in the set formed in step 2. This is where code generation
   *     actually occurs.
   * <li>The {@link GeneratorAdapter#postGenerate(Object, Object) postGenerate(Object, Object)} method is invoked on all
   *     the adapters for every object in the set formed in step 2, giving adapters a chance to clean up from code
   *     generation.
   * </ol>
   * 
   * <p>The operation may be canceled during step 4 or 5, either based on the <code>Monitor</code> or the
   * <code>Diagnostic</code> returned by any generator adapter invocation. By default, only a <code>CANCEL</code>
   * {@link org.eclipse.emf.common.util.Diagnostic#getSeverity severity} will cause code generation to stop; however,
   * this can be customized by overriding {@link #canContinue(Diagnostic) canContinue(Diagnostic)}. Even if code
   * generation is canceled, {@link GeneratorAdapter#postGenerate(Object, Object) postGenerate(Object, Object)} will
   * still be called on all the adapters on which {@link GeneratorAdapter#preGenerate(Object, Object) preGenerate(Object, Object)}
   * has been called.
   * 
   * @see #canGenerate(Object, Object)
   * @see #requestInitialize()
   * @see GeneratorAdapter#getGenerateParent(Object, Object)
   * @see GeneratorAdapter#getGenerateChildren(Object, Object)
   * @see GeneratorAdapter#preGenerate(Object, Object)
   * @see GeneratorAdapter#generate(Object, Object, Monitor)
   * @see GeneratorAdapter#postGenerate(Object, Object)
   * @see GeneratorAdapterFactory#initialize(Object)
   */
  public Diagnostic generate(Object object, Object projectType, String projectTypeName, Monitor monitor)
  {
    if (SYSOUT_BEGIN_END) System.out.println("******* Begin: " + new java.util.Date());
    try
    {
      String message = projectTypeName != null ?
        CodeGenEcorePlugin.INSTANCE.getString("_UI_Generating_message", new Object[] { projectTypeName }) :
        CodeGenEcorePlugin.INSTANCE.getString("_UI_GeneratingCode_message");
      BasicDiagnostic result = new BasicDiagnostic(CodeGenEcorePlugin.ID, 0, message, null);

      GeneratorData[] data = getGeneratorData(object, projectType, true);
      monitor.beginTask("", data.length + 2);
      monitor.subTask(message);

      // Initialization is deferred until adapters are attached to all the objects of interest and we're
      // about to ask them to generate.
      //
      if (initializeNeeded)
      {
        initializeNeeded = false;
        initialize();
      }

      // Give all generator adapters the chance to do setup work.
      //
      int preIndex = 0;
      for (; preIndex < data.length && canContinue(result); preIndex++)
      {
        result.add(data[preIndex].adapter.preGenerate(data[preIndex].object, projectType));
      }
      monitor.worked(1);

      // Invoke generator adapters for each object.
      //
      for (int i = 0; i < data.length && canContinue(result); i++)
      {
        result.add(data[i].adapter.generate(data[i].object, projectType, CodeGenUtil.createMonitor(monitor, 1)));
        if (monitor.isCanceled())
        {
          result.add(Diagnostic.CANCEL_INSTANCE);
        }
      }

      // Give all generator adapters the chance to do tear down.
      //
      for (int i = 0; i < preIndex; i++)
      {
        result.add(data[i].adapter.postGenerate(data[i].object, projectType));
      }

      // Optionally invoke any source cleanup actions.
      // This is only possible if JDT and JDT UI are available.
      //
      if (getOptions().cleanup && CommonPlugin.IS_RESOURCES_BUNDLE_AVAILABLE && !generatedOutputs.isEmpty() && jControlModel != null && jControlModel.getFacadeHelper() != null)
      {
        EclipseHelper.sourceCleanup(generatedOutputs);
      }

      return result;
    }
    finally
    {
      monitor.done();
      if (SYSOUT_BEGIN_END) System.out.println("******* End: " + new java.util.Date());
    }
  }

  /**
   * Clients are not expect to implement this interface.
   * It can only be implemented if the JDT UI is available, because source cleanup actions are implemented there.
   * @since 2.10
   */
  public interface CleanupScheduler
  {
    void schedule(Set<ICompilationUnit> compilationUnits);
  }

  private static class EclipseHelper
  {
    private static final CleanupScheduler SCHEDULER;
    static
    {
      CleanupScheduler cleanupScheduler = null;
      try
      {
        Class<?> generatorUIUtilClass = CommonPlugin.loadClass("org.eclipse.emf.codegen.ecore.ui", "org.eclipse.emf.codegen.ecore.genmodel.presentation.GeneratorUIUtil");
        cleanupScheduler = (CleanupScheduler)generatorUIUtilClass.getField("CLEANUP_SCHEDULER").get(null);
      }
      catch (Exception exception)
      {
        // Ignore
      }

      SCHEDULER = cleanupScheduler;
    }

    public static void sourceCleanup(final Set<URI> generatedOutputs)
    {
      if (SCHEDULER != null)
      {
        IWorkspaceRoot workspaceRoot = EcorePlugin.getWorkspaceRoot();
        if (workspaceRoot != null)
        {
          Set<ICompilationUnit> compilationUnits = new LinkedHashSet<ICompilationUnit>();
          for (URI generatedOutput : generatedOutputs)
          {
            if ("java".equals(generatedOutput.fileExtension()))
            {
              IFile file = workspaceRoot.getFile(new Path(generatedOutput.toString()));
              ICompilationUnit compilationUnit = JavaCore.createCompilationUnitFrom(file);
              if (compilationUnit.getJavaProject().isOnClasspath(compilationUnit))
              {
                compilationUnits.add(compilationUnit);
              }
            }
          }

          SCHEDULER.schedule(compilationUnits);
        }
      }
    }
  }

  /**
   * Determines whether code generation can continue, based on the given <code>Diagnostic</code>. This implementation
   * returns true unless the {@link org.eclipse.emf.common.util.Diagnostic#getSeverity severity} is
   * <code>CANCEL</code>.
   * 
   * @see org.eclipse.emf.common.util.Diagnostic#getSeverity()
   */
  protected boolean canContinue(Diagnostic diagnostic)
  {
    return diagnostic.getSeverity() != Diagnostic.CANCEL;
  }

  /**
   * A {@link AbstractGeneratorAdapter#createOutputStream(URI) callback} indicating that output was generated for the given workspace path.
   * @see #getGeneratedOutputs()
   * @see AbstractGeneratorAdapter#createOutputStream(URI)
   * @since 2.10
   */
  public void generatedOutput(URI workspacePath)
  {
    generatedOutputs.add(workspacePath);
  }

  /**
   * Returns the workspace paths for which outputs have been {@link AbstractGeneratorAdapter#createOutputStream(URI) generated}.
   * @since 2.10
   * @see #generatedOutput(URI)
   * @see AbstractGeneratorAdapter#createOutputStream(URI)
   */
  public Set<URI> getGeneratedOutputs()
  {
    return generatedOutputs;
  }

  /**
   * Disposes all of the generator's adapter factories, by calling {@link GeneratorAdapterFactory#dispose() dispose()}
   * on each.
   * 
   * @see GeneratorAdapterFactory#dispose()
   */
  public void dispose()
  {
    if (packageIDToAdapterFactories != null)
    {
      for (Collection<GeneratorAdapterFactory> adapterFactories : packageIDToAdapterFactories.values())
      {
        for (GeneratorAdapterFactory adapterFactory : adapterFactories)
        {
          adapterFactory.dispose();
        }
      }
    }
  }
}
