/*  PAT: Persistent Applications Toolkit (patsystem.sf.net)
 *  Copyright (C) 2004, 2005  Tomasz Nazar, nthx at irc dot pl
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Full version of the license is /docs/LICENSE.txt
 */
package org.nthx.pat;

import org.apache.log4j.Logger;
import org.prevayler.Command;
import org.prevayler.PrevalentSystem;

import java.io.Serializable;
import java.lang.reflect.Method;


/** Generic command for storing "transaction" information and invoking
 *  transactions.
 *  <p>The trick is not to store objects involved in transaction:
 *  <ul><li>target object - on which method is invoked
 *  <li>argument objects
 *  </ul>
 *  .. but to store their ids (OID) only ({@link ObjectForStream ObjectForStream
 *  really}).. And recovering real objects having an
 *  OID and vice versa.
 *
 *  @version $Id: GenericCommand.java 3725 2005-06-09 23:57:03Z nthx $
 *  @author nthx@users.sourceforge.net
 */
public class GenericCommand
       implements Command
{

    //--- Fields ----------------
    private String command;
    private ObjectForStream[] targetAndArgsForStream;
    private Class[] parameterTypes; //to differ between 'long' and 'Long', etc.
    private Class  targetClass;


    //--- Constructors ----------
    public GenericCommand(Object target, Method method, Object[] params)
    {
        String command = method.getName();

        this.command = command;
        this.targetAndArgsForStream = CommandGenerationHelper.targetAndBos2FakeOIDs(target, params);
        this.parameterTypes = method.getParameterTypes();
        this.targetClass = method.getDeclaringClass();
    }


    //--- Implementation --------

    /** Invoked during writing (made by AOP) and reading (made by Prevayler
     *  deserialisation)
     */
    public Serializable execute(PrevalentSystem system)
    throws Throwable
    {
//        log.debug("GC: command: " + command);
//        CommandGenerationHelper.showParamsForStream(targetAndArgsForStream);
//        logCommand();


        Object[] recoveredParams = 
            CommandGenerationHelper.fakeBOs2targetAndBos((IdentityMap)system, 
                                                        targetAndArgsForStream, 
                                                        targetClass+"."+command, 
                                                        parameterTypes);
        Object recoveredTarget = recoveredParams[0];


        Method commandMethod = targetClass.getMethod(command, parameterTypes);

        try
        {
            Object[] recoveredParamsOnly = 
                new Object[recoveredParams.length - 1];

            System.arraycopy(recoveredParams, 1, 
                             recoveredParamsOnly, 0, 
                             recoveredParamsOnly.length);


            commandMethod.setAccessible(true);
            
            return (java.io.Serializable) commandMethod.invoke(
                                    recoveredTarget, 
                                    recoveredParamsOnly);
        } catch (IllegalAccessException e)
        {
            throw e.getCause();
        } catch (java.lang.reflect.InvocationTargetException e)
        {
            //log.error("Normal execution error. Commands may throw them: " + e.getMessage(), e);
            //normal execution Exception: commands may throw them..
            //XXX: i'm not sure if above stmnt is true
            throw e.getCause();
        }
    }

    private void logCommand()
    {
        StringBuffer params = new StringBuffer();
        for (int i=1; i<targetAndArgsForStream.length; i++)
        {
            if (targetAndArgsForStream[i] instanceof Identifiable)
                params.append(targetAndArgsForStream[i].objectOrOID);
            else
                params.append(".., ");
        }
        log.debug("Recovering: " + command + "("+params+")");
    }


    public String toString()
    {
        String result = "<" + command + "\n";
        if (null != targetAndArgsForStream)
        {
            for (int i = 0; i < targetAndArgsForStream.length; i++)
                result = result + "\t<arg" + i + " " + targetAndArgsForStream[i].getClass().getName()
                        + "\n\t     " + targetAndArgsForStream[i] + "/>\n";
        }
        result = result + "\n</" + command + ">";
        return result;
    }

    //--- internal Fields -------
    private static Logger log = Logger.getLogger("pat");
}