Coverage Report - org.kuali.rice.core.api.util.collect.PropertiesMap
 
Classes in this File Line Coverage Branch Coverage Complexity
PropertiesMap
0%
0/42
0%
0/24
2.372
PropertiesMap$PropertyTree
93%
122/131
85%
51/60
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  
 }