/* 
 * <file> 
 *  Name:    KeyValueMap.js
 *  Purpose: Creates a Key-Value map which maps string keys
 *           to arrays of string values.
 *  
 *  Created: 1999-09-01
 *  $Id: KeyValueMap.js,v 1.1.2.2 2003/11/19 16:01:37 ckoch Exp $
 * </file> 
*/ 

//------------------------------------------------------------
/** 
 * A class for managing maps from string keys to arrays
 * of values for parameter definitions.
 * 
 * @author Christian Fessl
 * @author Simon Zwoelfer
**/ 
//------------------------------------------------------------
// <JSClass Name="KeyValueMap"> 

  //----------------------------------------------------------
  /** 
   * Constructor for this class.
   * @examplecall my_obj = new KeyValueMap()
  **/ 
  function KeyValueMap ( theParams )
  {
    if ( theParams == "__proto__" && typeof theParams == "string" )
      return;
 
    this.map_ = new Object(); // hold the key-value pairs
    this.serialize_ = false; // do not serialize values by default
    this.valueSort_ = true // sort values of each key before sending by default
  }
	
  //===========================================================
  // prototyping
  
  // private members
  KeyValueMap.prototype._solveFirewallProblem      = KeyValueMap__solveFirewallProblem;
  KeyValueMap.prototype._getParameterStringFromUrl = KeyValueMap__getParameterStringFromUrl;

  // public members
  KeyValueMap.prototype.addKeyValue            = KeyValueMap_addKeyValue;
  KeyValueMap.prototype.addParameterString     = KeyValueMap_addParameterString;
  KeyValueMap.prototype.addUrlParameters       = KeyValueMap_addUrlParameters;
  KeyValueMap.prototype.clone                  = KeyValueMap_clone;
  KeyValueMap.prototype.containsKey            = KeyValueMap_containsKey;
  KeyValueMap.prototype.containsKeyValue       = KeyValueMap_containsKeyValue;
  KeyValueMap.prototype.deleteKey              = KeyValueMap_deleteKey;
  KeyValueMap.prototype.getAllValues           = KeyValueMap_getAllValues;
  KeyValueMap.prototype.getKeys                = KeyValueMap_getKeys;
  KeyValueMap.prototype.getNumberOfKeys        = KeyValueMap_getNumberOfKeys;
  KeyValueMap.prototype.getNumberOfValues      = KeyValueMap_getNumberOfValues;
  KeyValueMap.prototype.getOneValue            = KeyValueMap_getOneValue;
  KeyValueMap.prototype.getParameterString     = KeyValueMap_getParameterString;
  KeyValueMap.prototype.setKeyValue            = KeyValueMap_setKeyValue;
  KeyValueMap.prototype.enableSerializing      = KeyValueMap_enableSerializing;
  KeyValueMap.prototype.disableSerializing     = KeyValueMap_disableSerializing;
  KeyValueMap.prototype.enableSort             = KeyValueMap_enableSort;
  KeyValueMap.prototype.disableSort            = KeyValueMap_disableSort;
  KeyValueMap.prototype.cutParametersFromUrl   = KeyValueMap_cutParametersFromUrl;

  // public members for debugging purposes only
  KeyValueMap.prototype.debugGetObjectStatus   = KeyValueMap_debugGetObjectStatus;

  //===========================================================
  // definition of the member functions

  //-----------------------------------------------------------
  /**
   * Solves some problems occuring by bad firewalls.
   * ";" --> "%3B" and "&" --> "%26" were converted by some firewalls.
   * Now they are converted back to their original values.
   * 
   * @param  String: aParametersString: the parameters part of an URL
   * @return String: the converted parameters part of the URL
  **/
  function KeyValueMap__solveFirewallProblem(aParametersString)
  {
    var converted_par_str = "";
    var split = aParametersString.split("=");
    
    if (split.length > 0)
    {
      converted_par_str += split[0] + "=";
      for (var k=0; k<split.length; k++)
      {
        var and_position = split[k].lastIndexOf("%26");            
        if (and_position != -1)
        {
          var prev_value = split[k].substring(0,and_position);
          var next_key   = split[k].substr(and_position+3);
          converted_par_str += prev_value + "&" +  next_key + "=";
        }
      }
      converted_par_str += split[split.length-1];
    }

    return converted_par_str;
  }

  //-----------------------------------------------------------
  /**
   * Returns only the parameter part of an URL.
   * @param  String: anUrl: the URL called by the browser
   * @return String: the parameter string of the URL
   * @examplecall 
   *   this._getKeyValueMapFromUrl("http://www.server.com;p1=1&p2=2&p3=3")
   *   results in "p1=1&p2=2&p3=3"
  **/
  function KeyValueMap__getParameterStringFromUrl(anUrl)
  {
    var par_str = "";

    var found_position_of_questionmark = anUrl.indexOf("?");
    var found_position_of_semicolon = anUrl.indexOf(";");
    var found_position_of_unescaped_semicolon = anUrl.indexOf("%3B");
    
    if (found_position_of_questionmark != -1)
      par_str = anUrl.substr(found_position_of_questionmark + 1);
    else 
    {
      if (found_position_of_semicolon != -1)
        par_str = anUrl.substr(found_position_of_semicolon + 1);
      else
      {
        if (found_position_of_unescaped_semicolon != -1)
        { // firewall problem by ";" --> "%3B" and "&" --> "%26"
          par_str = anUrl.substr(found_position_of_unescaped_semicolon + 3);
          par_str = this._solveFirewallProblem(par_str);
        } // firewall problem solved
      }
    }

    return par_str;
  }

  //-----------------------------------------------------------
  /**
   * Adds a key value pair to the map. Note that all other
   * values remain in the map and are not changed. Regardless
   * to the existence of a same value the given value will
   * simply be added to the array of values for this key.
   * @param  String: aKey: contains the name which should have
   *   at leas the given value.
   * @param  String: aValue: is the value which should be
   *   added for the key.
   * @examplecall
   *   var my_params = new KeyValueField();
   *   my_params.addKeyValue ( "firstPar", 1 );
   *   my_params.addKeyValue ( "firstPar", 2 );
  **/
  function KeyValueMap_addKeyValue(aKey, aValue)  
  {
    if (this.map_[aKey])
    {
      this.map_[aKey][this.map_[aKey].length] = aValue;
    }
    else 
    {
      this.map_[aKey] = new Array();
      this.map_[aKey][0] = aValue;
    }
  }
  
  //-----------------------------------------------------------
  /**
   * Deletes a key and ist associated values from the map.
   * @param  String: aKey: the key which should be deleted.
   * @examplecall 
   *   var my_params = new KeyValueField();
   *   my_params.addUrlParameters ( window.location.href );
   *   
   *   // assure that there is no Key: "line":
   *   my_params.deleteKey ( "line" );
  **/
  function KeyValueMap_deleteKey(aKey)  
  {
    if (this.map_[aKey])
      delete this.map_[aKey];
  }
  
  //-----------------------------------------------------------
  /**
   * Assures that the map contains exactly one key (with the name
   * aKey) with one corresponsing value
   * @param  String: aKey: contains the name which should have 
   *   the given value.
   * @param  String: aValue: defines the value.
   * @examplecall
   *   var my_params = new KeyValueField();
   *   my_params.addKeyValue ( "firstPar", 1 );
   *   my_params.addKeyValue ( "firstPar", 2 );
   *   
   *   // ... some processing with my_params ...
   *   
   *   // now assure that firstPar has exactly one value:
   *   my_params.setKeyValue ( "firstPar", "only_this_value" );
      
  **/
  function KeyValueMap_setKeyValue(aKey, aValue)  
  {
    this.map_[aKey] = new Array();
    this.map_[aKey][0] = aValue;
  }
  
  //-----------------------------------------------------------
  /** 
   * Splits the parameter string into its keys and values
   * and adds them to the map as this is done in addUrlParameters.
   * Note: the values are added regardless to their existence within 
   * the map!
   * @param  String: theParams: the Parameters in String represenation
   * @return Object of type Parameters
   * @examplecall
   *   var my_par = new KeyValueMap ();
   *   my_par.addParameters ( "key1=value1&key1=value2&key2=value3" );
   *   
  **/ 
  function KeyValueMap_addParameterString ( theParams )
  {
    var theUrl = "?" + theParams;
    this.addUrlParameters ( theUrl );
  }

  //-----------------------------------------------------------
  /** 
   * Fetches all availabe parameters from the given URL string
   * and puts them into this KeyValueMap. The parameters are
   * detected with the following algorithm:
   * <ol>
   * <li>Detect the parameter string:
   *  <ul>
   *  <li>If there is a question mark, take the string after
   *      the question mark and process the parameters.
   *  <li>Otherwise: if there is a semicolon, treat all characters
   *      after the semicolon as parameter string and 
   *      process them.
   *  <li>Otherwise: if there is an escaped semicolon (which
   *      is in fact the string "%3B" then take the characters
   *      which follow this string. In this case take care
   *      of escaped "=" and "&" characters too.
   *  </ul>
   * <li>Extract the parameters by splitting the whole
   *     string by its "&" characters. Subsequently use
   *     the first "=" character within the resulting
   *     string fragments as end for the key string
   *     and beginning for the value string. The
   *     general form of parameter strings is then:
   *     <pre>
   *      key1=value11&key1=value12&key...
   *     </pre>
   * </ul>
   * Note: the values are added regardless to their existence within 
   * the map!   
   *     
   * @param  String: theUrl: the URL containing the parameters.
   *   which should be added.
   * @examplecall 
   *   // Client Example: get URL of window, add
   *   //   a parameter and make a link ...
   *   var my_par = new KeyValueMap ();
   *   my_par.addUrlParameters ( window.location.href );
   *   my_par.addKeyValue ( "key10", "value10" );
   *   document.writeln ( "<a href='" +
   *                      window.loaction.pathname + ";" +
   *                      my_par.getParameterString () );
  **/ 
  function KeyValueMap_addUrlParameters ( theUrl )
  {
             
    var my_get_params_string = this._getParameterStringFromUrl(theUrl);   
    if (my_get_params_string != "")
    { 
      var my_key_values_array = my_get_params_string.split("&");
      for (var i=0; i<my_key_values_array.length; i++)
      {
        var key_value_pair = my_key_values_array [i].split("=");
        if (key_value_pair.length == 2)
        {
          var temp_value = unescape(key_value_pair[1].replace (/\+/g, ' '));
          if (this.serialize_ == true)
            eval("var evaluated_value = " + temp_value);
          else 
            var evaluated_value = temp_value;
          this.addKeyValue(unescape(key_value_pair[0]), 
                           evaluated_value );
        }
      }
    } 
  }

  //-----------------------------------------------------------
  /** 
   * Checks if a given key exists which has at least the 
   * desired value.
   * @param String: aKey: contains the name of the parameter
   * @param Object: aValue: contains the desired value
   * @return Boolean: true, if the key aKey exists and one of 
   *   its values is equal to aValue. Otherwise false will be 
   *   returned.
   * @examplecall .containsKeyValue("my_key","my_value")
  **/ 
  function KeyValueMap_containsKeyValue(aKey, aValue)
  {
    var ret = false;
    if (this.map_[aKey])
    {
      for (var i=0; i<this.map_[aKey].length; i++)
      {
        if (this.map_[aKey][i] == aValue)
        {
          ret = true;
          break;
        }
      }
    }
    return ret;
  }

  //-----------------------------------------------------------
  /** 
   * Returns an array of strings containing all keys with at 
   * least one value (domain set).
   * @return Array: an array of strings containing the keys 
   *   which have at least one value.
   * @examplecall
   *   URL: http://www.server.com/module;p1=1&p1=2&p2=5&p3=10
   *   
   *   var my_params = new KeyValueMap();
   *   my_params.addUrlParameters ( location.href ); 
   *   var my_keys = my_params.getKeys(); 
   *   
   *   Result: my_keys = ["p1", "p2", "p3"]   
   **/ 
  function KeyValueMap_getKeys()
  {
    var ret = new Array();
    for (var x in this.map_)
    {
      ret[ret.length] = x;
    }
    return ret;
  }

  //-----------------------------------------------------------
  /** 
   * Returns the number of keys with at least one value which
   * are present.
   * @return Integer: number of keys with at least one value 
   * @examplecall
   *   var my_params = new KeyValueMap();
   *   my_params.addUrlParameters ( location.href ); 
   *   var number_of_keys = my_params.getNumberOfKeys();
   *   writeln("Number of Keys sent: " + number_of_keys);   
   **/ 
  function KeyValueMap_getNumberOfKeys()
  {
    var ret = 0;
    for (var x in this.map_)
    {
      ret = ret + 1;
    }
    return ret;
  }

  //-----------------------------------------------------------
  /** 
   * Checks if a given key with at least one value is present.
   * @param  String: aKey: contains the name of the parameter 
   * @return Boolean: true, if <i>aKey</i> has at least one
   *   value within the map.
   * @examplecall 
   *   var my_params = new KeyValueMap();
   *   if ( my_params.containsKey("param1") ) 
   *   { 
   *     writeln("param1 exists!"); 
   *   } 
  **/ 
  function KeyValueMap_containsKey(aKey)
  {
    var ret = false;
    if (this.map_[aKey])
    {
      ret = true;
    }
    return ret;
  }

  //-----------------------------------------------------------
  /** 
   * Returns the number of values of a given key. 
   * @param String: aKey: contains the name of the parameter 
   * @return Integer: representing the number of values to that key 
   * @examplecall
   *   var my_params = new KeyValueMap();
   *   my_params.addUrlParameters ( location.href ); 
   *   var number_of_values = my_params.getNumberOfValues("param1");
   *   writeln("Number of values to key: " + number_of_values);   
   **/ 
  function KeyValueMap_getNumberOfValues (aKey)
  {
    var ret = 0;
    if (this.map_[aKey])
    {
      ret = this.map_[aKey].length;
    }
    return ret;
  }

  //-----------------------------------------------------------
  /** 
   * Returns all values of a given key. 
   * @param  String: aKey: contains the name of the parameter 
   * @return Array: an array of strings containing all values 
   *   of the given key. 
   *   <b>Note</b> that this array can contain multiple 
   *   entries with the same value.
   * @examplecall 
   *   URL: http://www.server.com/module;p1=1&p1=2&p2=5&p3=10
   *   
   *   var my_params = new KeyValueMap();
   *   my_params.addUrlParameters ( location.href ); 
   *   my_values = my_params.getAllValues("p1"); 
   *   
   *   Result: my_values = ["1", "2"]
  **/ 
  function KeyValueMap_getAllValues(aKey)
  {
    var ret = new Array();
    if (this.map_[aKey])
    {
      ret = this.map_[aKey];
    }
    return ret;
  }

  //-----------------------------------------------------------
  /** 
   * If a key has exactly one value it will be returned, if there
   * are more than one values only one value will be selected in a
   * nondeterministic way.
   * @param String: aKey: contains the name of the parameter 
   * @return Object: contains one value of the given key 
   * @examplecall 
   *   var my_params = new KeyValueMap();
   *   my_params.addUrlParameters ( location.href ); 
   *   writeln( "param1: one value = " + my_params.getOneValue("param1") ); 
  **/ 
  function KeyValueMap_getOneValue(aKey)
  {
    var ret = null;
    if (this.map_[aKey])
    {
      if (this.map_[aKey].length > 0)
      {
        ret = this.map_[aKey][0];
      }
    }
    return ret;
  }

  //-----------------------------------------------------------
  /** 
   * Clones a KeyValueMap at the level of keys. This means, that
   * a new KeyValueMap will be returned with new keys BUT
   * references to the values. If the values are strings
   * or only key-manipulations are done this is sufficient.
   *
   * @param Array: theExclList: array of string keys which 
   *   should not be contained in the resulting map.
   * @return KeyValueMap: a new KeyValueMap containing all
   *   keys and their corresponding values but the keys in 
   *   the exclusion list
  **/
  function KeyValueMap_clone ( theExclList )
  {
    var out = new KeyValueMap;
    var look_up = {};
    if (theExclList) {
      for ( var i = 0; i < theExclList.length; ++i )
        look_up [theExclList [i]] = true;
    }

    var key_array = this.getKeys ();

    for ( var i = 0; i < key_array.length; ++i )
    {
      var key = key_array [i];
      if ( look_up [key] )
        continue;

      var values = this.getAllValues (key);
      for ( var j = 0; j < values.length; ++j )
        out.addKeyValue ( key, values [j] );
    }
    return out;    
  }

  //-----------------------------------------------------------
  /** 
   * Create a parameter-string which is unique for this 
   * set of parameters (sorted by keys and values). This
   * string can be appended to an URL for transmitting ...
   * Note: This function is the inverse function of
   *   addParameterString. It <i>can</i> be used
   *   to &quote;serialize&quote; the state of the
   *   object which can be later de-serialized with
   *   addParameterString of an empty object!
   * @return String: the key-value pairs for an URL
   * @examplecall
   *   // Client Example: get URL of window, add
   *   //   a parameter and make a link ...
   *   var my_par = new KeyValueMap ();
   *   
   *   my_par.addUrlParameters ( "key2=2&key2=1&key1=2" );
   *   writeln ( my_par.getParameterString () )
   *   // would give: "key1=2&key2=1&key2=2" !
  **/ 
  function KeyValueMap_getParameterString ()
  {
    var key_array = this.getKeys ();
    if (key_array.length==0) return "";
    var out_arr = [];
    key_array.sort ();

    for ( var i = 0; i < key_array.length; ++i )
    {
      var key = key_array [i];
      var values = this.getAllValues (key);
      if (this.valueSort_)
        values.sort ();
      for ( var j = 0; j < values.length; ++j ) {
        if (this.serialize_ == true)
          var temp_value = ObjectExt.toSource(values [j]);
        else 
          var temp_value = values[j];
        out_arr [out_arr.length] = 
          key + "=" + escape(temp_value).replace ( /\+/g, "%2B" );
      }
    }
    return "?"+out_arr.join ( "&" );
    
  }

  //-----------------------------------------------------------
  /** 
   * Returns a string containing a description of the status of that object.
   * @param  void
   * @return String: containing the status of the Parameter-object.
   * @examplecall .debugGetObjectStatus()
  **/ 
  function KeyValueMap_debugGetObjectStatus ()
  {
    var out_arr = [];
    var key_array = this.getKeys ();

    for ( var i = 0; i < key_array.length; ++i )
    {
      var key = key_array [i];
      out_arr [out_arr.length] = 
        "'" + key + "' => '" +
        this.getAllValues (key).join ( "', '" ) + "'";
    }
    return out_arr.join ( "\n" );
  }
    
  //------------------------------------------------------------
  /**
   * after calling this method serializing is activated.
   * all values of KeyValuePairs are serialized using ObjectExt.toSource8) while calling 
   * the method getParameterString. with this behaviour whole objects are represented as string
   * and can be sent to the serevr. When calling addUrlParameters those string representations 
   * are tried to be evaluated using "eval" before assigning as value to a specified key.
   *
   * @examplecall:
   * //CLIENT
   * var a1 = {p1:"hallo", p2:[1,"3"]};
   * var map1 = new KeyValueMap();
   * map1.enableSerializing();
   * map1.addKeyValue( "a", a1 );
   * var url = "test?" + map1.getParameterString();
   * // SERVER
   * var map2 = new KeyValueMap();
   * map2.enableSerializing();
   * map2.addUrlParameters( url );
   * var a2 = map2.getOneValue( "a" );
   * // results:
   * // a2 = {p1:"hallo", p2:[1,"3"]}
   * // but (a1 == a2) returns false!
  **/
  function KeyValueMap_enableSerializing()
  {
    this.serialize_ = true;
  }
  
  //------------------------------------------------------------
  /**
   * after calling this method serializing is deactivated.
   * @see KeyValueMap_enableSerializing() for further details.
  **/
  function KeyValueMap_disableSerializing()
  {
    this.serialize_ = false;
  }
  
  //------------------------------------------------------------
  /**
   * after calling this method all values of a given key are inserted
   * in sorted order when creating the parameter string (getParameterString()).
   * this is also the default behaviour.
  **/
  function KeyValueMap_enableSort()
  {
    this.valueSort_ = true;
  }
  
  //------------------------------------------------------------
  /**
   * after calling this method all values of a given key are inserted
   * in the order they appear when creating the parameter string (getParameterString()).
   * this is NOT the default behaviour (default: insert in sorted order).
  **/
  function KeyValueMap_disableSort()
  {
    this.valueSort_ = false;
  }
    

  function KeyValueMap_cutParametersFromUrl(anUrl)
  {
    var ind = anUrl.indexOf(";");
    var ind2 = anUrl.indexOf("?");
    if ((ind==-1) || ((ind2!=-1) && (ind2<ind))) ind=ind2;
    if (ind > -1){
      anUrl = anUrl.substring(0,ind);
    }
    return anUrl;
  }
// </JSClass>
//------------------------------------------------------------

/* End of KeyValueMap.js */

