sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1707296 [1/3] - in /sis/branches/JDK7: ./ core/sis-metadata/src/main/java/org/apache/sis/metadata/ core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/ core/s...
Date Wed, 07 Oct 2015 13:20:27 GMT
Author: desruisseaux
Date: Wed Oct  7 13:20:26 2015
New Revision: 1707296

URL: http://svn.apache.org/viewvc?rev=1707296&view=rev
Log:
Merge from the JDK8 branch.

Added:
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/ModifiableIdentifierMap.java
      - copied unchanged from r1707295, sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/ModifiableIdentifierMap.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/jaxb/ModifiableIdentifierMapTest.java
      - copied unchanged from r1707295, sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/internal/jaxb/ModifiableIdentifierMapTest.java
Removed:
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCases.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapWithSpecialCasesTest.java
Modified:
    sis/branches/JDK7/   (props changed)
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFraction.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/io/wkt/WKTParserTest.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
    sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/jaxb/IdentifierMapAdapterTest.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/jaxb/gco/PropertyTypeMock.java
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java

Propchange: sis/branches/JDK7/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Wed Oct  7 13:20:26 2015
@@ -1,4 +1,4 @@
 /sis/branches/Android:1430670-1480699
 /sis/branches/JDK6:1394913-1508480
-/sis/branches/JDK8:1584960-1706793
+/sis/branches/JDK8:1584960-1707295
 /sis/trunk:1394364-1508466,1519089-1519674

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Cloner.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -18,12 +18,15 @@ package org.apache.sis.metadata;
 
 import java.util.Map;
 import java.util.Set;
+import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import org.apache.sis.util.collection.Containers;
+import org.apache.sis.util.collection.CodeListSet;
 import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.metadata.iso.identification.DefaultRepresentativeFraction;
 
 
 /**
@@ -33,7 +36,7 @@ import org.apache.sis.internal.util.Coll
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.7
  * @module
  */
 final class Cloner extends org.apache.sis.internal.util.Cloner {
@@ -53,6 +56,15 @@ final class Cloner extends org.apache.si
     }
 
     /**
+     * Recursively clones all elements in the given array.
+     */
+    private void clones(final Object[] array) throws CloneNotSupportedException {
+        for (int i=0; i<array.length; i++) {
+            array[i] = clone(array[i]);
+        }
+    }
+
+    /**
      * Returns an unmodifiable copy of the specified object.
      * This method performs the following heuristic tests:
      *
@@ -73,12 +85,17 @@ final class Cloner extends org.apache.si
     @Override
     public Object clone(final Object object) throws CloneNotSupportedException {
         /*
-         * CASE 1 - The object is an implementation of ModifiableMetadata. It may have
+         * CASE 1 - The object is an org.apache.sis.metadata.* implementation. It may have
          *          its own algorithm for creating an unmodifiable view of metadata.
          */
         if (object instanceof ModifiableMetadata) {
             return ((ModifiableMetadata) object).unmodifiable();
         }
+        if (object instanceof DefaultRepresentativeFraction) {
+            final DefaultRepresentativeFraction c = ((DefaultRepresentativeFraction) object).clone();
+            c.freeze();
+            return c;
+        }
         /*
          * CASE 2 - The object is a collection. All elements are replaced by their
          *          unmodifiable variant and stored in a new collection of similar
@@ -87,29 +104,39 @@ final class Cloner extends org.apache.si
         if (object instanceof Collection<?>) {
             Collection<?> collection = (Collection<?>) object;
             final boolean isSet = (collection instanceof Set<?>);
-            if (collection.isEmpty()) {
-                if (isSet) {
-                    collection = Collections.EMPTY_SET;
-                } else {
-                    collection = Collections.EMPTY_LIST;
+            final Object[] array = collection.toArray();
+            switch (array.length) {
+                case 0: {
+                    collection = isSet ? Collections.EMPTY_SET
+                                       : Collections.EMPTY_LIST;
+                    break;
                 }
-            } else {
-                final Object[] array = collection.toArray();
-                for (int i=0; i<array.length; i++) {
-                    array[i] = clone(array[i]);
+                case 1: {
+                    final Object value = clone(array[0]);
+                    collection = isSet ? Collections.singleton(value)
+                                       : Collections.singletonList(value);
+                    break;
                 }
-                // Do not use the SIS Checked* classes since
-                // we don't need type checking anymore.
-                if (isSet) {
-                    collection = CollectionsExt.immutableSet(false, array);
-                } else {
-                    // Conservatively assumes a List if we are not sure to have a Set,
-                    // since the list is less destructive (no removal of duplicated).
-                    switch (array.length) {
-                        case 0:  collection = Collections.EMPTY_LIST; break; // Redundant with isEmpty(), but we are paranoiac.
-                        case 1:  collection = Collections.singletonList(array[0]); break;
-                        default: collection = Containers.unmodifiableList(array); break;
+                default: {
+                    if (isSet) {
+                        if (collection instanceof EnumSet<?>) {
+                            collection = Collections.unmodifiableSet(((EnumSet<?>) collection).clone());
+                        } else if (collection instanceof CodeListSet<?>) {
+                            collection = Collections.unmodifiableSet(((CodeListSet<?>) collection).clone());
+                        } else {
+                            clones(array);
+                            collection = CollectionsExt.immutableSet(false, array);
+                        }
+                    } else {
+                        /*
+                         * Do not use the SIS Checked* classes since we don't need type checking anymore.
+                         * Conservatively assumes a List if we are not sure to have a Set since the list
+                         * is less destructive (no removal of duplicated values).
+                         */
+                        clones(array);
+                        collection = Containers.unmodifiableList(array);
                     }
+                    break;
                 }
             }
             return collection;

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -79,8 +79,7 @@ import static org.apache.sis.util.collec
  * }
  *
  * 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 the {@link #freeze()} method.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
@@ -105,8 +104,7 @@ public abstract class ModifiableMetadata
     }
 
     /**
-     * A flag used for {@link #unmodifiable} in order to specify that
-     * {@link #freeze()} is under way.
+     * A sentinel value used for {@link #unmodifiable} in order to specify that {@link #freeze()} is under way.
      */
     private static final ModifiableMetadata FREEZING = new Null();
 
@@ -129,17 +127,13 @@ public abstract class ModifiableMetadata
      * Returns {@code true} if this metadata is modifiable. This method returns
      * {@code false} if {@link #freeze()} has been invoked on this object.
      *
-     * <div class="warning"><b>Warning:</b>
-     * this method is likely to change. Future SIS version will probably uses a "state" enumeration
-     * (modifiable, appendable, etc.).</div>
-     *
      * @return {@code true} if this metadata is modifiable.
      *
      * @see #freeze()
      * @see #checkWritePermission()
      */
     public final boolean isModifiable() {
-        return unmodifiable != this;
+        return unmodifiable != this && unmodifiable != FREEZING;
     }
 
     /**
@@ -164,16 +158,11 @@ public abstract class ModifiableMetadata
      * The default implementation makes the following choice:
      *
      * <ul>
-     *   <li>If this metadata is itself unmodifiable, then this method returns {@code this}
-     *       unchanged.</li>
+     *   <li>If this metadata is itself unmodifiable, then this method returns {@code this} unchanged.</li>
      *   <li>Otherwise this method {@linkplain #clone() clone} this metadata and
      *       {@linkplain #freeze() freeze} the clone before to return it.</li>
      * </ul>
      *
-     * <div class="warning"><b>Warning:</b>
-     * this method is likely to change. Future SIS version will probably uses a "state" enumeration
-     * (modifiable, appendable, etc.).</div>
-     *
      * @return An unmodifiable copy of this metadata.
      */
     public AbstractMetadata unmodifiable() {
@@ -209,13 +198,9 @@ public abstract class ModifiableMetadata
      * property after this method call will throw an {@link UnmodifiableMetadataException}.
      * If this metadata is already unmodifiable, then this method does nothing.
      *
-     * <p>Subclasses usually don't need to override this method since the default implementation
+     * <p>Subclasses usually do not need to override this method since the default implementation
      * performs its work using Java reflection.</p>
      *
-     * <div class="warning"><b>Warning:</b>
-     * this method is likely to change. Future SIS version will probably uses a "state" enumeration
-     * (modifiable, appendable, etc.).</div>
-     *
      * @see #isModifiable()
      * @see #checkWritePermission()
      */
@@ -254,10 +239,13 @@ public abstract class ModifiableMetadata
      * @see #freeze()
      */
     protected void checkWritePermission() throws UnmodifiableMetadataException {
-        if (!isModifiable()) {
-            throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata));
+        if (unmodifiable != null) {
+            if (unmodifiable == this) {
+                throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata));
+            } else if (unmodifiable != FREEZING) {
+                unmodifiable = null;
+            }
         }
-        unmodifiable = null;
     }
 
     /**
@@ -265,8 +253,7 @@ public abstract class ModifiableMetadata
      * creating it if needed. This method performs the following steps:
      *
      * <ul>
-     *   <li>Invokes {@link #checkWritePermission()} in order to ensure that this metadata is
-     *       modifiable.</li>
+     *   <li>Invokes {@link #checkWritePermission()} in order to ensure that this metadata is modifiable.</li>
      *   <li>If {@code source} is null or empty, returns {@code null}
      *       (meaning that the metadata property is not provided).</li>
      *   <li>If {@code target} is null, creates a new {@link List}.</li>
@@ -313,8 +300,7 @@ public abstract class ModifiableMetadata
      * creating it if needed. This method performs the following steps:
      *
      * <ul>
-     *   <li>Invokes {@link #checkWritePermission()} in order to ensure that this metadata is
-     *       modifiable.</li>
+     *   <li>Invokes {@link #checkWritePermission()} in order to ensure that this metadata is modifiable.</li>
      *   <li>If {@code source} is null or empty, returns {@code null}
      *       (meaning that the metadata property is not provided).</li>
      *   <li>If {@code target} is null, creates a new {@link Set}.</li>
@@ -325,8 +311,8 @@ public abstract class ModifiableMetadata
      * @param  source      The source set, or {@code null}.
      * @param  target      The target set, or {@code null} if not yet created.
      * @param  elementType The base type of elements to put in the set.
-     * @return A set (possibly the {@code target} instance) containing the {@code source}
-     *         elements, or {@code null} if the source was null.
+     * @return A set (possibly the {@code target} instance) containing the {@code source} elements,
+     *         or {@code null} if the source was null.
      * @throws UnmodifiableMetadataException if this metadata is unmodifiable.
      *
      * @see #nonNullSet(Set, Class)
@@ -361,8 +347,7 @@ public abstract class ModifiableMetadata
      * creating it if needed. This method performs the following steps:
      *
      * <ul>
-     *   <li>Invokes {@link #checkWritePermission()} in order to ensure that this metadata is
-     *       modifiable.</li>
+     *   <li>Invokes {@link #checkWritePermission()} in order to ensure that this metadata is modifiable.</li>
      *   <li>If {@code source} is null or empty, returns {@code null}
      *       (meaning that the metadata property is not provided).</li>
      *   <li>If {@code target} is null, creates a new {@link Set} or a new {@link List}
@@ -381,8 +366,8 @@ public abstract class ModifiableMetadata
      * @param  source      The source collection, or {@code null}.
      * @param  target      The target collection, or {@code null} if not yet created.
      * @param  elementType The base type of elements to put in the collection.
-     * @return A collection (possibly the {@code target} instance) containing the {@code source}
-     *         elements, or {@code null} if the source was null.
+     * @return A collection (possibly the {@code target} instance) containing the {@code source} elements,
+     *         or {@code null} if the source was null.
      * @throws UnmodifiableMetadataException if this metadata is unmodifiable.
      */
     @SuppressWarnings("unchecked")
@@ -671,12 +656,12 @@ public abstract class ModifiableMetadata
      */
     @SuppressWarnings({"rawtypes","unchecked"})
     protected <E> Class<? extends Collection<E>> collectionType(final Class<E> elementType) {
-        return (Class) (CodeList.class.isAssignableFrom(elementType) ||
-                            Enum.class.isAssignableFrom(elementType) ||
-                         Charset.class.isAssignableFrom(elementType) ||
-                          String.class ==               elementType  ||
-                          Locale.class ==               elementType  ||
-                        Currency.class ==               elementType
+        return (Class) (CodeList.class.isAssignableFrom(elementType)
+                ||          Enum.class.isAssignableFrom(elementType)
+                ||       Charset.class.isAssignableFrom(elementType)
+                ||        String.class ==               elementType
+                ||        Locale.class ==               elementType
+                ||      Currency.class ==               elementType
                 ? Set.class : List.class);
     }
 

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -29,7 +29,8 @@ import org.apache.sis.xml.IdentifierSpac
 import org.apache.sis.xml.IdentifiedObject;
 import org.apache.sis.metadata.MetadataStandard;
 import org.apache.sis.metadata.ModifiableMetadata;
-import org.apache.sis.internal.jaxb.IdentifierMapWithSpecialCases;
+import org.apache.sis.internal.jaxb.IdentifierMapAdapter;
+import org.apache.sis.internal.jaxb.ModifiableIdentifierMap;
 import org.apache.sis.internal.util.Utilities;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.CharSequences;
@@ -52,7 +53,7 @@ import static org.apache.sis.util.collec
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.7
  * @module
  */
 @XmlTransient
@@ -151,13 +152,14 @@ public class ISOMetadata extends Modifia
          */
         identifiers = nonNullCollection(identifiers, Identifier.class);
         if (identifiers == null) {
-            return IdentifierMapWithSpecialCases.EMPTY;
+            return IdentifierMapAdapter.EMPTY;
         }
         /*
          * 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 new IdentifierMapWithSpecialCases(identifiers);
+        return isModifiable() ? new ModifiableIdentifierMap(identifiers)
+                              : new IdentifierMapAdapter(identifiers);
     }
 
 
@@ -204,7 +206,7 @@ public class ISOMetadata extends Modifia
     @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
     private String getUUID() {
         /*
-         * IdentifierMapWithSpecialCases will take care of converting UUID to String,
+         * IdentifierMapAdapter will take care of converting UUID to String,
          * or to return a previously stored String if it was an unparsable UUID.
          */
         return isNullOrEmpty(identifiers) ? null : getIdentifierMap().get(IdentifierSpace.UUID);
@@ -216,9 +218,9 @@ public class ISOMetadata extends Modifia
      */
     private void setUUID(final String id) {
         /*
-         * IdentifierMapWithSpecialCases will take care of converting the String to UUID if possible,
-         * or will store the value as a plain String if it can not be converted. In the later case, a
-         * warning will be emitted (logged or processed by listeners).
+         * IdentifierMapAdapter will take care of converting the String to UUID if possible, or
+         * will store the value as a plain String if it can not be converted. In the later case,
+         * a warning will be emitted (logged or processed by listeners).
          */
         getIdentifierMap().put(IdentifierSpace.UUID, id);
     }

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/acquisition/DefaultObjective.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -48,7 +48,7 @@ import org.apache.sis.internal.jaxb.NonM
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.7
  * @module
  */
 @XmlType(name = "MI_Objective_Type", propOrder = {
@@ -175,23 +175,20 @@ public class DefaultObjective extends IS
     @Override
     @XmlElement(name = "identifier", required = true)
     public Collection<Identifier> getIdentifiers() {
-        return NonMarshalledAuthority.excludeOnMarshalling(super.getIdentifiers());
+        return NonMarshalledAuthority.filterOnMarshalling(super.getIdentifiers());
     }
 
     /**
      * Sets the code used to identify the objective.
      *
-     * <p>This method overwrites all previous identifiers with the given new values,
-     * <strong>except</strong> the XML identifiers ({@linkplain IdentifierSpace#ID ID},
-     * {@linkplain IdentifierSpace#UUID UUID}, <i>etc.</i>), if any. We do not overwrite
-     * the XML identifiers because they are usually associated to object identity.</p>
+     * <p>XML identifiers ({@linkplain IdentifierSpace#ID ID}, {@linkplain IdentifierSpace#UUID UUID}, <i>etc.</i>),
+     * are not affected by this method, unless they are explicitely provided in the given collection.</p>
      *
      * @param newValues The new identifiers values.
      */
-    public void setIdentifiers(final Collection<? extends Identifier> newValues) {
-        final Collection<Identifier> oldIds = NonMarshalledAuthority.filteredCopy(identifiers);
+    public void setIdentifiers(Collection<? extends Identifier> newValues) {
+        newValues = NonMarshalledAuthority.setMarshallables(identifiers, newValues);
         identifiers = writeCollection(newValues, identifiers, Identifier.class);
-        NonMarshalledAuthority.replace(identifiers, oldIds);
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultCitation.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -63,7 +63,7 @@ import static org.apache.sis.internal.me
  * @author  Cédric Briançon (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
  * @since   0.3
- * @version 0.5
+ * @version 0.7
  * @module
  */
 @XmlType(name = "CI_Citation_Type", propOrder = {
@@ -364,29 +364,25 @@ public class DefaultCitation extends ISO
     @Override
     @XmlElement(name = "identifier")
     public Collection<Identifier> getIdentifiers() {
-        return NonMarshalledAuthority.excludeOnMarshalling(super.getIdentifiers());
+        return NonMarshalledAuthority.filterOnMarshalling(super.getIdentifiers());
     }
 
     /**
      * Sets the unique identifier for the resource.
      * Example: Universal Product Code (UPC), National Stock Number (NSN).
      *
-     * <p>This method overwrites all previous identifiers with the given new values,
-     * <strong>except</strong> the XML identifiers ({@linkplain IdentifierSpace#ID ID},
-     * {@linkplain IdentifierSpace#UUID UUID}, <i>etc.</i>), ISBN and ISSN codes, if any.
-     * We do not overwrite the XML identifiers because they are usually associated to object
-     * identity, and we do not overwrite ISBN/ISSN codes because they have dedicated setters
-     * for compliance with the ISO 19115 model.</p>
+     * <p>XML identifiers ({@linkplain IdentifierSpace#ID ID}, {@linkplain IdentifierSpace#UUID UUID}, <i>etc.</i>),
+     * {@linkplain #getISBN() ISBN} and {@linkplain #getISSN() ISSN} codes are not affected by this method, unless
+     * they are explicitely provided in the given collection.</p>
      *
      * @param newValues The new identifiers, or {@code null} if none.
      *
      * @see #setISBN(String)
      * @see #setISSN(String)
      */
-    public void setIdentifiers(final Collection<? extends Identifier> newValues) {
-        final Collection<Identifier> oldIds = NonMarshalledAuthority.filteredCopy(identifiers);
+    public void setIdentifiers(Collection<? extends Identifier> newValues) {
+        newValues = NonMarshalledAuthority.setMarshallables(identifiers, newValues);
         identifiers = writeCollection(newValues, identifiers, Identifier.class);
-        NonMarshalledAuthority.replace(identifiers, oldIds);
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFraction.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFraction.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFraction.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFraction.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -26,7 +26,9 @@ import javax.xml.bind.annotation.adapter
 import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.identification.RepresentativeFraction;
-import org.apache.sis.internal.jaxb.IdentifierMapWithSpecialCases;
+import org.apache.sis.metadata.UnmodifiableMetadataException;
+import org.apache.sis.internal.jaxb.ModifiableIdentifierMap;
+import org.apache.sis.internal.jaxb.IdentifierMapAdapter;
 import org.apache.sis.internal.jaxb.gco.GO_Integer64;
 import org.apache.sis.internal.util.CheckedArrayList;
 import org.apache.sis.measure.ValueRange;
@@ -63,14 +65,14 @@ import static org.apache.sis.internal.me
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see DefaultResolution#getEquivalentScale()
  */
 @XmlType(name = "MD_RepresentativeFraction_Type")
 @XmlRootElement(name = "MD_RepresentativeFraction")
-public class DefaultRepresentativeFraction extends Number implements RepresentativeFraction, IdentifiedObject, Emptiable {
+public class DefaultRepresentativeFraction extends Number implements RepresentativeFraction, IdentifiedObject, Emptiable, Cloneable {
     /**
      * Serial number for compatibility with different versions.
      */
@@ -88,6 +90,11 @@ public class DefaultRepresentativeFracti
     private Collection<Identifier> identifiers;
 
     /**
+     * {@code true} if this representative fraction has been made unmodifiable.
+     */
+    private transient boolean isUnmodifiable;
+
+    /**
      * Creates a uninitialized representative fraction.
      * The {@linkplain #getDenominator() denominator} is initially zero
      * and the {@linkplain #doubleValue() double value} is NaN.
@@ -161,6 +168,9 @@ public class DefaultRepresentativeFracti
      * @throws IllegalArgumentException if the given value is negative.
      */
     public void setDenominator(final long denominator) {
+        if (isUnmodifiable) {
+            throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata));
+        }
         if (ensurePositive(DefaultRepresentativeFraction.class, "denominator", false, denominator)) {
             this.denominator = denominator;
         }
@@ -176,6 +186,9 @@ public class DefaultRepresentativeFracti
      * @throws IllegalArgumentException if the given scale is our of range.
      */
     public void setScale(final double scale) {
+        if (isUnmodifiable) {
+            throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.UnmodifiableMetadata));
+        }
         /*
          * For the following argument check, we do not need to use a Metadatautility method because
          * 'setScale' is never invoked at (un)marshalling time. Note also that we accept NaN values
@@ -259,6 +272,35 @@ public class DefaultRepresentativeFracti
     }
 
     /**
+     * Makes this representative fraction unmodifiable. After invocation to this method,
+     * any call to a setter method will throw an {@link UnmodifiableMetadataException}.
+     *
+     * @since 0.7
+     *
+     * @see org.apache.sis.metadata.ModifiableMetadata#freeze()
+     */
+    public void freeze() {
+        isUnmodifiable = true;
+    }
+
+    /**
+     * Returns a modifiable copy of this representative fraction.
+     *
+     * @return A modifiable copy of this representative fraction.
+     */
+    @Override
+    public DefaultRepresentativeFraction clone() {
+        final DefaultRepresentativeFraction c;
+        try {
+            c = (DefaultRepresentativeFraction) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(e);    // Should never happen since we are cloneable.
+        }
+        c.isUnmodifiable = false;
+        return c;
+    }
+
+    /**
      * Compares this object with the specified value for equality.
      *
      * @param object The object to compare with.
@@ -326,7 +368,9 @@ public class DefaultRepresentativeFracti
      */
     @Override
     public IdentifierMap getIdentifierMap() {
-        return new IdentifierMapWithSpecialCases(getIdentifiers());
+        final Collection<Identifier> identifiers = getIdentifiers();
+        return isUnmodifiable ? new IdentifierMapAdapter(identifiers)
+                              : new ModifiableIdentifierMap(identifiers);
     }
 
 

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/TreeTableFormatTest.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -20,14 +20,13 @@ import java.util.Arrays;
 import javax.measure.unit.SI;
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.PresentationForm;
-import org.opengis.util.InternationalString;
 import org.apache.sis.util.collection.TableColumn;
 import org.apache.sis.util.collection.TreeTableFormat;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.metadata.iso.content.DefaultBand;
 import org.apache.sis.metadata.iso.content.DefaultImageDescription;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
-import org.apache.sis.metadata.iso.citation.DefaultIndividual;
+import org.apache.sis.metadata.iso.citation.DefaultCitationTest;
 import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
 import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
 import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
@@ -76,53 +75,41 @@ public final strictfp class TreeTableFor
     }
 
     /**
-     * Creates the citation to use for testing purpose.
-     */
-    private static DefaultCitation createCitation() {
-        final DefaultCitation citation = new DefaultCitation();
-        final InternationalString title = new SimpleInternationalString("Undercurrent");
-        citation.setTitle(title);
-        citation.setISBN("9782505004509");
-        citation.setPresentationForms(asList(
-                PresentationForm.DOCUMENT_HARDCOPY,
-                PresentationForm.IMAGE_HARDCOPY));
-        citation.setAlternateTitles(asList(
-                new SimpleInternationalString("Alt A"),
-                new SimpleInternationalString("Alt B")));
-        citation.setCitedResponsibleParties(asList(
-                new DefaultResponsibility(Role.AUTHOR, null, new DefaultIndividual("Testsuya Toyoda", null, null)),
-                new DefaultResponsibility(null, null, new DefaultIndividual("A japanese author", null, null))));
-        return citation;
-    }
-
-    /**
      * Tests the formatting of a {@link DefaultCitation} object.
      */
     @Test
     public void testCitation() {
-        final DefaultCitation citation = createCitation();
+        final DefaultCitation citation = DefaultCitationTest.create();
         final String text = format.format(citation.asTreeTable());
         assertMultilinesEquals(
             "Citation\n" +
-            "  ├─Title……………………………………………………………………… Undercurrent\n" +
-            "  ├─Alternate title (1 of 2)…………………… Alt A\n" +
-            "  ├─Alternate title (2 of 2)…………………… Alt B\n" +
+            "  ├─Title…………………………………………………………………………… Undercurrent\n" +
+            "  ├─Alternate title………………………………………………… Andākarento\n" +
             "  ├─Identifier\n" +
-            "  │   ├─Code……………………………………………………………… 9782505004509\n" +
+            "  │   ├─Code…………………………………………………………………… 9782505004509\n" +
             "  │   ├─Authority\n" +
-            "  │   │   ├─Title………………………………………………… International Standard Book Number\n" +
-            "  │   │   └─Alternate title……………………… ISBN\n" +
-            "  │   └─Code space……………………………………………… ISBN\n"+
+            "  │   │   ├─Title……………………………………………………… International Standard Book Number\n" +
+            "  │   │   └─Alternate title…………………………… ISBN\n" +
+            "  │   └─Code space…………………………………………………… ISBN\n"+
             "  ├─Cited responsible party (1 of 2)\n" +
             "  │   ├─Party\n" +
-            "  │   │   └─Name…………………………………………………… Testsuya Toyoda\n" +
-            "  │   └─Role……………………………………………………………… Author\n" +
+            "  │   │   └─Name………………………………………………………… Testsuya Toyoda\n" +
+            "  │   └─Role…………………………………………………………………… Author\n" +
             "  ├─Cited responsible party (2 of 2)\n" +
-            "  │   └─Party\n" +
-            "  │       └─Name…………………………………………………… A japanese author\n" +
-            "  ├─Presentation form (1 of 2)……………… Document hardcopy\n" +
-            "  ├─Presentation form (2 of 2)……………… Image hardcopy\n" +
-            "  └─ISBN………………………………………………………………………… 9782505004509\n", text);
+            "  │   ├─Party\n" +
+            "  │   │   └─Name………………………………………………………… Kōdansha\n" +
+            "  │   ├─Role…………………………………………………………………… Editor\n" +
+            "  │   └─Extent\n" +
+            "  │       ├─Description……………………………………… World\n" +
+            "  │       └─Geographic element\n" +
+            "  │           ├─West bound longitude…… 180°W\n" +
+            "  │           ├─East bound longitude…… 180°E\n" +
+            "  │           ├─South bound latitude…… 90°S\n" +
+            "  │           ├─North bound latitude…… 90°N\n" +
+            "  │           └─Extent type code……………… true\n" +
+            "  ├─Presentation form (1 of 2)…………………… Document digital\n" +
+            "  ├─Presentation form (2 of 2)…………………… Document hardcopy\n" +
+            "  └─ISBN……………………………………………………………………………… 9782505004509\n", text);
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/citation/DefaultCitationTest.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -18,12 +18,22 @@ package org.apache.sis.metadata.iso.cita
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
 import org.opengis.metadata.Identifier;
+import org.opengis.metadata.citation.Role;
+import org.opengis.metadata.citation.Responsibility;
+import org.opengis.metadata.citation.PresentationForm;
+import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.xml.IdentifierMap;
+import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.metadata.iso.DefaultIdentifier;
+import org.apache.sis.util.iso.SimpleInternationalString;
+import org.apache.sis.util.iso.DefaultInternationalString;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
+import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.junit.Assert.*;
 
 
@@ -32,11 +42,36 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.7
  * @module
  */
 public final strictfp class DefaultCitationTest extends TestCase {
     /**
+     * Creates a citation with an arbitrary title, presentation form and other properties.
+     *
+     * @return An arbitrary citation.
+     *
+     * @since 0.7
+     */
+    public static DefaultCitation create() {
+        final DefaultCitation citation = new DefaultCitation();
+        final DefaultInternationalString title = new DefaultInternationalString();
+        title.add(Locale.JAPANESE, "アンダーカレント");
+        title.add(Locale.ENGLISH,  "Undercurrent");
+        citation.setTitle(title);
+        citation.setISBN("9782505004509");
+        citation.setPresentationForms(Arrays.asList(
+                PresentationForm.DOCUMENT_HARDCOPY,
+                PresentationForm.DOCUMENT_DIGITAL));
+        citation.setAlternateTitles(Collections.singleton(
+                new SimpleInternationalString("Andākarento")));   // Actually a different script of the Japanese title.
+        citation.setCitedResponsibleParties(Arrays.asList(
+                new DefaultResponsibility(Role.AUTHOR, null, new DefaultIndividual("Testsuya Toyoda", null, null)),
+                new DefaultResponsibility(Role.EDITOR, Extents.WORLD, new DefaultOrganisation("Kōdansha", null, null, null))));
+        return citation;
+    }
+
+    /**
      * Tests the identifier map, which handles ISBN and ISSN codes in a special way.
      */
     @Test
@@ -61,11 +96,67 @@ public final strictfp class DefaultCitat
         citation.setIdentifiers(Arrays.asList(
                 new DefaultIdentifier(Citations.NETCDF, "MyNetCDF"),
                 new DefaultIdentifier(Citations.EPSG,   "MyEPSG"),
-                new DefaultIdentifier(Citations.ISBN,   "MyIgnored"),
+                new DefaultIdentifier(Citations.ISBN,   "NewISBN"),
                 new DefaultIdentifier(Citations.ISSN,   "MyISSN")));
 
-        assertEquals("The ISBN value shall not have been overwritten.",    "MyISBN", citation.getISBN());
+        assertEquals("The ISBN value shall have been overwritten.",       "NewISBN", citation.getISBN());
         assertEquals("The ISSN value shall have been added, because new.", "MyISSN", citation.getISSN());
-        assertEquals("{NetCDF=“MyNetCDF”, EPSG=“MyEPSG”, ISSN=“MyISSN”, ISBN=“MyISBN”}", identifierMap.toString());
+        assertEquals("{NetCDF=“MyNetCDF”, EPSG=“MyEPSG”, ISBN=“NewISBN”, ISSN=“MyISSN”}", identifierMap.toString());
+    }
+
+    /**
+     * Tests {@link DefaultCitation#freeze()}, which is needed for the constants defined in {@link Citations}.
+     */
+    @Test
+    public void testFreeze() {
+        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 ("ISBN",  original.getISBN(),  clone.getISBN());
+        assertSame ("title", original.getTitle(), clone.getTitle());
+        assertSame ("alternateTitle", getSingleton(original.getAlternateTitles()),
+                                     getSingleton(clone.getAlternateTitles()));
+
+        assertCopy(original.getIdentifiers(),             clone.getIdentifiers());
+        assertCopy(original.getCitedResponsibleParties(), clone.getCitedResponsibleParties());
+        assertCopy(original.getPresentationForms(),       clone.getPresentationForms());
+        /*
+         * Verify the unique identifier, which is the ISBN code. ISBN and ISSN codes are handled
+         * in a special way by DefaultCitation (they are instances of SpecializedIdentifier), but
+         * the should nevertheless be cloned.
+         */
+        final Identifier ide = getSingleton(original.getIdentifiers());
+        final Identifier ida = getSingleton(   clone.getIdentifiers());
+        assertNotSame("identifier", ide, ida);
+        assertSame("code",      ide.getCode(),      ida.getCode());
+        assertSame("authority", ide.getAuthority(), ida.getAuthority());
+        /*
+         * Verify the author metadata.
+         */
+        final Responsibility re = CollectionsExt.first(original.getCitedResponsibleParties());
+        final Responsibility ra = CollectionsExt.first(clone   .getCitedResponsibleParties());
+        assertNotSame("citedResponsibleParty", re, ra);
+        assertSame("role", re.getRole(), ra.getRole());
+        assertSame("name", getSingleton(re.getParties()).getName(),
+                           getSingleton(ra.getParties()).getName());
+    }
+
+    /**
+     * Verifies that {@code actual} is an unmodifiable copy of {@code expected}.
+     */
+    private static <T> void assertCopy(final Collection<T> expected, final Collection<T> actual) {
+        assertNotSame("ModifiableMetadata.freeze() shall have copied the collection.", expected, actual);
+        assertEquals("The copied collection shall have the same content than the original.", expected, actual);
+        try {
+            actual.add(null);
+            fail("The copied collection shall be unmodifiable.");
+        } catch (UnsupportedOperationException e) {
+            // This is the expected exception.
+        }
     }
 }

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/identification/DefaultRepresentativeFractionTest.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -31,7 +31,7 @@ import static org.apache.sis.test.Assert
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
- * @version 0.4
+ * @version 0.7
  * @module
  */
 public final strictfp class DefaultRepresentativeFractionTest extends TestCase {
@@ -72,4 +72,27 @@ public final strictfp class DefaultRepre
          */
         assertEquals(fraction, XML.unmarshal(xml));
     }
+
+    /**
+     * Tests indirectly {@link DefaultRepresentativeFraction#freeze()}.
+     * This method verifies that a call to {@link DefaultResolution#freeze()}
+     * implies a call to {@link DefaultRepresentativeFraction#freeze()}.
+     *
+     * @since 0.7
+     */
+    @Test
+    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);
+        try {
+            clone.setDenominator(10);
+            fail("Shall not be allowed to modify an unmodifiable fraction.");
+        } catch (UnsupportedOperationException e) {
+            // This is the expected exception.
+        }
+    }
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -36,11 +36,16 @@ import static java.lang.Math.*;
  * (Source: §1.3.3 in IOGP Publication 373-7-2 – Geomatics Guidance Note number 7, part 2 – April 2015).
  * Indeed, those two projections have some equation in commons which are provided in this base class.</p>
  *
- * <p>The polar stereographic projection is not documented as a special case of Lambert Conic Conformal,
+ * <p>The Polar Stereographic projection is not documented as a special case of Lambert Conic Conformal,
  * but the equations in the {@code PolarStereographic.transform(…)} and {@code inverseTransform(…)} methods
  * appear to be the same with the <var>n</var> factor fixed to 1 or -1, so we leverage the code provided by
  * this base class. This class hierarchy is only an implementation convenience and not part of public API.</p>
  *
+ * <p>The Transverse Mercator projection is also conformal, but does not use the formulas provided in this class.
+ * It will instead compute the coefficients itself and use its own, more complex, formulas with those coefficients.
+ * However the formulas provided in this {@code ConformalProjection} class can be seen as a special case of
+ * Transverse Mercator formulas for <var>x</var> = 0.</p>
+ *
  * <div class="note"><b>Reference:</b>
  * “Lambert developed the regular Conformal Conic as the oblique aspect of a family containing the previously
  * known polar Stereographic and regular Mercator projections. (…) If the standard parallels are symmetrical
@@ -49,7 +54,7 @@ import static java.lang.Math.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  */
 abstract class ConformalProjection extends NormalizedProjection {
@@ -59,63 +64,98 @@ abstract class ConformalProjection exten
     private static final long serialVersionUID = 458860570536642265L;
 
     /**
-     * The threshold value of {@link #excentricity} at which we consider the accuracy of the
-     * series expansion insufficient. This threshold is determined empirically with the help
-     * of the {@code MercatorMethodComparison} class in the test directory.
-     * We choose the value where:
+     * {@code false} for using the original formulas as published by EPSG, or {@code true} for using formulas
+     * modified using trigonometric identities. The use of trigonometric identities is for reducing the amount
+     * of calls to the {@link Math#sin(double)} and similar methods. Some identities used are:
      *
      * <ul>
-     *   <li>the average error of series expansion become greater than {@link NormalizedProjection#ITERATION_TOLERANCE},</li>
-     *   <li>the maximal error of series expansion become greater than {@link NormalizedProjection#ANGULAR_TOLERANCE}.</li>
+     *   <li>sin(2θ) = 2⋅sinθ⋅cosθ</li>
+     *   <li>cos(2θ) = cos²θ - sin²θ</li>
+     *   <li>sin(3θ) = (3 - 4⋅sin²θ)⋅sinθ</li>
+     *   <li>cos(3θ) = (4⋅cos³θ) - 3⋅cosθ</li>
+     *   <li>sin(4θ) = (4 - 8⋅sin²θ)⋅sinθ⋅cosθ</li>
+     *   <li>cos(4θ) = (8⋅cos⁴θ) - (8⋅cos²θ) + 1</li>
      * </ul>
-     */
-    static final double EXCENTRICITY_THRESHOLD = 0.16;
-
-    /**
-     * Whether to use the original formulas a published by EPSG, or their form modified using trigonometric identities.
-     * The modified form uses trigonometric identifies for reducing the amount of calls to the {@link Math#sin(double)}
-     * method. The identities used are:
+     *
+     * Hyperbolic formulas (used in Transverse Mercator projection):
      *
      * <ul>
-     *   <li>sin(2⋅x) = 2⋅sin(x)⋅cos(x)</li>
-     *   <li>sin(3⋅x) = (3 - 4⋅sin²(x))⋅sin(x)</li>
-     *   <li>sin(4⋅x) = (4 - 8⋅sin²(x))⋅sin(x)⋅cos(x)</li>
+     *   <li>sinh(2θ) = 2⋅sinhθ⋅coshθ</li>
+     *   <li>cosh(2θ) = cosh²θ + sinh²θ   =   2⋅cosh²θ - 1   =   1 + 2⋅sinh²θ</li>
+     *   <li>sinh(3θ) = (3 + 4⋅sinh²θ)⋅sinhθ</li>
+     *   <li>cosh(3θ) = ((4⋅cosh²θ) - 3)⋅coshθ</li>
+     *   <li>sinh(4θ) = (1 + 2⋅sinh²θ)⋅4.sinhθ⋅coshθ
+     *                = 4.cosh(2θ).sinhθ⋅coshθ</li>
+     *   <li>cosh(4θ) = (8⋅cosh⁴θ) - (8⋅cosh²θ) + 1
+     *                = 8⋅cosh²(θ) ⋅ (cosh²θ - 1) + 1
+     *                = 8⋅cosh²(θ) ⋅ sinh²(θ) + 1
+     *                = 2⋅sinh²(2θ) + 1</li>
      * </ul>
      *
      * Note that since this boolean is static final, the compiler should exclude the code in the branch that is never
      * executed (no need to comment-out that code).
+     *
+     * @see #identityEquals(double, double)
      */
-    private static final boolean ORIGINAL_FORMULA = false;
+    static final boolean ALLOW_TRIGONOMETRIC_IDENTITIES = true;
 
     /**
-     * Coefficients in the series expansion used by {@link #φ(double)}.
+     * The threshold value of {@link #excentricity} at which we consider the accuracy of the
+     * series expansion insufficient. This threshold is determined empirically with the help
+     * of the {@code MercatorMethodComparison} class in the test directory.
+     * We choose the value where:
      *
-     * <p>Consider those fields as final. They are not only of the purpose of {@link #readObject(ObjectInputStream)}.</p>
+     * <ul>
+     *   <li>the average error of series expansion become greater than {@link NormalizedProjection#ITERATION_TOLERANCE},</li>
+     *   <li>the maximal error of series expansion become greater than {@link NormalizedProjection#ANGULAR_TOLERANCE}.</li>
+     * </ul>
      */
-    private transient double c2χ, c4χ, c6χ, c8χ;
+    static final double EXCENTRICITY_THRESHOLD = 0.16;
 
     /**
      * {@code true} if the {@link #excentricity} value is greater than or equals to {@link #EXCENTRICITY_THRESHOLD},
      * in which case the {@link #φ(double)} method will need to use an iterative method.
      *
-     * <p>Consider this field as final. It is not only of the purpose of {@link #readObject(ObjectInputStream)}.</p>
+     * <p><strong>Consider this field as final!</strong>
+     * It is not final only for the purpose of {@link #readObject(ObjectInputStream)}.</p>
      */
     private transient boolean useIterations;
 
     /**
+     * Coefficients in the series expansion of the inverse projection,
+     * depending only on {@linkplain #excentricity excentricity} value.
+     * The series expansion is of the following form, where f(θ) is typically sin(θ):
+     *
+     *     <blockquote>ci₂⋅f(2θ) + ci₄⋅f(4θ) + ci₆⋅f(6θ) + ci₈⋅f(8θ)</blockquote>
+     *
+     * This {@code ConformalProjection} class uses those coefficients in {@link #φ(double)}.
+     * However some subclasses may compute those coefficients differently and use them in a
+     * different series expansion (but for the same purpose).
+     *
+     * <p><strong>Consider those fields as final!</strong> They are not final only for sub-class
+     * constructors convenience and for the purpose of {@link #readObject(ObjectInputStream)}.</p>
+     *
+     * @see #computeCoefficients()
+     */
+    transient double ci2, ci4, ci6, ci8;
+
+    /**
      * Creates a new normalized projection from the parameters computed by the given initializer.
      *
+     * <p>It is sub-classes responsibility to invoke {@code super.computeCoefficients()} in their
+     * constructor when ready, or to compute the coefficients themselves.</p>
+     *
      * @param initializer The initializer for computing map projection internal parameters.
      */
     ConformalProjection(final Initializer initializer) {
         super(initializer);
-        initialize();
     }
 
     /**
-     * Computes the transient fields after construction or deserialization.
+     * Computes the coefficients in the series expansions from the {@link #excentricitySquared} value.
+     * This method shall be invoked after {@code ConformalProjection} construction or deserialization.
      */
-    private void initialize() {
+    void computeCoefficients() {
         useIterations = (excentricity >= EXCENTRICITY_THRESHOLD);
         final double e2 = excentricitySquared;
         final double e4 = e2 * e2;
@@ -125,14 +165,20 @@ abstract class ConformalProjection exten
          * For each line below, add the smallest values first in order to reduce rounding errors.
          * The smallest values are the one using the excentricity raised to the highest power.
          */
-        c2χ  =    13/   360.* e8  +   1/ 12.* e6  +  5/24.* e4  +  e2/2;
-        c4χ  =   811/ 11520.* e8  +  29/240.* e6  +  7/48.* e4;
-        c6χ  =    81/  1120.* e8  +   7/120.* e6;
-        c8χ  =  4279/161280.* e8;
-        if (!ORIGINAL_FORMULA) {
-            c4χ *= 2;
-            c6χ *= 4;
-            c8χ *= 8;
+        ci2  =    13/   360.* e8  +   1/ 12.* e6  +  5/24.* e4  +  e2/2;
+        ci4  =   811/ 11520.* e8  +  29/240.* e6  +  7/48.* e4;
+        ci6  =    81/  1120.* e8  +   7/120.* e6;
+        ci8  =  4279/161280.* e8;
+        /*
+         * When rewriting equations using trigonometric identities, some constants appear.
+         * For example sin(2θ) = 2⋅sinθ⋅cosθ, so we can factor out the 2 constant into the
+         * corresponding 'c' field.
+         */
+        if (ALLOW_TRIGONOMETRIC_IDENTITIES) {
+            // Multiplication by powers of 2 does not bring any additional rounding error.
+            ci4 *= 2;
+            ci6 *= 4;
+            ci8 *= 8;
         }
     }
 
@@ -145,10 +191,10 @@ abstract class ConformalProjection exten
     ConformalProjection(final ConformalProjection other) {
         super(other);
         useIterations = other.useIterations;
-        c2χ = other.c2χ;
-        c4χ = other.c4χ;
-        c6χ = other.c6χ;
-        c8χ = other.c8χ;
+        ci2 = other.ci2;
+        ci4 = other.ci4;
+        ci6 = other.ci6;
+        ci8 = other.ci8;
     }
 
     /**
@@ -156,6 +202,12 @@ abstract class ConformalProjection exten
      * This formula is also part of other projections, since Mercator can be considered as a special case of
      * Lambert Conic Conformal for instance.
      *
+     * <div class="note"><b>Warning:</b>
+     * this method is valid only if the series expansion coefficients where computed by the
+     * {@link #computeCoefficients()} method defined in this class. This is not the case of
+     * {@link TransverseMercator} for instance. Note however that even in the later case,
+     * this method can still be seen as a special case of {@code TransverseMercator} formulas.</div>
+     *
      * <p>This function is <em>almost</em> the converse of the {@link #expOfNorthing(double, double)} function.
      * In a Mercator inverse projection, the value of the {@code expOfSouthing} argument is {@code exp(-y)}.</p>
      *
@@ -196,23 +248,20 @@ abstract class ConformalProjection exten
          * EPSG guidance note. Note that we add those terms in reverse order, beginning with the smallest
          * values, for reducing rounding errors due to IEEE 754 arithmetic.
          */
-        if (ORIGINAL_FORMULA) {
-            φ += c8χ * sin(8*φ)
-               + c6χ * sin(6*φ)
-               + c4χ * sin(4*φ)
-               + c2χ * sin(2*φ);
+        if (!ALLOW_TRIGONOMETRIC_IDENTITIES) {
+            φ += ci8 * sin(8*φ)
+               + ci6 * sin(6*φ)
+               + ci4 * sin(4*φ)
+               + ci2 * sin(2*φ);
         } else {
             /*
              * Same formula than above, be rewriten using trigonometric identities in order to have only two
-             * calls to Math.sin/cos instead than 5. The performance gain is twice faster on some machines.
+             * calls to Math.sin/cos instead than 5. The performance gain is twice faster on tested machine.
              */
-            final double sin2χ     = sin(2*φ);
-            final double sin_cos2χ = cos(2*φ) * sin2χ;
-            final double sin_sin2χ = sin2χ * sin2χ;
-            φ += c8χ * (0.50 - sin_sin2χ)*sin_cos2χ     // ÷8 compared to original formula
-               + c6χ * (0.75 - sin_sin2χ)*sin2χ         // ÷4 compared to original formula
-               + c4χ * (       sin_cos2χ)               // ÷2 compared to original formula
-               + c2χ * sin2χ;
+            final double sin_2φ = sin(2*φ);
+            final double sin2 = sin_2φ * sin_2φ;
+            φ += ((ci4 + ci8 * (0.50 - sin2)) * cos(2*φ)
+                + (ci2 + ci6 * (0.75 - sin2))) * sin_2φ;
         }
         /*
          * Note: a previous version checked if the value of the smallest term c8χ⋅sin(8φ) was smaller than
@@ -336,6 +385,18 @@ abstract class ConformalProjection exten
      */
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
-        initialize();
+        computeCoefficients();
+    }
+
+    /**
+     * Verifies if a trigonometric identity produced the expected value. This method is used in assertions only,
+     * for values close to the [-1 … +1] range. The tolerance threshold is approximatively 1.5E-12 (note that it
+     * still about 7000 time greater than {@code Math.ulp(1.0)}).
+     *
+     * @see #ALLOW_TRIGONOMETRIC_IDENTITIES
+     */
+    static boolean identityEquals(final double actual, final double expected) {
+        return !(abs(actual - expected) >                               // Use !(a > b) instead of (a <= b) in order to tolerate NaN.
+                (ANGULAR_TOLERANCE / 1000) * max(1, abs(expected)));    // Increase tolerance for values outside the [-1 … +1] range.
     }
 }

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -210,6 +210,7 @@ public class LambertConicConformal exten
     @Workaround(library="JDK", version="1.7")
     private LambertConicConformal(final Initializer initializer) {
         super(initializer);
+        super.computeCoefficients();
         double φ0 = initializer.getAndStore(((initializer.variant & 1) != 0) ?  // Odd 'type' are SP1, even 'type' are SP2.
                 LambertConformal1SP.LATITUDE_OF_ORIGIN : LambertConformal2SP.LATITUDE_OF_FALSE_ORIGIN);
         /*
@@ -367,7 +368,7 @@ public class LambertConicConformal exten
 
     /**
      * Returns the sequence of <cite>normalization</cite> → {@code this} → <cite>denormalization</cite> transforms
-     * as a whole. The transform returned by this method except (<var>longitude</var>, <var>latitude</var>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      *
      * <p>The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -201,6 +201,7 @@ public class Mercator extends ConformalP
     @Workaround(library="JDK", version="1.7")
     private Mercator(final Initializer initializer) {
         super(initializer);
+        super.computeCoefficients();
         this.variant = initializer.variant;
         /*
          * The "Longitude of natural origin" parameter is found in all Mercator projections and is mandatory.
@@ -302,7 +303,7 @@ public class Mercator extends ConformalP
 
     /**
      * Returns the sequence of <cite>normalization</cite> → {@code this} → <cite>denormalization</cite> transforms
-     * as a whole. The transform returned by this method except (<var>longitude</var>, <var>latitude</var>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      *
      * <p>The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -116,7 +116,7 @@ import java.util.Objects;
  * @author  Rueben Schulz (UBC)
  * @author  Rémi Maréchal (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see ContextualParameters
@@ -456,7 +456,7 @@ public abstract class NormalizedProjecti
 
     /**
      * Returns the sequence of <cite>normalization</cite> → {@code this} → <cite>denormalization</cite> transforms
-     * as a whole. The transform returned by this method except (<var>longitude</var>, <var>latitude</var>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      * Conversion to other units and {@linkplain org.apache.sis.referencing.cs.CoordinateSystems#swapAndScaleAxes
      * changes in axis order} are <strong>not</strong> managed by the returned transform.

Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java?rev=1707296&r1=1707295&r2=1707296&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] Wed Oct  7 13:20:26 2015
@@ -133,6 +133,7 @@ public class PolarStereographic extends
     @Workaround(library="JDK", version="1.7")
     private PolarStereographic(final Initializer initializer) {
         super(initializer);
+        super.computeCoefficients();
         final byte variant = initializer.variant;
         /*
          * "Standard parallel" and "Latitude of origin" should be mutually exclusive,
@@ -248,7 +249,7 @@ public class PolarStereographic extends
 
     /**
      * Returns the sequence of <cite>normalization</cite> → {@code this} → <cite>denormalization</cite> transforms
-     * as a whole. The transform returned by this method except (<var>longitude</var>, <var>latitude</var>)
+     * as a whole. The transform returned by this method expects (<var>longitude</var>, <var>latitude</var>)
      * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>.
      *
      * <p>The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid



Mime
View raw message