// **********************************************************************
//
// <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_<localization string>.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);
}
}
}
}