// $Id: SequentialComposer.java,v 1.6 1998/02/07 07:17:17 oliva Exp $

package BR.unicamp.Guarana;

import java.util.Enumeration;

/** Implements a Composer that sequentially requests MetaObjects to
    handle Operations.

    @author Alexandre Oliva
    @version $Revision: 1.6 $  */
public abstract class SequentialComposer extends Composer {
  /** Creates a Composer that will sequentially delegate operations
      and messages to a set of MetaObjects that is obtained by invoking
      getMetaObjectsArray or getMetaObjects, depending on the
      Operation.  */
  public SequentialComposer() {}
  
  /** Asks each MetaObject in the array returned by
      getMetaObjectsArray() to handle the Operation.  If any MetaObject
      provides a Result for the Operation, it is presented to the
      remaining MetaObjects as Handle(Result) does.  If any replacement
      Operation is provided, the last one is returned.

      <p>If a MetaObject provides a Result for an Operation, the
      previous MetaObjects are asked to handle the Result (even the
      ones that asked for no further notice).  If any of these
      provides another Result, this Result replaces any previously
      returned one.  The last Result produced is finally returned.

      @param operation the Operation to be delegated to MetaObjects.

      @param object the target Object of the Operation.

      @return a Result for the Operation, if one was produced by any
      MetaObject, or a replacement Operation, if one was produced by
      any MetaObject, or a Result request, if any MetaObject asked to
      read or modify the Result.  If both read and modify requests
      were made, a modify request is returned.  */
  public Result handle(Operation operation, final Object object) {
    int finmode = Result.noResultMode;
    // intentionally hide global metaObjects, to prevent race conditions
    final MetaObject[] metaObjects = getMetaObjectsArray();
    for(int i = 0, l = metaObjects.length; i < l; ++i) {
      Result res = null;
      try {
	res = metaObjects[i].handle(operation, object);
      } catch (Throwable t) {
	res = Result.throwObject(new MetaException(t), operation);
      }
      if (res != null) {
	Operation nop = res.getOperation();
	if (nop.replaced(operation))
	  operation = nop;
	else
	  continue;
	int mode = res.getMode();
	if ((mode & Result.resultValueMask) != 0) {
	  while (i-- > 0) {
	    Result nres;
	    try {
	      nres = metaObjects[i].handle(res, object);
	    } catch (Throwable t) {
	      nres = Result.throwObject(new MetaException(t), operation);
	    }
	    if (nres != null &&
		(nres.getMode() & Result.resultValueMask) != 0) {
	      nop = nres.getOperation();
	      if (nop != null && nop.replaced(operation)) {
		operation = nop;
		res = nres;
	      }
	    }
	  }
	  return res;
	}
	final int newmode = res.getMode() & Result.resultRequestMask;
	if (newmode > finmode)
	  finmode = newmode;
      }
    }
    return Result.operation(operation, finmode);
  }

  /** Asks each MetaObject in the array returned by
      getMetaObjectsArray(), from last to first, to handle the Result.
      If any of them returns a non-null Result, the returned Result
      replaces the one provided, even if the MetaObject had not
      requested to modify it.

      @param res the Result to be presented to the MetaObjects.

      @param object the target Object of the Operation the Result
      refers to.

      @return the last Result returned by a MetaObject, or the original
      Result, if every MetaObject returned null.  */
  public Result handle(Result res, final Object object) {
    final MetaObject[] metaObjects = getMetaObjectsArray();
    for(int i = metaObjects.length; i-- > 0;) {
      Result nres;
      try {
	nres = metaObjects[i].handle(res, object);
      } catch (Throwable t) {
	nres = Result.throwObject(new MetaException(t),
				  res.getOperation());
      }
      if (nres != null &&
	  (nres.getMode() & Result.resultValueMask) != 0) {
	final Operation op = nres.getOperation();
	if (op != null && op.replaced(res.getOperation()))
	  res = nres;
      }
    }
    return res;
  }

  /** Delegates the Message to all MetaObjects.

      @param message the Message to be broadcasted.

      @param object the Object the Message refers to.  */
  public void handle(final Message message, final Object object) {
    final MetaObject[] metaObjects = getMetaObjectsArray();
    for(int i = 0, l = metaObjects.length; i < l; ++i) {
      metaObjects[i].handle(message, object);
    }
  }

  /** Asks each MetaObject to provide a MetaObject to occupy its place
      in a new Object's meta-configuration.  The returned array is
      ensured not to contain any null element, since it is packed, and
      the array length is determined after each MetaObject is
      consulted.  MetaObjects that return null have their positions
      removed.  If no MetaObject is created, a null array (not an empty
      one) is returned.

      <p>This operation is intended to be used by subclasses, so that
      they can override Config without having to reimplement the array
      construction funcionality.

      @param newObject the Object MetaObjects should be created for.

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

      @param metaObjects the list of MetaObjects to be requested to
      create MetaObjects for the new Object's meta-configuration.

      @return a packed array of MetaObjects to be associated with the
      given Object, or null, if no MetaObjects are needed.  */
  protected MetaObject[] makeMetaObjectsFor(final Object newObject,
					    final Object object,
					    final MetaObject[] metaObjects) {
    int count = 0;
    final int l = metaObjects.length;
    for(int i = 0; i < l; ++i) {
      MetaObject r;
      metaObjects[i] = r = metaObjects[i].configure(newObject, object);
      if (r != null)
	++count;
    }
    if (count == 0)
      return null;
    final MetaObject[] newMetaObjects = new MetaObject[count];
    for(int i = 0, j = 0; i < l && j < count; ++i) {
      final MetaObject r = metaObjects[i];
      if (r != null)
	newMetaObjects[j++] = r;
    }
    return newMetaObjects;
  }

  /** Delegates reconfiguration requests to component MetaObjects.
      This method should check whether oldMetaObject is itself; if it
      is, consider whether it should be replaced or not, and return an
      appropriate MetaObject.  A reasonable default for a
      SequentialComposer is to insert the newMetaObject at some point
      in the MetaObject sequence.

      If the oldMetaObject is not itself, it should delegate the
      reconfigure request to each MetaObject and, if the oldMetaObject
      is any of them, replace it with the value it returns.

      If an Operation is being currently handled, this method should
      try to avoid modifying the set of MetaObjects the Operation
      would be delegated to.

      @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 a MetaObject to take its place.  If oldMetaObject is not
      this, just return this.  */
  public abstract MetaObject reconfigure(final Object object,
					 final MetaObject oldMetaObject,
					 final MetaObject newMetaObject);

  /** Delegates the initialization to all MetaObjects obtained with
      getMetaObjects().

      @param factory the Operation Factory to be used to create
      Operations for that Object.

      @param object the Object MetaObjects should become able to handle.  */
  public synchronized void initialize(final OperationFactory factory,
				      final Object object) {
    for(final Enumeration metaObjects = getMetaObjects();
	metaObjects.hasMoreElements();)
      ((MetaObject)metaObjects.nextElement()).initialize(factory, object);
  }

  /** Delegates the release information to all MetaObjects obtained
      with getMetaObjects().

      @param object the Object that should no longer be handled.  */
  public synchronized void release(final Object object) {
    for(final Enumeration metaObjects = getMetaObjects();
	metaObjects.hasMoreElements();)
      ((MetaObject)metaObjects.nextElement()).release(object);
  }
}
