// ********************************************************************** // // <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/LayerHandler.java,v $ // $RCSfile: LayerHandler.java,v $ // $Revision: 1.16 $ // $Date: 2006/08/09 21:08:41 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap; import java.awt.Component; import java.beans.PropertyVetoException; import java.beans.beancontext.BeanContext; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import com.bbn.openmap.event.LayerConfigurationListener; import com.bbn.openmap.event.LayerConfigurationListenerSupport; import com.bbn.openmap.event.LayerEvent; import com.bbn.openmap.event.LayerListener; import com.bbn.openmap.event.LayerSupport; import com.bbn.openmap.plugin.PlugIn; import com.bbn.openmap.plugin.PlugInLayer; import com.bbn.openmap.util.ComponentFactory; import com.bbn.openmap.util.PropUtils; /** * The LayerHandler is a component that keeps track of all Layers for the * MapBean, whether or not they are currently part of the map or not. It is able * to dynamically add and remove layers from the list of available layers. * Whether a layer is added to the MapBean depends on the visibility setting of * the layer. If Layer.isVisible() is true, the layer will be added to the * MapBean. There are methods within the LayerHandler that let you change the * visibility setting of a layer. * <p> * The LayerHandler is able to take a Properties object, and create layers that * are defined within it. The key property is "layers", which may or may not * have a prefix for it. If that property does have a prefix (prefix.layers, * i.e. openmap.layers), then that prefix has to be known and passed in to the * constructor or init method. This layers property should fit the general * openmap marker list paradigm, where the marker names are listed in a space * separated list, and then each marker name is used as a prefix for the * properties for a particular layer. As a minimum, each layer needs to have the * class and prettyName properties defined. The class property should define the * class name to use for the layer, and the prettyName property needs to be a * name for the layer to be used in the GUI. Any other property that the * particular layer can use should be listed in the Properties, with the * applicable marker name as a prefix. Each layer should have its available * properties defined in its documentation. For example: * <p> * <p> * * <pre> * <p/> * openmap.layers=marker1 marker2 (etc) * marker1.class=com.bbn.openmap.layer.GraticuleLayer * marker1.prettyName=Graticule Layer * # false is default * marker1.addToBeanContext=false * <p/> * marker2.class=com.bbn.openmap.layer.shape.ShapeLayer * marker2.prettyName=Political Boundaries * marker2.shapeFile=pathToShapeFile * marker2.spatialIndex=pathToSpatialIndexFile * marker2.lineColor=FFFFFFFF * marker2.fillColor=FFFF0000 * <p/> * </pre> * * <p> * The LayerHandler is a SoloMapComponent, which means that for a particular * map, there should only be one of them. When a LayerHandler is added to a * BeanContext, it will look for a MapBean to connect to itself as a * LayerListener so that the MapBean will receive LayerEvents - this is the * mechanism that adds and removes layers on the map. If more than one MapBean * is added to the BeanContext, then the last MapBean added will be added as a * LayerListener, with any prior MapBeans added as a LayerListener removed from * the LayerHandler. The MapHandler controls the behavior of multiple * SoloMapComponent addition to the BeanContext. */ public class LayerHandler extends OMComponent implements SoloMapComponent, Serializable { public static Logger logger = Logger.getLogger("com.bbn.openmap.LayerHandler"); /** * Property for space separated layers. If a prefix is needed, just use the * methods that let you use the prefix - don't worry about the period, it * will be added automatically. */ public static final String layersProperty = "layers"; /** * Property for space separated layers to be displayed at startup. If a * prefix is needed, just use the methods that let you use the prefix - * don't worry about the period, it will be added automatically. */ public static final String startUpLayersProperty = "startUpLayers"; /** * Flag to set synchronous threading on the LayerHandler, telling it to * react to layer order changes and layer visibility requests within the * calling thread. By default, this action is true. Setting it to false may * eliminate pauses in GUI reactions by off-loading work done by layers * being added to the MapBean, but there have been reports that the * asynchronous nature of the threading queue may be causing an unexpected * state in layer order and/or availability under certain intense layer * management conditions (created by automated processes, for example). */ public static final String SynchronousThreadingProperty = "synchronousThreading"; /** * The object holding on to all LayerListeners interested in the layer * arrangement and availability. Not expected to be null. */ protected transient LayerSupport listeners = new LayerSupport(this); /** * LayerConfigurationListenerSupport for LayerConfigurationListeners. */ protected transient LayerConfigurationListenerSupport layerConfigListeners = new LayerConfigurationListenerSupport(this); /** * The list of all layers, even the ones that are not part of the map. */ protected List<Layer> allLayers = new ArrayList<Layer>(); /** * This handle is only here to keep it appraised of layer prefix names. */ protected PropertyHandler propertyHandler; /** * If you use this constructor, the LayerHandler expects that the layers * will be created and added later, either by addLayer() or init(). */ public LayerHandler() { } /** * Start the LayerHandler, and have it create all the layers as defined in a * properties file. * * @param props properties as defined in an openmap.properties file. */ public LayerHandler(Properties props) { init(null, props); } /** * Start the LayerHandler, and have it create all the layers as defined in a * properties file. * * @param prefix the prefix for the layers and startUpLayers properties, as * if they are listed as prefix.layers, and prefix.startUpLayers. * @param props properties as defined in an openmap.properties file. */ public LayerHandler(String prefix, Properties props) { init(prefix, props); } /** * Start the LayerHandler with configured layers. */ public LayerHandler(Layer[] layers) { init(layers); } /** * Extension of the OMComponent. If the LayerHandler is created by the * ComponentFactory (via the PropertyHandler), this method will be called * automatically. For the OpenMap applications, this method is rigged to * handle the openmap.layers property by calling init("openmap", props). If * you are using the LayerHandler in a different setting, then you might * want to just call init() directly, or extend this class and have * setProperties do what you want. */ public void setProperties(String prefix, Properties props) { super.setProperties(prefix, props); // Whoa! We used to replace the prefix provided to this method with // 'openmap', // AKA Environment.OpenMapPrefix, but that seems rude and hackish. We're // going to have the getLayers(prefix, props) method use the prefix // passed in, and if the layerHandler prefix.layers can't be found, // we'll revert to looking for the openmap.layers property, just to be // backward compatible. // init(Environment.OpenMapPrefix, props); init(prefix, props); } /** * Initialize the LayerHandler by having it construct it's layers from a * properties object. The properties should be created from an * openmap.properties file. * * @param prefix the prefix to use for the layers and startUpLayers * properties. * @param props properties as defined in an openmap.properties file. */ public void init(String prefix, Properties props) { prefix = PropUtils.getScopedPropertyPrefix(prefix); init(getLayers(prefix, props)); getListeners().setSynchronous(PropUtils.booleanFromProperties(props, prefix + SynchronousThreadingProperty, getListeners().isSynchronous())); } /** * Initialize the LayerHandler by having it construct it's layers from a URL * containing an openmap.properties file. * * @param url a url for a properties file. */ public void init(java.net.URL url) { init(null, url); } /** * Initialize the LayerHandler by having it construct it's layers from a URL * containing an openmap.properties file. * * @param prefix the prefix to use for the layers and startUpLayers * properties. * @param url a url for a properties file. */ public void init(String prefix, java.net.URL url) { try { java.io.InputStream in = url.openStream(); Properties props = new Properties(); props.load(in); init(getLayers(prefix, props)); } catch (java.net.MalformedURLException murle) { logger.warning("LayerHandler.init(URL): " + url + " is not a valid URL"); } catch (java.io.IOException e) { logger.warning("LayerHandler.init(URL): Caught an IOException"); } } /** * Initialize from an array of layers. This will cause the LayerListeners, * if they exist, to update themselves with the current list of layers. This * will check to add layers to the MapHandler. * * @param layers the initial array of layers. */ public void init(Layer[] layers) { init(Arrays.asList(layers)); } public void init(List<Layer> layers) { // Should get rid of the old layers properly, holding onto // non-removeable layers. removeAll(); // OK, we need to check the allLayers array, because at this point it // could still be holding non-removable layers. If we just replace them, // we've broken the contract of non-removal. Move the non-removable // layers to the bottom and put the new layers on top. We also need to // check to make sure that any duplicate layers on either list are // parsed down to one layer. We use the Vector.contains() method for // that check. List<Layer> currentLayers = getLayerList(); if (layers == null) { layers = new ArrayList<Layer>(); } // Go through the list of old-non-removable layers, if they are not on // the new layer list, add them to the end. for (Layer layer : currentLayers) { if (!layers.contains(layer) || !layer.isRemovable()) { layers.add(layer); } } setLayerList(layers); // This should work for layers being reloaded from the PropertyHandler, // it's better than doing it in the getLayers(...) method below // (getLayers() is called before init()). For the // initial LayerHandler construction and Layer creation in an // application, the BeanContext should be null at this point, so this // method call will do nothing. But for resetting the layers with new // ones, they will get dumped into the BeanContext/MapHandler. addLayersToBeanContext(layers); } public void setPropertyHandler(PropertyHandler ph) { propertyHandler = ph; } public PropertyHandler getPropertyHandler() { return propertyHandler; } /** * This is the method that gets used to parse the layer properties from an * openmap.properties file, where the layer marker names are listed under a * layers property, and each layer is then represented by a marker.class * property, and a maker.prettyName property. * * @param p properties containing layers property, the startupLayers * property listing the layers to make visible immediately, and the * layer properties as well. * @return Layer[] of layers created from the properties. */ protected Layer[] getLayers(Properties p) { return getLayers(null, p); } /** * This is the method that gets used to parse the layer properties from an * openmap.properties file, where the layer marker names are listed under a * prefix.layers property, and each layer is then represented by a * marker.class property, and a maker.prettyName property. * * @param prefix the prefix to use to use for the layer list (layers) * property and the startUpLayers property. If it is not null, this * will cause the method to look for prefix.layers and * prefix.startUpLayers. * @param p the properties to build the layers from. * @return Layer[] */ protected Layer[] getLayers(String prefix, Properties p) { logger.fine("Getting new layers from properties..."); prefix = PropUtils.getScopedPropertyPrefix(prefix); String layersValueString = p.getProperty(prefix + layersProperty); String startupLayersValueString = p.getProperty(prefix + startUpLayersProperty); if (layersValueString == null) { layersValueString = p.getProperty(PropUtils.getScopedPropertyPrefix(Environment.OpenMapPrefix) + layersProperty); } if (startupLayersValueString == null) { startupLayersValueString = p.getProperty(PropUtils.getScopedPropertyPrefix(Environment.OpenMapPrefix) + startUpLayersProperty); } Vector<String> startuplayers = PropUtils.parseSpacedMarkers(startupLayersValueString); Vector<String> layersValue = PropUtils.parseSpacedMarkers(layersValueString); if (startuplayers.isEmpty()) { logger.info("No layers on startup list"); } if (layersValue.isEmpty()) { logger.info("No property \"" + layersProperty + "\" found in properties."); return new Layer[0]; } else { if (logger.isLoggable(Level.FINE)) { logger.fine("Layer markers found = " + layersValue); } } Layer[] layers = getLayers(layersValue, startuplayers, p); // You don't want to call addLayersToBeanContext here, it sets up a // cycle. The layers are not yet set in the LayerHandler, so the // LayerHandle won't know to ignore them when they show up in // findAndInit(). The one thing this call did, however, is get the // BeanContext to the layers before the startup layers were added to the // MapBean. It's possible that without this call, layers that build // their OMGraphicLists once may not have the BeanContext resources they // need in order to build that list. // addLayersToBeanContext(layers); return layers; } /** * A static method that lets you pass in a Properties object, along with two * Vectors of strings, each Vector representing marker names for layers * contained in the Properties. * <p/> * If a PlugIn is listed in the properties, the LayerHandler will create a * PlugInLayer for it and set the PlugIn in that layer. * * @param layerList Vector of marker names to use to inspect the properties * with. * @param visibleLayerList Vector of marker names representing the layers * that should initially be set to visible when created, so that * those layers are initially added to the map. * @param p Properties object containing the layers properties. * @return Layer[] */ public static Layer[] getLayers(Vector<String> layerList, Vector<String> visibleLayerList, Properties p) { int nLayerNames = layerList.size(); Vector<Layer> layers = new Vector<Layer>(nLayerNames); for (String layerName : layerList) { String classProperty = layerName + ".class"; String className = p.getProperty(classProperty); if (className == null) { logger.info("Failed to locate property \"" + classProperty + "\"\n Skipping layer \"" + layerName + "\""); continue; } Object obj = ComponentFactory.create(className, layerName, p); Layer l; if (obj instanceof Layer) { l = (Layer) obj; } else if (obj instanceof PlugIn) { PlugInLayer pl = new PlugInLayer(); pl.setProperties(layerName, p); pl.setPlugIn((PlugIn) obj); l = pl; } else { logger.info("Skipped \"" + layerName + "\" " + (obj == null ? " - unable to create " : ", type " + obj.getClass().getName() + " is not a layer or plugin")); continue; } // Figure out of the layer is on the startup list, // and make it visible if it is... l.setVisible(visibleLayerList.contains(layerName)); // The ComponentFactory does this now // l.setProperties(layerName, p); layers.addElement(l); if (logger.isLoggable(Level.FINE)) { logger.fine("layer " + l.getName() + (l.isVisible() ? " is visible" : " is not visible")); } } int nLayers = layers.size(); if (nLayers == 0) { return new Layer[0]; } else { Layer[] value = new Layer[nLayers]; layers.copyInto(value); return value; } } /** * Add a LayerListener to the LayerHandler, in order to be told about layers * that need to be added to the map. The new LayerListener will receive two * events, one telling it all the layers available, and one telling it which * layers are active (visible). * * @param ll LayerListener, usually the MapBean or other GUI components * interested in providing layer controls. */ public void addLayerListener(LayerListener ll) { logger.fine("adding layer listener"); listeners.add(ll); // Usually, the listeners are interested in one type of event // or the other. So fire both, and let the listener hash it // out. ll.setLayers(new LayerEvent(this, LayerEvent.ALL, getLayers())); ll.setLayers(new LayerEvent(this, LayerEvent.ADD, getMapLayers())); } /** * Add a LayerConfigurationListener, so a component can make adjustments to * new layer configurations before they get sent to the map. * * @param lcl LayerConfigurationListener */ public void addLayerConfigurationListener(LayerConfigurationListener lcl) { layerConfigListeners.add(lcl); } /** * Add a LayerListener to the LayerHandler, in order to be told about layers * that need to be added to the map. * * @param ll LayerListener, usually the MapBean or other GUI components * interested in providing layer controls. */ public void removeLayerListener(LayerListener ll) { if (listeners != null) { listeners.remove(ll); } } /** * Remove a LayerConfigurationListener. * * @param lcl LayerConfigurationListener */ public void removeLayerConfigurationListener(LayerConfigurationListener lcl) { layerConfigListeners.remove(lcl); } /** * Set all the layers held by the LayerHandler. The visible layers will be * sent to listeners interested in visible layers (LayerEvent.REPLACE), and * the list of all layers will be sent to listeners interested in * LayerEvent.ALL events. Calling this method from other GUI components * could break the non-removable contract that previous layers had. The * previous layers aren't checked against this list to insure that * non-removable layers are still on this list and visible. GUI components * that may call this need to set up their GUI controls to not allow * non-removable layers to be eliminated from the application. * <p/> * <p/> * This method will not add the layers to the MapHandler, so you can call * this if you know the layers are already in the MapHandler or don't need * to be. If you want layers to be added to the MapHandler (if the * LayerHandler knows about it), call init(Layer[]) instead. * <p/> * <p/> * Also, this method will disregard layer non-removable status for any * layers currently held, and will simply replace all layers with the ones * provided. If you want the non-removable flag to be adhered to, call * init(Layers[]). * * @param layers Layer array of all the layers to be held by the * LayerHandler. */ public synchronized void setLayers(Layer[] layers) { setLayerList(Arrays.asList(layers)); } public synchronized void setLayerList(List<Layer> layers) { List<Layer> localAllLayers = organizeBackgroundLayers(layers); if (logger.isLoggable(Level.FINE)) { logger.fine("setting layers: " + getLayerNamesFromArray(layers)); } // I think this is where we need to make the call to // LayerConfigurationListeners to check the status of the layer cake, so // they can make appropriate changes before the LayerListeners get da // layers. List<Layer> checkedList = layerConfigListeners.checkLayerConfiguration(new ArrayList<Layer>(localAllLayers)); if (checkedList != null) { localAllLayers = organizeBackgroundLayers(checkedList); } allLayers = localAllLayers; getListeners().pushLayerEvent(LayerEvent.ALL, getLayers()); getListeners().pushLayerEvent(LayerEvent.REPLACE, getMapLayers()); } protected String getLayerNamesFromArray(List<Layer> lArray) { StringBuilder buf = new StringBuilder(); if (lArray != null) { for (Layer l : lArray) { if (buf.length() > 0) { buf.append(" "); } buf.append(l.getName()); } } return buf.toString(); } /** * Checks to see if there are background layers on top of foreground layers. * * @param layers * @return true if background layers need to be pushed down */ protected boolean isForegroundUnderBackgroundLayer(List<Layer> layers) { boolean foundBackgroundLayer = false; if (layers != null) { for (Layer layer : layers) { if (layer != null) { if (layer.getAddAsBackground()) { foundBackgroundLayer = true; } else if (foundBackgroundLayer) { return true; } } } } return false; } /** * Does the check to see of foreground layers are below background layers, * and then iterates through the Layer[] switching layers around until they * are in the appropriate order. * * @param layers * @return Layer[] of layers with background layers moved to back. */ protected List<Layer> organizeBackgroundLayers(List<Layer> layers) { if (isForegroundUnderBackgroundLayer(layers)) { List<Layer> fLayers = new ArrayList<Layer>(layers.size()); List<Layer> bLayers = new ArrayList<Layer>(layers.size()); for (Layer layer : layers) { if (layer != null) { if (layer.getAddAsBackground()) { bLayers.add(layer); } else { fLayers.add(layer); } } } // Append all background layers behind foreground layers. fLayers.addAll(bLayers); return fLayers; } else { return layers; } } /** * Returns the object responsible for holding on to objects listening to * layer changes. * * @return LayerSupport containing pointers to all objects interested in the * status (order, visibility) of the available layers. */ protected LayerSupport getListeners() { return listeners; } /** * If you are futzing with the layer visibility outside the purview of the * LayerHandler (not using the turnLayerOn() methods) then you can call this * to get all the listeners using the current set of visible layers. */ public void setLayers() { setLayerList(getLayerList()); } /** * Get a layer array, of potential layers that CAN be added to the map, not * the ones that are active on the map. A new array is returned, containing * the current layers. * * @return new Layer[] containing new layers. */ public synchronized Layer[] getLayers() { List<Layer> layers = getLayerList(); return layers.toArray(new Layer[layers.size()]); } /** * Returns a copy of the List of all the available layers. * * @return List of Layers */ public synchronized List<Layer> getLayerList() { if (allLayers == null) { return new ArrayList<Layer>(); } else { return new ArrayList<Layer>(allLayers); } } /** * Get the layers that are currently part of the Map - the ones that are * visible. * * @return an Layer[] of visible Layers. */ public Layer[] getMapLayers() { List<Layer> activeLayers = new ArrayList<Layer>(); for (Layer layer : getLayers()) { if (layer != null && layer.isVisible()) { activeLayers.add(layer); } } Layer[] cake = activeLayers.toArray(new Layer[activeLayers.size()]); if (logger.isLoggable(Level.FINE)) { logger.fine("providing map layers: " + getLayerNamesFromArray(activeLayers)); } return cake; } /** * Move a layer to a certain position. Returns true if the layer exists in * the LayerHandler, false if is doesn't. No action is taken if the layer * isn't already added to the LayerHandler stack. If the position is 0 or * less the layer is moved on top. If the position is greater or equal to * the number of layers, the layer is moved to the bottom of the pile. * * @param layer the layer to move. * @param toPosition the array index to place it, shifting the other layers * up or down, depending on where the layer is originally. * @return true if the layer is already contained in the LayerHandler, false * if not. */ public boolean moveLayer(Layer layer, int toPosition) { boolean found = getLayerList().contains(layer); addLayer(layer, toPosition); return found; } /** * Add a layer to the bottom of the layer stack. If the layer is already * part of the layer stack, nothing is done. * * @param layer the layer to add. */ public void addLayer(Layer layer) { if (allLayers == null) { addLayer(layer, 0); return; } if (!allLayers.contains(layer)) { allLayers.add(layer); } } /** * Add a layer to a certain position in the layer array. If the position is * 0 or less, the layer is put up front (on top). If the position is greater * than the length of the current array, the layer is put at the end, (on * the bottom). A Layer can only be added once. If you add a layer that is * already added to the LayerHandler, it will be moved to the requested * position. * * @param layer the layer to add. * @param position the array index to place it. */ public void addLayer(Layer layer, int position) { // Working copy List<Layer> currentLayers = getLayerList(); // If it is already part of the list, we're going to move it to the new // position. Remove it from the current position. If it's not on the // list this call is a NO-OP. currentLayers.remove(layer); if (position > allLayers.size()) { currentLayers.add(layer); } else { if (position < 0) { position = 0; } currentLayers.add(position, layer); } if (propertyHandler != null) { String pre = layer.getPropertyPrefix(); if (pre != null && pre.length() > 0) { propertyHandler.addUsedPrefix(pre); } } // Need to make this call before thinking about adding the Layer to the // BeanContext, so when the Layer shows up in the findAndInit() method, // it's already a part of the Layer list. One potential problem that may // occur is that the Layer might not be ready to be added to the map and // to other application components that get LayerEvents from the // LayerHandler, and they will all know about the layer being in the // stack after the setLayers() call. setLayerList(currentLayers); // Add the layer to the BeanContext, if it wants to be and it's not // already in a BeanContext. Thought about making the BC check look for // the same BC as the LayerHandler is a part of, but it's probably // better just to do a null check in case the Layer is a member of a // more restricted BeanContext with limited access. BeanContext bc = getBeanContext(); if (bc != null && layer.getAddToBeanContext() && layer.getBeanContext() == null) { bc.add(layer); } } /** * Remove a layer from the list of potentials. * * @param layer to remove. */ public void removeLayer(Layer layer) { if (layer != null && layer.isRemovable()) { List<Layer> currentLayers = getLayerList(); currentLayers.remove(layer); setLayerList(currentLayers); } else { if (layer != null) { logger.warning("received command to remove " + layer.getName() + ", which has been designated as *NOT* removeable"); throw new com.bbn.openmap.util.HandleError("LayerHandler commanded to delete a layer (" + layer.getName() + ") that is not removeable"); } } } /** * Remove a layer from the list of potentials. * * @param index of layer in the layer array. Top-most is first. */ public void removeLayer(int index) { List<Layer> currentLayers = getLayerList(); try { currentLayers.remove(index); setLayerList(currentLayers); } catch (IndexOutOfBoundsException ioobe) { // ignore it. } } public boolean hasLayer(Layer l) { return getLayerList().contains(l); } /** * Remove all the layers (that are marked as removable). */ public void removeAll() { List<Layer> oldLayers = getLayerList(); if (allLayers == null || allLayers.isEmpty()) { return; } BeanContext bc = getBeanContext(); List<Layer> nonRemoveableLayers = new ArrayList<Layer>(); for (Layer layer : oldLayers) { if (layer != null) { if (layer.isRemovable()) { turnLayerOn(false, layer); layer.clearListeners(); if (bc != null) { // Remove the layer from the BeanContext bc.remove(layer); } } else { nonRemoveableLayers.add(layer); } } } setLayerList(nonRemoveableLayers); // I know this is bad but it seems to work, forcing the // memory from old, deleted layers to be freed. With such a // drastic method call as removeAll, this should be OK. System.gc(); } /** * The version that does the work. The other two functions do sanity checks. * Calls setLayers(), and removes the layer from the BeanContext. * * @param currentLayers the current layers handled in the LayersMenu. * @param index the validated index of the layer to remove. */ protected void removeLayer(Layer[] currentLayers, int index) { Layer rLayer = currentLayers[index]; if (!rLayer.isRemovable()) { logger.warning("received command to remove " + rLayer.getName() + ", which has been designated as *NOT* removeable"); return; } rLayer.setVisible(false); Layer[] newLayers = new Layer[currentLayers.length - 1]; System.arraycopy(currentLayers, 0, newLayers, 0, index); System.arraycopy(currentLayers, index + 1, newLayers, index, currentLayers.length - index - 1); // Remove the layer to the BeanContext, if it wants to be. BeanContext bc = getBeanContext(); if (bc != null) { bc.remove(rLayer); } turnLayerOn(false, rLayer); rLayer.clearListeners(); rLayer = null; // Shouldn't call this, but it's the only thing that seems to // make it work... // if (Debug.debugging("helpgc")) { // System.gc(); // } setLayers(newLayers); } /** * Take a layer that the LayersMenu knows about, that may or may not be a * part of the map, and change its visibility by adding/removing it from the * MapBean. * * @param setting true to add layer to the map. * @param index the index of the layer to turn on/off. * @return true of index represented a layer, false if not or if something * went wrong. */ public boolean turnLayerOn(boolean setting, int index) { try { return turnLayerOn(setting, getLayerList().get(index)); } catch (ArrayIndexOutOfBoundsException aoobe) { // Do nothing... } catch (NullPointerException npe) { // Do nothing... } return false; } /** * Take a layer that the LayersMenu knows about, that may or may not be a * part of the map, and change its visibility by adding/removing it from the * MapBean. If the layer is not found, it's added and the visibility depends * on the setting parameter. * * @param setting true to add layer to the map. * @param layer the layer to turn on. * @return true if the layer was found, false if not or if something went * wrong. */ public boolean turnLayerOn(boolean setting, Layer layer) { if ((setting && !layer.isVisible()) || (!setting && layer.isVisible())) { if (logger.isLoggable(Level.FINE)) { logger.fine("turning " + layer.getName() + (setting ? " on" : " off")); } layer.setVisible(setting); List<Layer> newList = layerConfigListeners.checkLayerConfiguration(getLayerList()); if (newList != null) { allLayers = organizeBackgroundLayers(newList); } getListeners().pushLayerEvent(LayerEvent.REPLACE, getMapLayers()); return true; } return false; } /** * Called from childrenAdded(), when a new component is added to the * BeanContext, and from setBeanContext() when the LayerHandler is initially * added to the BeanContext. This method takes the iterator provided when * those methods are called, and looks for the objects that the LayerHandler * is interested in, namely, the MapBean, the PropertyHandler, or any other * LayerListeners. The LayerHandler handles multiple LayerListeners, and if * one is found, it is added to the LayerListener list. If a PropertyHandler * is found, then init() is called, effectively resetting the layers held by * the LayerHandler. * * @param someObj an Object being added to the MapHandler/BeanContext. */ public void findAndInit(Object someObj) { if (someObj instanceof LayerListener) { logger.fine("LayerHandler found a LayerListener."); addLayerListener((LayerListener) someObj); } if (someObj instanceof LayerConfigurationListener) { logger.fine("LayerHandler found a LayerConfigurationListener."); addLayerConfigurationListener((LayerConfigurationListener) someObj); } if (someObj instanceof Layer) { if (logger.isLoggable(Level.FINE)) { logger.fine("LayerHandler found a Layer |" + ((Layer) someObj).getName() + "|" + someObj.getClass().getName()); } if (!hasLayer((Layer) someObj)) { addLayer((Layer) someObj, 0); } } if (someObj instanceof PlugIn) { PlugIn pi = (PlugIn) someObj; if (pi.getComponent() == null) { PlugInLayer pil = new PlugInLayer(); pil.setPlugIn(pi); addLayer(pil, 0); } } if (someObj instanceof PropertyHandler) { // Used to notify the PropertyHandler of used property // prefix names. setPropertyHandler((PropertyHandler) someObj); } } /** * A BeanContextMembershipListener interface method, which is called when * new objects are removed from the BeanContext. If a LayerListener or Layer * is found on this list, it is removed from the list of LayerListeners. * * @param someObj an Object being removed from the MapHandler/BeanContext. */ public void findAndUndo(Object someObj) { if (someObj instanceof LayerListener) { logger.fine("LayerListener object is being removed"); removeLayerListener((LayerListener) someObj); } if (someObj instanceof LayerConfigurationListener) { logger.fine("LayerConfigurationListener is being removed."); removeLayerConfigurationListener((LayerConfigurationListener) someObj); } if (someObj instanceof Layer) { removeLayer((Layer) someObj); } if (someObj instanceof PlugIn) { PlugIn pi = (PlugIn) someObj; Component comp = pi.getComponent(); if (comp instanceof Layer && hasLayer((Layer) comp)) { removeLayer((Layer) comp); } } if (someObj instanceof PropertyHandler && someObj == getPropertyHandler()) { setPropertyHandler(null); } } /** * Add layers to the BeanContext, if they want to be. Since the BeanContext * is a Collection, it doesn't matter if a layer is already there because * duplicates aren't allowed. * * @param layers layers to add, if they want to be. */ public void addLayersToBeanContext(List<Layer> layers) { BeanContext bc = getBeanContext(); if (bc == null || layers == null) { return; } for (Layer layer : layers) { if (layer.getAddToBeanContext() && layer.getBeanContext() == null) { bc.add(layer); } } } /** * Add layers to the BeanContext, if they want to be. Since the BeanContext * is a Collection, it doesn't matter if a layer is already there because * duplicates aren't allowed. * * @param layers layers to add, if they want to be. */ public void removeLayersFromBeanContext(List<Layer> layers) { BeanContext bc = getBeanContext(); if (bc == null || layers == null) { return; } for (Layer layer : layers) { bc.remove(layer); } } /** * Called when the LayerHandler is added to a BeanContext. This method calls * findAndInit() to hook up with any objects that may already be added to * the BeanContext. A BeanContextChild method. * * @param in_bc BeanContext. */ public void setBeanContext(BeanContext in_bc) throws PropertyVetoException { if (in_bc != null) { logger.fine("setting bean context"); in_bc.addBeanContextMembershipListener(this); beanContextChildSupport.setBeanContext(in_bc); // This will cause findAndInit to be called on the layers and // plugins after they are added to the MapHandler, so they can find // the components they need before they get added to the // map (if they are to be added at startup). addLayersToBeanContext(getLayerList()); // Calling this here may (will) cause the MapBean to get // loaded with its initial layers, since it is a // LayerListener. findAndInit(in_bc.iterator()); } } public void setSynchronousThreading(boolean s) { getListeners().setSynchronous(s); } public boolean isSynchronousThreading() { return getListeners().isSynchronous(); } public Properties getProperties(Properties props) { props = super.getProperties(props); props.put(PropUtils.getScopedPropertyPrefix(this) + SynchronousThreadingProperty, Boolean.toString(getListeners().isSynchronous())); return props; } public Properties getPropertyInfo(Properties props) { props = super.getPropertyInfo(props); String internString = i18n.get(LayerHandler.class, SynchronousThreadingProperty, I18n.TOOLTIP, "Launch new threads to do work."); props.put(SynchronousThreadingProperty, internString); internString = i18n.get(LayerHandler.class, SynchronousThreadingProperty, "Synchronous Threading"); props.put(SynchronousThreadingProperty + LabelEditorProperty, internString); props.put(SynchronousThreadingProperty + EditorProperty, "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor"); return props; } }