// $Id: MetaSequenceArray.java,v 1.5 1998/02/07 07:17:16 oliva Exp $

package BR.unicamp.Guarana;

import java.util.Enumeration;

/** A SequentialComposer that maintains a set of MetaObjects in a
    MetaObject array.

    @author Alexandre Oliva
    @version $Revision: 1.5 $ */
public class MetaSequenceArray extends SequentialComposer {
  /** Stores the metaObjects this composer delegates to.

      <p>This array is unmodifiable.  It is only possible to replace
      the array completely, but not to change its elements.

      <p>Subclasses willing to obtain a consistent snapshot of the
      array should invoke getMetaObjectsArray() or getMetaObjects(), or
      synchronize on both the Composer itself and on the array.

      @see getMetaObjectsArray
      @see Composer#getMetaObjects  */
  private MetaObject[] metaObjects;

  /** Stores the OperationFactory argument of the last invocation of
      initialize.  For multi-object Composers, this is usually
      meaningless.  */
  private OperationFactory myOperationFactory;

  /** Creates a Composer that will sequentially delegate operations
      and messages to the elements of a clone of metaObjects.

      @param metaObjects the set of MetaObjects the Composer should
      delegate to.  */
  public MetaSequenceArray(final MetaObject[] metaObjects) {
    this.metaObjects = (MetaObject[])metaObjects.clone();
  }

  /** Obtains the MetaObjects this composer delegates to, ordered as
      they receive Operations.

      @return an Enumeration whose elements correspond to the elements
      of the array of metaObjects.  */
  public Enumeration getMetaObjects() {
    // intentionally hide global metaObjects, to prevent race conditions
    final MetaObject[] metaObjects = this.metaObjects;
    return new Enumeration() {
      private int i = 0;
      public boolean hasMoreElements() {
	return i < metaObjects.length;
      }
      public Object nextElement() {
	return metaObjects[i];
      }
    };
  }

  /** Obtains a copy of the array of MetaObjects this Composer
      delegates to.

      @return a copy of the array of MetaObjects this Composer
      delegates to.  */
  public synchronized MetaObject[] getMetaObjectsArray() {
    return (MetaObject[])metaObjects.clone();
  }

  /** Establishes a new set of MetaObjects for this Composer to
      delegate to.

      The caller must Initialize all the elements of the new Array
      before invoking this method, and must Release all the elements
      of the original array after this method returns.

      @param metaObjects the new array of MetaObjects.  */
  public synchronized void
    setMetaObjectsArray(final MetaObject[] metaObjects) {
    this.metaObjects = metaObjects;
  }

  /** Provides a MetaObject for newObject, to be used at the point the
      current Composer is in object's meta-configuration.

      <p>Subclasses should specialize this method so that, instead of
      creating a new MetaSequenceArray, a specialized instance is
      created too, if this is intended.  They may also specialize
      method makeMetaObjectsFor, which is inherited from
      SequentialComposer.

      @param newObject the Object a new Composer should be provided
      for.

      @param object the Object whose meta-configuration is begin
      requested to create the newObject's meta-configuration.

      @return if makeMetaObjectsFor returns null, so does this method.
      If the returned array has length 1, the only element of the
      array is returned.  Otherwise, a new SequentialComposer is
      created to delegate to the returned array of MetaObjects.

      @see SequentialComposer#makeMetaObjectsFor */
  public MetaObject configure(final Object newObject, final Object object) {
    MetaObject[] rs = makeMetaObjectsFor(newObject, object, metaObjects);
    if (rs == null)
      return null;
    if (rs.length == 1)
      return rs[0];
    return new MetaSequenceArray(rs);
  }

  /** Obtain an OperationFactory to be given to the specified
      MetaObject to create Operations for the given Object.

      This method just returns the last OperationFactory it was
      initialized with.  If this Composer is to be associated with
      multiple Objects, or more restrictive OperationFactories should
      be given to MetaObjects, this method must be overridden.

      A subclass might prefer, for security reasons, to check whether
      the MetaObject is actually in the MetaObject array, but this
      method is usually called to initialize the MetaObject
      <b>before</b> it is inserted in the array.  In this case, it may
      return null or a very restricted OperationFactory.  This should
      usually work, whenever Guarana.reconfigure is invoked, it will
      distribute new OperationFactories.  However, if reconfigure is
      called internally, instead of from Guarana.reconfigure, or if
      some MetaObject above the current Composer in the MetaObject
      hierarchy decides not to distribute the new OperationFactory,
      the newly associated MetaObject may remain with a restricted
      OperationFactory.

      @param metaObject the MetaObject an OperationFactory should be
      provided for.

      @param object the base-level Object that should be the target of
      Operations created by the returned OperationFactory.

      @return the OperationFactory from the last invocation of
      initialize.  This must be specialized for multi-object
      MetaObjects.  */
  protected OperationFactory getOperationFactoryFor
    (final MetaObject metaObject, final Object object) {
    return myOperationFactory;
  }
			   

  /** Delegates reconfiguration requests to component MetaObjects.

      This method always returns itself (unless the array becomes
      empty or contains a single element), which means it will not
      accept to be replaced.  Subclasses may change this behavior.

      It delegates the reconfigure request to each component
      MetaObject.  If the oldMetaObject is any of them, replace it
      with the value it returns.  If the returned value is different
      from the MetaObject itself, the returned MetaObject is
      initialized before it is stored in the array of MetaObjects, and
      the original one is released after that.  If the returned
      MetaObject was null, the array is shrinked, so that it does not
      contain null elements.  If the array ever becomes empty, this
      method returns null.  If the array every contains a single
      element, this method returns that element.

      @param object the Object whose meta-configuration should be
      affected.

      @param oldMetaObject that MetaObject that may be replaced.

      @param newMetaObject the candidate MetaObject to replace it.

      @return this, unless the reconfiguration resulted an empty
      array.  */
  public synchronized MetaObject reconfigure(final Object object,
					     final MetaObject oldMetaObject,
					     final MetaObject newMetaObject) {
    MetaObject[] metaObjects = this.metaObjects;
    for(int i = 0, l = metaObjects.length; i < l; ++i) {
      final MetaObject
	oldm = metaObjects[i],
	newm = oldm.reconfigure(object, oldMetaObject, newMetaObject);
      if (newm != oldm) {
	if (newm != null) {
	  newm.initialize(getOperationFactoryFor(newm, object), object);
	  metaObjects[i] = newm;
	} else {
	  final MetaObject[]
	    newMetaObjects = new MetaObject[l-1];
	  for(int j = 0; j < i; ++j)
	    newMetaObjects[j] = metaObjects[j];
	  for(int j = i+1; j < l; ++j)
	    newMetaObjects[j-1] = metaObjects[j];
	  setMetaObjectsArray(metaObjects = newMetaObjects);
	}
	oldm.release(object);
      }
    }
    switch (metaObjects.length) {
    case 0:
      return null;
    case 1:
      return metaObjects[0];
    default:
      return this;
    }
  }

  /** Saves a copy of the OperationFactory for later use, and invokes
      the same method in the base class.

      @param opf the OperationFactory that will be returned by
      getOperationFactoryFor until initialize is invoked again.

      @param object is ignored. */
  public void initialize(final OperationFactory opf,
			 final Object object) {
    myOperationFactory = opf;
    super.initialize(opf, object);
  }
}
