Coverage Report - org.kuali.rice.krad.util.properties.PropertyTree
 
Classes in this File Line Coverage Branch Coverage Complexity
PropertyTree
93%
122/131
85%
51/60
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  
 }