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 <[email protected]>
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
 [email protected]
-nbproject/build-impl.xml.data.CRC32=40d83d59
+nbproject/build-impl.xml.data.CRC32=fe2883d9
 nbproject/build-impl.xml.script.CRC32=b7ab89c5
 nbproject/[email protected]
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>

Reply via email to