sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1707309 [2/3] - in /sis/trunk: ./ 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/sis-metad...
Date Wed, 07 Oct 2015 13:59:48 GMT
Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -57,56 +57,32 @@ import static org.apache.sis.math.MathFu
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see Mercator
  * @see ObliqueMercator
  */
-public class TransverseMercator extends NormalizedProjection {
+public class TransverseMercator extends ConformalProjection {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = -4717976245811852528L;
+    private static final long serialVersionUID = 8167193470189463354L;
 
     /**
-     * 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)}
-     * and similar method. The identities used are:
+     * Coefficients in the series expansion of the forward projection,
+     * depending only on {@linkplain #excentricity excentricity} value.
+     * The series expansion is of the following form:
      *
-     * <ul>
-     *   <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>
+     *     <blockquote>cf₂⋅f(2θ) + cf₄⋅f(4θ) + cf₆⋅f(6θ) + cf₈⋅f(8θ)</blockquote>
      *
-     * Hyperbolic formulas:
-     * <ul>
-     *   <li>sinh(2θ) = 2⋅sinhθ⋅coshθ</li>
-     *   <li>cosh(2θ) = cosh²θ + sinh²θ =  2cosh²θ - 1 = 1 + 2sinh²θ</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>
+     * Those coefficients are named h₁, h₂, h₃ and h₄ in §1.3.5.1 of
+     * IOGP Publication 373-7-2 – Geomatics Guidance Note number 7, part 2 – April 2015.
      *
-     * 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).
-     */
-    private static final boolean ORIGINAL_FORMULA = false;
-
-    /**
-     * Internal coefficients for computation, depending only on value of excentricity.
-     * Defined in §1.3.5.1 of IOGP Publication 373-7-2 – Geomatics Guidance Note number 7, part 2 – April 2015.
+     * <p><strong>Consider those fields as final!</strong>
+     * They are not final only for the purpose of {@link #computeCoefficients()}.</p>
      */
-    private final double h1, h2, h3, h4, ih1, ih2, ih3, ih4;
+    private transient double cf2, cf4, cf6, cf8;
 
     /**
      * Creates a Transverse Mercator projection from the given parameters.
@@ -160,7 +136,6 @@ public class TransverseMercator extends
          * Opportunistically use double-double arithmetic for computation of B since we will store
          * it in the denormalization matrix, and there is no sine/cosine functions involved here.
          */
-        final double n;
         final DoubleDouble B;
         {   // For keeping the 't' variable locale.
             /*
@@ -169,35 +144,16 @@ public class TransverseMercator extends
              */
             final DoubleDouble t = initializer.axisLengthRatio();   // t  =  b/a
             t.ratio_1m_1p();                                        // t  =  (1 - t) / (1 + t)
-            n = t.doubleValue();
+            computeCoefficients(t.doubleValue());
             /*
              * Compute B  =  (1 + n²/4 + n⁴/64) / (1 + n)
              */
             B = new DoubleDouble(t);        // B  =  n
             B.square();
             B.series(1, 0.25, 1./64);       // B  =  (1 + n²/4 + n⁴/64)
-            t.add(1,0);
+            t.add(1, 0);
             B.divide(t);                    // B  =  (1 + n²/4 + n⁴/64) / (1 + n)
         }
-        final double n2 = n  * n;
-        final double n3 = n2 * n;
-        final double n4 = n2 * n2;
-        /*
-         * Coefficients for direct projection.
-         * Add the smallest values first in order to reduce rounding errors.
-         */
-        h1 = (   41. /    180)*n4  +  ( 5. /  16)*n3  +  (-2. /  3)*n2  +  n/2;
-        h2 = (  557. /   1440)*n4  +  (-3. /   5)*n3  +  (13. / 48)*n2;
-        h3 = ( -103. /    140)*n4  +  (61. / 240)*n3;
-        h4 = (49561. / 161280)*n4;
-        /*
-         * Coefficients for inverse projection.
-         * Add the smallest values first in order to reduce rounding errors.
-         */
-        ih1 = (  -1. /    360)*n4  +  (37. /  96)*n3  +  (-2. /  3)*n2  +  n/2;
-        ih2 = (-437. /   1440)*n4  +  ( 1. /  15)*n3  +  ( 1. / 48)*n2;
-        ih3 = ( -37. /    840)*n4  +  (17. / 480)*n3;
-        ih4 = (4397. / 161280)*n4;
         /*
          * Compute M₀ = B⋅(ξ₁ + ξ₂ + ξ₃ + ξ₄) and negate in anticipation for what will be needed
          * in the denormalization matrix. We opportunistically use double-double arithmetic but
@@ -211,10 +167,10 @@ public class TransverseMercator extends
         final double Q = asinh(tan(φ0)) - excentricity * atanh(excentricity * sin(φ0));
         final double β = atan(sinh(Q));
         final DoubleDouble M0 = new DoubleDouble();
-        M0.value = h4 * sin(8*β)
-                 + h3 * sin(6*β)
-                 + h2 * sin(4*β)
-                 + h1 * sin(2*β)
+        M0.value = cf8 * sin(8*β)
+                 + cf6 * sin(6*β)
+                 + cf4 * sin(4*β)
+                 + cf2 * sin(2*β)
                  + β;
         M0.multiply(B);
         M0.negate();
@@ -235,6 +191,81 @@ public class TransverseMercator extends
         final MatrixSIS denormalize = context.getMatrix(false);
         denormalize.convertBefore(0, B, null);
         denormalize.convertBefore(1, B, M0);
+        /*
+         * 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.  Note: this factorization can only be performed after
+         * the constructor finished to compute other constants.
+         */
+        if (ALLOW_TRIGONOMETRIC_IDENTITIES) {
+            // Multiplication by powers of 2 does not bring any additional rounding error.
+            cf4 *=  4;   ci4 *=  4;
+            cf6 *= 16;   ci6 *= 16;
+            cf8 *= 64;   ci8 *= 64;
+        }
+    }
+
+    /**
+     * Automatically invoked after deserialization for restoring transient fields.
+     */
+    @Override
+    final void computeCoefficients() {
+        /*
+         * Double-double precision is necessary for computing 'n', otherwise we have rounding errors
+         * in the 3 last digits. Note that we still have sometime a 1 ULP difference compared to the
+         * 'n' value at serialization time.
+         */
+        final DoubleDouble t = new DoubleDouble(1, 0);
+        t.subtract(excentricitySquared, 0);
+        t.sqrt();
+        t.ratio_1m_1p();
+        computeCoefficients(t.doubleValue());
+        if (ALLOW_TRIGONOMETRIC_IDENTITIES) {
+            // Same scaling than in the constructor.
+            cf4 *=  4;   ci4 *=  4;
+            cf6 *= 16;   ci6 *= 16;
+            cf8 *= 64;   ci8 *= 64;
+        }
+    }
+
+    /**
+     * Computes the transient fields after construction or deserialization.
+     * The <var>n</var> parameter is defined by the EPSG guide as:
+     *
+     *     <blockquote>n = f / (2-f)</blockquote>
+     *
+     * Where <var>f</var> is the flattening factor (1 - b/a). This equation can be rewritten as:
+     *
+     *     <blockquote>n = (1 - b/a) / (1 + b/a)</blockquote>
+     *
+     * As much as possible, b/a should be computed from the map projection parameters.
+     * However if those parameters are not available anymore, then they can be computed
+     * from the excentricity as:
+     *
+     *     <blockquote>b/a = √(1 - ℯ²)</blockquote>
+     *
+     * @param n The value of {@code f / (2-f)} where {@code f} is the flattening factor.
+     */
+    private void computeCoefficients(final double n) {
+        final double n2 = n  * n;
+        final double n3 = n2 * n;
+        final double n4 = n2 * n2;
+        /*
+         * Coefficients for the forward projections.
+         * Add the smallest values first in order to reduce rounding errors.
+         */
+        cf2 = (   41. /    180)*n4  +  ( 5. /  16)*n3  +  (-2. /  3)*n2  +  n/2;
+        cf4 = (  557. /   1440)*n4  +  (-3. /   5)*n3  +  (13. / 48)*n2;
+        cf6 = ( -103. /    140)*n4  +  (61. / 240)*n3;
+        cf8 = (49561. / 161280)*n4;
+        /*
+         * Coefficients for the inverse projections.
+         * Add the smallest values first in order to reduce rounding errors.
+         */
+        ci2 = (  -1. /    360)*n4  +  (37. /  96)*n3  +  (-2. /  3)*n2  +  n/2;
+        ci4 = (-437. /   1440)*n4  +  ( 1. /  15)*n3  +  ( 1. / 48)*n2;
+        ci6 = ( -37. /    840)*n4  +  (17. / 480)*n3;
+        ci8 = (4397. / 161280)*n4;
     }
 
     /**
@@ -242,19 +273,15 @@ public class TransverseMercator extends
      */
     TransverseMercator(final TransverseMercator other) {
         super(other);
-        h1  = other. h1;
-        h2  = other. h2;
-        h3  = other. h3;
-        h4  = other. h4;
-        ih1 = other.ih1;
-        ih2 = other.ih2;
-        ih3 = other.ih3;
-        ih4 = other.ih4;
+        cf2 = other.cf2;
+        cf4 = other.cf4;
+        cf6 = other.cf6;
+        cf8 = other.cf8;
     }
 
     /**
      * 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
@@ -286,15 +313,13 @@ public class TransverseMercator extends
                             final double[] dstPts, final int dstOff,
                             final boolean derivate) throws ProjectionException
     {
-        final double λ     = srcPts[srcOff];
-        final double φ     = srcPts[srcOff + 1];
-
-        final double ℯsinφ = excentricity * sin(φ);
-        final double Q     = asinh(tan(φ)) - atanh(ℯsinφ) * excentricity;
+        final double λ     = srcPts[srcOff  ];
+        final double φ     = srcPts[srcOff+1];
         final double sinλ  = sin(λ);
+        final double ℯsinφ = sin(φ) * excentricity;
+        final double Q     = asinh(tan(φ)) - atanh(ℯsinφ) * excentricity;
         final double coshQ = cosh(Q);
         final double η0    = atanh(sinλ / coshQ);
-
         /*
          * Original formula: η0 = atanh(sin(λ) * cos(β)) where
          * cos(β) = cos(atan(sinh(Q)))
@@ -307,16 +332,15 @@ public class TransverseMercator extends
          */
         final double coshη0 = cosh(η0);
         final double ξ0     = asin(tanh(Q) * coshη0);
-
         /*
          * Compute sin(2⋅ξ₀), sin(4⋅ξ₀), sin(6⋅ξ₀), sin(8⋅ξ₀) and same for cos, but using the following
          * trigonometric identities in order to reduce the number of calls to Math.sin and cos methods.
          */
-        final double sin_2ξ0, sin_4ξ0, sin_6ξ0, sin_8ξ0,
-                     cos_2ξ0, cos_4ξ0, cos_6ξ0, cos_8ξ0;
-        if (ORIGINAL_FORMULA) {
-            sin_2ξ0 = sin(2*ξ0);
-            cos_2ξ0 = cos(2*ξ0);
+        final double sin_2ξ0 = sin(2*ξ0);
+        final double cos_2ξ0 = cos(2*ξ0);
+        final double sin_4ξ0, sin_6ξ0, sin_8ξ0,
+                     cos_4ξ0, cos_6ξ0, cos_8ξ0;
+        if (!ALLOW_TRIGONOMETRIC_IDENTITIES) {
             sin_4ξ0 = sin(4*ξ0);
             cos_4ξ0 = cos(4*ξ0);
             sin_6ξ0 = sin(6*ξ0);
@@ -324,28 +348,25 @@ public class TransverseMercator extends
             sin_8ξ0 = sin(8*ξ0);
             cos_8ξ0 = cos(8*ξ0);
         } else {
-            sin_2ξ0 = sin(2*ξ0);                              // sin(2⋅ξ₀);
-            cos_2ξ0 = cos(2*ξ0);                              // cos(2⋅ξ₀)
             final double sin2 = sin_2ξ0 * sin_2ξ0;
             final double cos2 = cos_2ξ0 * cos_2ξ0;
-            sin_4ξ0 = 2 * sin_2ξ0 * cos_2ξ0;                  // sin(4⋅ξ₀)
-            cos_4ξ0 = cos2 - sin2;                            // cos(4⋅ξ₀)
-            sin_6ξ0 = (3 - 4*sin2) * sin_2ξ0;                 // sin(6⋅ξ₀)
-            cos_6ξ0 = (4*cos2 - 3) * cos_2ξ0;                 // cos(6⋅ξ₀)
-            sin_8ξ0 = 4*cos_4ξ0 * (sin_2ξ0 * cos_2ξ0);        // sin(8⋅ξ₀)
-            cos_8ξ0 = 1 - 2*sin_4ξ0*sin_4ξ0;                  // cos(8⋅ξ₀)
+            sin_4ξ0 = sin_2ξ0 * cos_2ξ0;                assert identityEquals(sin_4ξ0, sin(4*ξ0) / 2) : ξ0;
+            cos_4ξ0 = (cos2  - sin2)   * 0.5;           assert identityEquals(cos_4ξ0, cos(4*ξ0) / 2) : ξ0;
+            sin_6ξ0 = (0.75  - sin2)   * sin_2ξ0;       assert identityEquals(sin_6ξ0, sin(6*ξ0) / 4) : ξ0;
+            cos_6ξ0 = (cos2  - 0.75)   * cos_2ξ0;       assert identityEquals(cos_6ξ0, cos(6*ξ0) / 4) : ξ0;
+            sin_8ξ0 =          sin_4ξ0 * cos_4ξ0;       assert identityEquals(sin_8ξ0, sin(8*ξ0) / 8) : ξ0;
+            cos_8ξ0 =  0.125 - sin_4ξ0 * sin_4ξ0;       assert identityEquals(cos_8ξ0, cos(8*ξ0) / 8) : ξ0;
         }
-
         /*
          * Compute sinh(2⋅ξ₀), sinh(4⋅ξ₀), sinh(6⋅ξ₀), sinh(8⋅ξ₀) and same for cosh, but using the following
          * hyperbolic identities in order to reduce the number of calls to Math.sinh and cosh methods.
          * Note that the formulas are very similar to the above ones, with only some signs reversed.
          */
-        final double sinh_2η0, sinh_4η0, sinh_6η0, sinh_8η0,
-                     cosh_2η0, cosh_4η0, cosh_6η0, cosh_8η0;
-        if (ORIGINAL_FORMULA) {
-            sinh_2η0 = sinh(2*η0);
-            cosh_2η0 = cosh(2*η0);
+        final double sinh_2η0 = sinh(2*η0);
+        final double cosh_2η0 = cosh(2*η0);
+        final double sinh_4η0, sinh_6η0, sinh_8η0,
+                     cosh_4η0, cosh_6η0, cosh_8η0;
+        if (!ALLOW_TRIGONOMETRIC_IDENTITIES) {
             sinh_4η0 = sinh(4*η0);
             cosh_4η0 = cosh(4*η0);
             sinh_6η0 = sinh(6*η0);
@@ -353,18 +374,15 @@ public class TransverseMercator extends
             sinh_8η0 = sinh(8*η0);
             cosh_8η0 = cosh(8*η0);
         } else {
-            sinh_2η0 = sinh(2*η0);                              // sinh(2⋅η₀);
-            cosh_2η0 = cosh(2*η0);                              // cosh(2⋅η₀)
             final double sinh2 = sinh_2η0 * sinh_2η0;
             final double cosh2 = cosh_2η0 * cosh_2η0;
-            sinh_4η0 = 2 * sinh_2η0 * cosh_2η0;                 // sinh(4⋅η₀)
-            cosh_4η0 = cosh2 + sinh2;                           // cosh(4⋅η₀)
-            sinh_6η0 = (3 + 4*sinh2) * sinh_2η0;                // sinh(6⋅η₀)
-            cosh_6η0 = (4*cosh2 - 3) * cosh_2η0;                // cosh(6⋅η₀)
-            sinh_8η0 = 4*cosh_4η0 * (sinh_2η0 * cosh_2η0);      // sinh(8⋅η₀)
-            cosh_8η0 = 1 + 2*sinh_4η0*sinh_4η0;                 // cosh(8⋅η₀)
+            cosh_4η0 = (cosh2 + sinh2) * 0.5;           assert identityEquals(cosh_4η0, cosh(4*η0) / 2) : η0;
+            sinh_4η0 = cosh_2η0 * sinh_2η0;             assert identityEquals(sinh_4η0, sinh(4*η0) / 2) : η0;
+            cosh_6η0 = cosh_2η0 * (cosh2   - 0.75);     assert identityEquals(cosh_6η0, cosh(6*η0) / 4) : η0;
+            sinh_6η0 = sinh_2η0 * (sinh2   + 0.75);     assert identityEquals(sinh_6η0, sinh(6*η0) / 4) : η0;
+            cosh_8η0 = sinh_4η0 * sinh_4η0 + 0.125;     assert identityEquals(cosh_8η0, cosh(8*η0) / 8) : η0;
+            sinh_8η0 = sinh_4η0 * cosh_4η0;             assert identityEquals(sinh_8η0, sinh(8*η0) / 8) : η0;
         }
-
         /*
          * Assuming that (λ, φ) ↦ Proj((λ, φ))
          * where Proj is defined by: Proj((λ, φ)) : (η(λ, φ), ξ(λ, φ)).
@@ -372,36 +390,35 @@ public class TransverseMercator extends
          * => (λ, φ) ↦ (η(λ, φ), ξ(λ, φ)).
          */
         //-- ξ(λ, φ)
-        final double ξ = h4 * sin_8ξ0 * cosh_8η0
-                       + h3 * sin_6ξ0 * cosh_6η0
-                       + h2 * sin_4ξ0 * cosh_4η0
-                       + h1 * sin_2ξ0 * cosh_2η0
+        final double ξ = cf8 * sin_8ξ0 * cosh_8η0
+                       + cf6 * sin_6ξ0 * cosh_6η0
+                       + cf4 * sin_4ξ0 * cosh_4η0
+                       + cf2 * sin_2ξ0 * cosh_2η0
                        + ξ0;
 
         //-- η(λ, φ)
-        final double η = h4 * cos_8ξ0 * sinh_8η0
-                       + h3 * cos_6ξ0 * sinh_6η0
-                       + h2 * cos_4ξ0 * sinh_4η0
-                       + h1 * cos_2ξ0 * sinh_2η0
+        final double η = cf8 * cos_8ξ0 * sinh_8η0
+                       + cf6 * cos_6ξ0 * sinh_6η0
+                       + cf4 * cos_4ξ0 * sinh_4η0
+                       + cf2 * cos_2ξ0 * sinh_2η0
                        + η0;
 
         if (dstPts != null) {
-            dstPts[dstOff    ] = η;
-            dstPts[dstOff + 1] = ξ;
+            dstPts[dstOff  ] = η;
+            dstPts[dstOff+1] = ξ;
         }
-
         if (!derivate) {
             return null;
         }
 
-        final double cosλ          = cos(λ);                                     //-- λ
-        final double cosφ          = cos(φ);                                     //-- φ
-        final double cosh2Q        = coshQ * coshQ;                              //-- Q
+        final double cosλ          = cos(λ);                                        //-- λ
+        final double cosφ          = cos(φ);                                        //-- φ
+        final double cosh2Q        = coshQ * coshQ;                                 //-- Q
         final double sinhQ         = sinh(Q);
         final double tanhQ         = tanh(Q);
-        final double cosh2Q_sin2λ  = cosh2Q - sinλ * sinλ;                       //-- Qλ
-        final double sinhη0        = sinh(η0);                                   //-- η0
-        final double sqrt1_thQchη0 = sqrt(1 - tanhQ * tanhQ * coshη0 * coshη0);  //-- Qη0
+        final double cosh2Q_sin2λ  = cosh2Q - (sinλ * sinλ);                        //-- Qλ
+        final double sinhη0        = sinh(η0);                                      //-- η0
+        final double sqrt1_thQchη0 = sqrt(1 - (tanhQ * tanhQ) * (coshη0 * coshη0)); //-- Qη0
 
         //-- dQ_dλ = 0;
         final double dQ_dφ  = 1 / cosφ - excentricitySquared * cosφ / (1 - ℯsinφ * ℯsinφ);
@@ -411,11 +428,10 @@ public class TransverseMercator extends
 
         final double dξ0_dλ = sinhQ * sinhη0 * cosλ / (cosh2Q_sin2λ * sqrt1_thQchη0);
         final double dξ0_dφ = (dQ_dφ * coshη0 / cosh2Q + dη0_dφ * sinhη0 * tanhQ) / sqrt1_thQchη0;
-
         /*
          * Assuming that Jac(Proj((λ, φ))) is the Jacobian matrix of Proj((λ, φ)) function.
          *
-         * So derivative Proj((λ, φ)) is defined by:
+         * So the derivative of Proj((λ, φ)) is defined by:
          *                    ┌                              ┐
          *                    │ dη(λ, φ) / dλ, dη(λ, φ) / dφ │
          * Jac              = │                              │
@@ -424,31 +440,31 @@ public class TransverseMercator extends
          */
         //-- dξ(λ, φ) / dλ
         final double dξ_dλ = dξ0_dλ
-                           + 2 * (h1 * (dξ0_dλ * cos_2ξ0 * cosh_2η0 + dη0_dλ * sinh_2η0 * sin_2ξ0)
-                           + 3 *  h3 * (dξ0_dλ * cos_6ξ0 * cosh_6η0 + dη0_dλ * sinh_6η0 * sin_6ξ0)
-                           + 2 * (h2 * (dξ0_dλ * cos_4ξ0 * cosh_4η0 + dη0_dλ * sinh_4η0 * sin_4ξ0)
-                           + 2 *  h4 * (dξ0_dλ * cos_8ξ0 * cosh_8η0 + dη0_dλ * sinh_8η0 * sin_8ξ0)));
+                           + 2 * (cf2 * (dξ0_dλ * cos_2ξ0 * cosh_2η0 + dη0_dλ * sinh_2η0 * sin_2ξ0)
+                           + 3 *  cf6 * (dξ0_dλ * cos_6ξ0 * cosh_6η0 + dη0_dλ * sinh_6η0 * sin_6ξ0)
+                           + 2 * (cf4 * (dξ0_dλ * cos_4ξ0 * cosh_4η0 + dη0_dλ * sinh_4η0 * sin_4ξ0)
+                           + 2 *  cf8 * (dξ0_dλ * cos_8ξ0 * cosh_8η0 + dη0_dλ * sinh_8η0 * sin_8ξ0)));
 
         //-- dξ(λ, φ) / dφ
         final double dξ_dφ = dξ0_dφ
-                           + 2 * (h1 * (dξ0_dφ * cos_2ξ0 * cosh_2η0 + dη0_dφ * sinh_2η0 * sin_2ξ0)
-                           + 3 *  h3 * (dξ0_dφ * cos_6ξ0 * cosh_6η0 + dη0_dφ * sinh_6η0 * sin_6ξ0)
-                           + 2 * (h2 * (dξ0_dφ * cos_4ξ0 * cosh_4η0 + dη0_dφ * sinh_4η0 * sin_4ξ0)
-                           + 2 *  h4 * (dξ0_dφ * cos_8ξ0 * cosh_8η0 + dη0_dφ * sinh_8η0 * sin_8ξ0)));
+                           + 2 * (cf2 * (dξ0_dφ * cos_2ξ0 * cosh_2η0 + dη0_dφ * sinh_2η0 * sin_2ξ0)
+                           + 3 *  cf6 * (dξ0_dφ * cos_6ξ0 * cosh_6η0 + dη0_dφ * sinh_6η0 * sin_6ξ0)
+                           + 2 * (cf4 * (dξ0_dφ * cos_4ξ0 * cosh_4η0 + dη0_dφ * sinh_4η0 * sin_4ξ0)
+                           + 2 *  cf8 * (dξ0_dφ * cos_8ξ0 * cosh_8η0 + dη0_dφ * sinh_8η0 * sin_8ξ0)));
 
         //-- dη(λ, φ) / dλ
         final double dη_dλ = dη0_dλ
-                           + 2 * (h1 * (dη0_dλ * cosh_2η0 * cos_2ξ0 - dξ0_dλ * sin_2ξ0 * sinh_2η0)
-                           + 3 *  h3 * (dη0_dλ * cosh_6η0 * cos_6ξ0 - dξ0_dλ * sin_6ξ0 * sinh_6η0)
-                           + 2 * (h2 * (dη0_dλ * cosh_4η0 * cos_4ξ0 - dξ0_dλ * sin_4ξ0 * sinh_4η0)
-                           + 2 *  h4 * (dη0_dλ * cosh_8η0 * cos_8ξ0 - dξ0_dλ * sin_8ξ0 * sinh_8η0)));
+                           + 2 * (cf2 * (dη0_dλ * cosh_2η0 * cos_2ξ0 - dξ0_dλ * sin_2ξ0 * sinh_2η0)
+                           + 3 *  cf6 * (dη0_dλ * cosh_6η0 * cos_6ξ0 - dξ0_dλ * sin_6ξ0 * sinh_6η0)
+                           + 2 * (cf4 * (dη0_dλ * cosh_4η0 * cos_4ξ0 - dξ0_dλ * sin_4ξ0 * sinh_4η0)
+                           + 2 *  cf8 * (dη0_dλ * cosh_8η0 * cos_8ξ0 - dξ0_dλ * sin_8ξ0 * sinh_8η0)));
 
         //-- dη(λ, φ) / dφ
         final double dη_dφ = dη0_dφ
-                           + 2 * (h1 * (dη0_dφ * cosh_2η0 * cos_2ξ0 - dξ0_dφ * sin_2ξ0 * sinh_2η0)
-                           + 3 *  h3 * (dη0_dφ * cosh_6η0 * cos_6ξ0 - dξ0_dφ * sin_6ξ0 * sinh_6η0)
-                           + 2 * (h2 * (dη0_dφ * cosh_4η0 * cos_4ξ0 - dξ0_dφ * sin_4ξ0 * sinh_4η0)
-                           + 2 *  h4 * (dη0_dφ * cosh_8η0 * cos_8ξ0 - dξ0_dφ * sin_8ξ0 * sinh_8η0)));
+                           + 2 * (cf2 * (dη0_dφ * cosh_2η0 * cos_2ξ0 - dξ0_dφ * sin_2ξ0 * sinh_2η0)
+                           + 3 *  cf6 * (dη0_dφ * cosh_6η0 * cos_6ξ0 - dξ0_dφ * sin_6ξ0 * sinh_6η0)
+                           + 2 * (cf4 * (dη0_dφ * cosh_4η0 * cos_4ξ0 - dξ0_dφ * sin_4ξ0 * sinh_4η0)
+                           + 2 *  cf8 * (dη0_dφ * cosh_8η0 * cos_8ξ0 - dξ0_dφ * sin_8ξ0 * sinh_8η0)));
 
         return new Matrix2(dη_dλ, dη_dφ,
                            dξ_dλ, dξ_dφ);
@@ -464,20 +480,22 @@ public class TransverseMercator extends
                                     final double[] dstPts, final int dstOff)
             throws ProjectionException
     {
-        final double η = srcPts[srcOff    ];
-        final double ξ = srcPts[srcOff + 1];
+        final double η = srcPts[srcOff  ];
+        final double ξ = srcPts[srcOff+1];
         /*
          * Following calculation of sin_2ξ, sin_4ξ, etc. is basically a copy-and-paste of the code in transform(…).
          * Its purpose is the same than for transform(…): reduce the amount of calls to Math.sin(double) and other
          * methods.
          */
-        final double sin_2ξ,  sin_4ξ,  sin_6ξ,  sin_8ξ,
-                     cos_2ξ,  cos_4ξ,  cos_6ξ,  cos_8ξ,
-                     sinh_2η, sinh_4η, sinh_6η, sinh_8η,
-                     cosh_2η, cosh_4η, cosh_6η, cosh_8η;
-        if (ORIGINAL_FORMULA) {
-            sin_2ξ = sin(2*ξ);
-            cos_2ξ = cos(2*ξ);
+        final double sin_2ξ  = sin (2*ξ);
+        final double cos_2ξ  = cos (2*ξ);
+        final double sinh_2η = sinh(2*η);
+        final double cosh_2η = cosh(2*η);
+        final double sin_4ξ,  sin_6ξ,  sin_8ξ,
+                     cos_4ξ,  cos_6ξ,  cos_8ξ,
+                     sinh_4η, sinh_6η, sinh_8η,
+                     cosh_4η, cosh_6η, cosh_8η;
+        if (!ALLOW_TRIGONOMETRIC_IDENTITIES) {
             sin_4ξ = sin(4*ξ);
             cos_4ξ = cos(4*ξ);
             sin_6ξ = sin(6*ξ);
@@ -485,8 +503,6 @@ public class TransverseMercator extends
             sin_8ξ = sin(8*ξ);
             cos_8ξ = cos(8*ξ);
 
-            sinh_2η = sinh(2*η);
-            cosh_2η = cosh(2*η);
             sinh_4η = sinh(4*η);
             cosh_4η = cosh(4*η);
             sinh_6η = sinh(6*η);
@@ -494,40 +510,36 @@ public class TransverseMercator extends
             sinh_8η = sinh(8*η);
             cosh_8η = cosh(8*η);
         } else {
-            sin_2ξ = sin(2*ξ);
-            cos_2ξ = cos(2*ξ);
             final double sin2 = sin_2ξ * sin_2ξ;
             final double cos2 = cos_2ξ * cos_2ξ;
-            sin_4ξ = 2 * sin_2ξ * cos_2ξ;
-            cos_4ξ = cos2 - sin2;
-            sin_6ξ = (3 - 4*sin2) * sin_2ξ;
-            cos_6ξ = (4*cos2 - 3) * cos_2ξ;
-            sin_8ξ = 4*cos_4ξ * (sin_2ξ * cos_2ξ);
-            cos_8ξ = 1 - 2*sin_4ξ*sin_4ξ;
+            sin_4ξ = sin_2ξ * cos_2ξ;                   assert identityEquals(sin_4ξ, sin(4*ξ) / 2) : ξ;
+            cos_4ξ = (cos2  - sin2)   * 0.5;            assert identityEquals(cos_4ξ, cos(4*ξ) / 2) : ξ;
+            sin_6ξ = (0.75  - sin2)   * sin_2ξ;         assert identityEquals(sin_6ξ, sin(6*ξ) / 4) : ξ;
+            cos_6ξ = (cos2  - 0.75)   * cos_2ξ;         assert identityEquals(cos_6ξ, cos(6*ξ) / 4) : ξ;
+            sin_8ξ =          sin_4ξ * cos_4ξ;          assert identityEquals(sin_8ξ, sin(8*ξ) / 8) : ξ;
+            cos_8ξ =  0.125 - sin_4ξ * sin_4ξ;          assert identityEquals(cos_8ξ, cos(8*ξ) / 8) : ξ;
 
-            sinh_2η = sinh(2*η);
-            cosh_2η = cosh(2*η);
             final double sinh2 = sinh_2η * sinh_2η;
             final double cosh2 = cosh_2η * cosh_2η;
-            sinh_4η = 2 * sinh_2η * cosh_2η;
-            cosh_4η = cosh2 + sinh2;
-            sinh_6η = (3 + 4*sinh2) * sinh_2η;
-            cosh_6η = (4*cosh2 - 3) * cosh_2η;
-            sinh_8η = 4*cosh_4η * (sinh_2η * cosh_2η);
-            cosh_8η = 1 + 2*sinh_4η*sinh_4η;
+            cosh_4η = (cosh2 + sinh2) * 0.5;            assert identityEquals(cosh_4η, cosh(4*η) / 2) : η;
+            sinh_4η = cosh_2η * sinh_2η;                assert identityEquals(sinh_4η, sinh(4*η) / 2) : η;
+            cosh_6η = cosh_2η * (cosh2   - 0.75);       assert identityEquals(cosh_6η, cosh(6*η) / 4) : η;
+            sinh_6η = sinh_2η * (sinh2   + 0.75);       assert identityEquals(sinh_6η, sinh(6*η) / 4) : η;
+            cosh_8η = sinh_4η * sinh_4η + 0.125;        assert identityEquals(cosh_8η, cosh(8*η) / 8) : η;
+            sinh_8η = sinh_4η * cosh_4η;                assert identityEquals(sinh_8η, sinh(8*η) / 8) : η;
         }
         /*
          * The actual inverse transform.
          */
-        final double ξ0 = ξ - (ih4 * sin_8ξ * cosh_8η
-                             + ih3 * sin_6ξ * cosh_6η
-                             + ih2 * sin_4ξ * cosh_4η
-                             + ih1 * sin_2ξ * cosh_2η);
-
-        final double η0 = η - (ih4 * cos_8ξ * sinh_8η
-                             + ih3 * cos_6ξ * sinh_6η
-                             + ih2 * cos_4ξ * sinh_4η
-                             + ih1 * cos_2ξ * sinh_2η);
+        final double ξ0 = ξ - (ci8 * sin_8ξ * cosh_8η
+                             + ci6 * sin_6ξ * cosh_6η
+                             + ci4 * sin_4ξ * cosh_4η
+                             + ci2 * sin_2ξ * cosh_2η);
+
+        final double η0 = η - (ci8 * cos_8ξ * sinh_8η
+                             + ci6 * cos_6ξ * sinh_6η
+                             + ci4 * cos_4ξ * sinh_4η
+                             + ci2 * cos_2ξ * sinh_2η);
 
         final double β = asin(sin(ξ0) / cosh(η0));
         final double Q = asinh(tan(β));
@@ -539,8 +551,8 @@ public class TransverseMercator extends
             final double c = excentricity * atanh(excentricity * tanh(Qp));
             Qp = Q + c;
             if (abs(c - p) <= ITERATION_TOLERANCE) {
-                dstPts[dstOff    ] = asin(tanh(η0) / cos(β));
-                dstPts[dstOff + 1] = atan(sinh(Qp));
+                dstPts[dstOff  ] = asin(tanh(η0) / cos(β));
+                dstPts[dstOff+1] = atan(sinh(Qp));
                 return;
             }
             p = c;
@@ -582,8 +594,8 @@ public class TransverseMercator extends
                                 final double[] dstPts, final int dstOff,
                                 final boolean derivate) throws ProjectionException
         {
-            final double λ    = srcPts[srcOff];
-            final double φ    = srcPts[srcOff + 1];
+            final double λ    = srcPts[srcOff  ];
+            final double φ    = srcPts[srcOff+1];
             final double sinλ = sin(λ);
             final double cosλ = cos(λ);
             final double sinφ = sin(φ);

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/LambertConicConformalTest.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -37,7 +37,7 @@ import org.junit.Test;
 
 import static java.lang.StrictMath.*;
 import static java.lang.Double.*;
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 // Branch-specific imports
 import static org.junit.Assume.assumeTrue;
@@ -53,7 +53,7 @@ import static org.apache.sis.test.Assert
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Rémi Maréchal (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  */
 @DependsOn(ConformalProjectionTest.class)
@@ -340,4 +340,23 @@ public final strictfp class LambertConic
         tolerance = Formulas.LINEAR_TOLERANCE;
         compareEllipticalWithSpherical(CoordinateDomain.GEOGRAPHIC_SAFE, 0);
     }
+
+    /**
+     * Verifies that deserialized projections work as expected. This implies that deserialization
+     * recomputed the internal transient fields, especially the series expansion coefficients.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a coordinate.
+     */
+    @Test
+    @DependsOnMethod("testLambertConicConformal1SP")
+    public void testSerialization() throws FactoryException, TransformException {
+        createNormalizedProjection(true, 40);
+        final double[] source = CoordinateDomain.GEOGRAPHIC_RADIANS_NORTH.generateRandomInput(TestUtilities.createRandomNumberGenerator(), 2, 10);
+        final double[] target = new double[source.length];
+        transform.transform(source, 0, target, 0, 10);
+        transform = assertSerializedEquals(transform);
+        tolerance = Formulas.LINEAR_TOLERANCE;
+        verifyTransform(source, target);
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -75,6 +75,7 @@ final strictfp class NoOp extends Confor
         super(new Initializer(new DefaultOperationMethod(
                 Collections.singletonMap(DefaultOperationMethod.NAME_KEY, parameters.getDescriptor().getName()),
                 2, 2, parameters.getDescriptor()), parameters, Collections.<ParameterRole, ParameterDescriptor<Double>>emptyMap(), (byte) 0));
+        super.computeCoefficients();
     }
 
     /**

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/TransverseMercatorTest.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -24,9 +24,11 @@ import org.apache.sis.parameter.Paramete
 import org.apache.sis.referencing.operation.transform.CoordinateDomain;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestUtilities;
 import org.junit.Test;
 
 import static java.lang.StrictMath.toRadians;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -34,7 +36,7 @@ import static java.lang.StrictMath.toRad
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.6
- * @version 0.6
+ * @version 0.7
  * @module
  */
 @DependsOn(NormalizedProjectionTest.class)
@@ -138,4 +140,23 @@ public final strictfp class TransverseMe
         verifyDerivative(toRadians(-3), toRadians(30));
         verifyDerivative(toRadians(+6), toRadians(60));
     }
+
+    /**
+     * Verifies that deserialized projections work as expected. This implies that deserialization
+     * recomputed the internal transient fields, especially the series expansion coefficients.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a coordinate.
+     */
+    @Test
+    @DependsOnMethod("testTransverseMercator")
+    public void testSerialization() throws FactoryException, TransformException {
+        createNormalizedProjection(true, 40);
+        final double[] source = CoordinateDomain.GEOGRAPHIC_RADIANS_HALF_λ.generateRandomInput(TestUtilities.createRandomNumberGenerator(), 2, 10);
+        final double[] target = new double[source.length];
+        transform.transform(source, 0, target, 0, 10);
+        transform = assertSerializedEquals(transform);
+        tolerance = Formulas.LINEAR_TOLERANCE;
+        verifyTransform(source, target);
+    }
 }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/IdentifierMapAdapter.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -16,8 +16,10 @@
  */
 package org.apache.sis.internal.jaxb;
 
+import java.net.URI;
 import java.util.Set;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Collection;
 import java.util.Collections;
@@ -28,7 +30,7 @@ import java.io.Serializable;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.util.Debug;
-import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.xml.XLink;
 import org.apache.sis.xml.IdentifierMap;
 import org.apache.sis.xml.IdentifierSpace;
 
@@ -39,14 +41,21 @@ import org.apache.sis.internal.jdk7.Obje
 
 
 /**
- * A map of identifiers which can be used as a helper class for
- * {@link org.apache.sis.xml.IdentifiedObject} implementations.
+ * Implementation of the map of identifiers associated to {@link org.apache.sis.xml.IdentifiedObject} instances.
+ * This base class implements an unmodifiable map, but the {@link ModifiableIdentifierMap} subclass add write
+ * capabilities.
  *
  * <p>This class works as a wrapper around a collection of identifiers. Because all operations
  * are performed by an iteration over the collection elements, this implementation is suitable
  * only for small maps (less than 10 elements). Given that objects typically have only one or
  * two identifiers, this is considered acceptable.</p>
  *
+ * <div class="section">Special cases</div>
+ * The identifiers for the following authorities are handled in a special way:
+ * <ul>
+ *   <li>{@link IdentifierSpace#HREF}: handled as a shortcut to {@link XLink#getHRef()}.</li>
+ * </ul>
+ *
  * <div class="section">Handling of duplicated authorities</div>
  * The collection shall not contain more than one identifier for the same
  * {@linkplain Identifier#getAuthority() authority}. However duplications may happen if the user
@@ -65,22 +74,17 @@ import org.apache.sis.internal.jdk7.Obje
  * </ul>
  *
  * <div class="section">Handling of null identifiers</div>
- * The collection of identifiers shall not contains any null element. This is normally ensured by
+ * The collection of identifiers shall not contain any null element. This is normally ensured by
  * the {@link org.apache.sis.metadata.ModifiableMetadata} internal collection implementations.
- * This class performs opportunist null checks as an additional safety. However because we perform
- * those checks only in opportunist ways, the following inconsistencies remain:
- *
- * <ul>
- *   <li>{@link #isEmpty()} may return {@code false} when the more accurate {@link #size()}
- *       method returns 0.</li>
- * </ul>
+ * This class performs opportunist null checks as an additional safety, but consistency is not
+ * guaranteed. See {@link #size()} for more information.
  *
  * <div class="section">Thread safety</div>
  * This class is thread safe if the underlying identifier collection is thread safe.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.7
  * @module
  *
  * @see org.apache.sis.xml.IdentifiedObject
@@ -106,46 +110,113 @@ public class IdentifierMapAdapter extend
      *
      * @param identifiers The identifiers to wrap in a map view.
      */
-    IdentifierMapAdapter(final Collection<Identifier> identifiers) {
+    public IdentifierMapAdapter(final Collection<Identifier> identifiers) {
         this.identifiers = identifiers;
     }
 
     /**
-     * Removes every entries in the underlying collection.
+     * If the given authority is a special case, returns its {@link NonMarshalledAuthority} integer enum.
+     * Otherwise returns -1. See javadoc for more information about special cases.
      *
-     * @throws UnsupportedOperationException If the collection of identifiers is unmodifiable.
+     * @param authority A {@link Citation} constant. The type is relaxed to {@code Object}
+     *        because the signature of some {@code Map} methods are that way.
      */
-    @Override
-    public void clear() throws UnsupportedOperationException {
-        identifiers.clear();
+    static int specialCase(final Object authority) {
+        if (authority == IdentifierSpace.HREF) return NonMarshalledAuthority.HREF;
+        // A future Apache SIS version may add more special cases here.
+        return -1;
+    }
+
+    /**
+     * Extracts the {@code xlink:href} value from the {@link XLink} if presents.
+     * This method does not test if an explicit {@code xlink:href} identifier exists;
+     * this check must be done by the caller <strong>before</strong> to invoke this method.
+     *
+     * @see ModifiableIdentifierMap#setHRef(URI)
+     */
+    private URI getHRef() {
+        final Identifier identifier = getIdentifier(IdentifierSpace.XLINK);
+        if (identifier instanceof SpecializedIdentifier<?>) {
+            final Object link = ((SpecializedIdentifier<?>) identifier).value;
+            if (link instanceof XLink) {
+                return ((XLink) link).getHRef();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the string representation of the given value, or {@code null} if none.
+     *
+     * @param value The value returned be one of the above {@code getFoo()} methods.
+     */
+    private static String toString(final Object value) {
+        return (value != null) ? value.toString() : null;
+    }
+
+
+
+
+    ////////////////////////////////////////////////////////////////////////////////////////
+    ////////                                                                        ////////
+    ////////    END OF SPECIAL CASES.                                               ////////
+    ////////                                                                        ////////
+    ////////    Implementation of IdentifierMap methods follow. Each method may     ////////
+    ////////    have a switch statement over the special cases declared above.      ////////
+    ////////                                                                        ////////
+    ////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Whether this map support {@code put} and {@code remove} operations.
+     */
+    boolean isModifiable() {
+        return false;
     }
 
     /**
      * Returns {@code true} if the collection of identifiers contains at least one element.
      * This method does not verify if the collection contains null element (it should not).
-     * Consequently, this method may return {@code false} even if the {@link #size()} method
-     * returns 0.
      */
     @Override
-    public boolean isEmpty() {
+    public final boolean isEmpty() {
         return identifiers.isEmpty();
     }
 
     /**
-     * Returns {@code true} if at least one identifier declares the given
-     * {@linkplain Identifier#getCode() code}.
+     * Counts the number of entries, ignoring null elements and duplicated authorities.
+     *
+     * <p>Because {@code null} elements are ignored, this method may return 0 even if {@link #isEmpty()}
+     * returns {@code false}. However this inconsistency should not happen in practice because
+     * {@link org.apache.sis.metadata.ModifiableMetadata} internal collection implementations
+     * do not allow null values.</p>
+     */
+    @Override
+    public final int size() {
+        final HashSet<Citation> done = new HashSet<Citation>(hashMapCapacity(identifiers.size()));
+        for (final Identifier identifier : identifiers) {
+            if (identifier != null) {
+                done.add(identifier.getAuthority());
+            }
+        }
+        return done.size();
+    }
+
+    /**
+     * Returns {@code true} if at least one identifier declares the given {@linkplain Identifier#getCode() code}.
      *
      * @param  code The code to search, which should be an instance of {@link String}.
      * @return {@code true} if at least one identifier uses the given code.
      */
     @Override
-    public boolean containsValue(final Object code) {
+    public final boolean containsValue(final Object code) {
         if (code instanceof String) {
             for (final Identifier identifier : identifiers) {
                 if (identifier != null && code.equals(identifier.getCode())) {
                     return true;
                 }
             }
+            return code.equals(toString(getHRef()));
+            // A future Apache SIS version may add more special cases here.
         }
         return false;
     }
@@ -158,14 +229,23 @@ public class IdentifierMapAdapter extend
      * @return {@code true} if at least one identifier uses the given authority.
      */
     @Override
-    public boolean containsKey(final Object authority) {
-        return (authority instanceof Citation) && getIdentifier((Citation) authority) != null;
+    public final boolean containsKey(final Object authority) {
+        if (authority instanceof Citation) {
+            if (getIdentifier((Citation) authority) != null) {
+                return true;
+            }
+            switch (specialCase(authority)) {
+                case NonMarshalledAuthority.HREF: return getHRef() != null;
+                // A future Apache SIS version may add more special cases here.
+            }
+        }
+        return false;
     }
 
     /**
      * Returns the identifier for the given key, or {@code null} if none.
      */
-    private Identifier getIdentifier(final Citation authority) {
+    final Identifier getIdentifier(final Citation authority) {
         for (final Identifier identifier : identifiers) {
             if (identifier != null && Objects.equals(authority, identifier.getAuthority())) {
                 return identifier;
@@ -180,148 +260,94 @@ public class IdentifierMapAdapter extend
      */
     @Override
     @SuppressWarnings("unchecked")
-    public <T> T getSpecialized(final IdentifierSpace<T> authority) {
-        final Identifier identifier = getIdentifier(authority);
-        return (identifier instanceof SpecializedIdentifier<?>) ? ((SpecializedIdentifier<T>) identifier).value : null;
-    }
-
-    /**
-     * Returns the code of the first identifier associated with the given authority only if
-     * if is <strong>not</strong> a specialized identifier. Otherwise returns {@code null}.
-     *
-     * <p>This is a helper method for {@link IdentifierMapWithSpecialCases#put(Citation, String)},
-     * in order to be able to return the old value if that value was a {@link String} rather than
-     * the specialized type. We do not return the string for the specialized case in order to avoid
-     * the cost of invoking {@code toString()} on the specialized object (some may be costly). Such
-     * call would be useless because {@code IdentifierMapWithSpecialCase} discard the value of this
-     * method when it found a specialized type.</p>
-     */
-    final String getUnspecialized(final Citation authority) {
+    public final <T> T getSpecialized(final IdentifierSpace<T> authority) {
         final Identifier identifier = getIdentifier(authority);
-        if (identifier != null && !(identifier instanceof SpecializedIdentifier<?>)) {
-            return identifier.getCode();
+        if (identifier instanceof SpecializedIdentifier<?>) {
+            return ((SpecializedIdentifier<T>) identifier).value;
+        }
+        switch (specialCase(authority)) {
+            case NonMarshalledAuthority.HREF: return (T) getHRef();
+            // A future Apache SIS version may add more special cases here.
         }
         return null;
     }
 
     /**
      * Returns the code of the first identifier associated with the given
-     * {@linkplain Identifier#getAuthority() authority}, or {@code null}
-     * if no identifier was found.
+     * {@linkplain Identifier#getAuthority() authority}, or {@code null} if no identifier was found.
      *
      * @param  authority The authority to search, which should be an instance of {@link Citation}.
      * @return The code of the identifier for the given authority, or {@code null} if none.
      */
     @Override
-    public String get(final Object authority) {
+    public final String get(final Object authority) {
         if (authority instanceof Citation) {
             final Identifier identifier = getIdentifier((Citation) authority);
             if (identifier != null) {
                 return identifier.getCode();
             }
+            switch (specialCase(authority)) {
+                case NonMarshalledAuthority.HREF: return toString(getHRef());
+                // A future Apache SIS version may add more special cases here.
+            }
         }
         return null;
     }
 
     /**
      * Removes all identifiers associated with the given {@linkplain Identifier#getAuthority() authority}.
-     * The default implementation delegates to {@link #put(Citation, String)} with a {@code null} value.
      *
      * @param  authority The authority to search, which should be an instance of {@link Citation}.
      * @return The code of the identifier for the given authority, or {@code null} if none.
+     * @throws UnsupportedOperationException if the collection of identifiers is unmodifiable.
      */
     @Override
-    public String remove(final Object authority) {
-        return (authority instanceof Citation) ? put((Citation) authority, null) : null;
+    public String remove(Object authority) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Removes every entries in the underlying collection.
+     *
+     * @throws UnsupportedOperationException if the collection of identifiers is unmodifiable.
+     */
+    @Override
+    public void clear() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
     }
 
     /**
      * Sets the code of the identifier having the given authority to the given value.
-     * If no identifier is found for the given authority, a new one is created. If
-     * more than one identifier is found for the given authority, then all previous
-     * identifiers may be removed in order to ensure that the new entry will be the
-     * first entry, so it can be find by the {@code get} method.
+     * If no identifier is found for the given authority, a new one is created.
+     * If more than one identifier is found for the given authority, then all previous identifiers may be removed
+     * in order to ensure that the new entry will be the first entry, so it can be find by the {@code get} method.
      *
      * @param  authority The authority for which to set the code.
      * @param  code The new code for the given authority, or {@code null} for removing the entry.
      * @return The previous code for the given authority, or {@code null} if none.
+     * @throws UnsupportedOperationException if the collection of identifiers is unmodifiable.
      */
     @Override
-    public String put(final Citation authority, final String code)
-            throws UnsupportedOperationException
-    {
-        ArgumentChecks.ensureNonNull("authority", authority);
-        String old = null;
-        final Iterator<? extends Identifier> it = identifiers.iterator();
-        while (it.hasNext()) {
-            final Identifier identifier = it.next();
-            if (identifier == null) {
-                it.remove(); // Opportunist cleaning, but should not happen.
-            } else if (Objects.equals(authority, identifier.getAuthority())) {
-                if (code != null && identifier instanceof IdentifierMapEntry) {
-                    return ((IdentifierMapEntry) identifier).setValue(code);
-                    // No need to suppress other occurrences of the key (if any)
-                    // because we made a replacement in the first entry, so the
-                    // new value will be visible by the getter methods.
-                }
-                if (old == null) {
-                    old = identifier.getCode();
-                }
-                it.remove();
-                // Continue the iteration in order to remove all other occurrences,
-                // in order to ensure that the getter methods will see the new value.
-            }
-        }
-        if (code != null) {
-            identifiers.add(SpecializedIdentifier.parse(authority, code));
-        }
-        return old;
+    public String put(Citation authority, String code) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
     }
 
     /**
      * Sets the identifier associated with the given authority, and returns the previous value.
      */
     @Override
-    public <T> T putSpecialized(final IdentifierSpace<T> authority, final T value)
-            throws UnsupportedOperationException
-    {
-        ArgumentChecks.ensureNonNull("authority", authority);
-        T old = null;
-        final Iterator<? extends Identifier> it = identifiers.iterator();
-        while (it.hasNext()) {
-            final Identifier identifier = it.next();
-            if (identifier == null) {
-                it.remove(); // Opportunist cleaning, but should not happen.
-            } else if (Objects.equals(authority, identifier.getAuthority())) {
-                if (identifier instanceof SpecializedIdentifier<?>) {
-                    @SuppressWarnings("unchecked")
-                    final SpecializedIdentifier<T> id = (SpecializedIdentifier<T>) identifier;
-                    if (old == null) {
-                        old = id.value;
-                    }
-                    if (value != null) {
-                        id.value = value;
-                        return old;
-                        // No need to suppress other occurrences of the key (if any)
-                        // because we made a replacement in the first entry, so the
-                        // new value will be visible by the getter methods.
-                    }
-                }
-                it.remove();
-                // Continue the iteration in order to remove all other occurrences,
-                // in order to ensure that the getter methods will see the new value.
-            }
-        }
-        if (value != null) {
-            identifiers.add(new SpecializedIdentifier<T>(authority, value));
-        }
-        return old;
+    public <T> T putSpecialized(IdentifierSpace<T> authority, T value) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException();
     }
 
     /**
      * Returns a view over the collection of identifiers. This view supports removal operation
      * if the underlying collection of identifiers supports the {@link Iterator#remove()} method.
      *
+     * <p>If the backing identifier collection contains null entries, those entries will be ignored.
+     * If the backing collection contains many entries for the same authority, then only the first
+     * occurrence is included.</p>
+     *
      * @return A view over the collection of identifiers.
      */
     @Override
@@ -332,83 +358,35 @@ public class IdentifierMapAdapter extend
          * fields if the underlying list is thread-safe. Furthermore, IdentifierMapAdapter are temporary
          * objects anyway in the current ISOMetadata implementation.
          */
-        return new Entries(identifiers);
-    }
-
-    /**
-     * The view returned by {@link IdentifierMapAdapter#entrySet()}. If the backing identifier collection
-     * contains null entries, those entries will be ignored. If the backing collection contains many entries
-     * for the same authority, then only the first occurrence is retained.
-     *
-     * @author  Martin Desruisseaux (Geomatys)
-     * @since   0.3
-     * @version 0.3
-     * @module
-     */
-    private static final class Entries extends AbstractSet<Entry<Citation,String>> {
-        /**
-         * The identifiers to wrap in a set of entries view. This is a reference
-         * to the same collection than {@link IdentifierMapAdapter#identifiers}.
-         */
-        private final Collection<? extends Identifier> identifiers;
-
-        /**
-         * Creates a new view over the collection of identifiers.
-         *
-         * @param identifiers The identifiers to wrap in a set of entries view.
-         */
-        Entries(final Collection<? extends Identifier> identifiers) {
-            this.identifiers = identifiers;
-        }
-
-        /**
-         * Same implementation than {@link IdentifierMapAdapter#clear()}.
-         */
-        @Override
-        public void clear() throws UnsupportedOperationException {
-            identifiers.clear();
-        }
+        return new AbstractSet<Entry<Citation,String>>() {
+            /** Delegates to the enclosing class. */
+            @Override public void clear() throws UnsupportedOperationException {
+                IdentifierMapAdapter.this.clear();
+            }
 
-        /**
-         * Same implementation than {@link IdentifierMapAdapter#isEmpty()}.
-         */
-        @Override
-        public boolean isEmpty() {
-            return identifiers.isEmpty();
-        }
+            /** Delegates to the enclosing class. */
+            @Override public boolean isEmpty() {
+                return IdentifierMapAdapter.this.isEmpty();
+            }
 
-        /**
-         * Counts the number of entries, ignoring null elements and duplicated authorities.
-         * Because {@code null} elements are ignored, this method may return 0 even if
-         * {@link #isEmpty()} returns {@code false}.
-         */
-        @Override
-        public int size() {
-            final HashMap<Citation,Boolean> done = new HashMap<Citation,Boolean>(hashMapCapacity(identifiers.size()));
-            for (final Identifier identifier : identifiers) {
-                if (identifier != null) {
-                    done.put(identifier.getAuthority(), null);
-                }
+            /** Delegates to the enclosing class. */
+            @Override public int size() {
+                return IdentifierMapAdapter.this.size();
             }
-            return done.size();
-        }
 
-        /**
-         * Returns an iterator over the (<var>citation</var>, <var>code</var>) entries.
-         */
-        @Override
-        public Iterator<Entry<Citation, String>> iterator() {
-            return new Iter(identifiers);
-        }
+            /** Returns an iterator over the (<var>citation</var>, <var>code</var>) entries. */
+            @Override public Iterator<Entry<Citation, String>> iterator() {
+                return new Iter(identifiers, isModifiable());
+            }
+        };
     }
 
     /**
      * The iterator over the (<var>citation</var>, <var>code</var>) entries. This iterator is created by
      * the {@link IdentifierMapAdapter.Entries} collection. It extends {@link HashMap} as an opportunist
-     * implementation strategy, but users don't need to know this detail.
+     * implementation strategy, but users does not need to know this detail.
      *
-     * <p>This iterator supports the {@link #remove()} operation if the underlying collection
-     * supports it.</p>
+     * <p>This iterator supports the {@link #remove()} operation if the underlying collection supports it.</p>
      *
      * <p>The map entries are used as a safety against duplicated authority values. The map values
      * are non-null only after we iterated over an authority. Then the value is {@link Boolean#TRUE}
@@ -416,7 +394,7 @@ public class IdentifierMapAdapter extend
      *
      * @author  Martin Desruisseaux (Geomatys)
      * @since   0.3
-     * @version 0.3
+     * @version 0.7
      * @module
      */
     @SuppressWarnings("serial") // Not intended to be serialized.
@@ -439,11 +417,17 @@ public class IdentifierMapAdapter extend
         private transient Citation authority;
 
         /**
+         * {@code true} if the iterator should support the {@link #remove()} operation.
+         */
+        private final boolean isModifiable;
+
+        /**
          * Creates a new iterator for the given collection of identifiers.
          */
-        Iter(final Collection<? extends Identifier> identifiers) {
+        Iter(final Collection<? extends Identifier> identifiers, final boolean isModifiable) {
             super(hashMapCapacity(identifiers.size()));
             this.identifiers = identifiers.iterator();
+            this.isModifiable = isModifiable;
         }
 
         /**
@@ -518,6 +502,9 @@ public class IdentifierMapAdapter extend
          */
         @Override
         public void remove() throws IllegalStateException {
+            if (!isModifiable) {
+                throw new UnsupportedOperationException();
+            }
             final Iterator<? extends Identifier> it = identifiers;
             if (it == null || next != null) {
                 throw new IllegalStateException();

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/NonMarshalledAuthority.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -17,12 +17,18 @@
 package org.apache.sis.internal.jaxb;
 
 import java.util.Iterator;
+import java.util.List;
 import java.util.ArrayList;
+import java.util.Map;
+import java.util.IdentityHashMap;
 import java.util.Collection;
+import java.util.Collections;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.apache.sis.internal.simple.CitationConstant;
+import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.util.collection.Containers;
 import org.apache.sis.xml.IdentifierSpace;
 
 
@@ -57,7 +63,7 @@ import org.apache.sis.xml.IdentifierSpac
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.6
+ * @version 0.7
  * @module
  *
  * @see IdentifierSpace
@@ -73,6 +79,7 @@ public final class NonMarshalledAuthorit
      * mirror the constants defined in the {@link IdentifierSpace} interface
      * and {@link org.apache.sis.metadata.iso.citation.DefaultCitation} class.
      */
+    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
     public static final byte ID=0, UUID=1, HREF=2, XLINK=3, ISSN=4, ISBN=5;
     // If more codes are added, please update readResolve() below.
 
@@ -103,6 +110,9 @@ public final class NonMarshalledAuthorit
      * "special" identifiers (ISO 19139 attributes, ISBN codes...), which are recognized by
      * the implementation class of their authority.
      *
+     * <p>This method is used for implementation of {@code getIdentifier()} methods (singular form)
+     * in public metadata objects.</p>
+     *
      * @param  <T> The type of object used as identifier values.
      * @param  identifiers The collection from which to get identifiers, or {@code null}.
      * @return The first identifier, or {@code null} if none.
@@ -124,23 +134,26 @@ public final class NonMarshalledAuthorit
      * method is used when the given collection is expected to contains only one ISO 19115
      * identifier.
      *
-     * @param <T> The type of object used as identifier values.
+     * <p>This method is used for implementation of {@code setIdentifier(Identifier)} methods
+     * in public metadata objects.</p>
+     *
+     * @param <T>         The type of object used as identifier values.
      * @param identifiers The collection in which to add the identifier.
-     * @param id The identifier to add, or {@code null}.
+     * @param newValue    The identifier to add, or {@code null}.
+     *
+     * @see #setMarshallables(Collection, Collection)
      */
-    public static <T extends Identifier> void setMarshallable(final Collection<T> identifiers, final T id) {
+    public static <T extends Identifier> void setMarshallable(final Collection<T> identifiers, final T newValue) {
         final Iterator<T> it = identifiers.iterator();
         while (it.hasNext()) {
             final T old = it.next();
-            if (old != null) {
-                if (old.getAuthority() instanceof NonMarshalledAuthority<?>) {
-                    continue; // Don't touch this identifier.
-                }
+            if (old != null && old.getAuthority() instanceof NonMarshalledAuthority<?>) {
+                continue; // Don't touch this identifier.
             }
             it.remove();
         }
-        if (id != null) {
-            identifiers.add(id);
+        if (newValue != null) {
+            identifiers.add(newValue);
         }
     }
 
@@ -149,10 +162,15 @@ public final class NonMarshalledAuthorit
      * for which the authority is an instance of {@code NonMarshalledAuthority}. This should exclude
      * all {@link org.apache.sis.xml.IdentifierSpace} constants.
      *
+     * <p>This method is used for implementation of {@code getIdentifiers()} methods (plural form)
+     * in public metadata objects. Note that those methods override
+     * {@link org.apache.sis.xml.IdentifiedObject#getIdentifiers()}, which is expected to return
+     * all identifiers in normal (non-marshalling) usage.</p>
+     *
      * @param  identifiers The identifiers to filter, or {@code null}.
      * @return The identifiers to marshal, or {@code null} if none.
      */
-    public static Collection<Identifier> excludeOnMarshalling(Collection<Identifier> identifiers) {
+    public static Collection<Identifier> filterOnMarshalling(Collection<Identifier> identifiers) {
         if (identifiers != null && Context.isFlagSet(Context.current(), Context.MARSHALLING)) {
             int count = identifiers.size();
             if (count != 0) {
@@ -170,69 +188,72 @@ public final class NonMarshalledAuthorit
     }
 
     /**
-     * Returns a collection containing only the identifiers having a {@code NonMarshalledAuthority}.
-     * This method is invoked for saving the identifiers that are conceptually stored in distinct fields
-     * (XML identifier, UUID, ISBN, ISSN) before to overwrite the collection of all identifiers in
-     * a metadata object.
-     *
-     * <p>This method is invoked from {@code setIdentifiers(Collection<Identifier>)} implementation
-     * in {@link org.apache.sis.metadata.iso.ISOMetadata} subclasses as below:</p>
-     *
-     * {@preformat java
-     *     final Collection<Identifier> oldIds = NonMarshalledAuthority.filteredCopy(identifiers);
-     *     identifiers = writeCollection(newValues, identifiers, Identifier.class);
-     *     NonMarshalledAuthority.replace(identifiers, oldIds);
-     * }
+     * Returns a collection containing all marshallable values of {@code newValues}, together with unmarshallable
+     * values of {@code identifiers}. This method is invoked for preserving the identifiers that are conceptually
+     * stored in distinct fields (XML identifier, UUID, ISBN, ISSN) when setting the collection of all identifiers
+     * in a metadata object.
+     *
+     * <p>This method is used for implementation of {@code setIdentifiers(Collection)} methods
+     * in public metadata objects.</p>
      *
-     * @param  <T> The type of object used as identifier values.
      * @param  identifiers The metadata internal identifiers collection, or {@code null} if none.
-     * @return The new list containing the filtered identifiers, or {@code null} if none.
+     * @param  newValues   The identifiers to add, or {@code null}.
+     * @return The collection to set (may be {@code newValues}.
+     *
+     * @see #setMarshallable(Collection, Identifier)
      */
-    public static <T extends Identifier> Collection<T> filteredCopy(final Collection<T> identifiers) {
-        Collection<T> filtered = null;
-        if (identifiers != null) {
-            int remaining = identifiers.size();
-            for (final T candidate : identifiers) {
-                if (candidate != null && candidate.getAuthority() instanceof NonMarshalledAuthority<?>) {
-                    if (filtered == null) {
-                        filtered = new ArrayList<T>(remaining);
-                    }
-                    filtered.add(candidate);
+    @SuppressWarnings("null")
+    public static Collection<? extends Identifier> setMarshallables(
+            final Collection<Identifier> identifiers, final Collection<? extends Identifier> newValues)
+    {
+        int remaining;
+        if (identifiers == null || (remaining = identifiers.size()) == 0) {
+            return newValues;
+        }
+        /*
+         * If there is any identifiers that need to be preserved (XML identifier, UUID, ISBN, etc.),
+         * remember them. Otherwise there is nothing special to do and we can return the new values directly.
+         */
+        List<Identifier> toPreserve = null;
+        for (final Identifier id : identifiers) {
+            if (id != null && id.getAuthority() instanceof NonMarshalledAuthority<?>) {
+                if (toPreserve == null) {
+                    toPreserve = new ArrayList<Identifier>(remaining);
                 }
-                remaining--;
+                toPreserve.add(id);
             }
+            remaining--;
         }
-        return filtered;
-    }
-
-    /**
-     * Replaces all identifiers in the {@code identifiers} collection having the same
-     * {@linkplain Identifier#getAuthority() authority} than the ones in {@code oldIds}.
-     * More specifically:
-     *
-     * <ul>
-     *   <li>First, remove all {@code identifiers} elements having the same authority
-     *       than one of the elements in {@code oldIds}.</li>
-     *   <li>Next, add all {@code oldIds} elements to {@code identifiers}.</li>
-     * </ul>
-     *
-     * @param <T> The type of object used as identifier values.
-     * @param identifiers The metadata internal identifiers collection, or {@code null} if none.
-     * @param oldIds The previous filtered identifiers returned by {@link #filteredCopy(Collection)},
-     *               or {@code null} if none.
-     */
-    public static <T extends Identifier> void replace(final Collection<T> identifiers, final Collection<T> oldIds) {
-        if (oldIds != null && identifiers != null) {
-            for (final T old : oldIds) {
-                final Citation authority = old.getAuthority();
-                for (final Iterator<T> it=identifiers.iterator(); it.hasNext();) {
-                    final T id = it.next();
-                    if (id == null || id.getAuthority() == authority) {
-                        it.remove();
-                    }
+        if (toPreserve == null) {
+            return newValues;
+        }
+        /*
+         * We find at least one identifier that may need to be preserved.
+         * We need to create a combination of the two collections.
+         */
+        final Map<Citation,Identifier> authorities = new IdentityHashMap<Citation,Identifier>(4);
+        final List<Identifier> merged = new ArrayList<Identifier>(newValues.size());
+        for (final Identifier id : newValues) {
+            merged.add(id);
+            if (id != null) {
+                final Citation authority = id.getAuthority();
+                if (authority instanceof NonMarshalledAuthority<?>) {
+                    authorities.put(authority, id);
                 }
             }
-            identifiers.addAll(oldIds);
+        }
+        for (final Identifier id : toPreserve) {
+            if (!authorities.containsKey(id.getAuthority())) {
+                merged.add(id);
+            }
+        }
+        /*
+         * Wraps in an unmodifiable list in case the caller is creating an unmodifiable metadata.
+         */
+        switch (merged.size()) {
+            case 0:  return Collections.emptyList();
+            case 1:  return Collections.singletonList(merged.get(0));
+            default: return Containers.unmodifiableList(CollectionsExt.toArray(merged, Identifier.class));
         }
     }
 

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -44,10 +44,10 @@ import org.apache.sis.internal.jdk7.Obje
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.7
  * @module
  */
-public final class SpecializedIdentifier<T> implements Identifier, Serializable {
+public final class SpecializedIdentifier<T> implements Identifier, Cloneable, Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -210,6 +210,20 @@ public final class SpecializedIdentifier
     }
 
     /**
+     * Returns a clone of this identifier.
+     *
+     * @return A shallow clone of this identifier.
+     */
+    @Override
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(e);    // Should never happen, since we are cloneable.
+        }
+    }
+
+    /**
      * Returns a string representation of this identifier.
      * Example: {@code Identifier[gco:uuid=“42924124-032a-4dfe-b06e-113e3cb81cf0”]}.
      *

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/PropertyType.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -206,7 +206,7 @@ public abstract class PropertyType<Value
              *   </gmd:CI_Citation>
              *
              * We do not try to parse UUID or XLink objects from String because it should be the job of
-             * org.apache.sis.internal.jaxb.IdentifierMapWithSpecialCases.put(Citation, String).
+             * org.apache.sis.internal.jaxb.ModifiableIdentifierMap.put(Citation, String).
              */
             final IdentifierMap map = ((IdentifiedObject) value).getIdentifierMap();
             XLink  link = map.getSpecialized(IdentifierSpace.XLINK);

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/package-info.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -26,7 +26,7 @@
  * <div class="section">Main content</div>
  * {@link org.apache.sis.internal.jaxb.IdentifierMapAdapter} is our internal implementation of
  * the public {@link org.apache.sis.xml.IdentifierMap} interface. The actual implementation is
- * usually the {@code IdentifierMapWithSpecialCases} subclass.
+ * usually the {@code ModifiableIdentifierMap} subclass.
  *
  * <p>{@link org.apache.sis.internal.jaxb.SpecializedIdentifier} wraps {@link org.apache.sis.xml.XLink},
  * {@link java.net.URI} and {@link java.util.UUID} as {@link org.opengis.metadata.Identifier} instances.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/CodeListSet.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -547,7 +547,6 @@ public class CodeListSet<E extends CodeL
     @Override
     @SuppressWarnings("unchecked")
     public CodeListSet<E> clone() {
-        @SuppressWarnings("unchecked")
         final CodeListSet<E> clone;
         try {
             clone = (CodeListSet<E>) super.clone();

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/Containers.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -89,8 +89,7 @@ public final class Containers extends St
      *
      * @param  <E> The base type of elements in the list.
      * @param  array The array to wrap, or {@code null} if none.
-     * @return The given array wrapped in an unmodifiable list, or {@code null} if the given
-     *         array was null.
+     * @return The given array wrapped in an unmodifiable list, or {@code null} if the given array was null.
      *
      * @see java.util.Arrays#asList(Object[])
      */
@@ -148,10 +147,9 @@ public final class Containers extends St
      * @param  <S>       The type of elements in the storage (original) set.
      * @param  <E>       The type of elements in the derived set.
      * @param  storage   The storage set containing the original elements, or {@code null}.
-     * @param  converter The converter from the elements in the storage set to the elements
-     *                   in the derived set.
-     * @return A view over the {@code storage} set containing all elements converted by the given
-     *         converter, or {@code null} if {@code storage} was null.
+     * @param  converter The converter from the elements in the storage set to the elements in the derived set.
+     * @return A view over the {@code storage} set containing all elements converted by the given converter,
+     *         or {@code null} if {@code storage} was null.
      *
      * @see org.apache.sis.util.ObjectConverters#derivedSet(Set, ObjectConverter)
      */
@@ -195,8 +193,8 @@ public final class Containers extends St
      * @param storage      The storage map containing the original entries, or {@code null}.
      * @param keyConverter The converter from the keys in the storage map to the keys in the derived map.
      * @param valueConverter The converter from the values in the storage map to the values in the derived map.
-     * @return A view over the {@code storage} map containing all entries converted by the given
-     *         converters, or {@code null} if {@code storage} was null.
+     * @return A view over the {@code storage} map containing all entries converted by the given converters,
+     *         or {@code null} if {@code storage} was null.
      *
      * @see org.apache.sis.util.ObjectConverters#derivedMap(Map, ObjectConverter, ObjectConverter)
      * @see org.apache.sis.util.ObjectConverters#derivedKeys(Map, ObjectConverter, Class)

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/IdentifierMap.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -66,6 +66,5 @@ public interface IdentifierMap extends M
      *         if there was no mapping of the specialized type for {@code authority}.
      * @throws UnsupportedOperationException If the identifier map is unmodifiable.
      */
-    <T> T putSpecialized(IdentifierSpace<T> authority, T value)
-            throws UnsupportedOperationException;
+    <T> T putSpecialized(IdentifierSpace<T> authority, T value) throws UnsupportedOperationException;
 }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java?rev=1707309&r1=1707308&r2=1707309&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/xml/NilObjectHandler.java [UTF-8] Wed Oct  7 13:59:47 2015
@@ -31,7 +31,7 @@ import org.apache.sis.util.ComparisonMod
 import org.apache.sis.util.LenientComparable;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.jaxb.IdentifierMapAdapter;
-import org.apache.sis.internal.jaxb.IdentifierMapWithSpecialCases;
+import org.apache.sis.internal.jaxb.ModifiableIdentifierMap;
 
 // Branch-dependent imports
 import org.apache.sis.internal.jdk7.Objects;
@@ -73,7 +73,7 @@ final class NilObjectHandler implements
                 asList.add(identifier);
             }
         }
-        attribute = new IdentifierMapWithSpecialCases(asList);
+        attribute = new ModifiableIdentifierMap(asList);
     }
 
     /**



Mime
View raw message