Author: desruisseaux
Date: Fri Mar 27 00:31:14 2015
New Revision: 1669486

URL: http://svn.apache.org/r1669486
Log:
Referencing: call to ContextualParameters.completeTransform(...) mark the 
parameters as unmodifiable.
This method is invoked after we finished to build the ContextualParameters and 
need to keep the reference.

Added:
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java
   (with props)
    
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/UnmodifiableParameterValueTest.java
   (with props)
Modified:
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
    
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java
    
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
    
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java?rev=1669486&r1=1669485&r2=1669486&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
 [UTF-8] Fri Mar 27 00:31:14 2015
@@ -103,11 +103,11 @@ import java.nio.file.Path;
  * Consequently, the above-cited methods provide single points that subclasses 
can override
  * for modifying the behavior of all getter and setter methods.
  *
- * @param <T> The value type.
+ * @param <T> The type of the value stored in this parameter.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4
- * @version 0.4
+ * @version 0.6
  * @module
  *
  * @see DefaultParameterDescriptor
@@ -134,7 +134,7 @@ public class DefaultParameterValue<T> ex
     private T value;
 
     /**
-     * The unit of measure for the value, or {@code null} if it doesn't apply.
+     * The unit of measure for the value, or {@code null} if it does not apply.
      * Except for the constructors, the {@link #equals(Object)} and the {@link 
#hashCode()} methods,
      * this field shall be read only by {@link #getUnit()} and written by 
{@link #setValue(Object, Unit)}.
      */
@@ -159,6 +159,9 @@ public class DefaultParameterValue<T> ex
      * object is not cloned.
      *
      * @param parameter The parameter to copy values from.
+     *
+     * @see #clone()
+     * @see #unmodifiable(ParameterValue)
      */
     public DefaultParameterValue(final ParameterValue<T> parameter) {
         ensureNonNull("parameter", parameter);
@@ -758,6 +761,9 @@ public class DefaultParameterValue<T> ex
 
     /**
      * Returns a clone of this parameter value.
+     *
+     * @see #DefaultParameterValue(ParameterValue)
+     * @see #unmodifiable(ParameterValue)
      */
     @Override
     @SuppressWarnings("unchecked")
@@ -770,6 +776,39 @@ public class DefaultParameterValue<T> ex
     }
 
     /**
+     * Returns an unmodifiable implementation of the given parameter value.
+     * This method shall be used only with:
+     *
+     * <ul>
+     *   <li>immutable {@linkplain #getDescriptor() descriptor},</li>
+     *   <li>immutable or null {@linkplain #getUnit() unit}, and</li>
+     *   <li>immutable or {@linkplain Cloneable cloneable} parameter 
{@linkplain #getValue() value}.</li>
+     * </ul>
+     *
+     * If the parameter value implements the {@code Cloneable} interface and 
has a public {@code clone()} method,
+     * then that value will be cloned every time the {@link #getValue()} 
method is invoked.
+     *
+     * <div class="section">Instances sharing</div>
+     * If this method is invoked more than once with equal {@linkplain 
#getDescriptor() descriptor},
+     * {@linkplain #getValue() value} and {@linkplain #getUnit() unit}, then 
this method will return
+     * the same {@code DefaultParameterValue} instance on a <cite>best 
effort</cite> basis.
+     *
+     * <div class="note"><b>Rational:</b>
+     * the same parameter value is often used in many different coordinate 
operations. For example all <cite>Universal
+     * Transverse Mercator</cite> (UTM) projections use the same scale factor 
(0.9996) and false easting (500000 metres).
+     * </div>
+     *
+     * @param  <T> The type of the value stored in the given parameter.
+     * @param  parameter The parameter to make unmodifiable, or {@code null}.
+     * @return An unmodifiable implementation of the given parameter, or 
{@code null} if the given parameter was null.
+     *
+     * @since 0.6
+     */
+    public static <T> DefaultParameterValue<T> unmodifiable(final 
ParameterValue<T> parameter) {
+        return UnmodifiableParameterValue.create(parameter);
+    }
+
+    /**
      * Formats this parameter as a <cite>Well Known Text</cite> {@code 
Parameter[…]} element.
      * Example:
      *

Added: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java?rev=1669486&view=auto
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java
 (added)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java
 [UTF-8] Fri Mar 27 00:31:14 2015
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.parameter;
+
+import javax.measure.unit.Unit;
+import org.opengis.parameter.ParameterValue;
+import org.apache.sis.internal.util.Cloner;
+import org.apache.sis.util.collection.WeakHashSet;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * A parameter value which can not be modified. This implementation shall be 
used only with:
+ *
+ * <ul>
+ *   <li>immutable {@linkplain #getDescriptor() descriptor},</li>
+ *   <li>immutable or null {@linkplain #getUnit() unit}, and</li>
+ *   <li>immutable or {@linkplain Cloneable cloneable} parameter {@linkplain 
#getValue() value}.</li>
+ * </ul>
+ *
+ * If the parameter value implements the {@code Cloneable} interface and has a 
public {@code clone()} method,
+ * then that value will be cloned every time the {@link #getValue()} method is 
invoked.
+ *
+ * <div class="section">Instances sharing</div>
+ * If the {@link #create(ParameterValue)} method is invoked more than once 
with equal descriptor, value and unit,
+ * then the method will return the same {@code UnmodifiableParameterValue} 
instance on a <cite>best effort</cite>
+ * basis.
+ *
+ * <div class="note"><b>Rational:</b>
+ * the same parameter value is often used in many different coordinate 
operations. For example all <cite>Universal
+ * Transverse Mercator</cite> (UTM) projections use the same scale factor 
(0.9996) and false easting (500000 metres).
+ * </div>
+ *
+ * @param <T> The type of the value stored in this parameter.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.6
+ * @version 0.6
+ * @module
+ */
+final class UnmodifiableParameterValue<T> extends DefaultParameterValue<T> {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -4760030766220872555L;
+
+    /**
+     * Pool of parameter instances created in this running JVM.
+     * See class javadoc for a rational about why we use a pool.
+     */
+    @SuppressWarnings("rawtypes")
+    private static final WeakHashSet<UnmodifiableParameterValue> POOL =
+            new WeakHashSet<>(UnmodifiableParameterValue.class);
+
+    /**
+     * Creates a new parameter with the same value than the given one.
+     */
+    private UnmodifiableParameterValue(final ParameterValue<T> value) {
+        super(value);
+    }
+
+    /**
+     * Returns an unmodifiable implementation of the given parameter value.
+     * See class javadoc for more information.
+     *
+     * @param  <T> The type of the value stored in the given parameter.
+     * @param  parameter The parameter to make unmodifiable, or {@code null}.
+     * @return An unmodifiable implementation of the given parameter, or 
{@code null} if the given parameter was null.
+     */
+    static <T> UnmodifiableParameterValue<T> create(final ParameterValue<T> 
parameter) {
+        if (parameter == null || parameter instanceof 
UnmodifiableParameterValue<?>) {
+            return (UnmodifiableParameterValue<T>) parameter;
+        } else {
+            return POOL.unique(new UnmodifiableParameterValue<>(parameter));
+        }
+    }
+
+    /**
+     * If the value is cloneable, clones it before to return it.
+     */
+    @Override
+    public T getValue() {
+        T value = super.getValue();
+        if (value instanceof Cloneable) try {
+            value = 
getDescriptor().getValueClass().cast(Cloner.cloneIfPublic(value));
+        } catch (CloneNotSupportedException e) {
+            throw new 
UnsupportedOperationException(Errors.format(Errors.Keys.CloneNotSupported_1, 
value.getClass()), e);
+        }
+        return value;
+    }
+
+    /**
+     * Do not allow modification of the parameter value.
+     */
+    @Override
+    protected void setValue(final Object value, final Unit<?> unit) {
+        throw new 
UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, 
getClass()));
+    }
+
+    /**
+     * Returns a modifiable copy of this parameter.
+     */
+    @Override
+    public DefaultParameterValue<T> clone() {
+        return new DefaultParameterValue<>(this);
+    }
+}

Propchange: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/UnmodifiableParameterValue.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java?rev=1669486&r1=1669485&r2=1669486&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
 [UTF-8] Fri Mar 27 00:31:14 2015
@@ -54,7 +54,7 @@ import java.util.Objects;
  *   {@linkplain ContextualParameters#normalizeGeographicInputs normalize} 
affine transform.</li>
  *
  *   <li>On output, the {@link #transform(double[],int,double[],int,boolean) 
transform(…)} method returns
- *   (<var>x</var>, <var>y</var>) values on a sphere or ellipse having a 
semi-major axis length of 1.
+ *   (<var>x</var>, <var>y</var>) values on a sphere or ellipse having a 
semi-major axis length (<var>a</var>) of 1.
  *   The multiplication by the scale factor (<var>k</var>₀) and the 
translation by false easting (FE) and false
  *   northing (FN) are applied by the {@linkplain 
ContextualParameters#denormalizeCartesianOutputs denormalize}
  *   affine transform.</li>

Modified: 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java?rev=1669486&r1=1669485&r2=1669486&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java
 [UTF-8] Fri Mar 27 00:31:14 2015
@@ -17,7 +17,9 @@
 package org.apache.sis.referencing.operation.transform;
 
 import java.util.List;
+import java.util.ArrayList;
 import java.util.Objects;
+import java.util.Collections;
 import java.io.Serializable;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.operation.MathTransform;
@@ -32,7 +34,9 @@ import org.opengis.referencing.operation
 import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
 import org.apache.sis.internal.referencing.WKTUtilities;
 import org.apache.sis.internal.util.DoubleDouble;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.parameter.Parameterized;
+import org.apache.sis.parameter.DefaultParameterValue;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.referencing.operation.matrix.MatrixSIS;
 import 
org.apache.sis.referencing.operation.matrix.NoninvertibleMatrixException;
@@ -119,7 +123,7 @@ public class ContextualParameters extend
      * The parameters that represents the sequence of transforms as a whole. 
The parameter values may take effect
      * in either the {@linkplain #normalization(boolean) 
normalize/denormalize} transforms or in the kernel.
      *
-     * @see #getParameterDescriptors()
+     * @see #getDescriptor()
      */
     private final ParameterDescriptorGroup descriptor;
 
@@ -127,7 +131,7 @@ public class ContextualParameters extend
      * The affine transform to be applied before (<cite>normalize</cite>) and 
after (<cite>denormalize</cite>)
      * the kernel operation. On {@code ContextualParameters} construction, 
those affines are initially identity
      * transforms, to be modified in-place by callers of {@link 
#normalization(boolean)}.
-     * After {@link #createConcatenatedTransform(MathTransformFactory, 
MathTransform)} has been invoked,
+     * After {@link #completeTransform(MathTransformFactory, MathTransform)} 
has been invoked,
      * they are typically (but not necessarily) replaced by the {@link 
LinearTransform} instance itself.
      *
      * @see #normalization(boolean)
@@ -135,6 +139,12 @@ public class ContextualParameters extend
     private Matrix normalize, denormalize;
 
     /**
+     * The list of parameter values. This list is modifiable after 
construction, but is replaced by an
+     * unmodifiable list after {@link #completeTransform(MathTransformFactory, 
MathTransform)} has been invoked.
+     */
+    private List<GeneralParameterValue> values;
+
+    /**
      * Creates a new group of parameters for the given non-linear coordinate 
operation method.
      * The {@linkplain 
org.apache.sis.referencing.operation.DefaultOperationMethod#getParameters() 
method parameters}
      * shall apply to the <cite>normalize</cite> → <cite>non-linear 
kernel</cite> → <cite>denormalize</cite> sequence
@@ -148,6 +158,15 @@ public class ContextualParameters extend
         descriptor  = method.getParameters();
         normalize   = linear("sourceDimensions", method.getSourceDimensions());
         denormalize = linear("targetDimensions", method.getTargetDimensions());
+        values      = new ArrayList<>(descriptor.descriptors().size());  // 
See isModifiable() below.
+    }
+
+    /**
+     * Returns {@code true} if this parameter group is modifiable, or {@code 
false} if it has been made
+     * unmodifiable by a call to {@link 
#completeTransform(MathTransformFactory, MathTransform)}.
+     */
+    private boolean isModifiable() {
+        return values.getClass() == ArrayList.class;
     }
 
     /**
@@ -181,13 +200,20 @@ public class ContextualParameters extend
     }
 
     /**
-     * Returns an unmodifiable view of all parameter values in this group.
+     * Returns an unmodifiable list containing all parameters in this group.
+     * Callers should not attempt to modify the parameter values.
      *
      * @return All parameter values.
      */
     @Override
     public List<GeneralParameterValue> values() {
-        throw new UnsupportedOperationException("Not supported yet."); // TODO
+        if (isModifiable()) {
+            return Collections.unmodifiableList(values);
+        } else {
+            // Already unmodifiable, no need to protect against changes.
+            assert (values instanceof UnmodifiableArrayList<?>);
+            return values;
+        }
     }
 
     /**
@@ -203,6 +229,17 @@ public class ContextualParameters extend
     }
 
     /**
+     * Ensures that this instance if modifiable.
+     *
+     * @throws IllegalStateException if this {@code ContextualParameter} has 
been made unmodifiable.
+     */
+    private void ensureModifiable() throws IllegalStateException {
+        if (!isModifiable()) {
+            throw new 
IllegalStateException(Errors.format(Errors.Keys.UnmodifiableObject_1, 
getClass()));
+        }
+    }
+
+    /**
      * Prepends a normalization step before the non-linear kernel, which will 
convert ordinate values
      * in the two first dimensions from degrees to radians. Before this 
conversion, the normalization
      * can optionally subtract the given λ₀ value (in degrees) from the 
longitude.
@@ -215,8 +252,10 @@ public class ContextualParameters extend
      * @param  λ0 Longitude of the central meridian, in degrees.
      * @return The normalization affine transform as a matrix.
      *         Callers can change that matrix directly if they want to apply 
additional normalization operations.
+     * @throws IllegalStateException if this {@code ContextualParameter} has 
been made unmodifiable.
      */
     public MatrixSIS normalizeGeographicInputs(final double λ0) {
+        ensureModifiable();
         /*
          * In theory the check for (λ0 != 0) is useless. However Java has a 
notion of negative zero, and we want
          * to avoid negative zeros because we do not want them to appear in 
WKT formatting of matrix elements.
@@ -227,7 +266,7 @@ public class ContextualParameters extend
             offset = new DoubleDouble(-λ0);
             offset.multiply(toRadians);
         }
-        final MatrixSIS normalize = normalization(true);
+        final MatrixSIS normalize = (MatrixSIS) this.normalize;  // Must be 
the same instance, not a copy.
         normalize.concatenate(0, toRadians, offset);
         normalize.concatenate(1, toRadians, null);
         return normalize;
@@ -247,10 +286,12 @@ public class ContextualParameters extend
      * @param  ty    The false northing (FN).
      * @return The denormalization affine transform as a matrix.
      *         Callers can change that matrix directly if they want to apply 
additional denormalization operations.
+     * @throws IllegalStateException if this {@code ContextualParameter} has 
been made unmodifiable.
      */
     public MatrixSIS denormalizeCartesianOutputs(double scale, double tx, 
double ty) {
+        ensureModifiable();
         final DoubleDouble s = new DoubleDouble(scale);
-        final MatrixSIS denormalize = normalization(false);
+        final MatrixSIS denormalize = (MatrixSIS) this.denormalize;  // Must 
be the same instance, not a copy.
         denormalize.concatenate(0, s, tx);
         denormalize.concatenate(1, s, ty);
         return denormalize;
@@ -266,12 +307,19 @@ public class ContextualParameters extend
      * @return The requested normalize ({@code true}) or denormalize ({@code 
false}) affine transform.
      */
     public final MatrixSIS normalization(final boolean norm) {
-        return MatrixSIS.castOrCopy(norm ? normalize : denormalize);
+        final Matrix m = norm ? normalize : denormalize;
+        if (isModifiable()) {
+            return (MatrixSIS) m;       // Must be the same instance, not a 
copy.
+        } else {
+            return Matrices.copy(m);    // Should be protected against changes.
+        }
     }
 
     /**
-     * Creates a chain of {@linkplain ConcatenatedTransform concatenated 
transforms} from the
-     * <cite>normalize</cite> transform, the given kernel and the 
<cite>denormalize</cite> transform.
+     * Marks this {@code ContextualParameter} as unmodifiable and creates the
+     * <cite>normalize</cite> → {@code kernel} → <cite>denormalize</cite> 
transforms chain.
+     * This method shall be invoked only after the {@linkplain 
#normalization(boolean) (de)normalization}
+     * matrices have been set to their final values.
      *
      * @param  factory The factory to use for creating math transform 
instances.
      * @param  kernel The (usually non-linear) kernel.
@@ -279,9 +327,20 @@ public class ContextualParameters extend
      *         transforms.
      * @throws FactoryException if an error occurred while creating a math 
transform instance.
      */
-    public MathTransform createConcatenatedTransform(final 
MathTransformFactory factory, final MathTransform kernel)
+    public MathTransform completeTransform(final MathTransformFactory factory, 
final MathTransform kernel)
             throws FactoryException
     {
+        if (isModifiable()) {
+            final GeneralParameterValue[] p = values.toArray(new 
GeneralParameterValue[values.size()]);
+            for (int i=0; i<p.length; i++) {
+                p[i] = DefaultParameterValue.unmodifiable((ParameterValue<?>) 
p[i]);
+            }
+            values = UnmodifiableArrayList.wrap(p);
+        }
+        /*
+         * Creates the ConcatenatedTransform, letting the factory returns the 
cached instance
+         * if the caller already invoked this method previously (which usually 
do not happen).
+         */
         final MathTransform n = factory.createAffineTransform(normalize);
         final MathTransform d = factory.createAffineTransform(denormalize);
         Matrix m;

Added: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/UnmodifiableParameterValueTest.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/UnmodifiableParameterValueTest.java?rev=1669486&view=auto
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/UnmodifiableParameterValueTest.java
 (added)
+++ 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/UnmodifiableParameterValueTest.java
 [UTF-8] Fri Mar 27 00:31:14 2015
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.parameter;
+
+import java.util.Date;
+import org.opengis.parameter.ParameterValue;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestCase;
+import org.apache.sis.util.ComparisonMode;
+import org.junit.Test;
+
+import static org.apache.sis.test.TestUtilities.date;
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link UnmodifiableParameterValue} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.6
+ * @version 0.6
+ * @module
+ */
+@DependsOn(DefaultParameterValueTest.class)
+public final strictfp class UnmodifiableParameterValueTest extends TestCase {
+    /**
+     * Strict tolerance factor for floating point comparisons.
+     */
+    private static final double STRICT = 0.0;
+
+    /**
+     * Creates an {@link UnmodifiableParameterValue} implementation for the 
given parameter
+     * and asserts that we got a new instance equivalent to the original one.
+     */
+    private static <T> DefaultParameterValue<T> assertEquivalent(final 
ParameterValue<T> modifiable) {
+        final DefaultParameterValue<T> unmodifiable = 
DefaultParameterValue.unmodifiable(modifiable);
+        assertNotSame("Expected a new instance.", modifiable, unmodifiable);
+        assertTrue("New instance shall be equal to the original one.",
+                unmodifiable.equals(modifiable, ComparisonMode.BY_CONTRACT));
+        return unmodifiable;
+    }
+
+    /**
+     * Tests the value returned by {@link 
DefaultParameterValue#unmodifiable(ParameterValue)}.
+     */
+    @Test
+    public void testCreate() {
+        final ParameterValue<Double> modifiable = 
DefaultParameterDescriptorTest
+                .createSimpleOptional("Scale factor", 
Double.class).createValue();
+        modifiable.setValue(0.9996); // Scale factor of all UTM projections.
+        /*
+         * Create and validate an unmodifiable parameter,
+         * then verify that we can not modify its value.
+         */
+        final DefaultParameterValue<Double> unmodifiable = 
assertEquivalent(modifiable);
+        assertSame("Double instances do not need to be cloned.", 
modifiable.getValue(), unmodifiable.getValue());
+        modifiable.setValue(1.0);
+        try {
+            unmodifiable.setValue(1.0);
+            fail("UnmodifiableParameterValue shall not allow modification.");
+        } catch (UnsupportedOperationException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("DefaultParameterValue"));
+        }
+        assertEquals(1.0,      modifiable.doubleValue(), STRICT);
+        assertEquals(0.9996, unmodifiable.doubleValue(), STRICT);
+        /*
+         * Verify that invoking again DefaultParameterValue.unmodifiable(…) 
return the same instance.
+         * Opportunistically verify that we detect null value and instances 
already unmodifiable.
+         */
+        assertNull   (              DefaultParameterValue.unmodifiable(null));
+        assertSame   (unmodifiable, 
DefaultParameterValue.unmodifiable(unmodifiable));
+        assertNotSame(unmodifiable, assertEquivalent(modifiable));
+        modifiable.setValue(0.9996); // Restore our original value.
+        assertSame   (unmodifiable, assertEquivalent(modifiable));
+    }
+
+    /**
+     * Verifies that {@link UnmodifiableParameterValue#getValue()} can clone 
the value.
+     */
+    @Test
+    @DependsOnMethod("testCreate")
+    public void testGetValue() {
+        final ParameterValue<Date> modifiable = DefaultParameterDescriptorTest
+                .createSimpleOptional("Time reference", 
Date.class).createValue();
+        modifiable.setValue(date("1994-01-01 00:00:00"));
+        /*
+         * Create and validate an unmodifiable parameter,
+         * then verify that the values are not the same.
+         */
+        final DefaultParameterValue<Date> unmodifiable = 
assertEquivalent(modifiable);
+        final Date t1 =   modifiable.getValue();
+        final Date t2 = unmodifiable.getValue();
+        assertNotSame("Date should be cloned.", t1, t2);
+        assertEquals(t1, t2);
+        /*
+         * Verify that cloning the parameter also clone its value.
+         */
+        final DefaultParameterValue<Date> clone = unmodifiable.clone();
+        assertEquals(DefaultParameterValue.class, clone.getClass());
+        final Date t3 = clone.getValue();
+        assertNotSame(t1, t3);
+        assertNotSame(t2, t3);
+        assertEquals (t1, t3);
+    }
+}

Propchange: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/UnmodifiableParameterValueTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/parameter/UnmodifiableParameterValueTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java?rev=1669486&r1=1669485&r2=1669486&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
 [UTF-8] Fri Mar 27 00:31:14 2015
@@ -62,6 +62,7 @@ import org.junit.BeforeClass;
     org.apache.sis.parameter.DefaultParameterDescriptorGroupTest.class,
     org.apache.sis.parameter.DefaultParameterValueTest.class,
     org.apache.sis.parameter.DefaultParameterValueGroupTest.class,
+    org.apache.sis.parameter.UnmodifiableParameterValueTest.class,
     org.apache.sis.parameter.ParametersTest.class,
     org.apache.sis.parameter.ParameterBuilderTest.class,
     org.apache.sis.parameter.ParameterFormatTest.class,

Modified: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java?rev=1669486&r1=1669485&r2=1669486&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
 [UTF-8] Fri Mar 27 00:31:14 2015
@@ -29,7 +29,7 @@ import org.apache.sis.util.resources.Err
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.5
+ * @version 0.6
  * @module
  */
 @Workaround(library="JDK", version="1.7")
@@ -113,7 +113,7 @@ public class Cloner {
             }
         } catch (NoSuchMethodException e) {
             if (isCloneRequired(object)) {
-                throw fail(e);
+                throw fail(e, valueType);
             }
             method = null;
             type = valueType;
@@ -121,36 +121,80 @@ public class Cloner {
             if (security != null) {
                 e.addSuppressed(security);
             }
-            throw fail(e);
+            throw fail(e, valueType);
         } catch (InvocationTargetException e) {
-            final Throwable cause = e.getCause();
-            if (cause instanceof CloneNotSupportedException) {
-                throw (CloneNotSupportedException) cause;
-            }
-            if (cause instanceof RuntimeException) {
-                throw (RuntimeException) cause;
-            }
-            if (cause instanceof Error) {
-                throw (Error) cause;
-            }
-            throw fail(e);
+            rethrow(e.getCause());
+            throw fail(e, valueType);
         } catch (SecurityException e) {
-            throw fail(e);
+            throw fail(e, valueType);
         }
         return object;
     }
 
     /**
+     * Throws the given exception if it is an instance of {@code 
CloneNotSupportedException}
+     * or an unchecked exception, or do nothing otherwise. If this method 
returns normally,
+     * then it is caller's responsibility to throw an other exception.
+     *
+     * @param cause The value of {@link InvocationTargetException#getCause()}.
+     */
+    private static void rethrow(final Throwable cause) throws 
CloneNotSupportedException {
+        if (cause instanceof CloneNotSupportedException) {
+            throw (CloneNotSupportedException) cause;
+        }
+        if (cause instanceof RuntimeException) {
+            throw (RuntimeException) cause;
+        }
+        if (cause instanceof Error) {
+            throw (Error) cause;
+        }
+        // If we reach this point, caller should invoke "throw fail(e, type)".
+    }
+
+    /**
      * Returns an exception telling that the object can not be cloned because 
of the given error.
-     * The {@link #clone(Object)} method must have been attempted before to 
invoke this method.
      *
      * @param  cause The cause for the failure to clone an object.
+     * @param  type  The type of object that we failed to clone.
      * @return An exception with an error message and the given cause.
      */
-    private CloneNotSupportedException fail(final Throwable cause) {
-        CloneNotSupportedException e = new CloneNotSupportedException(
-                Errors.format(Errors.Keys.CloneNotSupported_1, type));
-        e.initCause(cause);
-        return e;
+    private static CloneNotSupportedException fail(final Throwable cause, 
final Class<?> type) {
+        return (CloneNotSupportedException) new CloneNotSupportedException(
+                Errors.format(Errors.Keys.CloneNotSupported_1, 
type)).initCause(cause);
+    }
+
+    /**
+     * Clones the given object if its {@code clone()} method is public, or 
returns the same object otherwise.
+     * This method may be convenient when there is only one object to clone, 
otherwise instantiating a new
+     * {@code Cloner} object is more efficient.
+     *
+     * @param  object The object to clone, or {@code null}.
+     * @return The given object (which may be {@code null}) or a clone of the 
given object.
+     * @throws CloneNotSupportedException if the call to {@link 
Object#clone()} failed.
+     *
+     * @since 0.6
+     */
+    public static Object cloneIfPublic(final Object object) throws 
CloneNotSupportedException {
+        if (object != null) {
+            final Class<?> type = object.getClass();
+            try {
+                final Method m = type.getMethod("clone", (Class[]) null);
+                if (Modifier.isPublic(m.getModifiers())) {
+                    return m.invoke(object, (Object[]) null);
+                }
+            } catch (NoSuchMethodException | IllegalAccessException e) {
+                /*
+                 * Should never happen because all objects have a clone() 
method
+                 * and we verified that the method is public.
+                 */
+                throw new AssertionError(e);
+            } catch (InvocationTargetException e) {
+                rethrow(e.getCause());
+                throw fail(e, type);
+            } catch (SecurityException e) {
+                throw fail(e, type);
+            }
+        }
+        return object;
     }
 }

Modified: 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java?rev=1669486&r1=1669485&r2=1669486&view=diff
==============================================================================
--- 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
 [UTF-8] (original)
+++ 
sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
 [UTF-8] Fri Mar 27 00:31:14 2015
@@ -245,7 +245,7 @@ public class DefaultTreeTable implements
     @Override
     public DefaultTreeTable clone() throws CloneNotSupportedException {
         final DefaultTreeTable clone = (DefaultTreeTable) super.clone();
-        clone.root = (TreeTable.Node) new Cloner().clone(clone.root);
+        clone.root = (TreeTable.Node) Cloner.cloneIfPublic(clone.root);
         return clone;
     }
 


Reply via email to