| 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.kns.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 | 0 |      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 | 0 |          this(false); | 
| 56 | 0 |      } | 
| 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 | 0 |      public PropertyTree(boolean flat) { | 
| 63 | 0 |          this.parent = null; | 
| 64 | 0 |          this.children = new LinkedHashMap(); | 
| 65 | 0 |          this.flat = flat; | 
| 66 | 0 |      } | 
| 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 | 0 |      private PropertyTree(PropertyTree parent) { | 
| 73 | 0 |          this.parent = parent; | 
| 74 | 0 |          this.children = new LinkedHashMap(); | 
| 75 | 0 |          this.flat = parent.flat; | 
| 76 | 0 |      } | 
| 77 | ||
| 78 |      /** | |
| 79 |       * Creates an instance pre-loaded with the given Properties | |
| 80 |       *  | |
| 81 |       * @param properties | |
| 82 |       */ | |
| 83 |      public PropertyTree(Properties properties) { | |
| 84 | 0 |          this(); | 
| 85 | ||
| 86 | 0 |          setProperties(properties); | 
| 87 | 0 |      } | 
| 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 | 0 |          validateKey(key); | 
| 116 | 0 |          validateValue(value); | 
| 117 | ||
| 118 | 0 |          if (parent == null) { | 
| 119 | 0 |              LOG.debug("setting (k,v) (" + key + "," + value + ")"); | 
| 120 | }  | |
| 121 | ||
| 122 | 0 |          if (StringUtils.contains(key, '.')) { | 
| 123 | 0 |              String prefix = StringUtils.substringBefore(key, "."); | 
| 124 | 0 |              String suffix = StringUtils.substringAfter(key, "."); | 
| 125 | ||
| 126 | 0 |              PropertyTree node = getChild(prefix); | 
| 127 | 0 |              node.setProperty(suffix, value); | 
| 128 | 0 |          } | 
| 129 |          else { | |
| 130 | 0 |              PropertyTree node = getChild(key); | 
| 131 | 0 |              node.setDirectValue(value); | 
| 132 | }  | |
| 133 | 0 |      } | 
| 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 | 0 |          if (properties == null) { | 
| 145 | 0 |              throw new IllegalArgumentException("invalid (null) Properties object"); | 
| 146 | }  | |
| 147 | ||
| 148 | 0 |          for (Iterator i = properties.entrySet().iterator(); i.hasNext();) { | 
| 149 | 0 |              Map.Entry e = (Map.Entry) i.next(); | 
| 150 | 0 |              setProperty((String) e.getKey(), (String) e.getValue()); | 
| 151 | 0 |          } | 
| 152 | 0 |      } | 
| 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 | 0 |          validateKey(key); | 
| 163 | ||
| 164 | 0 |          PropertyTree returnValue = null; | 
| 165 | 0 |          if (StringUtils.contains(key, '.')) { | 
| 166 | 0 |              String prefix = StringUtils.substringBefore(key, "."); | 
| 167 | 0 |              String suffix = StringUtils.substringAfter(key, "."); | 
| 168 | ||
| 169 | 0 |              PropertyTree child = (PropertyTree) this.children.get(prefix); | 
| 170 | 0 |              if (child != null) { | 
| 171 | 0 |                  returnValue = child.getSubtree(suffix); | 
| 172 | }  | |
| 173 | 0 |          } | 
| 174 |          else { | |
| 175 | 0 |              returnValue = (PropertyTree) this.children.get(key); | 
| 176 | }  | |
| 177 | ||
| 178 | 0 |          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 | 0 |          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 | 0 |          validateValue(value); | 
| 223 | ||
| 224 | 0 |          this.directValue = value; | 
| 225 | 0 |      } | 
| 226 | ||
| 227 |      /** | |
| 228 |       * @return directValue of this PropertyTree, or null if there is none | |
| 229 |       */ | |
| 230 |      private String getDirectValue() { | |
| 231 | 0 |          return this.directValue; | 
| 232 | }  | |
| 233 | ||
| 234 |      /** | |
| 235 |       * @return true if the directValue of this PropertyTree is not null | |
| 236 |       */ | |
| 237 | private boolean hasDirectValue() {  | |
| 238 | 0 |          return (this.directValue != null); | 
| 239 | }  | |
| 240 | ||
| 241 |      /** | |
| 242 |       * @return true if the this PropertyTree has children | |
| 243 |       */ | |
| 244 | private boolean hasChildren() {  | |
| 245 | 0 |          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 | 0 |          validateKey(key); | 
| 258 | ||
| 259 | 0 |          PropertyTree child = (PropertyTree) this.children.get(key); | 
| 260 | 0 |          if (child == null) { | 
| 261 | 0 |              child = new PropertyTree(this); | 
| 262 | 0 |              this.children.put(key, child); | 
| 263 | }  | |
| 264 | ||
| 265 | 0 |          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 | 0 |          if (!(key instanceof String)) { | 
| 274 | 0 |              throw new IllegalArgumentException("invalid (non-String) key"); | 
| 275 | }  | |
| 276 | 0 |          else if (key == null) { | 
| 277 | 0 |              throw new IllegalArgumentException("invalid (null) key"); | 
| 278 | }  | |
| 279 | 0 |      } | 
| 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 | 0 |          if (!(value instanceof String)) { | 
| 287 | 0 |              throw new IllegalArgumentException("invalid (non-String) value"); | 
| 288 | }  | |
| 289 | 0 |          else if (value == null) { | 
| 290 | 0 |              throw new IllegalArgumentException("invalid (null) value"); | 
| 291 | }  | |
| 292 | 0 |      } | 
| 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 | 0 |          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 | 0 |          LinkedHashMap entryMap = new LinkedHashMap(); | 
| 312 | ||
| 313 | 0 |          for (Iterator i = this.children.entrySet().iterator(); i.hasNext();) { | 
| 314 | 0 |              Map.Entry e = (Map.Entry) i.next(); | 
| 315 | 0 |              PropertyTree child = (PropertyTree) e.getValue(); | 
| 316 | 0 |              String childKey = (String) e.getKey(); | 
| 317 | ||
| 318 |              // handle children with values | |
| 319 | 0 |              if (child.hasDirectValue()) { | 
| 320 | 0 |                  String entryKey = (prefix == null) ? childKey : prefix + "." + childKey; | 
| 321 | 0 |                  String entryValue = child.getDirectValue(); | 
| 322 | ||
| 323 | 0 |                  entryMap.put(entryKey, entryValue); | 
| 324 | }  | |
| 325 | ||
| 326 |              // handle children with children | |
| 327 | 0 |              if (!flattenEntries && child.hasChildren()) { | 
| 328 | 0 |                  String childPrefix = (prefix == null) ? childKey : prefix + "." + childKey; | 
| 329 | ||
| 330 | 0 |                  entryMap.putAll(child.collectEntries(childPrefix, flattenEntries)); | 
| 331 | }  | |
| 332 | 0 |          } | 
| 333 | ||
| 334 | 0 |          return entryMap; | 
| 335 | }  | |
| 336 | ||
| 337 |      /** | |
| 338 |       * @return the number of keys contained, directly or indirectly, in this PropertyTree | |
| 339 |       */ | |
| 340 | public int size() {  | |
| 341 | 0 |          return entrySet().size(); | 
| 342 | }  | |
| 343 | ||
| 344 |      /** | |
| 345 |       * @see java.util.Map#isEmpty() | |
| 346 |       */ | |
| 347 | public boolean isEmpty() {  | |
| 348 | 0 |          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 | 0 |          ArrayList values = new ArrayList(); | 
| 358 | ||
| 359 | 0 |          Set entrySet = entrySet(); | 
| 360 | 0 |          for (Iterator i = entrySet.iterator(); i.hasNext();) { | 
| 361 | 0 |              Map.Entry e = (Map.Entry) i.next(); | 
| 362 | ||
| 363 | 0 |              values.add(e.getValue()); | 
| 364 | 0 |          } | 
| 365 | ||
| 366 | 0 |          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 | 0 |          LinkedHashSet keys = new LinkedHashSet(); | 
| 376 | ||
| 377 | 0 |          Set entrySet = entrySet(); | 
| 378 | 0 |          for (Iterator i = entrySet.iterator(); i.hasNext();) { | 
| 379 | 0 |              Map.Entry e = (Map.Entry) i.next(); | 
| 380 | ||
| 381 | 0 |              keys.add(e.getKey()); | 
| 382 | 0 |          } | 
| 383 | ||
| 384 | 0 |          return Collections.unmodifiableSet(keys); | 
| 385 | }  | |
| 386 | ||
| 387 |      /** | |
| 388 |       * @see java.util.Map#containsKey(java.lang.Object) | |
| 389 |       */ | |
| 390 | public boolean containsKey(Object key) {  | |
| 391 | 0 |          validateKey(key); | 
| 392 | ||
| 393 | 0 |          boolean containsKey = false; | 
| 394 | ||
| 395 | 0 |          Set entrySet = entrySet(); | 
| 396 | 0 |          for (Iterator i = entrySet.iterator(); !containsKey && i.hasNext();) { | 
| 397 | 0 |              Map.Entry e = (Map.Entry) i.next(); | 
| 398 | ||
| 399 | 0 |              Object entryKey = e.getKey(); | 
| 400 | 0 |              containsKey = (entryKey != null) && entryKey.equals(key); | 
| 401 | 0 |          } | 
| 402 | ||
| 403 | 0 |          return containsKey; | 
| 404 | }  | |
| 405 | ||
| 406 |      /** | |
| 407 |       * @see java.util.Map#containsValue(java.lang.Object) | |
| 408 |       */ | |
| 409 | public boolean containsValue(Object value) {  | |
| 410 | 0 |          validateValue(value); | 
| 411 | ||
| 412 | 0 |          boolean containsValue = false; | 
| 413 | ||
| 414 | 0 |          Set entrySet = entrySet(); | 
| 415 | 0 |          for (Iterator i = entrySet.iterator(); !containsValue && i.hasNext();) { | 
| 416 | 0 |              Map.Entry e = (Map.Entry) i.next(); | 
| 417 | ||
| 418 | 0 |              Object entryValue = e.getValue(); | 
| 419 | 0 |              containsValue = (entryValue != null) && entryValue.equals(value); | 
| 420 | 0 |          } | 
| 421 | ||
| 422 | 0 |          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 | 0 |          validateKey(key); | 
| 436 | ||
| 437 | 0 |          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 | 0 |          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 | 0 |          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 | 0 |          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 | 0 |          throw new UnsupportedOperationException(); | 
| 468 | }  | |
| 469 | }  |