sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/03: Replace ModifiableMetadata.isModifiable(), unmodifiable() and freeze() by an enumeration. https://issues.apache.org/jira/browse/SIS-81
Date Sat, 30 Jun 2018 15:42:36 GMT
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

commit 263f004669efd5f5f229bad10b7013350a01f9cd
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Jun 30 17:23:58 2018 +0200

    Replace ModifiableMetadata.isModifiable(), unmodifiable() and freeze() by an enumeration.
    https://issues.apache.org/jira/browse/SIS-81
---
 .../main/java/org/apache/sis/metadata/Freezer.java |  16 +-
 .../org/apache/sis/metadata/MetadataCopier.java    |  81 +++----
 .../org/apache/sis/metadata/MetadataStandard.java  |   2 -
 .../apache/sis/metadata/ModifiableMetadata.java    | 243 ++++++++++++++++-----
 .../org/apache/sis/metadata/PropertyAccessor.java  |   4 +-
 .../metadata/UnmodifiableMetadataException.java    |   5 +-
 .../org/apache/sis/metadata/iso/ISOMetadata.java   |  15 +-
 .../sis/metadata/iso/MetadataScopeAdapter.java     |   4 +-
 .../metadata/iso/citation/DefaultTelephone.java    |   2 +-
 .../sis/metadata/iso/lineage/DefaultSource.java    |   2 +-
 .../metadata/iso/citation/DefaultCitationTest.java |  54 ++++-
 .../DefaultRepresentativeFractionTest.java         |   8 +-
 ide-project/NetBeans/nbproject/genfiles.properties |   2 +-
 ide-project/NetBeans/nbproject/project.xml         |   1 +
 14 files changed, 311 insertions(+), 128 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Freezer.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Freezer.java
index a3621b2..3a38883 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Freezer.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Freezer.java
@@ -41,8 +41,8 @@ import org.apache.sis.metadata.iso.identification.DefaultRepresentativeFraction;
  */
 final class Freezer extends MetadataVisitor<Boolean> {
     /**
-     * The {@code Freezer} instance in current use. The clean way would have been to pass
the {@code Freezer}
-     * instance in argument to all {@code freeze()} and {@code unmodifiable()} methods in
metadata packages.
+     * The {@code Freezer} instance in current use. The clean way would have been to pass
the
+     * instance in argument to all {@code apply(State.FINAL)} methods in metadata packages.
      * But above-cited methods are public, and we do not want to expose {@code Freezer} in
public API for now.
      * This thread-local is a workaround for that situation.
      */
@@ -136,16 +136,16 @@ final class Freezer extends MetadataVisitor<Boolean> {
     @Override
     final Object visit(final Class<?> type, final Object object) throws CloneNotSupportedException
{
         /*
-         * CASE 1 - The object is an org.apache.sis.metadata.* implementation. It may have
-         *          its own algorithm for creating an unmodifiable view of metadata.
+         * CASE 1 - The object is an org.apache.sis.metadata.* implementation.
+         *          It may have its own algorithm for freezing itself.
          */
         if (object instanceof ModifiableMetadata) {
-            return unique(((ModifiableMetadata) object).unmodifiable());
+            ((ModifiableMetadata) object).freeze();
+            return unique(object);
         }
         if (object instanceof DefaultRepresentativeFraction) {
-            final DefaultRepresentativeFraction c = ((DefaultRepresentativeFraction) object).clone();
-            c.freeze();
-            return unique(c);
+            ((DefaultRepresentativeFraction) object).freeze();
+            return unique(object);
         }
         /*
          * CASE 2 - The object is a collection. All elements are replaced by their
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 a7e8a28..88da1c2 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
@@ -24,6 +24,7 @@ import java.util.LinkedHashMap;
 import java.util.IdentityHashMap;
 import java.util.Arrays;
 import java.util.Collection;
+import java.lang.reflect.Constructor;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.resources.Errors;
@@ -31,61 +32,35 @@ import org.apache.sis.util.collection.CodeListSet;
 
 
 /**
- * Performs deep copies of given metadata instances. This class performs a <em>copies</em>,
not clones,
+ * Performs deep copies of given metadata instances. This class performs <em>copies</em>,
not clones,
  * since the copied metadata may not be instances of the same class than the original metadata.
  * This class performs the following steps:
  *
  * <ul>
  *   <li>Get the {@linkplain MetadataStandard#getImplementation implementation class}
of the given metadata instance.</li>
- *   <li>Create a {@linkplain Class#newInstance() new instance} of the implementation
class using the public no-argument constructor.</li>
+ *   <li>Create a {@linkplain Constructor#newInstance new instance} of the implementation
class using the public no-argument constructor.</li>
  *   <li>Invoke all non-deprecated setter methods on the new instance with the corresponding
value from the given metadata.</li>
  *   <li>If any of the values copied in above step is itself a metadata, recursively
performs deep copy on those metadata instances too.</li>
  * </ul>
  *
- * This class supports cyclic graphs in the metadata tree. It may return the given {@code
metadata} object directly
- * if the {@linkplain MetadataStandard#getImplementation implementation class} does not provide
any setter method.
+ * This copier may be used for converting metadata tree of unknown implementations (for example
the result of a call to
+ * {@link org.apache.sis.metadata.sql.MetadataSource#lookup(Class, String)}) into instances
of {@link AbstractMetadata}.
+ * The copier may also be used if a {@linkplain ModifiableMetadata.State#EDITABLE modifiable}
metadata is desired after
+ * the original metadata has been made {@linkplain ModifiableMetadata.State#FINAL final}.
  *
- * <p>This class is not thread-safe.
- * In multi-threads environment, each thread should use its own {@code MetadataCopier} instance.</p>
- *
- * <div class="note"><b>Recommended alternative:</b>
- * deep metadata copies are sometime useful when using an existing metadata as a template.
- * But the {@link ModifiableMetadata#unmodifiable()} method may provide a better way to use
a metadata as a template,
- * as it returns a snapshot and allows the caller to continue to modify the original metadata
object and create new
- * snapshots. Example:
- *
- * {@preformat java
- *   // Prepare a Citation to be used as a template.
- *   DefaultCitation citation = new DefaultCitation();
- *   citation.getCitedResponsibleParties(someAuthor);
- *
- *   // Set the title and get a first snapshot.
- *   citation.setTitle(new SimpleInternationalString("A title"));
- *   Citation myFirstCitation = (Citation) citation.unmodifiable();
+ * <p>Default implementation copies all copiable children, regardless their {@linkplain
ModifiableMetadata#state() state}.
+ * Static factory methods allow to construct some variants, for example skipping the copy
of unmodifiable metadata instances
+ * since they can be safely shared.</p>
  *
- *   // Change the title and get another snapshot.
- *   citation.setTitle(new SimpleInternationalString("Another title"));
- *   Citation mySecondCitation = (Citation) citation.unmodifiable();
- * }
+ * <p>This class supports cyclic graphs in the metadata tree. It may return the given
{@code metadata} object directly
+ * if the {@linkplain MetadataStandard#getImplementation implementation class} does not provide
any setter method.</p>
  *
- * This approach allows sharing the children that have the same content, thus reducing memory
usage. In above example,
- * the {@code someAuthor} {@linkplain org.apache.sis.metadata.iso.citation.DefaultCitation#getCitedResponsibleParties()
- * cited responsible party} is the same instance in both citations. In comparison, deep copy
operations unconditionally
- * duplicate everything, no matter if it was needed or not. Nevertheless deep copies are
still sometime useful,
- * for example when we do not have the original {@link ModifiableMetadata} instance anymore.
- *
- * <p>{@code MetadataCopier} is also useful for converting a metadata tree of unknown
implementations (for example the
- * result of a call to {@link org.apache.sis.metadata.sql.MetadataSource#lookup(Class, String)})
into instances of the
- * public {@link AbstractMetadata} subclasses. But note that shallow copies as provided by
the {@code castOrCopy(…)}
- * static methods in each {@code AbstractMetadata} subclass are sometime sufficient.</p>
- * </div>
+ * <p>This class is not thread-safe.
+ * In multi-threads environment, each thread should use its own {@code MetadataCopier} instance.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
- *
- * @see ModifiableMetadata#unmodifiable()
- *
- * @since 0.8
+ * @since   0.8
  * @module
 */
 public class MetadataCopier {
@@ -113,6 +88,32 @@ public class MetadataCopier {
     }
 
     /**
+     * Creates a new metadata copier which avoid copying unmodifiable metadata.
+     * More specifically, any {@link ModifiableMetadata} instance in
+     * {@linkplain ModifiableMetadata.State#FINAL final state} will be kept <i>as-is</i>;
+     * those final metadata will not be copied since they can be safely shared.
+     *
+     * @param  standard  the default metadata standard to use for object that are not {@link
AbstractMetadata} instances,
+     *                   or {@code null} if none.
+     * @return a metadata copier which skip the copy of unmodifiable metadata.
+     *
+     * @since 1.0
+     */
+    public static MetadataCopier forModifiable(final MetadataStandard standard) {
+        return new MetadataCopier(standard) {
+            @Override protected Object copyRecursively(final Class<?> type, final Object
metadata) {
+                if (metadata instanceof ModifiableMetadata) {
+                    final ModifiableMetadata.State state = ((ModifiableMetadata) metadata).state();
+                    if (state == ModifiableMetadata.State.FINAL) {
+                        return metadata;
+                    }
+                }
+                return super.copyRecursively(type, metadata);
+            }
+        };
+    }
+
+    /**
      * Performs a potentially deep copy of a metadata object of unknown type.
      * The return value does not need to be of the same class than the argument.
      *
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
index 711bc2f..b4d3c40 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
@@ -922,8 +922,6 @@ public class MetadataStandard implements Serializable {
      *
      * @throws ClassCastException if the specified implementation class do
      *         not implements a metadata interface of the expected package.
-     *
-     * @see ModifiableMetadata#freeze()
      */
     final void freeze(final Object metadata) throws ClassCastException {
         if (metadata != null) {
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 eeaa56f..95b1802 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
@@ -77,7 +77,7 @@ import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
  * }
  *
  * An initially modifiable metadata may become unmodifiable at a later stage
- * (typically after its construction is completed) by the call to the {@link #freeze()} method.
+ * (typically after its construction is completed) by the call to {@code apply(State.FINAL)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
@@ -93,36 +93,181 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
     private static final int INITIAL_CAPACITY = 4;
 
     /**
-     * A null implementation for the {@link #FREEZING} constant.
+     * The {@link #state} value meaning that the metadata is modifiable.
+     * This is the default state when new {@link ModifiableMetadata} instances are created.
      */
-    @SuppressWarnings("CloneableClassWithoutClone")
-    private static final class Null extends ModifiableMetadata {
-        @Override public MetadataStandard getStandard() {
-            return null;
-        }
-    }
+    private static final byte EDITABLE = 0;
+
+    /**
+     * A bitmask for {@link #state} meaning that {@code apply(State.FINAL)} has been invoked.
+     */
+    private static final byte FINAL = 1;
 
     /**
-     * A sentinel value used for {@link #unmodifiable} in order to specify that {@link #freeze()}
is under way.
+     * See https://issues.apache.org/jira/browse/SIS-81 - not yet committed.
      */
-    private static final ModifiableMetadata FREEZING = new Null();
+    private static final byte STAGED = 2;
+
+    /**
+     * A value for {@link #state} meaning that execution of {@code apply(State.FINAL)} is
in progress.
+     */
+    private static final byte FREEZING = FINAL | STAGED;
+
+    /**
+     * Whether this metadata has been made unmodifiable, as one of {@link #EDITABLE}, {@link
#FREEZING}
+     * or {@link #FINAL} values.
+     *
+     * <p>This field is not yet serialized because we are not sure to keep this information
as a byte in
+     * the future. We could for example use an {@code int} and use remaining bits for caching
hash-code
+     * value of final metadata.</p>
+     */
+    private transient byte state;
 
     /**
      * An unmodifiable copy of this metadata, created only when first needed.
      * If {@code null}, then no unmodifiable entity is available.
      * If {@code this}, then this entity is itself unmodifiable.
      *
-     * @see #unmodifiable()
+     * @deprecated to be deleted after the removal of {@link #unmodifiable()}.
      */
+    @Deprecated
     private transient ModifiableMetadata unmodifiable;
 
     /**
      * Constructs an initially empty metadata.
+     * The initial state is {@link State#EDITABLE}.
      */
     protected ModifiableMetadata() {
     }
 
     /**
+     * Whether the metadata is still editable or has been made final.
+     * New {@link ModifiableMetadata} instances are initially {@link #EDITABLE}
+     * and can be made {@link #FINAL} after construction by a call to {@link ModifiableMetadata#apply(State)}.
+     *
+     * <div class="note"><b>Note:</b>
+     * more states may be added in future Apache SIS versions. On possible candidate is {@code
STAGED}.
+     * See <a href="https://issues.apache.org/jira/browse/SIS-81">SIS-81</a>.</div>
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @version 1.0
+     * @since   1.0
+     * @module
+     */
+    public enum State {
+        /**
+         * The metadata is modifiable.
+         * This is the default state when new {@link ModifiableMetadata} instances are created.
+         * Note that a modifiable metadata instance does <strong>not</strong>
imply that all
+         * properties contained in that instance are also editable.
+         */
+        EDITABLE,
+
+        /**
+         * The metadata is unmodifiable.
+         * When a metadata is final, it can not be moved back to an editable state
+         * (but it is still possible to create a modifiable copy with {@link MetadataCopier}).
+         * Invoking any setter method on an unmodifiable metadata cause an
+         * {@link UnmodifiableMetadataException} to be thrown.
+         */
+        FINAL;
+
+        /**
+         * Mapping from {@link ModifiableMetadata} private flags to {@code State} enumeration.
+         * A mapping exists because {@code ModifiableMetadata} does not use the same set
of enumeration values
+         * (e.g. it has an internal {@link #FREEZING} value), and because future versions
may use a bitmask.
+         */
+        private static final State[] VALUES = new State[ModifiableMetadata.FREEZING + 1];
+        static {
+            VALUES[ModifiableMetadata.EDITABLE] = EDITABLE;
+            VALUES[ModifiableMetadata.STAGED]   = EDITABLE;
+            VALUES[ModifiableMetadata.FREEZING] = FINAL;
+            VALUES[ModifiableMetadata.FINAL]    = FINAL;
+        }
+    }
+
+    /**
+     * Tells whether this instance of metadata is editable.
+     * This is initially {@link State#EDITABLE} for new {@code ModifiableMetadata} instances,
+     * but can be changed by a call to {@link #apply(State)}.
+     *
+     * <p>{@link State#FINAL} implies that all properties are also final.
+     * This recursivity does not necessarily apply to other states. For example {@link State#EDITABLE}
+     * does <strong>not</strong> imply that all {@code ModifiableMetadata} children
are also editable.</p>
+     *
+     * <div class="note"><b>API note:</b>
+     * the {@code ModifiableMetadata} state is not a metadata per se, but rather an information
about
+     * this particular instance of a metadata class. Two metadata instances may be in different
states
+     * but still have the same metadata content. For this reason, this method does not have
{@code get}
+     * prefix for avoiding confusion with getter and setter methods of metadata properties.</div>
+     *
+     * @return the state (editable or final) of this {@code ModifiableMetadata} instance.
+     *
+     * @since 1.0
+     */
+    public State state() {
+        return State.VALUES[state];
+    }
+
+    /**
+     * Applies a state transition on this metadata instance and (potentially) all its children.
+     * The action performed by this method depends on the {@linkplain #state() current state},
+     * as listed in the following table:
+     *
+     * <table class="sis">
+     *   <caption>State transitions</caption>
+     *   <tr>
+     *     <th>Current state</th>
+     *     <th>Target state</th>
+     *     <th>Action</th>
+     *   </tr><tr>
+     *     <td><var>Any</var></td>
+     *     <td><var>Same</var></td>
+     *     <td>Does nothing and returns {@code false}.</td>
+     *   </tr><tr>
+     *     <td>{@link State#EDITABLE}</td>
+     *     <td>{@link State#FINAL}</td>
+     *     <td>Marks this metadata and all children as unmodifiable.</td>
+     *   </tr><tr>
+     *     <td>{@link State#FINAL}</td>
+     *     <td>{@link State#EDITABLE}</td>
+     *     <td>Throws {@link UnmodifiableMetadataException}.</td>
+     *   </tr>
+     * </table>
+     *
+     * The effect of invoking this method may be recursive. For example transitioning to
{@link State#FINAL}
+     * implies transitioning all children {@code ModifiableMetadata} instances to the final
state too.
+     *
+     * @param  target  the desired new state.
+     * @return {@code true} if the state of this {@code ModifiableMetadata} changed as a
result of this method call.
+     * @throws UnmodifiableMetadataException if a transition from {@link State#FINAL} to
{@link State#EDITABLE} was attempted.
+     *
+     * @since 1.0
+     */
+    public boolean apply(final State target) {
+        switch (target) {
+            case EDITABLE: {
+                if ((state & FINAL) == 0) break;
+                throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata));
+            }
+            case FINAL: {
+                if ((state & FINAL) != 0) break;
+                final MetadataStandard standard = getStandard();
+                byte result = state;
+                try {
+                    state = FREEZING;
+                    standard.freeze(this);
+                    result = FINAL;
+                } finally {
+                    state = result;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Returns {@code true} if this metadata is modifiable. This method returns
      * {@code false} if {@link #freeze()} has been invoked on this object.
      *
@@ -130,9 +275,13 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
      *
      * @see #freeze()
      * @see #checkWritePermission()
+     *
+     * @deprecated Replaced by <code>{@linkplain #state()} != State.FINAL</code>.
+     *             See <a href="https://issues.apache.org/jira/browse/SIS-81">SIS-81</a>.
      */
+    @Deprecated
     public final boolean isModifiable() {
-        return unmodifiable != this && unmodifiable != FREEZING;
+        return (state & FINAL) == 0;
     }
 
     /**
@@ -165,29 +314,22 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
      * @return an unmodifiable copy of this metadata.
      *
      * @see MetadataCopier
+     *
+     * @deprecated Replaced by {@code MetadataCopier.forModifiable(getStandard()).copy(this).apply(State.FINAL)}.
      */
+    @Deprecated
     public AbstractMetadata unmodifiable() {
+        if ((state & FINAL) != 0) {
+            unmodifiable = this;
+        }
         /*
          * The 'unmodifiable' field is reset to null by checkWritePermission().
          * However this is not sufficient since the setter method of some child
          * could have been invoked without invoking any setter method on 'this'.
          * So we also need to perform an equality check.
          */
-        if (unmodifiable == null || (unmodifiable != this && unmodifiable != FREEZING
&& !equals(unmodifiable))) {
-            final ModifiableMetadata candidate;
-            try {
-                /*
-                 * Need a SHALLOW copy of this metadata, because some properties
-                 * may already be unmodifiable and we don't want to clone them.
-                 */
-                candidate = clone();
-            } catch (CloneNotSupportedException exception) {
-                /*
-                 * The metadata is not cloneable for some reason left to the user
-                 * (for example it may be backed by some external database).
-                 */
-                throw new UnsupportedOperationException(exception);
-            }
+        if (unmodifiable == null || !equals(unmodifiable)) {
+            final ModifiableMetadata candidate = (ModifiableMetadata) MetadataCopier.forModifiable(getStandard()).copy(this);
             candidate.freeze();
             /*
              * Set the field only after success. The 'unmodifiable' field must
@@ -207,20 +349,14 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
      * <p>Subclasses usually do not need to override this method since the default
implementation
      * performs its work using Java reflection.</p>
      *
-     * @see #isModifiable()
+     * @see #state()
      * @see #checkWritePermission()
+     *
+     * @deprecated Replaced by {@code apply(State.FINAL)}.
      */
+    @Deprecated
     public void freeze() {
-        if (isModifiable()) {
-            ModifiableMetadata success = null;
-            try {
-                unmodifiable = FREEZING;
-                getStandard().freeze(this);
-                success = this;
-            } finally {
-                unmodifiable = success;
-            }
-        }
+        apply(State.FINAL);
     }
 
     /**
@@ -229,16 +365,13 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
      *
      * @throws UnmodifiableMetadataException if this metadata is unmodifiable.
      *
-     * @see #isModifiable()
-     * @see #freeze()
+     * @see #state()
      */
     protected void checkWritePermission() throws UnmodifiableMetadataException {
-        if (unmodifiable != null) {
-            if (unmodifiable == this) {
-                throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata));
-            } else if (unmodifiable != FREEZING) {
-                unmodifiable = null;                    // Discard since this metadata is
going to change.
-            }
+        if (state == FINAL) {
+            throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata));
+        } else {
+            unmodifiable = null;                    // Discard since this metadata is going
to change.
         }
     }
 
@@ -271,7 +404,7 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
     {
         // See the comments in writeCollection(…) for implementation notes.
         if (source != target) {
-            if (unmodifiable == FREEZING) {
+            if (state == FREEZING) {
                 return (List<E>) source;
             }
             checkWritePermission();
@@ -318,7 +451,7 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
     {
         // See the comments in writeCollection(…) for implementation notes.
         if (source != target) {
-            if (unmodifiable == FREEZING) {
+            if (state == FREEZING) {
                 return (Set<E>) source;
             }
             checkWritePermission();
@@ -376,9 +509,9 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
          * and JAXB unmarshalling.
          */
         if (source != target) {
-            if (unmodifiable == FREEZING) {
+            if (state == FREEZING) {
                 /*
-                 * freeze() method is under progress. The source collection is already
+                 * apply(State.FINAL) is under progress. The source collection is already
                  * an unmodifiable instance created by Freezer.clone(Object).
                  */
                 assert collectionType(elementType).isInstance(source);
@@ -527,7 +660,7 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
         if (emptyCollectionAsNull()) {
             return null;
         }
-        if (isModifiable()) {
+        if ((state & FINAL) == 0) {
             /*
              * Do not specify an initial capacity, because the list will stay empty in a
majority of cases
              * (i.e. the users will want to iterate over the list elements more often than
they will want
@@ -556,7 +689,7 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
         if (emptyCollectionAsNull()) {
             return null;
         }
-        if (isModifiable()) {
+        if ((state & FINAL) == 0) {
             return createSet(elementType, INITIAL_CAPACITY);
         }
         return Collections.emptySet();
@@ -586,7 +719,7 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
         if (emptyCollectionAsNull()) {
             return null;
         }
-        final boolean isModifiable = isModifiable();
+        final boolean isModifiable = (state & FINAL) == 0;
         if (useSet(elementType)) {
             if (isModifiable) {
                 return createSet(elementType, INITIAL_CAPACITY);
@@ -679,8 +812,12 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
      *
      * @see #unmodifiable()
      * @see MetadataCopier
+     *
+     * @deprecated Apache SIS 1.0 no longer use this mechanism. SIS 1.1 will make the standard
+     *             {@link Object#clone()} available for subclasses at their implementation
choice.
      */
     @Override
+    @Deprecated
     protected ModifiableMetadata clone() throws CloneNotSupportedException {
         return (ModifiableMetadata) super.clone();
     }
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 33ed5f8..b2d67f0 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
@@ -1251,8 +1251,8 @@ class PropertyAccessor {
      * @throws Exception if an error occurred while visiting a property.
      */
     final void walkWritable(final MetadataVisitor<?> visitor, final Object metadata)
throws Exception {
-        assert implementation.isInstance(metadata) : metadata;
-        if (setters == null) {
+        assert type.isInstance(metadata) : metadata;
+        if (setters == null || !implementation.isInstance(metadata)) {
             return;
         }
         final Object[] arguments = new Object[1];
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/UnmodifiableMetadataException.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/UnmodifiableMetadataException.java
index 1583dff..5e9af2c 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/UnmodifiableMetadataException.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/UnmodifiableMetadataException.java
@@ -30,7 +30,10 @@ package org.apache.sis.metadata;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 0.3
- * @since   0.3
+ *
+ * @see org.apache.sis.metadata.ModifiableMetadata.State#FINAL
+ *
+ * @since 0.3
  * @module
  */
 public class UnmodifiableMetadataException extends UnsupportedOperationException {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
index cbcea74..6ed6a40 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
@@ -54,7 +54,7 @@ import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -161,8 +161,8 @@ public class ISOMetadata extends ModifiableMetadata implements IdentifiedObject,
          * We do not cache (for now) the IdentifierMap because it is cheap to create, and
if we were
          * caching it we would need anyway to check if 'identifiers' still references the
same list.
          */
-        return isModifiable() ? new ModifiableIdentifierMap(identifiers)
-                              : new IdentifierMapAdapter(identifiers);
+        return (super.state() != State.FINAL) ? new ModifiableIdentifierMap(identifiers)
+                                              : new IdentifierMapAdapter(identifiers);
     }
 
     // --------------------------------------------------------------------------------------
@@ -173,10 +173,10 @@ public class ISOMetadata extends ModifiableMetadata implements IdentifiedObject,
      * {@inheritDoc}
      */
     @Override
-    public void freeze() {
-        if (isModifiable()) {
-            final Collection<Identifier> p = identifiers;
-            super.freeze();
+    public boolean apply(final State target) {
+        final Collection<Identifier> p = identifiers;
+        final boolean changed = super.apply(target);
+        if (changed) {
             /*
              * The 'identifiers' collection will have been replaced by an unmodifiable collection
if
              * subclass has an "identifiers" property. If this is not the case, then the
collection
@@ -186,6 +186,7 @@ public class ISOMetadata extends ModifiableMetadata implements IdentifiedObject,
                 identifiers = CollectionsExt.unmodifiableOrCopy(p);                     //
Null safe.
             }
         }
+        return changed;
     }
 
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
index cadd60b..66889d0 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
@@ -64,7 +64,9 @@ abstract class MetadataScopeAdapter<L> extends LegacyPropertyAdapter<L,MetadataS
              * But if the metadata is not modifiable, then we will need to clone it and replaces
the element in
              * the collection.
              */
-            if (!(scope instanceof DefaultMetadataScope) || !((DefaultMetadataScope) scope).isModifiable())
{
+            if (!(scope instanceof DefaultMetadataScope) ||
+                    ((DefaultMetadataScope) scope).state() == DefaultMetadataScope.State.FINAL)
+            {
                 scope = new DefaultMetadataScope(scope);
                 if (elements instanceof List<?>) {
                     ((List<MetadataScope>) elements).set(n, scope);
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
index 1cec7aa..b02438f 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultTelephone.java
@@ -256,7 +256,7 @@ public class DefaultTelephone extends ISOMetadata implements Telephone
{
      */
     final Collection<Telephone> getOwner() {
        if (owner == null) {
-           if (isModifiable()) {
+           if (super.state() != State.FINAL) {
                owner = new ArrayList<>(4);
                owner.add(this);
            } else {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
index de8c25e..e47b5af 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/lineage/DefaultSource.java
@@ -428,7 +428,7 @@ public class DefaultSource extends ISOMetadata implements Source {
             Scope scope = getScope();
             if (scope != null) {
                 if (!(scope instanceof DefaultScope)) {
-                    if (isModifiable()) {
+                    if (super.state() != State.FINAL) {
                         scope = new DefaultScope(scope);
                         this.scope = scope;
                     } else {
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
index 82149a8..ddf5708 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
@@ -36,6 +36,9 @@ import org.opengis.metadata.citation.PresentationForm;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.xml.IdentifierMap;
 import org.apache.sis.xml.IdentifierSpace;
+import org.apache.sis.metadata.MetadataCopier;
+import org.apache.sis.metadata.MetadataStandard;
+import org.apache.sis.metadata.UnmodifiableMetadataException;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.metadata.xml.TestUsingFile;
@@ -124,18 +127,57 @@ public final strictfp class DefaultCitationTest extends TestUsingFile
{
     }
 
     /**
-     * Tests {@link DefaultCitation#freeze()}, which is needed for the constants defined
in {@link Citations}.
+     * Tests {@link DefaultCitation#freeze()}.
      */
     @Test
     public void testFreeze() {
         final DefaultCitation original = create();
+        final DefaultCitation clone = create();
+        clone.freeze();
+        assertEquals("original.state", DefaultCitation.State.EDITABLE, original.state());
+        assertEquals("clone.state",    DefaultCitation.State.FINAL,    clone.state());
+        assertEquals(original, clone);
+        SimpleInternationalString title = new SimpleInternationalString("Undercurrent");
+        original.setTitle(title);
+        try {
+            clone.setTitle(title);
+            fail("Frozen metadata shall not be modifiable.");
+        } catch (UnmodifiableMetadataException e) {
+            // This is the expected exception.
+        }
+    }
+
+    /**
+     * Tests {@link MetadataCopier} on a citation.
+     */
+    public void testCopy() {
+        final DefaultCitation original = create();
+        final DefaultCitation clone = (DefaultCitation) new MetadataCopier(MetadataStandard.ISO_19115).copy(original);
+        assertCopy(original, clone);
+    }
+
+    /**
+     * Tests {@link DefaultCitation#unmodifiable()}.
+     *
+     * @deprecated To be removed after we removed {@link DefaultCitation#unmodifiable()}.
+     */
+    @Test
+    @Deprecated
+    public void testUnmodifiable() {
+        final DefaultCitation original = create();
         final DefaultCitation clone = (DefaultCitation) original.unmodifiable();    // This
will invoke 'freeze()'.
-        assertNotSame(original, clone);
-        assertTrue ("original.isModifiable",        original.isModifiable());
-        assertFalse(   "clone.isModifiable",           clone.isModifiable());
-        assertSame ("original.unmodifiable", clone, original.unmodifiable());
-        assertSame (   "clone.unmodifiable", clone,    clone.unmodifiable());
+        assertSame("original.unmodifiable", clone, original.unmodifiable());
+        assertSame("clone.unmodifiable",    clone, clone.unmodifiable());
+        assertEquals("original.state", DefaultCitation.State.EDITABLE, original.state());
+        assertEquals("clone.state",    DefaultCitation.State.FINAL,    clone.state());
+        assertCopy(original, clone);
+    }
 
+    /**
+     * Verifies that {@code clone} is a copy of {@code original}, sharing same instance
of values when possible.
+     */
+    private static void assertCopy(final DefaultCitation original, final DefaultCitation
clone) {
+        assertNotSame(original, clone);
         assertSame ("ISBN",  original.getISBN(),  clone.getISBN());
         assertSame ("title", original.getTitle(), clone.getTitle());
         assertSame ("alternateTitle", getSingleton(original.getAlternateTitles()),
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java
b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java
index 8155553..f22a8a5 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java
@@ -120,12 +120,10 @@ public final strictfp class DefaultRepresentativeFractionTest extends
XMLTestCas
     public void testFreeze() {
         final DefaultRepresentativeFraction fraction = new DefaultRepresentativeFraction(1000);
         final DefaultResolution resolution = new DefaultResolution(fraction);
-        resolution.freeze();
-        final DefaultRepresentativeFraction clone = (DefaultRepresentativeFraction) resolution.getEquivalentScale();
-        assertEquals ("Fraction should have the same value.",      fraction, clone);
-        assertNotSame("Should have copied the fraction instance.", fraction, clone);
+        resolution.apply(DefaultResolution.State.FINAL);
+        assertSame(fraction, resolution.getEquivalentScale());
         try {
-            clone.setDenominator(10);
+            fraction.setDenominator(10);
             fail("Shall not be allowed to modify an unmodifiable fraction.");
         } catch (UnsupportedOperationException e) {
             // This is the expected exception.
diff --git a/ide-project/NetBeans/nbproject/genfiles.properties b/ide-project/NetBeans/nbproject/genfiles.properties
index ec32b8a..f1aa966 100644
--- a/ide-project/NetBeans/nbproject/genfiles.properties
+++ b/ide-project/NetBeans/nbproject/genfiles.properties
@@ -3,6 +3,6 @@
 build.xml.data.CRC32=58e6b21c
 build.xml.script.CRC32=462eaba0
 build.xml.stylesheet.CRC32=28e38971@1.53.1.46
-nbproject/build-impl.xml.data.CRC32=40d83d59
+nbproject/build-impl.xml.data.CRC32=fe2883d9
 nbproject/build-impl.xml.script.CRC32=b7ab89c5
 nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48
diff --git a/ide-project/NetBeans/nbproject/project.xml b/ide-project/NetBeans/nbproject/project.xml
index 507a692..6779df9 100644
--- a/ide-project/NetBeans/nbproject/project.xml
+++ b/ide-project/NetBeans/nbproject/project.xml
@@ -111,6 +111,7 @@
             <word>recursivity</word>
             <word>spliterator</word>
             <word>timezone</word>
+            <word>transitioning</word>
             <word>Unicode</word>
             <word>uninstall</word>
             <word>unmarshal</word>


Mime
View raw message