// $Id: MetaLogger.java,v 1.3 1998/05/29 16:06:24 oliva Exp $

/* Copyright 1997,1998 Alexandre Oliva <oliva@dcc.unicamp.br>
 *
 * See the files "COPYING" and "README" for information on usage and
 * redistribution of this file.  */

import BR.unicamp.Guarana.*;

/**
 * Logs all Operations, Results, Messages and Reconfiguration
 * requests.
 *
 * It can be used for starting-up an originally non-reflexive
 * application with logging enabled.
 *
 * @see MetaLogger#main
 *
 * @author Alexandre Oliva
 * @version $Revision: 1.3 $ */
public class MetaLogger extends MetaObject {
    /**
     * PrintWriter which gets all logged messages. */
    private java.io.PrintWriter out;

    /**
     * String that is prepended to any output line. */
    private String prefix = "";

    /**
     * Create a MetaLogger that logs to System.out.  */
    public MetaLogger() {
	this(System.out);
    }

    /**
     * Creates a MetaLogger that logs to the specified OutputStream.
     * It will be wrapped in a PrintWriter with autoflush enabled.
     *
     * @param out the OutputStream that will get all logging.  */
    public MetaLogger(final java.io.OutputStream out) {
	this(new java.io.PrintWriter(out, true));
    }
    
    /**
     * Creates a MetaLogger that logs to the specified Writer.  It
     * will be wrapper in a PrintWriter with autoflush enabled.
     *
     * @param out the Writer that will get all logging.  */
    public MetaLogger(final java.io.Writer out) {
	this(new java.io.PrintWriter(out, true));
    }

    /**
     * Creates a MetaLogger that logs to the specified PrintWriter.
     *
     * @param out the PrintWriter that will get all logging.  */
    public MetaLogger(final java.io.PrintWriter out) {
	this.out = out;
    }

    /**
     * Sets a prefix String, prepended to every line printed by this
     * MetaLogger.
     *
     * @param prefix the prefix String.  It should not be null,
     * otherwise the prefix will be the string `null'.
     *
     * @return the MetaLogger itself, so this can be chained with
     * construction or whatever.  */
    public MetaLogger setPrefix(final String prefix) {
	this.prefix = prefix;
	return this;
    }
    
    /**
     * Logs an `Operation' string.
     *
     * @param op the Operation to be logged.
     *
     * @param ob the target Object of the Operation.  Unused.
     *
     * @return a request to inspect the result of the Operation:
     * Result.inspectResult.  */
    public Result handle(final Operation op, final Object ob) {
	out.println(prefix + "Operation: " + op);
	return Result.inspectResult;
    }

    /**
     * Logs a `Result' string.
     *
     * @param res the Result of an Operation.
     *
     * @param ob the target Object of the Operation.
     *
     * @return null, i.e., the provided Result should be maintained.
     */
    public Result handle(final Result res, final Object ob) {
	out.println(prefix + "Result: " + res);
	return null;
    }

    /**
     * Logs a `Message' string.
     *
     * @param msg the Message to be logged.
     *
     * @param ob the Object whose meta-configuration was the target of 
     * the Message broadcast.  */
    public void handle(final Message msg, final Object ob) {
	out.println(prefix + "Message: " + msg + " for " +
		    Guarana.toString(ob));
    }

    /**
     * Copies itself into the meta-configuration of the new object, as
     * long as this object is not a String or a StringBuffer.  Logs a
     * message informing about the configuration request, and another
     * indicating whether it propagated itself or not.
     *
     * @param newObject the new Object whose meta-configuration is to
     * be provided.  Just used for printing the Object identification.
     *
     * @param object the Object whose meta-configuration the new
     * meta-configuration was based on.  Just used for printing its
     * identification.
     *
     * @return this or null, depending on whether it decided to
     * propagate itself or not.  */
    public MetaObject configure(final Object newObject,
				final Object object) {
	out.print(prefix + "Configure " + Guarana.toString(newObject) +
		  " based on " + Guarana.toString(object));
	if (Guarana.getClass(newObject) == String.class ||
	    Guarana.getClass(newObject) == StringBuffer.class) {
	    out.println(": not propagated");
	    return null;
	} else {
	    out.println(": propagated");
	    return this;
	}
    }

    /**
     * Accepts any reconfiguration request targeted to itself.  All
     * requests are logged.  Furthermore, if the reconfiguration
     * request is targeted on itself, an acceptance message is logged; 
     * otherwise, a message stating that the request was ignored is.
     *
     * @param object the Object whose meta-configuration is to be
     * affected.  Used only for logging.
     *
     * @param prev the MetaObject to be replaced.
     *
     * @param next the MetaObject that should take its place.
     *
     * @return next if prev == this, this otherwise.  */
    public MetaObject reconfigure(final Object object,
				  final MetaObject prev,
				  final MetaObject next) {
	out.println(prefix + "Reconfigure " +
		    Guarana.toString(object) + ": " +
		    Guarana.toString(prev) + " -> " +
		    Guarana.toString(next));
	if (prev == this)
	    return next;
	else
	    return this;
    }

    /**
     * Logs an initialization message.
     *
     * @param fact ignored.
     * @param obj the Object whose meta-configuration is to be
     * initialized.  Used just for logging purposes.  */
    public void initialize(final OperationFactory fact,
			   final Object obj) {
	out.println(prefix + "Initialize: " + Guarana.toString(obj));
    }

    /**
     * Logs an object release message.
     *
     * @param obj the Object to be released.  Used just for logging
     * purposes.  */
    public void release(final Object obj) {
	out.println(prefix + "Release: " + Guarana.toString(obj));
    }

    /**
     * The MetaLogger class can be used as an application starter.
     * Running `java MetaLogger classname arguments ...' is equivalent
     * to associating classname with a MetaLogger that logs to
     * System.out and invoking classname.main with the provided
     * argument list.<p>
     *
     * If the first argument is a number instead of a classname, it
     * should be followed by a list of classnames that will be
     * associated with MetaLoggers that log to System.out.  After this
     * list, a classname and an argument list should appear.  In this
     * case, the classname will not be associated with a MetaLogger.
     * For example: `java 3 A B C D args' will cause MetaLoggers to be
     * associated with classes A, B and C.  Finally, D.main will be
     * invoked with the provided argument list.
     */
    public static void main(String[] argv)
	throws java.lang.ClassNotFoundException,
	       java.lang.NoSuchMethodException,
	       java.lang.IllegalAccessException,
	       java.lang.reflect.InvocationTargetException
    {
	int n;
	Class c;
	java.lang.reflect.Method m;
	try {
	    n = Math.abs(Integer.parseInt(argv[0]));
	    ++n;
	    c = Class.forName(argv[n]);
	    m = c.getMethod("main", new Class[] { String[].class });
	    for(int i = 1; i < n; ++i)
		Guarana.reconfigure(Class.forName(argv[i]), null,
				    new MetaLogger());
	} catch (NumberFormatException e) {
	    n = 0;
	    c = Class.forName(argv[n]);
	    m = c.getMethod("main", new Class[] { String[].class });
	    Guarana.reconfigure(c, null, new MetaLogger());
	}
	++n;
	String[] newargv = new String[argv.length-n];
	System.arraycopy(argv, n, newargv, 0, newargv.length);
	m.invoke(null, new Object[] { newargv });
    }
}
