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); } }