1  /*
     2   * Copyright the original author or authors.
     3   * 
     4   * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   * 
     8   *      http://www.mozilla.org/MPL/MPL-1.1.html
     9   * 
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  import org.as2lib.data.holder.Map;
    18  import org.as2lib.data.holder.Iterator;
    19  import org.as2lib.data.holder.map.AbstractMap;
    20  import org.as2lib.data.holder.map.ValueMapIterator;
    21  import org.as2lib.data.holder.map.KeyMapIterator;
    22  
    23  /**
    24   * {@code PrimitiveTypeMap} can be used to map any primitive type key to any type
    25   * of value.
    26   * 
    27   * <p>Primitive data types are strings, numbers and booleans. When you use this map
    28   * you normally use it in conjunction with string keys, because you can use arrays
    29   * for numbers and for booleans you normally do not need a data holder at all.
    30   *
    31   * <p>Note that if you only want to map values to keys you can also do this using a
    32   * dynamic or untyped object. But if you also want to be able to iterate over your
    33   * keys and values in the correct order or if you want this iteration to be fast -
    34   * the for..in loop is quite slow - you should use this map.
    35   * 
    36   * <p>This class offers ordered mapping functionality. That means that the methods
    37   * {@link #getKeys} and {@link #getValues} return the keys and values in the order
    38   * they were put to the map and that the iterators returned by the methods
    39   * {@link #valueIterator} and {@link #keyIterator} also iterate over the keys and
    40   * values in the correct order.
    41   *
    42   * <p>This map offers two methods that help you find out whether it contains a
    43   * specific key or value. These two methods are {@link #containsKey} and
    44   * {@link #containsValue}.
    45   * 
    46   * <p>To get the data stored in this map you can use the {@link #getKeys},
    47   * {@link #getValues} and {@link #get} methods. If you want to iterate over the
    48   * values of this map you can use the iterators returned by the methods {@link #iterator}
    49   * or {@link #valueIterator}. If you want to iterate over the keys you can use the
    50   * iterator returned by the {@link #keyIterator} method.
    51   *
    52   * <p>To add key-value pairs to this map you can use the methods {@link #put} and
    53   * {@link #putAll}. The {@code putAll} method lets you add all key-value pairs
    54   * contained in the passed-in {@code map} to this map.
    55   * 
    56   * <p>To remove key-value pairs you can use the methods {@link #remove} and
    57   * {@link #clear}. The {@code remove} method deletes only the key-value pair
    58   * corresponding to the passed-in {@code key}, while the clear method removes all
    59   * key-value pairs.
    60   *
    61   * <p>There are two more methods you may need. The {@link #isEmpty} and the {@link #size}
    62   * method. These methods give you information about whether this map contains any
    63   * mappings and how many mappings it contains.
    64   *
    65   * <p>To change the string representation returned by the {@link #toString}
    66   * method you can set your own stringifier using the static
    67   * {@link AbstractMap#setStringifier} method.
    68   *
    69   * <p>Example:
    70   * <code>
    71   *   // constructs the map
    72   *   var map:Map = new PrimitiveTypeMap();
    73   *   map.put("key1", "value1");
    74   *   map.put("key2", "value2");
    75   *   map.put("key3", "value3");
    76   *   // uses the map
    77   *   trace(map.get("key1"));
    78   *   trace(map.get("key2"));
    79   *   trace(map.get("key3"));
    80   * </code>
    81   *
    82   * <p>Output:
    83   * <pre>
    84   *   value1
    85   *   value2
    86   *   value3
    87   * </pre>
    88   *
    89   * @author Simon Wacker
    90   */
    91  class org.as2lib.data.holder.map.PrimitiveTypeMap extends AbstractMap implements Map {
    92  
    93  	/** Makes the static variables of the super-class accessible through this class. */
    94  	private static var __proto__:Function = AbstractMap;
    95  	
    96  	/** Contains the mappings. */
    97  	private var map:Object;
    98  	
    99  	/** The key-index pairs. */
   100  	private var indexMap:Object;
   101  	
   102  	/** The keys stored in an array. */
   103  	private var keys:Array;
   104  	
   105  	/** The values stored in an array. */
   106  	private var values:Array;
   107  	
   108  	/**
   109  	 * Constructs a new {@code PrimitiveTypeMap} instance.
   110  	 *
   111  	 * <p>This map iterates over the passed-in {@code source} with the for..in loop and
   112  	 * uses the variables' names as key and their values as value. Variables that are
   113  	 * hidden from for..in loops will not be added to this map.
   114  	 * 
   115  	 * @param source (optional) an object that contains key-value pairs to populate this
   116  	 * map with
   117  	 */
   118  	public function PrimitiveTypeMap(source) {
   119  		map = new Object();
   120  		indexMap = new Object();
   121  		indexMap.__proto__ = undefined;
   122  		keys = new Array();
   123  		values = new Array();
   124  		populate(source);
   125  	}
   126  
   127  	/**
   128  	 * Checks if the passed-in {@code key} exists.
   129  	 *
   130  	 * <p>That means whether a value has been mapped to it.
   131  	 *
   132  	 * @param key the key to be checked for availability
   133  	 * @return {@code true} if the {@code key} exists else {@code false}
   134  	 */
   135  	public function containsKey(key):Boolean {
   136  		return (map[key] != undefined);
   137  	}
   138  	
   139  	/**
   140  	 * Checks if the passed-in {@code value} is mapped to a key.
   141  	 *
   142  	 * @param value the value to be checked for availability
   143  	 * @return {@code true} if the {@code value} is mapped to a key else {@code false}
   144  	 */
   145  	public function containsValue(value):Boolean {
   146  		var i:Number = keys.length;
   147  		while (--i-(-1)) {
   148  			if (values[i] == value) {
   149  				return true;
   150  			}
   151  		}
   152  		return false;
   153  	}
   154  	
   155  	/**
   156  	 * Returns an array that contains all keys that have a value mapped to it.
   157  	 * 
   158  	 * @return an array that contains all keys
   159  	 */
   160  	public function getKeys(Void):Array {
   161  		return keys.concat();
   162  	}
   163  	
   164  	/**
   165  	 * Returns an array that contains all values that are mapped to a key.
   166  	 *
   167  	 * @return an array that contains all mapped values
   168  	 */
   169  	public function getValues(Void):Array {
   170  		return values.concat();
   171  	}
   172  	
   173  	/**
   174  	 * Returns the value that is mapped to the passed-in {@code key}.
   175  	 *
   176  	 * @param key the key to return the corresponding value for
   177  	 * @return the value corresponding to the passed-in {@code key}
   178  	 */
   179  	public function get(key) {
   180  		return map[key];
   181  	}
   182  	
   183  	/**
   184  	 * Maps the given {@code key} to the {@code value}.
   185  	 *
   186  	 * <p>{@code null} and {@code undefined} values are allowed.
   187  	 *
   188  	 * @param key the key used as identifier for the {@code value}
   189  	 * @param value the value to map to the {@code key}
   190  	 * @return the value that was originally mapped to the {@code key} or {@code undefined}
   191  	 */
   192  	public function put(key, value) {
   193  		var result;
   194  		var i:Number = indexMap[key];
   195  		if (i == undefined) {
   196  			indexMap[key] = keys.push(key) - 1;
   197  			values.push(value);
   198  		} else {
   199  			result = values[i];
   200  			values[i] = value;
   201  		}
   202  		map[key] = value;
   203  		return result;
   204  	}
   205  	
   206  	/**
   207  	 * Copies all mappings from the passed-in {@code map} to this map.
   208  	 *
   209  	 * @param map the mappings to add to this map
   210  	 */
   211  	public function putAll(map:Map):Void {
   212  		var values:Array = map.getValues();
   213  		var keys:Array = map.getKeys();
   214  		var l:Number = keys.length;
   215  		for (var i:Number = 0; i < l; i = i-(-1)) {
   216  			put(keys[i], values[i]);
   217  		}
   218  	}
   219  	
   220  	/**
   221  	 * Removes the mapping from the given {@code key} to the value.
   222  	 *
   223  	 * @param key the key identifying the mapping to remove
   224  	 * @return the value that was originally mapped to the {@code key}
   225  	 */
   226  	public function remove(key) {
   227  		var result;
   228  		var i:Number = indexMap[key];
   229  		if (i != undefined) {
   230  			result = values[i];
   231  			map[key] = undefined;
   232  			indexMap[key] = undefined;
   233  			keys.splice(i, 1);
   234  			values.splice(i, 1);
   235  			// restore indexes in indexMap
   236  			for (var k:Number = i; k < keys.length; k++) {
   237  				indexMap[keys[k]]--;
   238  			}
   239  		}
   240  		return result;
   241  	}
   242  	
   243  	/**
   244  	 * Clears all mappings.
   245  	 */
   246  	public function clear(Void):Void {
   247  		map = new Object();
   248  		indexMap = new Object();
   249  		indexMap.__proto__ = undefined;
   250  		keys = new Array();
   251  		values = new Array();
   252  	}
   253  	
   254  	/**
   255  	 * Returns an iterator to iterate over the values of this map.
   256  	 *
   257  	 * @return an iterator to iterate over the values of this map
   258  	 * @see #valueIterator
   259  	 * @see #getValues
   260  	 */
   261  	public function iterator(Void):Iterator {
   262  		return (new ValueMapIterator(this));
   263  	}
   264  	
   265  	/**
   266  	 * Returns an iterator to iterate over the values of this map.
   267  	 *
   268  	 * @return an iterator to iterate over the values of this map
   269  	 * @see #iterator
   270  	 * @see #getValues
   271  	 */
   272  	public function valueIterator(Void):Iterator {
   273  		return iterator();
   274  	}
   275  	
   276  	/**
   277  	 * Returns an iterator to iterate over the keys of this map.
   278  	 *
   279  	 * @return an iterator to iterate over the keys of this map
   280  	 * @see #getKeys
   281  	 */
   282  	public function keyIterator(Void):Iterator {
   283  		return new KeyMapIterator(this);
   284  	}
   285  
   286  	/**
   287  	 * Returns the amount of mappings.
   288  	 *
   289  	 * @return the amount of mappings
   290  	 */
   291  	public function size(Void):Number {
   292  		return keys.length;
   293  	}
   294  	
   295  	/**
   296  	 * Returns whether this map contains any mappings.
   297  	 *
   298  	 * @return {@code true} if this map contains no mappings else {@code false}
   299  	 */
   300  	public function isEmpty(Void):Boolean {
   301  		return (size() < 1);
   302  	}
   303  	
   304  	/**
   305  	 * Returns the string representation of this map.
   306  	 * 
   307  	 * <p>The string representation is obtained using the stringifier returned by the
   308  	 * static {@link AbstractMap#getStringifier} method.
   309  	 *
   310  	 * @return the string representation of this map
   311  	 */
   312  	public function toString():String {
   313  		return getStringifier().execute(this);
   314  	}
   315  	
   316  }