// $Id: SequentialComposer.java,v 1.9 1998/06/08 10:14:28 oliva Exp $

/* Copyright 1997,1998 Alexandre Oliva <oliva@dcc.unicamp.br>
 *
 * This file is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package BR.unicamp.Guarana;

import java.util.Enumeration;

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

    @author Alexandre Oliva
    @version $Revision: 1.9 $  */
public class SequentialComposer extends Composer {
  /** 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 SequentialComposer(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;
  }

  /** Asks each MetaObject in the metaObjects array 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(final Operation operation,
		       final Object object) {
    final MetaObject[] metaObjects = this.metaObjects;
    return handle(operation, object,
		  metaObjects, 0, 1, metaObjects.length);
  }

  /** Asks MetaObjects in the given array to handle the Operation.
      The first MetaObject that gets the Operation is the one whose
      index is <tt>begin</tt>.  Then, <tt>increment</tt> is added to
      <tt>begin</tt> to compute the index of the next element of the
      array, and so on, until the computed index is exactly equal to
      <tt>end</tt>.

      <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.

      @param metaObjects an array of MetaObjects.
      
      @param begin the index of the first element of the array that
      should be presented the Operation.  This is usually 0.

      @param increment the value added to an index to compute the
      index of the next element that will be presented an Operation.
      This is usually 1.

      @param end the index of the first element of the array that will 
      not be presented the Operation.  This is usually
      <tt>metaObjects.length</tt>.

      @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 static Result handle(final Operation op, final Object object,
			      final MetaObject[] metaObjects,
			      final int begin, final int increment,
			      final int end) {
    int finmode = Result.noResultMode;
    Operation operation = op;
    for(int i = begin; i != end; i += increment) {
      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 != null && nop != operation) {
	    if (nop.replaced(operation))
	      operation = nop;
	    else /* if result is unrelated with the requested
		  * operation, ignore it */
	      continue;
	}
	final int mode = res.getMode();
	if ((mode & Result.resultValueMask) != 0)
	  return handle(res, object, metaObjects,
			i-increment, -increment, begin-increment);
	final int newmode = mode & Result.resultRequestMask;
	if (newmode > finmode)
	  finmode = newmode;
      }
    }
    if (op == operation && finmode == Result.noResultMode)
      return null;
    else
      return Result.operation(operation, finmode);
  }

  /** Asks each MetaObject in the metaObjects array, 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(final Result res, final Object object) {
    final MetaObject[] metaObjects = this.metaObjects;
    return handle(res, object, metaObjects, metaObjects.length-1, -1, -1);
  }

  /** Asks MetaObject in the given array to handle the Result.  The
      first MetaObject that gets the Result is the one whose index is
      <tt>begin</tt>.  Then, <tt>increment</tt> is added to
      <tt>begin</tt> to compute the index of the next element of the
      array, and so on, until the computed index is exactly equal to
      <tt>end</tt>.

      <p>If any of the MetaObjects returns a valid 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.

      @param metaObjects an array of MetaObjects.
      
      @param begin the index of the first element of the array that
      should be presented the Operation.  This is usually
      <tt>metaObjects.length-1</tt>.

      @param increment the value added to an index to compute the
      index of the next element that will be presented an Operation.
      This is usually -1.

      @param end the index of the first element of the array that will
      not be presented the Operation.  This is usually -1.

      @return the last Result returned by a MetaObject, or the original
      Result, if every MetaObject returned null.  */
  public static Result handle(Result res, final Object object,
			      final MetaObject[] metaObjects,
			      final int begin, final int increment,
			      final int end) {
    for(int i = begin; i != end; i += increment) {
      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 = this.metaObjects;
    for(int i = 0, l = metaObjects.length; i < l; ++i)
      metaObjects[i].handle(message, object);
  }

  /** 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 SequentialComposer, a specialized instance is
      created too, if this is intended.

      @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 the static configure 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.  */
  public MetaObject configure(final Object newObject, final Object object) {
    final MetaObject[] rs =
	makeMetaObjectsFor(newObject, object, metaObjects);
    if (rs == null)
      return null;
    if (rs.length == 1)
      return rs[0];
    return new SequentialComposer(rs);
  }

  /** @deprecated

      Asks each MetaObject to provide a MetaObject to occupy its place
      in a new Object's meta-configuration.  By default, it just calls
      the static version of method configure.

      <p>This method is maintained primarily for backward
      compatibility.

      @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.

      @see #configure  */
  protected MetaObject[] makeMetaObjectsFor(final Object newObject,
					    final Object object,
					    final MetaObject[] metaObjects) {
    return configure(newObject, object, metaObjects,
		     0, 1, metaObjects.length);
  }

  /** 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.
      
      @param begin the index of the first element of the array that
      should be asked to configure the new Object.  This is usually 0.

      @param increment the value added to an index to compute the
      index of the next element that will be asked to configure the
      new Object.  This is usually 1.

      @param end the index of the first element of the array that will
      not be asked to configure the new Object.  This is usually
      <tt>metaObjects.length</tt>.

      @return a packed array of MetaObjects to be associated with the
      given Object, or null, if no MetaObjects are needed.  */
  public static MetaObject[] configure(final Object newObject,
				       final Object object,
				       final MetaObject[] metaObjects,
				       final int begin,
				       final int increment,
				       final int end) {
    int count = 0;
    final int l = (end-begin)/increment;
    final MetaObject[] newMetaObjects = new MetaObject[l];
    for(int i = begin; i != end; i += increment) {
      MetaObject r = metaObjects[i];
      if (r != null)
	newMetaObjects[count] = r = r.configure(newObject, object);
      if (r != null)
	++count;
    }
    if (count == 0)
      return null;
    if (count == l)
      return newMetaObjects;
    final MetaObject[] packMetaObjects = new MetaObject[count];
    System.arraycopy(newMetaObjects, 0, packMetaObjects, 0, count);
    return packMetaObjects;
  }

  /** 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.

      If an Operation is being currently handled, this method should
      try to avoid modifying the set of MetaObjects the Operation
      would be delegated to, but it doesn't; this is left for
      subclasses to implement.

      @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;
    }
  }

  /** 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) {
    this.myOperationFactory = factory;
    final MetaObject[] metaObjects = this.metaObjects;
    for(int i = 0, l = metaObjects.length; i < l; ++i)
      metaObjects[i].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) {
    final MetaObject[] metaObjects = this.metaObjects;
    for(int i = 0, l = metaObjects.length; i < l; ++i)
      metaObjects[i].release(object);
  }
}
