This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 012d9b3 Add documentation on some portrayal classes.
012d9b3 is described below
commit 012d9b33727da3f65532bffc1891a75cb048755c
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Feb 4 20:01:56 2020 +0100
Add documentation on some portrayal classes.
---
.../apache/sis/coverage/grid/GridCoverage2D.java | 1 -
.../java/org/apache/sis/internal/map/MapItem.java | 317 ++++++++++++++++-----
.../org/apache/sis/internal/map/Presentation.java | 1 -
.../apache/sis/internal/map/RenderException.java | 40 ++-
.../org/apache/sis/internal/map/package-info.java | 8 +-
5 files changed, 275 insertions(+), 92 deletions(-)
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
index 2f8eabd..405386a 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverage2D.java
@@ -59,7 +59,6 @@ import static java.lang.Math.subtractExact;
import static java.lang.Math.toIntExact;
// Branch-specific imports
-
import org.opengis.coverage.CannotEvaluateException;
import org.opengis.coverage.PointOutsideCoverageException;
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/MapItem.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/MapItem.java
index d34dc9b..b432dcd 100644
--- a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/MapItem.java
+++ b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/MapItem.java
@@ -16,140 +16,229 @@
*/
package org.apache.sis.internal.map;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.HashMap;
import java.util.Map;
+import java.util.HashMap;
+import java.util.Arrays;
import java.util.Objects;
-import javax.swing.event.EventListenerList;
+import java.util.ConcurrentModificationException;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import org.opengis.util.InternationalString;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
/**
* Parent class of all map elements.
+ * This base class does not make any assumption about how this {@code MapItem}
will be rendered;
+ * the actual feature or coverage data, together with styling information, are
provided by subclasses.
+ * A {@code MapItem} contains the following properties:
+ *
+ * <ul>
+ * <li>An {@linkplain #getIdentifier() identifier}, which can be any {@link
String} at developer choice.</li>
+ * <li>A human-readable {@linkplain #getTitle() title} for pick lists, for
example in GUI.</li>
+ * <li>A {@linkplain #getAbstract() narrative description} providing
additional information.</li>
+ * </ul>
*
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
+ * Additional information can be added in a map of {@linkplain
#getUserProperties() user properties}.
+ *
+ * <h2>Synchronization</h2>
+ * {@code MapItem} instances are not thread-safe. Synchronization, if desired,
is caller responsibility.
*
* @author Johann Sorel (Geomatys)
- * @version 2.0
- * @since 2.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
public abstract class MapItem {
-
- /** Identifies a change in the map item identifier. */
+ /**
+ * The {@value} property name, used for notifications about changes in map
item identifier.
+ * The identifier (or name) can be used to reference the item externally.
+ * Associated values are instances of {@link String}.
+ *
+ * @see #getIdentifier()
+ * @see #setIdentifier(String)
+ * @see #addPropertyChangeListener(String, PropertyChangeListener)
+ *
+ * @todo This property seems to be named {@code "se:Name"} in SLD
specification. Should we rename?
+ */
public static final String IDENTIFIER_PROPERTY = "identifier";
- /** Identifies a change in the map item title. */
+
+ /**
+ * The {@value} property name, used for notifications about changes in map
item title.
+ * The title is a short description for item that might be displayed in a
GUI pick list.
+ * Associated values are instances of {@link String} or {@link
InternationalString}.
+ *
+ * @see #getTitle()
+ * @see #setTitle(CharSequence)
+ * @see #addPropertyChangeListener(String, PropertyChangeListener)
+ */
public static final String TITLE_PROPERTY = "title";
- /** Identifies a change in the map item abstract description. */
+
+ /**
+ * The {@value} property name, used for notifications about changes in map
item description.
+ * The abstract is a narrative description providing additional
information.
+ * It is more detailed than the {@value #TITLE_PROPERTY} property and may
be a few paragraphs long.
+ * Associated values are instances of {@link String} or {@link
InternationalString}.
+ *
+ * @see #getAbstract()
+ * @see #setAbstract(CharSequence)
+ * @see #addPropertyChangeListener(String, PropertyChangeListener)
+ */
public static final String ABSTRACT_PROPERTY = "abstract";
- /** Identifies a change in the map item visibility state. */
- public static final String VISIBLE_PROPERTY = "visible";
- private final EventListenerList listeners = new EventListenerList();
+ /**
+ * The {@value} property name, used for notifications about changes in map
item visibility state.
+ * Associated values are instances of {@link Boolean}.
+ */
+ public static final String VISIBLE_PROPERTY = "visible";
/**
* Identifier of this map item.
+ *
+ * @see #IDENTIFIER_PROPERTY
+ * @see #getIdentifier()
*/
private String identifier;
+
/**
* The title of this map item, for display to the user.
+ *
+ * @see #TITLE_PROPERTY
+ * @see #getTitle()
*/
private CharSequence title;
/**
- * A description of this map item, for display to the user.
+ * A description of this map item, for display to the user. The property
name is
+ * {@value #ABSTRACT_PROPERTY} but we use a different field name because
"abstract"
+ * is a reserved keyword.
+ *
+ * @see #ABSTRACT_PROPERTY
+ * @see #getAbstract()
*/
- private CharSequence abtract;
+ private CharSequence description;
/**
* Whether this item should be shown on the map.
+ *
+ * @see #VISIBLE_PROPERTY
+ * @see #isVisible()
*/
- private boolean visible = true;
+ private boolean visible;
/**
- * Additional user defined properties.
+ * Additional user defined properties, created when first requested.
+ *
+ * @see #getUserProperties()
*/
private Map<String,Object> userMap;
/**
+ * The registered listeners for each property, created when first needed.
+ *
+ * @see #addPropertyChangeListener(String, PropertyChangeListener)
+ * @see #removePropertyChangeListener(String, PropertyChangeListener)
+ */
+ private Map<String,PropertyChangeListener[]> listeners;
+
+ /**
* Only used by classes in this package.
*/
MapItem() {
+ visible = true;
}
/**
- * Returns the identifier of this map item.
+ * Returns the identifier of this map item. The identifier can be any
character string at developer choice;
+ * there is currently no restriction on the identifier form and no
restriction about identifier uniqueness.
+ * The identifier is currently not used by Apache SIS; it is made
available as a user convenience for
+ * referencing {@code MapItem} instances externally.
+ *
+ * <p>NOTE: restriction about identifier form and uniqueness may be added
in a future version.</p>
*
* @return identifier, or {@code null} if none.
+ *
+ * @see #IDENTIFIER_PROPERTY
*/
public String getIdentifier() {
return identifier;
}
/**
- * Sets a new identifier for this map item.
+ * Sets a new identifier for this map item. If this method is never
invoked, the default value is {@code null}.
+ * If the given value is different than the previous value, then a change
event is sent to all listeners
+ * registered for the {@value #IDENTIFIER_PROPERTY} property.
*
- * @param identifier identifier, or {@code null} if none.
+ * @param newValue the new identifier, or {@code null} if none.
*/
- public void setIdentifier(String identifier) {
- if (!Objects.equals(this.identifier, identifier)) {
- CharSequence old = this.identifier;
- this.identifier = identifier;
- firePropertyChange(IDENTIFIER_PROPERTY, old, identifier);
+ public void setIdentifier(final String newValue) {
+ final String oldValue = identifier;
+ if (!Objects.equals(oldValue, newValue)) {
+ identifier = newValue;
+ firePropertyChange(IDENTIFIER_PROPERTY, oldValue, newValue);
}
}
/**
- * Returns the title of this map item.
- * This title should be user friendly and may be an {@link
org.opengis.util.InternationalString}.
+ * Returns a human-readable short description for pick lists.
+ * This title should be user friendly and may be a {@link String} or
{@link InternationalString} instance.
* It shall not be used as an identifier.
*
- * @return title to be shown to the user, or {@code null} if none.
+ * @return a short description to be shown to the user, or {@code null} if
none.
+ *
+ * @see #TITLE_PROPERTY
*/
public CharSequence getTitle() {
return title;
}
/**
- * Sets a new title for this map item.
+ * Sets a new human-readable short description for pick lists. If this
method is never invoked,
+ * the default value is {@code null}. If the given value is different than
the previous value,
+ * then a change event is sent to all listeners registered for the {@value
#TITLE_PROPERTY} property.
*
- * @param title title to be shown to the user, or {@code null} if none.
+ * @param newValue a short description to be shown to the user, or
{@code null} if none.
*/
- public void setTitle(CharSequence title) {
- if (!Objects.equals(this.title, title)) {
- CharSequence old = this.title;
- this.title = title;
- firePropertyChange(TITLE_PROPERTY, old, title);
+ public void setTitle(final CharSequence newValue) {
+ final CharSequence oldValue = title;
+ if (!Objects.equals(oldValue, newValue)) {
+ title = newValue;
+ firePropertyChange(TITLE_PROPERTY, oldValue, newValue);
}
}
/**
- * Returns the description of this map item.
- * This description should be user friendly and may be an {@link
org.opengis.util.InternationalString}.
+ * Returns a narrative description providing additional information.
+ * The abstract is more detailed than the {@linkplain #getTitle() title}
property and may be a few paragraphs long.
+ * This abstract should be user friendly and may be a {@link String} or
{@link InternationalString} instance.
+ *
+ * @return narrative description to be shown to the user, or {@code null}
if none.
*
- * @return description to be shown to the user, or {@code null} if none.
+ * @see #ABSTRACT_PROPERTY
*/
public CharSequence getAbstract() {
- return abtract;
+ return description;
}
/**
- * Sets a new description for this map item.
+ * Sets a new a narrative description providing additional information. If
this method is never invoked,
+ * the default value is {@code null}. If the given value is different than
the previous value, then
+ * a change event is sent to all listeners registered for the {@value
#ABSTRACT_PROPERTY} property.
*
- * @param abtract title to be shown to the user, or {@code null} if none.
+ * @param newValue a narrative description to be shown to the user, or
{@code null} if none.
*/
- public void setAbstract(CharSequence abtract) {
- if (!Objects.equals(this.abtract, abtract)) {
- CharSequence old = this.abtract;
- this.abtract = abtract;
- firePropertyChange(ABSTRACT_PROPERTY, old, abtract);
+ public void setAbstract(final CharSequence newValue) {
+ final CharSequence oldValue = description;
+ if (!Objects.equals(oldValue, newValue)) {
+ description = newValue;
+ firePropertyChange(ABSTRACT_PROPERTY, oldValue, newValue);
}
}
/**
- * Return whether this item should be shown on the map.
+ * Returns whether this item should be shown on the map. If this item is a
{@code MapGroup},
+ * then a {@code false} visibility status implies that all group
components are also hidden.
*
* @return {@code true} if this item is visible.
*/
@@ -159,52 +248,134 @@ public abstract class MapItem {
/**
* Sets whether this item should be shown on the map.
- * If this item is a {@code MapGroup}, then hiding this group should hide
all components in this group.
+ * If this method is never invoked, the default value is {@code true}.
+ * If the given value is different than the previous value, then a change
event
+ * is sent to all listeners registered for the {@value #VISIBLE_PROPERTY}
property.
*
- * @param visible {@code false} to hide this item and all it's components.
+ * <p>If this item is a {@code MapGroup}, then hiding this group should
hide all components in this group,
+ * but without changing the individual {@value #VISIBLE_PROPERTY} property
of those components.
+ * Consequently making the group visible again restore each component to
the visibility state
+ * it has before the group was hidden (assuming those states have not been
changed in other ways).</p>
+ *
+ * @param newValue {@code false} to hide this item and all it's
components.
*/
- public void setVisible(boolean visible) {
- if (this.visible != visible) {
- this.visible = visible;
- firePropertyChange(VISIBLE_PROPERTY, !visible, visible);
+ public void setVisible(final boolean newValue) {
+ final boolean oldValue = visible;
+ if (oldValue != newValue) {
+ visible = newValue;
+ firePropertyChange(VISIBLE_PROPERTY, oldValue, newValue);
}
}
/**
- * @return map of all user properties.
- * This is the live map.
+ * Returns a modifiable map of user properties.
+ * The content of this map is left to users; Apache SIS does not use it in
any way.
+ * This map is not thread-safe; synchronization if desired is user
responsibility.
+ *
+ * @return map of user properties. This map is live: changes in this map
+ * are immediately reflected in this {@code MapItem}.
*/
- public synchronized Map<String,Object> getUserProperties() {
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ public Map<String,Object> getUserProperties() {
if (userMap == null) {
userMap = new HashMap<>();
}
return userMap;
}
+
/**
- * Register a property listener.
+ * Register a listener for the property of the given name.
+ * The listener will be notified every time that the property of the given
name got a new value.
+ * The {@code propertyName} can be one of the following values:
+ *
+ * <ul>
+ * <li>{@value #IDENTIFIER_PROPERTY} — for changes in identifier of this
map item.</li>
+ * <li>{@value #TITLE_PROPERTY} — for changes in human-readable
short description.</li>
+ * <li>{@value #ABSTRACT_PROPERTY} — for changes in narrative
description.</li>
+ * <li>{@value #VISIBLE_PROPERTY} — for changes in visibility
state.</li>
+ * <li>Any other property defined by subclasses.</li>
+ * </ul>
*
- * @param listener property listener to register
+ * If the same listener is registered twice for the same property, then it
will be notified twice
+ * (this method does not perform duplication checks).
+ *
+ * @param propertyName name of the property to listen.
+ * @param listener property listener to register.
*/
- public final void addPropertyChangeListener(PropertyChangeListener
listener) {
- listeners.add(PropertyChangeListener.class, listener);
+ public final void addPropertyChangeListener(final String propertyName,
final PropertyChangeListener listener) {
+ ArgumentChecks.ensureNonEmpty("propertyName", propertyName);
+ ArgumentChecks.ensureNonNull("listener", listener);
+ if (listeners == null) {
+ listeners = new HashMap<>(4); // Assume few properties will
be listened.
+ }
+ final PropertyChangeListener[] oldList = listeners.get(propertyName);
+ final PropertyChangeListener[] newList;
+ final int n;
+ if (oldList != null) {
+ n = oldList.length;
+ newList = Arrays.copyOf(oldList, n+1);
+ } else {
+ n = 0;
+ newList = new PropertyChangeListener[1];
+ }
+ newList[n] = listener;
+ if (!listeners.replace(propertyName, oldList, newList)) {
+ // Opportunistic safety against some multi-threading misuse.
+ throw new ConcurrentModificationException();
+ }
}
/**
- * Unregister a property listener.
+ * Unregister a property listener. The given {@code propertyName} can be
any of the name documented in
+ * {@link #addPropertyChangeListener(String, PropertyChangeListener)}. If
the specified listener is not
+ * registered for the specified property, then nothing happen. If the
listener has been registered twice,
+ * then only one registration is removed (one registration will remain).
*
- * @param listener property listener to register
+ * @param propertyName name of the listened property.
+ * @param listener property listener to unregister.
*/
- public final void removePropertyChangeListener(PropertyChangeListener
listener) {
- listeners.remove(PropertyChangeListener.class, listener);
+ public final void removePropertyChangeListener(final String propertyName,
final PropertyChangeListener listener) {
+ ArgumentChecks.ensureNonEmpty("propertyName", propertyName);
+ ArgumentChecks.ensureNonNull("listener", listener);
+ if (listeners != null) {
+ final PropertyChangeListener[] oldList =
listeners.get(propertyName);
+ if (oldList != null) {
+ for (int i=oldList.length; --i >= 0;) {
+ if (oldList[i] == listener) {
+ if (oldList.length != 1) {
+ final PropertyChangeListener[] newList =
ArraysExt.remove(oldList, i, 1);
+ if (listeners.replace(propertyName, oldList,
newList)) {
+ return;
+ }
+ } else if (listeners.remove(propertyName, oldList)) {
+ return;
+ }
+ // Opportunistic safety against some multi-threading
misuse.
+ throw new ConcurrentModificationException();
+ }
+ }
+ }
+ }
}
+ /**
+ * Notifies all registered listener that a property of the given name
changed its value.
+ * It is caller responsibility to verify that the old and new values are
not equal
+ * (this method does not verify).
+ *
+ * @param propertyName name of the property that changed its value.
+ * @param oldValue the old property value (may be {@code null}).
+ * @param newValue the new property value (may be {@code null}).
+ */
protected void firePropertyChange(final String propertyName, final Object
oldValue, final Object newValue) {
- final PropertyChangeListener[] listPs =
listeners.getListeners(PropertyChangeListener.class);
- if (listPs.length == 0) return;
-
- final PropertyChangeEvent event = new
PropertyChangeEvent(this,propertyName,oldValue,newValue);
- for (PropertyChangeListener listener : listPs) {
- listener.propertyChange(event);
+ if (listeners != null) {
+ final PropertyChangeListener[] list = listeners.get(propertyName);
+ if (list != null) {
+ final PropertyChangeEvent event = new
PropertyChangeEvent(this, propertyName, oldValue, newValue);
+ for (final PropertyChangeListener listener : list) {
+ listener.propertyChange(event);
+ }
+ }
}
}
}
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
index 0673be1..065c7ed 100644
---
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
+++
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/Presentation.java
@@ -89,5 +89,4 @@ public abstract class Presentation {
public void setCandidate(Feature feature) {
this.candidate = feature;
}
-
}
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/RenderException.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/RenderException.java
index 36b3edd..ffab9dd 100644
---
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/RenderException.java
+++
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/RenderException.java
@@ -16,34 +16,46 @@
*/
package org.apache.sis.internal.map;
-import org.apache.sis.util.ArgumentChecks;
-
/**
* Thrown when a map rendering process failed.
*
- * <p>
- * NOTE: this class is a first draft subject to modifications.
- * </p>
- *
* @author Johann Sorel (Geomatys)
- * @version 2.0
- * @since 2.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
public class RenderException extends Exception {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 4185833217030999642L;
+ /**
+ * Creates an exception with the specified details message.
+ *
+ * @param message the detail message.
+ */
public RenderException(final String message) {
super(message);
- ArgumentChecks.ensureNonEmpty("message", message);
}
- public RenderException(final Throwable throwable) {
- this(((throwable.getMessage() == null) ? "No message" :
throwable.getMessage()), throwable);
+ /**
+ * Creates an exception with the specified cause and no details message.
+ *
+ * @param cause the cause for this exception.
+ */
+ public RenderException(final Throwable cause) {
+ super(cause);
}
- public RenderException(final String message, final Throwable throwable) {
- super(message, throwable);
- ArgumentChecks.ensureNonEmpty("message", message);
+ /**
+ * Creates an exception with the specified details message and cause.
+ *
+ * @param message the detail message in the default locale.
+ * @param cause the cause for this exception.
+ */
+ public RenderException(final String message, final Throwable cause) {
+ super(message, cause);
}
}
diff --git
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/package-info.java
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/package-info.java
index 4fcaf00..324ac5a 100644
---
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/package-info.java
+++
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/package-info.java
@@ -24,11 +24,13 @@
* Some classes in this package will move to public API after we gained enough
confidence
* about their stability.</p>
*
- * @todo Since everything is about maps in this package, should we omit the
{@code Map} prefix in class names?
+ * <h2>Synchronization</h2>
+ * Unless otherwise specified, classes in this package are not thread safe.
+ * Synchronization, if desired, must be done by the caller.
*
* @author Johann Sorel (Geomatys)
- * @version 2.0
- * @since 2.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
package org.apache.sis.internal.map;