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