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