/*
 *   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/>.
 */

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

package weka.core.xml;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;

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

/**
 * This class stores information about properties to ignore or properties
 * that are allowed for a certain class.
 * 
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 8034 $ 
 */
public class PropertyHandler
   implements RevisionHandler {
  
   /** 
    * contains display names of properties to ignore in the serialization
    * process
    * 
    * @see #ignored()
    * @see #addIgnored(String)
    * @see #removeIgnored(String)
    * @see #isIgnored(String)
    */
  protected Hashtable<Object,HashSet<String>> m_Ignored = null;
   
   /**
    * lists for a class the properties allowed to use for setting and getting.
    * if a class is not listed, then all get/set-methods are allowed.<br>
    * Mapping: classname (String) - properties (HashSet, containing the Property-Names)
    * 
    * @see #allowed()
    * @see #addAllowed(Class,String)
    * @see #removeAllowed(Class,String)
    * @see #isAllowed(Class,String)
    */
  protected Hashtable<Object,HashSet<String>> m_Allowed = null;

   /**
    * initializes the handling 
    */
   public PropertyHandler() {
      super();

      m_Ignored = new Hashtable<Object,HashSet<String>>();
      m_Allowed = new Hashtable<Object,HashSet<String>>();
   }
   
   /**
    * returns an enumeration of the stored display names and classes of 
    * properties to ignore.<br> 
    * <b>NOTE:</b> String and Class Objects are mixed in this enumeration, depending
    * whether it is a global property to ignore or just one for a certain class!
    * 
    * @return the display names and classes
    * @see #m_Ignored
    */
   public Enumeration ignored() {
      return m_Ignored.keys();
   }
   
   /**
    * adds the given display name of a property to the ignore list. Can either
    * be a complete path (e.g. <code>__root__.options</code>) or only a 
    * property name (e.g. <code>options</code>). In the latter case it matches 
    * all occurences of this display name.
    * 
    * @param displayName the property to ignore
    * @see #m_Ignored 
    */
   public void addIgnored(String displayName) {
      HashSet<String>        list;
      
      list = new HashSet<String>();
      list.add(displayName);
      
      m_Ignored.put(displayName, list);
   }
   
   /**
    * adds the given class with the display name of a property to the ignore list.
    * I.e. this property is only ignored for this class. 
    * 
    * @param c the class for which a property is to be ignored
    * @param displayName the property to ignore
    * @see #m_Ignored 
    */
   public void addIgnored(Class c, String displayName) {
      HashSet<String>        list;
      
      // retrieve list
      if (m_Ignored.contains(c)) {
         list = (HashSet<String>) m_Ignored.get(c);
      }
      else {
         list = new HashSet<String>();
         m_Ignored.put(c, list);
      }
      
      list.add(displayName);
   }
   
   /**
    * removes the given display name from the ignore list. returns whether the 
    * removing was succesful, i.e. whether the display name was in the list.
    * 
    * @param displayName the property to remove from the ignore list
    * @return whether the ignore list contained the specified property
    * @see #m_Ignored
    */
   public boolean removeIgnored(String displayName) {
      return (m_Ignored.remove(displayName) != null);
   }
   
   /**
    * removes the given display name from the ignore list of the class. 
    * returns whether the removing was succesful, i.e. whether the display 
    * name was in the list.
    * 
    * @param c the class to remove the property from
    * @param displayName the property to remove from the ignore list
    * @return whether the ignore list contained the specified property
    * @see #m_Ignored
    */
   public boolean removeIgnored(Class c, String displayName) {
      HashSet        list;
      
      // retrieve list
      if (m_Ignored.contains(c))
         list = (HashSet) m_Ignored.get(c);
      else
         list = new HashSet();
      
      return list.remove(displayName);
   }
   
   /**
    * checks whether the given display name is an ignored property
    * 
    * @param displayName the property to check whether it is on the ignore list
    * @return whether the property is in the ignored list
    * @see #m_Ignored
    */
   public boolean isIgnored(String displayName) {
      return m_Ignored.containsKey(displayName);
   }
   
   /**
    * checks whether the given display name of a certain class is an ignored 
    * property. It only checks for this certain class and no derivative classes.
    * If you also want to check for derivative classes, use 
    * <code>isIgnored(Object,String)</code>. 
    * 
    * @param c the class to check the property for
    * @param displayName the property to check whether it is on the ignore list
    * @return whether the property is in the ignored list
    * @see #m_Ignored
    * @see #isIgnored(Object, String)
    */
   public boolean isIgnored(Class c, String displayName) {
      HashSet        list;
      
      // retrieve list
      if (m_Ignored.containsKey(c))
         list = (HashSet) m_Ignored.get(c);
      else
         list = new HashSet();

      return list.contains(displayName);
   }
   
   /**
    * checks whether the given display name of a given object is an ignored 
    * property. The object is checked for each stored class whether it is an 
    * <code>instanceof</code>. If the class is not stored then it will default
    * to <code>false</code>, since there are no restrictions for this class. 
    * 
    * @param o the object to check the property for
    * @param displayName the property to check whether it is on the ignore list
    * @return whether the property is in the ignored list
    * @see #m_Ignored
    */
   public boolean isIgnored(Object o, String displayName) {
      Enumeration    enm;
      Class          c;
      Object         element;
      boolean        result;
      HashSet        list;
      
      result = false;
      
      enm = ignored();
      while (enm.hasMoreElements()) {
         element = enm.nextElement();
         
         // has to be class! not a display name
         if (!(element instanceof Class))
            continue;
         
         c = (Class) element;
         
         // is it an instance of this class?
         if (c.isInstance(o)) {
            list   = (HashSet) m_Ignored.get(c);
            result = list.contains(displayName); 
            break;
         }
      }
      
      return result;
   }
   
   /**
    * returns an enumeration of the classnames for which only certain properties
    * (display names) are allowed
    * 
    * @return the classnames with restriction to properties
    */
   public Enumeration allowed() {
      return m_Allowed.keys();
   }
   
   /**
    * adds the given property (display name) to the list of allowed properties 
    * for the specified class.
    * 
    * @param c the class to add a property for
    * @param displayName the property to allow for the class
    * @see #m_Allowed
    */
   public void addAllowed(Class c, String displayName) {
      HashSet<String>        list;
      
      // retrieve list
      list = (HashSet<String>) m_Allowed.get(c);
      if (list == null) {
         list = new HashSet<String>();
         m_Allowed.put(c, list);
      }
      
      // add property
      list.add(displayName);
   }
   
   /**
    * removes the given property (display name) for the specified class from 
    * the list of allowed properties.
    * 
    * @param c the class to remove the property for
    * @param displayName the property to remove
    * @return whether the property was found
    * @see #m_Allowed
    */
   public boolean removeAllowed(Class c, String displayName) {
      boolean           result;
      HashSet           list;
      
      result = false;
      
      // retrieve list
      list = (HashSet) m_Allowed.get(c);
      
      // remove property
      if (list != null)
         result = list.remove(displayName);
      
      return result;
   }
   
   /**
    * returns whether the given property (display name) is allowed for the
    * given class. It only checks for this certain class and no derivative classes.
    * If you also want to check for derivative classes, use 
    * <code>isAllowed(Object,String)</code>.
    * 
    * @param c the class to check the property for
    * @param displayName the property (display name) to check
    * @return whether the property is allowed in that context
    * @see #m_Allowed
    * @see #isAllowed(Object, String) 
    */
   public boolean isAllowed(Class c, String displayName) {
      boolean        result;
      HashSet        list;
      
      result = true;
      
      // retrieve list
      list = (HashSet) m_Allowed.get(c);
      
      // check list
      if (list != null)
         result = list.contains(displayName);
      
      return result;
   }
   
   /**
    * returns whether the given property (display name) is allowed for the given
    * object . The object is checked for each stored class whether it is an 
    * <code>instanceof</code>. If the class is not stored then it will default
    * to <code>true</code>, since there are no restrictions for this class.
    * 
    * @param o the object to check the property for
    * @param displayName the property (display name) to check
    * @return whether the property is allowed in that context 
    */
   public boolean isAllowed(Object o, String displayName) {
      Enumeration    enm;
      Class          c;
      boolean        result;
      HashSet        list;
      
      result = true;
      
      enm = allowed();
      while (enm.hasMoreElements()) {
         c = (Class) enm.nextElement();
         
         // is it an instance of this class?
         if (c.isInstance(o)) {
            list   = (HashSet) m_Allowed.get(c);
            result = list.contains(displayName); 
            break;
         }
      }
      
      return result;
   }
   
   /**
    * Returns the revision string.
    * 
    * @return		the revision
    */
   public String getRevision() {
     return RevisionUtils.extract("$Revision: 8034 $");
   }
}
