/******************************************************************************* * Copyright 2013 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.delegator; import gov.nasa.worldwind.avlist.AVList; import gov.nasa.worldwind.event.Message; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.render.DrawContext; import java.awt.Point; import java.beans.IntrospectionException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashSet; import java.util.Map.Entry; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import au.gov.ga.earthsci.common.util.AbstractPropertyChangeBean; import au.gov.ga.earthsci.common.util.IPropertyChangeBean; /** * Abstract {@link Layer} implementation that delegates methods to another * {@link Layer} instance. * <p/> * Also implements {@link IPropertyChangeBean}. All setters will fire a property * change. Any changed properties (ie opacity, name, etc) will be recorded, and * set on any new layers passed to the {@link #setLayer(Layer)} method. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public abstract class AbstractLayerDelegator<L extends Layer> extends AbstractPropertyChangeBean implements ILayerDelegator<L> { private static final Logger logger = LoggerFactory.getLogger(AbstractLayerDelegator.class); private final PropertyChangeListener propertyChangeListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { firePropertyChange(new PropertyChangeEvent(AbstractLayerDelegator.this, evt.getPropertyName(), evt.getOldValue(), evt.getNewValue())); } }; private L layer = createDummyLayer(); private Set<String> propertiesChanged = new HashSet<String>(); private boolean propertiesChangedTracking = true; private final Object layerSemaphore = new Object(); /** * @return A dummy layer that does nothing; used for storage of property * values before the real layer is set on this delegator */ protected abstract L createDummyLayer(); /** * Is the given layer an instance of a dummy layer that would be created by * the {@link #createDummyLayer()} method. * * @param layer * Layer to test * @return True if the given layer is a dummy layer */ protected abstract boolean isDummyLayer(L layer); @Override public L getLayer() { return layer; } @Override public void setLayer(L layer) { if (layer == null) { throw new NullPointerException("Layer delegate is null"); //$NON-NLS-1$ } if (layer == this) { throw new IllegalArgumentException("Cannot delegate to itself"); //$NON-NLS-1$ } Layer oldValue; synchronized (layerSemaphore) { oldValue = getLayer(); copyProperties(oldValue, layer); this.layer.removePropertyChangeListener(propertyChangeListener); this.layer = layer; this.layer.addPropertyChangeListener(propertyChangeListener); } firePropertyChange("layer", oldValue, layer); //$NON-NLS-1$ } @Override public Layer getGrandLayer() { Layer layer = getLayer(); if (layer instanceof ILayerDelegator) { ILayerDelegator<?> delegator = (ILayerDelegator<?>) layer; return delegator.getGrandLayer(); } return layer; } @Override public boolean isLayerSet() { return !isDummyLayer(layer); } @Override public boolean isGrandLayerSet() { if (layer instanceof ILayerDelegator<?>) { return ((ILayerDelegator<?>) layer).isGrandLayerSet(); } return isLayerSet(); } /** * Copy the changed properties between layers, by calling the getters of the * from layer and the setters on the to layer. * * @param from * Layer to get property values from * @param to * Layer to set property values on */ private void copyProperties(Layer from, Layer to) { if (from == to) { return; } synchronized (propertiesChanged) { setPropertiesChangedTrackingEnabled(false); for (String property : propertiesChanged) { try { PropertyDescriptor fromPropertyDescriptor = new PropertyDescriptor(property, from.getClass()); PropertyDescriptor toPropertyDescriptor = new PropertyDescriptor(property, to.getClass()); Method getter = fromPropertyDescriptor.getReadMethod(); Method setter = toPropertyDescriptor.getWriteMethod(); Object value = getter.invoke(from); setter.invoke(to, value); } catch (IntrospectionException e) { //ignore (invalid property name) } catch (Exception e) { logger.error("Error copying value between layers for property: " + property, e); //$NON-NLS-1$ } } setPropertiesChangedTrackingEnabled(true); } } @Override public void firePropertyChange(PropertyChangeEvent propertyChangeEvent) { super.firePropertyChange(propertyChangeEvent); addChangedProperty(propertyChangeEvent.getPropertyName()); } @Override public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { super.firePropertyChange(propertyName, oldValue, newValue); addChangedProperty(propertyName); } public void setPropertiesChangedTrackingEnabled(boolean enabled) { propertiesChangedTracking = enabled; } private void addChangedProperty(String propertyName) { synchronized (propertiesChanged) { if (propertiesChangedTracking && !"layer".equals(propertyName)) //$NON-NLS-1$ { propertiesChanged.add(propertyName); } } } @Override public void propertyChange(PropertyChangeEvent evt) { firePropertyChange(evt); } ////////////////////// // Layer delegation // ////////////////////// @Override public void dispose() { layer.dispose(); } @Override public void onMessage(Message msg) { layer.onMessage(msg); } @Override public Object setValue(String key, Object value) { return layer.setValue(key, value); } @Override public boolean isEnabled() { return layer.isEnabled(); } @Override public void setEnabled(boolean enabled) { boolean oldValue = isEnabled(); layer.setEnabled(enabled); firePropertyChange("enabled", oldValue, enabled); //$NON-NLS-1$ } @Override public String getName() { return layer.getName(); } @Override public void setName(String name) { String oldValue = getName(); layer.setName(name); firePropertyChange("name", oldValue, name); //$NON-NLS-1$ } @Override public AVList setValues(AVList avList) { return layer.setValues(avList); } @Override public String getRestorableState() { return layer.getRestorableState(); } @Override public double getOpacity() { return layer.getOpacity(); } @Override public void restoreState(String stateInXml) { layer.restoreState(stateInXml); } @Override public Object getValue(String key) { return layer.getValue(key); } @Override public void setOpacity(double opacity) { double oldValue = getOpacity(); layer.setOpacity(opacity); firePropertyChange("opacity", oldValue, opacity); //$NON-NLS-1$ } @Override public Collection<Object> getValues() { return layer.getValues(); } @Override public String getStringValue(String key) { return layer.getStringValue(key); } @Override public boolean isPickEnabled() { return layer.isPickEnabled(); } @Override public Set<Entry<String, Object>> getEntries() { return layer.getEntries(); } @Override public boolean hasKey(String key) { return layer.hasKey(key); } @Override public Object removeKey(String key) { return layer.removeKey(key); } @Override public void setPickEnabled(boolean isPickable) { boolean oldValue = isPickEnabled(); layer.setPickEnabled(isPickable); firePropertyChange("pickEnabled", oldValue, isPickable); //$NON-NLS-1$ } @Override public void preRender(DrawContext dc) { layer.preRender(dc); } @Override public void render(DrawContext dc) { layer.render(dc); } @Override public void pick(DrawContext dc, Point pickPoint) { layer.pick(dc, pickPoint); } @Override public boolean isAtMaxResolution() { return layer.isAtMaxResolution(); } @Override public boolean isMultiResolution() { return layer.isMultiResolution(); } @Override public double getScale() { return layer.getScale(); } @Override public boolean isNetworkRetrievalEnabled() { return layer.isNetworkRetrievalEnabled(); } @Override public void setNetworkRetrievalEnabled(boolean networkRetrievalEnabled) { boolean oldValue = isNetworkRetrievalEnabled(); layer.setNetworkRetrievalEnabled(networkRetrievalEnabled); firePropertyChange("networkRetrievalEnabled", oldValue, networkRetrievalEnabled); //$NON-NLS-1$ } @Override public AVList copy() { return layer.copy(); } @Override public void setExpiryTime(long expiryTime) { long oldValue = getExpiryTime(); layer.setExpiryTime(expiryTime); firePropertyChange("expiryTime", oldValue, expiryTime); //$NON-NLS-1$ } @Override public AVList clearList() { return layer.clearList(); } @Override public long getExpiryTime() { return layer.getExpiryTime(); } @Override public double getMinActiveAltitude() { return layer.getMinActiveAltitude(); } @Override public void setMinActiveAltitude(double minActiveAltitude) { double oldValue = getMinActiveAltitude(); layer.setMinActiveAltitude(minActiveAltitude); firePropertyChange("minActiveAltitude", oldValue, minActiveAltitude); //$NON-NLS-1$ } @Override public double getMaxActiveAltitude() { return layer.getMaxActiveAltitude(); } @Override public void setMaxActiveAltitude(double maxActiveAltitude) { double oldValue = getMaxActiveAltitude(); layer.setMaxActiveAltitude(maxActiveAltitude); firePropertyChange("maxActiveAltitude", oldValue, maxActiveAltitude); //$NON-NLS-1$ } @Override public boolean isLayerInView(DrawContext dc) { return layer.isLayerInView(dc); } @Override public boolean isLayerActive(DrawContext dc) { return layer.isLayerActive(dc); } @Override public Double getMaxEffectiveAltitude(Double radius) { return layer.getMaxEffectiveAltitude(radius); } @Override public Double getMinEffectiveAltitude(Double radius) { return layer.getMinEffectiveAltitude(radius); } }