// $Id: Guarana.java,v 1.19 1998/09/29 17:25:56 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;

/** This class provides the implementation of the kernel of Guarana.
    It provides methods for setting up meta-configurations and for
    communication between the base level and the meta level.

    This class, as well as Operation and Result, are the only special
    classes in Guarana, in the sense that they cannot be made
    reflexive at all.  Instances of Operation and Result cannot ever
    be made reflexive either.  This would break security constraints,
    by allowing its MetaObjects to obtain references to MetaObjects
    associated with base-level Objects, or to break restrictions
    imposed by OperationFactories.

    <p>This protection could have been implemented by associating
    suitable MetaObjects to those classes, so that no such security
    holes can be opened.  However, this would have serious performance
    impact.  Since we couldn't find any good reason to make any of
    these classes or objects reflexive, we decided not to pay that
    additional cost.  If we ever find a good reason to make any of
    these classes reflexive, we may change this constraint.

    @author Alexandre Oliva
    @version $Revision: 1.19 $  */
public final class Guarana {
    static {
        System.loadLibrary("guarana");
    }

    /** This needs not be instantiated at all.  */
    private Guarana() {}

    /** Obtain the metaObject associated with the given Object.  */
    private static native MetaObject getMetaObjectOf(final Object object);

    /** Associates the base level Object with a new primary
	MetaObject.  First, it checks whether the current primary
	MetaObject is the one specified as previousMetaObject; if it
	is not, this method terminates.  Otherwise, it initializes the
	newMetaObject, by giving it a (still invalid)
	OperationFactory.  Then, if the primary MetaObject has
	changed, the newMetaObject that was just initialized is asked
	to release the Object, and this method terminates.  Otherwise,
	the newMetaObject is named the primary MetaObject of the given
	Object, and the previousMetaObject is asked to release the
	Object.

	<p>This should only be called from method reconfigure.

	@param object the object whose primary MetaObject is to be
	changed.

	@param previousMetaObject the current primary MetaObject of the
	given Object.

	@param newMetaObject the MetaObject that should replace the
	previousMetaObject.  */
    private static native
	void setMetaObjectOf(final Object object,
			     final MetaObject previousMetaObject,
			     final MetaObject newMetaObject);

    /** Delivers an operation to the target object, without taking care
	of exceptions.  This should only be invoked from
	perform(Operation), that takes care of wrapping exceptions as
	appropriate.

	@exception Throwable is propagated from the execution of the
	Operation.  */
    private static native final Result deliver(final Operation operation)
	throws Throwable;

    /** Marks an operation as already performed, or throws if it
	cannot be performed.  It should be invoked inside a
	<tt>synchronized(operation)</tt> statement.

	@param operation the Operation to be marked.

	@exception IllegalArgumentException if the Operation was
	already marked as performed, or it is a replacement Operation.  */
    private static native final
	void markAsPerformed(final Operation operation)
	throws IllegalArgumentException;


    /** Whenever an operation is intercepted in the base level, it is
	reified and presented to the MetaObject by the invocation of
	this method.  It does implement exactly the protocol described
	in the general description of a MetaObject.

	<p>Just before delivering the Operation to the target Object, we
	check whether the primary MetaObject is no longer the one that
	was originally asked to handle it.  If there was any change,
	Result.throwObject(null) is presented to the MetaObject that was
	originally asked to handle the Operation, and the handling is
	restarted with the new MetaObject.

	<p>If the MetaObject is unchanged, the operation will be
	validated and delivered.  If the validate method throws, the
	thrown Object will be considered the Result of the Operation.

	<p>This Result will be presented to the Object's MetaObject, if
	it asked so.

	@param operation the Operation reified from the base level.

	@return the Result to be returned back to the base level.

	@exception MetaException if any Exception propagates from the
	execution of either Operation or Result handling methods.

	@exception IllegalArgumentException if the Operation was
	already performed, or it is a replacement Operation.

	@see MetaObject
	@see Operation#validate */
    public static Result perform(final Operation operation)
	throws MetaException {
	synchronized(operation) {
	    markAsPerformed(operation);
	}
	final Object object = operation.getObject();
	Result res;
	boolean retry;
	do {
	    res = null;
	    Operation op = operation;
	    retry = false;
	    final MetaObject meta = getMetaObjectOf(object);
	    int mode = Result.noResultMode;
	    if (meta != null) {
		Operation nop = operation;
		try {
		    res = meta.handle(operation, object);
		} catch (Throwable t) {
		    return Result.throwObject(new MetaException(t), operation);
		}
		if (res != null) {
		    nop = res.getOperation();
		    if (nop == null || nop.replaced(operation)) {
			if (nop != null)
			    op = nop;
			mode = res.getMode();
		    } else
			res = null;
		}
		if ((mode & Result.resultValueMask) != 0)
		    return res;
	    }
	    if (meta != getMetaObjectOf(object)) {
		retry = true;
		res = Result.throwObject(null, operation);
	    } else {
		try {
		    op.validate();
		    if (meta != getMetaObjectOf(object)) {
			retry = true;
			res = Result.throwObject(null, op);
		    } else
			res = deliver(op);
		} catch (Throwable t) {
		    res = Result.throwObject(t, op, false);
		}
	    }
	    switch(mode & Result.resultRequestMask) {
	    case Result.noResultMode:
		break;
	    case Result.inspectResultMode:
		meta.handle(res, object);
		break;
	    case Result.modifyResultMode:
		Result rres = meta.handle(res, object);
		if (rres != null) {
		    final Operation nop = rres.getOperation();
		    if ((nop == null || nop.replaced(op)) &&
			(rres.getMode() & Result.resultValueMask) != 0)
			res = rres;
		}
		break;
	    }
	} while (retry);
	return res;
    }

    /** This method asks the MetaObject associated with the given Object
	to handle the presented Message.  If the object is not
	associated with any MetaObject, nothing is done.

	@param message the Message to be sent to the Object's
	meta-level.

	@param object the object whose MetaObject should receive the
	Message.  */
    public static void broadcast(final Message message,
				 final Object object) {
	final MetaObject metaObject = getMetaObjectOf(object);
	if (metaObject != null)
	    metaObject.handle(message, object);
    }

    /** This is a reconfiguration request.  The caller wants the
	newMetaObject to occupy the place of the oldMetaObject in the
	meta-configuration of the given Object.

	<p>This Operation is a do-nothing if the given Object is either
	the class Guarana, the class Operation, the class Result, or any
	instace thereof, since these are non-reflexive classes.

	<p>This method ensures that any top-level reconfiguration is
	atomic, by synchronizing either on the current primary
	MetaObject associated with the Object or on the object's class,
	and checking whether the MetaObject associated with the Object
	hasn't changed.  If it has, the operation is restarted.

	<p>As soon as it makes sure there's no other thread
	reconfiguring the given Object, it sends the reconfigure request
	to the current MetaObject.  If the current MetaObject was null,
	but the oldMetaObject was not, the request is ignored, but if
	the oldMetaObject was null too, an InstanceReconfigure Message
	is created and broadcasted to the meta-configuration of the
	Object's class, then to its superclass, and so on.  After the
	InstanceReconfigure Message is broadcasted to the
	meta-configuration of class java.lang.Object, the MetaObject
	stored in the InstanceReconfigure Message is used as the new
	primary MetaObject.

	<p>Finally, if the returned MetaObject is different from the
	current MetaObject, and the current MetaObject is still the
	primary MetaObject, the returned MetaObject is initialized and
	associated with the Object, and then the previous primary
	MetaObject is told to release the Object.

	<p>If, at any point after the execution of method reconfigure
	and before the execution of method release, the primary
	MetaObject is found to have changed (which means that the
	execution of either reconfigure or initialize caused the
	MetaObject to change), the reconfiguration is considered
	terminated.
      
	<p>There's no standard way to check whether the reconfiguration
	succeeded, since the new MetaObject may have been accepted but
	guarded under a Composer controlled by the existing MetaObject,
	or it may have been rejected at all, or it may have been
	reconfigured before being accepted, so that the previous
	meta-configuration remains.  The only way to check whether the
	MetaObject was actually accepted is to Broadcast a Message after
	the Reconfiguration finishes.

	@param object the Object whose meta-configuration is to be
	changed.

	@param oldMetaObject the MetaObject to be replaced, or null,
	which means the current MetaObject.

	@param newMetaObject the MetaObject intended to be associated
	with the Object.

	@see Guarana#broadcast
	@see Message  */
    public static void reconfigure(final Object object,
				   final MetaObject oldMetaObject,
				   final MetaObject newMetaObject) {
	if (object == Guarana.class ||
	    object == Operation.class ||
	    object == Result.class ||
	    object instanceof Operation ||
	    object instanceof Result)
	    return;
	MetaObject current = getMetaObjectOf(object);
	while (true) {
	    synchronized(((current != null)
			  ? (Object)current
			  : (Object)getClass(object))) {
		final MetaObject changedMaybe = getMetaObjectOf(object);
		if (current != getMetaObjectOf(object))
		    continue;
		MetaObject changedMetaObject = newMetaObject;
		if (current != null)
		    changedMetaObject = current.reconfigure(object,
							    current,
							    newMetaObject);
		else if (oldMetaObject != null)
		    changedMetaObject = null;
		else {
		    final InstanceReconfigure
			msg = new InstanceReconfigure(object, newMetaObject);
		    for (Class clazz = getClass(object);
			 clazz != null;
			 clazz = clazz.getSuperclass())
			broadcast(msg, clazz);
		    changedMetaObject = msg.metaObject;
		}
		/* setMetaObject calls initialize and checks whether the
		   MetaObject changed for us, and it calls release for the old
		   MetaObject after setting the new MetaObject up.  */
		if (current != changedMetaObject)
		    setMetaObjectOf(object, current, changedMetaObject);
	    }
	    return;
	}
    }

    /** Creates an unitialized Object, broadcasts a NewObject Message
	to the Class' MetaObject, and tries to associate the given
	MetaObject with the uninitialized Object.  An unitialized
	Objects is useful for representing Objects in other address
	spaces, reinstantiating persistent Objects, to migrating,
	copying or replicating Objects, etc.  Since such Objects
	usually represent other Objects, they'll be called proxies,
	and the NewObject Message that will be broadcasted to the
	Class' MetaObject is in fact a NewProxy Message, an instance
	of a subclass of class NewObject.

	<p>A MetaObject associated with a proxy Object should not allow
	messages to be delivered to an uninitialized proxy Object.  In
	fact, this applied to any Object before it is constructed, but
	proxy Object are seldom constructed at all.  If they are, or if
	their fields are all properly initialized, they may be
	considered actual Objects.

	<p>The following pseudo-code depicts the behavior of this
	method.  The static pseudo-method &lt;alloc> is assumed to
	allocate an uninitialized Object, or an array of length 0, if
	the given Class is an array type.  For arrays of non-zero
	lengths, method newInstance of class java.lang.reflect.Array
	must be used.

	<p><hr><blockquote><pre>
	* if (cls.isPrimitive() ||
	*     cls == Guarana.class ||
	*     cls == Operation.class ||
	*     cls == Result.class)
	*   throw new IllegalArgumentException
	*     ("cannot create proxy of non-reflexive class");
	* Object object = &lt;alloc>(cls);
	* broadcast(new NewProxy(object), cls);
	* if (metaObject != null)
	*   reconfigure(object, null, metaObject);
	* return object;
	</pre></blockquote><hr>

	The meta-configuration of a Class may refuse to accept the
	instantiation of pseudo-Objects by throwing a MetaException
	when broadcasted the NewProxy Message.  It may also create a
	meta-configuration for the new Object that rejects or modifies
	the proposed meta-configuration.

	@param cls the Class whose pseudo-instance is to be created.

	@param metaObject the MetaObject to be associated with the new
	proxy Object.

	@return the created proxy Object, or null, if the given class
	is a primitive type or a non-reflexive class, such as Guarana,
	Operation, Result.

	@exception MetaException may be propagated out from the
	execution of either reconfigure or broadcast.

	@see NewProxy  */
    public static native Object makeProxy(final Class cls,
					  final MetaObject metaObject)
	throws MetaException;

    /** Obtains the address of memory where the Object is stored,
	which is the standard hashCode of an Object.  Note that this
	method does not invoke any method of the given object, so it
	is useful for meta-level objects to find out information about 
	base-level ones.

	@param object the Object whose address is to be returned.

	@return the address of the given Object.  */
    public static native int hashCode(final Object object);

    /** Obtains the most derived Class an Object belongs to.  Note
	that this method does not invoke any method of the given
	object, so it is useful for meta-level objects to find out
	information about base-level ones.

	@param object the Object whose Class is to be returned.

	@return the Class the given object belongs to.  */
    public static native Class getClass(final Object object);

    /** Obtains the name of a Class.  Note that this method does not
	invoke any method of the given class, so it is useful for
	meta-level objects to find out information about classes of
	base-level objects.

	@param object the Class whose name is to be returned.

	@return the name of the given Class.  */
    public static native String getClassName(final Class clazz);

    /** Obtains a String that contains the Object's class name and
	address.

	@param object the Object whose String representation is
	wanted.

	@return a String that contains the Object Class name, an `@'
	sign and the object address in hexa-decimal notation.  */
    public static String toString(final Object object) {
	if (object == null)
	    return ""+object;
	else
	    return getClassName(getClass(object)) +
		'@' + Integer.toHexString(hashCode(object));
    }
}
