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