Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
PropertiesMap |
|
| 2.372093023255814;2.372 | ||||
PropertiesMap$PropertyTree |
|
| 2.372093023255814;2.372 |
1 | /** | |
2 | * Copyright 2005-2011 The Kuali Foundation | |
3 | * | |
4 | * Licensed under the Educational Community License, Version 2.0 (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.opensource.org/licenses/ecl2.php | |
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 | package org.kuali.rice.core.api.util.collect; | |
17 | ||
18 | import org.apache.commons.lang.StringUtils; | |
19 | import org.apache.log4j.Logger; | |
20 | ||
21 | import java.util.ArrayList; | |
22 | import java.util.Collection; | |
23 | import java.util.Collections; | |
24 | import java.util.Iterator; | |
25 | import java.util.LinkedHashMap; | |
26 | import java.util.LinkedHashSet; | |
27 | import java.util.Map; | |
28 | import java.util.Properties; | |
29 | import java.util.Set; | |
30 | ||
31 | /** | |
32 | * This class implements the Map interface for a Properties instance. Exports all properties from the given Properties instance as | |
33 | * constants, usable from jstl. Implements the Map interface (by delegating everything to the PropertyTree, which really implements | |
34 | * the Map methods directly) so that jstl can translate ${Constants.a} into a call to ConfigConstants.get( "a" ). | |
35 | * <p> | |
36 | * The contents of this Map cannot be changed once it has been initialized. Any calls to any of the Map methods made before the | |
37 | * propertyTree has been initialized (i.e. before setProperties has been called) will throw an IllegalStateException. | |
38 | * <p> | |
39 | * Jstl converts ${Constants.a.b.c} into get("a").get("b").get("c"), so the properties are stored in a PropertyTree, which converts | |
40 | * the initial set( "a.b.c", "value" ) into construction of the necessary tree structure to support get("a").get("b").get("c"). | |
41 | * <p> | |
42 | * Implicitly relies on the assumption that the JSP will be calling toString() on the result of the final <code>get</code>, since | |
43 | * <code>get</code> can only return one type, and that type must be the complex one so that further dereferencing will be | |
44 | * possible. | |
45 | */ | |
46 | //FIXME: use generics, make class threadsafe | |
47 | 0 | public final class PropertiesMap implements Map { |
48 | private PropertyTree propertyTree; | |
49 | ||
50 | /** | |
51 | * Creates a propertyTree to store the given properties | |
52 | * | |
53 | * @param properties | |
54 | */ | |
55 | public void setProperties(Properties properties) { | |
56 | 0 | propertyTree = new PropertyTree(properties); |
57 | 0 | } |
58 | ||
59 | // delegated methods | |
60 | @Override | |
61 | public Object get(Object key) { | |
62 | 0 | if (propertyTree == null) { |
63 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
64 | } | |
65 | 0 | return this.propertyTree.get(key); |
66 | } | |
67 | ||
68 | @Override | |
69 | public int size() { | |
70 | 0 | if (propertyTree == null) { |
71 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
72 | } | |
73 | 0 | return this.propertyTree.size(); |
74 | } | |
75 | ||
76 | @Override | |
77 | public void clear() { | |
78 | 0 | if (propertyTree == null) { |
79 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
80 | } | |
81 | 0 | this.propertyTree.clear(); |
82 | 0 | } |
83 | ||
84 | @Override | |
85 | public boolean isEmpty() { | |
86 | 0 | if (propertyTree == null) { |
87 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
88 | } | |
89 | 0 | return this.propertyTree.isEmpty(); |
90 | } | |
91 | ||
92 | @Override | |
93 | public boolean containsKey(Object key) { | |
94 | 0 | if (propertyTree == null) { |
95 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
96 | } | |
97 | 0 | return this.propertyTree.containsKey(key); |
98 | } | |
99 | ||
100 | @Override | |
101 | public boolean containsValue(Object value) { | |
102 | 0 | if (propertyTree == null) { |
103 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
104 | } | |
105 | 0 | return this.propertyTree.containsValue(value); |
106 | } | |
107 | ||
108 | @Override | |
109 | public Collection values() { | |
110 | 0 | if (propertyTree == null) { |
111 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
112 | } | |
113 | 0 | return this.propertyTree.values(); |
114 | } | |
115 | ||
116 | @Override | |
117 | public void putAll(Map m) { | |
118 | 0 | if (propertyTree == null) { |
119 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
120 | } | |
121 | 0 | this.propertyTree.putAll(m); |
122 | 0 | } |
123 | ||
124 | @Override | |
125 | public Set entrySet() { | |
126 | 0 | if (propertyTree == null) { |
127 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
128 | } | |
129 | 0 | return this.propertyTree.entrySet(); |
130 | } | |
131 | ||
132 | @Override | |
133 | public Set keySet() { | |
134 | 0 | if (propertyTree == null) { |
135 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
136 | } | |
137 | 0 | return this.propertyTree.keySet(); |
138 | } | |
139 | ||
140 | @Override | |
141 | public Object remove(Object key) { | |
142 | 0 | if (propertyTree == null) { |
143 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
144 | } | |
145 | 0 | return this.propertyTree.remove(key); |
146 | } | |
147 | ||
148 | @Override | |
149 | public Object put(Object key, Object value) { | |
150 | 0 | if (propertyTree == null) { |
151 | 0 | throw new IllegalStateException("propertyTree has not been initialized"); |
152 | } | |
153 | 0 | return this.propertyTree.put(key, value); |
154 | } | |
155 | ||
156 | /** | |
157 | * This class is a Recursive container for single- and multi-level key,value pairs. It relies on the assumption that the consumer | |
158 | * (presumably a JSP) will (implicitly) call toString at the end of the chain, which will return the String value of the chain's | |
159 | * endpoint. | |
160 | * | |
161 | * It implements Map because that's how we fool jstl into converting "a.b.c" into get("a").get("b").get("c") instead of | |
162 | * getA().getB().getC() | |
163 | * | |
164 | * Uses LinkedHashMap and LinkedHashSet because iteration order is now important. | |
165 | * | |
166 | * | |
167 | */ | |
168 | 0 | static class PropertyTree implements Map { |
169 | 1 | private static final Logger LOG = Logger.getLogger(PropertyTree.class); |
170 | ||
171 | final boolean flat; | |
172 | final PropertyTree parent; | |
173 | String directValue; | |
174 | Map children; | |
175 | ||
176 | /** | |
177 | * Creates an empty instance with no parent | |
178 | */ | |
179 | public PropertyTree() { | |
180 | 79 | this(false); |
181 | 79 | } |
182 | ||
183 | /** | |
184 | * Creates an empty instance with no parent. If flat is true, entrySet and size and the iterators will ignore entries in | |
185 | * subtrees. | |
186 | */ | |
187 | 79 | public PropertyTree(boolean flat) { |
188 | 79 | this.parent = null; |
189 | 79 | this.children = new LinkedHashMap(); |
190 | 79 | this.flat = flat; |
191 | 79 | } |
192 | ||
193 | /** | |
194 | * Creates an empty instance with the given parent. If flat is true, entrySet and size and the iterators will ignore entries in | |
195 | * subtrees. | |
196 | */ | |
197 | 355 | private PropertyTree(PropertyTree parent) { |
198 | 355 | this.parent = parent; |
199 | 355 | this.children = new LinkedHashMap(); |
200 | 355 | this.flat = parent.flat; |
201 | 355 | } |
202 | ||
203 | /** | |
204 | * Creates an instance pre-loaded with the given Properties | |
205 | * | |
206 | * @param properties | |
207 | */ | |
208 | public PropertyTree(Properties properties) { | |
209 | 33 | this(); |
210 | ||
211 | 33 | setProperties(properties); |
212 | 33 | } |
213 | ||
214 | /** | |
215 | * Associates the given key with the given value. If the given key has multiple levels (consists of multiple strings separated | |
216 | * by '.'), the property value is stored such that it can be retrieved either directly, by calling get() and passing the entire | |
217 | * key; or indirectly, by decomposing the key into its separate levels and calling get() successively on the result of the | |
218 | * previous level's get. <br> | |
219 | * For example, given <br> | |
220 | * <code> | |
221 | * PropertyTree tree = new PropertyTree(); | |
222 | * tree.set( "a.b.c", "something" ); | |
223 | * </code> the following statements are | |
224 | * equivalent ways to retrieve the value: <br> | |
225 | * <code> | |
226 | * Object one = tree.get( "a.b.c" ); | |
227 | * </code> | |
228 | * <code> | |
229 | * Object two = tree.get( "a" ).get( "b" ).get( "c" ); | |
230 | * </code><br> | |
231 | * Note: since I can't have the get method return both a PropertyTree and a String, getting an actual String requires calling | |
232 | * toString on the PropertyTree returned by get. | |
233 | * | |
234 | * @param key | |
235 | * @param value | |
236 | * @throws IllegalArgumentException if the key is null | |
237 | * @throws IllegalArgumentException if the value is null | |
238 | */ | |
239 | public void setProperty(String key, String value) { | |
240 | 508 | validateKey(key); |
241 | 508 | validateValue(value); |
242 | ||
243 | 508 | if (parent == null) { |
244 | 237 | LOG.debug("setting (k,v) (" + key + "," + value + ")"); |
245 | } | |
246 | ||
247 | 508 | if (StringUtils.contains(key, '.')) { |
248 | 271 | String prefix = StringUtils.substringBefore(key, "."); |
249 | 271 | String suffix = StringUtils.substringAfter(key, "."); |
250 | ||
251 | 271 | PropertyTree node = getChild(prefix); |
252 | 271 | node.setProperty(suffix, value); |
253 | 271 | } |
254 | else { | |
255 | 237 | PropertyTree node = getChild(key); |
256 | 237 | node.setDirectValue(value); |
257 | } | |
258 | 508 | } |
259 | ||
260 | /** | |
261 | * Inserts all properties from the given Properties instance into this PropertyTree. | |
262 | * | |
263 | * @param properties | |
264 | * @throws IllegalArgumentException if the Properties object is null | |
265 | * @throws IllegalArgumentException if a property's key is null | |
266 | * @throws IllegalArgumentException if a property's value is null | |
267 | */ | |
268 | public void setProperties(Properties properties) { | |
269 | 33 | if (properties == null) { |
270 | 0 | throw new IllegalArgumentException("invalid (null) Properties object"); |
271 | } | |
272 | ||
273 | 33 | for (Iterator i = properties.entrySet().iterator(); i.hasNext();) { |
274 | 237 | Entry e = (Entry) i.next(); |
275 | 237 | setProperty((String) e.getKey(), (String) e.getValue()); |
276 | 237 | } |
277 | 33 | } |
278 | ||
279 | /** | |
280 | * Returns the PropertyTree object with the given key, or null if there is none. | |
281 | * | |
282 | * @param key | |
283 | * @return | |
284 | * @throws IllegalArgumentException if the key is null | |
285 | */ | |
286 | private PropertyTree getSubtree(String key) { | |
287 | 15 | validateKey(key); |
288 | ||
289 | 15 | PropertyTree returnValue = null; |
290 | 15 | if (StringUtils.contains(key, '.')) { |
291 | 4 | String prefix = StringUtils.substringBefore(key, "."); |
292 | 4 | String suffix = StringUtils.substringAfter(key, "."); |
293 | ||
294 | 4 | PropertyTree child = (PropertyTree) this.children.get(prefix); |
295 | 4 | if (child != null) { |
296 | 4 | returnValue = child.getSubtree(suffix); |
297 | } | |
298 | 4 | } |
299 | else { | |
300 | 11 | returnValue = (PropertyTree) this.children.get(key); |
301 | } | |
302 | ||
303 | 15 | return returnValue; |
304 | } | |
305 | ||
306 | ||
307 | /** | |
308 | * @param key | |
309 | * @return the directValue of the PropertyTree associated with the given key, or null if there is none | |
310 | */ | |
311 | public String getProperty(String key) { | |
312 | 0 | String propertyValue = null; |
313 | ||
314 | 0 | PropertyTree subtree = getSubtree(key); |
315 | 0 | if (subtree != null) { |
316 | 0 | propertyValue = subtree.getDirectValue(); |
317 | } | |
318 | ||
319 | 0 | return propertyValue; |
320 | } | |
321 | ||
322 | ||
323 | /** | |
324 | * @return an unmodifiable copy of the direct children of this PropertyTree | |
325 | */ | |
326 | public Map getDirectChildren() { | |
327 | 0 | return Collections.unmodifiableMap(this.children); |
328 | } | |
329 | ||
330 | ||
331 | /** | |
332 | * Returns the directValue of this PropertyTree, or null if there is none. | |
333 | * <p> | |
334 | * This is the hack that makes it possible for jstl to get what it needs when trying to retrive the value of a simple key or of | |
335 | * a complex (multi-part) key. | |
336 | */ | |
337 | public String toString() { | |
338 | 6 | return getDirectValue(); |
339 | } | |
340 | ||
341 | /** | |
342 | * Sets the directValue of this PropertyTree to the given value. | |
343 | * | |
344 | * @param value | |
345 | */ | |
346 | private void setDirectValue(String value) { | |
347 | 237 | validateValue(value); |
348 | ||
349 | 237 | this.directValue = value; |
350 | 237 | } |
351 | ||
352 | /** | |
353 | * @return directValue of this PropertyTree, or null if there is none | |
354 | */ | |
355 | private String getDirectValue() { | |
356 | 163 | return this.directValue; |
357 | } | |
358 | ||
359 | /** | |
360 | * @return true if the directValue of this PropertyTree is not null | |
361 | */ | |
362 | private boolean hasDirectValue() { | |
363 | 237 | return (this.directValue != null); |
364 | } | |
365 | ||
366 | /** | |
367 | * @return true if the this PropertyTree has children | |
368 | */ | |
369 | private boolean hasChildren() { | |
370 | 237 | return (!this.children.isEmpty()); |
371 | } | |
372 | ||
373 | /** | |
374 | * Returns the PropertyTree associated with the given key. If none exists, creates a new PropertyTree associates it with the | |
375 | * given key, and returns it. | |
376 | * | |
377 | * @param key | |
378 | * @return PropertyTree associated with the given key | |
379 | * @throws IllegalArgumentException if the given key is null | |
380 | */ | |
381 | private PropertyTree getChild(String key) { | |
382 | 508 | validateKey(key); |
383 | ||
384 | 508 | PropertyTree child = (PropertyTree) this.children.get(key); |
385 | 508 | if (child == null) { |
386 | 355 | child = new PropertyTree(this); |
387 | 355 | this.children.put(key, child); |
388 | } | |
389 | ||
390 | 508 | return child; |
391 | } | |
392 | ||
393 | /** | |
394 | * @param key | |
395 | * @throws IllegalArgumentException if the given key is not a String, or is null | |
396 | */ | |
397 | private void validateKey(Object key) { | |
398 | 1050 | if (!(key instanceof String)) { |
399 | 2 | throw new IllegalArgumentException("invalid (non-String) key"); |
400 | } | |
401 | 1048 | else if (key == null) { |
402 | 0 | throw new IllegalArgumentException("invalid (null) key"); |
403 | } | |
404 | 1048 | } |
405 | ||
406 | /** | |
407 | * @param value | |
408 | * @throws IllegalArgumentException if the given value is not a String, or is null | |
409 | */ | |
410 | private void validateValue(Object value) { | |
411 | 752 | if (!(value instanceof String)) { |
412 | 1 | throw new IllegalArgumentException("invalid (non-String) value"); |
413 | } | |
414 | 751 | else if (value == null) { |
415 | 0 | throw new IllegalArgumentException("invalid (null) value"); |
416 | } | |
417 | 751 | } |
418 | ||
419 | ||
420 | // Map methods | |
421 | /** | |
422 | * Returns an unmodifiable Set containing all key,value pairs in this PropertyTree and its children. | |
423 | * | |
424 | * @see java.util.Map#entrySet() | |
425 | */ | |
426 | public Set entrySet() { | |
427 | 35 | return Collections.unmodifiableSet(collectEntries(null, this.flat).entrySet()); |
428 | } | |
429 | ||
430 | /** | |
431 | * Builds a HashMap containing all of the key,value pairs stored in this PropertyTree | |
432 | * | |
433 | * @return | |
434 | */ | |
435 | private Map collectEntries(String prefix, boolean flattenEntries) { | |
436 | 137 | LinkedHashMap entryMap = new LinkedHashMap(); |
437 | ||
438 | 137 | for (Iterator i = this.children.entrySet().iterator(); i.hasNext();) { |
439 | 237 | Entry e = (Entry) i.next(); |
440 | 237 | PropertyTree child = (PropertyTree) e.getValue(); |
441 | 237 | String childKey = (String) e.getKey(); |
442 | ||
443 | // handle children with values | |
444 | 237 | if (child.hasDirectValue()) { |
445 | 157 | String entryKey = (prefix == null) ? childKey : prefix + "." + childKey; |
446 | 157 | String entryValue = child.getDirectValue(); |
447 | ||
448 | 157 | entryMap.put(entryKey, entryValue); |
449 | } | |
450 | ||
451 | // handle children with children | |
452 | 237 | if (!flattenEntries && child.hasChildren()) { |
453 | 102 | String childPrefix = (prefix == null) ? childKey : prefix + "." + childKey; |
454 | ||
455 | 102 | entryMap.putAll(child.collectEntries(childPrefix, flattenEntries)); |
456 | } | |
457 | 237 | } |
458 | ||
459 | 137 | return entryMap; |
460 | } | |
461 | ||
462 | /** | |
463 | * @return the number of keys contained, directly or indirectly, in this PropertyTree | |
464 | */ | |
465 | public int size() { | |
466 | 4 | return entrySet().size(); |
467 | } | |
468 | ||
469 | /** | |
470 | * @see java.util.Map#isEmpty() | |
471 | */ | |
472 | public boolean isEmpty() { | |
473 | 4 | return entrySet().isEmpty(); |
474 | } | |
475 | ||
476 | /** | |
477 | * Returns an unmodifiable Collection containing the values of all of the entries of this PropertyTree. | |
478 | * | |
479 | * @see java.util.Map#values() | |
480 | */ | |
481 | public Collection values() { | |
482 | 5 | ArrayList values = new ArrayList(); |
483 | ||
484 | 5 | Set entrySet = entrySet(); |
485 | 5 | for (Iterator i = entrySet.iterator(); i.hasNext();) { |
486 | 15 | Entry e = (Entry) i.next(); |
487 | ||
488 | 15 | values.add(e.getValue()); |
489 | 15 | } |
490 | ||
491 | 5 | return Collections.unmodifiableList(values); |
492 | } | |
493 | ||
494 | /** | |
495 | * Returns an unmodifiable Set containing the keys of all of the entries of this PropertyTree. | |
496 | * | |
497 | * @see java.util.Map#keySet() | |
498 | */ | |
499 | public Set keySet() { | |
500 | 5 | LinkedHashSet keys = new LinkedHashSet(); |
501 | ||
502 | 5 | Set entrySet = entrySet(); |
503 | 5 | for (Iterator i = entrySet.iterator(); i.hasNext();) { |
504 | 15 | Entry e = (Entry) i.next(); |
505 | ||
506 | 15 | keys.add(e.getKey()); |
507 | 15 | } |
508 | ||
509 | 5 | return Collections.unmodifiableSet(keys); |
510 | } | |
511 | ||
512 | /** | |
513 | * @see java.util.Map#containsKey(Object) | |
514 | */ | |
515 | public boolean containsKey(Object key) { | |
516 | 7 | validateKey(key); |
517 | ||
518 | 6 | boolean containsKey = false; |
519 | ||
520 | 6 | Set entrySet = entrySet(); |
521 | 6 | for (Iterator i = entrySet.iterator(); !containsKey && i.hasNext();) { |
522 | 26 | Entry e = (Entry) i.next(); |
523 | ||
524 | 26 | Object entryKey = e.getKey(); |
525 | 26 | containsKey = (entryKey != null) && entryKey.equals(key); |
526 | 26 | } |
527 | ||
528 | 6 | return containsKey; |
529 | } | |
530 | ||
531 | /** | |
532 | * @see java.util.Map#containsValue(Object) | |
533 | */ | |
534 | public boolean containsValue(Object value) { | |
535 | 7 | validateValue(value); |
536 | ||
537 | 6 | boolean containsValue = false; |
538 | ||
539 | 6 | Set entrySet = entrySet(); |
540 | 6 | for (Iterator i = entrySet.iterator(); !containsValue && i.hasNext();) { |
541 | 26 | Entry e = (Entry) i.next(); |
542 | ||
543 | 26 | Object entryValue = e.getValue(); |
544 | 26 | containsValue = (entryValue != null) && entryValue.equals(value); |
545 | 26 | } |
546 | ||
547 | 6 | return containsValue; |
548 | } | |
549 | ||
550 | /** | |
551 | * Traverses the tree structure until it finds the PropertyTree pointed to by the given key, and returns that PropertyTree | |
552 | * instance. | |
553 | * <p> | |
554 | * Only returns PropertyTree instances; if you want the String value pointed to by a given key, you must call toString() on the | |
555 | * returned PropertyTree (after verifying that it isn't null, of course). | |
556 | * | |
557 | * @see java.util.Map#get(Object) | |
558 | */ | |
559 | public Object get(Object key) { | |
560 | 12 | validateKey(key); |
561 | ||
562 | 11 | return getSubtree((String) key); |
563 | } | |
564 | ||
565 | ||
566 | // unsupported operations | |
567 | /** | |
568 | * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized. | |
569 | */ | |
570 | public void clear() { | |
571 | 1 | throw new UnsupportedOperationException(); |
572 | } | |
573 | ||
574 | /** | |
575 | * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized. | |
576 | */ | |
577 | public void putAll(Map t) { | |
578 | 1 | throw new UnsupportedOperationException(); |
579 | } | |
580 | ||
581 | /** | |
582 | * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized. | |
583 | */ | |
584 | public Object remove(Object key) { | |
585 | 1 | throw new UnsupportedOperationException(); |
586 | } | |
587 | ||
588 | /** | |
589 | * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized. | |
590 | */ | |
591 | public Object put(Object key, Object value) { | |
592 | 1 | throw new UnsupportedOperationException(); |
593 | } | |
594 | } | |
595 | } |