net.zcarioca.zcommons.config.util.PropertiesBuilder.java Source code

Java tutorial

Introduction

Here is the source code for net.zcarioca.zcommons.config.util.PropertiesBuilder.java

Source

/*
 * Project: zlib-config
 * 
 * Copyright (C) 2010 zcarioca.net
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zcarioca.zcommons.config.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.zcarioca.zcommons.config.Environment;
import net.zcarioca.zcommons.config.EnvironmentAccessor;

import org.apache.commons.lang.StringUtils;

/**
 * <p>
 * Works as a simple builder used to create properties objects. This class
 * allows for keyword substitution so that if there is a property defined as "
 * <code>my.greeting=Hello</code>" and a property defined as "
 * <code>my.message=${my.greeting} World<code>", the result of calling "<code>prop.getProperty("my.message");</code>
 * " will return the string "Hello World".
 * </p>
 * <p>
 * Properties are overridden by the order in which they are added. If you would
 * like to add a system property and allow that property to be overridden, then
 * you would need to add the system property first, followed by the overridden
 * property and value, if necessary.
 * </p>
 * <p>
 * Note: this object is not thread-safe.
 * </p>
 * 
 * @author zcarioca
 */
public class PropertiesBuilder {
    private static final Pattern pattern = Pattern.compile("\\$\\{[^\\s]+\\}");

    private final Map<String, String> props;
    private Environment environment;
    private boolean generated;

    /**
     * Constructor for the PropertiesBuilder.
     */
    public PropertiesBuilder() {
        this.environment = EnvironmentAccessor.getInstance().getEnvironment();
        this.props = new HashMap<String, String>();
        this.generated = false;
    }

    Environment getEnvironment() {
        return environment;
    }

    void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    /**
     * Adds an environment property to the builder. If the property doesn't
     * exist, a blank value will be added.
     * 
     * @param propertyName The name of the environment property.
     * @return Returns the builder.
     * @throws IllegalArgumentException if the property name is blank or null.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    public PropertiesBuilder addEnvironmentProperty(String propertyName) {
        return addEnvironmentProperty(propertyName, null);
    }

    /**
     * Adds an environment property to the builder, or the default value if the
     * property doesn't exist.
     * 
     * @param propertyName The name of the environment property.
     * @param defaultValue The default value.
     * @return Returns the builder.
     * @throws IllegalArgumentException if the property name is blank or null.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    public PropertiesBuilder addEnvironmentProperty(String propertyName, String defaultValue) {
        String value = environment.getEnvVariable(propertyName, defaultValue);
        return addProperty(propertyName, value);
    }

    /**
     * Adds a system property to the builder. If the property doesn't exist, a
     * blank value will be added.
     * 
     * @param propertyName The name of the system property.
     * @return Returns the builder.
     * @throws IllegalArgumentException if the property name is blank or null.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    public PropertiesBuilder addSystemProperty(String propertyName) {
        return addSystemProperty(propertyName, null);
    }

    /**
     * Adds a system property to the builder, or the default value if the
     * property doesn't exist.
     * 
     * @param propertyName The name of the system property.
     * @param defaultValue The default value.
     * @return Returns the builder.
     * @throws IllegalArgumentException if the property name is blank or null.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    public PropertiesBuilder addSystemProperty(String propertyName, String defaultValue) {
        if (StringUtils.isEmpty(propertyName)) {
            throw new IllegalArgumentException("Empty or NULL property name");
        }
        return addProperty(propertyName, environment.getSystemProperty(propertyName, defaultValue));
    }

    /**
     * Adds a new property to the builder. If the value is null, an empty string
     * will be added.
     * 
     * @param propertyName The property name.
     * @param propertyValue The property value (may be null).
     * @return Returns the builder.
     * @throws IllegalArgumentException if the property name is blank or null.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    public PropertiesBuilder addProperty(String propertyName, String propertyValue) {
        if (generated) {
            throw new IllegalStateException(
                    "The properties object for this builder has already been built. A separate builder is needed for each properties object.");
        }
        if (StringUtils.isEmpty(propertyName)) {
            throw new IllegalArgumentException("Empty or NULL property name");
        }
        if (propertyValue == null) {
            propertyValue = "";
        }
        this.props.put(propertyName, propertyValue);
        return this;
    }

    /**
     * Adds all of the environment properties to the builder.
     * 
     * @return Returns the builder.
     * @throws IllegalArgumentException if any of the keys are null or blank
     *         strings.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    public PropertiesBuilder addAllEnvironmentProperties() {
        return addAll(environment.getAllEnvProperties());
    }

    /**
     * Adds all of the system properties to the builder.
     * 
     * @return Returns the builder.
     * @throws IllegalArgumentException if any of the keys are null or blank
     *         strings.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    public PropertiesBuilder addAllSystemProperties() {
        return addAll(environment.getAllSystemProperties());
    }

    /**
     * Adds all of the entries in the map to the builder. If any values are null,
     * an empty string will be added in its place.
     * 
     * @param props The map of properties.
     * @return Returns the builder.
     * @throws IllegalArgumentException if any of the keys are null or blank
     *         strings.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    @SuppressWarnings("rawtypes")
    public PropertiesBuilder addAll(Map props) {
        for (Object key : props.keySet()) {
            Object val = props.get(key);
            this.addProperty(key.toString(), val != null ? val.toString() : null);
        }
        return this;
    }

    /**
     * Reads the properties from the input stream.
     * 
     * @param inputStream The input stream.
     * @return Returns the builder.
     * 
     * @throws IOException if an error occurred when reading from the input
     *         stream.
     */
    public PropertiesBuilder readAll(InputStream inputStream) throws IOException {
        if (inputStream == null) {
            throw new IOException("Empty or NULL input stream");
        }
        Properties props = new Properties();
        props.load(inputStream);
        addAll(props);
        return this;
    }

    /**
     * Builds the {@link Properties} object.
     * 
     * @return Returns a new {@link Properties} object.
     * @throws IllegalStateException if this method has been called more than
     *         once.
     */
    public Properties build() {
        if (this.generated) {
            throw new IllegalStateException("The build() method has already been called on this instance.");
        }
        this.generated = true;
        Properties newProps = new Properties();

        for (String key : this.props.keySet()) {
            newProps.setProperty(key, getFilteredValue(key));
        }

        return newProps;
    }

    /**
     * Returns the number of properties currently within this builder.
     * 
     * @return Returns the number of properties currently in the builder.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    int size() {
        if (generated) {
            throw new IllegalStateException(
                    "The properties object for this builder has already been built. A separate builder is needed for each properties object.");
        }
        return this.props.size();
    }

    /**
     * Returns the property value for given property name currently within this
     * builder.
     * 
     * @param propertyName The property name.
     * @return Returns the unfiltered property value, may be null.
     * @throws IllegalStateException if the {@link PropertiesBuilder#build()}
     *         method has already been called on this instance.
     */
    String getProperty(String propertyName) {
        if (generated) {
            throw new IllegalStateException(
                    "The properties object for this builder has already been built. A separate builder is needed for each properties object.");
        }
        return this.props.get(propertyName);
    }

    String getFilteredValue(String key) {
        String value = this.props.get(key);
        if (StringUtils.isEmpty(value)) {
            return "";
        }
        Matcher matcher = pattern.matcher(value);
        while (matcher.find()) {
            String grouping = matcher.group();
            String valKey = grouping.substring(2, grouping.length() - 1);

            String prop = getFilteredValue(valKey);
            if (!StringUtils.isEmpty(prop)) {
                value = matcher.replaceFirst(prop);
                matcher = pattern.matcher(value);
            }
        }
        return value;
    }
}