org.kuali.rice.kns.util.properties.PropertyTree.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.rice.kns.util.properties.PropertyTree.java

Source

/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 * 
 * Copyright 2005-2014 The Kuali Foundation
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.rice.kns.util.properties;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * This class is a Recursive container for single- and multi-level key,value pairs. It relies on the assumption that the consumer
 * (presumably a JSP) will (implicitly) call toString at the end of the chain, which will return the String value of the chain's
 * endpoint.
 * 
 * It implements Map because that's how we fool jstl into converting "a.b.c" into get("a").get("b").get("c") instead of
 * getA().getB().getC()
 * 
 * Uses LinkedHashMap and LinkedHashSet because iteration order is now important.
 * 
 * 
 */
public class PropertyTree implements Map {
    private static Logger LOG = Logger.getLogger(PropertyTree.class);

    final boolean flat;
    final PropertyTree parent;
    String directValue;
    Map children;

    /**
     * Creates an empty instance with no parent
     */
    public PropertyTree() {
        this(false);
    }

    /**
     * Creates an empty instance with no parent. If flat is true, entrySet and size and the iterators will ignore entries in
     * subtrees.
     */
    public PropertyTree(boolean flat) {
        this.parent = null;
        this.children = new LinkedHashMap();
        this.flat = flat;
    }

    /**
     * Creates an empty instance with the given parent. If flat is true, entrySet and size and the iterators will ignore entries in
     * subtrees.
     */
    private PropertyTree(PropertyTree parent) {
        this.parent = parent;
        this.children = new LinkedHashMap();
        this.flat = parent.flat;
    }

    /**
     * Associates the given key with the given value. If the given key has multiple levels (consists of multiple strings separated
     * by '.'), the property value is stored such that it can be retrieved either directly, by calling get() and passing the entire
     * key; or indirectly, by decomposing the key into its separate levels and calling get() successively on the result of the
     * previous level's get. <br>
     * For example, given <br>
     * <code>
     * PropertyTree tree = new PropertyTree();
     * tree.set( "a.b.c", "something" );
     * </code> the following statements are
     * equivalent ways to retrieve the value: <br>
     * <code>
     * Object one = tree.get( "a.b.c" );
     * </code>
     * <code>
     * Object two = tree.get( "a" ).get( "b" ).get( "c" );
     * </code><br>
     * Note: since I can't have the get method return both a PropertyTree and a String, getting an actual String requires calling
     * toString on the PropertyTree returned by get.
     * 
     * @param key
     * @param value
     * @throws IllegalArgumentException if the key is null
     * @throws IllegalArgumentException if the value is null
     */
    public void setProperty(String key, String value) {
        validateKey(key);
        validateValue(value);

        if (parent == null) {
            LOG.debug("setting (k,v) (" + key + "," + value + ")");
        }

        if (StringUtils.contains(key, '.')) {
            String prefix = StringUtils.substringBefore(key, ".");
            String suffix = StringUtils.substringAfter(key, ".");

            PropertyTree node = getChild(prefix);
            node.setProperty(suffix, value);
        } else {
            PropertyTree node = getChild(key);
            node.setDirectValue(value);
        }
    }

    /**
     * Inserts all properties from the given Properties instance into this PropertyTree.
     * 
     * @param properties
     * @throws IllegalArgumentException if the Properties object is null
     * @throws IllegalArgumentException if a property's key is null
     * @throws IllegalArgumentException if a property's value is null
     */
    public void setProperties(Properties properties) {
        if (properties == null) {
            throw new IllegalArgumentException("invalid (null) Properties object");
        }

        for (Iterator i = properties.entrySet().iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();
            setProperty((String) e.getKey(), (String) e.getValue());
        }
    }

    public void setProperties(Map<String, String> properties) {
        if (properties == null) {
            throw new IllegalArgumentException("invalid (null) Properties object");
        }

        for (Iterator i = properties.entrySet().iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();
            setProperty((String) e.getKey(), (String) e.getValue());
        }
    }

    /**
     * Returns the PropertyTree object with the given key, or null if there is none.
     * 
     * @param key
     * @return
     * @throws IllegalArgumentException if the key is null
     */
    private PropertyTree getSubtree(String key) {
        validateKey(key);

        PropertyTree returnValue = null;
        if (StringUtils.contains(key, '.')) {
            String prefix = StringUtils.substringBefore(key, ".");
            String suffix = StringUtils.substringAfter(key, ".");

            PropertyTree child = (PropertyTree) this.children.get(prefix);
            if (child != null) {
                returnValue = child.getSubtree(suffix);
            }
        } else {
            returnValue = (PropertyTree) this.children.get(key);
        }

        return returnValue;
    }

    /**
     * @param key
     * @return the directValue of the PropertyTree associated with the given key, or null if there is none
     */
    public String getProperty(String key) {
        String propertyValue = null;

        PropertyTree subtree = getSubtree(key);
        if (subtree != null) {
            propertyValue = subtree.getDirectValue();
        }

        return propertyValue;
    }

    /**
     * @return an unmodifiable copy of the direct children of this PropertyTree
     */
    public Map getDirectChildren() {
        return Collections.unmodifiableMap(this.children);
    }

    /**
     * Returns the directValue of this PropertyTree, or null if there is none.
     * <p>
     * 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
     * a complex (multi-part) key.
     */
    public String toString() {
        return getDirectValue();
    }

    /**
     * Sets the directValue of this PropertyTree to the given value.
     * 
     * @param value
     */
    private void setDirectValue(String value) {
        validateValue(value);

        this.directValue = value;
    }

    /**
     * @return directValue of this PropertyTree, or null if there is none
     */
    private String getDirectValue() {
        return this.directValue;
    }

    /**
     * @return true if the directValue of this PropertyTree is not null
     */
    private boolean hasDirectValue() {
        return (this.directValue != null);
    }

    /**
     * @return true if the this PropertyTree has children
     */
    private boolean hasChildren() {
        return (!this.children.isEmpty());
    }

    /**
     * Returns the PropertyTree associated with the given key. If none exists, creates a new PropertyTree associates it with the
     * given key, and returns it.
     * 
     * @param key
     * @return PropertyTree associated with the given key
     * @throws IllegalArgumentException if the given key is null
     */
    private PropertyTree getChild(String key) {
        validateKey(key);

        PropertyTree child = (PropertyTree) this.children.get(key);
        if (child == null) {
            child = new PropertyTree((PropertyTree) this);
            this.children.put(key, child);
        }

        return child;
    }

    /**
     * @param key
     * @throws IllegalArgumentException if the given key is not a String, or is null
     */
    private void validateKey(Object key) {
        if (!(key instanceof String)) {
            throw new IllegalArgumentException("invalid (non-String) key");
        } else if (key == null) {
            throw new IllegalArgumentException("invalid (null) key");
        }
    }

    /**
     * @param value
     * @throws IllegalArgumentException if the given value is not a String, or is null
     */
    private void validateValue(Object value) {
        if (!(value instanceof String)) {
            throw new IllegalArgumentException("invalid (non-String) value");
        } else if (value == null) {
            throw new IllegalArgumentException("invalid (null) value");
        }
    }

    // Map methods
    /**
     * Returns an unmodifiable Set containing all key,value pairs in this PropertyTree and its children.
     * 
     * @see java.util.Map#entrySet()
     */
    public Set entrySet() {
        return Collections.unmodifiableSet(collectEntries(null, this.flat).entrySet());
    }

    /**
     * Builds a HashMap containing all of the key,value pairs stored in this PropertyTree
     * 
     * @return
     */
    private Map collectEntries(String prefix, boolean flattenEntries) {
        LinkedHashMap entryMap = new LinkedHashMap();

        for (Iterator i = this.children.entrySet().iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();
            PropertyTree child = (PropertyTree) e.getValue();
            String childKey = (String) e.getKey();

            // handle children with values
            if (child.hasDirectValue()) {
                String entryKey = (prefix == null) ? childKey : prefix + "." + childKey;
                String entryValue = child.getDirectValue();

                entryMap.put(entryKey, entryValue);
            }

            // handle children with children
            if (!flattenEntries && child.hasChildren()) {
                String childPrefix = (prefix == null) ? childKey : prefix + "." + childKey;

                entryMap.putAll(child.collectEntries(childPrefix, flattenEntries));
            }
        }

        return entryMap;
    }

    /**
     * @return the number of keys contained, directly or indirectly, in this PropertyTree
     */
    public int size() {
        return entrySet().size();
    }

    /**
     * @see java.util.Map#isEmpty()
     */
    public boolean isEmpty() {
        return entrySet().isEmpty();
    }

    /**
     * Returns an unmodifiable Collection containing the values of all of the entries of this PropertyTree.
     * 
     * @see java.util.Map#values()
     */
    public Collection values() {
        ArrayList values = new ArrayList();

        Set entrySet = entrySet();
        for (Iterator i = entrySet.iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();

            values.add(e.getValue());
        }

        return Collections.unmodifiableList(values);
    }

    /**
     * Returns an unmodifiable Set containing the keys of all of the entries of this PropertyTree.
     * 
     * @see java.util.Map#keySet()
     */
    public Set keySet() {
        LinkedHashSet keys = new LinkedHashSet();

        Set entrySet = entrySet();
        for (Iterator i = entrySet.iterator(); i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();

            keys.add(e.getKey());
        }

        return Collections.unmodifiableSet(keys);
    }

    /**
     * @see java.util.Map#containsKey(java.lang.Object)
     */
    public boolean containsKey(Object key) {
        validateKey(key);

        boolean containsKey = false;

        Set entrySet = entrySet();
        for (Iterator i = entrySet.iterator(); !containsKey && i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();

            Object entryKey = e.getKey();
            containsKey = (entryKey != null) && entryKey.equals(key);
        }

        return containsKey;
    }

    /**
     * @see java.util.Map#containsValue(java.lang.Object)
     */
    public boolean containsValue(Object value) {
        validateValue(value);

        boolean containsValue = false;

        Set entrySet = entrySet();
        for (Iterator i = entrySet.iterator(); !containsValue && i.hasNext();) {
            Map.Entry e = (Map.Entry) i.next();

            Object entryValue = e.getValue();
            containsValue = (entryValue != null) && entryValue.equals(value);
        }

        return containsValue;
    }

    /**
     * Traverses the tree structure until it finds the PropertyTree pointed to by the given key, and returns that PropertyTree
     * instance.
     * <p>
     * Only returns PropertyTree instances; if you want the String value pointed to by a given key, you must call toString() on the
     * returned PropertyTree (after verifying that it isn't null, of course).
     * 
     * @see java.util.Map#get(java.lang.Object)
     */
    public Object get(Object key) {
        validateKey(key);

        return getSubtree((String) key);
    }

    // unsupported operations
    /**
     * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized.
     */
    public void clear() {
        throw new UnsupportedOperationException();
    }

    /**
     * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized.
     */
    public void putAll(Map t) {
        throw new UnsupportedOperationException();
    }

    /**
     * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized.
     */
    public Object remove(Object key) {
        throw new UnsupportedOperationException();
    }

    /**
     * Unsupported, since you can't change the contents of a PropertyTree once it has been initialized.
     */
    public Object put(Object key, Object value) {
        throw new UnsupportedOperationException();
    }
}