package org.freehep.util.commanddispatcher;

import java.lang.reflect.*;
import java.util.*;


/**
 * A default implementation of CommandGroup.
 *
 * Typically applications provide their own CommandProcessor(s) which extend this base class
 * and which can handle a set of commands. By default CommandProcessor's acceptCommand method
 * uses reflection to search for methods in the subClass which correspond to specific commands,
 * although subclasses could also override the acceptCommand method to implement a different
 * scheme. The default scheme looks for methods of type:
 * <pre>
 *            public void onXXX()
 * </pre>
 * to handle the command XXX. Also
 * <pre>
 *            public void enableXXX(CommandState state)
 * </pre>
 * to determine if the command is active or not.
 *
 * @author Tony Johnson (tonyj@slac.stanford.edu)
 * @version $Id: CommandProcessor.java,v 1.1.1.1 2004/05/31 05:08:49 kuromaru Exp $
 */

public class CommandProcessor extends Observable implements CommandGroup
{
   private static final Class[] noArg = {  };
   private static final Object[] noArgs = {  };
   private static final Class[] boolArg = { java.lang.Boolean.TYPE };
   private static final Class[] simpleEnableArg = { CommandState.class };
   private static final Class[] booleanEnableArg = { BooleanCommandState.class };
   private CommandTargetManager manager;

   public void setChanged()
   {
      if (!hasChanged())
      {
         super.setChanged();
         javax.swing.SwingUtilities.invokeLater(new Runnable()
            {
               public void run()
               {
                  notifyObservers();
                  clearChanged();
               }
            });
      }
   }

   public void setManager(CommandTargetManager m)
   {
      this.manager = m;
   }

   /**
    * The CommandTargetManager calls acceptCommand to find out if this CommandProcessor
    * can respond to the specified command. If it can it returns a CommandTarget for the command,
    * otherwise it returns null.
    * @param command The command to test for
    */
   public CommandTarget acceptCommand(String command)
   {
      return acceptCommand(getClass(), command);
   }

   /**
    * Uses reflection to check if the specified class has an "on" or "enable" method
    * for this comamnd.
    * @param klass The class to check
    * @param command The command to test for
    */
   protected CommandTarget acceptCommand(Class klass, String command)
   {
      String t = translate(command);
      String c = "on" + t;
      String e = "enable" + t;
      Method mc;
      Method me;
      try
      {
         mc = klass.getMethod(c, noArg);
         try
         {
            me = klass.getMethod(e, simpleEnableArg);
         }
         catch (NoSuchMethodException x)
         {
            me = null;
         }
         try // Support for RadioMenuItem
         {
            if (me == null)
               me = klass.getMethod(e, booleanEnableArg);
         }
         catch (NoSuchMethodException x)
         {
            me = null;
         }
         return new SimpleTarget(mc, me);
      }
      catch (NoSuchMethodException x) {}
      try
      {
         mc = klass.getMethod(c, boolArg);
         try
         {
            me = klass.getMethod(e, booleanEnableArg);
         }
         catch (NoSuchMethodException x)
         {
            me = null;
         }
         return new BooleanTarget(mc, me);
      }
      catch (NoSuchMethodException x) {}
      return null;
   }

   protected void invoke(Method method, Object[] args) throws IllegalAccessException, InvocationTargetException
   {
      method.invoke(this, args);
   }

   /**
    * Override this method to provide application specific handling
    * for errors generated during command dispatch
    */
   protected void invokeCommand(SimpleTarget t)
   {
      try
      {
         t.doCommand();
      }
      catch (CommandInvocationException x)
      {
         manager.handleCommandError(x.getTargetException());
      }
   }

   /**
    * Override this method to provide application specific handling
    * for errors generated during command dispatch
    */
   protected void invokeCommand(BooleanTarget t, boolean arg)
   {
      try
      {
         t.doCommand(arg);
      }
      catch (CommandInvocationException x)
      {
         manager.handleCommandError(x.getTargetException());
      }
   }

   protected void invokeCommand(Method method, Object[] args) throws IllegalAccessException, InvocationTargetException
   {
      invoke(method, args);
   }

   protected void invokeEnable(Method method, Object[] args) throws IllegalAccessException, InvocationTargetException
   {
      invoke(method, args);
   }

   /**
    * Translates a command. The default implementation removes trailing ..., and
    * any spaces and then uppercases the first character .
    */
   protected String translate(String command)
   {
      if (command.endsWith("..."))
         command = command.substring(0, command.length() - 3);

      for (int i = command.indexOf(" "); i >= 0; i = command.indexOf(" "))
         command = command.substring(0, i) + command.substring(i + 1);

      if ((command.length() < 2) || Character.isUpperCase(command.charAt(0)))
         return command;
      else
         return command.substring(0, 1).toUpperCase() + command.substring(1);
   }

   /**
    * Boolean target is an implementation of CommandTarget
    * for command targets which can be swithced on/off. (Typically
    * corresponding to JCheckBoxMenuItem or JRadioButtonMenuItem).
    */
   protected class BooleanTarget implements BooleanCommandTarget
   {
      private Method m_command;
      private Method m_enable;

      BooleanTarget(Method command, Method enable)
      {
         m_command = command;
         m_enable = enable;
         try
         {
            command.setAccessible(true);
            if (enable != null) enable.setAccessible(true);
         }
         catch (SecurityException x) {}
      }

      public CommandGroup getGroup()
      {
         return CommandProcessor.this;
      }

      public void doCommand(boolean arg) throws CommandInvocationException
      {
         try
         {
            Object[] args = { new Boolean(arg) };
            invokeCommand(m_command, args);
         }
         catch (IllegalAccessException x)
         {
            throw new RuntimeException("IllegalAccessException during command invocation");
         }
         catch (InvocationTargetException x)
         {
            throw new CommandInvocationException(x.getTargetException());
         }
      }

      public void enable(CommandState state)
      {
         if (m_enable == null)
            state.setEnabled(true);
         else
         {
            try
            {
               Object[] args = { (BooleanCommandState) state };
               invokeEnable(m_enable, args);
            }
            catch (IllegalAccessException x)
            {
               state.setEnabled(false);
            }
            catch (InvocationTargetException x)
            {
               state.setEnabled(false);
            }
         }
      }

      public void invoke(boolean arg)
      {
         CommandProcessor.this.invokeCommand(this, arg);
      }
   }

   /**
    * A SimpleTarget is an implementation of CommandTarget
    *
    */
   protected class SimpleTarget implements SimpleCommandTarget
   {
      private Method m_command;
      private Method m_enable;

      SimpleTarget(Method command, Method enable)
      {
         m_command = command;
         m_enable = enable;
         try
         {
            command.setAccessible(true);
            if (enable != null) enable.setAccessible(true);
         }
         catch (SecurityException x) {}
      }

      public CommandGroup getGroup()
      {
         return CommandProcessor.this;
      }

      public void doCommand() throws CommandInvocationException
      {
         try
         {
            invokeCommand(m_command, noArgs);
         }
         catch (IllegalAccessException x)
         {
            throw new RuntimeException("IllegalAccessException during command invocation");
         }
         catch (InvocationTargetException x)
         {
            throw new CommandInvocationException(x.getTargetException());
         }
      }

      public void enable(CommandState state)
      {
         if (m_enable == null)
            state.setEnabled(true);
         else
         {
            try
            {
               Object[] args = { state };
               invokeEnable(m_enable, args);
            }
            catch (IllegalAccessException x)
            {
               state.setEnabled(false);
            }
            catch (InvocationTargetException x)
            {
               state.setEnabled(false);
            }
         }
      }

      public void invoke()
      {
         CommandProcessor.this.invokeCommand(this);
      }
   }
}
