/*
 * <copyright>
 *  Copyright (c) 2006 by Know-Center, Graz, Austria
 * </copyright>
 *
 *  This software is the confidential and proprietary information of Know-Center,
 *  Graz, Austria. You shall not disclose such Confidential Information and shall 
 *  use it only in accordance with the terms of the license agreement you entered 
 *  into with Know-Center.
 *
 *  KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 *  SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, 
 *  OR NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES
 *  SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 *  THIS SOFTWARE OR ITS DERIVATIVES.
 * 
 * $Id: PropertyTree.java,v 1.4 2006/10/31 08:06:28 wprinz Exp $
 */
package at.knowcenter.wag.egov.egiz.cfg;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

/**
 * This class can be used to store a property config tree. The property key are separated by the
 * {@link at.knowcenter.wag.egov.egiz.cfg.PropertyTree#SPLIT_STRING}. Therefore the keys an also
 * the values of a configuration is stored in nested hashes. The keys in an area are stored in a
 * HashMap. The values of a key are stored in a Vector to overload some keys. The property tree can
 * be used to extract sub nodes and sub keys of different tree levels.
 * 
 * @author wlackner
 * @see java.util.HashMap
 * @see java.util.Vector
 */
public class PropertyTree implements Serializable {

  /**
   * SVUID.
   */
  private static final long serialVersionUID = -1686170519955886222L;
  
  /**
   * The key split string. A key can be a complex key. Sub keys are separated from each other with
   * the split string. This string is used to devide the complex key.
   */
  public static final String SPLIT_STRING = "\\.";
  /**
   * Stores the key references to the sub nodes
   */
  private Map keys_ = new HashMap(3);
  /**
   * Stores all values of a node
   */
  private Vector values_ = new Vector(3);

  /**
   * The default constructor od the class.
   */
  public PropertyTree() {
  }

  /**
   * This method takes a key value tupel and store them in the property tree. The key splitted into
   * different levels (splitted by the string
   * {@link at.knowcenter.wag.egov.egiz.cfg.PropertyTree#SPLIT_STRING}). All subnodes not stored in
   * the tree will be created. The last part of the key (last splitted element) adds the value to
   * there own value data structure (Vector). <br />
   * <strong>Example: </strong> <code>setKeyValue("key.1_level.2_level","the value for k_1_2")</code
   * 
   * @param splitKey the key that has to be store the value
   * @param value only String values can be stored
   */
  public void setKeyValue(String splitKey, String value) {
    String[] keys = splitKey.split(SPLIT_STRING);
    PropertyTree curr_tree = this;
    for (int key_idx = 0; key_idx < keys.length; key_idx++) {
      String key = keys[key_idx];
      if (!curr_tree.containsNode(key)) {
        curr_tree.setSubTree(key, null);
      }
      if (key_idx < keys.length - 0)
        curr_tree = (PropertyTree) curr_tree.getSubTree(key);
    }
    curr_tree.addValue(value);
  }

  /**
   * Adds a String value to the current key
   * 
   * @param value
   */
  private void addValue(String value) {
    values_.add(value);
  }

  /**
   * This method takes a key as input value, split them into subnodes and return the sub tree of the
   * last node of the key. If the key or a sub node not found, the method return null. This means
   * the key is not part of the sub property tree.
   * 
   * @param splitKey the key that has to be found as sub node of the current node
   * @return the sub tree (PropertyTree) or <code>null</code> if the key is not a subtree referece
   */
  private PropertyTree getLastSubTree(String splitKey) {
    String[] keys = splitKey.split(SPLIT_STRING);
    PropertyTree curr_tree = this;
    for (int key_idx = 0; key_idx < keys.length; key_idx++) {
      String key = keys[key_idx];
      if (!curr_tree.containsNode(key)) {
        return null;
      }
      curr_tree = (PropertyTree) curr_tree.getSubNode(key);
    }
    return curr_tree;
  }

  /**
   * This method return the subtree that corresponds to a particular key. The key does not split.
   * Therefore the key must be a children of the current node. Search only in the key map of the
   * current node.
   * 
   * @param key the key that has to be a sub node
   * @return a sub tree (PropertyTree) or <code>null</code> if the key is not a children of the
   *         current node
   */
  private PropertyTree getSubNode(String key) {
    return (PropertyTree) keys_.get(key);
  }

  /**
   * Returns the last value (keys can be overloaded) of a key. The key are splitted into subnodes
   * and the last node of the key is the current value holder. If a key or subnode is not in the sub
   * tree the return value is <code>null.</code>
   * 
   * @param key the key that holds the value (can be a nested key like <code>"key.1.2.3"</code>)
   * @return the value of the key (last node of the key) or <code>null</code> otherwise
   */
  public String getLastValue(String key) {
    PropertyTree curr_tree = getLastSubTree(key);
    String result = null;
    if (curr_tree != null && !curr_tree.values_.isEmpty()) {
      result = (String) curr_tree.values_.lastElement();
    }
//    if (logger_.isDebugEnabled()) {
//      logger_.debug("getLastValue:" + key + "=" + result);
//    }
    return result;
  }

  /**
   * Returns the first value (keys can be overloaded) of a key. The key are splitted into subnodes
   * and the last node of the key is the current value holder. If a key or subnode is not in the sub
   * tree the return value is <code>null</code>.
   * 
   * @param key the key that holds the value (can be a nested key like <code>"key.1.2.3"</code>)
   * @return the value of the key (last node of the key) or <code>null</code> otherwise
   */
  public String getFirstValue(String key) {
    PropertyTree curr_tree = getLastSubTree(key);
    String result = null;
    if (curr_tree != null && !curr_tree.values_.isEmpty()) {
      result = (String) curr_tree.values_.firstElement();
    }
//    if (logger_.isDebugEnabled()) {
//      logger_.debug("getFirstValue:" + key + "=" + result);
//    }
    return result;
  }

  /**
   * This method return all values of the current node. The values are stored as String values.
   * 
   * @return the values (type String) of the current node
   * @see Vector
   */
  public Vector getValues() {
    return values_;
  }

  /**
   * This method return all keys (sub tree references) of the current node as a Map. The keys are
   * stored as String values.
   * 
   * @return the keys (type String) of the current node
   * @see Map
   */
  public Map getKeyEntries() {
    return keys_;
  }

  /**
   * This method return all keys (sub tree references) of the current node as an ArrayList. The keys
   * are stored as String values.
   * 
   * @return the keys (type String) of the current node
   * @see ArrayList
   */
  public ArrayList getKeys() {
    if (!keys_.isEmpty()) {
      Object[] objs = keys_.keySet().toArray();
      ArrayList keys = new ArrayList(objs.length);
      for (int idx = 0; idx < objs.length; idx++) {
        keys.add((String) objs[idx]);
      }
      return keys;
    }
    return null;
  }

  /**
   * 
   * This method return all sub tree references of a key as an ArrayList. The keys are stored as
   * String values.
   * 
   * @param key (can be a nested key like <code>"key.1.2.3"</code>)
   * @return the keys (type String) of the current node
   * @see ArrayList
   */
  public ArrayList getKeys(String key) {
    PropertyTree curr_tree = getLastSubTree(key);
    if (curr_tree != null) {
      return curr_tree.getKeys();
    }
    return null;
  }

  /**
   * This method return all values of a key. The values are stored as String values.
   * 
   * @param key (can be a nested key like <code>"key.1.2.3"</code>)
   * @return the values (type Vector) of the key or <code>null</code> if the key is not in the sub
   *         tree of the current node
   * @see Vector
   */
  public Vector getValues(String key) {
    PropertyTree curr_tree = getLastSubTree(key);
    if (curr_tree != null) {
      return curr_tree.values_;
    }
    return null;
  }

  /**
   * Store a sub tree (type PropertyTree) in the current node. The key and it's sub tree are stored
   * in a HashMap.
   * 
   * @param key the reference of the sub tree
   * @param tree the sub tree of the key
   * @see HashMap
   */
  private void setSubTree(String key, PropertyTree tree) {
    if (tree == null) {
      tree = new PropertyTree();
    }
    keys_.put(key, tree);
  }

  /**
   * Extracts a sub tree of a nested key. The Method returns the last sub tree of the nested key.
   * <strong>Example: </strong>if the key is like: <code>key.1.2.3</code> the sub tree of the last
   * node <code>3</code> is returned.
   * 
   * @param key the reference of the sub tree
   * @return a sub tree of the key or <code>null</code> if the key can not be found
   */
  public PropertyTree getSubTree(String key) {
    return getLastSubTree(key);
  }

  /**
   * This method checks if a key is a reference to a sub tree in the current node.
   * 
   * @param key a simple key that is a parent reference of a sub tree
   * @return true if the key is found, false otherwise
   */
  public boolean containsNode(String key) {
    return keys_.containsKey(key);
  }

  /**
   * The default toString method. It starts with the current node recursively downwards and return
   * the String representation of the node.
   * 
   * @return the string representation of the node
   */
  public String toString() {
    return toString("", this);
  }

  /**
   * This is a helper function to define the prefix for different levels in the toString method, not
   * realy nice ;-).
   * It replaces all "." chars with " ".
   * 
   * @param key
   * @return a replaces prefix string
   */
  private static String getEmptyString(String key) {
    return key.replaceAll(".", " ");
  }

  /**
   * This method concatenates all values of the current node and return them as a combinded string.
   * 
   * @param prefix
   * @param tree
   * @return the string representation of the node values
   */
  private static String printValues(String prefix, PropertyTree tree) {
    String os = "";
    Iterator values = tree.getValues().iterator();
    while (values.hasNext()) {
      String value = (String) values.next();
      os += prefix + "=" + value;
    }
    return os;
  }

  /**
   * The toString method. It starts with a special level prefix, sub tree and recursively adds all
   * sub trees.
   * 
   * @param prefix the prefix for this node
   * @param tree the current node
   * @return the string representation of the node
   */
  public static String toString(String prefix, PropertyTree tree) {
    String os = "";
    Iterator entries = tree.getKeyEntries().entrySet().iterator();
    while (entries.hasNext()) {
      Map.Entry entry = (Map.Entry) entries.next();
      String key = (String) entry.getKey();
      PropertyTree sub = (PropertyTree) entry.getValue();
      String os_key = "\n" + prefix + "." + key;
      os += printValues(os_key, sub);
      String subs = toString(prefix + getEmptyString(key) + " |", sub);
      if (subs.length() > 0) {
        os += os_key + "|" + subs;
      }
    }
    return os;
  }
}