/**
 * 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.List;
import java.util.Map;

import org.eclipse.emf.common.notify.AdapterFactory;

/**
 * A factory for adapters that perform code generation for a {@link Generator}.
 * 
 * <p>A single <code>GeneratorAdapterFactory</code> is associated with an EMF or Java package, and can return an adapter
 * that implements {@link GeneratorAdapter} for instances of any or all of the classes within that package. Multiple
 * factories can be associated can be associated with a single package, with all of their adapters contributing
 * functionality to code generation for an object.
 * 
 * <p>A type of <code>GeneratorAdapterFactory</code> is registered, via a {@link Descriptor descriptor}, against a
 * package ID in a {@link Descriptor.Registry registry}, in order to create adapters for that package. Typically, this
 * is done via the <code>org.eclipse.emf.codegen.ecore.generatorAdapters</code> extension point, using an
 * <code>adapterFactory</code> element.
 * 
 * <p>A <code>GeneratorAdapterFactory</code> is a factory for type <code>GeneratorAdapter.class</code>; that is, its
 * {@link org.eclipse.emf.common.notify.AdapterFactory#isFactoryForType(Object) isFactoryForType(Object)} method should
 * return <code>true</code> for this type. However, because EMF's adapter framework supports only one adapter of a given
 * type on any object and because each of many generator adapter factories needs to be able to have a generator on a
 * single object, <code>GeneratorAdapter.class</code> cannot be used as the type of the adapters. Instead, each
 * generator adapter factory should use itself as the type of its adapters.
 *
 * <p>This interface is typically implemented by extending either {@link AbstractGeneratorAdapterFactory} or the
 * generated adapter factory for a package. The latter approach provides efficient, package-specific type switching for
 * <code>createAdapter(Notifier)</code>, while the former provides simple implementations for several other required
 * methods.
 * 
 * <p>There is also a generic implementation provided by the framework that can create a single adapter for just one
 * type of object. This implementation is used under the covers for adapters registered via the above extension point
 * with an <code>adapter</code> element.
 * 
 * @since 2.2.0
 */
public interface GeneratorAdapterFactory extends AdapterFactory
{
  /**
   * In essence, a factory for generator adapter factories. <code>Descriptor</code>s are stored in a
   * {@link Registry Registry} and create a particular type of {@link GeneratorAdapterFactory}, providing an additional
   * level of indirection that allows multiple {@link Generator}s to share a single registry and supports
   * extension-point-based registration with deferred class loading.
   */
  public interface Descriptor
  {
    /**
     * Creates and returns an instance of a particular type of <code>GeneratorAdapterFactory</code>. A new instance
     * should be returned on each invocation. 
     */
    GeneratorAdapterFactory createAdapterFactory();

    /**
     * A registry of {@link GeneratorAdapterFactory.Descriptor Descriptor}s. Descriptors are keyed by package ID, and
     * multiple descriptors can be associated with each package ID.
     */
    public interface Registry
    {
      /**
       * The global generator adapter factory descriptor registry.
       */
      public static final Registry INSTANCE = new DelegatingRegistry();

      /**
       * Returns the descriptors registered against the given package ID.
       */
      Collection<Descriptor> getDescriptors(String packageID);

      /**
       * Registers the given descriptor against the specified package ID.
       * @return whether the descriptor was added to the collection of descriptors for that package ID.
       */
      boolean addDescriptor(String packageID, Descriptor descriptor);

      /**
       * Removes the given descriptor from the collection registered against the given package ID.
       * @return whether the descriptor was deregistered (this should be true if it was previously registered against
       * that package ID).
       */
      boolean removeDescriptor(String packageID, Descriptor descriptor);

      /**
       * Deregisters all descriptors for a given package ID.
       * @return whether any descriptors were deregistered (this should be true if any descriptor was previously
       * registered against it).  
       */
      boolean removeDescriptors(String packageID);

      /**
       * Clears the registry of all descriptor registrations.
       */
      void clear();
    }

    /**
     * A simple <code>Registry</code> implementation, in which {@link #getDescriptors(String) getDescriptors(String)}
     * can delegate to another <code>Registry</code> if no descriptors are locally registered against the given package
     * ID. This implementation does not prevent duplicate registrations.
     */
    public static class DelegatingRegistry implements Registry
    {
      protected Registry delegateRegistry;
      protected Map<String, List<Descriptor>> map = new HashMap<String, List<Descriptor>>();

      public DelegatingRegistry()
      {
        super();
      }

      public DelegatingRegistry(Registry delegateRegistry)
      {
        this.delegateRegistry = delegateRegistry;
      }

      public Collection<Descriptor> getDescriptors(String packageID)
      {
        List<Descriptor> descriptors = getDescriptors(packageID, false);
        return descriptors != null && !descriptors.isEmpty() ? new ArrayList<Descriptor>(descriptors) : delegatedGetDescriptors(packageID);
      }

      protected Collection<Descriptor> delegatedGetDescriptors(String packageID)
      {
        return delegateRegistry != null ? delegateRegistry.getDescriptors(packageID) : Collections.<Descriptor>emptyList();
      }

      public boolean addDescriptor(String packageID, Descriptor descriptor)
      {
        return getDescriptors(packageID, true).add(descriptor);
      }

      public boolean removeDescriptor(String packageID, Descriptor descriptor)
      {
        List<Descriptor> list = getDescriptors(packageID, false);
        if (list != null)
        {
          return list.remove(descriptor);
        }
        return false;
      }

      public boolean removeDescriptors(String packageID)
      {
        List<Descriptor> list = getDescriptors(packageID, false);
        if (list != null && !list.isEmpty())
        {
          map.remove(packageID);
          return true;
        }
        return false;
      }

      public void clear()
      {
        map.clear();
      }

      protected List<Descriptor> getDescriptors(String packageID, boolean forceCreate)
      {
        List<Descriptor> result = map.get(packageID);
        if (result == null && forceCreate)
        {
          result = new ArrayList<Descriptor>();
          map.put(packageID, result);
        }
        return result;
      }
    }
  }

  /**
   * Returns the <code>Generator</code> associated with this adapter factory.
   * 
   * @see #setGenerator(Generator)
   */
  Generator getGenerator();

  /**
   * Sets the <code>Generator</code> associated with this adapter factory.
   * 
   * @see #getGenerator()
   */
  void setGenerator(Generator generator);

  /**
   * Performs initialization for the given model-level input object. Typically, this involves setting
   * {@link Generator#getOptions() options} on the associated {@link Generator}.
   */
  void initialize(Object input);

  /**
   * Disposes this adapter factory and all of the adapters it has created.
   */
  void dispose();
}
