sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 29/31: Call to Field.setAccessible(true) need to take in account security constrained environment. Call to ModifiableMetadata.freeze() share instances when possible.
Date Mon, 18 Jun 2018 09:44:40 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 28c0a1864be5520df24c0ca614b9117b5a692ceb
Author: Martin Desruisseaux <desruisseaux@apache.org>
AuthorDate: Fri Jun 8 22:25:17 2018 +0000

    Call to Field.setAccessible(true) need to take in account security constrained environment.
    Call to ModifiableMetadata.freeze() share instances when possible.
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sis/branches/JDK8@1833216 13f79535-47bb-0310-9956-ffa450edef68
---
 .../main/java/org/apache/sis/metadata/Freezer.java |  65 ++++++++-
 .../org/apache/sis/metadata/MetadataStandard.java  |  16 ++-
 .../apache/sis/metadata/ModifiableMetadata.java    |   8 +-
 .../org/apache/sis/metadata/PropertyAccessor.java  |  14 +-
 .../sis/metadata/StandardImplementation.java       |   2 +-
 .../org/apache/sis/metadata/iso/ISOMetadata.java   |   7 +-
 .../java/org/apache/sis/metadata/package-info.java |   2 +-
 .../referencing/PositionalAccuracyConstant.java    |   1 -
 .../org/apache/sis/parameter/TensorParameters.java |   9 +-
 .../java/org/apache/sis/internal/util/Cloner.java  |   1 +
 .../apache/sis/internal/util/FinalFieldSetter.java | 157 +++++++++++++++++++++
 .../org/apache/sis/internal/util/Utilities.java    |   2 +-
 .../java/org/apache/sis/measure/RangeFormat.java   |  20 +--
 .../java/org/apache/sis/measure/UnitFormat.java    |  21 ++-
 .../apache/sis/util/CorruptedObjectException.java  |  13 +-
 .../main/java/org/apache/sis/util/Exceptions.java  |   4 +
 16 files changed, 277 insertions(+), 65 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Freezer.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Freezer.java
index 7d46361..02de4a5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/Freezer.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/Freezer.java
@@ -21,6 +21,7 @@ import java.util.Set;
 import java.util.EnumSet;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import org.apache.sis.internal.util.Cloner;
 import org.apache.sis.util.collection.CodeListSet;
@@ -35,15 +36,67 @@ import org.apache.sis.metadata.iso.identification.DefaultRepresentativeFraction;
  * tries to avoid creating new clones as much as possible.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
 final class Freezer extends Cloner {
     /**
+     * The {@code Freezer} instance in current use. The clean way would have been to pass
the {@code Freezer}
+     * instance in argument to all {@code freeze()} and {@code unmodifiable()} methods in
metadata packages.
+     * But above-cited methods are public, and we do not want to expose {@code Freezer} in
public API for now.
+     * This thread-local is a workaround for that situation.
+     */
+    private static final ThreadLocal<Freezer> CURRENT = ThreadLocal.withInitial(Freezer::new);
+
+    /**
+     * All objects made immutable during iteration over children properties.
+     * Keys and values are the same instances. This is used for sharing unique instances
when possible.
+     */
+    private final Map<Object,Object> existings;
+
+    /**
+     * Usage count, for determining when to clean {@link #CURRENT}.
+     */
+    private int useCount;
+
+    /**
      * Creates a new {@code Freezer} instance.
      */
-    Freezer() {
+    private Freezer() {
+        existings = new HashMap<>(32);
+    }
+
+    /**
+     * Returns the freezer in current use, or a new one if none.
+     * Callers <strong>must</strong> invoke {@link #release()} in a {@code finally}
block.
+     */
+    static Freezer acquire() {
+        final Freezer freezer = CURRENT.get();
+        freezer.useCount++;
+        return freezer;
+    }
+
+    /**
+     * Release this freezer after usage.
+     */
+    final void release() {
+        if (--useCount == 0) {
+            CURRENT.remove();
+        }
+    }
+
+    /**
+     * Returns a unique instance of the given object (metadata or value).
+     */
+    private Object unique(final Object object) {
+        if (object != null) {
+            final Object c = existings.putIfAbsent(object, object);
+            if (c != null) {
+                return c;
+            }
+        }
+        return object;
     }
 
     /**
@@ -89,12 +142,12 @@ final class Freezer extends Cloner {
          *          its own algorithm for creating an unmodifiable view of metadata.
          */
         if (object instanceof ModifiableMetadata) {
-            return ((ModifiableMetadata) object).unmodifiable();
+            return unique(((ModifiableMetadata) object).unmodifiable());
         }
         if (object instanceof DefaultRepresentativeFraction) {
             final DefaultRepresentativeFraction c = ((DefaultRepresentativeFraction) object).clone();
             c.freeze();
-            return c;
+            return unique(c);
         }
         /*
          * CASE 2 - The object is a collection. All elements are replaced by their
@@ -156,11 +209,11 @@ final class Freezer extends Cloner {
          * CASE 4 - The object is presumed cloneable.
          */
         if (object instanceof Cloneable) {
-            return super.clone(object);
+            return unique(super.clone(object));
         }
         /*
          * CASE 5 - Any other case. The object is assumed immutable and returned unchanged.
          */
-        return object;
+        return unique(object);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
index 1e2af45..21173ab 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
@@ -26,7 +26,8 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.io.IOException;
 import java.io.Serializable;
 import java.io.ObjectInputStream;
-import java.lang.reflect.Field;
+import java.io.InvalidClassException;
+import java.security.AccessController;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.ExtendedElementInformation;
@@ -38,6 +39,7 @@ import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.system.Semaphores;
 import org.apache.sis.internal.system.SystemListener;
 import org.apache.sis.internal.simple.SimpleCitation;
+import org.apache.sis.internal.util.FinalFieldSetter;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 import static org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
@@ -1058,13 +1060,13 @@ public class MetadataStandard implements Serializable {
      * Assigns a {@link ConcurrentMap} instance to the given field.
      * Used on deserialization only.
      */
-    final void setMapForField(final Class<?> classe, final String name) {
+    static <T extends MetadataStandard> void setMapForField(final Class<T> classe,
final T instance, final String name)
+            throws InvalidClassException
+    {
         try {
-            final Field field = classe.getDeclaredField(name);
-            field.setAccessible(true);
-            field.set(this, new ConcurrentHashMap<>());
+            AccessController.doPrivileged(new FinalFieldSetter<>(classe, name)).set(instance,
new ConcurrentHashMap<>());
         } catch (ReflectiveOperationException e) {
-            throw new AssertionError(e);                // Should never happen (tested by
MetadataStandardTest).
+            throw FinalFieldSetter.readFailure(e);
         }
     }
 
@@ -1077,6 +1079,6 @@ public class MetadataStandard implements Serializable {
      */
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException
{
         in.defaultReadObject();
-        setMapForField(MetadataStandard.class, "accessors");
+        setMapForField(MetadataStandard.class, this, "accessors");
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
index 712e82f..4bd36d5 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
@@ -28,13 +28,11 @@ import java.lang.reflect.Modifier;
 import java.nio.charset.Charset;
 import javax.xml.bind.annotation.XmlTransient;
 import org.opengis.util.CodeList;
-import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.CodeListSet;
 import org.apache.sis.internal.util.CheckedHashSet;
 import org.apache.sis.internal.util.CheckedArrayList;
 import org.apache.sis.internal.system.Semaphores;
-import org.apache.sis.internal.system.Modules;
 
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
 
@@ -82,7 +80,7 @@ import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
  * (typically after its construction is completed) by the call to the {@link #freeze()} method.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -187,10 +185,8 @@ public abstract class ModifiableMetadata extends AbstractMetadata implements
Clo
                 /*
                  * The metadata is not cloneable for some reason left to the user
                  * (for example it may be backed by some external database).
-                 * Assumes that the metadata is unmodifiable.
                  */
-                Logging.unexpectedException(Logging.getLogger(Modules.METADATA), getClass(),
"unmodifiable", exception);
-                return this;
+                throw new UnsupportedOperationException(exception);
             }
             candidate.freeze();
             /*
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
index 859311c..1f5ae01 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
@@ -79,7 +79,7 @@ import static org.apache.sis.util.collection.Containers.hashMapCapacity;
  * {@link ModifiableMetadata} instances.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -1180,14 +1180,18 @@ class PropertyAccessor {
     /**
      * Replaces every properties in the specified metadata by their
      * {@linkplain ModifiableMetadata#unmodifiable() unmodifiable variant}.
+     * This method also replaces duplicated elements by single instances.
      *
      * @throws BackingStoreException if the implementation threw a checked exception.
      */
     final void freeze(final Object metadata) throws BackingStoreException {
         assert implementation.isInstance(metadata) : metadata;
-        if (setters != null) try {
-            final Object[] arguments = new Object[1];
-            final Freezer freezer = new Freezer();
+        if (setters == null) {
+            return;
+        }
+        final Object[] arguments = new Object[1];
+        final Freezer freezer = Freezer.acquire();
+        try {
             for (int i=0; i<allCount; i++) {
                 final Method setter = setters[i];
                 if (setter != null) {
@@ -1223,6 +1227,8 @@ class PropertyAccessor {
             }
         } catch (CloneNotSupportedException e) {
             throw new UnsupportedOperationException(e);
+        } finally {
+            freezer.release();
         }
     }
 
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
index 89959d3..18db579 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/StandardImplementation.java
@@ -179,7 +179,7 @@ final class StandardImplementation extends MetadataStandard {
          * newer version of the Apache SIS library. The newer version could contains constants
          * not yet declared in this older SIS version, so we have to use this instance.
          */
-        setMapForField(StandardImplementation.class, "implementations");
+        setMapForField(StandardImplementation.class, this, "implementations");
         return this;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
index 39902ba..cbcea74 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/ISOMetadata.java
@@ -170,12 +170,7 @@ public class ISOMetadata extends ModifiableMetadata implements IdentifiedObject,
     // --------------------------------------------------------------------------------------
 
     /**
-     * Declares this metadata and all its properties as unmodifiable. Any attempt to modify
a property
-     * after this method call will throw an {@link org.apache.sis.metadata.UnmodifiableMetadataException}.
-     * If this metadata is already unmodifiable, then this method does nothing.
-     *
-     * <p>Subclasses usually do not need to override this method since the default
implementation
-     * performs most of its work using Java reflection.</p>
+     * {@inheritDoc}
      */
     @Override
     public void freeze() {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
index 12f5f47..8b301fc 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/package-info.java
@@ -119,7 +119,7 @@
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Adrian Custer (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/PositionalAccuracyConstant.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/PositionalAccuracyConstant.java
index d5d471a..b5e5375 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/PositionalAccuracyConstant.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/PositionalAccuracyConstant.java
@@ -105,7 +105,6 @@ public final class PositionalAccuracyConstant extends DefaultAbsoluteExternalPos
     /**
      * Creates an positional accuracy initialized to the given result.
      */
-    @SuppressWarnings("OverridableMethodCallDuringObjectConstruction")                  //
Safe because this class is final.
     private PositionalAccuracyConstant(final InternationalString measureDescription,
             final InternationalString evaluationMethodDescription, final boolean pass)
     {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
index 96d5de8..c2e186d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorParameters.java
@@ -25,7 +25,7 @@ import java.util.Objects;
 import java.io.IOException;
 import java.io.Serializable;
 import java.io.ObjectInputStream;
-import java.lang.reflect.Field;
+import java.security.AccessController;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.parameter.ParameterDescriptor;
@@ -41,6 +41,7 @@ import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.internal.referencing.provider.Affine;
 import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.util.FinalFieldSetter;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.measure.NumberRange;
@@ -837,11 +838,9 @@ public class TensorParameters<E> implements Serializable {
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException
{
         in.defaultReadObject();
         try {
-            final Field field = TensorParameters.class.getDeclaredField("parameters");
-            field.setAccessible(true);
-            field.set(this, createCache());
+            AccessController.doPrivileged(new FinalFieldSetter<>(TensorParameters.class,
"parameters")).set(this, createCache());
         } catch (ReflectiveOperationException e) {
-            throw new AssertionError(e);
+            throw FinalFieldSetter.readFailure(e);
         }
     }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
index 1f11eb2..795c381 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
@@ -99,6 +99,7 @@ public class Cloner {
                  * in order to report it in case of failure.
                  */
                 if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) try {
+                    // TODO: use trySetAccessible() with JDK9.
                     method.setAccessible(true);
                 } catch (SecurityException e) {
                     security = e;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/FinalFieldSetter.java
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/FinalFieldSetter.java
new file mode 100644
index 0000000..929767a
--- /dev/null
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/FinalFieldSetter.java
@@ -0,0 +1,157 @@
+/*
+ * 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.internal.util;
+
+import java.lang.reflect.Field;
+import java.io.InvalidClassException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import org.apache.sis.internal.system.Modules;
+
+
+/**
+ * Convenience methods for setting the final field of an object with privileged permissions.
+ * This class shall be used only after deserialization or cloning of Apache SIS objects.
+ * The usage pattern is:
+ *
+ * <p><b>On deserialization:</b></p>
+ * {@preformat java
+ *     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
+ *         in.defaultReadObject();
+ *         Object someValue = ...;
+ *         try {
+ *             AccessController.doPrivileged(new FinalFieldSetter<>(MyClass.class,
"myField")).set(this, someValue);
+ *         } catch (ReflectiveOperationException e) {
+ *             throw FinalFieldSetter.readFailure(e);
+ *         }
+ *     }
+ * }
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ *
+ * @param <T> the type of object in which to set a final field. Should be Apache SIS
classes only.
+ *
+ * @since 1.0
+ * @module
+ */
+public final class FinalFieldSetter<T> implements PrivilegedAction<FinalFieldSetter<T>>
{
+    /**
+     * The field to make accessible in a privileged context.
+     */
+    private final Field field;
+
+    /**
+     * A second field to make accessible, or {@code null} if none.
+     */
+    private Field second;
+
+    /**
+     * Creates a new setter for a final field.
+     *
+     * <div class="note"><b>API note:</b>
+     * this constructor is executed in a non-privileged context because {@link SecurityException}
may happen here
+     * only if the given class was loaded with a different class loader than this {@code
FinalFieldSetter} class.
+     * If such situation happen, then the given class is probably not an Apache SIS one.</div>
+     *
+     * @param  classe  the Apache SIS class of object for which to set a final field.
+     * @param  field   the name of the final field for which to set a value.
+     * @throws NoSuchFieldException if the given field has not been found.
+     * @throws SecurityException may happen if the given class has been loaded with an unexpected
class loader.
+     */
+    public FinalFieldSetter(final Class<T> classe, final String field) throws NoSuchFieldException
{
+        assert classe.getName().startsWith(Modules.CLASSNAME_PREFIX) : classe;
+        this.field = classe.getDeclaredField(field);
+    }
+
+    /**
+     * Creates a new setter for two final fields.
+     *
+     * @param  classe  the Apache SIS class of object for which to set a final field.
+     * @param  field   the name of the first final field for which to set a value.
+     * @param  second  the name of the second final field for which to set a value.
+     * @throws NoSuchFieldException if the given field has not been found.
+     * @throws SecurityException may happen if the given class has been loaded with an unexpected
class loader.
+     */
+    public FinalFieldSetter(final Class<T> classe, final String field, final String
second) throws NoSuchFieldException {
+        this(classe, field);
+        this.second = classe.getDeclaredField(second);
+    }
+
+    /**
+     * Makes the final fields accessible.
+     * This is a callback for {@link AccessController#doPrivileged(PrivilegedAction)}.
+     * That call must be done from the caller, not from this {@code FinalFieldSetter} class,
+     * because {@link AccessController} check the caller for determining the permissions.
+     *
+     * @return {@code this}.
+     * @throws SecurityException if write permission has been denied.
+     */
+    @Override
+    public FinalFieldSetter<T> run() throws SecurityException {
+        field.setAccessible(true);
+        if (second != null) {
+            second.setAccessible(true);
+        }
+        return this;
+    }
+
+    /**
+     * Sets the value of the final field.
+     *
+     * @param  instance  the instance on which to set the value.
+     * @param  value     the value to set.
+     * @throws IllegalAccessException may happen if {@link #run()} has not been invoked before
this method.
+     */
+    public final void set(final T instance, final Object value) throws IllegalAccessException
{
+        field.set(instance, value);
+    }
+
+    /**
+     * Sets the values of the final fields.
+     *
+     * @param  instance  the instance on which to set the value.
+     * @param  value     the value of the first field to set.
+     * @param  more      the value of the second field to set.
+     * @throws IllegalAccessException may happen if {@link #run()} has not been invoked before
this method.
+     */
+    public final void set(final T instance, final Object value, final Object more) throws
IllegalAccessException {
+        field .set(instance, value);
+        second.set(instance, more);
+    }
+
+    /**
+     * Creates an exception for a {@code readObject(ObjectInputStream)} method.
+     *
+     * @param  cause  the failure.
+     * @return the exception to throw.
+     */
+    public static InvalidClassException readFailure(final ReflectiveOperationException cause)
{
+        return (InvalidClassException) new InvalidClassException(cause.getLocalizedMessage()).initCause(cause);
+    }
+
+    /**
+     * Creates an exception for a {@code clone()} method.
+     *
+     * @param  cause  the failure.
+     * @return the exception to throw.
+     */
+    public static RuntimeException cloneFailure(final ReflectiveOperationException cause)
{
+        return new RuntimeException(cause);
+        // TODO: use InaccessibleObjectException in JDK9.
+    }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Utilities.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Utilities.java
index 73740cc..84e7b8b 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Utilities.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Utilities.java
@@ -207,7 +207,7 @@ public final class Utilities extends Static {
                 n = Character.charCount(value.codePointAt(i));
             }
         }
-        // Double check since length() is faster than codePointCount(...).
+        // Double check since length() is faster than codePointCount(…).
         if (width > length && (width -= value.codePointCount(0, length)) >
0) {
             format = "%s%s";
             args = new Object[] {value, value};
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java b/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java
index f4c14d9..302fd4c 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java
@@ -31,11 +31,13 @@ import java.text.AttributedCharacterIterator;
 import java.text.FieldPosition;
 import java.text.ParseException;
 import java.text.ParsePosition;
+import java.security.AccessController;
 import javax.measure.Unit;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.internal.util.LocalizedParseException;
+import org.apache.sis.internal.util.FinalFieldSetter;
 
 
 /**
@@ -1030,23 +1032,11 @@ public class RangeFormat extends Format {
     public RangeFormat clone() {
         final RangeFormat f = (RangeFormat) super.clone();
         try {
-            f.setFinal("elementFormat", elementFormat);
-            f.setFinal("unitFormat",    unitFormat);
+            AccessController.doPrivileged(new FinalFieldSetter<>(RangeFormat.class,
"elementFormat", "unitFormat"))
+                            .set(f, elementFormat.clone(), unitFormat.clone());
         } catch (ReflectiveOperationException e) {
-            throw new AssertionError(e);
+            throw FinalFieldSetter.cloneFailure(e);
         }
         return f;
     }
-
-    /**
-     * Sets final field to a clone of the given format.
-     */
-    private void setFinal(final String name, Format value) throws ReflectiveOperationException
{
-        if (value != null) {
-            value = (Format) value.clone();
-            java.lang.reflect.Field f = RangeFormat.class.getDeclaredField(name);
-            f.setAccessible(true);
-            f.set(this, value);
-        }
-    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java b/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
index 15e0ec3..5b640e4 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
@@ -29,12 +29,14 @@ import java.text.ParseException;
 import java.util.ResourceBundle;
 import java.util.MissingResourceException;
 import java.io.IOException;
+import java.security.AccessController;
 import javax.measure.Dimension;
 import javax.measure.Unit;
 import javax.measure.format.ParserException;
 import org.apache.sis.internal.util.Citations;
 import org.apache.sis.internal.util.Constants;
 import org.apache.sis.internal.util.DefinitionURI;
+import org.apache.sis.internal.util.FinalFieldSetter;
 import org.apache.sis.internal.util.XPaths;
 import org.apache.sis.math.Fraction;
 import org.apache.sis.util.ArgumentChecks;
@@ -1376,26 +1378,23 @@ search:     while ((i = CharSequences.skipTrailingWhitespaces(symbols,
start, i)
     public UnitFormat clone() {
         final UnitFormat f = (UnitFormat) super.clone();
         try {
-            f.setFinal("unitToLabel", unitToLabel);
-            f.setFinal("labelToUnit", labelToUnit);
+            AccessController.doPrivileged(new FinalFieldSetter<>(UnitFormat.class,
"unitToLabel", "labelToUnit"))
+                            .set(f, clone(unitToLabel), clone(labelToUnit));
         } catch (ReflectiveOperationException e) {
-            throw new AssertionError(e);
+            throw FinalFieldSetter.cloneFailure(e);
         }
         return f;
     }
 
     /**
-     * Sets final field to a clone of the given map. The given map shall be either
-     * a {@link HashMap} or the instance returned by {@link Collections#emptyMap()}.
+     * Clones the given map, which can be either a {@link HashMap}
+     * or the instance returned by {@link Collections#emptyMap()}.
      */
-    private void setFinal(final String name, Map<?,?> value) throws ReflectiveOperationException
{
+    private static Object clone(final Map<?,?> value) {
         if (value instanceof HashMap<?,?>) {
-            value = (Map<?,?>) ((HashMap<?,?>) value).clone();
+            return ((HashMap<?,?>) value).clone();
         } else {
-            value = new HashMap<>();
+            return new HashMap<>();
         }
-        java.lang.reflect.Field f = UnitFormat.class.getDeclaredField(name);
-        f.setAccessible(true);
-        f.set(this, value);
     }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/CorruptedObjectException.java
b/core/sis-utility/src/main/java/org/apache/sis/util/CorruptedObjectException.java
index 598f4de..e120cf1 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/CorruptedObjectException.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/CorruptedObjectException.java
@@ -58,7 +58,7 @@ import org.opengis.referencing.IdentifiedObject;
  * ignored by the user.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
+ * @version 1.0
  * @since   0.5
  * @module
  */
@@ -85,6 +85,17 @@ public class CorruptedObjectException extends RuntimeException {
     }
 
     /**
+     * Constructs a new exception with the specified cause.
+     *
+     * @param cause  the cause, or {@code null} if none.
+     *
+     * @since 1.0
+     */
+    public CorruptedObjectException(final Exception cause) {
+        super(cause);
+    }
+
+    /**
      * Constructs a new exception with the name of the given object.
      *
      * @param  object  the corrupted object, or {@code null} if unknown.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java b/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java
index f4a1657..e9209b5 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/Exceptions.java
@@ -215,6 +215,10 @@ public final class Exceptions extends Static {
      *       in other {@code SQLException} without additional information.</li>
      * </ul>
      *
+     * <div class="note"><b>Note:</b>
+     * {@link java.security.PrivilegedActionException} is also a wrapper exception, but is
not included in above list
+     * because it is used in very specific contexts.</div>
+     *
      * This method uses only the exception class as criterion;
      * it does not verify if the exception messages are the same.
      *

-- 
To stop receiving notification emails like this one, please contact
desruisseaux@apache.org.

Mime
View raw message