Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
PropertyTree |
|
| 2.1666666666666665;2.167 |
1 | /* | |
2 | * Copyright 2005-2008 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.krad.util.properties; | |
17 | ||
18 | import java.util.ArrayList; | |
19 | import java.util.Collection; | |
20 | import java.util.Collections; | |
21 | import java.util.Iterator; | |
22 | import java.util.LinkedHashMap; | |
23 | import java.util.LinkedHashSet; | |
24 | import java.util.Map; | |
25 | import java.util.Properties; | |
26 | import java.util.Set; | |
27 | ||
28 | import org.apache.commons.lang.StringUtils; | |
29 | import org.apache.log4j.Logger; | |
30 | ||
31 | /** | |
32 | * This class is a Recursive container for single- and multi-level key,value pairs. It relies on the assumption that the consumer | |
33 | * (presumably a JSP) will (implicitly) call toString at the end of the chain, which will return the String value of the chain's | |
34 | * endpoint. | |
35 | * | |
36 | * It implements Map because that's how we fool jstl into converting "a.b.c" into get("a").get("b").get("c") instead of | |
37 | * getA().getB().getC() | |
38 | * | |
39 | * Uses LinkedHashMap and LinkedHashSet because iteration order is now important. | |
40 | * | |
41 | * | |
42 | */ | |
43 | public class PropertyTree implements Map { | |
44 | 1 | private static Logger LOG = Logger.getLogger(PropertyTree.class); |
45 | ||
46 | final boolean flat; | |
47 | final PropertyTree parent; | |
48 | String directValue; | |
49 | Map children; | |
50 | ||
51 | /** | |
52 | * Creates an empty instance with no parent | |
53 | */ | |
54 | public PropertyTree() { | |
55 | 79 | this(false); |
56 | 79 | } |
57 | ||
58 | /** | |
59 | * Creates an empty instance with no parent. If flat is true, entrySet and size and the iterators will ignore entries in | |
60 | * subtrees. | |
61 | */ | |
62 | 79 | public PropertyTree(boolean flat) { |
63 | 79 | this.parent = null; |
64 | 79 | this.children = new LinkedHashMap(); |
65 | 79 | this.flat = flat; |
66 | 79 | } |
67 | ||
68 | /** | |
69 | * Creates an empty instance with the given parent. If flat is true, entrySet and size and the iterators will ignore entries in | |
70 | * subtrees. | |
71 | */ | |
72 | 355 | private PropertyTree(PropertyTree parent) { |
73 | 355 | this.parent = parent; |
74 | 355 | this.children = new LinkedHashMap(); |
75 | 355 | this.flat = parent.flat; |
76 | 355 | } |
77 | ||
78 | /** | |
79 | * Creates an instance pre-loaded with the given Properties | |
80 | * | |
81 | * @param properties | |
82 | */ | |
83 | public PropertyTree(Properties properties) { | |
84 | 33 | this(); |
85 | ||
86 | 33 | setProperties(properties); |
87 | 33 | } |
88 | ||
89 | /** | |
90 | * Associates the given key with the given value. If the given key has multiple levels (consists of multiple strings separated | |
91 | * by '.'), the property value is stored such that it can be retrieved either directly, by calling get() and passing the entire | |
92 | * key; or indirectly, by decomposing the key into its separate levels and calling get() successively on the result of the | |
93 | * previous level's get. <br> | |
94 | * For example, given <br> | |
95 | * <code> | |
96 | * PropertyTree tree = new PropertyTree(); | |
97 | * tree.set( "a.b.c", "something" ); | |
98 | * </code> the following statements are | |
99 | * equivalent ways to retrieve the value: <br> | |
100 | * <code> | |
101 | * Object one = tree.get( "a.b.c" ); | |
102 | * </code> | |
103 | * <code> | |
104 | * Object two = tree.get( "a" ).get( "b" ).get( "c" ); | |
105 | * </code><br> | |
106 | * Note: since I can't have the get method return both a PropertyTree and a String, getting an actual String requires calling | |
107 | * toString on the PropertyTree returned by get. | |
108 | * | |
109 | * @param key | |
110 | * @param value | |
111 | * @throws IllegalArgumentException if the key is null | |
112 | * @throws IllegalArgumentException if the value is null | |
113 | */ | |
114 | public void setProperty(String key, String value) { | |
115 | 508 | validateKey(key); |
116 | 508 | validateValue(value); |
117 | ||
118 | 508 | if (parent == null) { |
119 | 237 | LOG.debug("setting (k,v) (" + key + "," + value + ")"); |
120 | } | |
121 | ||
122 | 508 | if (StringUtils.contains(key, '.')) { |
123 | 271 | String prefix = StringUtils.substringBefore(key, "."); |
124 | 271 | String suffix = StringUtils.substringAfter(key, "."); |
125 | ||
126 | 271 | PropertyTree node = getChild(prefix); |
127 | 271 | node.setProperty(suffix, value); |
128 | 271 | } |
129 | else { | |
130 | 237 | PropertyTree node = getChild(key); |
131 | 237 | node.setDirectValue(value); |
132 | } | |
133 | 508 | } |
134 | ||
135 | /** | |
136 | * Inserts all properties from the given Properties instance into this PropertyTree. | |
137 | * | |
138 | * @param properties | |
139 | * @throws IllegalArgumentException if the Properties object is null | |
140 | * @throws IllegalArgumentException if a property's key is null | |
141 | * @throws IllegalArgumentException if a property's value is null | |
142 | */ | |
143 | public void setProperties(Properties properties) { | |
144 | 33 | if (properties == null) { |
145 | 0 | throw new IllegalArgumentException("invalid (null) Properties object"); |
146 | } | |
147 | ||
148 | 33 | for (Iterator i = properties.entrySet().iterator(); i.hasNext();) { |
149 | 237 | Map.Entry e = (Map.Entry) i.next(); |
150 | 237 | setProperty((String) e.getKey(), (String) e.getValue()); |
151 | 237 | } |
152 | 33 | } |
153 | ||
154 | /** | |
155 | * Returns the PropertyTree object with the given key, or null if there is none. | |
156 | * | |
157 | * @param key | |
158 | * @return | |
159 | * @throws IllegalArgumentException if the key is null | |
160 | */ | |
161 | private PropertyTree getSubtree(String key) { | |
162 | 15 | validateKey(key); |
163 | ||
164 | 15 | PropertyTree returnValue = null; |
165 | 15 | if (StringUtils.contains(key, '.')) { |
166 | 4 | String prefix = StringUtils.substringBefore(key, "."); |
167 | 4 | String suffix = StringUtils.substringAfter(key, "."); |
168 | ||
169 | 4 | PropertyTree child = (PropertyTree) this.children.get(prefix); |
170 | 4 | if (child != null) { |
171 | 4 | returnValue = child.getSubtree(suffix); |
172 | } | |
173 | 4 | } |
174 | else { | |
175 | 11 | returnValue = (PropertyTree) this.children.get(key); |
176 | } | |
177 | ||
178 | 15 | return returnValue; |
179 | } | |
180 | ||
181 | ||
182 | /** | |
183 | * @param key | |
184 | * @return the directValue of the PropertyTree associated with the given key, or null if there is none | |
185 | */ | |
186 | public String getProperty(String key) { | |
187 | 0 | String propertyValue = null; |
188 | ||
189 | 0 | PropertyTree subtree = getSubtree(key); |
190 | 0 | if (subtree != null) { |
191 | 0 | propertyValue = subtree.getDirectValue(); |
192 | } | |
193 | ||
194 | 0 | return propertyValue; |
195 | } | |
196 | ||
197 | ||
198 | /** | |
199 | * @return an unmodifiable copy of the direct children of this PropertyTree | |
200 | */ | |
201 | public Map getDirectChildren() { | |
202 | 0 | return Collections.unmodifiableMap(this.children); |
203 | } | |
204 | ||
205 | ||
206 | /** | |
207 | * Returns the directValue of this PropertyTree, or null if there is none. | |
208 | * <p> | |
209 | * 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 | |
210 | * a complex (multi-part) key. | |
211 | */ | |
212 | public String toString() { | |
213 | 6 | return getDirectValue(); |
214 | } | |
215 | ||
216 | /** | |
217 | * Sets the directValue of this PropertyTree to the given value. | |
218 | * | |
219 | * @param value | |
220 | */ | |
221 | private void setDirectValue(String value) { | |
222 | 237 | validateValue(value); |
223 | ||
224 | 237 | this.directValue = value; |
225 | 237 | } |
226 | ||
227 | /** | |
228 | * @return directValue of this PropertyTree, or null if there is none | |
229 | */ | |
230 | private String getDirectValue() { | |
231 | 163 | return this.directValue; |
232 | } | |
233 | ||
234 | /** | |
235 | * @return true if the directValue of this PropertyTree is not null | |
236 | */ | |
237 | private boolean hasDirectValue() { | |
238 | 237 | return (this.directValue != null); |
239 | } | |
240 | ||
241 | /** | |
242 | * @return true if the this PropertyTree has children | |
243 | */ | |
244 | private boolean hasChildren() { | |
245 | 237 | return (!this.children.isEmpty()); |
246 | } | |
247 | ||
248 | /** | |
249 | * Returns the PropertyTree associated with the given key. If none exists, creates a new PropertyTree associates it with the | |
250 | * given key, and returns it. | |
251 | * | |
252 | * @param key | |
253 | * @return PropertyTree associated with the given key | |
254 | * @throws IllegalArgumentException if the given key is null | |
255 | */ | |
256 | private PropertyTree getChild(String key) { | |
257 | 508 | validateKey(key); |
258 | ||
259 | 508 | PropertyTree child = (PropertyTree) this.children.get(key); |
260 | 508 | if (child == null) { |
261 | 355 | child = new PropertyTree(this); |
262 | 355 | this.children.put(key, child); |
263 | } | |
264 | ||
265 | 508 | return child; |
266 | } | |
267 | ||
268 | /** | |
269 | * @param key | |
270 | * @throws IllegalArgumentException if the given key is not a String, or is null | |
271 | */ | |
272 | private void validateKey(Object key) { | |
273 | 1050 | if (!(key instanceof String)) { |
274 | 2 | throw new IllegalArgumentException("invalid (non-String) key"); |
275 | } | |
276 | 1048 | else if (key == null) { |
277 | 0 | throw new IllegalArgumentException("invalid (null) key"); |
278 | } | |
279 | 1048 | } |
280 | ||
281 | /** | |
282 | * @param value | |
283 | * @throws IllegalArgumentException if the given value is not a String, or is null | |
284 | */ | |
285 | private void validateValue(Object value) { | |
286 | 752 | if (!(value instanceof String)) { |
287 | 1 | throw new IllegalArgumentException("invalid (non-String) value"); |
288 | } | |
289 | 751 | else if (value == null) { |
290 | 0 | throw new IllegalArgumentException("invalid (null) value"); |
291 | } | |
292 | 751 | } |
293 | ||
294 | ||
295 | // Map methods | |
296 | /** | |
297 | * Returns an unmodifiable Set containing all key,value pairs in this PropertyTree and its children. | |
298 | * | |
299 | * @see java.util.Map#entrySet() | |
300 | */ | |
301 | public Set entrySet() { | |
302 | 35 | return Collections.unmodifiableSet(collectEntries(null, this.flat).entrySet()); |
303 | } | |
304 | ||
305 | /** | |
306 | * Builds a HashMap containing all of the key,value pairs stored in this PropertyTree | |
307 | * | |
308 | * @return | |
309 | */ | |
310 | private Map collectEntries(String prefix, boolean flattenEntries) { | |
311 | 137 | LinkedHashMap entryMap = new LinkedHashMap(); |
312 | ||
313 | 137 | for (Iterator i = this.children.entrySet().iterator(); i.hasNext();) { |
314 | 237 | Map.Entry e = (Map.Entry) i.next(); |
315 | 237 | PropertyTree child = (PropertyTree) e.getValue(); |
316 | 237 | String childKey = (String) e.getKey(); |
317 | ||
318 | // handle children with values | |
319 | 237 | if (child.hasDirectValue()) { |
320 | 157 | String entryKey = (prefix == null) ? childKey : prefix + "." + childKey; |
321 | 157 | String entryValue = child.getDirectValue(); |
322 | ||
323 | 157 | entryMap.put(entryKey, entryValue); |
324 | } | |
325 | ||
326 | // handle children with children | |
327 | 237 | if (!flattenEntries && child.hasChildren()) { |
328 | 102 | String childPrefix = (prefix == null) ? childKey : prefix + "." + childKey; |
329 | ||
330 | 102 | entryMap.putAll(child.collectEntries(childPrefix, flattenEntries)); |
331 | } | |
332 | 237 | } |
333 | ||
334 | 137 | return entryMap; |
335 | } | |
336 | ||
337 | /** | |
338 | * @return the number of keys contained, directly or indirectly, in this PropertyTree | |
339 | */ | |
340 | public int size() { | |
341 | 4 | return entrySet().size(); |
342 | } | |
343 | ||
344 | /** | |
345 | * @see java.util.Map#isEmpty() | |
346 | */ | |
347 | public boolean isEmpty() { | |
348 | 4 | return entrySet().isEmpty(); |
349 | } | |
350 | ||
351 | /** | |
352 | * Returns an unmodifiable Collection containing the values of all of the entries of this PropertyTree. | |
353 | * | |
354 | * @see java.util.Map#values() | |
355 | */ | |
356 | public Collection values() { | |
357 | 5 | ArrayList values = new ArrayList(); |
358 | ||
359 | 5 | Set entrySet = entrySet(); |
360 | 5 | for (Iterator i = entrySet.iterator(); i.hasNext();) { |
361 | 15 | Map.Entry e = (Map.Entry) i.next(); |
362 | ||
363 | 15 | values.add(e.getValue()); |
364 | 15 | } |
365 | ||
366 | 5 | return Collections.unmodifiableList(values); |
367 | } | |
368 | ||
369 | /** | |
370 | * Returns an unmodifiable Set containing the keys of all of the entries of this PropertyTree. | |
371 | * | |
372 | * @see java.util.Map#keySet() | |
373 | */ | |
374 | public Set keySet() { | |
375 | 5 | LinkedHashSet keys = new LinkedHashSet(); |
376 | ||
377 | 5 | Set entrySet = entrySet(); |
378 | 5 | for (Iterator i = entrySet.iterator(); i.hasNext();) { |
379 | 15 | Map.Entry e = (Map.Entry) i.next(); |
380 | ||
381 | 15 | keys.add(e.getKey()); |
382 | 15 | } |
383 | ||
384 | 5 | return Collections.unmodifiableSet(keys); |
385 | } | |
386 | ||
387 | /** | |
388 | * @see java.util.Map#containsKey(java.lang.Object) | |
389 | */ | |
390 | public boolean containsKey(Object key) { | |
391 | 7 | validateKey(key); |
392 | ||
393 | 6 | boolean containsKey = false; |
394 | ||
395 | 6 | Set entrySet = entrySet(); |
396 | 6 | for (Iterator i = entrySet.iterator(); !containsKey && i.hasNext();) { |
397 | 26 | Map.Entry e = (Map.Entry) i.next(); |
398 | ||
399 | 26 | Object entryKey = e.getKey(); |
400 | 26 | containsKey = (entryKey != null) && entryKey.equals(key); |
401 | 26 | } |
402 | ||
403 | 6 | return containsKey; |
404 | } | |
405 | ||
406 | /** | |
407 | * @see java.util.Map#containsValue(java.lang.Object) | |
408 | */ | |
409 | public boolean containsValue(Object value) { | |
410 | 7 | validateValue(value); |
411 | ||
412 | 6 | boolean containsValue = false; |
413 | ||
414 | 6 | Set entrySet = entrySet(); |
415 | 6 | for (Iterator i = entrySet.iterator(); !containsValue && i.hasNext();) { |
416 | 26 | Map.Entry e = (Map.Entry) i.next(); |
417 | ||
418 | 26 | Object entryValue = e.getValue(); |
419 | 26 | containsValue = (entryValue != null) && entryValue.equals(value); |
420 | 26 | } |
421 | ||
422 | 6 | return containsValue; |
423 | } | |
424 | ||
425 | /** | |
426 | * Traverses the tree structure until it finds the PropertyTree pointed to by the given key, and returns that PropertyTree | |
427 | * instance. | |
428 | * <p> | |
429 | * Only returns PropertyTree instances; if you want the String value pointed to by a given key, you must call toString() on the | |
430 | * returned PropertyTree (after verifying that it isn't null, of course). | |
431 | * | |
432 | * @see java.util.Map#get(java.lang.Object) | |
433 | */ | |
434 | public Object get(Object key) { | |
435 | 12 | validateKey(key); |
436 | ||
437 | 11 | return getSubtree((String) key); |
438 | } | |
439 | ||
440 | ||
441 | // unsupported operations | |
442 | /** | |
443 | * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized. | |
444 | */ | |
445 | public void clear() { | |
446 | 1 | throw new UnsupportedOperationException(); |
447 | } | |
448 | ||
449 | /** | |
450 | * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized. | |
451 | */ | |
452 | public void putAll(Map t) { | |
453 | 1 | throw new UnsupportedOperationException(); |
454 | } | |
455 | ||
456 | /** | |
457 | * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized. | |
458 | */ | |
459 | public Object remove(Object key) { | |
460 | 1 | throw new UnsupportedOperationException(); |
461 | } | |
462 | ||
463 | /** | |
464 | * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized. | |
465 | */ | |
466 | public Object put(Object key, Object value) { | |
467 | 1 | throw new UnsupportedOperationException(); |
468 | } | |
469 | } |