View Javadoc
1 /* 2 * Copyright (c) 2003, Henri Yandell 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or 6 * without modification, are permitted provided that the 7 * following conditions are met: 8 * 9 * + Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * + Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * + Neither the name of Genjava-Core nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.generationjava.collections; 33 34 import java.util.Map; 35 import java.util.HashMap; 36 import java.util.Set; 37 import java.util.Collection; 38 import java.util.ArrayList; 39 import java.util.HashSet; 40 import java.util.Iterator; 41 42 // @date 2000-05-13 43 44 // There is a potential bug, but it is rare, and a fix is 45 // scheduled. Also there are many opportunities for optimisation. 46 /*** 47 * A map which nests maps depending on a separator in the key. 48 * The default case is to use the '.' character. 49 */ 50 public class FQMap implements Map { 51 52 private Map myMap = null; 53 private char myChar; 54 55 // unused atm. Use this to optimise for situations when 56 // FQ's are not being used. Basically count FQ's out and in, 57 // if possible. 58 private int numberFQMapValues = 0; 59 60 // create a FQ map built on top of this type of map. 61 public FQMap(Map m, char separator) { 62 m.clear(); 63 myMap = m; 64 myChar = separator; 65 } 66 67 // create a FQ map built on top of a HashMap, and 68 // separated by '.' 69 public FQMap() { 70 this(new HashMap(),'.'); 71 } 72 73 public FQMap(Map m) { 74 this(m,'.'); 75 } 76 public FQMap(char c) { 77 this(new HashMap(),c); 78 } 79 80 public String toString() { 81 return "{"+myMap+"}"; 82 } 83 84 public Map createEmptyMap() { 85 // TODO: make a new version of myMap 86 87 return new FQMap(myChar); 88 } 89 90 public char getSeperationChar() { 91 return myChar; 92 } 93 94 /*** 95 * returns an array of size 2. First element is a 96 * Collection of the values that are FQMaps, second 97 * element is the values that aren`t FQMaps. 98 */ 99 public Collection[] getSeparatedValues() { 100 Collection col = myMap.values(); 101 if(col == null) { 102 return null; 103 } 104 ArrayList fqmaps = null; 105 ArrayList nonfqmaps = null; 106 107 Iterator it = col.iterator(); 108 while(it.hasNext()) { 109 Object ob = it.next(); 110 if(ob instanceof FQMap) { 111 if(fqmaps == null) { 112 fqmaps = new ArrayList(); 113 } 114 fqmaps.add(ob); 115 } else { 116 if(nonfqmaps == null) { 117 nonfqmaps = new ArrayList(); 118 } 119 nonfqmaps.add(ob); 120 } 121 } 122 Collection[] retVal = new Collection[2]; 123 retVal[0] = fqmaps; 124 retVal[1] = nonfqmaps; 125 return retVal; 126 } 127 128 /* map interface */ 129 130 // Removes all mappings from this map (optional operation). 131 public void clear() { 132 myMap.clear(); 133 } 134 135 // Returns true if this map contains a mapping for the specified key. 136 public boolean containsKey(Object key) { 137 return (get(key) == null); 138 } 139 140 // Returns true if this map maps one or more keys to the specified value. 141 public boolean containsValue(Object value) { 142 return (values().contains(value)); 143 } 144 145 // Returns a set view of the mappings contained in this map. 146 public Set entrySet() { 147 Set keys = keySet(); 148 if(keys == null) { 149 return null; 150 } 151 152 HashSet retSet = new HashSet(); 153 154 Iterator it = keys.iterator(); 155 while(it.hasNext()) { 156 Object key = it.next(); 157 retSet.add(new FQEntry(this,key)); 158 } 159 160 return retSet; 161 } 162 163 // Compares the specified object with this map for equality. 164 public boolean equals(Object o) { 165 return myMap.equals(o); // FIX:? 166 } 167 168 // Returns the value to which this map maps the specified key. 169 public Object get(Object key) { 170 if(key instanceof String) { 171 String keyStr = (String)key; 172 int idx = keyStr.indexOf(myChar); 173 if(idx == -1 || idx == keyStr.length() - 1) { 174 // it's top level. 175 return myMap.get(key); 176 } else { 177 String first = keyStr.substring(0,idx); 178 Object ob = myMap.get(first); 179 Map subMap = null; 180 if(ob != null) { 181 if(ob instanceof Map) { 182 subMap = (Map)ob; 183 return subMap.get(keyStr.substring(idx+1)); 184 } else { 185 return null; 186 } 187 } else { 188 return null; 189 } 190 } 191 } else { // handle non-string keys 192 return myMap.get(key); 193 } 194 } 195 196 // Returns the hash code value for this map. 197 public int hashCode() { 198 return myMap.hashCode(); // FIX:? 199 } 200 201 // Returns true if this map contains no key-value mappings. 202 public boolean isEmpty() { 203 return myMap.isEmpty(); 204 } 205 206 /*** 207 * Returns a set view of the keys contained in this map. 208 * BUG: If a FQMap is intentionally added to an FQMap, rather 209 * than created through a fully qualified key, such that 210 * put(someObj,FQMap) is effectively done, and then this top 211 * level FQMap is added to another, either intentionally or 212 * under the fully qualified scheme, then the keySet will 213 * contain the stringied version of someObj, which means 214 * that a request with this key will _not_ work, unless 215 * someObj's hashCode() and equals() are such that someObj 216 * equals the stringified version of someObj. 217 * 218 * To this end, a fix is to wrap all keys coming back out in 219 * a 'FQKey' object which contains an ArrayList of all subkeys. 220 * Code would need changing so a value may be obtained using 221 * this FQKey. 222 * 223 * Also to note, the stringified form of an object is fully 224 * qualified, using '.' as the package/class seperator. 225 * ie) the default behaviour of this class. 226 */ 227 public Set keySet() { 228 Set keys = myMap.keySet(); 229 if(keys == null) { 230 return null; 231 } 232 233 HashSet retSet = new HashSet(); 234 Iterator it = keys.iterator(); 235 while(it.hasNext()) { 236 Object key = it.next(); 237 Object value = myMap.get(key); 238 if(value instanceof FQMap) { 239 Set subkeys = ((FQMap)value).keySet(); 240 if(subkeys != null) { 241 Iterator subIt = subkeys.iterator(); 242 while(subIt.hasNext()) { 243 retSet.add(""+key+myChar+subIt.next()); 244 } 245 } 246 } else { 247 retSet.add(key); 248 } 249 } 250 return retSet; 251 } 252 253 // Associates the specified value with the specified key in this map (optional operation). 254 public Object put(Object key, Object value) { 255 if(key instanceof String) { 256 String keyStr = (String)key; 257 int idx = keyStr.indexOf(myChar); 258 if(idx == -1 || idx == keyStr.length() - 1) { 259 // it's top level. 260 return myMap.put(key,value); 261 } else { 262 String first = keyStr.substring(0,idx); 263 Object ob = myMap.get(first); 264 Map subMap = null; 265 if(ob != null) { 266 if(ob instanceof Map) { 267 subMap = (Map)ob; 268 return subMap.put(keyStr.substring(idx+1),value); 269 } else { 270 subMap = createEmptyMap(); 271 subMap.put(keyStr.substring(idx+1),value); 272 myMap.put(first,subMap); 273 return ob; 274 } 275 } else { 276 subMap = createEmptyMap(); 277 subMap.put(keyStr.substring(idx+1),value); 278 myMap.put(first,subMap); 279 return ob; 280 } 281 } 282 } else { // handle non-string keys 283 return myMap.put(key,value); 284 } 285 } 286 287 // Copies all of the mappings from the specified map to this map (optional operation). 288 public void putAll(Map t) { 289 if(t == null) { 290 return; 291 } 292 293 Set keys = t.keySet(); 294 295 if(keys == null) { 296 return; 297 } 298 299 Iterator it = keys.iterator(); 300 while(it.hasNext()) { 301 Object ob = it.next(); 302 put(ob,t.get(ob)); 303 } 304 } 305 306 // Removes the mapping for this key from this map if present (optional operation). 307 /*** 308 * Unimplemented. 309 */ 310 public Object remove(Object key) { 311 return null; // FIX: 312 } 313 314 // Returns the number of key-value mappings in this map. 315 public int size() { 316 if(myMap.size() == 0) { 317 return 0; 318 } 319 320 Collection[] values = getSeparatedValues(); 321 322 int ret_int = 0; 323 if(values[0] != null) { 324 Iterator it = values[0].iterator(); 325 while(it.hasNext()) { 326 FQMap map = (FQMap)it.next(); 327 ret_int += map.size(); 328 } 329 } 330 if(values[1] != null) { 331 ret_int += values[1].size(); 332 } 333 return ret_int; 334 } 335 336 // Returns a collection view of the values contained in this map. 337 public Collection values() { 338 Collection[] values = getSeparatedValues(); 339 if(values == null) { 340 return null; 341 } 342 ArrayList retList = null; 343 if(values[0] != null) { 344 if(retList == null) { 345 retList = new ArrayList(); 346 } 347 Iterator it = values[0].iterator(); 348 while(it.hasNext()) { 349 FQMap map = (FQMap)it.next(); 350 retList.addAll(map.values()); 351 } 352 } 353 if(values[1] != null) { 354 if(retList == null) { 355 retList = new ArrayList(); 356 } 357 retList.addAll(values[1]); 358 } 359 return retList; 360 } 361 362 } 363 // assumes u can`t have null keys. 364 class FQEntry implements Map.Entry { 365 366 private Map myMap = null; 367 private Object myKey = null; 368 369 public FQEntry(Map map, Object key) { 370 myMap = map; 371 myKey = key; 372 } 373 374 // Compares the specified object with this entry for equality. 375 public boolean equals(Object o) { 376 if(o == null) { 377 return false; 378 } 379 if( o instanceof FQEntry ) { 380 FQEntry fqe = (FQEntry)o; 381 Object key = getKey(); 382 Object okey = fqe.getKey(); 383 Object value = getValue(); 384 Object ovalue = fqe.getValue(); 385 386 if(okey == null && key == null) { 387 // null key assumption. doesn`t check value. 388 return true; 389 } 390 391 if(getKey().equals(fqe.getKey())) { 392 if(ovalue == null && value == null) { 393 return true; 394 } 395 if(getValue().equals(fqe.getValue())) { 396 return true; 397 } 398 } 399 } 400 return false; 401 } 402 403 // Returns the key corresponding to this entry. 404 public Object getKey() { 405 return myKey; 406 } 407 408 // Returns the value corresponding to this entry. 409 public Object getValue() { 410 if(myMap != null) { 411 return myMap.get(myKey); 412 } else { 413 return null; 414 } 415 } 416 417 // Returns the hash code value for this map entry. 418 public int hashCode() { 419 int n = 0; 420 421 if(myKey != null) { 422 n += myKey.hashCode(); 423 } 424 Object ob = getValue(); 425 if(ob != null) { 426 n &= ob.hashCode(); 427 } 428 return n; 429 } 430 431 // Replaces the value corresponding to this entry with the specified value (optional operation). 432 public Object setValue(Object value) { 433 if(myMap != null) { 434 return myMap.put(getKey(),value); 435 } else { 436 return null; // WHAT TO DO?? 437 } 438 } 439 440 public String toString() { 441 return ""+getKey()+":"+getValue(); 442 } 443 444 }

This page was automatically generated by Maven