sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1494312 - in /sis/branches/JDK7/core/sis-metadata/src: main/java/org/apache/sis/metadata/ test/java/org/apache/sis/metadata/
Date Tue, 18 Jun 2013 21:23:59 GMT
Author: desruisseaux
Date: Tue Jun 18 21:23:59 2013
New Revision: 1494312

URL: http://svn.apache.org/r1494312
Log:
Added checks against infinite recursivity and/or test cases for
AbstractMetadata.equals(Object), hashCode(), isEmpty() and prune().

Added:
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ObjectPair.java
  (with props)
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/RecursivityGuard.java
  (with props)
Modified:
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/PrunerTest.java

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java?rev=1494312&r1=1494311&r2=1494312&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
[UTF-8] Tue Jun 18 21:23:59 2013
@@ -314,13 +314,15 @@ public abstract class AbstractMetadata i
 
     /**
      * Computes a hash code value for this metadata using Java reflection. The hash code
-     * is defined as the sum of hash code values of all non-empty properties. This is a
-     * similar contract than {@link java.util.Set#hashCode()} and ensures that the hash code
-     * value is insensitive to the ordering of properties.
+     * is defined as the sum of hash code values of all non-empty properties, excluding
+     * cyclic dependencies. For acyclic metadata, this method contract is compatible with
+     * the {@link java.util.Set#hashCode()} one and ensures that the hash code value is
+     * insensitive to the ordering of properties.
      *
      * {@note This method does not cache the value because current implementation has no
notification
      *        mechanism for tracking changes in children properties. If this metadata is
known to be
-     *        immutable, then subclasses may consider caching the hash code value at their
choice.}
+     *        immutable, then subclasses may consider caching the hash code value if performance
is
+     *        important.}
      *
      * @see MetadataStandard#hashCode(Object)
      */

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java?rev=1494312&r1=1494311&r2=1494312&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
[UTF-8] Tue Jun 18 21:23:59 2013
@@ -746,7 +746,25 @@ public class MetadataStandard implements
         if (accessor.type != findInterface(metadata2.getClass())) {
             return false;
         }
-        return accessor.equals(metadata1, metadata2, mode);
+        /*
+         * At this point, we have to perform the actual property-by-property comparison.
+         * Cycle may exist in metadata tree, so we have to keep trace of pair in process
+         * of being compared for avoiding infinite recursivity.
+         */
+        final ObjectPair pair = new ObjectPair(metadata1, metadata2);
+        final Set<ObjectPair> inProgress = ObjectPair.CURRENT.get();
+        if (inProgress.add(pair)) try {
+            return accessor.equals(metadata1, metadata2, mode);
+        } finally {
+            inProgress.remove(pair);
+        } else {
+            /*
+             * If we get here, a cycle has been found. Returns 'true' in order to allow the
caller to continue
+             * comparing other properties. It is okay because someone else is comparing those
two same objects,
+             * and that later comparison will do the actual check for property values.
+             */
+            return true;
+        }
     }
 
     /**
@@ -756,14 +774,28 @@ public class MetadataStandard implements
      * and ensures that the hash code value is insensitive to the ordering of properties.
      *
      * @param  metadata The metadata object to compute hash code.
-     * @return A hash code value for the specified metadata.
+     * @return A hash code value for the specified metadata, or 0 if the given metadata is
null.
      * @throws ClassCastException if the metadata object doesn't implement a metadata
      *         interface of the expected package.
      *
      * @see AbstractMetadata#hashCode()
      */
     public int hashCode(final Object metadata) throws ClassCastException {
-        return getAccessor(metadata.getClass(), true).hashCode(metadata);
+        if (metadata != null) {
+            final Map<Object,Object> inProgress = RecursivityGuard.HASH_CODES.get();
+            if (inProgress.put(metadata, Boolean.TRUE) == null) try {
+                return getAccessor(metadata.getClass(), true).hashCode(metadata);
+            } finally {
+                inProgress.remove(metadata);
+            }
+            /*
+             * If we get there, a cycle has been found. We can not compute a hash code value
for that metadata.
+             * However it should not be a problem since this metadata is part of a bigger
metadata object, and
+             * that enclosing object has other properties for computing its hash code. We
just need the result
+             * to be consistent, we should be the case if properties ordering is always the
same.
+             */
+        }
+        return 0;
     }
 
     /**

Added: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ObjectPair.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ObjectPair.java?rev=1494312&view=auto
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ObjectPair.java
(added)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ObjectPair.java
[UTF-8] Tue Jun 18 21:23:59 2013
@@ -0,0 +1,90 @@
+/*
+ * 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.metadata;
+
+import java.util.Set;
+import java.util.HashSet;
+import org.apache.sis.util.Debug;
+import org.apache.sis.util.Classes;
+
+
+/**
+ * A pair of objects in process of being compared by the {@code MetadataStandard#equals(…)}
method.
+ * We have to remember those pairs for avoiding infinite recursivity when comparing metadata
objects
+ * having cyclic associations. The objects are compared using the identity comparison.
+ * Object order is not significant.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+final class ObjectPair {
+    /**
+     * The set of objects currently in process of being compared.
+     */
+    static final ThreadLocal<Set<ObjectPair>> CURRENT = new ThreadLocal<Set<ObjectPair>>()
{
+        @Override protected Set<ObjectPair> initialValue() {
+            return new HashSet<>();
+        }
+    };
+
+    /**
+     * The pair of objects in process of being compared.
+     */
+    private final Object o1, o2;
+
+    /**
+     * Creates a new pair of objects being compared.
+     */
+    ObjectPair(final Object o1, final Object o2) {
+        this.o1 = o1;
+        this.o2 = o2;
+    }
+
+    /**
+     * Returns a hash code value for this pair of objects.
+     * The hash code value shall be insensitive to the objects order.
+     */
+    @Override
+    public int hashCode() {
+        return System.identityHashCode(o1) ^ System.identityHashCode(o2);
+    }
+
+    /**
+     * Compares the given object with this pair for equality.
+     * The comparison shall be insensitive to the objects order.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other instanceof ObjectPair) {
+            final ObjectPair that = (ObjectPair) other;
+            return (o1 == that.o1 && o2 == that.o2) ||
+                   (o1 == that.o2 && o2 == that.o1);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a string representation of the object pair for debugging purpose only.
+     */
+    @Debug
+    @Override
+    public String toString() {
+        return '(' + Classes.getShortClassName(o1) + ", " + Classes.getShortClassName(o2)
+ ')';
+    }
+}

Propchange: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ObjectPair.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/ObjectPair.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java?rev=1494312&r1=1494311&r2=1494312&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
[UTF-8] Tue Jun 18 21:23:59 2013
@@ -19,7 +19,6 @@ package org.apache.sis.metadata;
 import java.util.Map;
 import java.util.Iterator;
 import java.util.Collection;
-import java.util.IdentityHashMap;
 import org.opengis.util.CodeList;
 import org.apache.sis.internal.util.CollectionsExt;
 
@@ -27,19 +26,25 @@ import static org.apache.sis.metadata.Va
 
 
 /**
- * Implementation of {@link AbstractMetadata#isEmpty()} and {@link ModifiableMetadata#prune()}
- * methods.
+ * Implementation of {@link AbstractMetadata#isEmpty()} and {@link ModifiableMetadata#prune()}
methods.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.20)
  * @version 0.3
  * @module
  */
-final class Pruner extends ThreadLocal<Map<Object,Boolean>> {
+final class Pruner {
     /**
-     * The thread-local map of metadata object already tested.
+     * The thread-local map of metadata objects already tested. The keys are metadata instances,
and values
+     * are the results of the {@code metadata.isEmpty()} operation.
+     *
+     * If the final operation requested by the user is {@code isEmpty()}, then this map will
contain at most
+     * one {@code false} value since the walk in the tree will stop at the first {@code false}
value found.
+     *
+     * If the final operation requested by the user is {@code prune()}, then this map will
contain a mix of
+     * {@code false} and {@code true} values since the operation will unconditionally walk
through the entire tree.
      */
-    private static final Pruner INSTANCE = new Pruner();
+    private static final RecursivityGuard<Boolean> MAPS = new RecursivityGuard<>();
 
     /**
      * For internal usage only.
@@ -48,15 +53,6 @@ final class Pruner extends ThreadLocal<M
     }
 
     /**
-     * Creates an initially empty hash map when the {@code isEmpty()} or {@code prune()}
-     * method is invoked, before any recursive invocation.
-     */
-    @Override
-    protected Map<Object,Boolean> initialValue() {
-        return new IdentityHashMap<>();
-    }
-
-    /**
      * Returns the metadata properties. When used for pruning empty values, the map needs
to
      * include empty (but non-null) values in order to allow us to set them to {@code null}.
      */
@@ -87,14 +83,19 @@ final class Pruner extends ThreadLocal<M
      */
     static boolean isEmpty(final AbstractMetadata metadata, final boolean prune) {
         final Map<String,Object> properties = asMap(metadata.getStandard(), metadata,
prune);
-        final Map<Object,Boolean> tested = INSTANCE.get();
+        final Map<Object,Boolean> tested = MAPS.get();
         if (!tested.isEmpty()) {
             return isEmpty(properties, tested, prune);
         } else try {
             tested.put(metadata, Boolean.FALSE);
             return isEmpty(properties, tested, prune);
         } finally {
-            INSTANCE.remove();
+            MAPS.remove();
+            /*
+             * Note: we could invoke 'tested.clear()' instead in order to recycle the existing
+             *       IdentityHashMap instance, but we presume that usage of this class will
be
+             *       rare enough for not being worth to keep those objects around.
+             */
         }
     }
 

Added: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/RecursivityGuard.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/RecursivityGuard.java?rev=1494312&view=auto
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/RecursivityGuard.java
(added)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/RecursivityGuard.java
[UTF-8] Tue Jun 18 21:23:59 2013
@@ -0,0 +1,68 @@
+/*
+ * 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.metadata;
+
+import java.util.Map;
+import java.util.IdentityHashMap;
+
+
+/**
+ * A guard against infinite recursivity in {@link AbstractMetadata#hashCode()},
+ * {@link AbstractMetadata#isEmpty()} and {@link ModifiableMetadata#prune()} methods.
+ * {@link AbstractMetadata#equals(Object)} uses an other implementation in {@link ObjectPair}.
+ *
+ * {@section The problem}
+ * Cyclic associations can exist in ISO 19115 metadata. For example {@code Instrument} know
the platform
+ * it is mounted on, and the {@code Platform} know its list of instrument. Consequently walking
down the
+ * metadata tree can cause infinite recursivity, unless we keep trace of previously visited
metadata objects
+ * in order to avoid visiting them again. We use an {@link IdentityHashMap} for that purpose,
since the
+ * recursivity problem exists only when revisiting the exact same instance. Furthermore,
{@code HashMap}
+ * would not suit since it invokes {@code equals(Object)} and {@code hashCode()}, which are
precisely
+ * the methods that we want to avoid invoking twice.
+ *
+ * @param <V> The kind of values to store in the maps.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ *
+ * @see #HASH_CODES
+ * @see ObjectPair#CURRENT
+ * @see Pruner#MAPS
+ */
+final class RecursivityGuard<V> extends ThreadLocal<Map<Object,V>> {
+    /**
+     * The recursivity guard to use during {@code hashCode()} computations.
+     * The values have no meaning for this map; only the keys matter.
+     */
+    static final RecursivityGuard<Object> HASH_CODES = new RecursivityGuard<>();
+
+    /**
+     * Creates a new thread-local map.
+     */
+    RecursivityGuard() {
+    }
+
+    /**
+     * Creates an initially empty hash map when first needed, before any recursive invocation.
+     */
+    @Override
+    protected Map<Object,V> initialValue() {
+        return new IdentityHashMap<>();
+    }
+}

Propchange: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/RecursivityGuard.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/RecursivityGuard.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java?rev=1494312&r1=1494311&r2=1494312&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/TreeTableView.java
[UTF-8] Tue Jun 18 21:23:59 2013
@@ -33,7 +33,7 @@ import org.apache.sis.internal.system.Lo
 
 /**
  * A tree table view over a metadata object.
- * The tree table is made of three columns:
+ * The tree table is made of the following columns:
  *
  * <ul>
  *   <li>{@link TableColumn#IDENTIFIER} - the property identifier as defined by the
UML (if any).</li>

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java?rev=1494312&r1=1494311&r2=1494312&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataStandardTest.java
[UTF-8] Tue Jun 18 21:23:59 2013
@@ -26,6 +26,8 @@ import org.opengis.metadata.quality.Comp
 import org.opengis.coverage.grid.RectifiedGrid;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.HardCodedCitations;
+import org.apache.sis.metadata.iso.acquisition.DefaultPlatform;
+import org.apache.sis.metadata.iso.acquisition.DefaultInstrument;
 import org.apache.sis.metadata.iso.quality.AbstractCompleteness;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.util.ComparisonMode;
@@ -35,6 +37,7 @@ import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.apache.sis.test.Assert.*;
+import static org.apache.sis.test.TestUtilities.getSingleton;
 
 
 /**
@@ -111,6 +114,36 @@ public final strictfp class MetadataStan
     }
 
     /**
+     * Creates a metadata object having a cyclic association. The cycle is between
+     * {@code platform.instrument} and {@code instrument.isMountedOn}.
+     */
+    static DefaultPlatform createCyclicMetadata() {
+        final DefaultInstrument instrument = new DefaultInstrument();
+        instrument.setType(new SimpleInternationalString("An instrument type."));
+        final DefaultPlatform platform = new DefaultPlatform();
+        platform.setDescription(new SimpleInternationalString("A platform."));
+        instrument.setMountedOn(platform);
+        platform.getInstruments().add(instrument);
+        return platform;
+    }
+
+    /**
+     * Tests the {@link MetadataStandard#equals(Object, Object, ComparisonMode)} method on
an object
+     * having cyclic associations. In absence of safety guard against infinite recursivity,
this test
+     * would produce {@link StackOverflowError}.
+     */
+    @Test
+    @DependsOnMethod("testEquals")
+    public void testEqualsOnCyclicMetadata() {
+        final DefaultPlatform p1 = createCyclicMetadata();
+        final DefaultPlatform p2 = createCyclicMetadata();
+        assertTrue(p1.equals(p2));
+        ((DefaultInstrument) getSingleton(p2.getInstruments()))
+                .setType(new SimpleInternationalString("An other instrument type."));
+        assertFalse(p1.equals(p2));
+    }
+
+    /**
      * Tests the {@link MetadataStandard#asValueMap(Object, KeyNamePolicy, ValueExistencePolicy)}
implementation.
      * This test duplicates {@link ValueMapTest}, but is done here again as an integration
test and because many
      * {@code MetadataStandard} methods depend on it ({@code equals}, {@code hashCode}, {@code
prune}, <i>etc.</i>).
@@ -166,6 +199,21 @@ public final strictfp class MetadataStan
     }
 
     /**
+     * Tests the {@link MetadataStandard#hashCode(Object)} method on an object having cyclic
associations.
+     * In absence of safety guard against infinite recursivity, this test would produce {@link
StackOverflowError}.
+     */
+    @Test
+    @DependsOnMethod("testHashCode")
+    public void testHashCodeOnCyclicMetadata() {
+        final int code = createCyclicMetadata().hashCode();
+        /*
+         * Following line checks that the hash code is stable, just for doing something with
the code.
+         * The real test was actually to ensure that the above line didn't threw a StackOverflowError.
+         */
+        assertEquals(code, createCyclicMetadata().hashCode());
+    }
+
+    /**
      * Tests the {@link MetadataStandard#ISO_19123} constant. Getters shall
      * be accessible even if there is no implementation on the classpath.
      */

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/PrunerTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/PrunerTest.java?rev=1494312&r1=1494311&r2=1494312&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/PrunerTest.java
[UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/PrunerTest.java
[UTF-8] Tue Jun 18 21:23:59 2013
@@ -21,6 +21,8 @@ import org.apache.sis.metadata.iso.citat
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
+import org.apache.sis.metadata.iso.acquisition.DefaultAcquisitionInformation;
+import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -116,9 +118,32 @@ public final strictfp class PrunerTest e
     }
 
     /**
+     * Adds to the {@link #metadata} an object having a cyclic association.
+     * The cycle is between {@code platform.instrument} and {@code instrument.isMountedOn}.
+     */
+    private void createCyclicMetadata() {
+        final DefaultAcquisitionInformation acquisition = new DefaultAcquisitionInformation();
+        acquisition.getPlatforms().add(MetadataStandardTest.createCyclicMetadata());
+        metadata.getAcquisitionInformation().add(acquisition);
+    }
+
+    /**
+     * Tests the {@link AbstractMetadata#isEmpty()} method on a metadata object having a
cycle association.
+     * In absence of safety guard against infinite recursivity, this test would produce {@link
StackOverflowError}.
+     */
+    @Test
+    @DependsOnMethod("testIsEmpty")
+    public void testIsEmptyOnCyclicMetadata() {
+        assertTrue(metadata.isEmpty());
+        createCyclicMetadata();
+        assertFalse(metadata.isEmpty());
+    }
+
+    /**
      * Tests the {@link ModifiableMetadata#prune()} method.
      */
     @Test
+    @DependsOnMethod("testIsEmpty")
     public void testPrune() {
         metadata.setFileIdentifier("A file identifiers");
         identification.setCitation(new DefaultCitation("A citation title"));
@@ -149,4 +174,19 @@ public final strictfp class PrunerTest e
         assertTrue(extent.getGeographicElements().isEmpty());
         assertTrue(metadata.isEmpty());
     }
+
+    /**
+     * Tests the {@link AbstractMetadata#prune()} method on a metadata object having a cycle
association.
+     * In absence of safety guard against infinite recursivity, this test would produce {@link
StackOverflowError}.
+     */
+    @Test
+    @DependsOnMethod({"testPrune", "testIsEmptyOnCyclicMetadata"})
+    public void testPruneOnCyclicMetadata() {
+        createCyclicMetadata();
+        assertEquals(1, metadata.getIdentificationInfo()    .size());
+        assertEquals(1, metadata.getAcquisitionInformation().size());
+        metadata.prune();
+        assertEquals(0, metadata.getIdentificationInfo()    .size());
+        assertEquals(1, metadata.getAcquisitionInformation().size());
+    }
 }



Mime
View raw message