Package com.bbn.openmap

Source Code of com.bbn.openmap.PropertyHandler

// **********************************************************************
//
// <copyright>
//
//  BBN Technologies
//  10 Moulton Street
//  Cambridge, MA 02138
//  (617) 873-8000
//
//  Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/PropertyHandler.java,v $
// $RCSfile: PropertyHandler.java,v $
// $Revision: 1.20.2.8 $
// $Date: 2008/02/28 23:36:26 $
// $Author: dietrick $
//
// **********************************************************************

package com.bbn.openmap;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;

import com.bbn.openmap.event.ProgressEvent;
import com.bbn.openmap.event.ProgressListener;
import com.bbn.openmap.event.ProgressSupport;
import com.bbn.openmap.gui.ProgressListenerGauge;
import com.bbn.openmap.gui.WindowSupport;
import com.bbn.openmap.plugin.PlugIn;
import com.bbn.openmap.proj.ProjectionFactory;
import com.bbn.openmap.util.ComponentFactory;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;

/**
* The PropertyHandler object is the organizer of properties, looking for
* settings on how to configure OpenMap components. It is designed to look
* through a series of locations to find properties files, loading them in
* order. If there is a name conflict for a property, the last version of the
* property set is the one that gets used. This object isn't really interested
* in hooking up with other objects. It's assumed that many objects will want to
* contact this object, and find the properties that apply to them. There is one
* exception this: When components start implementing the PropertyProvider
* interface, and the PropertyHandler becomes capable of creating an properties
* file, then the PropertyHandler will be able to use the BeanContext to query
* PropertyProviders to get their properties to put in the properties file.
* <P>
*
* The PropertyHandler looks in several places for an openmap.properties file:
* <UL>
* <LI>as a resource in the code base.
* <LI>in the configDir set as a system property at runtime.
* <LI>in the user's home directory.
* </UL>
*
* For each properties file, a check is performed to look within for an include
* property containing a marker name list. That list is parsed, and each item is
* checked (markerName.URL) for an URL to another properties file.
* <P>
*
* Also significant, the PropertyHandler can be given a BeanContext to load
* components. For this, the openmap.components property contains a marker name
* list for openmap objects. Each member of the list is then used to look for
* another property (markername.class) which specifies which class names are to
* be instantiated and added to the BeanContext. Intelligent components are
* smart enough to wire themselves together. Order does matter for the
* openmap.components property, especially for components that get added to
* lists and menus. Place the components in the list in the order that you want
* components added to the MapHandler.
* <P>
*
* If the debug.showprogress environment variable is set, the PropertyHandler
* will display a progress bar when it is creating components. If the
* debug.properties file is set, the steps that the PropertyHandler takes in
* looking for property files will be displayed.
* <P>
*
* If the PropertyHandler is created with an empty constructor or with a null
* Properties object, it will do the search for an openmap.properties file. If
* you don't want it to do that search, create it with an empty Properties
* object.
*/
public class PropertyHandler extends MapHandlerChild implements
        SoloMapComponent {

    /**
     * All components can have access to an I18n object, which is provided by
     * the Environment.
     */
    protected transient I18n i18n = Environment.getI18n();

    /**
     * The propertyPrefix can be set to reflect a particular set of properties,
     * or for an application. If this variable is not set, 'openmap' will be
     * used. This prefix will be placed in front of the default properties file
     * that will be sought if a specific properties file is not specified, and
     * will also be placed in front of the standard application component
     * properties.
     */
    protected String propertyPrefix;

    /**
     * The appendix for the name of the properties file to read. The
     * propertyPrefix will be prepended to this string for the default property
     * file search.
     */
    public final static String propsFileName = "properties";

    /**
     * The name of the system directory containing a properties file. The
     * propertyPrefix.configDir property will be checked for a possible location
     * for properties.
     */
    public final static String configDirProperty = "configDir";

    /**
     * The property name used to hold a list of marker names. Each marker name
     * is used to create another property to look for to create a component to
     * add to a BeanContext. For example:
     * <P>
     *
     * <PRE>
     * # if 'openmap' is the PropertyHandler property prefix...
     * openmap.components=name1 name2 name3
     * name1.class=com.bbn.openmap.InformationDelegator
     * name2.class=com.bbn.openmap.MouseDelegator
     * name3.class=com.bbn.openmap.LayerHandler
     *
     * </PRE>
     */
    public final static String componentProperty = "components";

    /**
     * The property name used to hold a list of marker names. Each marker name
     * is used to create another property to look for to connect to a URL to
     * load a properties file. For example:
     * <P>
     *
     * <PRE>
     *
     * openmap.include=name1 name2
     * name1.URL=http://openmap.bbn.com/props/link.properties
     * name2.URL=file:///usr/local/openmap/props/shape.properties
     *
     * </PRE>
     */
    public final static String includeProperty = "include";

    /**
     * The property name used to hold a file, resource or URL of a file to use
     * containing localized properties, like layer names. This is optional, if
     * it's not in the openmap.properties file or the properties file being read
     * in, an openmap_&ltlocalization string&gt.properties file will be searched
     * for in the classpath (i.e. openmap.localized=openmap_en_US.properties).
     */
    public final static String localizedProperty = "localized";

    /** Final openmap properties object. */
    protected Properties properties = new Properties();

    /**
     * Container to hold prefixes for components that have been created, in
     * order to determine if duplicates might have been made. Important if
     * properties are going to be written out, so that property scoping can
     * occur properly. This collection holds prefixes of objects that have been
     * created by this PropertyHandler, and also prefixes that have been given
     * out on request.
     */
    protected Set usedPrefixes = Collections.synchronizedSet(new HashSet());

    protected ProgressSupport progressSupport;

    /**
     * Flag to set whether the PropertyHandler should provide status updates to
     * any progress listeners, when it is building components.
     */
    protected boolean updateProgress = false;

    /**
     * A hashtable to keep track of property prefixes and the objects that were
     * created for them.
     */
    protected Hashtable prefixLibrarian = new Hashtable();

    protected boolean DEBUG = false;

    /**
     * Create a PropertyHandler object that checks in the default order for
     * openmap.properties files. It checks for the openmap.properties file as a
     * resource, in the configDir if specified as a system property, and lastly,
     * in the user's home directory. If you want an empty PropertyHandler that
     * doesn't do the search, use the constructor that takes a
     * java.util.Properties object and provide it with empty Properties.
     */
    public PropertyHandler() {
        this(false);
    }

    /**
     * Create a PropertyHandler object that checks in the default order for
     * openmap.properties files. It checks for the openmap.properties file as a
     * resource, in the configDir if specified as a system property, and lastly,
     * in the user's home directory.
     *
     * @param provideProgressUpdates if true, a progress bar will be displayed
     *        to show the progress of building components.
     */
    public PropertyHandler(boolean provideProgressUpdates) {
        DEBUG = Debug.debugging("properties");
        updateProgress = provideProgressUpdates;
        searchForAndLoadProperties();
    }

    /**
     * Constructor to take resource name, file path or URL string as argument,
     * to create context for a particular map.
     */
    public PropertyHandler(String urlString) throws MalformedURLException,
            IOException {
        this(PropUtils.getResourceOrFileOrURL(urlString));
    }

    /**
     * Constructor to take path (URL) as argument, to create context for a
     * particular map.
     */
    public PropertyHandler(URL url) throws IOException {
        DEBUG = Debug.debugging("properties");
        InputStream is = null;

        if (url != null) {
            // Open URL to read in properties
            is = url.openStream();
        }

        Properties tmpProperties = new Properties();
        if (is != null) {
            tmpProperties.load(is);
        }

        init(tmpProperties, "URL");
        Environment.init(getProperties());
    }

    /**
     * Constructor to take Properties object as argument, to create context for
     * a particular map.
     */
    public PropertyHandler(Properties props) {
        DEBUG = Debug.debugging("properties");
        init(props, null);
        Environment.init(getProperties());
    }

    /**
     * Look for openmap.properties files as a resource in the classpath, in the
     * config directory, and in the user's home directory, in that order. If any
     * property is duplicated in any version, last one wins.
     */
    protected void searchForAndLoadProperties() {

        Properties tmpProperties = new Properties();
        Properties includeProperties;
        Properties localizedProperties;
        boolean foundProperties = false;

        boolean showDebugMessages = false;

        if (Debug.debugging("locale")) {
            java.util.Locale.setDefault(new java.util.Locale("pl", "PL"));
        }

        if (DEBUG) {
            showDebugMessages = true;
        }

        if (Debug.debugging("showprogress")) {
            updateProgress = true;
        }

        if (showDebugMessages) {
            Debug.output("***** Searching for properties ****");
        }

        String propertyPrefix = getPropertyPrefix();

        String propsFileName = propertyPrefix + PropertyHandler.propsFileName;

        // look for openmap.properties file in jar archive(of course
        // only in same package as this class) or wherever this
        // object's class file lives.
        if (showDebugMessages) {
            Debug.output("PropertyHandler: Looking for " + propsFileName
                    + " in Resources");
        }

        InputStream propsIn = getClass().getResourceAsStream(propsFileName);

        // Look in the codebase for applets...
        if (propsIn == null && Environment.isApplet()) {
            URL[] cba = new URL[1];
            cba[0] = Environment.getApplet().getCodeBase();

            URLClassLoader ucl = URLClassLoader.newInstance(cba);
            propsIn = ucl.getResourceAsStream(propsFileName);
        }

        if (propsIn == null) {
            propsIn = ClassLoader.getSystemResourceAsStream(propsFileName);

            if (propsIn != null && showDebugMessages) {
                Debug.output("Loading properties from System Resources: "
                        + propsFileName);
            }
        } else {
            if (showDebugMessages) {
                Debug.output("Loading properties from file " + propsFileName
                        + " from package of class " + getClass());
            }
        }

        if (propsIn != null) {
            foundProperties = PropUtils.loadProperties(tmpProperties, propsIn);
            init(tmpProperties, "resources");
            tmpProperties.clear();
        }

        if (!foundProperties && (Environment.isApplet() || showDebugMessages)) {
            Debug.output("PropertyHandler: Unable to locate as resource: "
                    + propsFileName);
        }

        // Seems like we can kick out here in event of Applet...
        if (Environment.isApplet()) {
            Environment.init(getProperties());
            return;
        }

        Properties systemProperties;

        try {
            systemProperties = System.getProperties();
        } catch (java.security.AccessControlException ace) {
            systemProperties = new Properties();
        }

        String configDirProperty = propertyPrefix
                + PropertyHandler.configDirProperty;

        String openmapConfigDirectory = systemProperties.getProperty(configDirProperty);

        if (openmapConfigDirectory == null) {
            Vector cps = Environment.getClasspathDirs();
            String defaultExtraDir = "share";
            for (int searchCount = 0; searchCount < cps.size(); searchCount++) {
                File shareDir = new File((String) cps.elementAt(searchCount), defaultExtraDir);
                if (shareDir.exists()) {
                    // Debug.output("Found share directory: " +
                    // shareDir.getPath());
                    openmapConfigDirectory = shareDir.getPath();
                    break;
                    // } else {
                    // Debug.output("No share directory in: " +
                    // shareDir.getPath());
                }
            }
        }

        Environment.addPathToClasspaths(openmapConfigDirectory);

        // in OpenMap config directory
        if (showDebugMessages) {
            Debug.output("PropertyHandler: Looking for "
                    + propsFileName
                    + " in configuration directory: "
                    + (openmapConfigDirectory == null ? "not set"
                            : openmapConfigDirectory));
        }

        // We want foundProperties to reflect if properties have ever
        // been found.
        foundProperties |= PropUtils.loadProperties(tmpProperties,
                openmapConfigDirectory,
                propsFileName);

        // Include properties from config file properties.
        includeProperties = getIncludeProperties(tmpProperties.getProperty(propertyPrefix
                + includeProperty),
                tmpProperties);
        merge(includeProperties,
                "include file properties",
                openmapConfigDirectory);

        // OK, now merge the config file properties into the main
        // properties
        merge(tmpProperties, propsFileName, openmapConfigDirectory);
        // Clear out the tmp
        tmpProperties.clear();

        // Let system properties take precidence over resource and
        // config dir properties.
        merge(systemProperties, "system properties", "system");

        // in user's home directory, most precedence.
        String userHomeDirectory = systemProperties.getProperty("user.home");
        if (showDebugMessages) {
            Debug.output("PropertyHandler: Looking for " + propsFileName
                    + " in user's home directory: " + userHomeDirectory);
        }

        // We want foundProperties to reflect if properties have ever
        // been found.
        foundProperties |= PropUtils.loadProperties(tmpProperties,
                userHomeDirectory,
                propsFileName);
        if (showDebugMessages) {
            Debug.output("***** Done with property search ****");
        }

        if (!foundProperties && !Environment.isApplet()) {
            PropUtils.copyProperties(PropUtils.promptUserForProperties(),
                    properties);
        }

        // Before we the user properties into the overall properties,
        // need to check for the include properties URLs, and load
        // those first.
        includeProperties = getIncludeProperties(tmpProperties.getProperty(propertyPrefix
                + includeProperty),
                tmpProperties);
        merge(includeProperties, "include file properties", userHomeDirectory);

        // Now, load the user home preferences last, since they take
        // the highest precedence.
        merge(tmpProperties, propsFileName, userHomeDirectory);

        // Well, they used to take the highest precedence. Now, we
        // look for a localized property file, and write those
        // properties on top.
        localizedProperties = getLocalizedProperties(tmpProperties.getProperty(propertyPrefix
                + localizedProperty),
                userHomeDirectory);
        merge(localizedProperties, "localized properties", null);

        Environment.init(getProperties());
    }

    /**
     * Load the localized properties that will take precidence over all other
     * properties. If the localizedPropertyFile is null, a localized version of
     * the openmap.properties file will be searched for in the classpath and in
     * the user home directory (if that isn't null as well).
     */
    protected Properties getLocalizedProperties(String localizedPropertyFile,
                                                String userHomeDirectory) {
        Properties props = null;
        if (localizedPropertyFile == null) {
            java.util.Locale loc = java.util.Locale.getDefault();
            localizedPropertyFile = "openmap_" + loc.toString() + ".properties";
        }

        boolean tryHomeDirectory = false;
        if (DEBUG) {
            Debug.output("PropertyHandler: Looking for localized file: "
                    + localizedPropertyFile);
        }

        try {
            URL propsURL = PropUtils.getResourceOrFileOrURL(localizedPropertyFile);
            if (propsURL == null) {
                tryHomeDirectory = true;
            } else {
                if (DEBUG) {
                    Debug.output("Found localized properties in classpath");
                }
                props = fetchProperties(propsURL);
            }
        } catch (MalformedURLException murle) {
            Debug.error("PropertyHandler can't find localized property file: "
                    + localizedPropertyFile);
            tryHomeDirectory = true;
        }

        if (tryHomeDirectory) {
            props = new Properties();
            if (!PropUtils.loadProperties(props,
                    userHomeDirectory,
                    localizedPropertyFile)) {
                props = null;
            } else {
                if (DEBUG) {
                    Debug.output("Found localized properties in home directory");
                }
            }
        }

        if (props == null) {
            props = new Properties();
        }

        return props;
    }

    /**
     * Initialize internal properties from Properties object. Appends all the
     * properties it finds, overwriting the ones with the same key. Called by
     * the two constructors where a Properties object is passed in, or when a
     * URL for a Properties file is provided. This is not called by the
     * consstructor that has to go looking for the properties to use.
     *
     * @param props the properties to merge into the properties held by the
     *        PropertyHandler.
     * @param howString a string describing where the properties come from. Just
     *        used for debugging purposes, so passing in a null value is no big
     *        deal.
     * @return the properties contained in this PropertyHandler.
     */
    protected void init(Properties props, String howString) {

        // Include properties noted in resources properties.
        Properties includeProperties = getIncludeProperties(props.getProperty(getPropertyPrefix()
                + includeProperty),
                props);
        merge(includeProperties, "include file properties", howString);

        if (!Environment.isApplet()) {
            Properties systemProperties = System.getProperties();
            merge(systemProperties, props);
        }

        merge(props, "loaded", howString);

        if (DEBUG) {
            Debug.output("PropertyHandler: loaded properties");
        }
    }

    /**
     * Take a marker name list (space separated names), and open the properties
     * files listed in the propertu with keys of marker.URL.
     *
     * @param markerList space separated marker names in a single string that
     *        needs to be parsed.
     * @param props the properties that the markerList comes from, in order to
     *        get the marker.URL properties.
     * @return an allocated Properties object containing all the properties from
     *         the inlude files. If no include files are listed, the Properties
     *         object is empty, not null.
     */
    protected Properties getIncludeProperties(String markerList,
                                              Properties props) {
        Properties newProps = new Properties();
        Properties tmpProps = new Properties();
        Vector includes = PropUtils.parseSpacedMarkers(markerList);
        int size = includes.size();
        if (size > 0) {

            if (Debug.debugging("propertiesdetail")) {
                Debug.output("PropertyHandler: handling include files: "
                        + includes);
            }

            for (int i = 0; i < size; i++) {
                String includeName = (String) includes.elementAt(i);
                String includeProperty = includeName + ".URL";
                String include = props.getProperty(includeProperty);

                if (include == null) {
                    Debug.error("PropertyHandler.getIncludeProperties(): Failed to locate include file \""
                            + includeName
                            + "\" with URL \""
                            + includeProperty
                            + "\"\n  Skipping include file \"" + include + "\"");
                    continue;
                }
                try {
                    tmpProps.clear();
                    // Open URL to read in properties
                    URL tmpInclude = PropUtils.getResourceOrFileOrURL(null,
                            include);

                    if (tmpInclude == null) {
                        continue;
                    }

                    InputStream is = tmpInclude.openStream();
                    tmpProps.load(is);
                    if (DEBUG) {
                        Debug.output("PropertyHandler.getIncludeProperties(): located include properties file URL: "
                                + include);
                    }
                    // Include properties noted in resources
                    // properties - a little recursive action,
                    // here.
                    Properties includeProperties = getIncludeProperties(tmpProps.getProperty(includeProperty),
                            tmpProps);
                    merge(includeProperties,
                            newProps,
                            "include file properties",
                            "within " + include);

                    merge(tmpProps,
                            newProps,
                            "include file properties",
                            include);

                } catch (MalformedURLException e) {
                    Debug.error("PropertyHandler: malformed URL for include file: |"
                            + include + "| for " + includeName);
                } catch (IOException ioe) {
                    Debug.error("PropertyHandler: IOException processing "
                            + include + "| for " + includeName);
                }
            }
        } else {
            Debug.message("properties",
                    "PropertyHandler.getIncludeProperties(): no include files found.");
        }
        return newProps;
    }

    /**
     * Take the from properties, copy them into the internal PropertyHandler
     * properties.
     *
     * @param from the source properties.
     */
    protected void merge(Properties from) {
        merge(from, getProperties(), null, null);
    }

    /**
     * Take the from properties, copy them into the to properties.
     *
     * @param from the source properties.
     * @param to the destination properties.
     */
    protected void merge(Properties from, Properties to) {
        merge(from, to, null, null);
    }

    /**
     * Take the from properties, copy them into the internal PropertyHandler
     * properties. The what and where are simple for a more clearly defined
     * Debug statement. The what and where are only used for debugging
     * statements when there are no properties found, so don't put too much work
     * into creating them, like adding strings together before passing them in.
     * The what and where fit into a debug output statement like:
     * PropertyHandler.merge(): no _what_ found in _where_.
     *
     * @param from the source properties.
     * @param what a description of what the from properties represent.
     * @param where a description of where the properties were read from.
     */
    protected void merge(Properties from, String what, String where) {
        merge(from, getProperties(), what, where);
    }

    /**
     * Take the from properties, copy them into the to properties. The what and
     * where are simple for a more clearly defined Debug statement. The what and
     * where are only used for debugging statements when there are no properties
     * found, so don't put too much work into creating them, like adding strings
     * together before passing them in. The what and where fit into a debug
     * output statement like: PropertyHandler.merge(): no _what_ found in
     * _where_.
     *
     * @param from the source properties.
     * @param to the destination properties.
     * @param what a description of what the from properties represent.
     * @param where a description of where the properties were read from.
     */
    protected void merge(Properties from, Properties to, String what,
                         String where) {
        if (from.size() > 0) {

            if (to == null) {
                to = getProperties();
            }
            PropUtils.copyProperties(from, to);
        } else {
            if (what != null && DEBUG) {
                Debug.output("PropertyHandler.merge(): no " + what + " found"
                        + (where == null ? "." : (" in " + where)));
            }
        }
    }

    /**
     * Merges the properties to the overall properties held by the
     * PropertyHandler.
     */
    public void setProperties(Properties props) {
        init(props, null);
    }

    /**
     * Get the current properties set within this handler.
     */
    public Properties getProperties() {
        if (properties == null) {
            properties = new Properties();
        }
        return properties;
    }

    /**
     * Given a property prefix, or markername, from the properties file, get the
     * object that was created for it. This method uses the prefix librarian.
     */
    public Object get(String markername) {
        return prefixLibrarian.get(markername.intern());
    }

    /**
     * Get a properties object containing all the properties with the given
     * prefix.
     */
    public Properties getProperties(String prefix) {
        Properties prefixProperties = new Properties();
        Properties props = getProperties();
        if (prefix != null) {
            String scopedPrefix = prefix + ".";
            for (Enumeration e = props.keys(); e.hasMoreElements();) {
                String key = (String) e.nextElement();
                if (key.startsWith(scopedPrefix)) {
                    prefixProperties.put(key, props.get(key));
                }
            }
        }
        return prefixProperties;
    }

    /**
     * Register an object with the prefix librarian against a specific
     * markername.
     */
    public void put(String markername, Object obj) {
        prefixLibrarian.put(markername.intern(), obj);
    }

    /**
     * Remove an object from the prefix librarian register, returning that
     * object if it has been found.
     */
    public Object remove(String markername) {
        return prefixLibrarian.remove(markername);
    }

    /**
     * Get the Hashtable being held that matches property prefix strings with
     * components.
     */
    public Hashtable getPrefixLibrarian() {
        return prefixLibrarian;
    }

    /**
     * Given a BeanContext (actually a MapHandler, to handle SoloMapComponents),
     * look for the openmap.components property in the current properties, and
     * parse the list given as that property. From that list of marker names,
     * look for the marker.class properties and create those Java objects. Those
     * objects will be added to the BeanContext given.
     *
     * @param mapHandler BeanContext.
     */
    public void createComponents(MapHandler mapHandler) {
        int i; // default counter

        if (mapHandler == null) {
            Debug.message("properties",
                    "PropertyHandler.createComponents(): null handler.");
            return;
        }

        ProgressListenerGauge plg = null;

        if (updateProgress) {
            try {
                String internString = i18n.get(this.getClass(),
                        "progressTitle",
                        "Progress");
                plg = new ProgressListenerGauge(internString);
                plg.setWindowSupport(new WindowSupport(plg, new WindowSupport.Frm("", true)));
                addProgressListener(plg);
            } catch (Exception e) {
                // Since ProgressListenerGauge is a Swing component, catch
                // any exception that would be tossed if it can't be
                // created, like if the PropertyHandler is being used on a
                // unix system without a DISPLAY.
                // plg = null;
            }
        }

        Vector debugList = PropUtils.parseSpacedMarkers(properties.getProperty(Environment.DebugList));
        int size = debugList.size();

        for (i = 0; i < size; i++) {
            String debugMarker = (String) debugList.elementAt(i);
            Debug.put(debugMarker);
            if (DEBUG) {
                Debug.output("PropertyHandler: adding " + debugMarker
                        + " to Debug list.");
            }
        }

        String componentProperty = getPropertyPrefix()
                + PropertyHandler.componentProperty;
        Vector componentList = PropUtils.parseSpacedMarkers(properties.getProperty(componentProperty));

        if (Debug.debugging("propertiesdetail")) {
            Debug.output("PropertyHandler: creating components from "
                    + componentList);
        }

        if (updateProgress) {
            fireProgressUpdate(ProgressEvent.START, i18n.get(this.getClass(),
                    "creatingComponentsProgressMessage",
                    "Creating Components"), 0, 100);
        }

        Vector components = ComponentFactory.create(componentList,
                properties,
                (updateProgress ? getProgressSupport() : null),
                true);

        size = components.size();

        for (i = 0; i < size; i++) {
            Object obj = (Object) components.elementAt(i);
            try {
                if (obj instanceof String) {
                    Debug.error("PropertyHandler finding out that the " + obj
                            + " wasn't created");
                    continue;
                }

                // mapHandler.add(obj);

                // The call to add(obj) above was replaced by the call to
                // addLayer() below. This seems to speed up the startup process,
                // but if some other component calls mapHandler.add(obj), then
                // this list will be purged.
                mapHandler.addLater(obj);

                String markerName = ((String) componentList.elementAt(i)).intern();
                prefixLibrarian.put(markerName, obj);
                addUsedPrefix(markerName);

            } catch (MultipleSoloMapComponentException msmce) {
                Debug.error("PropertyHandler.createComponents(): "
                        + "tried to add multiple components of the same "
                        + "type when only one is allowed! - " + msmce);
            }
        }

        // Added as a result of the addLater(obj) call above...
        mapHandler.purgeLaterList();

        if (updateProgress) {
            fireProgressUpdate(ProgressEvent.DONE, i18n.get(this.getClass(),
                    "completedProgressMessage",
                    "Created all components, ready..."), size, size);
            removeProgressListener(plg);
        }
    }

    public String getPropertyPrefix() {
        String propertyPrefix = this.propertyPrefix;
        if (propertyPrefix == null) {
            propertyPrefix = Environment.OpenMapPrefix;
        }
        propertyPrefix = PropUtils.getScopedPropertyPrefix(propertyPrefix);
        return propertyPrefix;
    }

    public void setPropertyPrefix(String propertyPrefix) {
        this.propertyPrefix = propertyPrefix;
    }

    /**
     * Creates a Properties object containing the current settings as defined by
     * OpenMap components held by the MapHandler. If the MapHandler contains a
     * PropertyHandler, that property handler will be consulted for properties
     * for different objects in case those objects don't know how to provide
     * their settings correctly.
     *
     * @param mapHandler MapHandler containing components to use for Properties.
     * @param ps PrintStream to write properties to, may be null if you just
     *        want the Properties object that is returned.
     * @return Properties object containing everything written (or that would
     *         have been written, if the PrintStream is null) to PrintStream.
     */
    public static Properties createOpenMapProperties(MapHandler mapHandler,
                                                     PrintStream ps) {

        Properties createdProperties = new Properties();

        // First, get all the components in the MapHandler. Create
        // the openmap.components list, with the .class properties
        // listing all the class names. Ignore the layers for now,
        // and if the class is a PropertyConsumer, get its properties
        // too.
        if (mapHandler == null) {
            Debug.error("PropertyHandler.createOpenMapProperties: can't create properties with null MapHandler");
            return null;
        }

        Iterator it = mapHandler.iterator();
        Object someObj;

        Debug.message("properties",
                "PropertyHandler: Looking for Objects in mapHandler");

        MapBean mapBean = null;
        LayerHandler layerHandler = null;
        PropertyHandler propertyHandler = null;
        InformationDelegator infoDelegator = null;
        Vector otherComponents = new Vector();

        while (it.hasNext()) {
            someObj = it.next();
            Debug.message("properties", "PropertyHandler found "
                    + someObj.getClass().getName());

            if (someObj instanceof MapBean) {
                mapBean = (MapBean) someObj;
            } else if (someObj instanceof LayerHandler) {
                layerHandler = (LayerHandler) someObj;
            } else if (someObj instanceof Layer || someObj instanceof PlugIn) {
                // do nothing, layerhandler will handle
            } else if (someObj instanceof PropertyHandler) {
                propertyHandler = (PropertyHandler) someObj;
                if (infoDelegator != null) {
                    propertyHandler.addProgressListener(infoDelegator);
                }
            } else if (someObj instanceof InformationDelegator) {
                infoDelegator = (InformationDelegator) someObj;
                if (propertyHandler != null) {
                    propertyHandler.addProgressListener((ProgressListener) someObj);
                }
            } else {
                // Add the rest to a component vector thingy.
                otherComponents.add(someObj);
            }
        }

        // if the MapBean and/or the LayerHandler are null, what's the
        // point?
        if (mapBean == null || layerHandler == null) {
            Debug.error("PropertyHandler: no MapBean(" + mapBean
                    + ") or LayerHandler(" + layerHandler
                    + ") to use to write properties");
            return null;
        }

        // First, print the Map parameters...

        ps.println("######  OpenMap properties file ######");
        ps.println("## Refer to original openmap.properties file\n## for instructions on how to modify this file.");
        ps.println("######################################");

        printMapProperties(mapBean, ps, createdProperties);
        printComponentProperties(otherComponents,
                propertyHandler,
                ps,
                createdProperties);
        printLayerProperties(layerHandler,
                propertyHandler,
                ps,
                createdProperties);

        if (Debug.debugging("properties") && createdProperties != null) {
            System.out.println(createdProperties);
        }

        return createdProperties;
    }

    /**
     * A simple helper method that writes key-value pairs to a print stream or
     * Properties, whatever is not null.
     */
    protected static void printProperties(String key, String value,
                                          PrintStream ps,
                                          Properties createdProperties) {
        if (ps != null) {
            ps.println(key + "=" + value);
        }
        if (createdProperties != null) {
            createdProperties.put(key, value);
        }
    }

    /**
     * A helper function to createOpenMapProperties that gets the current
     * properties of the MapBean and prints them out to the PrintStream and the
     * provided Properties object.
     *
     * @param mapBean MapBean to get parameters from.
     * @param ps PrintStream to write properties to, may be null.
     * @param createdProperties Properties object to store properties in, may be
     *        null.
     */
    protected static void printMapProperties(MapBean mapBean, PrintStream ps,
                                             Properties createdProperties) {

        // warning...hackish...
        com.bbn.openmap.proj.Proj proj = mapBean.projection;

        ps.println("\n### OpenMap initial Map Settings ###");
        LatLonPoint llp = proj.getCenter();

        printProperties(Environment.Latitude,
                Float.toString(llp.getLatitude()),
                ps,
                createdProperties);

        printProperties(Environment.Longitude,
                Float.toString(llp.getLongitude()),
                ps,
                createdProperties);

        printProperties(Environment.Scale,
                Float.toString(proj.getScale()),
                ps,
                createdProperties);

        printProperties(Environment.Projection,
                proj.getName(),
                ps,
                createdProperties);

        printProperties(Environment.BackgroundColor,
                Integer.toHexString(mapBean.getBackground().getRGB()),
                ps,
                createdProperties);

        // Height and Width are in the OpenMapFrame properties, or
        // whatever other component contains everything.
    }

    /**
     * A helper function to createOpenMapProperties that gets the current
     * properties of the given components and prints them out to the PrintStream
     * and the provided Properties object.
     *
     * @param components Vector of components to get parameters from.
     * @param ph PropertyHandler that may have properties to use as a foundation
     *        for the properties for the components. If the component can't
     *        provide properties reflecting its settings, the property handler
     *        will be consulted for properties it knows about for that
     *        component.
     * @param ps PrintStream to write properties to, may be null.
     * @param createdProperties Properties object to store properties in, may be
     *        null.
     */
    protected static void printComponentProperties(Vector components,
                                                   PropertyHandler ph,
                                                   PrintStream ps,
                                                   Properties createdProperties) {

        // this section looks at the components and trys to create
        // the openmap.components list and then write out all the
        // properties for them.

        // Since order is important to the look of the application, we
        // need to do work here to maintain the current loaded order
        // of the application components. Until then, just swipe the
        // openmap.components property to get the list of current
        // components.

        boolean buildConfiguredApplication = true;
        boolean componentListBuilt = false;
        Object someObj;
        int numComponents = 0;
        String markerName;
        String componentProperty = ph.getPropertyPrefix()
                + PropertyHandler.componentProperty;
        StringBuffer componentMarkerString = new StringBuffer(componentProperty
                + "=");

        StringBuffer componentPropsString = new StringBuffer();

        if (ph != null && buildConfiguredApplication) {
            Properties phProps = ph.getProperties();
            // Ahh, phProps'l never be null, right?

            // Let's build them from the current properties file.
            componentMarkerString.append(phProps.getProperty(componentProperty));

            Vector componentList = PropUtils.parseSpacedMarkers(phProps.getProperty(componentProperty));

            for (int i = 0; i < componentList.size(); i++) {
                String markerNameClass = (String) componentList.elementAt(i)
                        + ".class";
                componentPropsString.append(markerNameClass + "="
                        + phProps.get(markerNameClass) + "\n");
                if (createdProperties != null) {
                    createdProperties.put(markerNameClass,
                            phProps.get(markerNameClass));
                }
            }
            componentListBuilt = true;

        }

        // We're still going through the objects, but only adding the
        // .class properties if the list wasn't built above.
        // Otherwise, the components will be checked to see of they
        // are PropertyConsumers, in order to get their properties
        // written to the file.

        Properties componentProperties = new Properties();
        Enumeration comps = components.elements();

        while (comps.hasMoreElements()) {

            someObj = comps.nextElement();

            if (someObj instanceof PropertyConsumer) {
                Debug.message("properties", "Getting Property Info for"
                        + someObj.getClass().getName());

                PropertyConsumer pc = (PropertyConsumer) someObj;
                componentProperties.clear();
                markerName = pc.getPropertyPrefix();

                if (ph != null && markerName != null
                        && !markerName.equals("openmap")) {
                    // Gets the properties for the prefix that the
                    // property handler was set with. This should
                    // handle components that aren't good
                    // PropertyConsumers.
                    componentProperties = ph.getProperties(markerName);
                } else {
                    componentProperties.clear();
                }

                if (!componentListBuilt) {
                    if (markerName != null) {
                        componentMarkerString.append(" " + markerName);
                    } else {
                        markerName = "component" + (numComponents++);
                        componentMarkerString.append(" " + markerName);
                        pc.setPropertyPrefix(markerName);
                    }

                    componentPropsString.append(markerName + ".class="
                            + someObj.getClass().getName() + "\n");

                    if (createdProperties != null) {
                        createdProperties.put(markerName, someObj.getClass()
                                .getName());
                    }
                }

                pc.getProperties(componentProperties);

                TreeMap orderedProperties = new TreeMap(componentProperties);

                if (componentProperties.size() > 0) {
                    componentPropsString.append("####\n");
                    for (Iterator keys = orderedProperties.keySet().iterator(); keys.hasNext();) {
                        String key = (String) keys.next();
                        String value = componentProperties.getProperty(key);

                        if (value != null) {
                            componentPropsString.append(key + "=" + value
                                    + "\n");
                        }

                        if (createdProperties != null && value != null) {
                            createdProperties.put(key, value);
                        }
                    }
                }
            } else if (!componentListBuilt) {
                markerName = "component" + (numComponents++);
                componentMarkerString.append(" " + markerName);
                componentPropsString.append(markerName + ".class="
                        + someObj.getClass().getName() + "\n");
                if (createdProperties != null) {
                    createdProperties.put(markerName, someObj.getClass()
                            .getName());
                }
            }
        }

        if (ps != null) {
            ps.println("\n\n### OpenMap Components ###");
            ps.println(componentMarkerString.toString());

            ps.println("\n### OpenMap Component Properties ###");
            // list created, add the actual component properties
            ps.println(componentPropsString.toString());
            ps.println("### End Component Properties ###");
        }

        if (createdProperties != null) {
            createdProperties.put(PropertyHandler.componentProperty,
                    componentMarkerString.substring(PropertyHandler.componentProperty.length() + 1));
        }
    }

    /**
     * A helper function to createOpenMapProperties that gets the current
     * properties of the layers in the LayerHandler and prints them out to the
     * PrintStream and the provided Properties object.
     *
     * @param layerHandler LayerHandler to get layers from.
     * @param ph PropertyHandler that may have properties to use as a foundation
     *        for the properties for the components. If the component can't
     *        provide properties reflecting its settings, the property handler
     *        will be consulted for properties it knows about for that
     *        component.
     * @param ps PrintStream to write properties to, may be null.
     * @param createdProperties Properties object to store properties in, may be
     *        null.
     */
    protected static void printLayerProperties(LayerHandler layerHandler,
                                               PropertyHandler ph,
                                               PrintStream ps,
                                               Properties createdProperties) {

        // Keep track of the LayerHandler. Use it to get the layers,
        // which can be used to get all the marker names for the
        // openmap.layers property. The visible layers go to the
        // openmap.startUpLayers property. Then, cycle through all
        // the layers to get their properties, since they all are
        // PropertyConsumers.
        String markerName;

        String layerMarkerStringKey = Environment.OpenMapPrefix + "."
                + LayerHandler.layersProperty;

        StringBuffer layerMarkerString = new StringBuffer(layerMarkerStringKey
                + "=");

        String startUpLayerMarkerStringKey = Environment.OpenMapPrefix + "."
                + LayerHandler.startUpLayersProperty;

        StringBuffer startUpLayerMarkerString = new StringBuffer(startUpLayerMarkerStringKey
                + "=");

        StringBuffer layerPropertiesString = new StringBuffer();

        Properties layerProperties = new Properties();

        Layer[] layers = layerHandler.getLayers();
        int numLayers = 0;

        for (int i = 0; i < layers.length; i++) {

            markerName = layers[i].getPropertyPrefix();

            if (markerName == null) {
                markerName = "layer" + (numLayers++);
                layers[i].setPropertyPrefix(markerName);
            }

            if (ph != null) {
                // Gets the properties for the prefix that the
                // property handler was set with. This should
                // handle components that aren't good
                // PropertyConsumers.
                layerProperties = ph.getProperties(markerName);
            } else {
                layerProperties.clear();
            }

            layerMarkerString.append(" " + markerName);

            if (layers[i].isVisible()) {
                startUpLayerMarkerString.append(" " + markerName);
            }

            layers[i].getProperties(layerProperties);
            layerPropertiesString.append("### -" + markerName
                    + "- layer properties\n");

            TreeMap orderedProperties = new TreeMap(layerProperties);
            for (Iterator keys = orderedProperties.keySet().iterator(); keys.hasNext();) {
                String key = (String) keys.next();
                // Could add .replace("\\", "/") to the end of this
                // line to prevent \\ from appearing in the properties
                // file.
                String value = layerProperties.getProperty(key);

                if (value != null) {
                    layerPropertiesString.append(key + "=" + value + "\n");
                }

                if (createdProperties != null && value != null) {
                    createdProperties.put(key, value);
                }
            }

            layerPropertiesString.append("### end of -" + markerName
                    + "- properties\n\n");
        }

        if (ps != null) {
            ps.println("\n### OpenMap Layers ###");
            ps.println(layerMarkerString.toString());
            ps.println(startUpLayerMarkerString.toString());
            ps.println(layerPropertiesString.toString());
        }

        if (createdProperties != null) {
            createdProperties.put(layerMarkerStringKey,
                    layerMarkerString.substring(layerMarkerStringKey.length() + 1));
            createdProperties.put(startUpLayerMarkerStringKey,
                    startUpLayerMarkerString.substring(startUpLayerMarkerStringKey.length() + 1));
        }
    }

    /**
     * Given a MapHandler and a Java Properties object, the LayerHandler will be
     * cleared of it's current layers, and reloaded with the layers in the
     * properties. The MapBean will be set to the projection settings listed in
     * the properties.
     */
    public void loadProjectionAndLayers(MapHandler mapHandler, Properties props) {

        MapBean mapBean = (MapBean) mapHandler.get("com.bbn.openmap.MapBean");
        LayerHandler layerHandler = (LayerHandler) mapHandler.get("com.bbn.openmap.LayerHandler");
        // InformationDelegator id = (InformationDelegator)
        // mapHandler.get("com.bbn.openmap.InformationDelegator");

        // if (id != null) {
        // id.requestCursor(new Cursor(Cursor.WAIT_CURSOR));
        // }

        if (layerHandler != null) {
            layerHandler.removeAll();
            layerHandler.init(Environment.OpenMapPrefix, props);
        } else {
            Debug.error("Can't load new layers - can't find LayerHandler");
        }

        if (mapBean != null) {
            mapBean.setProjection(ProjectionFactory.getDefaultProjectionFromEnvironment(mapBean.getWidth(),
                    mapBean.getHeight()));
        } else {
            Debug.error("Can't load new projection - can't find MapBean");
        }

        // if (id != null) {
        // id.requestCursor(null);
        // }
    }

    /**
     * If you are creating a new object, it's important to get a unique prefix
     * for its properties. This function takes a prefix string and checks it
     * against all others it knows about. If there is a conflict, it adds a
     * number to the end until it becomes unique. This prefix will be logged by
     * the PropertyHandler as a name given out, so duplicate instances of that
     * string will not be given out later. It doesn't, however, log that name in
     * the prefixLibrarian. That only occurs when the object is programmatically
     * registered with the prefixLibrarian or when the PropertyHandler finds
     * that object in the MapHandler (and even then that object must be a
     * PropertyConsumer to be registered this way).
     */
    public String getUniquePrefix(String prefix) {
        prefix = prefix.replace(' ', '_');

        if (!addUsedPrefix(prefix)) {
            int count = 2;
            String nextTry = prefix + (count);
            while (!addUsedPrefix(nextTry)) {
                nextTry = prefix + (++count);
            }
            return nextTry;
        } else {
            return prefix;
        }
    }

    /**
     * Changes ' ' characters to '_', and then tries to add it to the used
     * prefix list. Returns true if successful.
     */
    public boolean addUsedPrefix(String prefix) {
        prefix = prefix.replace(' ', '_');

        return usedPrefixes.add(prefix.intern());
    }

    /**
     * Changes ' ' characters to '_', and then tries to remove it to the used
     * prefix list. Returns true if successful.
     */
    public boolean removeUsedPrefix(String prefix) {
        prefix = prefix.replace(' ', '_');

        return usedPrefixes.remove(prefix.intern());
    }

    /**
     * Add a ProgressListener that will display build progress.
     */
    public void addProgressListener(ProgressListener list) {
        getProgressSupport().addProgressListener(list);
    }

    /**
     * Remove a ProgressListener that displayed build progress.
     */
    public void removeProgressListener(ProgressListener list) {
        getProgressSupport().removeProgressListener(list);
    }

    /**
     * Clear all progress listeners.
     */
    public void clearProgressListeners() {
        getProgressSupport().removeAll();
    }

    /**
     * Get progress support if needed.
     */
    protected ProgressSupport getProgressSupport() {
        if (progressSupport == null) {
            progressSupport = new ProgressSupport(this);
        }
        return progressSupport;
    }

    /**
     * Fire an build update to progress listeners.
     *
     * @param frameNumber the current frame count
     * @param totalFrames the total number of frames.
     */
    protected void fireProgressUpdate(int type, String task, int frameNumber,
                                      int totalFrames) {
        if (updateProgress) {
            getProgressSupport().fireUpdate(type,
                    task,
                    totalFrames,
                    frameNumber);
        } else if (type == ProgressEvent.DONE) {
            // At least turn off progress listeners if they are up.
            getProgressSupport().fireUpdate(ProgressEvent.DONE,
                    task,
                    totalFrames,
                    frameNumber);
        }
    }

    /**
     * Set a flag that will trigger the PropertyHandler to fire progress events
     * when it is going through the creation process.
     */
    public void setUpdateProgress(boolean set) {
        updateProgress = set;
    }

    public boolean getUpdateProgress() {
        return updateProgress;
    }

    // Property Functions:
    // ///////////////////

    /**
     * Remove an existing property if it exists.
     *
     * @return true if a property was actually removed.
     */
    public boolean removeProperty(String property) {
        return getProperties().remove(property) != null;
    }

    /**
     * Add (or overwrite) a property to the current properties
     */
    public void addProperty(String property, String value) {
        getProperties().setProperty(property, value);
    }

    /**
     * Add in the properties from the given URL. Any existing properties will be
     * overwritten except for openmap.components, openmap.layers and
     * openmap.startUpLayers which will be appended.
     */
    public void addProperties(URL urlToProperties) {
        addProperties(fetchProperties(urlToProperties));
    }

    /**
     * Add in the properties from the given source, which can be a resorce, file
     * or URL. Any existing properties will be overwritten except for
     * openmap.components, openmap.layers and openmap.startUpLayers which will
     * be appended.
     *
     * @throws MalformedURLException if propFile doesn't resolve properly.
     */
    public void addProperties(String propFile) throws MalformedURLException {
        addProperties(fetchProperties(PropUtils.getResourceOrFileOrURL(propFile)));
    }

    /**
     * Add in the properties from the given Properties object. Any existing
     * properties will be overwritten except for openmap.components,
     * openmap.layers and openmap.startUpLayers where values will be prepended
     * to any existing lists.
     */
    public void addProperties(Properties p) {
        String[] specialProps = new String[] {
                Environment.OpenMapPrefix + "." + LayerHandler.layersProperty,
                Environment.OpenMapPrefix + "."
                        + LayerHandler.startUpLayersProperty, componentProperty };

        Properties tmp = new Properties();
        tmp.putAll(p);

        for (int i = 0; i < specialProps.length; i++) {
            prependProperty(specialProps[i], tmp);
            tmp.remove(specialProps[i]);
        }

        getProperties().putAll(tmp);
    }

    /**
     * remove a marker from a space delimated set of properties.
     */
    public void removeMarker(String property, String marker) {
        // Requires jdk 1.4
        // StringBuffer sb =
        // new StringBuffer(getProperties().getProperty(property,
        // ""));
        // int idx = sb.indexOf(marker);

        // jdk 1.3 version
        String propertyString = getProperties().getProperty(property, "");
        int idx = propertyString.indexOf(marker);
        if (idx != -1) {
            StringBuffer sb = new StringBuffer(propertyString);
            sb.delete(idx, idx + marker.length());
            getProperties().setProperty(property, sb.toString());
        }
    }

    /**
     * Append the given property into the current properties
     */
    public void appendProperty(String property, Properties src) {
        appendProperty(property, src.getProperty(property, ""));
    }

    /**
     * Append the given property into the current properties
     */
    public void appendProperty(String property, String value) {
        String curVal = getProperties().getProperty(property, "");
        getProperties().setProperty(property, curVal + " " + value);
    }

    /**
     * Prepend the given property into the current properties
     */
    public void prependProperty(String property, Properties src) {
        prependProperty(property, src.getProperty(property, ""));
    }

    /**
     * Prepend the given property into the current properties
     */
    public void prependProperty(String property, String value) {
        String curVal = getProperties().getProperty(property, "");
        getProperties().setProperty(property, value + " " + curVal);
    }

    /**
     * Load a Properties object from the classpath. The method always returns a
     * <code>Properties</code> object. If there was an error loading the
     * properties from <code>propsURL</code>, an empty
     * <code>Properties</code> object is returned.
     *
     * @param propsURL the URL of the properties to be loaded
     * @return the loaded properties, or an empty Properties object if there was
     *         an error.
     */
    public static Properties fetchProperties(URL propsURL) {
        if (Debug.debugging("properties")) {
            Debug.output("PropertyHandler.getProperties(" + propsURL + ")");
        }
        Properties p = new Properties();
        if (propsURL != null) {
            try {
                InputStream is = propsURL.openStream();
                p.load(is);
                is.close();
            } catch (IOException e) {
                Debug.error("PropertyHandler.getProperties(): "
                        + "Exception reading map properties at " + propsURL
                        + ": " + e);
            }
        }
        return p;
    }

    /**
     * All the PropertyHandler does with the MapHandler is look for
     * PropertyConsumers and register their prefixes with the prefixLibarian.
     */
    public void findAndInit(Object obj) {
        if (obj instanceof PropertyConsumer) {
            String prefix = ((PropertyConsumer) obj).getPropertyPrefix();
            if (prefix != null) {
                getPrefixLibrarian().put(prefix, obj);
            }
        }
    }

    public void findAndUndo(Object obj) {
        if (obj instanceof PropertyConsumer) {
            String prefix = ((PropertyConsumer) obj).getPropertyPrefix();
            if (prefix != null) {
                getPrefixLibrarian().remove(prefix);
            }
        }
    }
}
TOP

Related Classes of com.bbn.openmap.PropertyHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.