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 3808f1393025f5467fd8ed6a018edbaf3e0e38a4
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sat Jun 17 17:23:45 2023 +0200

    Add a `CloneAccess` internal interface as a workaround for accessing the 
`clone()` method in internal classes of other modules.
---
 .../org/apache/sis/feature/CharacteristicMap.java  |   5 +-
 .../java/org/apache/sis/feature/DenseFeature.java  |   5 +-
 .../apache/sis/feature/MultiValuedAttribute.java   |   5 +-
 .../org/apache/sis/feature/SingletonAttribute.java |   5 +-
 .../java/org/apache/sis/feature/SparseFeature.java |   5 +-
 .../sis/internal/jaxb/SpecializedIdentifier.java   |   5 +-
 .../iso/quality/DefaultEvaluationMethod.java       |   3 +-
 .../sis/internal/map/coverage/RenderingData.java   |   3 +-
 .../sis/internal/referencing/AnnotatedMatrix.java  |   5 +-
 .../sis/internal/referencing/j2d/AffineMatrix.java |   3 +-
 .../org/apache/sis/parameter/TensorValues.java     |   3 +-
 .../org/apache/sis/internal/util/CloneAccess.java  |  48 +++++++
 .../java/org/apache/sis/internal/util/Cloner.java  | 155 +++++++++++----------
 .../sis/util/collection/DefaultTreeTable.java      |   4 +-
 14 files changed, 162 insertions(+), 92 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java 
b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
index ecca81273a..c5b19ed0ee 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/feature/CharacteristicMap.java
@@ -20,6 +20,7 @@ import java.util.Map;
 import org.opengis.util.GenericName;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.util.Cloner;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.internal.util.AbstractMap;
 import org.apache.sis.internal.util.AbstractMapEntry;
 import org.apache.sis.internal.feature.Resources;
@@ -36,10 +37,10 @@ import org.opengis.feature.PropertyNotFoundException;
  * This map holds only the attribute characteristics which have been 
explicitly set or requested.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
+ * @version 1.4
  * @since   0.5
  */
-final class CharacteristicMap extends AbstractMap<String,Attribute<?>> 
implements Cloneable {
+final class CharacteristicMap extends AbstractMap<String,Attribute<?>> 
implements CloneAccess {
     /**
      * The attribute source for which to provide characteristics.
      */
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java 
b/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
index 54193162b0..83815273c2 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
@@ -20,6 +20,7 @@ import java.util.Map;
 import java.util.Arrays;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.quality.DataQuality;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.internal.util.Cloner;
 import org.apache.sis.util.ArgumentChecks;
 
@@ -37,14 +38,14 @@ import org.opengis.feature.PropertyNotFoundException;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Marc le Bihan
- * @version 1.1
+ * @version 1.4
  *
  * @see SparseFeature
  * @see DefaultFeatureType
  *
  * @since 0.5
  */
-final class DenseFeature extends AbstractFeature implements Cloneable {
+final class DenseFeature extends AbstractFeature implements CloneAccess {
     /**
      * For cross-version compatibility.
      */
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
 
b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
index 1f1b7a2b28..c89fda682b 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
@@ -17,6 +17,7 @@
 package org.apache.sis.feature;
 
 import java.util.Collection;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.internal.util.CheckedArrayList;
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.util.ArgumentChecks;
@@ -47,7 +48,7 @@ import org.opengis.feature.MultiValuedPropertyException;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.4
  *
  * @param <V>  the type of the attribute values.
  *
@@ -55,7 +56,7 @@ import org.opengis.feature.MultiValuedPropertyException;
  *
  * @since 0.5
  */
-final class MultiValuedAttribute<V> extends AbstractAttribute<V> implements 
Cloneable {
+final class MultiValuedAttribute<V> extends AbstractAttribute<V> implements 
CloneAccess {
     /**
      * For cross-version compatibility.
      */
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java 
b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
index ab25bc7501..0750738d1c 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
@@ -17,6 +17,7 @@
 package org.apache.sis.feature;
 
 import java.util.Objects;
+import org.apache.sis.internal.util.CloneAccess;
 
 // Branch-dependent imports
 import org.opengis.feature.AttributeType;
@@ -38,7 +39,7 @@ import org.opengis.feature.AttributeType;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.4
  *
  * @param <V>  the type of the attribute value.
  *
@@ -47,7 +48,7 @@ import org.opengis.feature.AttributeType;
  * @since 0.5
  */
 @SuppressWarnings("CloneableImplementsClone")       // Nothing to add compared 
to subclass.
-final class SingletonAttribute<V> extends AbstractAttribute<V> implements 
Cloneable {
+final class SingletonAttribute<V> extends AbstractAttribute<V> implements 
CloneAccess {
     /**
      * For cross-version compatibility.
      */
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java 
b/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
index 7b3ddce242..781bd7475a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
@@ -22,6 +22,7 @@ import java.util.Objects;
 import java.util.ConcurrentModificationException;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.quality.DataQuality;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.internal.util.Cloner;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CorruptedObjectException;
@@ -41,14 +42,14 @@ import org.opengis.feature.PropertyNotFoundException;
  * @author  Travis L. Pinney
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  *
  * @see DenseFeature
  * @see DefaultFeatureType
  *
  * @since 0.5
  */
-final class SparseFeature extends AbstractFeature implements Cloneable {
+final class SparseFeature extends AbstractFeature implements CloneAccess {
     /**
      * For cross-version compatibility.
      */
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
index 4620c3c516..b783a3188f 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/SpecializedIdentifier.java
@@ -28,6 +28,7 @@ import org.apache.sis.xml.IdentifierMap;
 import org.apache.sis.xml.IdentifierSpace;
 import org.apache.sis.xml.ValueConverter;
 import org.apache.sis.util.resources.Messages;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.metadata.iso.citation.Citations;
 
 // Branch-dependent imports
@@ -40,13 +41,13 @@ import org.opengis.metadata.Identifier;
  * is an object of a type constrained by the authority.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 1.4
  *
  * @param <T>  the value type, typically {@link XLink}, {@link UUID} or {@link 
String}.
  *
  * @since 0.3
  */
-public final class SpecializedIdentifier<T> implements Identifier, Cloneable, 
Serializable {
+public final class SpecializedIdentifier<T> implements Identifier, 
CloneAccess, Serializable {
     /**
      * For cross-version compatibility.
      */
diff --git 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
index a88ac7b622..98b8c69e3c 100644
--- 
a/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
+++ 
b/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/DefaultEvaluationMethod.java
@@ -32,6 +32,7 @@ import org.opengis.metadata.quality.EvaluationMethodType;
 import org.opengis.metadata.quality.DataEvaluation;
 import org.opengis.metadata.quality.AggregationDerivation;
 import org.apache.sis.internal.system.Semaphores;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
@@ -108,7 +109,7 @@ public class DefaultEvaluationMethod extends ISOMetadata 
implements EvaluationMe
      * The start and end times as a list of O, 1 or 2 elements.
      */
     private static final class Dates extends AbstractList<Temporal>
-            implements CheckedContainer<Temporal>, Cloneable, Serializable
+            implements CheckedContainer<Temporal>, CloneAccess, Serializable
     {
         /**
          * For cross-version compatibility.
diff --git 
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java
 
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java
index 0b5f9efe5b..e1d83fbd21 100644
--- 
a/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java
+++ 
b/core/sis-portrayal/src/main/java/org/apache/sis/internal/map/coverage/RenderingData.java
@@ -57,6 +57,7 @@ import org.apache.sis.internal.coverage.j2d.ColorModelType;
 import org.apache.sis.internal.coverage.j2d.ImageUtilities;
 import org.apache.sis.internal.referencing.WraparoundApplicator;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.math.Statistics;
 import org.apache.sis.measure.Quantities;
@@ -106,7 +107,7 @@ import org.apache.sis.portrayal.PlanarCanvas;       // For 
javadoc.
  * @version 1.4
  * @since   1.1
  */
-public class RenderingData implements Cloneable {
+public class RenderingData implements CloneAccess {
     /**
      * The logger for portrayal.
      */
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AnnotatedMatrix.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AnnotatedMatrix.java
index 33ac141b41..3a53ae65a0 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AnnotatedMatrix.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/AnnotatedMatrix.java
@@ -18,6 +18,7 @@ package org.apache.sis.internal.referencing;
 
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.metadata.quality.PositionalAccuracy;
+import org.apache.sis.internal.util.CloneAccess;
 
 
 /**
@@ -25,10 +26,10 @@ import org.opengis.metadata.quality.PositionalAccuracy;
  * We use this class for passing additional information in methods that 
returns only a {@link Matrix}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.4
  * @since   1.1
  */
-public final class AnnotatedMatrix implements Matrix, Cloneable {
+public final class AnnotatedMatrix implements Matrix, CloneAccess {
     /**
      * The matrix which contains the actual values.
      */
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
index 2d719617bd..58aab57c37 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
@@ -20,6 +20,7 @@ import java.util.Arrays;
 import java.io.Serializable;
 import java.awt.geom.AffineTransform;
 import org.opengis.referencing.operation.Matrix;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
 import org.apache.sis.referencing.operation.matrix.Matrices;
@@ -36,7 +37,7 @@ import org.apache.sis.util.ArgumentChecks;
  * @version 1.4
  * @since   0.5
  */
-class AffineMatrix extends MatrixSIS implements Serializable, Cloneable {
+class AffineMatrix extends MatrixSIS implements Serializable, CloneAccess {
     /**
      * For cross-version compatibility.
      */
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorValues.java 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorValues.java
index befec92301..501eca0d68 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorValues.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/TensorValues.java
@@ -36,6 +36,7 @@ import org.apache.sis.referencing.operation.matrix.Matrices;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.referencing.WKTUtilities;
 import org.apache.sis.internal.referencing.WKTKeywords;
+import org.apache.sis.internal.util.CloneAccess;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.io.wkt.Formatter;
@@ -61,7 +62,7 @@ import org.apache.sis.util.resources.Errors;
  */
 @XmlTransient
 final class TensorValues<E> extends AbstractParameterDescriptor
-        implements ParameterDescriptorGroup, ParameterValueGroup, Cloneable
+        implements ParameterDescriptorGroup, ParameterValueGroup, CloneAccess
 {
     /**
      * Serial number for inter-operability with different versions.
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/CloneAccess.java 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CloneAccess.java
new file mode 100644
index 0000000000..5b54ae7479
--- /dev/null
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/CloneAccess.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+
+/**
+ * Workaround for the absence of public {@code clone()} method in the standard 
interface.
+ * The purpose of this interface is to avoid the following exception when 
{@link Cloner}
+ * tries to clone a class defined in an internal package of another module:
+ *
+ * <blockquote>{@link IllegalAccessException}: class {@link Cloner}
+ * (in module {@code org.apache.sis.util}) cannot access class <var>Foo</var>
+ * (in module <var>bar</var>) because module <var>bar</var> does not export
+ * <var>foo</var> to module {@code org.apache.sis.util}</blockquote>
+ *
+ * This workaround is needed only for Apache SIS internal classes, because 
{@link Cloner}
+ * usage of reflection should work for exported packages.
+ *
+ * <p>This interface may be removed in any future Apache SIS version if we 
find a better
+ * way to workaround the lack of public {@code clone()} method in {@link 
Cloneable}.</p>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.4
+ * @since   1.4
+ */
+public interface CloneAccess extends java.lang.Cloneable {
+    /**
+     * Returns a clone of this object.
+     *
+     * @return a clone of this object.
+     * @throws CloneNotSupportedException if clones are not supported.
+     */
+    Object clone() throws CloneNotSupportedException;
+}
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 29955d6da9..ad15639b7f 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
@@ -82,7 +82,7 @@ public final class Cloner {
     }
 
     /**
-     * Clones the given array, then clone all array elements recursively.
+     * Clones the given array, then clones all array elements recursively.
      *
      * @param  array          the array to clone.
      * @param  componentType  value of {@code 
array.getClass().getComponentType()}.
@@ -107,8 +107,8 @@ public final class Cloner {
     }
 
     /**
-     * Clones the given object. If the given object does not provide a public 
{@code clone()}
-     * method, then there is a choice:
+     * Clones the given object.
+     * If the given object does not provide a public {@code clone()} method, 
then there is a choice:
      *
      * <ul>
      *   <li>If {@code isCloneRequired(object)} returns {@code true} (the 
default),
@@ -128,52 +128,56 @@ public final class Cloner {
         if (result != null) {
             return result;
         }
-        final Class<?> valueType = object.getClass();
-        final Class<?> componentType = valueType.getComponentType();
-        if (componentType != null) {
-            return cloneArray(object, componentType);
-        }
-        RuntimeException security = null;
-        result = object;
-        try {
-            if (valueType != type) {
-                method = valueType.getMethod("clone", (Class<?>[]) null);
-                type = valueType;                                           // 
Set only if the above line succeed.
+        if (object instanceof CloneAccess) {
+            result = ((CloneAccess) object).clone();
+        } else {
+            final Class<?> valueType = object.getClass();
+            final Class<?> componentType = valueType.getComponentType();
+            if (componentType != null) {
+                return cloneArray(object, componentType);
+            }
+            RuntimeException security = null;
+            result = object;
+            try {
+                if (valueType != type) {
+                    method = valueType.getMethod("clone", (Class<?>[]) null);
+                    type = valueType;                                          
 // Set only if the above line succeed.
+                    /*
+                     * If the class implementing the `clone()` method is not 
public, we may not be able to access that
+                     * method even if it is public. Try to make the method 
accessible. If we fail, try to clone anyway
+                     * because maybe a parent class is accessible, but we 
remember the exception in order to report it
+                     * in case of failure.
+                     */
+                    if 
(!Modifier.isPublic(method.getDeclaringClass().getModifiers())) try {
+                        method.setAccessible(true);
+                    } catch (SecurityException | InaccessibleObjectException 
e) {
+                        security = e;
+                    }
+                }
                 /*
-                 * If the class implementing the `clone()` method is not 
public, we may not be able to access that
-                 * method even if it is public. Try to make the method 
accessible. If we fail for security reason,
-                 * we will still attempt to clone (maybe a parent class is 
public), but we remember the exception
-                 * in order to report it in case of failure.
+                 * `method` may be null if a previous call to this 
clone(Object) method threw NoSuchMethodException
+                 * (see the first `catch` block below). In this context, 
`null` means "no public clone() method".
                  */
-                if 
(!Modifier.isPublic(method.getDeclaringClass().getModifiers())) try {
-                    method.setAccessible(true);
-                } catch (SecurityException | InaccessibleObjectException e) {
-                    security = e;
+                if (method != null) {
+                    result = method.invoke(object, (Object[]) null);
+                }
+            } catch (NoSuchMethodException e) {
+                if (isCloneRequired) {
+                    throw fail(e, valueType);
+                }
+                method = null;
+                type = valueType;
+            } catch (IllegalAccessException e) {
+                if (security != null) {
+                    e.addSuppressed(security);
                 }
-            }
-            /*
-             * `method` may be null if a previous call to this clone(Object) 
method threw NoSuchMethodException
-             * (see the first `catch` block below). In this context, `null` 
means "no public clone() method".
-             */
-            if (method != null) {
-                result = method.invoke(object, (Object[]) null);
-            }
-        } catch (NoSuchMethodException e) {
-            if (isCloneRequired) {
+                throw fail(e, valueType);
+            } catch (InvocationTargetException e) {
+                rethrow(e.getCause());
+                throw fail(e, valueType);
+            } catch (SecurityException e) {
                 throw fail(e, valueType);
             }
-            method = null;
-            type = valueType;
-        } catch (IllegalAccessException e) {
-            if (security != null) {
-                e.addSuppressed(security);
-            }
-            throw fail(e, valueType);
-        } catch (InvocationTargetException e) {
-            rethrow(e.getCause());
-            throw fail(e, valueType);
-        } catch (SecurityException e) {
-            throw fail(e, valueType);
         }
         if (cloneResults.put(object, result) != null) {
             // Should never happen unless we have a bug.
@@ -219,6 +223,8 @@ public final class Cloner {
      * This method may be convenient when there is only one object to clone, 
otherwise instantiating a new
      * {@code Cloner} object is more efficient.
      *
+     * <p>Callers should test {@code if (object instanceof Cloneable)} before 
to invoke this method.</p>
+     *
      * @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.
@@ -227,36 +233,39 @@ public final class Cloner {
      */
     @SuppressWarnings("SuspiciousSystemArraycopy")
     public static Object cloneIfPublic(final Object object) throws 
CloneNotSupportedException {
-        if (object != null) {
-            final Class<?> type = object.getClass();
-            final Class<?> componentType = type.getComponentType();
-            if (componentType != null) {
-                if (componentType.isPrimitive()) {
-                    final int length = Array.getLength(object);
-                    final Object copy = Array.newInstance(componentType, 
length);
-                    System.arraycopy(object, 0, copy, 0, length);
-                    return copy;
-                }
-                return new Cloner().cloneArray(object, componentType);
-            }
-            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);
+        if (object instanceof CloneAccess) {
+            return ((CloneAccess) object).clone();
+        }
+        final Class<?> type = object.getClass();
+        final Class<?> componentType = type.getComponentType();
+        if (componentType != null) {
+            if (componentType.isPrimitive()) {
+                final int length = Array.getLength(object);
+                final Object copy = Array.newInstance(componentType, length);
+                System.arraycopy(object, 0, copy, 0, length);
+                return copy;
             }
+            return new Cloner().cloneArray(object, componentType);
+        }
+        try {
+            final Method method = type.getMethod("clone", (Class[]) null);
+            return method.invoke(object, (Object[]) null);
+        } catch (NoSuchMethodException e) {
+            /*
+             * May happen if the `clone()` method is not public.
+             * The method inherited from `Object` is protected,
+             * and `getMethod(…)` does not return protected methods.
+             */
+            return object;
+        } catch (SecurityException | IllegalAccessException e) {
+            /*
+             * May happen if the class is defined in a module which
+             * does not export the package containing the class.
+             */
+            throw fail(e, type);
+        } catch (InvocationTargetException e) {
+            rethrow(e.getCause());
+            throw fail(e, type);
         }
-        return object;
     }
 }
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
 
b/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
index f46178efd1..71654783e6 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
@@ -258,7 +258,9 @@ public class DefaultTreeTable implements TreeTable, 
Cloneable, Serializable {
     @Override
     public DefaultTreeTable clone() throws CloneNotSupportedException {
         final DefaultTreeTable clone = (DefaultTreeTable) super.clone();
-        clone.root = (TreeTable.Node) Cloner.cloneIfPublic(clone.root);
+        if (clone.root instanceof Cloneable) {
+            clone.root = (TreeTable.Node) Cloner.cloneIfPublic(clone.root);
+        }
         return clone;
     }
 

Reply via email to