sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1597134 [1/2] - in /sis/branches/JDK7: ./ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/test/java/org/apache/sis/feature/ core/sis-feature/src/test/java/org/apache/sis/test/suite/ core/sis-referencing/src/main...
Date Fri, 23 May 2014 17:19:25 GMT
Author: desruisseaux
Date: Fri May 23 17:19:24 2014
New Revision: 1597134

URL: http://svn.apache.org/r1597134
Log:
Merge from the JDK8 branch. Feature work is now complete, except for bug fixes as we discover them and GeoAPI compliance.

Added:
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
      - copied, changed from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
      - copied unchanged from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
      - copied unchanged from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/Field.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
      - copied unchanged from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAssociation.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
      - copied unchanged from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/MultiValuedAttribute.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
      - copied unchanged from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAssociation.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
      - copied unchanged from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/SingletonAttribute.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/benchmarks.html
      - copied unchanged from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/benchmarks.html
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/MultiValuedAttributeTest.java
      - copied, changed from r1597130, sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/MultiValuedAttributeTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
      - copied unchanged from r1597130, sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAttributeTest.java
      - copied, changed from r1597130, sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAttributeTest.java
Removed:
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttribute.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTest.java
Modified:
    sis/branches/JDK7/   (props changed)
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java   (contents, props changed)
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java   (contents, props changed)
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java   (contents, props changed)
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultOperationTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DenseFeatureTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/FeaturesTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/PropertySingletonTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/SparseFeatureTest.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
    sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterValueList.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/Cloner.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK7/core/sis-utility/src/test/java/org/apache/sis/internal/util/CheckedArrayListTest.java

Propchange: sis/branches/JDK7/
------------------------------------------------------------------------------
  Merged /sis/branches/JDK8:r1596664-1597130

Copied: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java (from r1597130, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java?p2=sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java&p1=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java&r1=1597130&r2=1597134&rev=1597134&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAssociation.java [UTF-8] Fri May 23 17:19:24 2014
@@ -232,6 +232,11 @@ public abstract class AbstractAssociatio
             @Override public Object next() {
                 return it.next().getPropertyValue(pt);
             }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
         });
     }
 }

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java [UTF-8] Fri May 23 17:19:24 2014
@@ -16,6 +16,9 @@
  */
 package org.apache.sis.feature;
 
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.Collections;
 import java.io.Serializable;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.quality.DataQuality;
@@ -23,6 +26,7 @@ import org.opengis.metadata.maintenance.
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CorruptedObjectException;
+import org.apache.sis.internal.util.CheckedArrayList;
 
 
 /**
@@ -30,8 +34,8 @@ import org.apache.sis.util.CorruptedObje
  * Each feature instance can provide values for the following properties:
  *
  * <ul>
- *   <li>{@linkplain DefaultAttribute   Attributes}</li>
- *   <li>{@linkplain DefaultAssociation Associations to other features}</li>
+ *   <li>{@linkplain AbstractAttribute  Attributes}</li>
+ *   <li>{@link AbstractAssociation Associations to other features}</li>
  *   <li>{@linkplain DefaultOperation   Operations}</li>
  * </ul>
  *
@@ -62,7 +66,7 @@ import org.apache.sis.util.CorruptedObje
  *
  * @see DefaultFeatureType#newInstance()
  */
-public abstract class AbstractFeature implements Cloneable, Serializable {
+public abstract class AbstractFeature implements Serializable {
     /**
      * For cross-version compatibility.
      */
@@ -71,14 +75,16 @@ public abstract class AbstractFeature im
     /**
      * Information about the feature (name, characteristics, <i>etc.</i>).
      */
-    private final DefaultFeatureType type;
+    final DefaultFeatureType type;
 
     /**
      * Creates a new feature of the given type.
      *
      * @param type Information about the feature (name, characteristics, <i>etc.</i>).
+     *
+     * @see DefaultFeatureType#newInstance()
      */
-    public AbstractFeature(final DefaultFeatureType type) {
+    protected AbstractFeature(final DefaultFeatureType type) {
         ArgumentChecks.ensureNonNull("type", type);
         this.type = type;
     }
@@ -126,7 +132,7 @@ public abstract class AbstractFeature im
      *
      * <div class="note"><b>Tip:</b> This method returns the property <em>instance</em>. If only the property
      * <em>value</em> is desired, then {@link #getPropertyValue(String)} is preferred since it gives to SIS a
-     * chance to avoid the creation of {@link DefaultAttribute} or {@link DefaultAssociation} instances.</div>
+     * chance to avoid the creation of {@link AbstractAttribute} or {@link AbstractAssociation} instances.</div>
      *
      * @param  name The property name.
      * @return The property of the given name (never {@code null}).
@@ -178,9 +184,9 @@ public abstract class AbstractFeature im
     final Property createProperty(final String name, final Object value) {
         final PropertyType pt = getPropertyType(name);
         if (pt instanceof DefaultAttributeType<?>) {
-            return new DefaultAttribute<>((DefaultAttributeType<?>) pt, value);
+            return AbstractAttribute.create((DefaultAttributeType<?>) pt, value);
         } else if (pt instanceof DefaultAssociationRole) {
-            return new DefaultAssociation((DefaultAssociationRole) pt, (AbstractFeature) value);
+            return AbstractAssociation.create((DefaultAssociationRole) pt, value);
         } else {
             // Should never happen, unless the user gave us some mutable FeatureType.
             throw new CorruptedObjectException(Errors.format(Errors.Keys.UnknownType_1, pt));
@@ -197,16 +203,17 @@ public abstract class AbstractFeature im
     final Property createProperty(final String name) throws IllegalArgumentException {
         final PropertyType pt = getPropertyType(name);
         if (pt instanceof DefaultAttributeType<?>) {
-            return new DefaultAttribute<>((DefaultAttributeType<?>) pt);
+            return AbstractAttribute.create((DefaultAttributeType<?>) pt);
         } else if (pt instanceof DefaultAssociationRole) {
-            return new DefaultAssociation((DefaultAssociationRole) pt);
+            return AbstractAssociation.create((DefaultAssociationRole) pt);
         } else {
-            throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
+            throw unsupportedPropertyType(pt.getName());
         }
     }
 
     /**
-     * Returns the default value for the property of the given name.
+     * Returns the default value to be returned by {@link #getPropertyValue(String)}
+     * for the property of the given name.
      *
      * @param  name The name of the property for which to get the default value.
      * @return The default value for the {@code Property} of the given name.
@@ -215,29 +222,50 @@ public abstract class AbstractFeature im
     final Object getDefaultValue(final String name) throws IllegalArgumentException {
         final PropertyType pt = getPropertyType(name);
         if (pt instanceof DefaultAttributeType<?>) {
-            return ((DefaultAttributeType<?>) pt).getDefaultValue();
+            return getDefaultValue((DefaultAttributeType<?>) pt);
         } else if (pt instanceof DefaultAssociationRole) {
             return null; // No default value for associations.
         } else {
-            throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
+            throw unsupportedPropertyType(pt.getName());
+        }
+    }
+
+    /**
+     * Returns the default value to be returned by {@link #getPropertyValue(String)} for the given attribute type.
+     */
+    private static <V> Object getDefaultValue(final DefaultAttributeType<V> attribute) {
+        final V defaultValue = attribute.getDefaultValue();
+        if (Field.isSingleton(attribute.getMaximumOccurs())) {
+            return defaultValue;
+        } else {
+            // Following is for compliance with getPropertyValue(String) method contract - see its javadoc.
+            return (defaultValue != null) ? Collections.singletonList(defaultValue) : Collections.emptyList();
         }
     }
 
     /**
      * Returns the value for the property of the given name.
-     * This convenience method is equivalent to the following steps:
+     * This convenience method is equivalent to invoking {@link #getProperty(String)} for the given name,
+     * then to perform one of the following actions depending on the property type and the cardinality:
      *
-     * <ul>
-     *   <li>Get the property of the given name.</li>
-     *   <li>Delegates to {@link DefaultAttribute#getValue()} or {@link DefaultAssociation#getValue()},
-     *       depending on the property type.
-     * </ul>
+     * <table class="sis">
+     *   <caption>Class of returned value</caption>
+     *   <tr><th>Property type</th>           <th>max. occurs</th> <th>Method invoked</th>                  <th>Return type</th></tr>
+     *   <tr><td>{@link AttributeType}</td>   <td>0 or 1</td>      <td>{@link Attribute#getValue()}</td>    <td>{@link Object}</td></tr>
+     *   <tr><td>{@code AttributeType}</td>   <td>2 or more</td>   <td>{@link Attribute#getValues()}</td>   <td>{@code Collection<?>}</td></tr>
+     *   <tr><td>{@link AssociationRole}</td> <td>0 or 1</td>      <td>{@link Association#getValue()}</td>  <td>{@link Feature}</td></tr>
+     *   <tr><td>{@code AssociationRole}</td> <td>2 or more</td>   <td>{@link Association#getValues()}</td> <td>{@code Collection<Feature>}</td></tr>
+     * </table>
+     *
+     * <div class="note"><b>Note:</b> “max. occurs” is the {@linkplain DefaultAttributeType#getMaximumOccurs() maximum
+     * number of occurrences} and does not depend on the actual number of values. If an attribute allows more than one
+     * value, then this method will always return a collection for that attribute even if the collection is empty.</div>
      *
      * @param  name The property name.
      * @return The value for the given property, or {@code null} if none.
      * @throws IllegalArgumentException If the given argument is not an attribute or association name of this feature.
      *
-     * @see DefaultAttribute#getValue()
+     * @see AbstractAttribute#getValue()
      */
     public abstract Object getPropertyValue(final String name) throws IllegalArgumentException;
 
@@ -255,21 +283,36 @@ public abstract class AbstractFeature im
      * @throws ClassCastException If the value is not assignable to the expected value class.
      * @throws IllegalArgumentException If the given value can not be assigned for an other reason.
      *
-     * @see DefaultAttribute#setValue(Object)
+     * @see AbstractAttribute#setValue(Object)
      */
     public abstract void setPropertyValue(final String name, final Object value) throws IllegalArgumentException;
 
     /**
+     * Returns the value of the given attribute, as a singleton or as a collection depending
+     * on the maximum number of occurrences.
+     */
+    static Object getAttributeValue(final AbstractAttribute<?> property) {
+        return Field.isSingleton(property.getType().getMaximumOccurs()) ? property.getValue() : property.getValues();
+    }
+
+    /**
+     * Returns the value of the given association, as a singleton or as a collection depending
+     * on the maximum number of occurrences.
+     */
+    static Object getAssociationValue(final AbstractAssociation property) {
+        return Field.isSingleton(property.getRole().getMaximumOccurs()) ? property.getValue() : property.getValues();
+    }
+
+    /**
      * Sets the value of the given property, with some minimal checks.
      */
     static void setPropertyValue(final Property property, final Object value) {
-        if (property instanceof DefaultAttribute<?>) {
-            setAttributeValue((DefaultAttribute<?>) property, value);
-        } else if (property instanceof DefaultAssociation) {
-            ArgumentChecks.ensureCanCast("value", AbstractFeature.class, value);
-            setAssociationValue((DefaultAssociation) property, (AbstractFeature) value);
+        if (property instanceof AbstractAttribute<?>) {
+            setAttributeValue((AbstractAttribute<?>) property, value);
+        } else if (property instanceof AbstractAssociation) {
+            setAssociationValue((AbstractAssociation) property, value);
         } else {
-            throw new IllegalArgumentException(unsupportedPropertyType(property.getName()));
+            throw unsupportedPropertyType(property.getName());
         }
     }
 
@@ -279,44 +322,64 @@ public abstract class AbstractFeature im
      * use {@link Validator} instead.
      */
     @SuppressWarnings("unchecked")
-    private static <T> void setAttributeValue(final DefaultAttribute<T> attribute, final Object value) {
+    private static <V> void setAttributeValue(final AbstractAttribute<V> attribute, final Object value) {
         if (value != null) {
-            final DefaultAttributeType<T> pt = attribute.getType();
+            final DefaultAttributeType<V> pt = attribute.getType();
             final Class<?> base = pt.getValueClass();
             if (!base.isInstance(value)) {
-                throw new ClassCastException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
-                        pt.getName(), value.getClass()));
+                Object element = value;
+                if (value instanceof Collection<?>) {
+                    /*
+                     * If the given value is a collection, verify the class of all values
+                     * before to delegate to Attribute.setValues(Collection<? extends V>).
+                     */
+                    final Iterator<?> it = ((Collection<?>) value).iterator();
+                    do if (!it.hasNext()) {
+                        ((AbstractAttribute) attribute).setValues((Collection) value);
+                        return;
+                    } while ((element = it.next()) == null || base.isInstance(element));
+                    // Found an illegal value. Exeption is thrown below.
+                }
+                throw illegalValueClass(attribute.getName(), element); // 'element' can not be null here.
             }
         }
-        ((DefaultAttribute) attribute).setValue(value);
+        ((AbstractAttribute) attribute).setValue(value);
     }
 
     /**
      * Sets the association value after verification of its type.
      * For a more exhaustive validation, use {@link Validator} instead.
      */
-    private static <T> void setAssociationValue(final DefaultAssociation association, final AbstractFeature value) {
+    @SuppressWarnings("unchecked")
+    private static void setAssociationValue(final AbstractAssociation association, final Object value) {
         if (value != null) {
-            final DefaultAssociationRole pt = association.getRole();
-            final DefaultFeatureType base = pt.getValueType();
-            final DefaultFeatureType actual = value.getType();
-            if (!base.maybeAssignableFrom(actual)) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
-                        pt.getName(), actual.getName()));
+            final DefaultAssociationRole role = association.getRole();
+            final DefaultFeatureType base = role.getValueType();
+            if (value instanceof AbstractFeature) {
+                final DefaultFeatureType actual = ((AbstractFeature) value).getType();
+                if (!base.maybeAssignableFrom(actual)) {
+                    throw illegalPropertyType(role.getName(), actual.getName());
+                }
+            } else if (value instanceof Collection<?>) {
+                verifyAssociationValues(role, (Collection<?>) value);
+                association.setValues((Collection<? extends AbstractFeature>) value);
+                return; // Skip the setter at the end of this method.
+            } else {
+                throw illegalValueClass(association.getName(), value);
             }
         }
-        association.setValue(value);
+        association.setValue((AbstractFeature) value);
     }
 
     /**
-     * Returns {@code true} if the caller can skip the call to {@link #verifyValueType(String, Object)}.
+     * Returns {@code true} if the caller can skip the call to {@link #verifyPropertyValue(String, Object)}.
      * This is a slight optimization for the case when we replaced an attribute value by a new value of
      * the same class. Since the type check has already been done by the previous assignation, we do not
      * need to perform it again.
      *
      * @param previous The previous value, or {@code null}.
      * @param value    The new value, or {@code null}.
-     * @return         {@code true} if the caller can skip the verification performed by {@code verifyValueType}.
+     * @return         {@code true} if the caller can skip the verification performed by {@code verifyPropertyValue}.
      */
     static boolean canSkipVerification(final Object previous, final Object value) {
         if (previous != null) {
@@ -331,61 +394,151 @@ public abstract class AbstractFeature im
     }
 
     /**
-     * Verifies the validity of the given value for the property of the given name. If a check failed,
-     * returns the exception to throw. Otherwise returns {@code null}. This method does not throw the
-     * exception immediately in order to give to the caller a chance to perform cleanup operation first.
+     * Verifies if the given properties can be assigned to this feature.
+     *
+     * @param name Shall be {@code property.getName().toString()}.
+     * @param property The property to verify.
+     */
+    final void verifyPropertyType(final String name, final Property property) {
+        final PropertyType type, base = getPropertyType(name);
+        if (property instanceof AbstractAttribute<?>) {
+            type = ((AbstractAttribute<?>) property).getType();
+        } else if (property instanceof AbstractAssociation) {
+            type = ((AbstractAssociation) property).getRole();
+        } else {
+            throw illegalPropertyType(base.getName(), property.getClass());
+        }
+        if (type != base) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedPropertyType_1, name));
+        }
+    }
+
+    /**
+     * Verifies the validity of the given value for the property of the given name, then returns the value
+     * to store. The returned value is usually the same than the given one, except in the case of collections.
      */
-    final RuntimeException verifyValueType(final String name, final Object value) {
+    final Object verifyPropertyValue(final String name, final Object value) {
         final PropertyType pt = getPropertyType(name);
         if (pt instanceof DefaultAttributeType<?>) {
-            if (value == null || ((DefaultAttributeType<?>) pt).getValueClass().isInstance(value)) {
-                return null;
+            if (value != null) {
+                return verifyAttributeValue((DefaultAttributeType<?>) pt, value);
             }
         } else if (pt instanceof DefaultAssociationRole) {
-            if (value == null) {
-                return null;
-            }
-            if (value instanceof AbstractFeature) {
-                final DefaultFeatureType valueType = ((AbstractFeature) value).getType();
-                if (((DefaultAssociationRole) pt).getValueType().maybeAssignableFrom(valueType)) {
-                    return null;
-                }
-                return new IllegalArgumentException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
-                        name, valueType.getName()));
+            if (value != null) {
+                return verifyAssociationValue((DefaultAssociationRole) pt, value);
             }
         } else {
-            return new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
+            throw unsupportedPropertyType(pt.getName());
         }
-        return new ClassCastException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
-                name, value.getClass())); // 'value' should not be null at this point.
+        return value;
     }
 
     /**
-     * Verifies if the given properties can be assigned to this feature.
+     * Verifies the validity of the given attribute value, and returns the value to store in the feature.
+     * An attribute:
+     * <ul>
+     *   <li>May be a singleton,  in which case the value class is verified.</li>
+     *   <li>May be a collection, in which case the class each elements in the collection is verified.</li>
+     * </ul>
      *
-     * @param name Shall be {@code property.getName().toString()}.
-     * @param property The property to verify.
+     * @param value The value, which shall be non-null.
      */
-    final void verifyPropertyType(final String name, final Property property) {
-        final PropertyType type;
-        if (property instanceof DefaultAttribute<?>) {
-            type = ((DefaultAttribute<?>) property).getType();
-        } else if (property instanceof DefaultAssociation) {
-            type = ((DefaultAssociation) property).getRole();
+    private static <T> Object verifyAttributeValue(final DefaultAttributeType<T> type, final Object value) {
+        final Class<T> valueClass = type.getValueClass();
+        final boolean isSingleton = Field.isSingleton(type.getMaximumOccurs());
+        if (valueClass.isInstance(value)) {
+            return isSingleton ? value : singletonList(valueClass, type.getMinimumOccurs(), value);
+        } else if (!isSingleton && value instanceof Collection<?>) {
+            return CheckedArrayList.castOrCopy((Collection<?>) value, valueClass);
         } else {
-            throw new IllegalArgumentException(Errors.format(
-                    Errors.Keys.IllegalArgumentClass_2, "property", property.getClass()));
+            throw illegalValueClass(type.getName(), value);
         }
-        if (type != getPropertyType(name)) {
-            throw new IllegalArgumentException(Errors.format(Errors.Keys.MismatchedPropertyType_1, name));
+    }
+
+    /**
+     * Verifies the validity of the given association value, and returns the value to store in the feature.
+     * An association:
+     * <ul>
+     *   <li>May be a singleton,  in which case the feature type is verified.</li>
+     *   <li>May be a collection, in which case the class each elements in the collection is verified.</li>
+     * </ul>
+     *
+     * @param value The value, which shall be non-null.
+     */
+    private static Object verifyAssociationValue(final DefaultAssociationRole role, final Object value) {
+        final boolean isSingleton = Field.isSingleton(role.getMaximumOccurs());
+        if (value instanceof AbstractFeature) {
+            /*
+             * If the user gave us a single value, first verify its validity.
+             * Then wrap it in a list of 1 element if this property is multi-valued.
+             */
+            final DefaultFeatureType valueType = ((AbstractFeature) value).getType();
+            if (role.getValueType().maybeAssignableFrom(valueType)) {
+                return isSingleton ? value : singletonList(AbstractFeature.class, role.getMinimumOccurs(), value);
+            } else {
+                throw illegalPropertyType(role.getName(), valueType.getName());
+            }
+        } else if (!isSingleton && value instanceof Collection<?>) {
+            verifyAssociationValues(role, (Collection<?>) value);
+            return CheckedArrayList.castOrCopy((Collection<?>) value, AbstractFeature.class);
+        } else {
+            throw illegalValueClass(role.getName(), value);
+        }
+    }
+
+    /**
+     * Verifies if all values in the given collection are valid instances of feature for the given association role.
+     */
+    private static void verifyAssociationValues(final DefaultAssociationRole role, final Collection<?> values) {
+        final DefaultFeatureType base = role.getValueType();
+        int index = 0;
+        for (final Object value : values) {
+            ArgumentChecks.ensureNonNullElement("values", index, value);
+            if (!(value instanceof AbstractFeature)) {
+                throw illegalValueClass(role.getName(), value);
+            }
+            final DefaultFeatureType type = ((AbstractFeature) value).getType();
+            if (!base.maybeAssignableFrom(type)) {
+                throw illegalPropertyType(role.getName(), type.getName());
+            }
+            index++;
         }
     }
 
     /**
-     * Returns the exception message for a property type which neither an attribute or an association.
+     * Creates a collection which will initially contain only the given value.
+     * At the difference of {@link Collections#singletonList(Object)}, this method returns a modifiable list.
+     */
+    @SuppressWarnings("unchecked")
+    private static <V> Collection<V> singletonList(final Class<V> valueClass, final int minimumOccurs, final Object value) {
+        final CheckedArrayList<V> values = new CheckedArrayList<>(valueClass, Math.max(minimumOccurs, 4));
+        values.add((V) value); // Type will be checked by CheckedArrayList.
+        return values;
+    }
+
+    /**
+     * Returns the exception for a property type which neither an attribute or an association.
+     * This method is invoked after a {@link PropertyType} has been found for the user-supplied name,
+     * but that property can not be stored in a {@link Property} instance.
+     */
+    static IllegalArgumentException unsupportedPropertyType(final GenericName name) {
+        return new IllegalArgumentException(Errors.format(Errors.Keys.CanNotInstantiate_1, name));
+    }
+
+    /**
+     * Returns the exception for a property value of wrong Java class.
+     *
+     * @param value The value, which shall be non-null.
+     */
+    private static ClassCastException illegalValueClass(final GenericName name, final Object value) {
+        return new ClassCastException(Errors.format(Errors.Keys.IllegalPropertyClass_2, name, value.getClass()));
+    }
+
+    /**
+     * Returns the exception for a property value (usually a feature) of wrong type.
      */
-    static String unsupportedPropertyType(final GenericName name) {
-        return Errors.format(Errors.Keys.CanNotInstantiate_1, name);
+    private static IllegalArgumentException illegalPropertyType(final GenericName name, final Object value) {
+        return new IllegalArgumentException(Errors.format(Errors.Keys.IllegalPropertyClass_2, name, value));
     }
 
     /**
@@ -405,7 +558,7 @@ public abstract class AbstractFeature im
      *     element per property. Implementations are free to omit element for properties having nothing to report.
      *   </li><li>
      *     Each report may have one or more {@linkplain org.apache.sis.metadata.iso.quality.DefaultConformanceResult
-     *     conformance result}, as documented on {@link DefaultAttribute#quality()} javadoc.
+     *     conformance result}, as documented on {@link AbstractAttribute#quality()} javadoc.
      *   </li>
      * </ul>
      *
@@ -433,18 +586,18 @@ public abstract class AbstractFeature im
      *
      * @return Reports on all constraint violations found.
      *
-     * @see DefaultAttribute#quality()
-     * @see DefaultAssociation#quality()
+     * @see AbstractAttribute#quality()
+     * @see AbstractAssociation#quality()
      */
     public DataQuality quality() {
         final Validator v = new Validator(ScopeCode.FEATURE);
         for (final String name : type.indices().keySet()) {
             final Property property = (Property) getProperty(name);
             final DataQuality quality;
-            if (property instanceof DefaultAttribute<?>) {
-                quality = ((DefaultAttribute<?>) property).quality();
-            } else if (property instanceof DefaultAssociation) {
-                quality = ((DefaultAssociation) property).quality();
+            if (property instanceof AbstractAttribute<?>) {
+                quality = ((AbstractAttribute<?>) property).quality();
+            } else if (property instanceof AbstractAssociation) {
+                quality = ((AbstractAssociation) property).quality();
             } else {
                 continue;
             }
@@ -456,45 +609,6 @@ public abstract class AbstractFeature im
     }
 
     /**
-     * Returns a copy of this feature
-     * This method clones also all {@linkplain Cloneable cloneable} property instances in this feature,
-     * but not necessarily property values. Whether the property values are cloned or not (i.e. whether
-     * the clone operation is <cite>deep</cite> or <cite>shallow</cite>) depends on the behavior or
-     * property {@code clone()} methods (see for example {@link DefaultAttribute#clone()}).
-     *
-     * @return A clone of this attribute.
-     * @throws CloneNotSupportedException if this feature can not be cloned, typically because
-     *         {@code clone()} on a property instance failed.
-     */
-    @Override
-    public AbstractFeature clone() throws CloneNotSupportedException {
-        return (AbstractFeature) super.clone();
-    }
-
-    /**
-     * Returns a hash code value for this feature.
-     *
-     * @return A hash code value.
-     */
-    @Override
-    public int hashCode() {
-        return type.hashCode();
-    }
-
-    /**
-     * Compares this feature with the given object for equality.
-     *
-     * @return {@code true} if both objects are equal.
-     */
-    @Override
-    public boolean equals(final Object obj) {
-        if (obj != null && obj.getClass() == getClass()) {
-            return type.equals(((AbstractFeature) obj).type);
-        }
-        return false;
-    }
-
-    /**
      * Formats this feature in a tabular format.
      *
      * @return A string representation of this feature in a tabular format.

Propchange: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
------------------------------------------------------------------------------
  Merged /sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java:r1596664-1597130

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] Fri May 23 17:19:24 2014
@@ -46,7 +46,7 @@ import static org.apache.sis.util.Argume
  * @version 0.5
  * @module
  *
- * @see DefaultAssociation
+ * @see AbstractAssociation
  */
 public class DefaultAssociationRole extends FieldType {
     /**
@@ -64,7 +64,7 @@ public class DefaultAssociationRole exte
     /**
      * The name of the property to use as a title for the associated feature, or an empty string if none.
      * This field is initially null, then computed when first needed.
-     * This field is used only by {@link DefaultAssociation#toString()} implementation.
+     * This information is used only by {@link AbstractAssociation#toString()} implementation.
      *
      * @see #getTitleProperty()
      */

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java [UTF-8] Fri May 23 17:19:24 2014
@@ -62,10 +62,10 @@ import java.util.Objects;
  * Such immutable instances can be shared by many objects and passed between threads without synchronization.
  *
  * <p>In particular, the {@link #getDefaultValue()} method does <strong>not</strong> clone the returned value.
- * This means that the same {@code defaultValue} instance may be shared by many {@link DefaultAttribute} instances.
+ * This means that the same {@code defaultValue} instance may be shared by many {@link AbstractAttribute} instances.
  * Consequently the default value should be immutable for avoiding unexpected behavior.</p>
  *
- * @param <T> The type of attribute values.
+ * @param <V> The type of attribute values.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -73,9 +73,9 @@ import java.util.Objects;
  * @version 0.5
  * @module
  *
- * @see DefaultAttribute
+ * @see AbstractAttribute
  */
-public class DefaultAttributeType<T> extends FieldType {
+public class DefaultAttributeType<V> extends FieldType {
     /**
      * For cross-version compatibility.
      */
@@ -86,14 +86,14 @@ public class DefaultAttributeType<T> ext
      *
      * @see #getValueClass()
      */
-    private final Class<T> valueClass;
+    private final Class<V> valueClass;
 
     /**
      * The default value for the attribute, or {@code null} if none.
      *
      * @see #getDefaultValue()
      */
-    private final T defaultValue;
+    private final V defaultValue;
 
     /**
      * Constructs an attribute type from the given properties. The identification map is given unchanged to
@@ -136,8 +136,8 @@ public class DefaultAttributeType<T> ext
      *                       or {@link Integer#MAX_VALUE} if there is no restriction.
      * @param defaultValue   The default value for the attribute, or {@code null} if none.
      */
-    public DefaultAttributeType(final Map<String,?> identification, final Class<T> valueClass,
-            final int minimumOccurs, final int maximumOccurs, final T defaultValue)
+    public DefaultAttributeType(final Map<String,?> identification, final Class<V> valueClass,
+            final int minimumOccurs, final int maximumOccurs, final V defaultValue)
     {
         super(identification, minimumOccurs, maximumOccurs);
         ensureNonNull("valueClass",   valueClass);
@@ -151,7 +151,7 @@ public class DefaultAttributeType<T> ext
      *
      * @return The type of attribute values.
      */
-    public final Class<T> getValueClass() {
+    public final Class<V> getValueClass() {
         return valueClass;
     }
 
@@ -168,10 +168,13 @@ public class DefaultAttributeType<T> ext
      */
 
     /**
-     * Returns the minimum number of occurrences of the property within its containing entity.
+     * Returns the minimum number of attribute values.
      * The returned value is greater than or equal to zero.
      *
-     * @return The minimum number of occurrences of the property within its containing entity.
+     * <p>To be valid, an {@code Attribute} instance of this {@code AttributeType} shall have at least
+     * this minimum number of elements in its {@link AbstractAttribute#getValues() collection of values}.</p>
+     *
+     * @return The minimum number of attribute values.
      */
     @Override
     public final int getMinimumOccurs() {
@@ -179,12 +182,14 @@ public class DefaultAttributeType<T> ext
     }
 
     /**
-     * Returns the maximum number of occurrences of the property within its containing entity.
+     * Returns the maximum number of attribute values.
      * The returned value is greater than or equal to the {@link #getMinimumOccurs()} value.
      * If there is no maximum, then this method returns {@link Integer#MAX_VALUE}.
      *
-     * @return The maximum number of occurrences of the property within its containing entity,
-     *         or {@link Integer#MAX_VALUE} if none.
+     * <p>To be valid, an {@code Attribute} instance of this {@code AttributeType} shall have no more than
+     * this maximum number of elements in its {@link AbstractAttribute#getValues() collection of values}.</p>
+     *
+     * @return The maximum number of attribute values, or {@link Integer#MAX_VALUE} if none.
      */
     @Override
     public final int getMaximumOccurs() {
@@ -197,7 +202,7 @@ public class DefaultAttributeType<T> ext
      *
      * @return The default value for the attribute, or {@code null} if none.
      */
-    public T getDefaultValue() {
+    public V getDefaultValue() {
         return defaultValue;
     }
 

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] Fri May 23 17:19:24 2014
@@ -395,10 +395,11 @@ public class DefaultFeatureType extends 
 
     /**
      * Returns {@code true} if the feature type acts as an abstract super-type.
+     * Abstract types can not be {@linkplain #newInstance() instantiated}.
      *
      * @return {@code true} if the feature type acts as an abstract super-type.
      */
-    public boolean isAbstract() {
+    public final boolean isAbstract() {
         return isAbstract;
     }
 
@@ -570,8 +571,12 @@ public class DefaultFeatureType extends 
      * Creates a new feature instance of this type.
      *
      * @return A new feature instance.
+     * @throws IllegalStateException if this feature type {@linkplain #isAbstract() is abstract}.
      */
-    public AbstractFeature newInstance() {
+    public AbstractFeature newInstance() throws IllegalStateException {
+        if (isAbstract) {
+            throw new IllegalStateException(Errors.format(Errors.Keys.AbstractType_1, getName()));
+        }
         return isSparse ? new SparseFeature(this) : new DenseFeature(this);
     }
 

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java [UTF-8] Fri May 23 17:19:24 2014
@@ -38,7 +38,7 @@ import org.apache.sis.util.resources.Err
  * @see SparseFeature
  * @see DefaultFeatureType
  */
-final class DenseFeature extends AbstractFeature {
+final class DenseFeature extends AbstractFeature implements Cloneable {
     /**
      * For cross-version compatibility.
      */
@@ -162,12 +162,12 @@ final class DenseFeature extends Abstrac
             if (element != null) {
                 if (!(properties instanceof Property[])) {
                     return element; // Most common case.
-                } else if (element instanceof DefaultAttribute<?>) {
-                    return ((DefaultAttribute<?>) element).getValue();
-                } else if (element instanceof DefaultAssociation) {
-                    return ((DefaultAssociation) element).getValue();
+                } else if (element instanceof AbstractAttribute<?>) {
+                    return getAttributeValue((AbstractAttribute<?>) element);
+                } else if (element instanceof AbstractAssociation) {
+                    return getAssociationValue((AbstractAssociation) element);
                 } else {
-                    throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
+                    throw unsupportedPropertyType(((Property) element).getName());
                 }
             }
         }
@@ -183,7 +183,7 @@ final class DenseFeature extends Abstrac
      * @throws IllegalArgumentException If the given value can not be assigned for an other reason.
      */
     @Override
-    public void setPropertyValue(final String name, final Object value) throws IllegalArgumentException {
+    public void setPropertyValue(final String name, Object value) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
         final int index = getIndex(name);
         if (properties == null) {
@@ -193,10 +193,7 @@ final class DenseFeature extends Abstrac
         if (!(properties instanceof Property[])) {
             if (value != null) {
                 if (!canSkipVerification(properties[index], value)) {
-                    final RuntimeException e = verifyValueType(name, value);
-                    if (e != null) {
-                        throw e;
-                    }
+                    value = verifyPropertyValue(name, value);
                 }
                 properties[index] = value;
                 return;
@@ -233,13 +230,18 @@ final class DenseFeature extends Abstrac
     }
 
     /**
-     * Returns a shallow copy of this feature.
-     * The properties are cloned, but not the property values.
-     *
-     * @return A clone of this feature.
+     * Returns a copy of this feature
+     * This method clones also all {@linkplain Cloneable cloneable} property instances in this feature,
+     * but not necessarily property values. Whether the property values are cloned or not (i.e. whether
+     * the clone operation is <cite>deep</cite> or <cite>shallow</cite>) depends on the behavior or
+     * property {@code clone()} methods.
+     *
+     * @return A clone of this attribute.
+     * @throws CloneNotSupportedException if this feature can not be cloned, typically because
+     *         {@code clone()} on a property instance failed.
      */
     @Override
-    public AbstractFeature clone() throws CloneNotSupportedException {
+    public DenseFeature clone() throws CloneNotSupportedException {
         final DenseFeature clone = (DenseFeature) super.clone();
         clone.properties = clone.properties.clone();
         if (clone.properties instanceof Property[]) {
@@ -262,7 +264,7 @@ final class DenseFeature extends Abstrac
      */
     @Override
     public int hashCode() {
-        return super.hashCode() + 37 * Arrays.hashCode(properties);
+        return type.hashCode() + 37 * Arrays.hashCode(properties);
     }
 
     /**
@@ -275,8 +277,9 @@ final class DenseFeature extends Abstrac
         if (obj == this) {
             return true;
         }
-        if (super.equals(obj)) {
-            return Arrays.equals(properties, ((DenseFeature) obj).properties);
+        if (obj instanceof DenseFeature) {
+            final DenseFeature that = (DenseFeature) obj;
+            return type.equals(that.type) && Arrays.equals(properties, that.properties);
         }
         return false;
     }

Propchange: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
------------------------------------------------------------------------------
  Merged /sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java:r1596664-1597130

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] Fri May 23 17:19:24 2014
@@ -38,8 +38,8 @@ import org.apache.sis.util.resources.Voc
  * This format assumes a monospaced font and an encoding supporting drawing box characters (e.g. UTF-8).
  *
  * <div class="note"><b>Example:</b> a feature named “City” and containing 3 properties (“name”, “population” and
- * “twin town”) may be formatted like below. The two first properties are {@linkplain DefaultAttribute attributes}
- * while the last property is an {@linkplain DefaultAssociation association} to an other feature.
+ * “twin town”) may be formatted like below. The two first properties are {@linkplain AbstractAttribute attributes}
+ * while the last property is an {@linkplain AbstractAssociation association} to an other feature.
  *
  * {@preformat text
  *   City

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Features.java [UTF-8] Fri May 23 17:19:24 2014
@@ -40,7 +40,7 @@ public final class Features extends Stat
      * An exception is thrown immediately if the given type does not have the expected
      * {@linkplain DefaultAttributeType#getValueClass() value class}.
      *
-     * @param  <T>        The expected value class.
+     * @param  <V>        The expected value class.
      * @param  type       The attribute type to cast, or {@code null}.
      * @param  valueClass The expected value class.
      * @return The attribute type casted to the given value class, or {@code null} if the given type was null.
@@ -49,19 +49,19 @@ public final class Features extends Stat
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <T> DefaultAttributeType<T> cast(final DefaultAttributeType<?> type, final Class<T> valueClass)
+    public static <V> DefaultAttributeType<V> cast(final DefaultAttributeType<?> type, final Class<V> valueClass)
             throws ClassCastException
     {
         if (type != null) {
             final Class<?> actual = type.getValueClass();
             // We require a strict equality - not type.isAssignableFrom(actual) - because in
-            // the later case we could have (to be strict) to return a <? extends T> type.
+            // the later case we could have (to be strict) to return a <? extends V> type.
             if (!valueClass.equals(actual)) {
                 throw new ClassCastException(Errors.format(Errors.Keys.MismatchedValueClass_3,
                         type.getName(), valueClass, actual));
             }
         }
-        return (DefaultAttributeType<T>) type;
+        return (DefaultAttributeType<V>) type;
     }
 
     /**
@@ -69,7 +69,7 @@ public final class Features extends Stat
      * An exception is thrown immediately if the given instance does not have the expected
      * {@linkplain DefaultAttributeType#getValueClass() value class}.
      *
-     * @param  <T>        The expected value class.
+     * @param  <V>        The expected value class.
      * @param  attribute  The attribute instance to cast, or {@code null}.
      * @param  valueClass The expected value class.
      * @return The attribute instance casted to the given value class, or {@code null} if the given instance was null.
@@ -78,18 +78,18 @@ public final class Features extends Stat
      * @category verification
      */
     @SuppressWarnings("unchecked")
-    public static <T> DefaultAttribute<T> cast(final DefaultAttribute<?> attribute, final Class<T> valueClass)
+    public static <V> AbstractAttribute<V> cast(final AbstractAttribute<?> attribute, final Class<V> valueClass)
             throws ClassCastException
     {
         if (attribute != null) {
             final Class<?> actual = attribute.getType().getValueClass();
             // We require a strict equality - not type.isAssignableFrom(actual) - because in
-            // the later case we could have (to be strict) to return a <? extends T> type.
+            // the later case we could have (to be strict) to return a <? extends V> type.
             if (!valueClass.equals(actual)) {
                 throw new ClassCastException(Errors.format(Errors.Keys.MismatchedValueClass_3,
                         attribute.getName(), valueClass, actual));
             }
         }
-        return (DefaultAttribute<T>) attribute;
+        return (AbstractAttribute<V>) attribute;
     }
 }

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FieldType.java [UTF-8] Fri May 23 17:19:24 2014
@@ -17,12 +17,18 @@
 package org.apache.sis.feature;
 
 import java.util.Map;
+import java.util.Iterator;
 import org.opengis.util.GenericName;
 import org.apache.sis.util.resources.Errors;
 
 
 /**
  * Base class of property types having a value and a cardinality.
+ * This include {@code AttributeType} and {@code AssociationRole}, but not {@code Operation}.
+ *
+ * <div class="note"><b>Analogy:</b> if we compare {@code FeatureType} to a class in the Java language,
+ * attributes and associations would be fields while operations would be methods. This analogy explains
+ * the {@code FieldType} name of this class.</div>
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -107,14 +113,19 @@ abstract class FieldType extends Propert
     public boolean equals(final Object obj) {
         if (super.equals(obj)) {
             final FieldType that = (FieldType) obj;
-            return minimumOccurs  == that.minimumOccurs  &&
-                   maximumOccurs  == that.maximumOccurs;
+            return minimumOccurs == that.minimumOccurs &&
+                   maximumOccurs == that.maximumOccurs;
         }
         return false;
     }
 
     /**
-     * Implementation of {@link #toString()} to be shared by subclasses and {@link DefaultAttribute#toString()}.
+     * Helper method for implementation of {@code PropertyType.toString()} methods.
+     * Example:
+     *
+     * {@preformat text
+     *     FooType[“name” : ValueClass]
+     * }
      */
     final StringBuilder toString(final String typeName, final Object valueName) {
         final StringBuilder buffer = new StringBuilder(40).append(typeName).append('[');
@@ -128,4 +139,33 @@ abstract class FieldType extends Propert
         }
         return buffer.append(valueName).append(']');
     }
+
+    /**
+     * Helper method for implementation of {@code Property.toString()} methods.
+     * Example:
+     *
+     * {@preformat text
+     *     FooType[“name” : ValueClass] = {value1, value2, ...}
+     * }
+     */
+    final String toString(final String typeName, final Object valueName, final Iterator<?> values) {
+        final StringBuilder buffer = toString(typeName, valueName);
+        if (values.hasNext()) {
+            final Object value = values.next();
+            final boolean isMultiValued = values.hasNext();
+            buffer.append(" = ");
+            if (isMultiValued) {
+                buffer.append('{');
+            }
+            buffer.append(value);
+            if (isMultiValued) {
+                buffer.append(", ").append(values.next());
+                if (values.hasNext()) {
+                    buffer.append(", ...");
+                }
+                buffer.append('}');
+            }
+        }
+        return buffer.toString();
+    }
 }

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java [UTF-8] Fri May 23 17:19:24 2014
@@ -27,7 +27,7 @@ import static org.apache.sis.util.Argume
 
 /**
  * A list containing 0 or 1 value. This implementation is used in the very common case where a
- * {@link DefaultAttribute} accepts at most one value. Its main purpose is to reduce the amount
+ * {@link AbstractAttribute} accepts at most one value. Its main purpose is to reduce the amount
  * of objects in memory compared to {@link java.util.ArrayList}.
  *
  * <p>There is no need to keep long-lived references to instances of this class.
@@ -42,12 +42,12 @@ final class PropertySingleton<V> extends
     /**
      * The property where to read and write the value.
      */
-    private final DefaultAttribute<V> property;
+    private final Field<V> property;
 
     /**
      * Creates a new list for the value of the given property.
      */
-    PropertySingleton(final DefaultAttribute<V> property) {
+    PropertySingleton(final Field<V> property) {
         this.property = property;
     }
 

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java [UTF-8] Fri May 23 17:19:24 2014
@@ -19,7 +19,6 @@ package org.apache.sis.feature;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.ConcurrentModificationException;
-import java.lang.reflect.Field;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.metadata.quality.DataQuality;
 import org.apache.sis.internal.util.Cloner;
@@ -42,7 +41,7 @@ import org.apache.sis.util.CorruptedObje
  * @see DenseFeature
  * @see DefaultFeatureType
  */
-final class SparseFeature extends AbstractFeature {
+final class SparseFeature extends AbstractFeature implements Cloneable {
     /**
      * For cross-version compatibility.
      */
@@ -73,7 +72,7 @@ final class SparseFeature extends Abstra
      *
      * @see #valuesKind
      */
-    private final HashMap<String, Object> properties;
+    private HashMap<String, Object> properties;
 
     /**
      * {@link #PROPERTIES} if the values in the {@link #properties} map are {@link Property} instances,
@@ -176,12 +175,12 @@ final class SparseFeature extends Abstra
         if (element != null) {
             if (valuesKind == VALUES) {
                 return element; // Most common case.
-            } else if (element instanceof DefaultAttribute<?>) {
-                return ((DefaultAttribute<?>) element).getValue();
-            } else if (element instanceof DefaultAssociation) {
-                return ((DefaultAssociation) element).getValue();
+            } else if (element instanceof AbstractAttribute<?>) {
+                return getAttributeValue((AbstractAttribute<?>) element);
+            } else if (element instanceof AbstractAssociation) {
+                return getAssociationValue((AbstractAssociation) element);
             } else if (valuesKind == PROPERTIES) {
-                throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
+                throw unsupportedPropertyType(((Property) element).getName());
             } else {
                 throw new CorruptedObjectException(String.valueOf(getName()));
             }
@@ -211,10 +210,13 @@ final class SparseFeature extends Abstra
              * a new value or a value of a different type, then we need to check the name and type validity.
              */
             if (!canSkipVerification(previous, value)) {
-                final RuntimeException e = verifyValueType(name, value);
-                if (e != null) {
-                    replace(name, value, previous); // Restore the previous value.
-                    throw e;
+                Object toStore = previous; // This initial value will restore the previous value if the check fail.
+                try {
+                    toStore = verifyPropertyValue(name, value);
+                } finally {
+                    if (toStore != value) {
+                        replace(name, value, toStore);
+                    }
                 }
             }
         } else if (valuesKind == PROPERTIES) {
@@ -246,7 +248,7 @@ final class SparseFeature extends Abstra
     public DataQuality quality() {
         if (valuesKind == VALUES) {
             final Validator v = new Validator(ScopeCode.FEATURE);
-            for (final String name : super.getType().indices().keySet()) {
+            for (final String name : type.indices().keySet()) {
                 v.validateAny(getPropertyType(name), properties.get(name));
             }
             return v.quality;
@@ -258,21 +260,21 @@ final class SparseFeature extends Abstra
     }
 
     /**
-     * Returns a copy of this feature.
-     * The properties are cloned, but not the property values.
-     *
-     * @return A clone of this feature.
+     * Returns a copy of this feature
+     * This method clones also all {@linkplain Cloneable cloneable} property instances in this feature,
+     * but not necessarily property values. Whether the property values are cloned or not (i.e. whether
+     * the clone operation is <cite>deep</cite> or <cite>shallow</cite>) depends on the behavior or
+     * property {@code clone()} methods.
+     *
+     * @return A clone of this attribute.
+     * @throws CloneNotSupportedException if this feature can not be cloned, typically because
+     *         {@code clone()} on a property instance failed.
      */
     @Override
-    public AbstractFeature clone() throws CloneNotSupportedException {
+    @SuppressWarnings("unchecked")
+    public SparseFeature clone() throws CloneNotSupportedException {
         final SparseFeature clone = (SparseFeature) super.clone();
-        try {
-            final Field field = SparseFeature.class.getDeclaredField("properties");
-            field.setAccessible(true);
-            field.set(clone, clone.properties.clone());
-        } catch (ReflectiveOperationException e) {
-            throw new AssertionError(e);
-        }
+        clone.properties = (HashMap<String,Object>) clone.properties.clone();
         switch (clone.valuesKind) {
             default:        throw new AssertionError(clone.valuesKind);
             case CORRUPTED: throw new CorruptedObjectException(clone.getName());
@@ -298,7 +300,7 @@ final class SparseFeature extends Abstra
      */
     @Override
     public int hashCode() {
-        return super.hashCode() + 37 * properties.hashCode();
+        return type.hashCode() + 37 * properties.hashCode();
     }
 
     /**
@@ -311,8 +313,9 @@ final class SparseFeature extends Abstra
         if (obj == this) {
             return true;
         }
-        if (super.equals(obj)) {
-            return properties.equals(((SparseFeature) obj).properties);
+        if (obj instanceof SparseFeature) {
+            final SparseFeature that = (SparseFeature) obj;
+            return type.equals(that.type) && properties.equals(that.properties);
         }
         return false;
     }

Propchange: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
------------------------------------------------------------------------------
  Merged /sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java:r1596664-1597130

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java [UTF-8] Fri May 23 17:19:24 2014
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.feature;
 
+import java.util.Collection;
+import java.util.Collections;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.Identifier;
@@ -90,50 +92,65 @@ final class Validator {
     }
 
     /**
+     * Wraps singleton value in a collection for processing by {@code validate(…)} methods.
+     */
+    private static Collection<?> asList(final Object value, final int maximumOccurrences) {
+        if (maximumOccurrences <= 1) {
+            return (value != null) ? Collections.singletonList(value) : Collections.emptyList();
+        } else {
+            return (Collection<?>) value;
+        }
+    }
+
+    /**
      * Verifies if the given value is valid for the given attribute type.
      * This method delegates to one of the {@code validate(…)} methods depending of the value type.
      */
     void validateAny(final PropertyType type, final Object value) {
         if (type instanceof DefaultAttributeType<?>) {
-            validate((DefaultAttributeType<?>) type, value);
+            validate((DefaultAttributeType<?>) type, asList(value,
+                    ((DefaultAttributeType<?>) type).getMaximumOccurs()));
         }
         if (type instanceof DefaultAssociationRole) {
-            validate((DefaultAssociationRole) type, (AbstractFeature) value);
+            validate((DefaultAssociationRole) type, asList(value,
+                    ((DefaultAssociationRole) type).getMaximumOccurs()));
         }
     }
 
     /**
-     * Verifies if the given value is valid for the given attribute type.
+     * Verifies if the given values are valid for the given attribute type.
      */
-    void validate(final DefaultAttributeType<?> type, final Object value) {
+    void validate(final DefaultAttributeType<?> type, final Collection<?> values) {
         AbstractElement report = null;
-        if (value != null) {
+        for (final Object value : values) {
             /*
-             * In theory, the following check is unnecessary since the type was constrained by the Attribute.setValue(T)
+             * In theory, the following check is unnecessary since the type was constrained by the Attribute.setValue(V)
              * method signature. However in practice the call to Attribute.setValue(…) is sometime done after type erasure,
              * so we are better to check.
              */
             if (!type.getValueClass().isInstance(value)) {
                 report = addViolationReport(report, type, Errors.formatInternational(
                         Errors.Keys.IllegalPropertyClass_2, type.getName(), value.getClass()));
+                break; // Report only the first violation for now.
             }
         }
-        verifyCardinality(report, type, type.getMinimumOccurs(), type.getMaximumOccurs(), value);
+        verifyCardinality(report, type, type.getMinimumOccurs(), type.getMaximumOccurs(), values.size());
     }
 
     /**
      * Verifies if the given value is valid for the given association role.
      */
-    void validate(final DefaultAssociationRole role, final AbstractFeature value) {
+    void validate(final DefaultAssociationRole role, final Collection<?> values) {
         AbstractElement report = null;
-        if (value != null) {
-            final DefaultFeatureType type = value.getType();
+        for (final Object value : values) {
+            final DefaultFeatureType type = ((AbstractFeature) value).getType();
             if (!role.getValueType().isAssignableFrom(type)) {
                 report = addViolationReport(report, role, Errors.formatInternational(
                         Errors.Keys.IllegalPropertyClass_2, role.getName(), type.getName()));
+                break; // Report only the first violation for now.
             }
         }
-        verifyCardinality(report, role, role.getMinimumOccurs(), role.getMaximumOccurs(), value);
+        verifyCardinality(report, role, role.getMinimumOccurs(), role.getMaximumOccurs(), values.size());
     }
 
     /**
@@ -142,18 +159,24 @@ final class Validator {
      * @param report Where to add the result, or {@code null} if not yet created.
      */
     private void verifyCardinality(final AbstractElement report, final AbstractIdentifiedType type,
-            final int minimumOccurs, final int maximumOccurs, final Object value)
+            final int minimumOccurs, final int maximumOccurs, final int count)
     {
-        if (value == null) {
-            if (minimumOccurs != 0) {
-                addViolationReport(report, type, Errors.formatInternational(
-                        Errors.Keys.MissingValueForProperty_1, type.getName()));
+        if (count < minimumOccurs) {
+            final InternationalString message;
+            if (count == 0) {
+                message = Errors.formatInternational(Errors.Keys.MissingValueForProperty_1, type.getName());
+            } else {
+                message = Errors.formatInternational(Errors.Keys.TooFewOccurrences_2, minimumOccurs, type.getName());
             }
-        } else {
+            addViolationReport(report, type, message);
+        } else if (count > maximumOccurs) {
+            final InternationalString message;
             if (maximumOccurs == 0) {
-                addViolationReport(report, type, Errors.formatInternational(
-                        Errors.Keys.ForbiddenProperty_1, type.getName()));
+                message = Errors.formatInternational(Errors.Keys.ForbiddenProperty_1, type.getName());
+            } else {
+                message = Errors.formatInternational(Errors.Keys.TooManyOccurrences_2, maximumOccurs, type.getName());
             }
+            addViolationReport(report, type, message);
         }
     }
 }

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java [UTF-8] Fri May 23 17:19:24 2014
@@ -72,10 +72,10 @@
  * {@code      ├─} {@linkplain org.apache.sis.feature.DefaultAssociationRole  Feature association role}<br>
  * {@code      └─} {@linkplain org.apache.sis.feature.DefaultOperation        Operation}<br>
  * </td><td class="sep" style="width: 50%; white-space: nowrap">
- *             {@linkplain org.apache.sis.feature.AbstractFeature    Feature}<br>
- *                                                                   Property<br>
- * {@code  ├─} {@linkplain org.apache.sis.feature.DefaultAttribute   Attribute}<br>
- * {@code  └─} {@linkplain org.apache.sis.feature.DefaultAssociation Feature association}<br>
+ *             {@linkplain org.apache.sis.feature.AbstractFeature     Feature}             (<cite>sparse</cite> or <cite>dense</cite>)<br>
+ *                                                                    Property<br>
+ * {@code  ├─} {@linkplain org.apache.sis.feature.AbstractAttribute   Attribute}           (<cite>singleton</cite> or <cite>multi-valued</cite>)<br>
+ * {@code  └─} {@linkplain org.apache.sis.feature.AbstractAssociation Feature association} (<cite>singleton</cite> or <cite>multi-valued</cite>)<br>
  * </td></tr></table>
  *
  * @author  Travis L. Pinney

Modified: sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/CustomAttribute.java [UTF-8] Fri May 23 17:19:24 2014
@@ -27,7 +27,7 @@ import static org.junit.Assert.*;
 
 
 /**
- * For testing {@link DefaultAttribute} customization.
+ * For testing {@link AbstractAttribute} customization.
  * This implementation adds its own criterion to the attribute quality evaluation.
  *
  * @author  Martin Desruisseaux (Geomatys)
@@ -36,17 +36,39 @@ import static org.junit.Assert.*;
  * @module
  */
 @SuppressWarnings("serial")
-final strictfp class CustomAttribute<V> extends DefaultAttribute<V> {
+final strictfp class CustomAttribute<V> extends AbstractAttribute<V> {
     /**
      * A quality information that this attribute will report in addition to the default ones.
      */
     static final String ADDITIONAL_QUALITY_INFO = "Some statistical quality measurement.";
 
     /**
+     * The singleton value.
+     */
+    private V value;
+
+    /**
      * Creates a new attribute.
      */
     public CustomAttribute(final DefaultAttributeType<V> type) {
         super(type);
+        value = type.getDefaultValue();
+    }
+
+    /**
+     * Returns the singleton value.
+     */
+    @Override
+    public V getValue() {
+        return value;
+    }
+
+    /**
+     * Sets the singleton value.
+     */
+    @Override
+    public void setValue(final V value) {
+        this.value = value;
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationRoleTest.java [UTF-8] Fri May 23 17:19:24 2014
@@ -35,11 +35,12 @@ import static org.apache.sis.test.Assert
 @DependsOn(DefaultFeatureTypeTest.class)
 public final strictfp class DefaultAssociationRoleTest extends TestCase {
     /**
-     * Creates an association.
+     * Creates an association to a twin town. We arbitrarily fix the maximum number
+     * of occurrences to 1, even if in reality some cities have many twin towns.
      */
     static DefaultAssociationRole twinTown() {
         return new DefaultAssociationRole(singletonMap(DefaultAssociationRole.NAME_KEY, "twin town"),
-                DefaultFeatureTypeTest.city(), 0, Integer.MAX_VALUE);
+                DefaultFeatureTypeTest.city(), 0, 1);
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAttributeTypeTest.java [UTF-8] Fri May 23 17:19:24 2014
@@ -42,6 +42,15 @@ public final strictfp class DefaultAttri
     /**
      * Creates an attribute type for city name.
      *
+     * @return An attribute type for a city name.
+     */
+    public static DefaultAttributeType<String> city() {
+        return city(new HashMap<String,Object>());
+    }
+
+    /**
+     * Implementation of {@link #city()} using the given map (for reusing existing objects).
+     *
      * @param identification An empty temporary map (provided only for recycling existing instances).
      */
     static DefaultAttributeType<String> city(final Map<String,Object> identification) {
@@ -66,6 +75,8 @@ public final strictfp class DefaultAttri
      */
     static DefaultAttributeType<Integer> population(final Map<String,Object> identification) {
         assertNull(identification.put(DefaultAttributeType.NAME_KEY, "population"));
+        // We may add more properties here in a future version.
+
         final DefaultAttributeType<Integer> population = new DefaultAttributeType<>(
                 identification, Integer.class, 1, 1, null);
         identification.clear();
@@ -74,10 +85,27 @@ public final strictfp class DefaultAttri
 
     /**
      * Creates an attribute type for a parliament name.
+     * This applies only to features of type "Capital".
+     * This is used for testing feature type inheritance.
+     *
+     * @return An attribute type for the name of the parliament in a capital.
+     */
+    public static DefaultAttributeType<String> parliament() {
+        return new DefaultAttributeType<>(
+                singletonMap(DefaultAttributeType.NAME_KEY, "parliament"),
+                String.class, 1, 1, null);
+    }
+
+    /**
+     * Creates an attribute type for a list of universities.
+     * The cardinality is [0 … ∞].
+     *
+     * @return An attribute type for university names.
      */
-    static DefaultAttributeType<String> parliament() {
-        return new DefaultAttributeType<>(singletonMap(DefaultAttributeType.NAME_KEY, "parliament"),
-                        String.class, 1, 1, null);
+    public static DefaultAttributeType<String> universities() {
+        return new DefaultAttributeType<>(
+                singletonMap(DefaultAttributeType.NAME_KEY, "universities"),
+                String.class, 0, Integer.MAX_VALUE, null);
     }
 
     /**
@@ -85,7 +113,7 @@ public final strictfp class DefaultAttri
      */
     @Test
     public void testMandatorySingleton() {
-        final DefaultAttributeType<String> city = city(new HashMap<String,Object>());
+        final DefaultAttributeType<String> city = city();
         final GenericName name = city.getName();
         assertInstanceOf("city.name", LocalName.class, name);
         assertEquals("city.name", "city", name.toString());
@@ -129,7 +157,7 @@ public final strictfp class DefaultAttri
     @Test
     @DependsOnMethod("testEquals")
     public void testSerialization() {
-        final DefaultAttributeType<String> attribute = city(new HashMap<String,Object>(4));
+        final DefaultAttributeType<String> attribute = city();
         assertSerializedEquals(attribute);
     }
 
@@ -138,7 +166,7 @@ public final strictfp class DefaultAttri
      */
     @Test
     public void testToString() {
-        final DefaultAttributeType<String> city = city(new HashMap<String,Object>());
+        final DefaultAttributeType<String> city = city();
         assertEquals("AttributeType[“city” : String]", city.toString());
     }
 }

Modified: sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java [UTF-8] Fri May 23 17:19:24 2014
@@ -63,6 +63,23 @@ public final strictfp class DefaultFeatu
     }
 
     /**
+     * Creates a sub-type of the "city" type with only one additional property, an arbitrary amount of strings.
+     * The feature contains the following attribute:
+     *
+     * <ul>
+     *   <li>{@code city}         as a  {@link String}  (mandatory)</li>
+     *   <li>{@code population}   as an {@link Integer} (mandatory)</li>
+     *   <li>{@code universities} as an arbitrary amount of {@link String}</li>
+     * </ul>
+     *
+     * @return The feature for an university city.
+     */
+    public static DefaultFeatureType universityCity() {
+        return new DefaultFeatureType(singletonMap(DefaultFeatureType.NAME_KEY, "University city"), false,
+                new DefaultFeatureType[] {city()}, DefaultAttributeTypeTest.universities());
+    }
+
+    /**
      * Creates a sub-type of the "city" type with only one additional property, a string giving the parliament name.
      * The feature contains the following attribute:
      *
@@ -106,19 +123,21 @@ public final strictfp class DefaultFeatu
 
     /**
      * Creates a sub-type of the "metropolis" type with the "region" attribute overridden to
-     * {@link InternationalString}.
+     * {@link InternationalString} and an arbitrary amount of universities.
      */
     static DefaultFeatureType worldMetropolis() {
-        return worldMetropolis(metropolis(), InternationalString.class);
+        return worldMetropolis(metropolis(), universityCity(), InternationalString.class);
     }
 
     /**
      * Creates a sub-type of the "metropolis" type with the "region" attribute overridden to the given type.
      * The given type should be {@link InternationalString}, but we allow other type for testing argument checks.
      */
-    private static DefaultFeatureType worldMetropolis(final DefaultFeatureType metropolis, final Class<?> regionType) {
+    private static DefaultFeatureType worldMetropolis(final DefaultFeatureType metropolis,
+            final DefaultFeatureType universityCity, final Class<?> regionType)
+    {
         return new DefaultFeatureType(singletonMap(DefaultFeatureType.NAME_KEY, "World metropolis"), false,
-                new DefaultFeatureType[] {metropolis},
+                new DefaultFeatureType[] {metropolis, universityCity},
                 new DefaultAttributeType<>(singletonMap(DefaultAttributeType.NAME_KEY, "region"),
                         regionType, 1, 1, null));
 
@@ -359,26 +378,27 @@ public final strictfp class DefaultFeatu
     @Test
     @DependsOnMethod({"testMultiInheritance", "testNameCollision"})
     public void testPropertyOverride() {
-        final DefaultFeatureType metropolis = metropolis();
+        final DefaultFeatureType metropolis     = metropolis();
+        final DefaultFeatureType universityCity = universityCity();
         try {
-            worldMetropolis(metropolis, Integer.class);
+            worldMetropolis(metropolis, universityCity, Integer.class);
             fail("Shall not be allowed to override a 'CharSequence' attribute with an 'Integer' one.");
         } catch (IllegalArgumentException e) {
             final String message = e.getMessage();
             assertTrue(message, message.contains("region"));
             assertTrue(message, message.contains("Metropolis"));
         }
-        final DefaultFeatureType worldMetropolis = worldMetropolis(metropolis, InternationalString.class);
+        final DefaultFeatureType worldMetropolis = worldMetropolis(metropolis, universityCity, InternationalString.class);
         assertUnmodifiable(worldMetropolis);
         assertEquals     ("name", "World metropolis", worldMetropolis.getName().toString());
-        assertArrayEquals("superTypes", new Object[] {metropolis}, worldMetropolis.getSuperTypes().toArray());
+        assertArrayEquals("superTypes", new Object[] {metropolis, universityCity}, worldMetropolis.getSuperTypes().toArray());
         assertFalse      ("isAbstract",      worldMetropolis.isAbstract());
         assertFalse      ("isSparse",        worldMetropolis.isSparse());
-        assertTrue       ("isSimple",        worldMetropolis.isSimple());
-        assertEquals     ("instanceSize", 4, worldMetropolis.indices().size());
+        assertFalse      ("isSimple",        worldMetropolis.isSimple()); // Because of the arbitrary amount of universities.
+        assertEquals     ("instanceSize", 5, worldMetropolis.indices().size());
 
         assertPropertiesEquals(worldMetropolis, false, "region");
-        assertPropertiesEquals(worldMetropolis, true, "city", "population", "region", "isGlobal");
+        assertPropertiesEquals(worldMetropolis, true, "city", "population", "region", "isGlobal", "universities");
         assertEquals("property(“region”).valueClass", InternationalString.class,
                 ((DefaultAttributeType) worldMetropolis.getProperty("region")).getValueClass());
 

Modified: sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultOperationTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultOperationTest.java?rev=1597134&r1=1597133&r2=1597134&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultOperationTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultOperationTest.java [UTF-8] Fri May 23 17:19:24 2014
@@ -16,7 +16,6 @@
  */
 package org.apache.sis.feature;
 
-import java.util.HashMap;
 import org.opengis.parameter.ParameterDescriptor;
 import org.apache.sis.parameter.ParameterBuilder;
 import org.apache.sis.test.DependsOn;
@@ -35,7 +34,7 @@ import static org.apache.sis.test.Assert
  * @version 0.5
  * @module
  */
-@DependsOn(DefaultAttributeTest.class)
+@DependsOn(SingletonAttributeTest.class)
 public final strictfp class DefaultOperationTest extends TestCase {
     /**
      * Returns an operation that found new cities.
@@ -47,7 +46,7 @@ public final strictfp class DefaultOpera
         };
         return new DefaultOperation(singletonMap(DefaultOperation.NAME_KEY, "found city"),
                 builder.addName("found city").createGroup(parameters),
-                DefaultAttributeTypeTest.city(new HashMap<String,Object>(4)));
+                DefaultAttributeTypeTest.city());
     }
 
     /**



Mime
View raw message