// ********************************************************************** // // <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.29 $ // $Date: 2008/02/28 23:36:09 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap; import java.awt.geom.Point2D; 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 java.util.logging.Level; import java.util.logging.Logger; 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.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 { public static Logger logger = Logger.getLogger("com.bbn.openmap.PropertyHandler"); /** * 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(new Builder()); } /** * 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) { this(new Builder().setProgressUpdates(provideProgressUpdates)); } /** * 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(new Builder().setPropertiesFile(urlString)); } /** * Constructor to take path (URL) as argument, to create context for a * particular map. */ public PropertyHandler(URL url) throws IOException { this(new Builder().setPropertiesFile(url)); } /** * Constructor to take Properties for configuration, using the default * "openmap" property prefix for configuration. * * @param props */ public PropertyHandler(Properties props) { this(new Builder().setProperties(props)); } public PropertyHandler(Builder builder) { DEBUG = logger.isLoggable(Level.FINE); setPropertyPrefix(builder.propertyPrefix); setUpdateProgress(builder.update); Properties properties = builder.properties; if (properties == null) { searchForAndLoadProperties(); } else { init(properties, "URL"); Environment.init(getProperties()); } } /** * Provides this class with the default properties file name to look for. * Can be overridden by subclasses to return custom file names. * * @return String of properties file to search for in classpath, configDir * and user's home directory. */ public String getDefaultPropertyFileName() { return PropUtils.getScopedPropertyPrefix(Environment.OpenMapPrefix) + PropertyHandler.propsFileName; } /** * Look for an openmap.properties file in the classpath, configDirectory and * user home directory, in that order. If any property is duplicated in any * file found in those locations, the last one in wins. */ protected void searchForAndLoadProperties() { searchForAndLoadProperties(getDefaultPropertyFileName()); } /** * Look for a properties file 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. * * @param propsFileName to search for */ protected void searchForAndLoadProperties(String propsFileName) { Properties tmpProperties = new Properties(); Properties includeProperties; Properties localizedProperties; boolean foundProperties = false; if (Debug.debugging("locale")) { java.util.Locale.setDefault(new java.util.Locale("pl", "PL")); } if (Debug.debugging("showprogress")) { updateProgress = true; } logger.fine("***** Searching for properties ****"); String propertyPrefix = PropUtils.getScopedPropertyPrefix(getPropertyPrefix()); // 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 (DEBUG) { logger.fine("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 && DEBUG) { logger.fine("Loading properties from System Resources: " + propsFileName); } } else { if (DEBUG) { logger.fine("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() || DEBUG)) { logger.fine("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<String> cps = Environment.getClasspathDirs(); String defaultExtraDir = "share"; for (String searchLoc : cps) { File shareDir = new File(searchLoc, 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 (DEBUG) { logger.fine("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 precedence 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 (DEBUG) { logger.fine("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 (DEBUG) { logger.fine("***** 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 precedence 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) { logger.fine("Looking for localized file: " + localizedPropertyFile); } try { URL propsURL = PropUtils.getResourceOrFileOrURL(localizedPropertyFile); if (propsURL == null) { tryHomeDirectory = true; } else { if (DEBUG) { logger.fine("Found localized properties in classpath"); } props = fetchProperties(propsURL); } } catch (MalformedURLException murle) { logger.warning("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) { logger.fine("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 * constructor 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. */ protected void init(Properties props, String howString) { // Include properties noted in resources properties. String prefix = PropUtils.getScopedPropertyPrefix(getPropertyPrefix()); Properties includeProperties = getIncludeProperties(props.getProperty(prefix + includeProperty), props); merge(includeProperties, "include file properties", howString); if (!Environment.isApplet()) { Properties systemProperties = System.getProperties(); merge(systemProperties, props); } merge(props, "loaded", howString); if (DEBUG) { logger.fine("loaded properties"); } } /** * Take a marker name list (space separated names), and open the properties * files listed in the property 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 include 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<String> includes = PropUtils.parseSpacedMarkers(markerList); int size = includes.size(); if (size > 0) { if (logger.isLoggable(Level.FINER)) { logger.finer("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 (logger.isLoggable(Level.FINER)) { logger.finer("checking " + includeProperty + ", getting: " + include); } if (include == null) { logger.warning("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) { logger.fine("Can't locate URL trying to find included properties: " + include); continue; } InputStream is = tmpInclude.openStream(); tmpProps.load(is); if (DEBUG) { logger.fine("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) { logger.warning("malformed URL for include file: |" + include + "| for " + includeName); } catch (IOException ioe) { logger.warning("IOException processing " + include + "| for " + includeName); } } } else { logger.fine("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 * logging 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 logging 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.isEmpty()) { if (to == null) { to = getProperties(); } PropUtils.copyProperties(from, to); } else { if (what != null && DEBUG) { logger.fine("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 marker * name. */ 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) { logger.fine("no MapHandler to use to handle created components, skipping creation."); 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<String> debugList = PropUtils.parseSpacedMarkers(properties.getProperty(Environment.DebugList)); int size = debugList.size(); for (String debugMarker : debugList) { Debug.put(debugMarker); if (DEBUG) { logger.fine("adding " + debugMarker + " to Debug list."); } } String propPrefix = PropUtils.getScopedPropertyPrefix(getPropertyPrefix()); String componentProperty = propPrefix + PropertyHandler.componentProperty; Vector<String> componentList = PropUtils.parseSpacedMarkers(properties.getProperty(componentProperty)); if (logger.isLoggable(Level.FINER)) { logger.finer("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) { logger.warning("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) { logger.warning("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; } 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) { logger.warning("can't create properties with null MapHandler"); return null; } Iterator it = mapHandler.iterator(); Object someObj; logger.fine("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(); logger.fine("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) { logger.warning("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 (logger.isLoggable(Level.FINE) && 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 ###"); Point2D llp = proj.getCenter(); printProperties(Environment.Latitude, Double.toString(llp.getY()), ps, createdProperties); printProperties(Environment.Longitude, Double.toString(llp.getX()), 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 = PropertyHandler.componentProperty; StringBuffer componentMarkerString = new StringBuffer(componentProperty).append("="); if (ph != null) { String phPrefix = PropUtils.getScopedPropertyPrefix(ph.getPropertyPrefix()); componentProperty = phPrefix + PropertyHandler.componentProperty; componentMarkerString = new StringBuffer(componentProperty).append("="); } 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).append("=").append(phProps.get(markerNameClass)).append("\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) { logger.fine("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(" ").append(markerName); } else { markerName = "component" + (numComponents++); componentMarkerString.append(" ").append(markerName); pc.setPropertyPrefix(markerName); } componentPropsString.append(markerName).append(".class=").append(someObj.getClass().getName()).append("\n"); if (createdProperties != null) { createdProperties.put(markerName, someObj.getClass().getName()); } } pc.getProperties(componentProperties); TreeMap orderedProperties = new TreeMap(componentProperties); if (!componentProperties.isEmpty()) { 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).append("=").append(value).append("\n"); } if (createdProperties != null && value != null) { createdProperties.put(key, value); } } } } else if (!componentListBuilt) { markerName = "component" + (numComponents++); componentMarkerString.append(" ").append(markerName); componentPropsString.append(markerName).append(".class=").append(someObj.getClass().getName()).append("\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).append("="); String startUpLayerMarkerStringKey = Environment.OpenMapPrefix + "." + LayerHandler.startUpLayersProperty; StringBuffer startUpLayerMarkerString = new StringBuffer(startUpLayerMarkerStringKey).append("="); 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(" ").append(markerName); if (layers[i].isVisible()) { startUpLayerMarkerString.append(" ").append(markerName); } layers[i].getProperties(layerProperties); layerPropertiesString.append("### -").append(markerName).append("- 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).append("=").append(value).append("\n"); } if (createdProperties != null && value != null) { createdProperties.put(key, value); } } layerPropertiesString.append("### end of -").append(markerName).append("- 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 { logger.warning("Can't load new layers - can't find LayerHandler"); } if (mapBean != null) { mapBean.setProjection(mapBean.getProjectionFactory().getDefaultProjectionFromEnvironment(Environment.getInstance(), mapBean.getWidth(), mapBean.getHeight())); } else { logger.warning("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().add(list); } /** * Remove a ProgressListener that displayed build progress. */ public void removeProgressListener(ProgressListener list) { getProgressSupport().remove(list); } /** * Clear all progress listeners. */ public void clearProgressListeners() { getProgressSupport().clear(); } /** * 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 resource, * 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 delimited 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 (logger.isLoggable(Level.FINE)) { logger.fine("checking (" + propsURL + ")"); } Properties p = new Properties(); if (propsURL != null) { try { InputStream is = propsURL.openStream(); p.load(is); is.close(); } catch (IOException e) { logger.warning("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); } } if (obj == this) { dispose(); } } public void dispose() { if (prefixLibrarian != null) { prefixLibrarian.clear(); } if (properties != null) { properties.clear(); } if (usedPrefixes != null) { usedPrefixes.clear(); } } /** * * This Builder class lets you have more control over how a PropertyHandler * is constructed. If a properties file location or a properties file is not * provided, the PropertyHandler will look for an "openmap.properties" file * in the classpath, config directory or user home directory. If you don't * want the PropertyHandler to search for a properties file, set an empty * Properties object in the Builder. * * @author ddietrick */ public static class Builder { protected boolean update = false; protected Properties properties = null; protected String propertyPrefix = null; public Builder() { } /** * Have the builder look for a resource, file or URL at the location. * * @param location of the properties file * @return this Builder, so settings can be stacked. * @throws MalformedURLException * @throws IOException */ public Builder setPropertiesFile(String location) throws MalformedURLException, IOException { if (location != null) { this.properties = createProperties(PropUtils.getResourceOrFileOrURL(location)); } return this; } /** * Have the builder look for properties file at URL location. * * @param url * @return this Builder for stacking. * @throws IOException */ public Builder setPropertiesFile(URL url) throws IOException { if (url != null) { this.properties = createProperties(url); } return this; } /** * Have the builder use the provided properties. * * @param props Properties to use. * @return this Builder for stacking */ public Builder setProperties(Properties props) { this.properties = props; return this; } /** * Set the property prefix used for general settings in the properties * in configuration of application. If not set "openmap" will be used as * the default. * * @param prefix Set the property prefix for the PropertyHandler * @return this builder for stacking */ public Builder setPropertyPrefix(String prefix) { this.propertyPrefix = prefix; return this; } /** * * @param update flag for providing progress updates * @return This builder for stacking */ public Builder setProgressUpdates(boolean update) { this.update = update; return this; } /** * Reads the file at the given location and creates a Properties file * from the contents. * * @param location URL of file * @return Properties * @throws IOException if something goes wrong reading the file. */ protected Properties createProperties(URL location) throws IOException { InputStream is = null; if (location != null) { // Open URL to read in properties is = location.openStream(); } Properties tmpProperties = new Properties(); if (is != null) { tmpProperties.load(is); } return tmpProperties; } /** * Build the property handler. * * @return new property handler with builder settings. */ public PropertyHandler build() { return new PropertyHandler(this); } } }