package ca.ubc.cs.spl.pattern.library;

/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * This file is part of the design patterns project at UBC
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * either http://www.mozilla.org/MPL/ or http://aspectj.org/MPL/.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ca.ubc.cs.spl.patterns.
 *
 * Contributor(s):   
 */
 
import java.util.WeakHashMap;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Iterator;  

import ca.ubc.cs.spl.pattern.library.Command;
import ca.ubc.cs.spl.pattern.library.CommandInvoker;
import ca.ubc.cs.spl.pattern.library.CommandReceiver;

// TODO: This implementation allows only for exactly one command per invoker. 
// That can either stay this way or be replaced with composite (macro) 
// commands, either with or without defined order. [Jan, Oct 29, 2002]

/**
 * This is the abstract <i>Command</i> protocol.
 * 
 * @author  Jan Hannemann
 * @author  Gregor Kiczales
 * @version 1.1, 10/29/02
 * 
 * @see Command
 */

public abstract aspect CommandProtocol { 
    
////////////////////////////////
// Invoker -> Command mapping //
////////////////////////////////

    /**
     * stores the mapping between CommandInvokers and Commands
     */
    
    private WeakHashMap mappingInvokerToCommand = new WeakHashMap();
    
    /**
     * Sets a new command for an invoker
     *
     * @param invoker the object which will invoke the command
     * @param command the command to be set
     * @returns the former command
     */   
     
    public Object setCommand(CommandInvoker invoker, Command command) { 
    	return mappingInvokerToCommand.put(invoker, command); 
    }
    
    /**
     * Removes a command from an invoker
     *
     * @param invoker the object which will no longer invoke the command
     * @param command the command to be removed
     * @returns the former command
     */   
     
    public Object removeCommand(CommandInvoker invoker) { 
    	return setCommand(invoker, null); 
    }


    /**
     * Returns the command for an invoker
     *
     * @param invoker the object for which to return the command 
     * @returns the current command for the invoker
     */   
 
    public Command getCommand(CommandInvoker invoker) { 
    	return (Command) mappingInvokerToCommand.get(invoker); 
    }


/////////////////////////////////
// Command -> Receiver mapping //
/////////////////////////////////

    /**
     * stores the mapping between Coammnds and Receivers
     */
    
    private WeakHashMap mappingCommandToReceiver = new WeakHashMap();
    
    /**
     * Sets a new receiver for a command
     *
     * @param command the command to be set
     * @param receiver the object to be manipulated by the command's 
     *        execute() method
     * @returns the former receiver
     */   
     
    public Object setReceiver(Command command, CommandReceiver receiver) { 
    	return mappingCommandToReceiver.put(command, receiver); 
    }
    
    /**
     * Returns the receiver for a particular command
     *
     * @param invoker the object for which to return the command 
     * @returns the current command for the invoker
     */   
 
    public CommandReceiver getReceiver(Command command) { 
    	return (CommandReceiver) mappingCommandToReceiver.get(command); 
    }


///////////////////////////////////////
// Command Execution via PC & advice //
///////////////////////////////////////


    /**
     * The join points after which to execute the command.
     * This replaces the normally scattered myCommand.execute() calls.
     *
     * @param invoker the object invoking the command
     */

    protected abstract pointcut commandTrigger(CommandInvoker invoker);


    /**
     * Calls <code>executeCommand()</code> when the command is triggered. 
     *
     * @param invoker the object invoking the command
     */ 
     
    after(CommandInvoker invoker): commandTrigger(invoker) { 
        Command command = getCommand(invoker);
    	if (command != null) {
    	    CommandReceiver receiver = getReceiver(command);
        	command.executeCommand(receiver);
        } else {
            // Do nothing: This Invoker has no associated command
        }
    }
    

//////////////////////////////////
// setCommand() via PC & advice //
//////////////////////////////////

    /**
     * The join points after which to set a command for an invoker.
     * This replaces the normally scattered myInvoker.add(Command) calls.
     * The pointcut is provided in addition to the setCommand() method above,
     * to allow all pattern code to be removed from concrete invokers. 
     *
     * This PC is non-abstract, to make it optional for sub-aspcects to define
     * it.
     *
     * @param invoker the invoker to attach the command to
     * @param command the command to be attached to the invoker
     */

    protected pointcut setCommandTrigger(CommandInvoker invoker, Command command);   


    /**
     * Calls <code>addCommand()</code> when a command should be set. 
     *
     * @param invoker the invoker to attach the command to
     * @param command the command to be attached to the invoker
     */ 
     
    after(CommandInvoker invoker, Command command): setCommandTrigger(invoker, command) {                          // TODO: is "before" a better choice? 
    	if (invoker != null)
        	setCommand(invoker, command);  
                                                           // TODO: throw exception in else case?
    }

/////////////////////////////////////
// removeCommand() via PC & advice //
/////////////////////////////////////

    /**
     * The join points after which to remove a command from an invoker.
     * This replaces the normally scattered myInvoker.remove(Command) calls.
     * The pointcut is provided in addition to the removeCommand() method 
     * above, to allow all pattern code to be removed from concrete invokers.
     *
     * This PC is non-abstract, to make it optional for sub-aspcects to define
     * it.
     *
     * @param invoker the invoker to remove the command from
     */

    protected pointcut removeCommandTrigger(CommandInvoker invoker);   
    
    /**
     * Calls <code>removeCommand()</code> when a command should be removed. 
     *
     * @param invoker the invoker to remove the command from
     */ 
     
    after(CommandInvoker invoker): removeCommandTrigger(invoker) {                          // TODO: is "before" a better choice? 
    	if (invoker != null)
        	removeCommand(invoker);  
                                                           // TODO: throw exception in else case?
    }

////////////////////////////////////////////
// Command default method implementations //
////////////////////////////////////////////

    /**
     * Provides a deault implementation for the isExecutable method defined
     * in the Command interface. 
     *
     * @returns true (default implementation). Can be overwritten by concrete
     * aspects or even concrete commands.
     */ 

    public boolean ca.ubc.cs.spl.pattern.library.Command.isExecutable() {
        return true;
    } 
    
////////////////////////////////////////////////////////////////
// Calling specialized execute() instead of generic execute() //
////////////////////////////////////////////////////////////////

    public final void Command.execute() {}    // this prevents anyone from implementing this method instead of the specialized version. Invokers can call it though

    protected pointcut callGenericExecute(Command command): 
        ( call(void Command+.execute())) && target(command);

    after(Command command): callGenericExecute(command) {              // TODO: around?
        command.executeCommand(getReceiver(command));
    }
}
