com.commsen.apropos.core.PropertiesManager.java Source code

Java tutorial

Introduction

Here is the source code for com.commsen.apropos.core.PropertiesManager.java

Source

/*
 * Copyright 2008 COMMSEN International
 *
 * This file is part of APropOS.
 * 
 * APropOS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * APropOS 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with APropOS.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.commsen.apropos.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;

import com.thoughtworks.xstream.XStream;

/**
 * The class provides static synchronized methods for common operations on {@link Property} and
 * {@link PropertyPackage} like add, delete, update, etc. It is also responsible for persisting data
 * after any change and loading data form storage on startup.
 * 
 * @author Milen Dyankov
 * 
 */
public class PropertiesManager {

    /**
     * A folder to persists data into
     */
    private static final File storage = new File(SystemUtils.getUserHome(), ".apropos");

    /**
     * The name of the file containing data
     */
    private static final File dataFile = new File(storage, "data.xml");

    /**
     * 
     */
    private static XStream xStream = new XStream();

    /**
     * Map of all packages. Key is package name
     */
    private static Map<String, PropertyPackage> allPackages = new HashMap<String, PropertyPackage>();

    /**
     * The singletone instance used to persist data into file
     */
    private static PropertiesManager instance = new PropertiesManager();

    static {
        xStream.alias("package", PropertyPackage.class);

        try {
            FileUtils.forceMkdir(storage);
        } catch (IOException e) {
        }
        load();
    }

    /**
     * List of all top level packages
     */
    private List<PropertyPackage> rootPackages = new LinkedList<PropertyPackage>();

    /**
     * Private constructor.
     */
    private PropertiesManager() {
        // private constructor
    }

    /**
     * Loads properties form {@value #dataFile} and fills {@link #rootPackages} and
     * {@value #allPackages}. This method is called only once - during class initialization (from a
     * static block)
     */
    private static void load() {
        if (dataFile.exists() && dataFile.isFile()) {
            FileInputStream dataStream = null;
            try {
                dataStream = new FileInputStream(dataFile);
                instance = (PropertiesManager) xStream.fromXML(dataStream);
            } catch (FileNotFoundException e) {
                throw new InternalError(e.getMessage());
            } finally {
                if (dataStream != null)
                    try {
                        dataStream.close();
                    } catch (IOException e) {
                        // oops failed to close stream
                    }
            }
            for (PropertyPackage rootPackage : instance.rootPackages) {
                addToAllPackages(rootPackage);
            }
        }
    }

    /**
     * Adds a package and all of it's children to {@link #allPackages}.
     * 
     * @param propertyPackage the package to add
     */
    private static void addToAllPackages(PropertyPackage propertyPackage) {
        if (propertyPackage == null)
            return;
        allPackages.put(propertyPackage.getName(), propertyPackage);
        if (propertyPackage.getChildren() != null) {
            for (PropertyPackage child : propertyPackage.getChildren()) {
                addToAllPackages(child);
            }
        }
    }

    /**
     * Deletes a package and all of it's children from {@link #allPackages}.
     * 
     * @param propertyPackage the package to add
     */
    private static void deleteFromAllPackages(PropertyPackage propertyPackage) {
        if (propertyPackage == null)
            return;
        if (propertyPackage.getChildren() != null) {
            for (PropertyPackage child : propertyPackage.getChildren()) {
                deleteFromAllPackages(child);
            }
        }
        allPackages.remove(propertyPackage.getName());
    }

    /**
     * Saves data into {@link #dataFile}. This method is called by other static methods of this
     * class after modifying any data.
     */
    private static void save() {
        FileOutputStream fileStream = null;
        try {
            fileStream = new FileOutputStream(dataFile);
            xStream.toXML(instance, fileStream);
        } catch (FileNotFoundException e) {
            throw new InternalError(e.getMessage());
        } finally {
            if (fileStream != null)
                try {
                    fileStream.close();
                } catch (IOException e) {
                    // oops failed to close stream
                }
        }
    }

    /**
     * Adds new {@link PropertyPackage}. NOTE: The object passed is cloned internally.
     * 
     * @param propertyPackage the {@link PropertyPackage} to be added. Can not be null.
     * @throws PropertiesException if package with that name already exists or if circular
     *         parent/child relation is detected.
     * @throws IllegalArgumentException if <code>propertyPackage</code> is null
     */
    public static synchronized void addPropertyPackage(PropertyPackage propertyPackage) throws PropertiesException {
        if (propertyPackage == null)
            throw new IllegalArgumentException("propertySet can not be null");
        String key = propertyPackage.getName();
        if (allPackages.containsKey(key))
            throw new PropertiesException("Configuration called " + key + " already exists");

        String parentPackageName = null;
        if (propertyPackage.getParent() != null) {
            parentPackageName = propertyPackage.getParent().getName();
            propertyPackage.setParent(null);
        }

        PropertyPackage clonedPackage = null;
        try {
            clonedPackage = (PropertyPackage) propertyPackage.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e.toString());
        }

        clonedPackage.setParent(allPackages.get(parentPackageName));
        allPackages.put(key, clonedPackage);
        if (clonedPackage.getParent() == null) {
            instance.rootPackages.add(clonedPackage);
        }
        // packages.add(propertyPackage);
        save();
    }

    /**
     * Returns unmodifiable (read only) list of all top level packages
     * 
     * @return unmodifiable (read only) list of all top level packages
     */
    public static List<PropertyPackage> getRootPropertyPackages() {
        return Collections.unmodifiableList(instance.rootPackages);
    }

    /**
     * Returns unmodifiable (read only) list of all package names
     * 
     * @return unmodifiable (read only) list of all package names
     */
    public static List<String> getPropertyPackagesNames() {
        LinkedList<String> result = new LinkedList<String>(allPackages.keySet());
        Collections.sort(result);
        return Collections.unmodifiableList(result);
    }

    /**
     * Returns a clone of {@link PropertyPackage} called <code>name</code>. If no such
     * {@link PropertyPackage} exists it will return <code>null</code>.
     * 
     * @return a {@link PropertyPackage} called <code>name</code> or <code>null</code>.
     */
    public static PropertyPackage getPropertyPackage(String name) {
        try {
            PropertyPackage propertyPackage = allPackages.get(name);
            return propertyPackage == null ? null : (PropertyPackage) propertyPackage.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e.getMessage());
        }
    }

    /**
     * Deletes the {@link PropertyPackage} specified by <code>packageName</code> and all it's
     * children.
     * 
     * @param packageName
     * @throws PropertiesException
     */
    public static synchronized void deletePropertyPackage(String packageName) throws PropertiesException {
        if (StringUtils.isBlank(packageName))
            throw new IllegalArgumentException("package name can not be null nor empty string");
        if (!allPackages.containsKey(packageName))
            throw new PropertiesException("No package called " + packageName + " found ");
        PropertyPackage propertyPackage = allPackages.get(packageName);
        if (propertyPackage.getParent() != null) {
            propertyPackage.setParent(null); // to delete it from parent's collection
        } else {
            instance.rootPackages.remove(propertyPackage);
        }
        deleteFromAllPackages(propertyPackage);
        save();
    }

    /**
     * Adds new {@link Property} in {@link PropertyPackage} called <code>packageName</code> by
     * calling {@link PropertyPackage#addProperty(Property)}.
     * 
     * @param packageName the name of the package. Can not be <code>null</code> or empty.
     * @param property the {@link Property} to add. Can not be <code>null</code>
     * @return a new clone of the {@link PropertyPackage} made after the property is added
     * @throws PropertiesException if no package called <code>packageName</code> found or if such
     *         {@link Property} already exists in this package
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     */
    public static synchronized PropertyPackage addProperty(String packageName, Property property)
            throws PropertiesException {
        if (StringUtils.isBlank(packageName))
            throw new IllegalArgumentException("package name can not be null nor empty string");
        if (property == null)
            throw new IllegalArgumentException("property can not be null");
        if (!allPackages.containsKey(packageName))
            throw new PropertiesException("No package called " + packageName + " found ");
        PropertyPackage propertyPackage = allPackages.get(packageName);
        try {
            propertyPackage.addProperty((Property) property.clone());
            save();
            return (PropertyPackage) propertyPackage.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e.toString());
        }
    }

    /**
     * Updates {@link Property} in {@link PropertyPackage} called <code>packageName</code>. The
     * actual update is handled by {@link PropertyPackage#updateProperty(Property)}
     * 
     * @param packageName the name of the package. Can not be <code>null</code> or empty.
     * @param property the {@link Property} to be updated. Can not be <code>null</code>
     * @return a new clone of the {@link PropertyPackage} made after the property is updated
     * @throws PropertiesException if no package called <code>packageName</code> found or if no
     *         such {@link Property} exists in this package
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     */
    public static synchronized PropertyPackage updateProperty(String packageName, Property property)
            throws PropertiesException {
        if (StringUtils.isBlank(packageName))
            throw new IllegalArgumentException("package name can not be null nor empty string");
        if (property == null)
            throw new IllegalArgumentException("property can not be null");
        if (!allPackages.containsKey(packageName))
            throw new PropertiesException("No package called " + packageName + " found ");
        PropertyPackage propertyPackage = allPackages.get(packageName);
        try {
            propertyPackage.updateProperty((Property) property.clone());
            save();
            return (PropertyPackage) propertyPackage.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e.toString());
        }
    }

    /**
     * Updates existing {@link Property} called <code>oldPropertyName</code> in
     * {@link PropertyPackage} called <code>packageName</code> with values from
     * <code>property</code>. The actual update is handled by
     * {@link PropertyPackage#updateProperty(Property)}.
     * 
     * @param packageName the name of the package. Can not be <code>null</code> or empty.
     * @param oldPropertyName the name of the property to be updated
     * @param property the {@link Property} object to get the values from. Can not be
     *        <code>null</code>
     * @return a new clone of the {@link PropertyPackage} made after the property is updated
     * @throws PropertiesException if no package called <code>packageName</code> found or if no
     *         such {@link Property} exists in this package
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     */
    public static synchronized PropertyPackage updateProperty(String packageName, String oldPropertyName,
            Property property) throws PropertiesException {
        if (StringUtils.isBlank(packageName))
            throw new IllegalArgumentException("package name can not be null nor empty string");
        if (property == null)
            throw new IllegalArgumentException("property can not be null");
        if (!allPackages.containsKey(packageName))
            throw new PropertiesException("No package called " + packageName + " found ");
        PropertyPackage propertyPackage = allPackages.get(packageName);
        try {
            propertyPackage.updateProperty(oldPropertyName, (Property) property.clone());
            save();
            return (PropertyPackage) propertyPackage.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e.toString());
        }
    }

    /**
     * Imports external properties into {@link PropertyPackage} called <code>packageName</code> .
     * The actual import is handled by {@link PropertyPackage#importProperties(Properties, boolean)}.
     * 
     * @param packageName the name of the package. Can not be <code>null</code> or empty.
     * @param externalProperties the properties to import. This can not be <code>null</code>
     * @param overwrite boolean flag indicates whether to override existing properties
     * @return a new clone of the {@link PropertyPackage} made after the properties are imported
     * @throws PropertiesException if there is no package called <code>packageName</code>
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     */
    public static synchronized PropertyPackage importProperties(String packageName, Properties externalProperties,
            boolean overwrite) throws PropertiesException {
        if (StringUtils.isBlank(packageName))
            throw new IllegalArgumentException("package name can not be null nor empty string");
        if (!allPackages.containsKey(packageName))
            throw new PropertiesException("No package called " + packageName + " found ");
        PropertyPackage propertyPackage = allPackages.get(packageName);
        try {
            propertyPackage.importProperties(externalProperties, overwrite);
            save();
            return (PropertyPackage) propertyPackage.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e.toString());
        }
    }

    /**
     * Deletes {@link Property} called <code>propertyName</code> from {@link PropertyPackage}
     * called <code>packageName</code>. The actual deletion is handled by
     * {@link PropertyPackage#removeProperty(String)}.
     * 
     * @param packageName the name of the package. Can not be <code>null</code> or empty.
     * @param propertyName the name of the property to be updated
     * @return a new clone of the {@link PropertyPackage} made after the property is deleted
     * @throws PropertiesException if there is no package called <code>packageName</code> or no
     *         property called <code>propertyName</code>
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     */
    public static synchronized PropertyPackage deleteProperty(String packageName, String propertyName)
            throws PropertiesException {
        if (StringUtils.isBlank(packageName))
            throw new IllegalArgumentException("package name can not be null nor empty string");
        if (StringUtils.isBlank(propertyName))
            throw new IllegalArgumentException("property can not be null nor empty string");
        if (!allPackages.containsKey(packageName))
            throw new PropertiesException("No package called " + packageName + " found ");
        PropertyPackage propertyPackage = allPackages.get(packageName);
        propertyPackage.removeProperty(propertyName);
        save();
        try {
            return (PropertyPackage) propertyPackage.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e.toString());
        }
    }

}