/*
 *   This program 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 3 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, see <http://www.gnu.org/licenses/>.
 */

/*
 * XMLSerializationMethodHandler.java
 * Copyright (C) 2004-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core.xml;

import java.lang.reflect.Method;

import org.w3c.dom.Element;

import weka.core.RevisionHandler;
import weka.core.RevisionUtils;


/**
 * This class handles relationships between display names of properties 
 * (or classes) and Methods that are associated with them. It differentiates 
 * between read and write methods. It automatically stores public methods that 
 * have the same signature as the <code>readFromXML()</code> and 
 * <code>writeToXML()</code> methods in the <code>XMLSerialization</code>
 * class.  
 *  
 * @see MethodHandler
 * @see XMLSerialization
 * 
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 8034 $ 
 */
public class XMLSerializationMethodHandler
   implements RevisionHandler {
  
   /** for storing read methods */
   protected MethodHandler m_ReadMethods = null;
   
   /** for storing write methods */
   protected MethodHandler m_WriteMethods = null;
   
   /** the object to retrieve the methods from */
   protected Object owner = null;
   
   /**
    * initializes the method handling, executes also <code>clear()</code>, which
    * adds initial methods automatically.
    * 
    * @param owner the owner to retrieve the methods from
    * @throws Exception if initialization fails
    * @see #clear() 
    */
   public XMLSerializationMethodHandler(Object owner) throws Exception {
      super();
      
      this.owner     = owner;
      m_ReadMethods  = new MethodHandler();
      m_WriteMethods = new MethodHandler();
      
      clear();
   }
   
   /**
    * adds all methods that are like <code>template</code> to the method list
    * 
    * @param handler the list to add fitting methods to
    * @param template the signature to check the given methods against
    * @param methods the methods to check
    */
   protected void addMethods(MethodHandler handler, Method template, Method[] methods) {
      int            i;
      int            n;
      Method         method;
      boolean        equal;
      String         name;
      
      for (i = 0; i < methods.length; i++) {
         method = methods[i];
         
         // is it template?
         if (template.equals(method))
            continue;
         
         // tests
         // 1. return type
         if (!template.getReturnType().equals(method.getReturnType()))
            continue;
            
         // 2. signature
         if (template.getParameterTypes().length != method.getParameterTypes().length)
            continue;
         
         equal = true;
         for (n = 0; n < template.getParameterTypes().length; n++) {
            if (!template.getParameterTypes()[n].equals(method.getParameterTypes()[n])) {
               equal = false;
               break;
            }
         }
            
         // add to list
         if (equal) {
            name = method.getName();
            name = name.replaceAll("read|write", "");
            name = name.substring(0, 1).toLowerCase() + name.substring(1);
            handler.add(name, method);
         }
      }
   }
   
   /**
    * automatically adds all fitting methods to the custom read/write lists,
    * it excludes only the generic ones. it is automatically called in 
    * <code>clear()</code>
    * It only work with methods that apply to the naming rule "read" + property
    * name (same for "write")
    *
    * @throws Exception if retrieving of methods fails
    * @see #clear()
    */
   protected void addMethods() throws Exception {
      Method         method;
      Class[]        params;
      
      // read
      params    = new Class[1];
      params[0] = Element.class;
      method    = owner.getClass().getMethod("readFromXML", params);
      addMethods(m_ReadMethods, method, owner.getClass().getMethods());
      
      // write
      params    = new Class[3];
      params[0] = Element.class;
      params[1] = Object.class;
      params[2] = String.class;
      method    = owner.getClass().getMethod("writeToXML", params);
      addMethods(m_WriteMethods, method, owner.getClass().getMethods());
   }
   
   /**
    * returns the method with the given name that has the same signature as
    * <code>readFromXML()</code> of the <code>XMLSerialiation</code> class. 
    * simplifies the adding of custom methods.
    * 
    * @param o the object to inspect
    * @param name the name of the method to return
    * @return either <code>null</code> if no method was found or a reference 
    * @see XMLSerialization#readFromXML(Element)
    */
   public static Method findReadMethod(Object o, String name) {
      Class[]        params;
      Method         result;
      
      result    = null;
      
      params    = new Class[1];
      params[0] = Element.class; 
      try {
         result = o.getClass().getMethod(name, params);
      }
      catch (Exception e) {
         result = null;  
      }
      
      return result;
   }
   
   /**
    * returns the method with the given name that has the same signature as
    * <code>writeToXML()</code> of the <code>XMLSerialiation</code> class. 
    * simplifies the adding of custom methods.
    * 
    * @param o the object to inspect
    * @param name the name of the method to return
    * @return either <code>null</code> if no method was found or a reference 
    * @see XMLSerialization#writeToXML(Element, Object, String)
    */
   public static Method findWriteMethod(Object o, String name) {
      Class[]        params;
      Method         result;
      
      result    = null;
      
      params    = new Class[3];
      params[0] = Element.class; 
      params[1] = Object.class; 
      params[2] = String.class; 
      try {
         result = o.getClass().getMethod(name, params);
      }
      catch (Exception e) {
         result = null;  
      }
      
      return result;
   }
   
   /**
    * removes all current methods and adds the methods according to the 
    *  
    */
   public void clear() {
      m_ReadMethods.clear();
      m_WriteMethods.clear();
      
      try {
         addMethods();
      }
      catch (Exception e) {
         e.printStackTrace();
      }
   }
   
   /**
    * returns the handler for read methods
    * 
    * @return the methodhandler for read methods
    */
   public MethodHandler read() {
      return m_ReadMethods;
   }
   
   /**
    * returns the handler for write methods
    * 
    * @return the methodhandler for read methods
    */
   public MethodHandler write() {
      return m_WriteMethods;
   }
   
   /**
    * adds read and write methods for the given class, i.e., read&;lt;name&gt;
    * and write&lt;name&gt; ("name" is prefixed by read and write)
    * 
    * @param handler  the handler class that contains the read and write method
    * @param cls      the class to register the read and write method for
    * @param name     the suffix of the read and write method
    */
   public void register(Object handler, Class cls, String name) {
     read().add(cls, XMLSerializationMethodHandler.findReadMethod(handler, "read" + name));
     write().add(cls, XMLSerializationMethodHandler.findWriteMethod(handler, "write" + name));
   }
   
   /**
    * returns the read and write method handlers as string
    * 
    * @return the read/write method handlers as string 
    */
   public String toString() {
      return "Read Methods:\n" + read() + "\n\n" + "Write Methods:\n" + write();
   }
   
   /**
    * Returns the revision string.
    * 
    * @return		the revision
    */
   public String getRevision() {
     return RevisionUtils.extract("$Revision: 8034 $");
   }
}
