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 20c4fcb Refactor MetadataCopier on top of MetadataVisitor.
20c4fcb is described below
commit 20c4fcb0e78e89991e243458f3e15103403a871a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Jul 2 15:48:59 2018 +0200
Refactor MetadataCopier on top of MetadataVisitor.
---
.../java/org/apache/sis/metadata/HashCode.java | 10 +--
.../org/apache/sis/metadata/MetadataCopier.java | 88 ++++++++++++++------
.../org/apache/sis/metadata/MetadataVisitor.java | 95 ++++++++++++++++++----
.../apache/sis/metadata/ModifiableMetadata.java | 25 +++---
.../org/apache/sis/metadata/PropertyAccessor.java | 63 +++++---------
.../main/java/org/apache/sis/metadata/Pruner.java | 13 ++-
.../java/org/apache/sis/metadata/StateChanger.java | 8 +-
7 files changed, 188 insertions(+), 114 deletions(-)
diff --git
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/HashCode.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/HashCode.java
index 161e814..8aca3df 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/HashCode.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/HashCode.java
@@ -64,13 +64,13 @@ final class HashCode extends MetadataVisitor<Integer> {
* Resets the hash code to an initial value for a new metadata instance.
* If another hash code computation was in progress, that code shall be
saved before this method is invoked.
*
- * @param type the standard interface of the metadata for which a hash
code value will be computed.
- * @return {@code false} since this visitor is not restricted to writable
properties.
+ * @param accessor contains the standard interface of the metadata for
which a hash code value will be computed.
+ * @return {@link Filter#NON_EMPTY} since this visitor is not restricted
to writable properties.
*/
@Override
- boolean preVisit(final Class<?> type) {
- code = type.hashCode();
- return false;
+ Filter preVisit(final PropertyAccessor accessor) {
+ code = accessor.type.hashCode();
+ return Filter.NON_EMPTY;
}
/**
diff --git
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java
index 88da1c2..329a6e9 100644
---
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java
+++
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataCopier.java
@@ -21,7 +21,7 @@ import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.LinkedHashMap;
-import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Arrays;
import java.util.Collection;
import java.lang.reflect.Constructor;
@@ -63,7 +63,7 @@ import org.apache.sis.util.collection.CodeListSet;
* @since 0.8
* @module
*/
-public class MetadataCopier {
+public class MetadataCopier extends MetadataVisitor<Object> {
/**
* The default metadata standard to use for object that are not {@link
AbstractMetadata} instances,
* or {@code null} if none.
@@ -71,10 +71,16 @@ public class MetadataCopier {
private final MetadataStandard standard;
/**
- * The metadata objects that have been copied so far.
- * This is used for resolving cyclic graphs.
+ * The current metadata instance where to copy the property values.
*/
- final Map<Object,Object> copies;
+ private Object target;
+
+ /**
+ * Creates a new {@code MetadataCopier} instance.
+ */
+ private MetadataCopier() {
+ standard = null;
+ }
/**
* Creates a new metadata copier.
@@ -84,7 +90,6 @@ public class MetadataCopier {
*/
public MetadataCopier(final MetadataStandard standard) {
this.standard = standard;
- copies = new IdentityHashMap<>();
}
/**
@@ -123,11 +128,7 @@ public class MetadataCopier {
* or an implementation class does not provide a public default
constructor.
*/
public Object copy(final Object metadata) {
- try {
- return copyRecursively(null, metadata);
- } finally {
- copies.clear();
- }
+ return copyRecursively(null, metadata);
}
/**
@@ -143,11 +144,7 @@ public class MetadataCopier {
*/
public <T> T copy(final Class<T> type, final T metadata) {
ArgumentChecks.ensureNonNull("type", type);
- try {
- return type.cast(copyRecursively(type, metadata));
- } finally {
- copies.clear();
- }
+ return type.cast(copyRecursively(type, metadata));
}
/**
@@ -171,12 +168,9 @@ public class MetadataCopier {
std = ((AbstractMetadata) metadata).getStandard();
}
if (std != null) {
- final PropertyAccessor accessor = std.getAccessor(new
CacheKey(metadata.getClass(), type), false);
- if (accessor != null) try {
- return accessor.copy(metadata, this);
- } catch (ReflectiveOperationException e) {
- throw new
UnsupportedOperationException(Errors.format(Errors.Keys.CanNotCopy_1,
accessor.type),
- Exceptions.unwrap(e));
+ final Object result = walk(std, type, metadata, false);
+ if (result != null) {
+ return result;
}
}
}
@@ -184,11 +178,39 @@ public class MetadataCopier {
}
/**
+ * Invoked before the properties of a metadata instance are visited. This
method creates a new instance,
+ * to be returned by {@link #result()}, and returns {@link
Filter#WRITABLE_RESULT} for notifying the caller
+ * that write operations need to be performed on that {@code result}
object.
+ */
+ @Override
+ final Filter preVisit(final PropertyAccessor accessor) {
+ if (accessor.isWritable()) try {
+ target = accessor.implementation.getConstructor().newInstance();
+ return Filter.WRITABLE_RESULT;
+ } catch (ReflectiveOperationException e) {
+ throw new
UnsupportedOperationException(Errors.format(Errors.Keys.CanNotCopy_1,
accessor.type), Exceptions.unwrap(e));
+ } else {
+ target = null;
+ return Filter.NONE;
+ }
+ }
+
+ /**
+ * Returns the metadata instance resulting from the copy. This method is
invoked <strong>before</strong>
+ * metadata properties are visited.
+ */
+ @Override
+ final Object result() {
+ return target;
+ }
+
+ /**
* Verifies if the given metadata value is a map or a collection before to
invoke
* {@link #copyRecursively(Class, Object)} for metadata elements. This
method is
- * invoked by {@link PropertyAccessor#copy(Object, MetadataCopier)}.
+ * invoked by {@link PropertyAccessor#walkWritable(MetadataVisitor,
Object, Object)}.
*/
- final Object copyAny(final Class<?> type, final Object metadata) {
+ @Override
+ final Object visit(final Class<?> type, final Object metadata) {
if (!type.isInstance(metadata)) {
if (metadata instanceof Collection<?>) {
Collection<?> c = (Collection<?>) metadata;
@@ -222,4 +244,22 @@ public class MetadataCopier {
}
return copyRecursively(type, metadata);
}
+
+ /**
+ * Returns the path to the currently copied property.
+ * Each element in the list is the UML identifier of a property.
+ * Element at index 0 is the name of the property of the root metadata
object being copied.
+ * Element at index 1 is the name of a property which is a children of
above property, <i>etc.</i>
+ *
+ * <p>The returned list is valid only during {@link
#copyRecursively(Class, Object)} method execution.
+ * The content of this list become undetermined after the {@code
copyRecursively} method returned.</p>
+ *
+ * @return the path to the currently copied property.
+ *
+ * @since 1.0
+ */
+ @Override
+ protected List<String> getCurrentPropertyPath() {
+ return super.getCurrentPropertyPath();
+ }
}
diff --git
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataVisitor.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataVisitor.java
index e3e5225..b64e160 100644
---
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataVisitor.java
+++
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataVisitor.java
@@ -17,10 +17,12 @@
package org.apache.sis.metadata;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.IdentityHashMap;
import java.util.ConcurrentModificationException;
import org.apache.sis.internal.system.Semaphores;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
/**
@@ -89,7 +91,7 @@ abstract class MetadataVisitor<R> {
/**
* Creates a new visitor.
*/
- protected MetadataVisitor() {
+ MetadataVisitor() {
visited = new IdentityHashMap<>();
propertyPath = new String[6];
}
@@ -97,8 +99,11 @@ abstract class MetadataVisitor<R> {
/**
* The thread-local variable that created this {@code MetadataVisitor}
instance.
* This is usually a static final {@code VISITORS} constant defined in the
subclass.
+ * May be {@code null} if this visitor does not use thread-local instances.
*/
- abstract ThreadLocal<? extends MetadataVisitor<?>> creator();
+ ThreadLocal<? extends MetadataVisitor<?>> creator() {
+ return null;
+ }
/**
* Sets the name of the method being visited. This is invoked by {@code
PropertyAccessor.walk} methods only.
@@ -108,6 +113,21 @@ abstract class MetadataVisitor<R> {
}
/**
+ * Returns the path to the currently visited property.
+ * Each element in the list is the UML identifier of a property.
+ * Element at index 0 is the name of the property of the root metadata
object being visited.
+ * Element at index 1 is the name of a property which is a children of
above property, <i>etc.</i>
+ *
+ * <p>The returned list is valid only during {@link #visit(Class, Object)}
method execution.
+ * The content of this list become undetermined after the {@code visit}
method returned.</p>
+ *
+ * @return the path to the currently visited property.
+ */
+ List<String> getCurrentPropertyPath() {
+ return UnmodifiableArrayList.wrap(propertyPath, 0, nestedCount);
+ }
+
+ /**
* Invokes {@link #visit(Class, Object)} for all elements of the given
metadata if that metadata has not
* already been visited. The computation result is returned (may be the
result of a previous computation).
*
@@ -127,8 +147,15 @@ abstract class MetadataVisitor<R> {
if (!visited.containsKey(metadata)) { // Reminder: the
associated value may be null.
final PropertyAccessor accessor = standard.getAccessor(new
CacheKey(metadata.getClass(), type), mandatory);
if (accessor != null) {
- final boolean write = preVisit(accessor.type);
- if (visited.put(metadata, null) != null) {
+ final Filter filter = preVisit(accessor);
+ final boolean preconstructed;
+ final R sentinel;
+ switch (filter) {
+ case NONE: return null;
+ case WRITABLE_RESULT: preconstructed = true; sentinel =
result(); break;
+ default: preconstructed = false; sentinel =
null; break;
+ }
+ if (visited.put(metadata, sentinel) != null) {
// Should never happen, unless this method is invoked
concurrently in another thread.
throw new ConcurrentModificationException();
}
@@ -146,10 +173,10 @@ abstract class MetadataVisitor<R> {
allowNull =
Semaphores.queryAndSet(Semaphores.NULL_COLLECTION);
}
try {
- if (write) {
- accessor.walkWritable(this, metadata);
- } else {
- accessor.walkReadable(this, metadata);
+ switch (filter) {
+ case NON_EMPTY: accessor.walkReadable(this,
metadata); break;
+ case WRITABLE: accessor.walkWritable(this,
metadata, metadata); break;
+ case WRITABLE_RESULT: accessor.walkWritable(this,
metadata, sentinel); break;
}
} catch (MetadataVisitorException e) {
throw e;
@@ -160,11 +187,12 @@ abstract class MetadataVisitor<R> {
if (!allowNull) {
Semaphores.clear(Semaphores.NULL_COLLECTION);
}
- creator().remove();
+ final ThreadLocal<? extends MetadataVisitor<?>>
creator = creator();
+ if (creator != null) creator.remove();
}
}
- final R result = result();
- if (visited.put(metadata, result) != null) {
+ final R result = preconstructed ? sentinel : result();
+ if (visited.put(metadata, result) != sentinel) {
throw new ConcurrentModificationException();
}
return result;
@@ -174,15 +202,43 @@ abstract class MetadataVisitor<R> {
}
/**
+ * Filter the properties to visit. A value of this enumeration is returned
by {@link #preVisit(PropertyAccessor)}
+ * before the properties of a metadata instance are visited.
+ */
+ enum Filter {
+ /**
+ * Do not visit any property (skip completely the metadata).
+ */
+ NONE,
+
+ /**
+ * Visit all non-null and non-empty standard properties.
+ */
+ NON_EMPTY,
+
+ /**
+ * Visit all writable properties. May include some non-standard
properties.
+ */
+ WRITABLE,
+
+ /**
+ * Same as {@link #WRITABLE}, but write properties in the object
returned by {@link #result()}.
+ * This mode implies that {@code result()} is invoked
<strong>before</strong> metadata properties
+ * are visited instead than after.
+ */
+ WRITABLE_RESULT
+ }
+
+ /**
* Invoked when a new metadata is about to be visited. After this method
has been invoked,
* {@link #visit(Class, Object)} will be invoked for each property in the
metadata object.
*
- * @param type the standard interface implemented by the metadata
instance being visited.
- * @return {@code true} for visiting only writable properties, or
- * {@code false} for visiting all readable properties.
+ * @param accessor information about the standard interface and
implementation of the metadata being visited.
+ * @return most common values are {@code NON_EMPTY} for visiting all
non-empty properties (the default),
+ * or {@code WRITABLE} for visiting only writable properties.
*/
- boolean preVisit(Class<?> type) {
- return false;
+ Filter preVisit(PropertyAccessor accessor) {
+ return Filter.NON_EMPTY;
}
/**
@@ -210,8 +266,11 @@ abstract class MetadataVisitor<R> {
/**
* Returns the result of visiting all elements in a metadata instance.
- * This method is invoked after all metadata properties have been visited,
- * or after a {@link #visit(Class, Object)} method call returned {@link
#SKIP_SIBLINGS}.
+ * This method is invoked exactly once per metadata instance.
+ * It is usually invoked after all metadata properties have been visited
+ * (or after a {@link #visit(Class, Object)} method call returned {@link
#SKIP_SIBLINGS}),
+ * unless {@link #preVisit(PropertyAccessor)} returned {@link
Filter#WRITABLE_RESULT}
+ * in which case this method is invoked <strong>before</strong> metadata
properties are visited.
* The value returned by this method will be cached in case the same
metadata instance is revisited again.
*/
abstract R result();
diff --git
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
index 039c427..dc5799c 100644
---
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
+++
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
@@ -38,15 +38,20 @@ import static
org.apache.sis.util.collection.Containers.isNullOrEmpty;
/**
- * Provides convenience methods for support of modifiable properties in
metadata implementations.
- * Implementations typically provide {@code set*(…)} methods for each
corresponding {@code get*()}
- * method. Subclasses can follow the pattern below for every {@code get} and
{@code set} methods,
- * with a different processing for singleton value or for {@linkplain
Collection collections}.
+ * Base class of metadata having an editable content.
+ * Newly created {@code ModifiableMetadata} are initially in {@linkplain
State#EDITABLE editable} state.
+ * The metadata can be populated using the setter methods provided by
subclasses, then transition to the
+ * {@linkplain State#FINAL final} state for making it safe to share by many
consumers.
*
- * <p>For singleton value:</p>
+ * <div class="section">Tip for subclass implementations</div>
+ * Subclasses can follow the pattern below for every {@code get} and {@code
set} methods,
+ * with a different processing for singleton value or for {@linkplain
Collection collections}.
*
* {@preformat java
* public class MyMetadata {
+ *
+ * // ==== Example for a singleton value =============================
+ *
* private Foo property;
*
* public Foo getProperty() {
@@ -57,13 +62,9 @@ import static
org.apache.sis.util.collection.Containers.isNullOrEmpty;
* checkWritePermission();
* property = newValue;
* }
- * }
- * }
*
- * For collections (note that the call to {@link #checkWritePermission()} is
implicit):
+ * // ==== Example for a collection ==================================
*
- * {@preformat java
- * public class MyMetadata {
* private Collection<Foo> properties;
*
* public Collection<Foo> getProperties() {
@@ -71,14 +72,12 @@ import static
org.apache.sis.util.collection.Containers.isNullOrEmpty;
* }
*
* public void setProperties(Collection<Foo> newValues) {
+ * // the call to checkWritePermission() is implicit
* properties = writeCollection(newValues, properties, Foo.class);
* }
* }
* }
*
- * An initially modifiable metadata may become unmodifiable at a later stage
- * (typically after its construction is completed) by the call to {@code
apply(State.FINAL)}.
- *
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 0.3
diff --git
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
index b2d67f0..76f0849 100644
---
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
+++
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
@@ -64,7 +64,7 @@ import static
org.apache.sis.util.collection.Containers.hashMapCapacity;
* <ul>
* <li>The standard properties defined by the GeoAPI (or other standard)
interfaces.
* Those properties are the only ones accessible by most methods in this
class, except
- * {@link #equals(Object, Object, ComparisonMode)} and {@link
#walkWritable(MetadataVisitor, Object)}.</li>
+ * {@link #equals(Object, Object, ComparisonMode)} and {@link
#walkWritable(MetadataVisitor, Object, Object)}.</li>
*
* <li>Extra properties defined by the {@link IdentifiedObject} interface.
Those properties
* invisible in the ISO 19115-1 model, but appears in ISO 19115-3 XML
marshalling. So we
@@ -667,6 +667,13 @@ class PropertyAccessor {
}
/**
+ * Returns {@code true} if the {@link #implementation} class has at least
one setter method.
+ */
+ final boolean isWritable() {
+ return setters != null;
+ }
+
+ /**
* Returns {@code true} if the property at the given index is writable.
*/
final boolean isWritable(final int index) {
@@ -1185,41 +1192,6 @@ class PropertyAccessor {
}
/**
- * Returns a potentially deep copy of the given metadata object.
- *
- * @param metadata the metadata object to copy.
- * @param copier contains a map of metadata objects already copied.
- * @return a copy of the given metadata object, or {@code metadata} itself
if there is
- * no known implementation class or that implementation has no
setter method.
- * @throws ReflectiveOperationException if an error occurred while
creating the copy.
- */
- final Object copy(final Object metadata, final MetadataCopier copier)
throws ReflectiveOperationException {
- if (setters == null) {
- return metadata;
- }
- Object copy = copier.copies.get(metadata);
- if (copy == null) {
- copy = implementation.getConstructor().newInstance();
- copier.copies.put(metadata, copy); // Need to be
first in case of cyclic graphs.
- final Object[] arguments = new Object[1];
- for (int i=0; i<allCount; i++) {
- final Method setter = setters[i];
- if (setter != null &&
!setter.isAnnotationPresent(Deprecated.class)) {
- Object value = get(getters[i], metadata);
- if (value != null) {
- value = copier.copyAny(elementTypes[i], value);
- if (value != null) {
- arguments[0] = value;
- set(setter, copy, arguments);
- }
- }
- }
- }
- }
- return copy;
- }
-
- /**
* Invokes {@link MetadataVisitor#visit(Class, Object)} for all non-null
properties in the given metadata.
* This method is not recursive, i.e. it does not traverse the children of
the elements in the given metadata.
*
@@ -1246,13 +1218,18 @@ class PropertyAccessor {
* Invokes {@link MetadataVisitor#visit(Class, Object)} for all writable
properties in the given metadata.
* This method is not recursive, i.e. it does not traverse the children of
the elements in the given metadata.
*
+ * <p><b>Constraint:</b> in current implementation, if {@code source} and
{@code target} are not the same,
+ * then {@code target} is assumed empty. The intent is to skip easily null
or empty properties.</p>
+ *
* @param visitor the object on which to invoke {@link
MetadataVisitor#visit(Class, Object)}.
- * @param metadata the metadata instance for which to visit the writable
properties.
+ * @param source the metadata from which to read properties. May be
the same than {@code target}.
+ * @param target the metadata instance where to write properties.
* @throws Exception if an error occurred while visiting a property.
*/
- final void walkWritable(final MetadataVisitor<?> visitor, final Object
metadata) throws Exception {
- assert type.isInstance(metadata) : metadata;
- if (setters == null || !implementation.isInstance(metadata)) {
+ final void walkWritable(final MetadataVisitor<?> visitor, final Object
source, final Object target) throws Exception {
+ assert type.isInstance(source) : source;
+ assert type.isInstance(target) : target;
+ if (setters == null || !implementation.isInstance(target)) {
return;
}
final Object[] arguments = new Object[1];
@@ -1273,12 +1250,12 @@ class PropertyAccessor {
*/
continue;
}
- final Object value = get(getters[i], metadata);
+ final Object value = get(getters[i], source);
final Object result = visitor.visit(elementTypes[i], value);
- if (result != value) {
+ if (source == target ? (result != value) :
!isNullOrEmpty(result)) { // See "constraint" in Javadoc
if (result == MetadataVisitor.SKIP_SIBLINGS) break;
arguments[0] = result;
- set(setter, metadata, arguments);
+ set(setter, target, arguments);
/*
* We invoke the set(…) method variant that do not perform
type conversion
* because we do not want it to replace the immutable
collections created
diff --git
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
index f29ed4d..b2fbc85 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
@@ -90,17 +90,16 @@ final class Pruner extends MetadataVisitor<Boolean> {
/**
* Marks a metadata instance as empty before we start visiting its
non-null properties.
- * If the metadata does not contain any property, then the {@link
#isEmpty} field will
- * stay {@code true}.
+ * If the metadata does not contain any property, then the {@link
#isEmpty} field will stay {@code true}.
*
- * @return {@code false} since this visitor is not restricted to writable
properties.
- * We need to visit all readable properties even for pruning
operation since
- * we need to determine if the metadata is empty.
+ * @return {@link Filter#NON_EMPTY} since this visitor is not restricted
to writable properties.
+ * We need to visit all readable properties even for pruning
operation since we need to
+ * determine if the metadata is empty.
*/
@Override
- boolean preVisit(final Class<?> type) {
+ Filter preVisit(final PropertyAccessor accessor) {
isEmpty = true;
- return false;
+ return Filter.NON_EMPTY;
}
/**
diff --git
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/StateChanger.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/StateChanger.java
index 18381f5..8c25e3a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/StateChanger.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/StateChanger.java
@@ -95,12 +95,12 @@ final class StateChanger extends MetadataVisitor<Boolean> {
/**
* Notifies {@link MetadataVisitor} that we want to visit all writable
properties.
*
- * @param type ignored.
- * @return {@code true}, for iterating over all writable properties.
+ * @param accessor ignored.
+ * @return {@link Filter#WRITABLE}, for iterating over all writable
properties.
*/
@Override
- boolean preVisit(final Class<?> type) {
- return true;
+ Filter preVisit(final PropertyAccessor accessor) {
+ return Filter.WRITABLE;
}
/**