You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2013/05/02 00:14:48 UTC

svn commit: r1478218 [1/2] - in /sis/trunk: ./ sis-metadata/src/main/java/org/apache/sis/metadata/ sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/ sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/ sis-metadata/src/main/...

Author: desruisseaux
Date: Wed May  1 22:14:47 2013
New Revision: 1478218

URL: http://svn.apache.org/r1478218
Log:
Merge from the JDK6 branch. Includes a preliminary version of metadata tree table,
which implies a first working version of AbstractMetadata.toString().

Added:
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java
      - copied unchanged from r1478216, sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeChildren.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java
      - copied unchanged from r1478216, sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeNode.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java
      - copied unchanged from r1478216, sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataTreeTable.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/
      - copied from r1478216, sis/branches/JDK6/sis-metadata/src/main/java/org/apache/sis/metadata/iso/doc-files/
    sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
      - copied unchanged from r1478216, sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTestCase.java
    sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java
      - copied unchanged from r1478216, sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeChildrenTest.java
    sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeNodeTest.java
      - copied unchanged from r1478216, sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/MetadataTreeNodeTest.java
    sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
      - copied unchanged from r1478216, sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
    sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/
      - copied from r1478216, sis/branches/JDK6/sis-metadata/src/test/java/org/apache/sis/metadata/iso/quality/
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java
      - copied unchanged from r1478216, sis/branches/JDK6/sis-utility/src/test/java/org/apache/sis/test/AnnotationsTestCase.java
Modified:
    sis/trunk/   (props changed)
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyMap.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/package-info.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
    sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/AbstractElement.java
    sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
    sis/trunk/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
    sis/trunk/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/UnmodifiableArrayList.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/About.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/CharSequences.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Classes.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/DefaultTreeTable.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TableColumn.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTable.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTableFormat.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/collection/TreeTables.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/iso/AbstractInternationalString.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/iso/ResourceInternationalString.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
    sis/trunk/sis-utility/src/test/java/org/apache/sis/util/CharSequencesTest.java
    sis/trunk/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
    sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/DefaultTreeTableTest.java
    sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTableFormatTest.java
    sis/trunk/sis-utility/src/test/java/org/apache/sis/util/collection/TreeTablesTest.java

Propchange: sis/trunk/
------------------------------------------------------------------------------
  Merged /sis/branches/JDK7:r1470196-1478202
  Merged /sis/branches/JDK6:r1470199-1478216

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] Wed May  1 22:14:47 2013
@@ -186,6 +186,7 @@ public abstract class AbstractMetadata i
      * the UML identifier.</p>
      *
      * <p>The default implementation is equivalent to the following method call:</p>
+     *
      * {@preformat java
      *   return getStandard().asValueMap(this, KeyNamePolicy.JAVABEANS_PROPERTY, ValueExistencePolicy.NON_EMPTY);
      * }
@@ -203,13 +204,21 @@ public abstract class AbstractMetadata i
 
     /**
      * Returns the property types and values as a tree table.
-     * In the current implementation, the tree is not live (i.e. changes in metadata are not
-     * reflected in the tree). However it may be improved in a future SIS implementation.
+     * The tree table is backed by the metadata object using Java reflection, so changes in the
+     * underlying metadata object are immediately reflected in the tree table and conversely.
+     *
+     * <p>The default implementation is equivalent to the following method call:</p>
+     *
+     * {@preformat java
+     *   return getStandard().asTreeTable(this, ValueExistencePolicy.NON_EMPTY);
+     * }
+     *
+     * @return A tree table representation of the specified metadata.
      *
-     * @return The property types and values as a tree table.
+     * @see MetadataStandard#asTreeTable(Object, ValueExistencePolicy)
      */
     public TreeTable asTreeTable() {
-        return getStandard().asTreeTable(this);
+        return getStandard().asTreeTable(this, ValueExistencePolicy.NON_EMPTY);
     }
 
     /**

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/MetadataStandard.java [UTF-8] Wed May  1 22:14:47 2013
@@ -431,13 +431,13 @@ public class MetadataStandard {
      * {@linkplain KeyNamePolicy#METHOD_NAME method names} or {@linkplain KeyNamePolicy#SENTENCE
      * sentences} (usually in English).
      *
-     * <p><b>Example:</b> The {@code value} in the following code is
-     * <code>"alternateTitle<u>s</u>"</code> (note the plural):</p>
+     * <p><b>Example:</b> the following code prints <code>"alternateTitle<u>s</u>"</code> (note the plural):</p>
      *
      * {@preformat java
      *   MetadataStandard standard = MetadataStandard.ISO_19115;
      *   Map<String, String> names = standard.asNameMap(Citation.class, UML_IDENTIFIER, JAVABEANS_PROPERTY);
-     *   String value = names.get("alternateTitle");  // alternateTitles
+     *   String value = names.get("alternateTitle");
+     *   System.out.println(value); // alternateTitles
      * }
      *
      * The {@code keyPolicy} argument specify only the string representation of keys returned by the iterators.
@@ -471,12 +471,14 @@ public class MetadataStandard {
      * argument, which can be {@linkplain TypeValuePolicy#ELEMENT_TYPE element type} or the
      * {@linkplain TypeValuePolicy#DECLARING_INTERFACE declaring interface} among others.
      *
-     * <p><b>Example:</b> The {@code value} in the following code is {@code InternationalString.class}:</p>
+     * <p><b>Example:</b> the following code prints the
+     * {@link org.opengis.util.InternationalString} class name:</p>
      *
      * {@preformat java
      *   MetadataStandard  standard = MetadataStandard.ISO_19115;
      *   Map<String,Class<?>> types = standard.asTypeMap(Citation.class, UML_IDENTIFIER, ELEMENT_TYPE);
-     *   Class<?> value = names.get("alternateTitle");  // InternationalString.class
+     *   Class<?> value = types.get("alternateTitle");
+     *   System.out.println(value);  // class org.opengis.util.InternationalString
      * }
      *
      * @param  type        The interface or implementation class of a metadata.
@@ -521,15 +523,15 @@ public class MetadataStandard {
      *     <ul>
      *       <li>The {@linkplain CheckedContainer#getElementType() element type} is the type of property values
      *           as defined by {@link TypeValuePolicy#ELEMENT_TYPE}.</li>
-     *
-     *       {@note The rational for implementing <code>CheckedContainer</code> is to consider each
-     *       <code>ExtendedElementInformation</code> instance as the set of all possible values for
-     *       the property. If the information had a <code>contains(E)</code> method, it would return
-     *       <code>true</code> if the given value is valid for that property.}
      *     </ul>
      *   </li>
      * </ul>
      *
+     * {@note The rational for implementing <code>CheckedContainer</code> is to consider each
+     * <code>ExtendedElementInformation</code> instance as the set of all possible values for
+     * the property. If the information had a <code>contains(E)</code> method, it would return
+     * <code>true</code> if the given value is valid for that property.}
+     *
      * In addition, for each map entry the value returned by {@link ExtendedElementInformation#getDomainValue()}
      * may optionally be an instance of any of the following classes:
      *
@@ -542,6 +544,8 @@ public class MetadataStandard {
      * @return Information about all properties defined in the given metadata type.
      * @throws ClassCastException if the given type doesn't implement a metadata
      *         interface of the expected package.
+     *
+     * @see org.apache.sis.metadata.iso.DefaultExtendedElementInformation
      */
     public Map<String,ExtendedElementInformation> asInformationMap(Class<?> type,
             final KeyNamePolicy keyPolicy) throws ClassCastException
@@ -591,18 +595,24 @@ public class MetadataStandard {
 
     /**
      * Returns the specified metadata object as a tree table.
-     * In the current implementation, the tree is not live (i.e. changes in metadata are not
-     * reflected in the tree). However it may be improved in a future SIS implementation.
+     * The tree table is backed by the metadata object using Java reflection, so changes in the
+     * underlying metadata object are immediately reflected in the tree table and conversely.
      *
-     * @param  metadata The metadata object to formats as a tree table.
+     * @param  metadata The metadata object to view as a tree table.
+     * @param  valuePolicy Whether the property having null value or empty collection shall be
+     *         included in the tree.
      * @return A tree table representation of the specified metadata.
      * @throws ClassCastException if the metadata object doesn't implement a metadata
      *         interface of the expected package.
      *
      * @see AbstractMetadata#asTreeTable()
      */
-    public TreeTable asTreeTable(final Object metadata) throws ClassCastException {
-        throw new UnsupportedOperationException("Not yet implemented"); // TODO
+    public TreeTable asTreeTable(final Object metadata, final ValueExistencePolicy valuePolicy)
+            throws ClassCastException
+    {
+        ensureNonNull("metadata",    metadata);
+        ensureNonNull("valuePolicy", valuePolicy);
+        return new MetadataTreeTable(this, metadata, valuePolicy);
     }
 
     /**

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ModifiableMetadata.java [UTF-8] Wed May  1 22:14:47 2013
@@ -28,9 +28,7 @@ import org.apache.sis.util.resources.Err
 import org.apache.sis.internal.util.CheckedHashSet;
 import org.apache.sis.internal.util.CheckedArrayList;
 
-import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
-import static org.apache.sis.util.collection.Containers.hashMapCapacity;
 import static org.apache.sis.internal.jaxb.MarshalContext.isMarshalling;
 
 
@@ -85,6 +83,12 @@ import static org.apache.sis.internal.ja
 @ThreadSafe
 public abstract class ModifiableMetadata extends AbstractMetadata implements Cloneable {
     /**
+     * Initial capacity of lists and sets. We use a small value because those
+     * collections will typically contain few elements (often just a singleton).
+     */
+    private static final int INITIAL_CAPACITY = 4;
+
+    /**
      * A null implementation for the {@link #FREEZING} constant.
      */
     private static final class Null extends ModifiableMetadata {
@@ -266,7 +270,7 @@ public abstract class ModifiableMetadata
                 if (target != null) {
                     target.clear();
                 } else {
-                    target = new MutableList<E>(elementType, source.size());
+                    target = new CheckedArrayList<E>(elementType, source.size());
                 }
                 target.addAll(source);
             }
@@ -314,7 +318,7 @@ public abstract class ModifiableMetadata
                 if (target != null) {
                     target.clear();
                 } else {
-                    target = new MutableSet<E>(elementType, source.size());
+                    target = new CheckedHashSet<E>(elementType, source.size());
                 }
                 target.addAll(source);
             }
@@ -379,9 +383,9 @@ public abstract class ModifiableMetadata
                 } else {
                     final int capacity = source.size();
                     if (useSet(elementType)) {
-                        target = new MutableSet<E>(elementType, capacity);
+                        target = new CheckedHashSet<E>(elementType, capacity);
                     } else {
-                        target = new MutableList<E>(elementType, capacity);
+                        target = new CheckedArrayList<E>(elementType, capacity);
                     }
                 }
                 target.addAll(source);
@@ -405,7 +409,7 @@ public abstract class ModifiableMetadata
         if (isNullOrEmpty(source)) {
             return null;
         }
-        final List<E> target = new MutableList<E>(elementType, source.size());
+        final List<E> target = new CheckedArrayList<E>(elementType, source.size());
         target.addAll(source);
         return target;
     }
@@ -425,7 +429,7 @@ public abstract class ModifiableMetadata
         if (isNullOrEmpty(source)) {
             return null;
         }
-        final Set<E> target = new MutableSet<E>(elementType, source.size());
+        final Set<E> target = new CheckedHashSet<E>(elementType, source.size());
         target.addAll(source);
         return target;
     }
@@ -451,9 +455,9 @@ public abstract class ModifiableMetadata
         final Collection<E> target;
         final int capacity = source.size();
         if (useSet(elementType)) {
-            target = new MutableSet<E>(elementType, capacity);
+            target = new CheckedHashSet<E>(elementType, capacity);
         } else {
-            target = new MutableList<E>(elementType, capacity);
+            target = new CheckedArrayList<E>(elementType, capacity);
         }
         target.addAll(source);
         return target;
@@ -478,9 +482,9 @@ public abstract class ModifiableMetadata
         }
         final Collection<E> collection;
         if (useSet(elementType)) {
-            collection = new MutableSet<E>(elementType);
+            collection = new CheckedHashSet<E>(elementType, INITIAL_CAPACITY);
         } else {
-            collection = new MutableList<E>(elementType);
+            collection = new CheckedArrayList<E>(elementType, INITIAL_CAPACITY);
         }
         collection.add(value);
         return collection;
@@ -503,7 +507,7 @@ public abstract class ModifiableMetadata
             return null;
         }
         if (isModifiable()) {
-            return new MutableList<E>(elementType);
+            return new CheckedArrayList<E>(elementType, INITIAL_CAPACITY);
         }
         return Collections.emptyList();
     }
@@ -525,7 +529,7 @@ public abstract class ModifiableMetadata
             return null;
         }
         if (isModifiable()) {
-            return new MutableSet<E>(elementType);
+            return new CheckedHashSet<E>(elementType, INITIAL_CAPACITY);
         }
         return Collections.emptySet();
     }
@@ -558,13 +562,13 @@ public abstract class ModifiableMetadata
         final boolean isModifiable = isModifiable();
         if (useSet(elementType)) {
             if (isModifiable) {
-                return new MutableSet<E>(elementType);
+                return new CheckedHashSet<E>(elementType, INITIAL_CAPACITY);
             } else {
                 return Collections.emptySet();
             }
         } else {
             if (isModifiable) {
-                return new MutableList<E>(elementType);
+                return new CheckedArrayList<E>(elementType, INITIAL_CAPACITY);
             } else {
                 return Collections.emptyList();
             }
@@ -572,58 +576,6 @@ public abstract class ModifiableMetadata
     }
 
     /**
-     * A set checking element validity and write permission before to change any value.
-     */
-    private final class MutableSet<E> extends CheckedHashSet<E> {
-        private static final long serialVersionUID = 3032602282358733056L;
-
-        MutableSet(Class<E> type) {
-            super(type, 4); // Use a small capacity because we typically have few elements.
-        }
-
-        MutableSet(Class<E> type, int capacity) {
-            super(type, hashMapCapacity(capacity));
-        }
-
-        @Override
-        protected void checkWritePermission() throws UnsupportedOperationException {
-            ModifiableMetadata.this.checkWritePermission();
-        }
-
-        @Override
-        protected void ensureValid(final E element) throws IllegalArgumentException {
-            ensureNonNull("element", element);
-            super.ensureValid(element);
-        }
-    }
-
-    /**
-     * A list checking element validity and write permission before to change any value.
-     */
-    private final class MutableList<E> extends CheckedArrayList<E> {
-        private static final long serialVersionUID = 5800381255701183058L;
-
-        MutableList(Class<E> type) {
-            super(type, 4); // Use a small capacity because we typically have few elements.
-        }
-
-        MutableList(Class<E> type, int capacity) {
-            super(type, capacity);
-        }
-
-        @Override
-        protected void checkWritePermission() throws UnsupportedOperationException {
-            ModifiableMetadata.this.checkWritePermission();
-        }
-
-        @Override
-        protected void ensureValid(final E element) throws IllegalArgumentException {
-            ensureNonNull("element", element);
-            super.ensureValid(element);
-        }
-    }
-
-    /**
      * Returns {@code true} if we should use a {@link Set} instead than a {@link List}
      * for elements of the given type.
      */

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyAccessor.java [UTF-8] Wed May  1 22:14:47 2013
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.metadata;
 
-import java.util.Set;
 import java.util.Map;
 import java.util.List;
 import java.util.Arrays;
@@ -31,7 +30,9 @@ import net.jcip.annotations.ThreadSafe;
 import org.opengis.annotation.UML;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.ExtendedElementInformation;
+import org.apache.sis.internal.util.Citations;
 import org.apache.sis.measure.ValueRange;
+import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ArraysExt;
@@ -97,6 +98,12 @@ final class PropertyAccessor {
     private static final Map<Class<?>, Method[]> SHARED_GETTERS = new IdentityHashMap<Class<?>, Method[]>();
 
     /**
+     * Enumeration constants for the {@code mode} argument in the
+     * {@link #count(Object, ValueExistencePolicy, int)} method.
+     */
+    static final int COUNT_FIRST=0, COUNT_SHALLOW=1, COUNT_DEEP=2;
+
+    /**
      * Additional getter to declare in every list of getter methods that do not already provide
      * their own {@code getIdentifiers()} method. We handle this method specially because it is
      * needed for XML marshalling in ISO 19139 compliant document, while not part of abstract
@@ -124,7 +131,8 @@ final class PropertyAccessor {
     final Class<?> type;
 
     /**
-     * The implementation class. The following condition must hold:
+     * The implementation class, or {@link #type} if none.
+     * The following condition must hold:
      *
      * {@preformat java
      *     type.isAssignableFrom(implementation);
@@ -218,7 +226,7 @@ final class PropertyAccessor {
      * @param  standard The standard which define the {@code type} interface.
      * @param  type The interface implemented by the metadata, which must be
      *         the value returned by {@link #getStandardType(Class, String)}.
-     * @param  implementation The class of metadata implementations.
+     * @param  implementation The class of metadata implementations, or {@code type} if none.
      */
     PropertyAccessor(final Citation standard, final Class<?> type, final Class<?> implementation) {
         assert type.isAssignableFrom(implementation) : implementation;
@@ -431,6 +439,8 @@ final class PropertyAccessor {
 
     /**
      * Returns the number of properties that can be read.
+     *
+     * @see #count(Object, ValueExistencePolicy, int)
      */
     final int count() {
         return standardCount;
@@ -548,6 +558,16 @@ final class PropertyAccessor {
     }
 
     /**
+     * Returns {@code true} if the type at the given index is {@link Collection}.
+     */
+    final boolean isCollection(final int index) {
+        if (index >= 0 && index < standardCount) {
+            return Collection.class.isAssignableFrom(getters[index].getReturnType());
+        }
+        return false;
+    }
+
+    /**
      * Returns the information for the property at the given index.
      * The information are created when first needed.
      *
@@ -590,6 +610,13 @@ final class PropertyAccessor {
     }
 
     /**
+     * Returns {@code true} if the property at the given index is writable.
+     */
+    final boolean isWritable(final int index) {
+        return (index >= 0) && (index < standardCount) && (setters != null) && (setters[index] != null);
+    }
+
+    /**
      * Returns the value for the specified metadata, or {@code null} if none.
      * If the given index is out of bounds, then this method returns {@code null},
      * so it is safe to invoke this method even if {@link #indexOf(String, boolean)}
@@ -666,25 +693,28 @@ final class PropertyAccessor {
             final Method getter = getters[index];
             final Method setter = setters[index];
             if (setter != null) {
-                Object old;
+                final Object old;
+                final Object copy;
                 if (getOld) {
                     old = get(getter, metadata);
                     if (old instanceof Collection<?>) {
                         if (old instanceof List<?>) {
-                            old = snapshot((List<?>) old);
+                            copy = snapshot((List<?>) old);
                         } else {
-                            old = modifiableCopy((Collection<?>) old);
+                            copy = modifiableCopy((Collection<?>) old);
                         }
                     } else if (old instanceof Map<?,?>) {
-                        old = modifiableCopy((Map<?,?>) old);
+                        copy = modifiableCopy((Map<?,?>) old);
+                    } else {
+                        copy = old;
                     }
                 } else {
-                    old = null;
+                    copy = old = null;
                 }
                 final Object[] newValues = new Object[] {value};
-                converter = convert(getter, metadata, newValues, elementTypes[index], converter);
+                converter = convert(getter, metadata, old, newValues, elementTypes[index], converter);
                 set(setter, metadata, newValues);
-                return old;
+                return copy;
             }
         }
         throw new UnmodifiableMetadataException(Errors.format(Errors.Keys.CanNotSetPropertyValue_1, names[index]));
@@ -732,6 +762,9 @@ final class PropertyAccessor {
      *
      * @param getter      The method to use for fetching the previous value.
      * @param metadata    The metadata object to query.
+     * @param oldValue    The value returned by {@code get(getter, metadata)}, or {@code null} if unknown.
+     *                    This parameter is only an optimization for avoiding to invoke the getter method
+     *                    twice if the value is already known.
      * @param newValues   The argument to convert. It must be an array of length 1.
      *                    The content of this array will be modified in-place.
      * @param elementType The type required by the setter method.
@@ -742,8 +775,8 @@ final class PropertyAccessor {
      * @throws BackingStoreException If the implementation threw a checked exception.
      */
     private static ObjectConverter<?,?> convert(final Method getter, final Object metadata,
-            final Object[] newValues, Class<?> elementType, ObjectConverter<?,?> converter)
-            throws ClassCastException, BackingStoreException
+            final Object oldValue, final Object[] newValues, Class<?> elementType,
+            ObjectConverter<?,?> converter) throws ClassCastException, BackingStoreException
     {
         assert newValues.length == 1;
         Object newValue = newValues[0];
@@ -804,7 +837,7 @@ final class PropertyAccessor {
                     addTo = null;
                 } else {
                     elements = new Object[] {newValue};
-                    newValue = addTo = (Collection<?>) get(getter, metadata);
+                    newValue = addTo = (Collection<?>) (oldValue != null ? oldValue : get(getter, metadata));
                     if (addTo == null) {
                         // No previous collection. Create one.
                         newValue = Arrays.asList(elements);
@@ -883,6 +916,56 @@ final class PropertyAccessor {
     }
 
     /**
+     * Counts the number of non-null or non-empty properties.
+     * The {@code mode} argument can be one of the following:
+     *
+     * <ul>
+     *   <li>COUNT_FIRST:   stop at the first property found. This mode is used for testing if a
+     *                      metadata is empty or not, without the need to known the exact count.</li>
+     *   <li>COUNT_SHALLOW: count all properties, counting collections as one property.</li>
+     *   <li>COUNT_DEEP:    count all properties, counting collections as the number of
+     *                      properties returned by {@link Collection#size()}.</li>
+     * </ul>
+     *
+     * @param  mode Kinds of count, as described above.
+     * @param  valuePolicy The behavior of the count toward null or empty values.
+     * @throws BackingStoreException If the implementation threw a checked exception.
+     *
+     * @see #count()
+     */
+    public int count(final Object metadata, final ValueExistencePolicy valuePolicy, final int mode)
+            throws BackingStoreException
+    {
+        assert type.isInstance(metadata) : metadata;
+        if (valuePolicy == ValueExistencePolicy.ALL && mode != COUNT_DEEP) {
+            return count();
+        }
+        int count = 0;
+        for (int i=0; i<standardCount; i++) {
+            final Object value = get(getters[i], metadata);
+            if (!valuePolicy.isSkipped(value)) {
+                switch (mode) {
+                    case COUNT_FIRST:{
+                        return 1;
+                    }
+                    case COUNT_SHALLOW:{
+                        count++;
+                        break;
+                    }
+                    case COUNT_DEEP: {
+                        // Count always at least one element because if the user wanted to skip null or empty
+                        // collections, then 'valuePolicy.isSkipped(value)' above would have returned 'true'.
+                        count += (value != null && isCollection(i)) ? Math.max(((Collection<?>) value).size(), 1) : 1;
+                        break;
+                    }
+                    default: throw new AssertionError(mode);
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
      * Compares the two specified metadata objects. This method implements a <cite>shallow</cite>
      * comparison, i.e. all metadata properties are compared using their {@code properties.equals(…)}
      * method without explicit calls to this {@code accessor.equals(…)} method for children.
@@ -953,7 +1036,7 @@ final class PropertyAccessor {
                 }
                 final Method setter = setters[i];
                 if (setter != null) {
-                    converter = convert(getter, target, arguments, elementTypes[i], converter);
+                    converter = convert(getter, target, null, arguments, elementTypes[i], converter);
                     set(setter, target, arguments);
                 } else {
                     success = false;
@@ -1021,21 +1104,35 @@ final class PropertyAccessor {
     }
 
     /**
-     * Counts the number of non-empty properties.
+     * Returns a string representation of this accessor for debugging purpose.
+     * Output example:
      *
-     * @param  max Stop the count if we reach that value.
-     * @throws BackingStoreException If the implementation threw a checked exception.
+     * {@preformat text
+     *     PropertyAccessor[13 getters & 13 setters in DefaultCitation:Citation from “ISO 19115”]
+     * }
      */
-    public int count(final Object metadata, final int max) throws BackingStoreException {
-        assert type.isInstance(metadata) : metadata;
-        int count = 0;
-        for (int i=0; i<standardCount; i++) {
-            if (!isNullOrEmpty(get(getters[i], metadata))) {
-                if (++count >= max) {
-                    break;
+    @Debug
+    @Override
+    public String toString() {
+        final StringBuilder buffer = new StringBuilder(60);
+        buffer.append("PropertyAccessor[").append(standardCount).append(" getters");
+        final int extra = allCount - standardCount;
+        if (extra != 0) {
+            buffer.append(" (+").append(extra).append(" ext.)");
+        }
+        if (setters != null) {
+            int c = 0;
+            for (final Method setter : setters) {
+                if (setter != null) {
+                    c++;
                 }
             }
+            buffer.append(" & ").append(c).append(" setters");
         }
-        return count;
+        buffer.append(" in ").append(Classes.getShortName(implementation));
+        if (type != implementation) {
+            buffer.append(':').append(Classes.getShortName(type));
+        }
+        return buffer.append(" from “").append(Citations.getIdentifier(standard)).append("”]").toString();
     }
 }

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyMap.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyMap.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyMap.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/PropertyMap.java [UTF-8] Wed May  1 22:14:47 2013
@@ -43,7 +43,7 @@ import java.util.Iterator;
  */
 abstract class PropertyMap<V> extends AbstractMap<String,V> {
     /**
-     * The accessor to use for the metadata.
+     * The accessor to use for accessing the property names, types or values.
      */
     final PropertyAccessor accessor;
 

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueExistencePolicy.java [UTF-8] Wed May  1 22:14:47 2013
@@ -23,9 +23,9 @@ import java.lang.reflect.Array;
 
 /**
  * Whatever {@link MetadataStandard#asValueMap MetadataStandard.asValueMap(…)} shall contain
- * entries for null values or empty collections. By default the map does not provide
+ * entries for null or empty values. By default the map does not provide
  * {@linkplain java.util.Map.Entry entries} for {@code null} metadata properties or
- * {@linkplain java.util.Collection#isEmpty() empty} collections.
+ * {@linkplain java.util.Collection#isEmpty() empty collections}.
  * This enumeration allows control on this behavior.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -40,21 +40,75 @@ public enum ValueExistencePolicy {
      * Includes all entries in the map, including those having a null value or an
      * empty collection.
      */
-    ALL,
+    ALL() {
+        /** Never skip values. */
+        @Override boolean isSkipped(final Object value) {
+            return false;
+        }
+
+        /** Substitutes null or empty collections by a null singleton element
+            in order to make the property visible in {@link MetadataTreeNode}. */
+        @Override boolean substituteByNullElement(final Collection<?> values) {
+            return (values == null) || values.isEmpty();
+        }
+    },
 
     /**
      * Includes only the non-null properties.
      * Collections are included no matter if they are empty or not.
      */
-    NON_NULL,
+    NON_NULL() {
+        /** Skips all null values. */
+        @Override boolean isSkipped(final Object value) {
+            return (value == null);
+        }
+
+        /** Substitutes empty collections by a null singleton element, but not
+            null references since they are supposed to be skipped by this policy. */
+        @Override boolean substituteByNullElement(final Collection<?> values) {
+            return (values != null) && values.isEmpty();
+        }
+    },
 
     /**
-     * Includes only the properties that are non-null and, in the case of collections,
-     * non-{@linkplain java.util.Collection#isEmpty() empty}.
+     * Includes only the properties that are non-null and non empty.
+     * A non-null property is considered empty if:
+     *
+     * <ul>
+     *   <li>It is a character sequence containing only {@linkplain Character#isWhitespace(int) whitespaces}.</li>
+     *   <li>It is an {@linkplain Collection#isEmpty() empty collection}.</li>
+     *   <li>It is an {@linkplain Map#isEmpty() empty map}.</li>
+     *   <li>It is an empty array (of length 0).</li>
+     * </ul>
+     *
      * This is the default behavior of {@link AbstractMetadata#asMap()}.
      */
-    NON_EMPTY;
+    NON_EMPTY() {
+        /** Skips all null or empty values. */
+        @Override boolean isSkipped(final Object value) {
+            return isNullOrEmpty(value);
+        }
 
+        /** Never substitute null or empty collections since they should be skipped. */
+        @Override boolean substituteByNullElement(final Collection<?> values) {
+            return false;
+        }
+    };
+
+    /**
+     * Returns {@code true} if the given value shall be skipped for this policy.
+     */
+    abstract boolean isSkipped(Object value);
+
+    /**
+     * Returns {@code true} if {@link MetadataTreeNode} shall substitute the given collection by
+     * a singleton containing only a null element.
+     *
+     * <p><b>Purpose:</b>
+     * When a collection is null or empty, while not excluded according this {@code ValueExistencePolicy},
+     * we need an empty space for making the metadata property visible in {@code MetadataTreeNode}.</p>
+     */
+    abstract boolean substituteByNullElement(Collection<?> values);
 
     /**
      * Returns {@code true} if the specified object is null or an empty collection, array or string.

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/ValueMap.java [UTF-8] Wed May  1 22:14:47 2013
@@ -49,7 +49,7 @@ final class ValueMap extends PropertyMap
     final ValueExistencePolicy valuePolicy;
 
     /**
-     * Creates a property map for the specified metadata and accessor.
+     * Creates a map of values for the specified metadata and accessor.
      *
      * @param metadata    The metadata object to wrap.
      * @param accessor    The accessor to use for the metadata.
@@ -65,18 +65,11 @@ final class ValueMap extends PropertyMap
     }
 
     /**
-     * Returns {@code true} if the given value should be ignored.
-     */
-    final boolean ignore(final Object value) {
-        return valuePolicy == ValueExistencePolicy.NON_EMPTY && ValueExistencePolicy.isNullOrEmpty(value);
-    }
-
-    /**
      * Returns {@code true} if this map contains no key-value mappings.
      */
     @Override
     public boolean isEmpty() {
-        return accessor.count(metadata, 1) == 0;
+        return accessor.count(metadata, valuePolicy, PropertyAccessor.COUNT_FIRST) == 0;
     }
 
     /**
@@ -84,7 +77,7 @@ final class ValueMap extends PropertyMap
      */
     @Override
     public int size() {
-        return accessor.count(metadata, Integer.MAX_VALUE);
+        return accessor.count(metadata, valuePolicy, PropertyAccessor.COUNT_SHALLOW);
     }
 
     /**
@@ -103,7 +96,7 @@ final class ValueMap extends PropertyMap
     public Object get(final Object key) {
         if (key instanceof String) {
             final Object value = accessor.get(accessor.indexOf((String) key, false), metadata);
-            if (!ignore(value)) {
+            if (!valuePolicy.isSkipped(value)) {
                 return value;
             }
         }
@@ -120,7 +113,7 @@ final class ValueMap extends PropertyMap
     @Override
     public Object put(final String key, final Object value) {
         final Object old = accessor.set(accessor.indexOf(key, true), metadata, value, true);
-        return ignore(old) ? null : old;
+        return valuePolicy.isSkipped(old) ? null : old;
     }
 
     /**
@@ -147,7 +140,7 @@ final class ValueMap extends PropertyMap
     public Object remove(final Object key) throws UnsupportedOperationException {
         if (key instanceof String) {
             final Object old = accessor.set(accessor.indexOf((String) key, false), metadata, null, true);
-            if (!ignore(old)) {
+            if (!valuePolicy.isSkipped(old)) {
                 return old;
             }
         }
@@ -219,7 +212,7 @@ final class ValueMap extends PropertyMap
         @Override
         public Object getValue() {
             final Object value = accessor.get(index, metadata);
-            return ignore(value) ? null : value;
+            return valuePolicy.isSkipped(value) ? null : value;
         }
 
         /**
@@ -299,26 +292,7 @@ final class ValueMap extends PropertyMap
         private void move(int index) {
             final int count = accessor.count();
             while (index < count) {
-                final Object value = accessor.get(index, metadata);
-                final boolean skip;
-                switch (valuePolicy) {
-                    case ALL: {
-                        skip = false; // Never skip entries.
-                        break;
-                    }
-                    case NON_NULL: {
-                        skip = (value == null); // Skip only null values (not empty collections).
-                        break;
-                    }
-                    case NON_EMPTY: {
-                        skip = ValueExistencePolicy.isNullOrEmpty(value);
-                        break;
-                    }
-                    default: {
-                        throw new AssertionError(valuePolicy);
-                    }
-                }
-                if (!skip) {
+                if (!valuePolicy.isSkipped(accessor.get(index, metadata))) {
                     next = new Property(index);
                     return;
                 }

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/DefaultOnlineResource.java [UTF-8] Wed May  1 22:14:47 2013
@@ -222,7 +222,7 @@ public class DefaultOnlineResource exten
 
     /**
      * Returns the location (address) for on-line access using a Uniform Resource Locator address or
-     * similar addressing scheme such as @{@code http://www.statkart.no/isotc211}".
+     * similar addressing scheme such as "{@code http://www.statkart.no/isotc211}".
      */
     @Override
     @XmlElement(name = "linkage", required = true)
@@ -242,11 +242,18 @@ public class DefaultOnlineResource exten
     }
 
     /**
-     * Returns the connection protocol to be used.
+     * Returns the connection protocol to be used. If no protocol has been {@linkplain #setProtocol(String)
+     * explicitely set}, then this method returns the {@linkplain #getLinkage() linkage}
+     * {@linkplain URI#getScheme() scheme} (if any).
+     *
+     * @see <a href="../doc-files/auto-properties.html">List of automatic properties</a>
      */
     @Override
     @XmlElement(name = "protocol")
     public String getProtocol() {
+        if (protocol == null && linkage != null) {
+            return linkage.getScheme();
+        }
         return protocol;
     }
 

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/package-info.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/package-info.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/package-info.java [UTF-8] Wed May  1 22:14:47 2013
@@ -64,6 +64,11 @@
  * except at XML marshalling time (for ISO 19139 compliance).
  * See {@link org.apache.sis.xml.IdentifierMap} for more information.
  *
+ * {@section Automatic properties}
+ * Some properties provide default value based on the value of other properties.
+ * See the <a href="../doc-files/auto-properties.html">list of automatic properties</a>
+ * page for more information.
+ *
  * {@section Null values, nil objects and collections}
  * All constructors (except the <cite>copy constructors</cite>) and setter methods accept {@code null} arguments.
  * A null argument value means that the metadata element can not be provided, and the reason for that is unspecified.

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java [UTF-8] Wed May  1 22:14:47 2013
@@ -218,6 +218,8 @@ public class DefaultScopeDescription ext
             c = cast(value, type);
         } else if (isNullOrEmpty(newValue)) {
             return;
+        } else {
+            property = code;
         }
         value = writeSet(newValue, c, type);
     }
@@ -343,6 +345,7 @@ public class DefaultScopeDescription ext
     public void setDataset(final String newValue) {
         checkWritePermission();
         if (newValue != null || property == DATASET) {
+            property = DATASET;
             value = newValue;
         }
     }
@@ -370,6 +373,7 @@ public class DefaultScopeDescription ext
     public void setOther(final String newValue) {
         checkWritePermission();
         if (newValue != null || property == OTHER) {
+            property = OTHER;
             value = newValue;
         }
     }

Modified: sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/AbstractElement.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/AbstractElement.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/AbstractElement.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/main/java/org/apache/sis/metadata/iso/quality/AbstractElement.java [UTF-8] Wed May  1 22:14:47 2013
@@ -17,10 +17,10 @@
 package org.apache.sis.metadata.iso.quality;
 
 import java.util.Date;
-import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.AbstractList;
+import java.io.Serializable;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -38,8 +38,12 @@ import org.opengis.metadata.quality.Logi
 import org.opengis.metadata.quality.EvaluationMethodType;
 import org.opengis.util.InternationalString;
 import org.apache.sis.metadata.iso.ISOMetadata;
+import org.apache.sis.util.collection.CheckedContainer;
 import org.apache.sis.util.resources.Errors;
 
+import static org.apache.sis.util.collection.Containers.isNullOrEmpty;
+import static org.apache.sis.internal.jaxb.MarshalContext.isMarshalling;
+
 
 /**
  * Type of test applied to the data specified by a data quality scope.
@@ -107,10 +111,171 @@ public class AbstractElement extends ISO
     private Citation evaluationProcedure;
 
     /**
-     * Start time ({@code date1}) and end time ({@code date2}) on which a data quality measure
-     * was applied. Value is {@link Long#MIN_VALUE} if this information is not available.
+     * Start time ({@code date1}) and end time ({@code date2}) on which a data quality measure was applied.
      */
-    private long date1, date2;
+    private Dates dates;
+
+    /**
+     * The start and end times as a list of O, 1 or 2 elements.
+     */
+    private static final class Dates extends AbstractList<Date>
+            implements CheckedContainer<Date>, Cloneable, Serializable
+    {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = 1210175223467194009L;
+
+        /**
+         * Start time ({@code date1}) and end time ({@code date2}) on which a data quality measure
+         * was applied. Value is {@link Long#MIN_VALUE} if this information is not available.
+         */
+        private long date1, date2;
+
+        /**
+         * Creates a new list initialized with no dates.
+         */
+        Dates() {
+            clear();
+        }
+
+        /**
+         * Returns the type of elements in this list.
+         * @return
+         */
+        @Override
+        public Class<Date> getElementType() {
+            return Date.class;
+        }
+
+        /**
+         * Removes all dates in this list.
+         */
+        @Override
+        public void clear() {
+            date1 = Long.MIN_VALUE;
+            date2 = Long.MIN_VALUE;
+        }
+
+        /**
+         * Returns the number of elements in this list.
+         */
+        @Override
+        public int size() {
+            if (date2 != Long.MIN_VALUE) return 2;
+            if (date1 != Long.MIN_VALUE) return 1;
+            return 0;
+        }
+
+        /**
+         * Returns the value at the given index.
+         */
+        @Override
+        @SuppressWarnings("fallthrough")
+        public Date get(final int index) {
+            long date = date1;
+            switch (index) {
+                case 1:  date = date2; // Fall through
+                case 0:  if (date != Long.MIN_VALUE) return new Date(date); // else fallthrough.
+                default: throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1, index));
+            }
+        }
+
+        /**
+         * Sets the value at the given index.
+         * Null values are not allowed.
+         */
+        @Override
+        public Date set(final int index, final Date value) {
+            final long date = value.getTime();
+            final Date previous = get(index);
+            switch (index) {
+                case 0: date1 = date; break;
+                case 1: date2 = date; break;
+            }
+            modCount++;
+            return previous;
+        }
+
+        /**
+         * Removes the value at the given index.
+         */
+        @Override
+        @SuppressWarnings("fallthrough")
+        public Date remove(final int index) {
+            final Date previous = get(index);
+            switch (index) {
+                case 0: date1 = date2; // Fallthrough
+                case 1: date2 = Long.MIN_VALUE; break;
+            }
+            modCount++;
+            return previous;
+        }
+
+        /**
+         * Adds a date at the given position.
+         * Null values are not allowed.
+         */
+        @Override
+        public void add(final int index, final Date value) {
+            final long date = value.getTime();
+            if (date2 == Long.MIN_VALUE) {
+                switch (index) {
+                    case 0: {
+                        date2 = date1;
+                        date1 = date;
+                        modCount++;
+                        return;
+                    }
+                    case 1: {
+                        if (date1 == Long.MIN_VALUE) {
+                            break; // Exception will be thrown below.
+                        }
+                        date2 = date;
+                        modCount++;
+                        return;
+                    }
+                }
+            }
+            throw new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndexOutOfBounds_1, index));
+        }
+
+        /**
+         * Adds all content from the given collection into this collection.
+         */
+        @Override
+        @SuppressWarnings("fallthrough")
+        public boolean addAll(final Collection<? extends Date> dates) {
+            final int c = modCount;
+            if (dates != null) {
+                final Iterator<? extends Date> it = dates.iterator();
+                switch (size()) { // Fallthrough everywhere.
+                    case 0:  if (!it.hasNext()) break;
+                             date1 = it.next().getTime();
+                             modCount++;
+                    case 1:  if (!it.hasNext()) break;
+                             date2 = it.next().getTime();
+                             modCount++;
+                    default: if (!it.hasNext()) break;
+                             throw new IllegalArgumentException(Errors.format(
+                                     Errors.Keys.ExcessiveArgumentSize_3, "dates", 2, dates.size()));
+                }
+            }
+            return modCount != c;
+        }
+
+        /**
+         * Returns a clone of this list.
+         */
+        @Override
+        public Object clone() {
+            try {
+                return super.clone();
+            } catch (CloneNotSupportedException e) {
+                throw new AssertionError(e);
+            }
+        }
+    }
 
     /**
      * Value (or set of values) obtained from applying a data quality measure or the out
@@ -123,8 +288,6 @@ public class AbstractElement extends ISO
      * Constructs an initially empty element.
      */
     public AbstractElement() {
-        date1 = Long.MIN_VALUE;
-        date2 = Long.MIN_VALUE;
     }
 
     /**
@@ -331,15 +494,13 @@ public class AbstractElement extends ISO
     @Override
     @XmlElement(name = "dateTime")
     public Collection<Date> getDates() {
-        if (date1 == Long.MIN_VALUE) {
-            return Collections.emptyList();
+        if (isMarshalling()) {
+            return isNullOrEmpty(dates) ? null : dates;
         }
-        if (date2 == Long.MIN_VALUE) {
-            return Collections.singleton(new Date(date1));
+        if (dates == null) {
+            dates = new Dates();
         }
-        return Arrays.asList(
-            new Date[] {new Date(date1), new Date(date2)}
-        );
+        return dates;
     }
 
     /**
@@ -350,26 +511,23 @@ public class AbstractElement extends ISO
      */
     public void setDates(final Collection<? extends Date> newValues) {
         checkWritePermission();
-        writeDates(newValues);
+        if (newValues != dates) { // Mandatory check for avoiding the call to 'dates.clear()'.
+            writeDates(newValues);
+        }
     }
 
     /**
      * Implementation of {@link #setDates(Collection)}.
      */
     private void writeDates(final Collection<? extends Date> newValues) {
-        date1 = date2 = Long.MIN_VALUE;
-        if (newValues != null) {
-            final Iterator<? extends Date> it = newValues.iterator();
-            if (it.hasNext()) {
-                date1 = it.next().getTime();
-                if (it.hasNext()) {
-                    date2 = it.next().getTime();
-                    if (it.hasNext()) {
-                        throw new IllegalArgumentException(Errors.format(
-                                Errors.Keys.ExcessiveArgumentSize_3, "dates", 2, newValues.size()));
-                    }
-                }
+        if (isNullOrEmpty(newValues)) {
+            dates = null;
+        } else {
+            if (dates == null) {
+                dates = new Dates();
             }
+            dates.clear();
+            dates.addAll(newValues);
         }
     }
 

Modified: sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/test/java/org/apache/sis/metadata/PropertyAccessorTest.java [UTF-8] Wed May  1 22:14:47 2013
@@ -399,4 +399,14 @@ public final strictfp class PropertyAcce
         hashCode = accessor.hashCode(instance);
         assertEquals("Metadata with two values.", baseCode + title.hashCode() + Arrays.asList(alternateTitle).hashCode(), hashCode);
     }
+
+    /**
+     * Tests {@link PropertyAccessor#toString()}. The {@code toString()}
+     * method is only for debugging purpose, but we test it anyway.
+     */
+    @Test
+    public void testToString() {
+        final PropertyAccessor accessor = createPropertyAccessor();
+        assertEquals("PropertyAccessor[13 getters & 13 setters in DefaultCitation:Citation from “ISO 19115”]", accessor.toString());
+    }
 }

Modified: sis/trunk/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] (original)
+++ sis/trunk/sis-metadata/src/test/java/org/apache/sis/test/suite/MetadataTestSuite.java [UTF-8] Wed May  1 22:14:47 2013
@@ -33,6 +33,7 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.metadata.MetadataUtilitiesTest.class,
     org.apache.sis.metadata.iso.citation.DefaultCitationDateTest.class,
     org.apache.sis.metadata.iso.citation.DefaultCitationTest.class,
+    org.apache.sis.metadata.iso.quality.AbstractElementTest.class,
 
     // Classes using Java reflection.
     org.apache.sis.metadata.PropertyInformationTest.class,
@@ -41,8 +42,11 @@ import org.junit.BeforeClass;
     org.apache.sis.metadata.TypeMapTest.class,
     org.apache.sis.metadata.InformationMapTest.class,
     org.apache.sis.metadata.ValueMapTest.class,
+    org.apache.sis.metadata.MetadataTreeChildrenTest.class,
+    org.apache.sis.metadata.MetadataTreeNodeTest.class,
     org.apache.sis.metadata.MetadataStandardTest.class,
-    org.apache.sis.metadata.PrunerTest.class
+    org.apache.sis.metadata.PrunerTest.class,
+    org.apache.sis.metadata.iso.AllMetadataTest.class
 })
 public final strictfp class MetadataTestSuite extends TestSuite {
     /**

Modified: sis/trunk/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java [UTF-8] (original)
+++ sis/trunk/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java [UTF-8] Wed May  1 22:14:47 2013
@@ -20,6 +20,7 @@ import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.test.DependsOnMethod;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -451,9 +452,25 @@ public strictfp class GeneralEnvelopeTes
     }
 
     /**
+     * Tests the {@link GeneralEnvelope#toString()} method.
+     */
+    @Test
+    public void testToString() {
+        GeneralEnvelope envelope = new GeneralEnvelope(new double[] {-180, -90}, new double[] {180, 90});
+        assertEquals("BOX(-180 -90, 180 90)", envelope.toString());
+
+        envelope = new GeneralEnvelope(3);
+        envelope.setRange(0, -180, +180);
+        envelope.setRange(1,  -90,  +90);
+        envelope.setRange(2,   10,   30);
+        assertEquals("BOX3D(-180 -90 10, 180 90 30)", envelope.toString());
+    }
+
+    /**
      * Tests the {@link GeneralEnvelope#GeneralEnvelope(CharSequence)} constructor.
      */
     @Test
+    @DependsOnMethod("testToString")
     public void testWktParsing() {
         GeneralEnvelope envelope = new GeneralEnvelope("BOX(-180 -90,180 90)");
         assertEquals(2, envelope.getDimension());

Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java [UTF-8] (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java [UTF-8] Wed May  1 22:14:47 2013
@@ -17,13 +17,9 @@
 package org.apache.sis.internal.util;
 
 import java.util.List;
-import java.util.Iterator;
-import java.util.ListIterator;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.NoSuchElementException;
-import org.apache.sis.util.Decorator;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.CheckedContainer;
 
@@ -35,12 +31,11 @@ import static org.apache.sis.util.Argume
  * The type checks are performed at run-time in addition to the compile-time checks.
  *
  * <p>Using this class is similar to wrapping an {@link ArrayList} using the methods provided
- * in the standard {@link Collections} class, except for the following advantages:</p>
+ * in the standard {@link Collections} class, except for the following differences:</p>
  *
  * <ul>
  *   <li>Avoid one level of indirection.</li>
- *   <li>Checks for write permission.</li>
- *   <li>Overrideable methods for controlling the type checks and write permission checks.</li>
+ *   <li>Does not accept null elements.</li>
  * </ul>
  *
  * @param <E> The type of elements in the list.
@@ -52,7 +47,7 @@ import static org.apache.sis.util.Argume
  *
  * @see Collections#checkedList(List, Class)
  */
-public class CheckedArrayList<E> extends ArrayList<E> implements CheckedContainer<E>, Cloneable {
+public final class CheckedArrayList<E> extends ArrayList<E> implements CheckedContainer<E> {
     /**
      * Serial version UID for compatibility with different versions.
      */
@@ -95,16 +90,15 @@ public class CheckedArrayList<E> extends
     }
 
     /**
-     * Ensures that the given element can be added to this list.
-     * The default implementation ensures that the object is {@code null} or assignable
-     * to the type specified at construction time. Subclasses can override this method
-     * if they need to perform additional checks.
+     * Ensures that the given element is non-null and assignable to the type
+     * specified at construction time.
      *
      * @param  element the object to check, or {@code null}.
      * @throws IllegalArgumentException if the specified element can not be added to this list.
      */
-    protected void ensureValid(final E element) throws IllegalArgumentException {
-        if (element != null && !type.isInstance(element)) {
+    private void ensureValid(final E element) throws IllegalArgumentException {
+        if (!type.isInstance(element)) {
+            ensureNonNull("element", element);
             throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_3, "element", type, element.getClass()));
         }
@@ -123,143 +117,6 @@ public class CheckedArrayList<E> extends
     }
 
     /**
-     * Checks if changes in this list are allowed. This method is automatically invoked before any
-     * operation that may change the content. If the write operation is allowed, then this method
-     * shall returns normally. Otherwise an {@link UnsupportedOperationException} is thrown.
-     *
-     * <p>The default implementation does nothing, thus allowing this list to be modified.
-     * Subclasses can override this method if they want to control write permissions.</p>
-     *
-     * @throws UnsupportedOperationException if this list is unmodifiable.
-     */
-    protected void checkWritePermission() throws UnsupportedOperationException {
-    }
-
-    /**
-     * An iterator with a check for write permission prior element removal.
-     * This class wraps the iterator provided by {@link ArrayList#iterator()}, and is
-     * also the base class for the wrapper around {@link ArrayList#listIterator()}.
-     *
-     * @see CheckedArrayList#iterator()
-     */
-    @Decorator(Iterator.class)
-    private class Iter<I extends Iterator<E>> implements Iterator<E> {
-        /** The {@link ArrayList} iterator. */
-        protected final I iterator;
-
-        /** Creates a new wrapper for the given {@link ArrayList} iterator. */
-        Iter(final I iterator) {
-            this.iterator = iterator;
-        }
-
-        /** Returns {@code true} if there is more elements in the iteration. */
-        @Override
-        public final boolean hasNext() {
-            return iterator.hasNext();
-        }
-
-        /** Returns the next element in the iteration. */
-        @Override
-        public final E next() throws NoSuchElementException {
-            return iterator.next();
-        }
-
-        /** Removes the previous element if the enclosing {@link CheckedArrayList} allows write operations. */
-        @Override
-        public final void remove() throws UnsupportedOperationException {
-            checkWritePermission();
-            iterator.remove();
-        }
-    }
-
-    /**
-     * A list iterator with a check for write permission prior element removal.
-     * This class wraps the iterator provided by {@link ArrayList#listIterator()}.
-     *
-     * @see CheckedArrayList#listIterator()
-     * @see CheckedArrayList#listIterator(int)
-     */
-    @Decorator(ListIterator.class)
-    private class ListIter extends Iter<ListIterator<E>> implements ListIterator<E> {
-        /** Creates a new wrapper for the given {@link ArrayList} list iterator. */
-        ListIter(final ListIterator<E> iterator) {
-            super(iterator);
-        }
-
-        /** Returns the index of the element to be returned by {@link #next()}. */
-        @Override
-        public int nextIndex() {
-            return iterator.nextIndex();
-        }
-
-        /** Returns the index of the element to be returned by {@link #previous()}. */
-        @Override
-        public int previousIndex() {
-            return iterator.previousIndex();
-        }
-
-        /** Returns {@code true} if there is elements before current position. */
-        @Override
-        public boolean hasPrevious() {
-            return iterator.hasPrevious();
-        }
-
-        /** Returns the previous element in the iteration. */
-        @Override
-        public E previous() throws NoSuchElementException {
-            return iterator.previous();
-        }
-
-        /** See the {@link CheckedArrayList#set(int, Object)} method contract. */
-        @Override
-        public void set(final E element) throws IllegalArgumentException, UnsupportedOperationException {
-            ensureValid(element);
-            checkWritePermission();
-            iterator.set(element);
-        }
-
-        /** See the {@link CheckedArrayList#add(Object)} method contract. */
-        @Override
-        public void add(final E element) throws IllegalArgumentException, UnsupportedOperationException {
-            ensureValid(element);
-            checkWritePermission();
-            iterator.add(element);
-        }
-    }
-
-    /**
-     * Returns an iterator over the elements in this list.
-     * The returned iterator will support {@linkplain Iterator#remove() element removal}
-     * only if the {@link #checkWritePermission()} method does not throw exception.
-     */
-    @Override
-    public Iterator<E> iterator() {
-        return new Iter<Iterator<E>>(super.iterator());
-    }
-
-    /**
-     * Returns an iterator over the elements in this list.
-     * The returned iterator will support {@linkplain ListIterator#remove() element removal},
-     * {@linkplain ListIterator#add(Object) addition} or {@linkplain ListIterator#set(Object)
-     * modification} only if the {@link #checkWritePermission()} method does not throw exception.
-     */
-    @Override
-    public ListIterator<E> listIterator() {
-        return new ListIter(super.listIterator());
-    }
-
-    /**
-     * Returns an iterator over the elements in this list, starting at the given index.
-     * The returned iterator will support {@linkplain ListIterator#remove() element removal},
-     * {@linkplain ListIterator#add(Object) addition} or {@linkplain ListIterator#set(Object)
-     * modification} only if the {@link #checkWritePermission()} method does not throw exception.
-     */
-    @Override
-    public ListIterator<E> listIterator(final int index) {
-        return new ListIter(super.listIterator(index));
-    }
-
-    /**
      * Replaces the element at the specified position in this list with the specified element.
      *
      * @param  index   index of element to replace.
@@ -267,14 +124,10 @@ public class CheckedArrayList<E> extends
      * @return the element previously at the specified position.
      * @throws IndexOutOfBoundsException if index out of range.
      * @throws IllegalArgumentException if the specified element is not of the expected type.
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
      */
     @Override
-    public E set(final int index, final E element)
-            throws IllegalArgumentException, UnsupportedOperationException
-    {
+    public E set(final int index, final E element) throws IllegalArgumentException {
         ensureValid(element);
-        checkWritePermission();
         return super.set(index, element);
     }
 
@@ -284,14 +137,10 @@ public class CheckedArrayList<E> extends
      * @param  element element to be appended to this list.
      * @return always {@code true}.
      * @throws IllegalArgumentException if the specified element is not of the expected type.
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
      */
     @Override
-    public boolean add(final E element)
-            throws IllegalArgumentException, UnsupportedOperationException
-    {
+    public boolean add(final E element) throws IllegalArgumentException {
         ensureValid(element);
-        checkWritePermission();
         return super.add(element);
     }
 
@@ -302,14 +151,10 @@ public class CheckedArrayList<E> extends
      * @param  element element to be inserted.
      * @throws IndexOutOfBoundsException if index out of range.
      * @throws IllegalArgumentException if the specified element is not of the expected type.
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
      */
     @Override
-    public void add(final int index, final E element)
-            throws IllegalArgumentException, UnsupportedOperationException
-    {
+    public void add(final int index, final E element) throws IllegalArgumentException {
         ensureValid(element);
-        checkWritePermission();
         super.add(index, element);
     }
 
@@ -320,14 +165,10 @@ public class CheckedArrayList<E> extends
      * @param  collection the elements to be inserted into this list.
      * @return {@code true} if this list changed as a result of the call.
      * @throws IllegalArgumentException if at least one element is not of the expected type.
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
      */
     @Override
-    public boolean addAll(final Collection<? extends E> collection)
-            throws IllegalArgumentException, UnsupportedOperationException
-    {
+    public boolean addAll(final Collection<? extends E> collection) throws IllegalArgumentException {
         ensureValidCollection(collection);
-        checkWritePermission();
         return super.addAll(collection);
     }
 
@@ -339,69 +180,10 @@ public class CheckedArrayList<E> extends
      * @param  collection elements to be inserted into this list.
      * @return {@code true} if this list changed as a result of the call.
      * @throws IllegalArgumentException if at least one element is not of the expected type.
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
      */
     @Override
-    public boolean addAll(final int index, final Collection<? extends E> collection)
-            throws IllegalArgumentException, UnsupportedOperationException
-    {
+    public boolean addAll(final int index, final Collection<? extends E> collection) throws IllegalArgumentException {
         ensureValidCollection(collection);
-        checkWritePermission();
         return super.addAll(index, collection);
     }
-
-    /**
-     * Removes the element at the specified position in this list.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public E remove(int index) throws UnsupportedOperationException {
-        checkWritePermission();
-        return super.remove(index);
-    }
-
-    /**
-     * Removes the first occurrence of the specified element from this list.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public boolean remove(Object o) throws UnsupportedOperationException {
-        checkWritePermission();
-        return super.remove(o);
-    }
-
-    /**
-     * Removes all of this list's elements that are also contained in the specified collection.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public boolean removeAll(Collection<?> c) throws UnsupportedOperationException {
-        checkWritePermission();
-        return super.removeAll(c);
-    }
-
-    /**
-     * Retains only the elements in this list that are contained in the specified collection.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public boolean retainAll(Collection<?> c) throws UnsupportedOperationException {
-        checkWritePermission();
-        return super.retainAll(c);
-    }
-
-    /**
-     * Removes all of the elements from this list.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public void clear() throws UnsupportedOperationException {
-        checkWritePermission();
-        super.clear();
-    }
 }

Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java?rev=1478218&r1=1478217&r2=1478218&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java [UTF-8] (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java [UTF-8] Wed May  1 22:14:47 2013
@@ -17,12 +17,8 @@
 package org.apache.sis.internal.util;
 
 import java.util.Set;
-import java.util.Iterator;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashSet;
-import java.util.NoSuchElementException;
-import org.apache.sis.util.Decorator;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.CheckedContainer;
 
@@ -34,12 +30,11 @@ import static org.apache.sis.util.Argume
  * The type checks are performed at run-time in addition to the compile-time checks.
  *
  * <p>Using this class is similar to wrapping a {@link LinkedHashSet} using the methods provided
- * in the standard {@link Collections} class, except for the following advantages:</p>
+ * in the standard {@link Collections} class, except for the following differences:</p>
  *
  * <ul>
  *   <li>Avoid one level of indirection.</li>
- *   <li>Checks for write permission.</li>
- *   <li>Overrideable methods for controlling the type checks and write permission checks.</li>
+ *   <li>Does not accept null elements.</li>
  * </ul>
  *
  * @param <E> The type of elements in the set.
@@ -51,7 +46,7 @@ import static org.apache.sis.util.Argume
  *
  * @see Collections#checkedSet(Set, Class)
  */
-public class CheckedHashSet<E> extends LinkedHashSet<E> implements CheckedContainer<E>, Cloneable {
+public final class CheckedHashSet<E> extends LinkedHashSet<E> implements CheckedContainer<E> {
     /**
      * Serial version UID for compatibility with different versions.
      */
@@ -94,167 +89,23 @@ public class CheckedHashSet<E> extends L
     }
 
     /**
-     * Ensures that the given element can be added to this set.
-     * The default implementation ensures that the object is {@code null} or assignable
-     * to the type specified at construction time. Subclasses can override this method
-     * if they need to perform additional checks.
-     *
-     * @param  element the object to check, or {@code null}.
-     * @throws IllegalArgumentException if the specified element can not be added to this set.
-     */
-    protected void ensureValid(final E element) throws IllegalArgumentException {
-        if (element != null && !type.isInstance(element)) {
-            throw new IllegalArgumentException(Errors.format(
-                    Errors.Keys.IllegalArgumentClass_3, "element", type, element.getClass()));
-        }
-    }
-
-    /**
-     * Ensures that all elements of the given collection can be added to this set.
-     *
-     * @param  collection the collection to check, or {@code null}.
-     * @throws IllegalArgumentException if at least one element can not be added to this set.
-     */
-    private void ensureValidCollection(final Collection<? extends E> collection) throws IllegalArgumentException {
-        for (final E element : collection) {
-            ensureValid(element);
-        }
-    }
-
-    /**
-     * Checks if changes in this set are allowed. This method is automatically invoked before any
-     * operation that may change the content. If the write operation is allowed, then this method
-     * shall returns normally. Otherwise an {@link UnsupportedOperationException} is thrown.
-     *
-     * <p>The default implementation does nothing, thus allowing this set to be modified.
-     * Subclasses can override this method if they want to control write permissions.</p>
-     *
-     * @throws UnsupportedOperationException if this set is unmodifiable.
-     */
-    protected void checkWritePermission() throws UnsupportedOperationException {
-    }
-
-    /**
-     * An iterator with a check for write permission prior element removal.
-     * This class wraps the iterator provided by {@link LinkedHashSet#iterator()}.
-     *
-     * @see CheckedHashSet#iterator()
-     */
-    @Decorator(Iterator.class)
-    private final class Iter implements Iterator<E> {
-        /** The {@link LinkedHashSet} iterator. */
-        private final Iterator<E> iterator;
-
-        /** Creates a new wrapper for the given {@link LinkedHashSet} iterator. */
-        Iter(final Iterator<E> iterator) {
-            this.iterator = iterator;
-        }
-
-        /** Returns {@code true} if there is more elements in the iteration. */
-        @Override
-        public boolean hasNext() {
-            return iterator.hasNext();
-        }
-
-        /** Returns the next element in the iteration. */
-        @Override
-        public E next() throws NoSuchElementException {
-            return iterator.next();
-        }
-
-        /** Removes the previous element if the enclosing {@link CheckedHashSet} allows write operations. */
-        @Override
-        public void remove() throws UnsupportedOperationException {
-            checkWritePermission();
-            iterator.remove();
-        }
-    }
-
-    /**
-     * Returns an iterator over the elements in this set.
-     * The returned iterator will support {@linkplain Iterator#remove() element removal}
-     * only if the {@link #checkWritePermission()} method does not throw exception.
-     */
-    @Override
-    public Iterator<E> iterator() {
-        return new Iter(super.iterator());
-    }
-
-    /**
      * Adds the specified element to this set if it is not already present.
      *
      * @param  element element to be added to this set.
      * @return {@code true} if the set did not already contain the specified element.
      * @throws IllegalArgumentException if the specified element is not of the expected type.
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
      */
     @Override
-    public boolean add(final E element)
-            throws IllegalArgumentException, UnsupportedOperationException
-    {
-        ensureValid(element);
-        checkWritePermission();
+    public boolean add(final E element) throws IllegalArgumentException {
+        if (!type.isInstance(element)) {
+            ensureNonNull("element", element);
+            throw new IllegalArgumentException(Errors.format(
+                    Errors.Keys.IllegalArgumentClass_3, "element", type, element.getClass()));
+        }
         return super.add(element);
     }
 
-    /**
-     * Appends all of the elements in the specified collection to this set.
-     *
-     * @param  collection the elements to be inserted into this set.
-     * @return {@code true} if this set changed as a result of the call.
-     * @throws IllegalArgumentException if at least one element is not of the expected type.
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public boolean addAll(final Collection<? extends E> collection)
-            throws IllegalArgumentException, UnsupportedOperationException
-    {
-        ensureValidCollection(collection);
-        checkWritePermission();
-        return super.addAll(collection);
-    }
-
-    /**
-     * Removes the specified element from this set.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public boolean remove(Object o) throws UnsupportedOperationException {
-        checkWritePermission();
-        return super.remove(o);
-    }
-
-    /**
-     * Removes all of this set's elements that are also contained in the specified collection.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public boolean removeAll(Collection<?> c) throws UnsupportedOperationException {
-        checkWritePermission();
-        return super.removeAll(c);
-    }
-
-    /**
-     * Retains only the elements in this set that are contained in the specified collection.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
+    /*
+     * No need to override 'addAll', since it is implemented on top of 'add'.
      */
-    @Override
-    public boolean retainAll(Collection<?> c) throws UnsupportedOperationException {
-        checkWritePermission();
-        return super.retainAll(c);
-    }
-
-    /**
-     * Removes all of the elements from this set.
-     *
-     * @throws UnsupportedOperationException if this collection is unmodifiable.
-     */
-    @Override
-    public void clear() throws UnsupportedOperationException {
-        checkWritePermission();
-        super.clear();
-    }
 }