package com.sun.syndication.io.impl;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.WeakHashMap;

/**
 * Properties loader that aggregates a master properties file and several extra
 * properties files, all from the current classpath.
 * <P>
 * The master properties file has to be in a distinct location than the extra
 * properties files. First the master properties file is loaded, then all the
 * extra properties files in their order of appearance in the classpath.
 * <P>
 * Current use cases (plugin manager for parsers/converters/generators for feeds
 * and modules and date formats) assume properties are list of tokens, that why
 * the only method to get property values is the getTokenizedProperty().
 * <p>
 * 
 * @author Alejandro Abdelnur
 * 
 */
public class PropertiesLoader {

    private static final String MASTER_PLUGIN_FILE = "com/sun/syndication/rome.properties";
    private static final String EXTRA_PLUGIN_FILE = "rome.properties";

    private static Map<ClassLoader, PropertiesLoader> clMap = new WeakHashMap<ClassLoader, PropertiesLoader>();

    /**
     * Returns the PropertiesLoader singleton used by ROME to load plugin
     * components.
     * 
     * @return PropertiesLoader singleton.
     * 
     */
    public static PropertiesLoader getPropertiesLoader() {
        synchronized (PropertiesLoader.class) {
            PropertiesLoader loader = clMap.get(Thread.currentThread().getContextClassLoader());
            if (loader == null) {
                try {
                    loader = new PropertiesLoader(MASTER_PLUGIN_FILE, EXTRA_PLUGIN_FILE);
                    clMap.put(Thread.currentThread().getContextClassLoader(), loader);
                } catch (final IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
            return loader;
        }
    }

    private final Properties[] properties;

    /**
     * Creates a PropertiesLoader.
     * <p>
     * 
     * @param masterFileLocation master file location, there must be only one.
     * @param extraFileLocation extra file location, there may be many.
     * @throws IOException thrown if one of the properties file could not be
     *             read.
     * 
     */
    private PropertiesLoader(final String masterFileLocation, final String extraFileLocation) throws IOException {
        final List<Properties> propertiesList = new ArrayList<Properties>();
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        try {
            final InputStream is = classLoader.getResourceAsStream(masterFileLocation);
            final Properties p = new Properties();
            p.load(is);
            is.close();
            propertiesList.add(p);
        } catch (final IOException ioex) {
            final IOException ex = new IOException("could not load ROME master plugins file [" + masterFileLocation + "], " + ioex.getMessage());
            ex.setStackTrace(ioex.getStackTrace());
            throw ex;
        }

        final Enumeration<URL> urls = classLoader.getResources(extraFileLocation);
        while (urls.hasMoreElements()) {
            final URL url = urls.nextElement();
            final Properties p = new Properties();
            try {
                final InputStream is = url.openStream();
                p.load(is);
                is.close();
            } catch (final IOException ioex) {
                final IOException ex = new IOException("could not load ROME extensions plugins file [" + url.toString() + "], " + ioex.getMessage());
                ex.setStackTrace(ioex.getStackTrace());
                throw ex;
            }
            propertiesList.add(p);
        }

        properties = new Properties[propertiesList.size()];
        propertiesList.toArray(properties);
    }

    /**
     * Returns an array of tokenized values stored under a property key in all
     * properties files. If the master file has this property its tokens will be
     * the first ones in the array.
     * <p>
     * 
     * @param key property key to retrieve values
     * @param separator String with all separator characters to tokenize from
     *            the values in all properties files.
     * @return all the tokens for the given property key from all the properties
     *         files.
     * 
     */
    public String[] getTokenizedProperty(final String key, final String separator) {
        final List<String> entriesList = new ArrayList<String>();
        for (final Properties property : properties) {
            final String values = property.getProperty(key);
            if (values != null) {
                final StringTokenizer st = new StringTokenizer(values, separator);
                while (st.hasMoreTokens()) {
                    final String token = st.nextToken();
                    entriesList.add(token);
                }
            }
        }
        final String[] entries = new String[entriesList.size()];
        entriesList.toArray(entries);
        return entries;
    }

    /**
     * Returns an array of values stored under a property key in all properties
     * files. If the master file has this property it will be the first ones in
     * the array.
     * <p>
     * 
     * @param key property key to retrieve values
     * @return all the values for the given property key from all the properties
     *         files.
     * 
     */
    public String[] getProperty(final String key) {
        final List<String> entriesList = new ArrayList<String>();
        for (final Properties property : properties) {
            final String values = property.getProperty(key);
            if (values != null) {
                entriesList.add(values);
            }
        }
        final String[] entries = new String[entriesList.size()];
        entriesList.toArray(entries);
        return entries;
    }

}
