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 0d157264f6ee3b5042184ef06870f2e7005d2d20
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sun Jun 18 17:37:26 2023 +0200

    Remove `FinalFieldSetter` (an helper class for clone and deserialization)
    because the reflection methods used by that class are caller-sensitive.
    They do not work anymore when invoked from a different module.
---
 .../sis/coverage/grid/GridCoverageProcessor.java   |   9 +-
 .../org/apache/sis/metadata/MetadataStandard.java  |  25 +---
 .../sis/metadata/StandardImplementation.java       |   6 +-
 .../org/apache/sis/parameter/TensorParameters.java |  14 +--
 .../apache/sis/internal/util/FinalFieldSetter.java | 128 ---------------------
 .../java/org/apache/sis/measure/FormatField.java   |   4 +-
 .../org/apache/sis/measure/QuantityFormat.java     |  21 +++-
 .../java/org/apache/sis/measure/RangeFormat.java   |  19 ++-
 .../java/org/apache/sis/measure/UnitFormat.java    |  24 ++--
 9 files changed, 67 insertions(+), 183 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
index 897c4598c5..01d0d93d92 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridCoverageProcessor.java
@@ -21,6 +21,8 @@ import java.util.Set;
 import java.util.EnumSet;
 import java.util.Objects;
 import java.util.function.Function;
+import java.lang.reflect.Field;
+import java.lang.reflect.InaccessibleObjectException;
 import java.awt.Shape;
 import java.awt.Rectangle;
 import java.awt.image.RenderedImage;
@@ -44,7 +46,6 @@ import org.apache.sis.internal.coverage.MultiSourceArgument;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.collection.WeakHashSet;
-import org.apache.sis.internal.util.FinalFieldSetter;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.measure.NumberRange;
 
@@ -902,12 +903,14 @@ public class GridCoverageProcessor implements Cloneable {
     public GridCoverageProcessor clone() {
         try {
             final GridCoverageProcessor clone = (GridCoverageProcessor) 
super.clone();
-            FinalFieldSetter.set(GridCoverageProcessor.class, 
"imageProcessor", clone, imageProcessor.clone());
+            final Field f = 
GridCoverageProcessor.class.getDeclaredField("imageProcessor");
+            f.setAccessible(true);      // Caller sensitive: must be invoked 
in same module.
+            f.set(clone, imageProcessor.clone());
             return clone;
         } catch (CloneNotSupportedException e) {
             throw new AssertionError(e);
         } catch (ReflectiveOperationException e) {
-            throw FinalFieldSetter.cloneFailure(e);
+            throw (InaccessibleObjectException) new 
InaccessibleObjectException().initCause(e);
         }
     }
 }
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 b66610bf6f..55b96066aa 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,6 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.io.IOException;
 import java.io.Serializable;
 import java.io.ObjectInputStream;
-import java.io.InvalidClassException;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.ExtendedElementInformation;
@@ -39,7 +38,6 @@ 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 org.apache.sis.internal.util.Strings;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
@@ -91,7 +89,7 @@ import static 
org.apache.sis.util.ArgumentChecks.ensureNonNullElement;
  * by a large amount of {@link ModifiableMetadata}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @see AbstractMetadata
  *
@@ -203,8 +201,11 @@ public class MetadataStandard implements Serializable {
      *   <li>{@link Class} if we found the interface for the type but did not 
yet created the {@link PropertyAccessor}.</li>
      *   <li>{@link PropertyAccessor} otherwise.</li>
      * </ul>
+     *
+     * Consider this field as final.
+     * It is not final only for {@link #readObject(ObjectInputStream)} purpose.
      */
-    private final transient ConcurrentMap<CacheKey,Object> accessors;      // 
Written by reflection on deserialization.
+    private transient ConcurrentMap<CacheKey,Object> accessors;
 
     /**
      * Creates a new instance working on implementation of interfaces defined 
in the specified package.
@@ -1093,20 +1094,6 @@ public class MetadataStandard implements Serializable {
         return Strings.bracket(getClass(), citation.getTitle());
     }
 
-    /**
-     * Assigns a {@link ConcurrentMap} instance to the given field.
-     * Used on deserialization only.
-     */
-    static <T extends MetadataStandard> void setMapForField(final Class<T> 
classe, final T instance, final String name)
-            throws InvalidClassException
-    {
-        try {
-            FinalFieldSetter.set(classe, name, instance, new 
ConcurrentHashMap<>());
-        } catch (ReflectiveOperationException e) {
-            throw FinalFieldSetter.readFailure(e);
-        }
-    }
-
     /**
      * Invoked during deserialization for restoring the transient fields.
      *
@@ -1116,6 +1103,6 @@ public class MetadataStandard implements Serializable {
      */
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
         in.defaultReadObject();
-        setMapForField(MetadataStandard.class, this, "accessors");
+        accessors = new ConcurrentHashMap<>();
     }
 }
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 76d0f246aa..14196dcfee 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
@@ -20,6 +20,7 @@ import java.util.Map;
 import java.util.IdentityHashMap;
 import java.util.logging.Logger;
 import java.io.ObjectStreamException;
+import java.util.concurrent.ConcurrentHashMap;
 import org.opengis.annotation.UML;
 import org.opengis.annotation.Classifier;
 import org.opengis.annotation.Stereotype;
@@ -64,6 +65,7 @@ final class StandardImplementation extends MetadataStandard {
 
     /**
      * Implementations for a given interface, computed when first needed then 
cached.
+     * Consider this field as final. It is not final only for {@link 
#readResolve()} purpose.
      *
      * <h4>Implementation note</h4>
      * In the particular case of {@code Class} keys, {@code IdentityHashMap} 
and {@code HashMap} have identical
@@ -72,7 +74,7 @@ final class StandardImplementation extends MetadataStandard {
      * But maybe the most interesting property is that it allocates less 
objects since {@code IdentityHashMap}
      * implementation doesn't need the chain of objects created by {@code 
HashMap}.
      */
-    private final transient Map<Class<?>,Class<?>> implementations;     // 
written by reflection on deserialization.
+    private transient Map<Class<?>,Class<?>> implementations;
 
     /**
      * Creates a new instance working on implementation of interfaces defined 
in the specified package.
@@ -185,7 +187,7 @@ final class StandardImplementation extends MetadataStandard 
{
          * newer version of the Apache SIS library. The newer version could 
contain constants
          * not yet declared in this older SIS version, so we have to use this 
instance.
          */
-        setMapForField(StandardImplementation.class, this, "implementations");
+        implementations = new ConcurrentHashMap<>();
         return this;
     }
 }
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 8c90808d59..893e4e66d1 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
@@ -39,7 +39,6 @@ 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;
@@ -137,7 +136,7 @@ import org.apache.sis.util.resources.Errors;
  *     }
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @param <E>  the type of tensor element values.
  *
@@ -284,8 +283,11 @@ public class TensorParameters<E> implements Serializable {
     /**
      * The cached descriptors for each elements in a tensor. Descriptors do 
not depend on tensor element values.
      * Consequently, the same descriptors can be reused for all {@link 
TensorValues} instances.
+     *
+     * <p>Consider this field as final.
+     * It is not final only for {@link #readObject(ObjectInputStream)} 
implementation.</p>
      */
-    private final transient ParameterDescriptor<E>[] parameters;
+    private transient ParameterDescriptor<E>[] parameters;
 
     /**
      * The elements for the 0 and 1 values, or {@code null} if unknown.
@@ -833,10 +835,6 @@ public class TensorParameters<E> implements Serializable {
      */
     private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
         in.defaultReadObject();
-        try {
-            FinalFieldSetter.set(TensorParameters.class, "parameters", this, 
createCache());
-        } catch (ReflectiveOperationException e) {
-            throw FinalFieldSetter.readFailure(e);
-        }
+        parameters = createCache();
     }
 }
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
deleted file mode 100644
index e94f42cba9..0000000000
--- 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/FinalFieldSetter.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.lang.reflect.InaccessibleObjectException;
-import org.apache.sis.internal.system.Modules;
-
-
-/**
- * Convenience methods for setting the final field of an object.
- * This class shall be used only after deserialization or cloning of Apache 
SIS objects.
- * The usage pattern is:
- *
- * <p><b>On deserialization:</b></p>
- * {@snippet lang="java" :
- *     private void readObject(ObjectInputStream in) throws IOException, 
ClassNotFoundException {
- *         in.defaultReadObject();
- *         Object someValue = ...;
- *         try {
- *             FinalFieldSetter.set(MyClass.class, "myField", this, someValue);
- *         } catch (ReflectiveOperationException e) {
- *             throw FinalFieldSetter.readFailure(e);
- *         }
- *     }
- * }
- *
- * <p><b>On clone:</b></p>
- * Same as above but invoking {@code cloneFailure(e)} if the operation failed.
- * The exception to be thrown is not the same.
- *
- * <h2>Historical note</h2>
- * Previous version was implementing {@code 
PrivilegedAction<FinalFieldSetter<T>>}
- * for working in the context of a security manager. This feature has been 
removed
- * since {@code java.security.AccessController} has been deprecated in Java 17.
- *
- * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
- *
- * @see <a href="https://openjdk.java.net/jeps/411";>JEP-411</a>
- * @see <a href="https://issues.apache.org/jira/browse/SIS-525";>SIS-525</a>
- *
- * @since 1.0
- */
-public final class FinalFieldSetter {
-    /**
-     * Do not allow instantiation of this class.
-     */
-    private FinalFieldSetter() {
-    }
-
-    /**
-     * Sets the value of the final field.
-     *
-     * @param  <T>       the type of object in which to set a final field. 
Should be Apache SIS classes only.
-     * @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.
-     * @param  instance  the instance on which to set the value.
-     * @param  value     the value to set.
-     * @throws NoSuchFieldException if the given field has not been found.
-     * @throws IllegalAccessException if the value cannot be set.
-     */
-    public static <T> void set(final Class<T> classe, final String field, 
final T instance,
-            final Object value) throws NoSuchFieldException, 
IllegalAccessException
-    {
-        assert classe.getName().startsWith(Modules.CLASSNAME_PREFIX) : classe;
-        final Field f = classe.getDeclaredField(field);
-        f.setAccessible(true);
-        f.set(instance, value);
-    }
-
-    /**
-     * Sets the values of the final fields.
-     *
-     * @param  <T>       the type of object in which to set a final field. 
Should be Apache SIS classes only.
-     * @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.
-     * @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 NoSuchFieldException if a given field has not been found.
-     * @throws IllegalAccessException if a value cannot be set.
-     */
-    public static <T> void set(final Class<T> classe, final String field, 
final String second, final T instance,
-            final Object value, final Object more) throws 
NoSuchFieldException, IllegalAccessException
-    {
-        set(classe, field, instance, value);
-        final Field f = classe.getDeclaredField(second);
-        f.setAccessible(true);
-        f.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 (InaccessibleObjectException) new 
InaccessibleObjectException().initCause(cause);
-    }
-}
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java 
b/core/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java
index 81be32cb34..a5e31d71a9 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java
@@ -24,7 +24,7 @@ import java.text.Format;
  * Base class of format fields.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.3
+ * @version 1.4
  * @since   0.3
  */
 class FormatField extends Format.Field {
@@ -68,7 +68,7 @@ class FormatField extends Format.Field {
         final Class<?> type = getClass();
         try {
             return type.cast(type.getField(getName()).get(null));
-        } catch (Exception cause) {                                 // Many 
exceptions, including unchecked ones.
+        } catch (ReflectiveOperationException | ClassCastException cause) {
             throw (InvalidObjectException) new 
InvalidObjectException(cause.toString()).initCause(cause);
         }
     }
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/measure/QuantityFormat.java 
b/core/sis-utility/src/main/java/org/apache/sis/measure/QuantityFormat.java
index 9823f9464c..1bc913a303 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/QuantityFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/QuantityFormat.java
@@ -22,13 +22,13 @@ import java.text.FieldPosition;
 import java.text.NumberFormat;
 import java.text.ParsePosition;
 import java.io.IOException;
+import java.lang.reflect.InaccessibleObjectException;
 import javax.measure.Quantity;
 import javax.measure.Unit;
 import javax.measure.format.MeasurementParseException;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.internal.util.FinalFieldSetter;
 
 
 /**
@@ -238,13 +238,22 @@ public class QuantityFormat extends Format implements 
javax.measure.format.Quant
      */
     @Override
     public QuantityFormat clone() {
-        final QuantityFormat clone = (QuantityFormat) super.clone();
+        final QuantityFormat f = (QuantityFormat) super.clone();
         try {
-            FinalFieldSetter.set(QuantityFormat.class, "numberFormat", 
"unitFormat",
-                                 clone, numberFormat.clone(), 
unitFormat.clone());
+            f.clone("numberFormat");
+            f.clone("unitFormat");
         } catch (ReflectiveOperationException e) {
-            throw FinalFieldSetter.cloneFailure(e);
+            throw (InaccessibleObjectException) new 
InaccessibleObjectException().initCause(e);
         }
-        return clone;
+        return f;
+    }
+
+    /**
+     * Clones the value in the specified field.
+     */
+    private void clone(final String field) throws ReflectiveOperationException 
{
+        final var f = QuantityFormat.class.getDeclaredField(field);
+        f.setAccessible(true);
+        f.set(this, ((Format) f.get(this)).clone());
     }
 }
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 809fc5c9ed..233402c612 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
@@ -36,6 +36,7 @@ import java.time.format.DateTimeFormatterBuilder;
 import java.time.temporal.Temporal;
 import java.time.Instant;
 import javax.measure.Unit;
+import java.lang.reflect.InaccessibleObjectException;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.ArgumentChecks;
@@ -43,7 +44,6 @@ 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.StandardDateFormat;
-import org.apache.sis.internal.util.FinalFieldSetter;
 import org.apache.sis.internal.util.Numerics;
 
 
@@ -98,7 +98,7 @@ import org.apache.sis.internal.util.Numerics;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  *
  * @see Range#toString()
  * @see <a href="https://en.wikipedia.org/wiki/ISO_31-11";>Wikipedia: ISO 
31-11</a>
@@ -1077,11 +1077,20 @@ public class RangeFormat extends Format implements 
Localized {
     public RangeFormat clone() {
         final RangeFormat f = (RangeFormat) super.clone();
         try {
-            FinalFieldSetter.set(RangeFormat.class, "elementFormat", 
"unitFormat",
-                                 f, elementFormat.clone(), unitFormat.clone());
+            f.clone("elementFormat");
+            f.clone("unitFormat");
         } catch (ReflectiveOperationException e) {
-            throw FinalFieldSetter.cloneFailure(e);
+            throw (InaccessibleObjectException) new 
InaccessibleObjectException().initCause(e);
         }
         return f;
     }
+
+    /**
+     * Clones the value in the specified field.
+     */
+    private void clone(final String field) throws ReflectiveOperationException 
{
+        final var f = RangeFormat.class.getDeclaredField(field);
+        f.setAccessible(true);
+        f.set(this, ((Format) f.get(this)).clone());
+    }
 }
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 a7ea2ff712..f5a4337175 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
@@ -30,13 +30,13 @@ import java.util.ResourceBundle;
 import java.util.MissingResourceException;
 import java.io.IOException;
 import java.io.UncheckedIOException;
+import java.lang.reflect.InaccessibleObjectException;
 import javax.measure.Dimension;
 import javax.measure.Unit;
 import javax.measure.format.MeasurementParseException;
 import org.apache.sis.internal.system.Configuration;
 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.math.Fraction;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.math.MathFunctions;
@@ -71,7 +71,7 @@ import org.apache.sis.util.logging.Logging;
  * each thread should have its own {@code UnitFormat} instance.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @see Units#valueOf(String)
  *
@@ -1625,23 +1625,27 @@ search:     while ((i = 
CharSequences.skipTrailingWhitespaces(symbols, start, i)
     public UnitFormat clone() {
         final UnitFormat f = (UnitFormat) super.clone();
         try {
-            FinalFieldSetter.set(UnitFormat.class, "unitToLabel", 
"labelToUnit",
-                                 f, clone(unitToLabel), clone(labelToUnit));
+            f.clone("unitToLabel");
+            f.clone("labelToUnit");
         } catch (ReflectiveOperationException e) {
-            throw FinalFieldSetter.cloneFailure(e);
+            throw (InaccessibleObjectException) new 
InaccessibleObjectException().initCause(e);
         }
         return f;
     }
 
     /**
-     * Clones the given map, which can be either a {@link HashMap}
-     * or the instance returned by {@link Map#of()}.
+     * Clones the map in the specified field.
+     * The map can be either a {@link HashMap} or the instance returned by 
{@link Map#of()}.
      */
-    private static Object clone(final Map<?,?> value) {
+    private void clone(final String field) throws ReflectiveOperationException 
{
+        final var f = UnitFormat.class.getDeclaredField(field);
+        f.setAccessible(true);
+        Object value = f.get(this);
         if (value instanceof HashMap<?,?>) {
-            return ((HashMap<?,?>) value).clone();
+            value = ((HashMap<?,?>) value).clone();
         } else {
-            return new HashMap<>();
+            value = new HashMap<>();
         }
+        f.set(this, value);
     }
 }

Reply via email to