/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.layer.tree; import gov.nasa.worldwind.globes.ElevationModel; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.layers.LayerList; import gov.nasa.worldwind.terrain.CompoundElevationModel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import au.gov.ga.earthsci.common.collection.ArrayListHashMap; import au.gov.ga.earthsci.common.collection.ListMap; import au.gov.ga.earthsci.common.persistence.Exportable; import au.gov.ga.earthsci.common.persistence.Persistent; import au.gov.ga.earthsci.common.util.IEnableable; import au.gov.ga.earthsci.common.util.IInformationed; import au.gov.ga.earthsci.core.model.IModelStatus; import au.gov.ga.earthsci.core.model.ModelStatus; import au.gov.ga.earthsci.core.tree.AbstractTreeNode; import au.gov.ga.earthsci.core.worldwind.WorldWindCompoundElevationModel; import au.gov.ga.earthsci.layer.DrawOrder; import au.gov.ga.earthsci.worldwind.common.layers.Bounds; /** * Abstract implementation of the {@link ILayerTreeNode} interface. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ @Exportable public abstract class AbstractLayerTreeNode extends AbstractTreeNode<ILayerTreeNode> implements ILayerTreeNode { private String id; private String name; private LayerList layerList; private List<Layer> unsortedLayers; private List<Layer> sortedLayers; private WorldWindCompoundElevationModel elevationModels; private ListMap<URI, ILayerTreeNode> catalogUriMap; private boolean lastAnyChildrenEnabled, lastAllChildrenEnabled; private String label; private URI catalogURI; private URL nodeInformationURL; private URL legendURL; private URL iconURL; private boolean expanded; private final Object semaphore = new Object(); private IModelStatus status = ModelStatus.ok(null); protected AbstractLayerTreeNode() { super(ILayerTreeNode.class); id = UUID.randomUUID().toString(); addPropertyChangeListener("enabled", new EnabledChangeListener()); //$NON-NLS-1$ } @Persistent @Override public String getId() { return id; } private void setId(String id) { this.id = id; } @Override public void setIdFrom(ILayerNode node) { setId(node.getId()); } @Persistent(attribute = true) @Override public String getName() { return name; } @Override public void setName(String name) { firePropertyChange("name", getName(), this.name = name); //$NON-NLS-1$ } @Persistent(attribute = true) @Override public String getLabel() { return label; } @Override public void setLabel(String label) { if (getName() != null && getName().equals(label)) { setLabel(null); return; } firePropertyChange("label", getLabel(), this.label = label); //$NON-NLS-1$ } @Override public String getLabelOrName() { return getLabel() == null ? getName() : getLabel(); } @Persistent(name = "catalogURI") @Override public URI getCatalogURI() { return catalogURI; } @Override public void setCatalogURI(URI catalogURI) { firePropertyChange("catalogURI", getCatalogURI(), this.catalogURI = catalogURI); //$NON-NLS-1$ } /** * The URL pointing to this node's information page. * <p/> * If this node is a layer node and the associated layer implements * {@link IInformationed}, then the layer's information URL should be used * for information instead of this one. * * @return The URL pointing to this node's information page. */ @Persistent(name = "infoURL") public URL getNodeInformationURL() { return nodeInformationURL; } public void setNodeInformationURL(URL nodeInformationURL) { firePropertyChange("nodeInformationURL", getNodeInformationURL(), this.nodeInformationURL = nodeInformationURL); //$NON-NLS-1$ } @Override public URL getInformationURL() { return getNodeInformationURL(); } @Override public String getInformationString() { return LayerNodeDescriber.describe(this); } @Override public Bounds getBounds() { Bounds bounds = null; for (ILayerTreeNode child : getChildren()) { bounds = Bounds.union(bounds, child.getBounds()); } return bounds; } @Override public boolean isFollowTerrain() { //if any layers don't follow the terrain, then return false for (ILayerTreeNode child : getChildren()) { if (!child.isFollowTerrain()) { return false; } } return true; } @Persistent @Override public URL getLegendURL() { return legendURL; } public void setLegendURL(URL legendURL) { firePropertyChange("legendURL", getLegendURL(), this.legendURL = legendURL); //$NON-NLS-1$ } @Persistent @Override public URL getIconURL() { return iconURL; } public void setIconURL(URL iconURL) { firePropertyChange("iconURL", getIconURL(), this.iconURL = iconURL); //$NON-NLS-1$ } @Persistent(name = "children") public ILayerTreeNode[] getChildrenAsArray() { List<ILayerTreeNode> children = getChildren(); return getChildren().toArray(new ILayerTreeNode[children.size()]); } public void setChildrenAsArray(ILayerTreeNode[] children) { setChildren(Arrays.asList(children)); } @Override public boolean isAnyChildrenEnabled() { return anyChildrenEnabledEquals(true); } @Override public boolean isAllChildrenEnabled() { return !anyChildrenEnabledEquals(false); } @Override public boolean anyChildrenEnabledEquals(boolean enabled) { if (this instanceof IEnableable) { IEnableable enableable = (IEnableable) this; if (enableable.isEnabled() == enabled) { return true; } } if (hasChildren()) { for (ILayerTreeNode child : getChildren()) { if (child.anyChildrenEnabledEquals(enabled)) { return true; } } } return false; } @Override public void enableChildren(boolean enabled) { if (this instanceof IEnableable) { IEnableable enableable = (IEnableable) this; enableable.setEnabled(enabled); } if (hasChildren()) { for (ILayerTreeNode child : getChildren()) { child.enableChildren(enabled); } } } @Override public LayerList getLayers() { synchronized (semaphore) { if (layerList == null) { layerList = new LayerList(); unsortedLayers = new ArrayList<Layer>(); sortedLayers = new ArrayList<Layer>(); updateLayers(); } return layerList; } } @Override public void updateLayers() { synchronized (semaphore) { if (layerList != null) { layerList.removeAll(); unsortedLayers.clear(); sortedLayers.clear(); addLayerNodesToList(this, unsortedLayers); DrawOrder.sortLayers(unsortedLayers, sortedLayers); layerList.addAll(sortedLayers); firePropertyChange("layers", null, layerList); //$NON-NLS-1$ } } if (!isRoot()) { getParent().updateLayers(); } } private static void addLayerNodesToList(ILayerTreeNode node, List<Layer> list) { if (node instanceof Layer) { list.add((Layer) node); } for (ILayerTreeNode child : node.getChildren()) { addLayerNodesToList(child, list); } } @Override public ILayerTreeNode getNodeForCatalogURI(URI catalogURI) { synchronized (semaphore) { if (catalogUriMap == null) { catalogUriMap = new ArrayListHashMap<URI, ILayerTreeNode>(); updateCatalogURIMap(); } List<ILayerTreeNode> nodes = catalogUriMap.get(catalogURI); if (nodes != null && nodes.size() > 0) { return nodes.get(0); } return null; } } @Override public CompoundElevationModel getElevationModels() { synchronized (semaphore) { if (elevationModels == null) { elevationModels = new WorldWindCompoundElevationModel(); updateElevationModels(); } return elevationModels; } } @Override public void updateElevationModels() { synchronized (semaphore) { if (elevationModels != null) { elevationModels.removeAll(); addElevationModelNodesToCompoundElevationModel(this, elevationModels); } } if (!isRoot()) { getParent().updateElevationModels(); } } private static void addElevationModelNodesToCompoundElevationModel(ILayerTreeNode node, CompoundElevationModel compound) { if (node instanceof ILayerNode) { ILayerNode layerNode = (ILayerNode) node; ElevationModel elevationModel = layerNode.getElevationModel(); if (elevationModel != null) { compound.addElevationModel(elevationModel); } } for (ILayerTreeNode child : node.getChildren()) { addElevationModelNodesToCompoundElevationModel(child, compound); } } private void updateCatalogURIMap() { synchronized (semaphore) { if (catalogUriMap != null) { catalogUriMap.clear(); addNodesToCatalogURIMap(this); } } } private void addNodesToCatalogURIMap(ILayerTreeNode node) { URI catalogUri = node.getCatalogURI(); if (catalogUri != null) { catalogUriMap.putSingle(node.getCatalogURI(), node); } for (ILayerTreeNode child : node.getChildren()) { addNodesToCatalogURIMap(child); } } @Override protected void fireChildrenPropertyChange(List<ILayerTreeNode> oldChildren, List<ILayerTreeNode> newChildren) { childrenChanged(oldChildren, children); super.fireChildrenPropertyChange(oldChildren, newChildren); } @Override public void childrenChanged(List<ILayerTreeNode> oldChildren, List<ILayerTreeNode> newChildren) { //TODO should we implement a (more efficient?) modification of these collections according to changed children? //update the collections if they exist updateLayers(); updateElevationModels(); updateCatalogURIMap(); //fire property changes fireAnyAllChildrenEnabledChanged(); //recurse up to the root node if (!isRoot()) { getParent().childrenChanged(oldChildren, newChildren); } } @Override public void enabledChanged() { //fire property changes fireAnyAllChildrenEnabledChanged(); //recurse up to the root node if (!isRoot()) { getParent().enabledChanged(); } } private void fireAnyAllChildrenEnabledChanged() { firePropertyChange( "allChildrenEnabled", lastAllChildrenEnabled, lastAllChildrenEnabled = isAllChildrenEnabled()); //$NON-NLS-1$ firePropertyChange( "anyChildrenEnabled", lastAnyChildrenEnabled, lastAnyChildrenEnabled = isAnyChildrenEnabled()); //$NON-NLS-1$ } private class EnabledChangeListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { enabledChanged(); } } @Override public String toString() { return getClass().getSimpleName() + " {" + getLabelOrName() + "}"; //$NON-NLS-1$ //$NON-NLS-2$ } @Persistent(attribute = true) @Override public boolean isExpanded() { return expanded; } @Override public void setExpanded(boolean expanded) { firePropertyChange("expanded", isExpanded(), this.expanded = expanded); //$NON-NLS-1$ } @Override public IModelStatus getStatus() { return status; } @Override public void setStatus(IModelStatus status) { firePropertyChange("status", getStatus(), this.status = status); //$NON-NLS-1$ } }