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>
