sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1595944 [1/2] - in /sis/branches/JDK8: 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/ storage/sis-shapefile/src/main/j...
Date Mon, 19 May 2014 16:01:13 GMT
Author: desruisseaux
Date: Mon May 19 16:01:13 2014
New Revision: 1595944

URL: http://svn.apache.org/r1595944
Log:
Refactored DefaultFeature in two different implementation: DenseFeature and SparseFeature.
The intend is to have an efficient storage method in the common case where all attributes
are expected to be present (also known as "simple feature").

Added:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java
      - copied, changed from r1595794, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java
      - copied, changed from r1595794, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java
      - copied, changed from r1595794, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
      - copied, changed from r1595781, sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTest.java
Removed:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTest.java
Modified:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttribute.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/Validator.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/package-info.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultAssociationTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureFormatTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
    sis/branches/JDK8/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeFile.java

Copied: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java (from r1595794, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java?p2=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java&p1=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java&r1=1595794&r2=1595944&rev=1595944&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java [UTF-8] Mon May 19 16:01:13 2014
@@ -16,16 +16,12 @@
  */
 package org.apache.sis.feature;
 
-import java.util.Map;
-import java.util.HashMap;
-import java.util.ConcurrentModificationException;
 import java.io.Serializable;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.quality.DataQuality;
 import org.opengis.metadata.maintenance.ScopeCode;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.CorruptedObjectException;
 
 
@@ -39,6 +35,8 @@ import org.apache.sis.util.CorruptedObje
  *   <li>{@linkplain DefaultOperation   Operations}</li>
  * </ul>
  *
+ * {@code AbstractFeature} can be instantiated by calls to {@link DefaultFeatureType#newInstance()}.
+ *
  * {@section Simple features}
  * A feature is said “simple” if it complies to the following conditions:
  * <ul>
@@ -46,9 +44,14 @@ import org.apache.sis.util.CorruptedObje
  *   <li>the cardinality of all attributes is constrained to [1 … 1].</li>
  * </ul>
  *
- * {@section Usage in multi-thread environment}
- * {@code DefaultFeature} are <strong>not</strong> thread-safe.
- * Synchronization, if needed, shall be done externally by the caller.
+ * {@section Limitations}
+ * <ul>
+ *   <li><b>Multi-threading:</b> {@code AbstractFeature} instances are <strong>not</strong> thread-safe.
+ *       Synchronization, if needed, shall be done externally by the caller.</li>
+ *   <li><b>Serialization:</b> serialized objects of this class are not guaranteed to be compatible with future
+ *       versions. Serialization should be used only for short term storage or RMI between applications running
+ *       the same SIS version.</li>
+ * </ul>
  *
  * @author  Travis L. Pinney
  * @author  Johann Sorel (Geomatys)
@@ -57,28 +60,13 @@ import org.apache.sis.util.CorruptedObje
  * @version 0.5
  * @module
  *
- * @see DefaultFeatureType
+ * @see DefaultFeatureType#newInstance()
  */
-public class DefaultFeature implements Serializable {
+public abstract class AbstractFeature implements Serializable {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 6594295132544357870L;
-
-    /**
-     * A {@link #valuesKind} flag meaning that the {@link #properties} map contains raw values.
-     */
-    private static final byte VALUES = 0; // Must be zero, because we want it to be 'valuesKind' default value.
-
-    /**
-     * A {@link #valuesKind} flag meaning that the {@link #properties} map contains {@link Property} instances.
-     */
-    private static final byte PROPERTIES = 1;
-
-    /**
-     * A {@link #valuesKind} flag meaning that the {@link #properties} map is invalid.
-     */
-    private static final byte CORRUPTED = 2;
+    private static final long serialVersionUID = -5637918246427380190L;
 
     /**
      * Information about the feature (name, characteristics, <i>etc.</i>).
@@ -86,36 +74,21 @@ public class DefaultFeature implements S
     private final DefaultFeatureType type;
 
     /**
-     * The properties (attributes, operations, feature associations) of this feature.
-     *
-     * Conceptually, values in this map are {@link Property} instances. However at first we will store
-     * only the property <em>values</em>, and build the full {@code Property} objects only if they are
-     * requested. The intend is to reduce the amount of allocated objects as much as possible, because
-     * typical SIS applications may create a very large amount of features.
-     *
-     * @see #valuesKind
-     */
-    private final Map<String, Object> properties;
-
-    /**
-     * {@link #PROPERTIES} if the values in the {@link #properties} map are {@link Property} instances,
-     * or {@link #VALUES} if the map contains only the "raw" property values.
-     *
-     * <p>This field is initially {@code VALUES}, and will be set to {@code PROPERTIES} only if at least
-     * one {@code Property} instance has been requested. In such case, all property values will have been
-     * wrapped into their appropriate {@code Property} instance.</p>
-     */
-    private byte valuesKind;
-
-    /**
      * Creates a new feature of the given type.
      *
      * @param type Information about the feature (name, characteristics, <i>etc.</i>).
      */
-    public DefaultFeature(final DefaultFeatureType type) {
+    public AbstractFeature(final DefaultFeatureType type) {
         ArgumentChecks.ensureNonNull("type", type);
         this.type = type;
-        properties = new HashMap<>(Math.min(16, Containers.hashMapCapacity(type.getInstanceSize())));
+    }
+
+    /**
+     * Return the type name as a non-null string. This is used mostly for formatting error message.
+     * This method should not be used when a null name should be considered as an error.
+     */
+    final String getName() {
+        return String.valueOf(type.getName());
     }
 
     /**
@@ -137,12 +110,12 @@ public class DefaultFeature implements S
      * @return The type for the property of the given name (never {@code null}).
      * @throws IllegalArgumentException If the given argument is not a property name of this feature.
      */
-    private PropertyType getPropertyType(final String name) throws IllegalArgumentException {
+    final PropertyType getPropertyType(final String name) throws IllegalArgumentException {
         final PropertyType pt = type.getProperty(name);
         if (pt != null) {
             return pt;
         }
-        throw new IllegalArgumentException(Errors.format(Errors.Keys.PropertyNotFound_2, type.getName(), name));
+        throw new IllegalArgumentException(Errors.format(Errors.Keys.PropertyNotFound_2, getName(), name));
     }
 
     /**
@@ -155,60 +128,62 @@ public class DefaultFeature implements S
      * @return The property of the given name.
      * @throws IllegalArgumentException If the given argument is not a property name of this feature.
      */
-    public Object getProperty(final String name) throws IllegalArgumentException {
-        ArgumentChecks.ensureNonNull("name", name);
-        /*
-         * Wraps values in Property objects for all entries in the properties map,
-         * if not already done. This operation is execute at most once per feature.
-         */
-        if (valuesKind != PROPERTIES) {
-            if (!properties.isEmpty()) { // The map is typically empty when this method is first invoked.
-                if (valuesKind != VALUES) {
-                    throw new CorruptedObjectException(String.valueOf(type.getName()));
-                }
-                valuesKind = CORRUPTED;
-                for (final Map.Entry<String, Object> entry : properties.entrySet()) {
-                    final String key      = entry.getKey();
-                    final Object value    = entry.getValue();
-                    final PropertyType pt = getPropertyType(key);
-                    final Property property;
-                    if (pt instanceof DefaultAttributeType<?>) {
-                        property = new DefaultAttribute<>((DefaultAttributeType<?>) pt, value);
-                    } else if (pt instanceof DefaultAssociationRole) {
-                        property = new DefaultAssociation((DefaultAssociationRole) pt, (DefaultFeature) value);
-                    } else {
-                        // Should never happen, unless the user gave us some mutable FeatureType.
-                        throw new IllegalStateException(Errors.format(Errors.Keys.UnknownType_1, pt));
-                    }
-                    if (entry.setValue(property) != value) {
-                        throw new ConcurrentModificationException(key);
-                    }
-                }
-            }
-            valuesKind = PROPERTIES; // Set only on success.
+    public abstract Object getProperty(final String name) throws IllegalArgumentException;
+
+    /**
+     * Wraps the given value in a {@link Property} object. This method is invoked only by
+     * {@link #getProperty(String)} when it needs to converts its {@code properties} data.
+     *
+     * @param  name  The name of the property to create.
+     * @param  value The value to wrap.
+     * @return A {@code Property} wrapping the given value.
+     */
+    final Property createProperty(final String name, final Object value) {
+        final PropertyType pt = getPropertyType(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return new DefaultAttribute<>((DefaultAttributeType<?>) pt, value);
+        } else if (pt instanceof DefaultAssociationRole) {
+            return new DefaultAssociation((DefaultAssociationRole) pt, (AbstractFeature) value);
+        } else {
+            // Should never happen, unless the user gave us some mutable FeatureType.
+            throw new CorruptedObjectException(Errors.format(Errors.Keys.UnknownType_1, pt));
         }
-        return getPropertyInstance(name);
     }
 
     /**
-     * Implementation of {@link #getProperty(String)} invoked when we know that the {@link #properties}
-     * map contains {@code Property} instances (as opposed to their value).
+     * Creates a new property initialized to its default value.
+     *
+     * @param  name The name of the property to create.
+     * @return A {@code Property} of the given name.
+     * @throws IllegalArgumentException If the given argument is not an attribute or association name of this feature.
      */
-    private Property getPropertyInstance(final String name) throws IllegalArgumentException {
-        assert valuesKind == PROPERTIES : valuesKind;
-        Property property = (Property) properties.get(name);
-        if (property == null) {
-            final PropertyType pt = getPropertyType(name);
-            if (pt instanceof DefaultAttributeType<?>) {
-                property = new DefaultAttribute<>((DefaultAttributeType<?>) pt);
-            } else if (pt instanceof DefaultAssociationRole) {
-                property = new DefaultAssociation((DefaultAssociationRole) pt);
-            } else {
-                throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
-            }
-            replace(name, null, property);
+    final Property createProperty(final String name) throws IllegalArgumentException {
+        final PropertyType pt = getPropertyType(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return new DefaultAttribute<>((DefaultAttributeType<?>) pt);
+        } else if (pt instanceof DefaultAssociationRole) {
+            return new DefaultAssociation((DefaultAssociationRole) pt);
+        } else {
+            throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
+        }
+    }
+
+    /**
+     * Returns the default value 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.
+     * @throws IllegalArgumentException If the given argument is not an attribute or association name of this feature.
+     */
+    final Object getDefaultValue(final String name) throws IllegalArgumentException {
+        final PropertyType pt = getPropertyType(name);
+        if (pt instanceof DefaultAttributeType<?>) {
+            return ((DefaultAttributeType<?>) pt).getDefaultValue();
+        } else if (pt instanceof DefaultAssociationRole) {
+            return null; // No default value for associations.
+        } else {
+            throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
         }
-        return property;
     }
 
     /**
@@ -227,37 +202,7 @@ public class DefaultFeature implements S
      *
      * @see DefaultAttribute#getValue()
      */
-    public Object getPropertyValue(final String name) throws IllegalArgumentException {
-        ArgumentChecks.ensureNonNull("name", name);
-        final Object element = properties.get(name);
-        final GenericName unsupported;
-        if (element != null) {
-            if (valuesKind == VALUES) {
-                return element;
-            } else if (element instanceof DefaultAttribute<?>) {
-                return ((DefaultAttribute<?>) element).getValue();
-            } else if (element instanceof DefaultAssociation) {
-                return ((DefaultAssociation) element).getValue();
-            } else if (valuesKind == PROPERTIES) {
-                unsupported = ((Property) element).getName();
-            } else {
-                throw new CorruptedObjectException(String.valueOf(type.getName()));
-            }
-        } else if (properties.containsKey(name)) {
-            return null; // Null has been explicitely set.
-        } else {
-            final PropertyType pt = getPropertyType(name);
-            if (pt instanceof DefaultAttributeType<?>) {
-                return ((DefaultAttributeType<?>) pt).getDefaultValue();
-            } else if (pt instanceof DefaultAssociationRole) {
-                return null; // No default value for associations.
-            } else {
-                unsupported = pt.getName();
-            }
-        }
-        // Happen if the property for the given name is an Operation or an unknown PropertyType.
-        throw new IllegalArgumentException(unsupportedPropertyType(unsupported));
-    }
+    public abstract Object getPropertyValue(final String name) throws IllegalArgumentException;
 
     /**
      * Sets the value for the property of the given name.
@@ -275,83 +220,20 @@ public class DefaultFeature implements S
      *
      * @see DefaultAttribute#setValue(Object)
      */
-    public void setPropertyValue(final String name, final Object value) throws IllegalArgumentException {
-        ArgumentChecks.ensureNonNull("name", name);
-        if (valuesKind == VALUES) {
-            final Object previous = properties.put(name, value);
-            /*
-             * Slight optimisation: if we replaced a previous value of the same class, then we can skip the
-             * checks for name and type validity since those checks has been done previously. But if we add
-             * a new value or a value of a different type, then we need to check the name and type validity.
-             */
-            if (previous == null || (value != null && previous.getClass() != value.getClass())) {
-                final PropertyType pt = type.getProperty(name);
-                final Object illegalType = verifyType(pt, value);
-                if (illegalType != null) {
-                    replace(name, value, previous); // Restore the previous value.
-                    if (pt == null) {
-                        throw new IllegalArgumentException(Errors.format(
-                                Errors.Keys.PropertyNotFound_2, type.getName(), name));
-                    } else {
-                        throw new ClassCastException(Errors.format(
-                                Errors.Keys.IllegalPropertyClass_2, name, illegalType));
-                    }
-                }
-            }
-        } else if (valuesKind == PROPERTIES) {
-            final Property property = getPropertyInstance(name);
-            if (property instanceof DefaultAttribute<?>) {
-                setAttributeValue((DefaultAttribute<?>) property, value);
-            } else if (property instanceof DefaultAssociation) {
-                ArgumentChecks.ensureCanCast("value", DefaultFeature.class, value);
-                setAssociationValue((DefaultAssociation) property, (DefaultFeature) value);
-            } else {
-                throw new IllegalArgumentException(unsupportedPropertyType(property.getName()));
-            }
-        }
-    }
+    public abstract void setPropertyValue(final String name, final Object value) throws IllegalArgumentException;
 
     /**
-     * Sets a value in the {@link #properties} map.
-     *
-     * @param name     The name of the property to set.
-     * @param oldValue The old value, used for verification purpose.
-     * @param newValue The new value.
-     */
-    private void replace(final String name, final Object oldValue, final Object newValue) {
-        if (properties.put(name, newValue) != oldValue) {
-            throw new ConcurrentModificationException(name);
-        }
-    }
-
-    /**
-     * Returns {@code null} if the given type is an attribute type or association role,
-     * and if the given value is valid for that type or role. Otherwise returns the name
-     * of the property type for building an exception message.
+     * Sets the value of the given property, with some minimal checks.
      */
-    private static Object verifyType(final PropertyType type, final Object value) {
-        if (type instanceof DefaultAttributeType<?>) {
-            if (value == null) {
-                return null;
-            }
-            if (((DefaultAttributeType<?>) type).getValueClass().isInstance(value)) {
-                return null;
-            }
-        } else if (type instanceof DefaultAssociationRole) {
-            if (value == null) {
-                return null;
-            }
-            if (value instanceof DefaultFeature) {
-                final DefaultFeatureType valueType = ((DefaultFeature) value).getType();
-                if (((DefaultAssociationRole) type).getValueType().maybeAssignableFrom(valueType)) {
-                    return null;
-                }
-                return String.valueOf(valueType.getName());
-            }
+    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);
         } else {
-            throw new IllegalArgumentException(unsupportedPropertyType(type.getName()));
+            throw new IllegalArgumentException(unsupportedPropertyType(property.getName()));
         }
-        return value.getClass();
     }
 
     /**
@@ -376,7 +258,7 @@ public class DefaultFeature implements S
      * 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 DefaultFeature value) {
+    private static <T> void setAssociationValue(final DefaultAssociation association, final AbstractFeature value) {
         if (value != null) {
             final DefaultAssociationRole pt = association.getRole();
             final DefaultFeatureType base = pt.getValueType();
@@ -390,9 +272,39 @@ public class DefaultFeature implements S
     }
 
     /**
+     * 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.
+     */
+    final RuntimeException verifyValueType(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;
+            }
+        } 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()));
+            }
+        } else {
+            return new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
+        }
+        return new ClassCastException(Errors.format(Errors.Keys.IllegalPropertyClass_2,
+                name, value.getClass())); // 'value' should not be null at this point.
+    }
+
+    /**
      * Returns the exception message for a property type which neither an attribute or an association.
      */
-    private static String unsupportedPropertyType(final GenericName name) {
+    static String unsupportedPropertyType(final GenericName name) {
         return Errors.format(Errors.Keys.CanNotInstantiate_1, name);
     }
 
@@ -434,9 +346,9 @@ public class DefaultFeature implements S
      */
     public final DataQuality validate() {
         final Validator v = new Validator(ScopeCode.FEATURE);
-        for (final Map.Entry<String, Object> entry : properties.entrySet()) {
-            v.validateAny(getPropertyType(entry.getKey()), entry.getValue());
-        }
+//        for (final Map.Entry<String, Object> entry : properties.entrySet()) {
+//            v.validateAny(getPropertyType(entry.getKey()), entry.getValue());
+//        }
         return v.quality;
     }
 
@@ -447,7 +359,7 @@ public class DefaultFeature implements S
      */
     @Override
     public int hashCode() {
-        return type.hashCode() + 37 * properties.hashCode();
+        return type.hashCode();
     }
 
     /**
@@ -457,13 +369,8 @@ public class DefaultFeature implements S
      */
     @Override
     public boolean equals(final Object obj) {
-        if (obj == this) {
-            return true;
-        }
         if (obj != null && obj.getClass() == getClass()) {
-            final DefaultFeature that = (DefaultFeature) obj;
-            return type.equals(that.type) &&
-                   properties.equals(that.properties);
+            return type.equals(((AbstractFeature) obj).type);
         }
         return false;
     }

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java?rev=1595944&r1=1595943&r2=1595944&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociation.java [UTF-8] Mon May 19 16:01:13 2014
@@ -55,7 +55,7 @@ public class DefaultAssociation extends 
     /**
      * The associated feature.
      */
-    private DefaultFeature value;
+    private AbstractFeature value;
 
     /**
      * Creates a new association of the given type.
@@ -73,7 +73,7 @@ public class DefaultAssociation extends 
      * @param role  Information about the association.
      * @param value The initial value.
      */
-    public DefaultAssociation(final DefaultAssociationRole role, final DefaultFeature value) {
+    public DefaultAssociation(final DefaultAssociationRole role, final AbstractFeature value) {
         ArgumentChecks.ensureNonNull("role", role);
         this.role  = role;
         this.value = value;
@@ -113,9 +113,9 @@ public class DefaultAssociation extends 
      *
      * @return The associated feature (may be {@code null}).
      *
-     * @see DefaultFeature#getPropertyValue(String)
+     * @see AbstractFeature#getPropertyValue(String)
      */
-    public DefaultFeature getValue() {
+    public AbstractFeature getValue() {
         return value;
     }
 
@@ -134,9 +134,9 @@ public class DefaultAssociation extends 
      * @param  value The new value, or {@code null}.
      * @throws IllegalArgumentException If the given feature is not valid for this association.
      *
-     * @see DefaultFeature#setPropertyValue(String, Object)
+     * @see AbstractFeature#setPropertyValue(String, Object)
      */
-    public void setValue(final DefaultFeature value) {
+    public void setValue(final AbstractFeature value) {
         if (value != null) {
             ensureValid(role.getValueType(), value.getType());
         }
@@ -166,11 +166,11 @@ public class DefaultAssociation extends 
      *
      * @return Reports on all constraint violations found.
      *
-     * @see DefaultFeature#validate()
+     * @see AbstractFeature#validate()
      */
     /*
      * API NOTE: this method is final for now because if we allowed users to override it, users would
-     * expect their method to be invoked by DefaultFeature.validate(). But this is not yet the case.
+     * expect their method to be invoked by AbstractFeature.validate(). But this is not yet the case.
      */
     public final DataQuality validate() {
         final Validator v = new Validator(null);

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttribute.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttribute.java?rev=1595944&r1=1595943&r2=1595944&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttribute.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttribute.java [UTF-8] Mon May 19 16:01:13 2014
@@ -121,7 +121,7 @@ public class DefaultAttribute<T> extends
      *
      * @return The attribute value (may be {@code null}).
      *
-     * @see DefaultFeature#getPropertyValue(String)
+     * @see AbstractFeature#getPropertyValue(String)
      */
     public T getValue() {
         return value;
@@ -138,7 +138,7 @@ public class DefaultAttribute<T> extends
      *
      * @param value The new value.
      *
-     * @see DefaultFeature#setPropertyValue(String, Object)
+     * @see AbstractFeature#setPropertyValue(String, Object)
      */
     public void setValue(final T value) {
         this.value = value;
@@ -173,11 +173,11 @@ public class DefaultAttribute<T> extends
      *
      * @return Reports on all constraint violations found.
      *
-     * @see DefaultFeature#validate()
+     * @see AbstractFeature#validate()
      */
     /*
      * API NOTE: this method is final for now because if we allowed users to override it, users would
-     * expect their method to be invoked by DefaultFeature.validate(). But this is not yet the case.
+     * expect their method to be invoked by AbstractFeature.validate(). But this is not yet the case.
      */
     public final DataQuality validate() {
         final Validator v = new Validator(ScopeCode.ATTRIBUTE);

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java?rev=1595944&r1=1595943&r2=1595944&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] Mon May 19 16:01:13 2014
@@ -39,7 +39,7 @@ import org.apache.sis.internal.util.Unmo
 
 /**
  * Abstraction of a real-world phenomena. A {@code FeatureType} instance describes the class of all
- * {@link DefaultFeature} instances of that type.
+ * {@linkplain AbstractFeature feature} instances of that type.
  *
  * <div class="note"><b>Note:</b>
  * Compared to the Java language, {@code FeatureType} is equivalent to {@link Class} while
@@ -85,7 +85,7 @@ import org.apache.sis.internal.util.Unmo
  * @version 0.5
  * @module
  *
- * @see DefaultFeature
+ * @see AbstractFeature
  */
 public class DefaultFeatureType extends AbstractIdentifiedType {
     /**
@@ -109,6 +109,12 @@ public class DefaultFeatureType extends 
     private transient boolean isSimple;
 
     /**
+     * {@code true} if the feature instances are expected to have lot of unset properties, or
+     * {@code false} if we expect most properties to be specified.
+     */
+    private transient boolean isSparse;
+
+    /**
      * The direct parents of this feature type, or an empty set if none.
      *
      * @see #getSuperTypes()
@@ -239,19 +245,49 @@ public class DefaultFeatureType extends 
      */
     private void computeTransientFields() {
         final int capacity = Containers.hashMapCapacity(properties.size());
-        isSimple     = true;
         byName       = new LinkedHashMap<>(capacity);
         indices      = new HashMap<>(capacity);
         assignableTo = new HashSet<>(4);
         assignableTo.add(getName());
         scanPropertiesFrom(this);
         byName        = compact(byName);
-        indices       = compact(indices);
         assignableTo  = CollectionsExt.unmodifiableOrCopy(assignableTo);
         allProperties = byName.values();
         if (byName instanceof HashMap<?,?>) {
             allProperties = Collections.unmodifiableCollection(allProperties);
         }
+        /*
+         * Now check if the feature is simple/complex or dense/sparse. We perform this check after we finished
+         * to create the list of all properties, because some properties may be overridden and we want to take
+         * in account only the most specific ones.
+         */
+        isSimple = true;
+        int mandatory = 0; // Count of mandatory properties.
+        for (final Map.Entry<String,PropertyType> entry : byName.entrySet()) {
+            final int minimumOccurs, maximumOccurs;
+            final PropertyType property = entry.getValue();
+            if (property instanceof DefaultAttributeType<?>) { // TODO: check for AttributeType instead (after GeoAPI upgrade).
+                minimumOccurs = ((DefaultAttributeType<?>) property).getMinimumOccurs();
+                maximumOccurs = ((DefaultAttributeType<?>) property).getMaximumOccurs();
+                isSimple &= (minimumOccurs == maximumOccurs);
+            } else if (property instanceof FieldType) { // TODO: check for AssociationRole instead (after GeoAPI upgrade).
+                minimumOccurs = ((FieldType) property).getMinimumOccurs();
+                maximumOccurs = ((FieldType) property).getMaximumOccurs();
+                isSimple = false;
+            } else {
+                continue; // For feature operations, maximumOccurs is implicitly 0.
+            }
+            if (maximumOccurs != 0) {
+                isSimple &= (maximumOccurs == 1);
+                indices.put(entry.getKey(), indices.size());
+                if (minimumOccurs != 0) {
+                    mandatory++;
+                }
+            }
+        }
+        indices = compact(indices);
+        final int n = indices.size();
+        isSparse = (n > 16) && (mandatory < n/2); // Arbitrary criterion.
     }
 
     /**
@@ -271,14 +307,11 @@ public class DefaultFeatureType extends 
     }
 
     /**
-     * Computes the transient fields using the non-transient information in the given {@code source}.
+     * Fills the {@link #byName} map using the non-transient information in the given {@code source}.
      * This method invokes itself recursively in order to use the information provided in super-types.
+     * This method also performs an opportunist verification of argument validity.
      */
     private void scanPropertiesFrom(final DefaultFeatureType source) {
-        /*
-         * Process all super-types before to process the given type. The intend is to have the
-         * super-types properties indexed before the sub-types ones in the 'indices' map.
-         */
         for (final DefaultFeatureType parent : source.getSuperTypes()) {
             if (assignableTo.add(parent.getName())) {
                 scanPropertiesFrom(parent);
@@ -288,9 +321,6 @@ public class DefaultFeatureType extends 
         Map<DefaultFeatureType,Boolean> done = null;
         for (final PropertyType property : source.properties) {
             ArgumentChecks.ensureNonNullElement("properties", ++index, property);
-            /*
-             * Fill the (name, property) map with opportunist verification of argument validity.
-             */
             final String name = toString(property.getName(), source, index);
             final PropertyType previous = byName.put(name, property);
             if (previous != null) {
@@ -304,26 +334,6 @@ public class DefaultFeatureType extends 
                 }
                 done.clear();
             }
-            /*
-             * Fill the (name, indice) map. Values are indices that the property elements would have
-             * in a flat array. This block also opportunistically check if the FeatureType is "simple".
-             */
-            final int maximumOccurs;
-            if (property instanceof DefaultAttributeType<?>) { // TODO: check for AttributeType instead (after GeoAPI upgrade).
-                maximumOccurs = ((DefaultAttributeType<?>) property).getMaximumOccurs();
-                if (isSimple && ((DefaultAttributeType<?>) property).getMinimumOccurs() != maximumOccurs) {
-                    isSimple = false;
-                }
-            } else if (property instanceof FieldType) { // TODO: check for AssociationRole instead (after GeoAPI upgrade).
-                maximumOccurs = ((FieldType) property).getMaximumOccurs();
-                isSimple = false;
-            } else {
-                continue; // For feature operations, maximumOccurs is implicitly 0.
-            }
-            if (maximumOccurs != 0) {
-                isSimple &= (maximumOccurs == 1);
-                indices.put(name, indices.size());
-            }
         }
     }
 
@@ -531,11 +541,20 @@ public class DefaultFeatureType extends 
     }
 
     /**
-     * Returns the number of attributes and features (not operations) that instance will have
-     * if all attributes are handled as simple attributes (maximum occurrences of 1).
+     * Returns the map from names to indices in an array of properties.
+     * This is used for {@link DenseFeature} implementation.
+     */
+    final Map<String,Integer> indices() {
+        return indices;
+    }
+
+    /**
+     * Creates a new feature instance of this type.
+     *
+     * @return A new feature instance.
      */
-    final int getInstanceSize() {
-        return indices.size();
+    public AbstractFeature newInstance() {
+        return isSparse ? new SparseFeature(this) : new DenseFeature(this);
     }
 
     /**

Copied: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java (from r1595794, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java?p2=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java&p1=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java&r1=1595794&r2=1595944&rev=1595944&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeature.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java [UTF-8] Mon May 19 16:01:13 2014
@@ -17,427 +17,176 @@
 package org.apache.sis.feature;
 
 import java.util.Map;
-import java.util.HashMap;
-import java.util.ConcurrentModificationException;
-import java.io.Serializable;
-import org.opengis.util.GenericName;
-import org.opengis.metadata.quality.DataQuality;
-import org.opengis.metadata.maintenance.ScopeCode;
+import java.util.Arrays;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.collection.Containers;
-import org.apache.sis.util.CorruptedObjectException;
 
 
 /**
- * An instance of a {@linkplain DefaultFeatureType feature type} containing values for a real-world phenomena.
- * Each feature instance can provide values for the following properties:
+ * A feature in which most properties are expected to be provided. This implementation uses a plain array for
+ * its internal storage of properties. This consumes less memory than {@link java.util.Map} when we know that
+ * all (or almost all) elements in the array will be assigned a value.
  *
- * <ul>
- *   <li>{@linkplain DefaultAttribute   Attributes}</li>
- *   <li>{@linkplain DefaultAssociation Associations to other features}</li>
- *   <li>{@linkplain DefaultOperation   Operations}</li>
- * </ul>
- *
- * {@section Simple features}
- * A feature is said “simple” if it complies to the following conditions:
- * <ul>
- *   <li>the feature allows only attributes and operations (no associations),</li>
- *   <li>the cardinality of all attributes is constrained to [1 … 1].</li>
- * </ul>
- *
- * {@section Usage in multi-thread environment}
- * {@code DefaultFeature} are <strong>not</strong> thread-safe.
- * Synchronization, if needed, shall be done externally by the caller.
- *
- * @author  Travis L. Pinney
- * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.5
  * @version 0.5
  * @module
  *
+ * @see SparseFeature
  * @see DefaultFeatureType
  */
-public class DefaultFeature implements Serializable {
+final class DenseFeature extends AbstractFeature {
     /**
      * For cross-version compatibility.
      */
-    private static final long serialVersionUID = 6594295132544357870L;
-
-    /**
-     * A {@link #valuesKind} flag meaning that the {@link #properties} map contains raw values.
-     */
-    private static final byte VALUES = 0; // Must be zero, because we want it to be 'valuesKind' default value.
-
-    /**
-     * A {@link #valuesKind} flag meaning that the {@link #properties} map contains {@link Property} instances.
-     */
-    private static final byte PROPERTIES = 1;
-
-    /**
-     * A {@link #valuesKind} flag meaning that the {@link #properties} map is invalid.
-     */
-    private static final byte CORRUPTED = 2;
+    private static final long serialVersionUID = -2041120433733230588L;
 
     /**
-     * Information about the feature (name, characteristics, <i>etc.</i>).
+     * The map of property names to indices in the {@link #properties} array. This map is a reference to the
+     * {@link DefaultFeatureType#indices} map (potentially shared by many feature instances) and shall not be
+     * modified.
      */
-    private final DefaultFeatureType type;
+    private final Map<String, Integer> indices;
 
     /**
      * The properties (attributes, operations, feature associations) of this feature.
      *
-     * Conceptually, values in this map are {@link Property} instances. However at first we will store
-     * only the property <em>values</em>, and build the full {@code Property} objects only if they are
-     * requested. The intend is to reduce the amount of allocated objects as much as possible, because
-     * typical SIS applications may create a very large amount of features.
-     *
-     * @see #valuesKind
-     */
-    private final Map<String, Object> properties;
-
-    /**
-     * {@link #PROPERTIES} if the values in the {@link #properties} map are {@link Property} instances,
-     * or {@link #VALUES} if the map contains only the "raw" property values.
-     *
-     * <p>This field is initially {@code VALUES}, and will be set to {@code PROPERTIES} only if at least
-     * one {@code Property} instance has been requested. In such case, all property values will have been
-     * wrapped into their appropriate {@code Property} instance.</p>
+     * Conceptually, values in this array are {@link Property} instances. However at first we will store only
+     * the property <em>values</em>, and convert to an array of type {@code Property[]} only when at least one
+     * property is requested. The intend is to reduce the amount of allocated objects as much as possible,
+     * because typical SIS applications may create a very large amount of features.
      */
-    private byte valuesKind;
+    private Object[] properties;
 
     /**
      * Creates a new feature of the given type.
      *
      * @param type Information about the feature (name, characteristics, <i>etc.</i>).
      */
-    public DefaultFeature(final DefaultFeatureType type) {
-        ArgumentChecks.ensureNonNull("type", type);
-        this.type = type;
-        properties = new HashMap<>(Math.min(16, Containers.hashMapCapacity(type.getInstanceSize())));
-    }
-
-    /**
-     * Returns information about the feature (name, characteristics, <i>etc.</i>).
-     *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.FeatureType}. This change is pending GeoAPI revision.</div>
-     *
-     * @return Information about the feature.
-     */
-    public DefaultFeatureType getType() {
-        return type;
+    public DenseFeature(final DefaultFeatureType type) {
+        super(type);
+        indices = type.indices();
     }
 
     /**
-     * Returns the type for the property of the given name.
+     * Returns the index for the property of the given name.
      *
      * @param  name The property name.
-     * @return The type for the property of the given name (never {@code null}).
+     * @return The index for the property of the given name.
      * @throws IllegalArgumentException If the given argument is not a property name of this feature.
      */
-    private PropertyType getPropertyType(final String name) throws IllegalArgumentException {
-        final PropertyType pt = type.getProperty(name);
-        if (pt != null) {
-            return pt;
+    private int getIndex(final String name) throws IllegalArgumentException {
+        final Integer index = indices.get(name);
+        if (index != null) {
+            return index;
         }
-        throw new IllegalArgumentException(Errors.format(Errors.Keys.PropertyNotFound_2, type.getName(), name));
+        throw new IllegalArgumentException(Errors.format(Errors.Keys.PropertyNotFound_2, getName(), name));
     }
 
     /**
      * Returns the property (attribute, operation or association) of the given name.
      *
-     * <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
-     * to {@code org.opengis.feature.Property}. This change is pending GeoAPI revision.</div>
-     *
      * @param  name The property name.
      * @return The property of the given name.
      * @throws IllegalArgumentException If the given argument is not a property name of this feature.
      */
+    @Override
     public Object getProperty(final String name) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
-        /*
-         * Wraps values in Property objects for all entries in the properties map,
-         * if not already done. This operation is execute at most once per feature.
-         */
-        if (valuesKind != PROPERTIES) {
-            if (!properties.isEmpty()) { // The map is typically empty when this method is first invoked.
-                if (valuesKind != VALUES) {
-                    throw new CorruptedObjectException(String.valueOf(type.getName()));
-                }
-                valuesKind = CORRUPTED;
-                for (final Map.Entry<String, Object> entry : properties.entrySet()) {
-                    final String key      = entry.getKey();
-                    final Object value    = entry.getValue();
-                    final PropertyType pt = getPropertyType(key);
-                    final Property property;
-                    if (pt instanceof DefaultAttributeType<?>) {
-                        property = new DefaultAttribute<>((DefaultAttributeType<?>) pt, value);
-                    } else if (pt instanceof DefaultAssociationRole) {
-                        property = new DefaultAssociation((DefaultAssociationRole) pt, (DefaultFeature) value);
-                    } else {
-                        // Should never happen, unless the user gave us some mutable FeatureType.
-                        throw new IllegalStateException(Errors.format(Errors.Keys.UnknownType_1, pt));
-                    }
-                    if (entry.setValue(property) != value) {
-                        throw new ConcurrentModificationException(key);
-                    }
-                }
-            }
-            valuesKind = PROPERTIES; // Set only on success.
+        final int index = getIndex(name);
+        if (properties instanceof Property[]) {
+            return ((Property[]) properties)[index];
         }
-        return getPropertyInstance(name);
+        if (properties == null) {
+            properties = new Property[indices.size()];
+        } else {
+            wrapValuesInProperties();
+        }
+        final Property property = createProperty(name);
+        properties[index] = property;
+        return property;
     }
 
     /**
-     * Implementation of {@link #getProperty(String)} invoked when we know that the {@link #properties}
-     * map contains {@code Property} instances (as opposed to their value).
+     * Wraps values in {@code Property} objects for all elements in the {@link #properties} array.
+     * This operation is executed at most once per feature.
      */
-    private Property getPropertyInstance(final String name) throws IllegalArgumentException {
-        assert valuesKind == PROPERTIES : valuesKind;
-        Property property = (Property) properties.get(name);
-        if (property == null) {
-            final PropertyType pt = getPropertyType(name);
-            if (pt instanceof DefaultAttributeType<?>) {
-                property = new DefaultAttribute<>((DefaultAttributeType<?>) pt);
-            } else if (pt instanceof DefaultAssociationRole) {
-                property = new DefaultAssociation((DefaultAssociationRole) pt);
-            } else {
-                throw new IllegalArgumentException(unsupportedPropertyType(pt.getName()));
+    private void wrapValuesInProperties() {
+        final Property[] c = new Property[properties.length];
+        for (final Map.Entry<String, Integer> entry : indices.entrySet()) {
+            final int   index  = entry.getValue();
+            final Object value = properties[index];
+            if (value != null) {
+                c[index] = createProperty(entry.getKey(), value);
             }
-            replace(name, null, property);
         }
-        return property;
+        properties = c; // Store only on success.
     }
 
     /**
      * Returns the value for the property of the given name.
-     * This convenience method is equivalent to the following steps:
-     *
-     * <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>
      *
      * @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()
      */
+    @Override
     public Object getPropertyValue(final String name) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
-        final Object element = properties.get(name);
-        final GenericName unsupported;
-        if (element != null) {
-            if (valuesKind == VALUES) {
-                return element;
-            } else if (element instanceof DefaultAttribute<?>) {
-                return ((DefaultAttribute<?>) element).getValue();
-            } else if (element instanceof DefaultAssociation) {
-                return ((DefaultAssociation) element).getValue();
-            } else if (valuesKind == PROPERTIES) {
-                unsupported = ((Property) element).getName();
-            } else {
-                throw new CorruptedObjectException(String.valueOf(type.getName()));
-            }
-        } else if (properties.containsKey(name)) {
-            return null; // Null has been explicitely set.
-        } else {
-            final PropertyType pt = getPropertyType(name);
-            if (pt instanceof DefaultAttributeType<?>) {
-                return ((DefaultAttributeType<?>) pt).getDefaultValue();
-            } else if (pt instanceof DefaultAssociationRole) {
-                return null; // No default value for associations.
-            } else {
-                unsupported = pt.getName();
+        if (properties != null) {
+            final Object element = properties[getIndex(name)];
+            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 {
+                    throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName()));
+                }
             }
         }
-        // Happen if the property for the given name is an Operation or an unknown PropertyType.
-        throw new IllegalArgumentException(unsupportedPropertyType(unsupported));
+        return getDefaultValue(name);
     }
 
     /**
      * Sets the value for the property of the given name.
      *
-     * {@section Validation}
-     * The amount of validation performed by this method is implementation dependent.
-     * Usually, only the most basic constraints are verified. This is so for performance reasons
-     * and also because some rules may be temporarily broken while constructing a feature.
-     * A more exhaustive verification can be performed by invoking the {@link #validate()} method.
-     *
      * @param  name  The attribute name.
      * @param  value The new value for the given attribute (may be {@code null}).
      * @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)
      */
+    @Override
     public void setPropertyValue(final String name, final Object value) throws IllegalArgumentException {
         ArgumentChecks.ensureNonNull("name", name);
-        if (valuesKind == VALUES) {
-            final Object previous = properties.put(name, value);
-            /*
-             * Slight optimisation: if we replaced a previous value of the same class, then we can skip the
-             * checks for name and type validity since those checks has been done previously. But if we add
-             * a new value or a value of a different type, then we need to check the name and type validity.
-             */
-            if (previous == null || (value != null && previous.getClass() != value.getClass())) {
-                final PropertyType pt = type.getProperty(name);
-                final Object illegalType = verifyType(pt, value);
-                if (illegalType != null) {
-                    replace(name, value, previous); // Restore the previous value.
-                    if (pt == null) {
-                        throw new IllegalArgumentException(Errors.format(
-                                Errors.Keys.PropertyNotFound_2, type.getName(), name));
-                    } else {
-                        throw new ClassCastException(Errors.format(
-                                Errors.Keys.IllegalPropertyClass_2, name, illegalType));
+        final int index = getIndex(name);
+        if (properties == null) {
+            final int n = indices.size();
+            properties = (value != null) ? new Object[n] : new Property[n];
+        }
+        if (!(properties instanceof Property[])) {
+            if (value != null) {
+                final Object previous = properties[index];
+                if (previous == null || previous.getClass() != value.getClass()) {
+                    final RuntimeException e = verifyValueType(name, value);
+                    if (e != null) {
+                        throw e;
                     }
                 }
-            }
-        } else if (valuesKind == PROPERTIES) {
-            final Property property = getPropertyInstance(name);
-            if (property instanceof DefaultAttribute<?>) {
-                setAttributeValue((DefaultAttribute<?>) property, value);
-            } else if (property instanceof DefaultAssociation) {
-                ArgumentChecks.ensureCanCast("value", DefaultFeature.class, value);
-                setAssociationValue((DefaultAssociation) property, (DefaultFeature) value);
+                properties[index] = value;
+                return;
             } else {
-                throw new IllegalArgumentException(unsupportedPropertyType(property.getName()));
+                wrapValuesInProperties();
             }
         }
-    }
-
-    /**
-     * Sets a value in the {@link #properties} map.
-     *
-     * @param name     The name of the property to set.
-     * @param oldValue The old value, used for verification purpose.
-     * @param newValue The new value.
-     */
-    private void replace(final String name, final Object oldValue, final Object newValue) {
-        if (properties.put(name, newValue) != oldValue) {
-            throw new ConcurrentModificationException(name);
-        }
-    }
-
-    /**
-     * Returns {@code null} if the given type is an attribute type or association role,
-     * and if the given value is valid for that type or role. Otherwise returns the name
-     * of the property type for building an exception message.
-     */
-    private static Object verifyType(final PropertyType type, final Object value) {
-        if (type instanceof DefaultAttributeType<?>) {
-            if (value == null) {
-                return null;
-            }
-            if (((DefaultAttributeType<?>) type).getValueClass().isInstance(value)) {
-                return null;
-            }
-        } else if (type instanceof DefaultAssociationRole) {
-            if (value == null) {
-                return null;
-            }
-            if (value instanceof DefaultFeature) {
-                final DefaultFeatureType valueType = ((DefaultFeature) value).getType();
-                if (((DefaultAssociationRole) type).getValueType().maybeAssignableFrom(valueType)) {
-                    return null;
-                }
-                return String.valueOf(valueType.getName());
-            }
-        } else {
-            throw new IllegalArgumentException(unsupportedPropertyType(type.getName()));
-        }
-        return value.getClass();
-    }
-
-    /**
-     * Sets the attribute value after verification of its type. This method is invoked only for checking
-     * that we are not violating the Java parameterized type contract. For a more exhaustive validation,
-     * use {@link Validator} instead.
-     */
-    @SuppressWarnings("unchecked")
-    private static <T> void setAttributeValue(final DefaultAttribute<T> attribute, final Object value) {
-        if (value != null) {
-            final DefaultAttributeType<T> 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()));
-            }
-        }
-        ((DefaultAttribute) 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 DefaultFeature 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()));
-            }
-        }
-        association.setValue(value);
-    }
-
-    /**
-     * Returns the exception message for a property type which neither an attribute or an association.
-     */
-    private static String unsupportedPropertyType(final GenericName name) {
-        return Errors.format(Errors.Keys.CanNotInstantiate_1, name);
-    }
-
-    /**
-     * Verifies if all current properties met the constraints defined by the feature type.
-     * This method returns {@linkplain org.apache.sis.metadata.iso.quality.DefaultDataQuality#getReports()
-     * reports} for all constraint violations found, if any.
-     *
-     * <div class="note"><b>Example:</b> given a feature with an attribute named “population”.
-     * If this attribute is mandatory ([1 … 1] cardinality) but no value has been assigned to it,
-     * then this {@code validate()} method will return the following data quality report:
-     *
-     * {@preformat text
-     *   Data quality
-     *     ├─Scope
-     *     │   └─Level………………………………………………… Feature
-     *     └─Report
-     *         ├─Measure identification
-     *         │   └─Code………………………………………… population
-     *         ├─Evaluation method type…… Direct internal
-     *         └─Result
-     *             ├─Explanation……………………… Missing value for “population” property.
-     *             └─Pass………………………………………… false
-     * }
-     * </div>
-     *
-     * This feature is valid if this method does not report any
-     * {@linkplain org.apache.sis.metadata.iso.quality.DefaultConformanceResult conformance result} having a
-     * {@linkplain org.apache.sis.metadata.iso.quality.DefaultConformanceResult#pass() pass} value of {@code false}.
-     *
-     * @return Reports on all constraint violations found.
-     *
-     * @see DefaultAttribute#validate()
-     * @see DefaultAssociation#validate()
-     */
-    /*
-     * API NOTE: this method is final for now because if we allowed users to override it, users would
-     * expect their method to be invoked by DefaultAssociation.validate(). But this is not yet the case.
-     */
-    public final DataQuality validate() {
-        final Validator v = new Validator(ScopeCode.FEATURE);
-        for (final Map.Entry<String, Object> entry : properties.entrySet()) {
-            v.validateAny(getPropertyType(entry.getKey()), entry.getValue());
+        Property property = ((Property[]) properties)[index];
+        if (property == null) {
+            property = createProperty(name);
+            properties[index] = property;
         }
-        return v.quality;
+        setPropertyValue(property, value);
     }
 
     /**
@@ -447,7 +196,7 @@ public class DefaultFeature implements S
      */
     @Override
     public int hashCode() {
-        return type.hashCode() + 37 * properties.hashCode();
+        return super.hashCode() + 37 * Arrays.hashCode(properties);
     }
 
     /**
@@ -460,23 +209,9 @@ public class DefaultFeature implements S
         if (obj == this) {
             return true;
         }
-        if (obj != null && obj.getClass() == getClass()) {
-            final DefaultFeature that = (DefaultFeature) obj;
-            return type.equals(that.type) &&
-                   properties.equals(that.properties);
+        if (super.equals(obj)) {
+            return Arrays.equals(properties, ((DenseFeature) obj).properties);
         }
         return false;
     }
-
-    /**
-     * Formats this feature in a tabular format.
-     *
-     * @return A string representation of this feature in a tabular format.
-     *
-     * @see FeatureFormat
-     */
-    @Override
-    public String toString() {
-        return FeatureFormat.sharedFormat(this);
-    }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java?rev=1595944&r1=1595943&r2=1595944&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] Mon May 19 16:01:13 2014
@@ -34,7 +34,7 @@ import org.apache.sis.util.resources.Voc
 
 
 /**
- * Formats {@linkplain DefaultFeature features} or {@linkplain DefaultFeatureType feature types} in a tabular format.
+ * Formats {@link AbstractFeature features} or {@linkplain DefaultFeatureType feature types} in a tabular format.
  * 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
@@ -149,9 +149,9 @@ public class FeatureFormat extends Tabul
         ArgumentChecks.ensureNonNull("object",     object);
         ArgumentChecks.ensureNonNull("toAppendTo", toAppendTo);
         final DefaultFeatureType featureType;
-        final DefaultFeature     feature;
-        if (object instanceof DefaultFeature) {
-            feature     = (DefaultFeature) object;
+        final AbstractFeature     feature;
+        if (object instanceof AbstractFeature) {
+            feature     = (AbstractFeature) object;
             featureType = feature.getType();
         } else if (object instanceof DefaultFeatureType) {
             featureType = (DefaultFeatureType) object;
@@ -221,7 +221,7 @@ header: for (int i=0; ; i++) {
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
                 valueType     = toString(pt.getValueType().getName());
-                valueClass    = DefaultFeature.class;
+                valueClass    = AbstractFeature.class;
             } else if (propertyType instanceof DefaultOperation) {
                 final DefaultAttributeType<?> resultType = ((DefaultOperation) propertyType).getResult();
                 valueType   = toString(resultType.getName());
@@ -258,8 +258,8 @@ header: for (int i=0; ; i++) {
                     value = format.format(value, buffer, dummyFP);
                 } else if (value instanceof InternationalString) {
                     value = ((InternationalString) value).toString(displayLocale);
-                } else if (value instanceof DefaultFeature && propertyType instanceof DefaultAssociationRole) {
-                    value = ((DefaultFeature) value).getPropertyValue(
+                } else if (value instanceof AbstractFeature && propertyType instanceof DefaultAssociationRole) {
+                    value = ((AbstractFeature) value).getPropertyValue(
                             ((DefaultAssociationRole) propertyType).getTitleProperty());
                 }
                 table.append(value.toString());

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java?rev=1595944&r1=1595943&r2=1595944&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/PropertySingleton.java [UTF-8] Mon May 19 16:01:13 2014
@@ -136,7 +136,7 @@ final class PropertySingleton<V> extends
      *
      * This method does not checks if the removal is allowed by the
      * {@linkplain DefaultAttributeType#getMinimumOccurs() cardinality}.
-     * Such check can be performed by {@link DefaultFeature#validate()}.
+     * Such check can be performed by {@link AbstractFeature#validate()}.
      */
     @Override
     public V remove(final int index) {
@@ -168,7 +168,7 @@ final class PropertySingleton<V> extends
      *
      * This method does not checks if the removal is allowed by the
      * {@linkplain DefaultAttributeType#getMinimumOccurs() cardinality}.
-     * Such check can be performed by {@link DefaultFeature#validate()}.
+     * Such check can be performed by {@link AbstractFeature#validate()}.
      */
     @Override
     public void clear() {



Mime
View raw message