sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1633035 [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-metadata/src/main/java/org/apache/sis/metadata/ core/sis-metadata/src/main/jav...
Date Mon, 20 Oct 2014 05:37:04 GMT
Author: desruisseaux
Date: Mon Oct 20 05:37:03 2014
New Revision: 1633035

URL: http://svn.apache.org/r1633035
Log:
Merge from the JDK8 branch. This merge contains a first draft of a Shapestore refactoring.

Added:
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
      - copied unchanged from r1633032, sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/NamedFeatureType.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
      - copied unchanged from r1633032, sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/MetadataScopeAdapter.java
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/internal/
      - copied from r1633032, sis/branches/JDK8/storage/sis-shapefile/src/main/java/org/apache/sis/internal/
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/Database.java
      - copied unchanged from r1633032, sis/branches/JDK8/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/Database.java
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/package-info.java
      - copied unchanged from r1633032, sis/branches/JDK8/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/package-info.java
    sis/branches/JDK7/storage/sis-shapefile/src/main/resources/
      - copied from r1633032, sis/branches/JDK8/storage/sis-shapefile/src/main/resources/
    sis/branches/JDK7/storage/sis-shapefile/src/test/java/org/apache/sis/internal/
      - copied from r1633032, sis/branches/JDK8/storage/sis-shapefile/src/test/java/org/apache/sis/internal/
Removed:
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/CmdLineDriver.java
    sis/branches/JDK7/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/CmdLineDriverTest.java
Modified:
    sis/branches/JDK7/   (props changed)
    sis/branches/JDK7/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/AbstractIdentifiedType.java
    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/DefaultFeatureType.java
    sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.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/FeatureTestCase.java
    sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
    sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
    sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
    sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.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/pom.xml
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/CodePage.java
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/DataType.java
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/FieldDescriptor.java
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeFile.java
    sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeTypeEnum.java
    sis/branches/JDK7/storage/sis-shapefile/src/test/java/org/apache/sis/storage/shapefile/ShapeFileTest.java
    sis/branches/JDK7/storage/sis-shapefile/src/test/java/org/apache/sis/test/suite/ShapefileTestSuite.java

Propchange: sis/branches/JDK7/
------------------------------------------------------------------------------
  Merged /sis/branches/JDK8:r1631331-1633034

Modified: sis/branches/JDK7/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?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/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] Mon Oct 20 05:37:03 2014
@@ -223,7 +223,7 @@ public abstract class AbstractAssociatio
     public String toString() {
         final String pt = DefaultAssociationRole.getTitleProperty(role);
         final Iterator<Feature> it = getValues().iterator();
-        return FieldType.toString("FeatureAssociation", role, role.getValueType().getName(), new Iterator<Object>() {
+        return FieldType.toString("FeatureAssociation", role, DefaultAssociationRole.getValueTypeName(role), new Iterator<Object>() {
             @Override public boolean hasNext() {
                 return it.hasNext();
             }

Modified: sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractIdentifiedType.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -203,10 +203,17 @@ public class AbstractIdentifiedType impl
      * <p>For {@linkplain DefaultFeatureType feature types}, the name is mandatory and shall be unique
      * in the unit processing the data (e.g. a {@link org.apache.sis.storage.DataStore} reading a file).</p>
      *
+     * <div class="note"><b>Note for subclasses:</b>
+     * this method is final because it is invoked (indirectly) by subclass constructors,
+     * and invoking a user-overrideable method at construction time is not recommended.
+     * Furthermore, this attribute is often used as the primary key for {@code IdentifiedType} instances
+     * and need some guarantees about its stability.
+     * </div>
+     *
      * @return The type name.
      */
     @Override
-    public GenericName getName() {
+    public final GenericName getName() {
         return name;
     }
 

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=1633035&r1=1633034&r2=1633035&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] Mon Oct 20 05:37:03 2014
@@ -19,6 +19,7 @@ package org.apache.sis.feature;
 import java.util.Map;
 import org.opengis.util.GenericName;
 import org.opengis.util.InternationalString;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Debug;
 
 import static org.apache.sis.util.ArgumentChecks.*;
@@ -65,7 +66,7 @@ public class DefaultAssociationRole exte
      *
      * @see #getValueType()
      */
-    private final FeatureType valueType;
+    private volatile FeatureType valueType;
 
     /**
      * The name of the property to use as a title for the associated feature, or an empty string if none.
@@ -77,7 +78,20 @@ public class DefaultAssociationRole exte
     private volatile transient String titleProperty;
 
     /**
-     * Constructs an association role from the given properties. The properties map is given unchanged to
+     * {@code true} if we determined that the feature type name in this association has been resolved.
+     * A value of {@code true} means that {@link #valueType} is a resolved feature type. However a value
+     * of {@code false} only means that we are not sure, and that we should check again next time.
+     *
+     * <div class="note"><b>Note:</b>
+     * Strictly speaking, this field should be declared {@code volatile} since the names could be
+     * resolved late after construction, after the {@code DefaultAssociationRole} instance became
+     * used by different threads. However this is not the intended usage of deferred associations.
+     * </div>
+     */
+    private transient boolean isResolved;
+
+    /**
+     * Constructs an association to the given feature type. The properties map is given unchanged to
      * the {@linkplain AbstractIdentifiedType#AbstractIdentifiedType(Map) super-class constructor}.
      * The following table is a reminder of main (not all) recognized map entries:
      *
@@ -125,13 +139,141 @@ public class DefaultAssociationRole exte
     }
 
     /**
+     * Constructs an association to a feature type of the given name.
+     * This constructor shall be used only when creating a cyclic graph of {@link DefaultFeatureType} instances.
+     *
+     * <div class="note"><b>Example:</b>
+     * A code first creates {@code FeatureType} <var>A</var>, then {@code FeatureType} <var>B</var>.
+     * It is easy to define an association from <var>B</var> to <var>A</var> since the later exists
+     * at <var>B</var> creation time. But if one wants to define also the converse association, i.e.
+     * from <var>A</var> to <var>B</var>, then it is not possible to create <var>A</var> with the
+     * {@linkplain #DefaultAssociationRole(Map, FeatureType, int, int) above constructor} since
+     * <var>B</var> does not yet exist. We can only give the <em>name</em> of <var>B</var> and
+     * let {@link DefaultFeatureType} substitutes that name by the actual instance when the later
+     * will be known.
+     * </div>
+     *
+     * @param identification The name and other information to be given to this association role.
+     * @param valueType      The name of the type of feature values.
+     * @param minimumOccurs  The minimum number of occurrences of the association within its containing entity.
+     * @param maximumOccurs  The maximum number of occurrences of the association within its containing entity,
+     *                       or {@link Integer#MAX_VALUE} if there is no restriction.
+     */
+    public DefaultAssociationRole(final Map<String,?> identification, final GenericName valueType,
+            final int minimumOccurs, final int maximumOccurs)
+    {
+        super(identification, minimumOccurs, maximumOccurs);
+        ensureNonNull("valueType", valueType);
+        this.valueType = new NamedFeatureType(valueType);
+    }
+
+    /**
+     * If the associated feature type is a placeholder for a {@code FeatureType} to be defined later,
+     * replaces the placeholder by the actual instance if available. Otherwise do nothing.
+     *
+     * This method is needed only in case of cyclic graph, e.g. feature <var>A</var> has an association
+     * to feature <var>B</var> which has an association back to <var>A</var>. It may also be <var>A</var>
+     * having an association to itself, <i>etc.</i>
+     *
+     * @param  creating The feature type in process of being constructed.
+     * @return {@code true} if this association references a resolved feature type after this method call.
+     */
+    final boolean resolve(final DefaultFeatureType creating) {
+        boolean resolved = isResolved;
+        if (!resolved) {
+            FeatureType type = valueType;
+            if (type instanceof NamedFeatureType) {
+                type = search(creating, type.getName());
+                if (type == null) {
+                    return false;
+                }
+                valueType = type;
+            }
+            isResolved = true; // Necessary for avoiding never-ending loop in case of cycle.
+            try {
+                resolved = creating.resolve(type);
+            } finally {
+                isResolved = resolved;
+            }
+        }
+        return resolved;
+    }
+
+    /**
+     * Searches in the given {@code feature} for a feature type of the given name.
+     *
+     * @param  feature The feature in which to search.
+     * @param  name The name of the feature to search.
+     * @return The feature of the given name, or {@code null} if none.
+     */
+    private static FeatureType search(final FeatureType feature, final GenericName name) {
+        if (name.equals(feature.getName())) {
+            return feature;
+        }
+        /*
+         * Search only in associations declared in the given feature, not in inherited associations.
+         * The inherited associations will be checked in a separated loop below if we did not found
+         * the request feature type in explicitly declared associations.
+         */
+        for (final PropertyType property : feature.getProperties(false)) {
+            if (property instanceof FeatureAssociationRole) {
+                final FeatureType valueType;
+                if (property instanceof DefaultAssociationRole) {
+                    valueType = ((DefaultAssociationRole) property).valueType;
+                    if (valueType instanceof NamedFeatureType) {
+                        continue; // Skip unresolved feature types.
+                    }
+                } else {
+                    valueType = ((FeatureAssociationRole) property).getValueType();
+                }
+                if (name.equals(valueType.getName())) {
+                    return valueType;
+                }
+            }
+        }
+        /*
+         * Search in inherited associations as a separated step, in order to include the overridden
+         * associations in the search. Overridden associations have the same association role name,
+         * but not necessarily the same feature type (may be a subtype). This is equivalent to
+         * "covariant return type" in the Java language.
+         */
+        for (FeatureType type : feature.getSuperTypes()) {
+            type = search(type, name);
+            if (type != null) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the type of feature values.
      *
      * @return The type of feature values.
+     * @throws IllegalStateException if the feature type has been specified
+     *         {@linkplain #DefaultAssociationRole(Map, GenericName, int, int) only by its name}
+     *         and not yet resolved.
      */
     @Override
     public final FeatureType getValueType() {
-        return valueType;
+        /*
+         * This method shall be final for consistency with other methods in this classes
+         * which use the 'valueType' field directly. Furthermore, this method is invoked
+         * (indirectly) by DefaultFeatureType constructors.
+         */
+        final FeatureType type = valueType;
+        if (type instanceof NamedFeatureType) {
+            throw new IllegalStateException(Errors.format(Errors.Keys.UnresolvedFeatureName_1, getName()));
+        }
+        return type;
+    }
+
+    /**
+     * Returns the name of the feature type. This information is always available
+     * even when the name has not yet been {@linkplain #resolve resolved}.
+     */
+    static GenericName getValueTypeName(final FeatureAssociationRole role) {
+        return (role instanceof DefaultAssociationRole ? ((DefaultAssociationRole) role).valueType : role.getValueType()).getName();
     }
 
     /**
@@ -198,7 +340,12 @@ public class DefaultAssociationRole exte
      */
     @Override
     public int hashCode() {
-        return super.hashCode() + valueType.hashCode();
+        /*
+         * Do not use the full 'valueType' object for computing hash code,
+         * because it may change before and after 'resolve' is invoked. In
+         * addition, this avoid infinite recursivity in case of cyclic graph.
+         */
+        return super.hashCode() + valueType.getName().hashCode();
     }
 
     /**

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=1633035&r1=1633034&r2=1633035&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] Mon Oct 20 05:37:03 2014
@@ -105,7 +105,7 @@ public class DefaultFeatureType extends 
 
     /**
      * {@code true} if this feature type contains only attributes constrained to the [1 … 1] cardinality,
-     * or operations.
+     * or operations. The feature type shall not contains associations.
      *
      * @see #isSimple()
      */
@@ -118,6 +118,21 @@ public class DefaultFeatureType extends 
     private transient boolean isSparse;
 
     /**
+     * {@code true} if we determined that this feature type does not have, directly or indirectly,
+     * any unresolved name (i.e. a {@link DefaultAssociationRole#valueType} specified only be the
+     * feature type name instead than its actual instance). A value of {@code true} means that all
+     * names have been resolved. However a value of {@code false} only means that we are not sure,
+     * and that {@link #resolve(FeatureType)} should check again.
+     *
+     * <div class="note"><b>Note:</b>
+     * Strictly speaking, this field should be declared {@code volatile} since the names could
+     * be resolved late after construction, after the {@code DefaultFeatureType} instance became
+     * used by different threads. However this is not the intended usage of deferred associations.
+     * </div>
+     */
+    private transient boolean isResolved;
+
+    /**
      * The direct parents of this feature type, or an empty set if none.
      *
      * @see #getSuperTypes()
@@ -211,14 +226,24 @@ public class DefaultFeatureType extends 
         super(identification);
         ArgumentChecks.ensureNonNull("properties", properties);
         this.isAbstract = isAbstract;
-        this.superTypes = (superTypes == null) ? Collections.<FeatureType>emptySet() :
-                          CollectionsExt.<FeatureType>immutableSet(true, superTypes);
+        if (superTypes == null) {
+            this.superTypes = Collections.emptySet();
+        } else {
+            this.superTypes = CollectionsExt.immutableSet(true, superTypes);
+            for (final FeatureType type : this.superTypes) {
+                if (type instanceof NamedFeatureType) {
+                    // Hierarchy of feature types can not be cyclic.
+                    throw new IllegalArgumentException(Errors.format(Errors.Keys.UnresolvedFeatureName_1, type.getName()));
+                }
+            }
+        }
         switch (properties.length) {
             case 0:  this.properties = Collections.emptyList(); break;
             case 1:  this.properties = Collections.singletonList(properties[0]); break;
             default: this.properties = UnmodifiableArrayList.wrap(Arrays.copyOf(properties, properties.length, PropertyType[].class)); break;
         }
         computeTransientFields();
+        isResolved = resolve(this, isSimple);
     }
 
     /**
@@ -240,10 +265,11 @@ public class DefaultFeatureType extends 
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
         computeTransientFields();
+        isResolved = isSimple; // Conservative value. The 'resolve' method will compute a more accurate value if needed.
     }
 
     /**
-     * Computes all transient fields ({@link #assignableTo}, {@link #byName}, {@link #indices}, {@link #isSimple}).
+     * Computes transient fields ({@link #assignableTo}, {@link #byName}, {@link #indices}, {@link #isSimple}).
      *
      * <p>As a side effect, this method checks for missing or duplicated names.</p>
      *
@@ -254,9 +280,9 @@ public class DefaultFeatureType extends 
         byName       = new LinkedHashMap<>(capacity);
         indices      = new LinkedHashMap<>(capacity);
         assignableTo = new HashSet<>(4);
-        assignableTo.add(getName());
+        assignableTo.add(super.getName());
         scanPropertiesFrom(this);
-        byName        = compact(byName);
+        byName        = CollectionsExt.compact(byName);
         assignableTo  = CollectionsExt.unmodifiableOrCopy(assignableTo);
         allProperties = byName.values();
         if (byName instanceof HashMap<?,?>) {
@@ -291,7 +317,7 @@ public class DefaultFeatureType extends 
                 }
             }
         }
-        indices = compact(indices);
+        indices = CollectionsExt.compact(indices);
         /*
          * Rational for choosing whether the feature is sparse: By default, java.util.HashMap implementation creates
          * an internal array of length 16 (see HashMap.DEFAULT_INITIAL_CAPACITY).  In addition, the HashMap instance
@@ -306,25 +332,15 @@ public class DefaultFeatureType extends 
     }
 
     /**
-     * Returns a more compact representation of the given map. This method is similar to
-     * {@link CollectionsExt#unmodifiableOrCopy(Map)}, except that it does not wrap the
-     * map in an unmodifiable view. The intend is to avoid one level of indirection for
-     * performance and memory reasons (keeping in mind that we will have lot of features).
-     * This is okay if we guaranteed that the map does not escape outside this class.
-     */
-    private static <K,V> Map<K,V> compact(final Map<K,V> map) {
-        switch (map.size()) {
-            case 0:  return Collections.emptyMap();
-            case 1:  final Map.Entry<K,V> entry = map.entrySet().iterator().next();
-                     return Collections.singletonMap(entry.getKey(), entry.getValue());
-            default: return map;
-        }
-    }
-
-    /**
      * 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.
+     *
+     * <p>{@code this} shall be the instance in process of being created, not any other instance
+     * (i.e. recursive method invocations are performed on the same {@code this} instance).</p>
+     *
+     * @param  source The feature from which to get properties.
+     * @throws IllegalArgumentException if two properties have the same name.
      */
     private void scanPropertiesFrom(final FeatureType source) {
         for (final FeatureType parent : source.getSuperTypes()) {
@@ -369,6 +385,8 @@ public class DefaultFeatureType extends 
      * Returns the string representation of the given name, making sure that the name is non-null
      * and the string non-empty. This method is used for checking argument validity.
      *
+     * <p>{@code this} shall be the instance in process of being created, not any other instance.</p>
+     *
      * @param name   The name for which to get the string representation.
      * @param source The feature which contains the property (typically {@code this}).
      * @param index  Index of the property having the given name.
@@ -390,6 +408,57 @@ public class DefaultFeatureType extends 
                 b.append("properties[").append(index).append("].name").toString()));
     }
 
+    /**
+     * If an associated feature type is a placeholder for a {@code FeatureType} to be defined later,
+     * replaces the placeholder by the actual instance if available. Otherwise do nothing.
+     *
+     * <p>This method is needed only in case of cyclic graph, e.g. feature <var>A</var> has an association
+     * to feature <var>B</var> which has an association back to <var>A</var>. It may also be <var>A</var>
+     * having an association to itself, <i>etc.</i></p>
+     *
+     * <p>{@code this} shall be the instance in process of being created, not other instance
+     * (i.e. recursive method invocations are performed on the same {@code this} instance).</p>
+     *
+     * @param  feature The feature type for which to resolve the properties.
+     * @return {@code true} if all names have been resolved.
+     */
+    final boolean resolve(final FeatureType feature) {
+        /*
+         * The isResolved field is used only as a cache for skipping completely the DefaultFeatureType instance if
+         * we have determined that there is no unresolved name.  If the given argument is not a DefaultFeatureType
+         * instance, conservatively assumes 'isSimple'. It may cause more calculation than needed, but should not
+         * change the result.
+         */
+        if (feature instanceof DefaultFeatureType) {
+            final DefaultFeatureType dt = (DefaultFeatureType) feature;
+            return dt.isResolved = resolve(feature, dt.isResolved);
+        } else {
+            return resolve(feature, feature.isSimple());
+        }
+    }
+
+    /**
+     * Implementation of {@link #resolve(FeatureType)}, also to be invoked from the constructor.
+     *
+     * @param  feature  The feature type for which to resolve the properties.
+     * @param  resolved {@code true} if we already know that all names are resolved.
+     * @return {@code true} if all names have been resolved.
+     */
+    private boolean resolve(final FeatureType feature, boolean resolved) {
+        if (!resolved) {
+            resolved = true;
+            for (final FeatureType type : feature.getSuperTypes()) {
+                resolved &= resolve(type);
+            }
+            for (final PropertyType property : feature.getProperties(false)) {
+                if (property instanceof DefaultAssociationRole) {
+                    resolved &= ((DefaultAssociationRole) property).resolve(this);
+                }
+            }
+        }
+        return resolved;
+    }
+
 
     // -------- END OF CONSTRUCTORS ------------------------------------------------------------------------------
 
@@ -415,7 +484,8 @@ public class DefaultFeatureType extends 
 
     /**
      * Returns {@code true} if this feature type contains only attributes constrained to the [1 … 1] cardinality,
-     * or operations. Such feature types can be handled as a {@link org.opengis.util.Record}s.
+     * or operations (no feature association).
+     * Such feature types can be handled as a {@linkplain org.apache.sis.util.iso.DefaultRecord records}.
      *
      * @return {@code true} if this feature type contains only simple attributes or operations.
      */
@@ -542,10 +612,16 @@ public class DefaultFeatureType extends 
      * if we compare {@code FeatureType} to {@link Class} in the Java language, then this method is equivalent
      * to {@link Class#getSuperclass()} except that feature types allow multi-inheritance.</div>
      *
+     * <div class="note"><b>Note for subclasses:</b>
+     * this method is final because it is invoked (indirectly) by constructors, and invoking a user-overrideable
+     * method at construction time is not recommended. Furthermore, many Apache SIS methods need guarantees about
+     * the stability of this collection.
+     * </div>
+     *
      * @return The parents of this feature type, or an empty set if none.
      */
     @Override
-    public Set<FeatureType> getSuperTypes() {
+    public final Set<FeatureType> getSuperTypes() {
         return superTypes;
     }
 
@@ -555,13 +631,19 @@ public class DefaultFeatureType extends 
      * inherited from the {@linkplain #getSuperTypes() super-types} only if {@code includeSuperTypes}
      * is {@code true}.
      *
+     * <div class="note"><b>Note for subclasses:</b>
+     * this method is final because it is invoked (indirectly) by constructors, and invoking a user-overrideable
+     * method at construction time is not recommended. Furthermore, many Apache SIS methods need guarantees about
+     * the stability of this collection.
+     * </div>
+     *
      * @param  includeSuperTypes {@code true} for including the properties inherited from the super-types,
      *         or {@code false} for returning only the properties defined explicitely in this type.
      * @return Feature operation, attribute type and association role that carries characteristics of this
      *         feature type (not including parent types).
      */
     @Override
-    public Collection<PropertyType> getProperties(final boolean includeSuperTypes) {
+    public final Collection<PropertyType> getProperties(final boolean includeSuperTypes) {
         return includeSuperTypes ? allProperties : properties;
     }
 

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=1633035&r1=1633034&r2=1633035&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] Mon Oct 20 05:37:03 2014
@@ -236,7 +236,7 @@ header: for (int i=0; ; i++) {
                 final FeatureAssociationRole pt = (FeatureAssociationRole) propertyType;
                 minimumOccurs = pt.getMinimumOccurs();
                 maximumOccurs = pt.getMaximumOccurs();
-                valueType     = toString(pt.getValueType().getName());
+                valueType     = toString(DefaultAssociationRole.getValueTypeName(pt));
                 valueClass    = Feature.class;
             } else if (propertyType instanceof Operation) {
                 final IdentifiedType resultType = ((Operation) propertyType).getResult();

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=1633035&r1=1633034&r2=1633035&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] Mon Oct 20 05:37:03 2014
@@ -16,11 +16,18 @@
  */
 package org.apache.sis.feature;
 
+import java.util.Map;
+import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.util.GenericName;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static java.util.Collections.singletonMap;
+import static org.apache.sis.feature.DefaultAssociationRole.NAME_KEY;
+import static org.apache.sis.test.TestUtilities.getSingleton;
 import static org.apache.sis.test.Assert.*;
 
 // Branch-dependent imports
@@ -40,27 +47,54 @@ public final strictfp class DefaultAssoc
     /**
      * 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.
+     *
+     * @param cyclic {@code true} if in addition to the association from <var>A</var> to <var>B</var>,
+     *        we also want an association from <var>B</var> to <var>A</var>, thus creating a cycle.
      */
-    static DefaultAssociationRole twinTown() {
-        return new DefaultAssociationRole(singletonMap(DefaultAssociationRole.NAME_KEY, "twin town"),
-                DefaultFeatureTypeTest.city(), 0, 1);
+    static DefaultAssociationRole twinTown(final boolean cyclic) {
+        final Map<String,?> properties = singletonMap(NAME_KEY, "twin town");
+        if (cyclic) {
+            final GenericName valueType = DefaultFactories.SIS_NAMES.createTypeName(null, "Twin town");
+            return new DefaultAssociationRole(properties, valueType, 0, 1);
+        } else {
+            final DefaultFeatureType valueType = DefaultFeatureTypeTest.city();
+            return new DefaultAssociationRole(properties, valueType, 0, 1);
+        }
     }
 
     /**
      * Returns a City feature type which may have a twin town.
+     *
+     * @param cyclic {@code true} if in addition to the association from <var>A</var> to <var>B</var>,
+     *        we also want an association from <var>B</var> to <var>A</var>, thus creating a cycle.
      */
-    static DefaultFeatureType twinTownCity() {
-        final DefaultAssociationRole twinTown = twinTown();
-        return new DefaultFeatureType(singletonMap(DefaultFeatureType.NAME_KEY, "Twin town"), false,
-                new FeatureType[] {twinTown.getValueType()}, twinTown);
+    static DefaultFeatureType twinTownCity(final boolean cyclic) {
+        final DefaultAssociationRole twinTown = twinTown(cyclic);
+        final FeatureType parent = cyclic ? DefaultFeatureTypeTest.city() : twinTown.getValueType();
+        return createType("Twin town", parent, twinTown);
+    }
+
+    /**
+     * Convenience method creating a feature type of the given name, parent and property.
+     *
+     * @param name     The name as either a {@link String} or a {@link GenericName}.
+     * @param parent   A feature type created by {@link DefaultFeatureTypeTest#city()}, or {@code null}.
+     * @param property The association to an other feature.
+     */
+    private static DefaultFeatureType createType(final Object name,
+            final FeatureType parent, final FeatureAssociationRole... property)
+    {
+        return new DefaultFeatureType(singletonMap(NAME_KEY, name),
+                false, new FeatureType[] {parent}, property);
     }
 
     /**
      * Tests serialization of an {@link DefaultAssociationRole} instance.
+     * This will also indirectly tests {@link DefaultAssociationRole#equals(Object)}.
      */
     @Test
     public void testSerialization() {
-        assertSerializedEquals(twinTown());
+        assertSerializedEquals(twinTown(false));
     }
 
     /**
@@ -68,7 +102,7 @@ public final strictfp class DefaultAssoc
      */
     @Test
     public void testGetTitleProperty() {
-        final DefaultAssociationRole twinTown = twinTown();
+        final DefaultAssociationRole twinTown = twinTown(false);
         assertEquals("city", DefaultAssociationRole.getTitleProperty(twinTown));
     }
 
@@ -77,7 +111,70 @@ public final strictfp class DefaultAssoc
      */
     @Test
     public void testToString() {
-        final DefaultAssociationRole twinTown = twinTown();
+        final DefaultAssociationRole twinTown = twinTown(false);
         assertEquals("FeatureAssociationRole[“twin town” : City]", twinTown.toString());
     }
+
+    /**
+     * Tests a bidirectional association (a feature having an association to itself).
+     */
+    @Test
+    public void testBidirectionalAssociation() {
+        final DefaultFeatureType twinTown = twinTownCity(true);
+        final FeatureAssociationRole association = (FeatureAssociationRole) twinTown.getProperty("twin town");
+        assertSame("twinTown.property(“twin town”).valueType", twinTown, association.getValueType());
+        /*
+         * Creates a FeatureType copy containing the same properties. Used for verifying
+         * that 'DefaultFeatureType.equals(Object)' does not fall in an infinite loop.
+         */
+        final DefaultFeatureType copy = createType(twinTown.getName(),
+                getSingleton(twinTown.getSuperTypes()), association);
+
+        assertTrue("equals", copy.equals(twinTown));
+        assertTrue("equals", twinTown.equals(copy));
+        assertEquals("hashCode", copy.hashCode(), twinTown.hashCode());
+    }
+
+    /**
+     * Tests {@link DefaultFeatureType#isAssignableFrom(FeatureType)} and {@link DefaultFeatureType#equals(Object)}
+     * on a feature type having a bidirectional association to an other feature. This test will fall in an infinite
+     * loop if the implementation does not have proper guard against infinite recursivity.
+     */
+    @Test
+    @DependsOnMethod("testBidirectionalAssociation")
+    public void testCyclicAssociation() {
+        final GenericName nameOfA = DefaultFactories.SIS_NAMES.createTypeName(null, "A");
+        final GenericName nameOfB = DefaultFactories.SIS_NAMES.createTypeName(null, "B");
+        final GenericName nameOfC = DefaultFactories.SIS_NAMES.createTypeName(null, "C");
+        final GenericName nameOfD = DefaultFactories.SIS_NAMES.createTypeName(null, "D");
+
+        final DefaultAssociationRole toB = new DefaultAssociationRole(singletonMap(NAME_KEY, "toB"), nameOfB, 1, 1);
+        final DefaultAssociationRole toC = new DefaultAssociationRole(singletonMap(NAME_KEY, "toC"), nameOfC, 1, 1);
+        final DefaultAssociationRole toD = new DefaultAssociationRole(singletonMap(NAME_KEY, "toD"), nameOfD, 1, 1);
+        final DefaultFeatureType   typeA = createType(nameOfA, null, toB);
+        final DefaultFeatureType   typeB = createType(nameOfB, null, toC);
+        final DefaultFeatureType   typeC = createType(nameOfC, null, toD);
+
+        final DefaultAssociationRole toA = new DefaultAssociationRole(singletonMap(NAME_KEY, "toA"), typeA, 1, 1);
+        final DefaultFeatureType typeD = createType(nameOfD, null, toA, toB, toC, toD);
+
+        assertSame("A.properties", toB, getSingleton(typeA.getProperties(false)));
+        assertSame("B.properties", toC, getSingleton(typeB.getProperties(false)));
+        assertSame("C.properties", toD, getSingleton(typeC.getProperties(false)));
+        assertSame("D.properties", toA, typeD.getProperty("toA"));
+        assertSame("D.properties", toB, typeD.getProperty("toB"));
+        assertSame("D.properties", toC, typeD.getProperty("toC"));
+        assertSame("D.properties", toD, typeD.getProperty("toD"));
+        assertSame("toA", typeA, toA.getValueType());
+//      assertSame("toB", typeB, toB.getValueType());
+//      assertSame("toC", typeC, toC.getValueType());
+        assertSame("toD", typeD, toD.getValueType());
+
+        assertFalse("equals", typeA.equals(typeD));
+        assertFalse("equals", typeD.equals(typeA));
+        assertFalse("equals", typeB.equals(typeC));
+        assertFalse("equals", typeC.equals(typeB));
+        assertFalse("hashCode", typeA.hashCode() == typeB.hashCode());
+        assertFalse("hashCode", typeC.hashCode() == typeD.hashCode());
+    }
 }

Modified: sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/FeatureTestCase.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -63,7 +63,7 @@ public abstract strictfp class FeatureTe
      * Creates a feature for twin towns.
      */
     static AbstractFeature twinTown(final boolean isSparse) {
-        final DefaultFeatureType twinTown = DefaultAssociationRoleTest.twinTownCity();
+        final DefaultFeatureType twinTown = DefaultAssociationRoleTest.twinTownCity(false);
 
         final AbstractFeature leMans = isSparse ? new SparseFeature(twinTown) : new DenseFeature(twinTown);
         leMans.setPropertyValue("city", "Le Mans");

Modified: sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-feature/src/test/java/org/apache/sis/feature/SingletonAssociationTest.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -51,7 +51,7 @@ public final strictfp class SingletonAss
         final Feature twinTown = DefaultFeatureTypeTest.city().newInstance();
         twinTown.setPropertyValue("city", "Le Mans");
         twinTown.setPropertyValue("population", 143240); // In 2011.
-        final AbstractAssociation association = new SingletonAssociation(DefaultAssociationRoleTest.twinTown());
+        final AbstractAssociation association = new SingletonAssociation(DefaultAssociationRoleTest.twinTown(false));
         association.setValue(twinTown);
         return association;
     }

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/AbstractMetadata.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -144,7 +144,7 @@ public abstract class AbstractMetadata i
      */
     @Override
     public boolean isEmpty() {
-        return Pruner.isEmpty(this, false);
+        return Pruner.isEmpty(this, true, false);
     }
 
     /**
@@ -155,7 +155,7 @@ public abstract class AbstractMetadata i
      * @throws UnmodifiableMetadataException If this metadata is not modifiable.
      */
     public void prune() {
-        Pruner.isEmpty(this, true);
+        Pruner.isEmpty(this, true, true);
     }
 
     /**

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/Pruner.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -57,8 +57,14 @@ final class Pruner {
      * Returns the metadata properties. When used for pruning empty values, the map needs to
      * include empty (but non-null) values in order to allow us to set them to {@code null}.
      */
-    private static Map<String, Object> asMap(final MetadataStandard standard, final Object metadata, final boolean prune) {
-        return standard.asValueMap(metadata, KeyNamePolicy.JAVABEANS_PROPERTY, prune ? NON_NULL : NON_EMPTY);
+    private static Map<String, Object> asMap(final MetadataStandard standard, final Object metadata,
+            final boolean mandatory, final boolean prune)
+    {
+        final PropertyAccessor accessor = standard.getAccessor(metadata.getClass(), mandatory);
+        if (accessor != null) {
+            return new ValueMap(metadata, accessor, KeyNamePolicy.JAVABEANS_PROPERTY, prune ? NON_NULL : NON_EMPTY);
+        }
+        return null;
     }
 
     /**
@@ -78,12 +84,16 @@ final class Pruner {
      * It creates a map of visited nodes when the iteration begin, and deletes that map when the
      * iteration ends.</p>
      *
-     * @param  metadata The metadata object.
-     * @param  prune {@code true} for deleting empty entries.
+     * @param  metadata  The metadata object.
+     * @param  mandatory {@code true} if we shall throw an exception if {@code metadata} is not of the expected class.
+     * @param  prune     {@code true} for deleting empty entries.
      * @return {@code true} if all metadata properties are null or empty.
      */
-    static boolean isEmpty(final AbstractMetadata metadata, final boolean prune) {
-        final Map<String,Object> properties = asMap(metadata.getStandard(), metadata, prune);
+    static boolean isEmpty(final AbstractMetadata metadata, final boolean mandatory, final boolean prune) {
+        final Map<String,Object> properties = asMap(metadata.getStandard(), metadata, mandatory, prune);
+        if (properties == null) {
+            return false; // For metadata of unknown class, conservatively assume non-empty.
+        }
         final Map<Object,Boolean> tested = MAPS.get();
         if (!tested.isEmpty()) {
             return isEmpty(properties, tested, prune);
@@ -165,7 +175,7 @@ final class Pruner {
                         } else if (!(element instanceof Enumerated)) {
                             final MetadataStandard standard = MetadataStandard.forClass(element.getClass());
                             if (standard != null) {
-                                isEmptyElement = isEmpty(asMap(standard, element, prune), tested, prune);
+                                isEmptyElement = isEmpty(asMap(standard, element, false, prune), tested, prune);
                                 if (!isEmptyElement && element instanceof Emptiable) {
                                     isEmptyElement = ((Emptiable) element).isEmpty();
                                 }

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/DefaultMetadata.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -260,8 +260,7 @@ public class DefaultMetadata extends ISO
      *
      * @param contact   Party responsible for the metadata information.
      * @param dateStamp Date that the metadata was created.
-     * @param identificationInfo Basic information about the resource
-     *        to which the metadata applies.
+     * @param identificationInfo Basic information about the resource to which the metadata applies.
      */
     public DefaultMetadata(final Responsibility contact,
                            final Date           dateStamp,
@@ -542,7 +541,7 @@ public class DefaultMetadata extends ISO
     @Override
     @Deprecated
     @XmlElement(name = "characterSet")
-    public final Charset getCharacterSet()  {
+    public final Charset getCharacterSet() {
         return LegacyPropertyAdapter.getSingleton(characterSets, Charset.class, null, DefaultMetadata.class, "getCharacterSet");
     }
 
@@ -646,40 +645,6 @@ public class DefaultMetadata extends ISO
     }
 
     /**
-     * A specialization of {@link LegacyPropertyAdapter} which will try to merge the
-     * {@code "hierarchyLevel"} and {@code "hierarchyLevelName"} properties in the same
-     * {@link DefaultMetadataScope} instance.
-     */
-    private static abstract class ScopeAdapter<L> extends LegacyPropertyAdapter<L,MetadataScope> {
-        /**
-         * @param scopes Value of {@link DefaultMetadata#getMetadataScopes()}.
-         */
-        ScopeAdapter(final Collection<MetadataScope> scopes)  {
-            super(scopes);
-        }
-
-        /**
-         * Invoked (indirectly) by JAXB when adding a new scope code or scope name. This implementation searches
-         * for an existing {@link MetadataScope} instance with a free slot for the new value before to create a
-         * new {@link DefaultMetadataScope} instance.
-         */
-        @Override
-        public boolean add(final L newValue) {
-            final Iterator<MetadataScope> it = elements.iterator();
-            if (it.hasNext()) {
-                MetadataScope scope = it.next();
-                if (unwrap(scope) == null) {
-                    if (!(scope instanceof DefaultMetadataScope)) {
-                        scope = new DefaultMetadataScope(scope);
-                    }
-                    return update(scope, newValue);
-                }
-            }
-            return super.add(newValue);
-        }
-    }
-
-    /**
      * Returns the scope to which the metadata applies.
      *
      * @return Scope to which the metadata applies.
@@ -691,7 +656,7 @@ public class DefaultMetadata extends ISO
     @Deprecated
     @XmlElement(name = "hierarchyLevel")
     public final Collection<ScopeCode> getHierarchyLevels() {
-        return new ScopeAdapter<ScopeCode>(getMetadataScopes()) {
+        return new MetadataScopeAdapter<ScopeCode>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
             @Override protected MetadataScope wrap(final ScopeCode value) {
                 return new DefaultMetadataScope(value);
@@ -739,7 +704,7 @@ public class DefaultMetadata extends ISO
     @Deprecated
     @XmlElement(name = "hierarchyLevelName")
     public final Collection<String> getHierarchyLevelNames() {
-        return new ScopeAdapter<String>(getMetadataScopes()) {
+        return new MetadataScopeAdapter<String>(getMetadataScopes()) {
             /** Stores a legacy value into the new kind of value. */
             @Override protected MetadataScope wrap(final String value) {
                 final DefaultMetadataScope scope = new DefaultMetadataScope();
@@ -756,7 +721,7 @@ public class DefaultMetadata extends ISO
             /** Updates the legacy value in an existing instance of the new kind of value. */
             @Override protected boolean update(final MetadataScope container, final String value) {
                 if (container instanceof DefaultMetadataScope) {
-                    ((DefaultMetadataScope) container).setName(new SimpleInternationalString(value));
+                    ((DefaultMetadataScope) container).setName(value != null ? new SimpleInternationalString(value) : null);
                     return true;
                 }
                 return false;

Modified: sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/maintenance/DefaultScopeDescription.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -298,11 +298,11 @@ public class DefaultScopeDescription ext
      * “<cite>Administrative area A — Road network</cite>” description.
      * </div>
      *
-     * @return Feature types to which the information applies.
-     *
      * {@section Conditions}
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
+     *
+     * @return Feature types to which the information applies.
      */
     @Override
     public Set<CharSequence> getFeatures() {
@@ -331,11 +331,11 @@ public class DefaultScopeDescription ext
      * “<cite>Administrative area A — Overhead clearance</cite>” description.
      * </div>
      *
-     * @return Attribute types to which the information applies.
-     *
      * {@section Conditions}
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
+     *
+     * @return Attribute types to which the information applies.
      */
     @Override
     public Set<CharSequence> getAttributes() {
@@ -364,11 +364,11 @@ public class DefaultScopeDescription ext
      * “<cite>Administrative area A — New bridge</cite>” description.
      * </div>
      *
-     * @return Feature instances to which the information applies.
-     *
      * {@section Conditions}
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
+     *
+     * @return Feature instances to which the information applies.
      */
     @Override
     public Set<CharSequence> getFeatureInstances() {
@@ -397,11 +397,11 @@ public class DefaultScopeDescription ext
      * “<cite>Administrative area A — New bridge — Overhead clearance</cite>” description.
      * </div>
      *
-     * @return Attribute instances to which the information applies.
-     *
      * {@section Conditions}
      * This method returns a modifiable collection only if no other property is set.
      * Otherwise, this method returns an unmodifiable empty collection.
+     *
+     * @return Attribute instances to which the information applies.
      */
     @Override
     public Set<CharSequence> getAttributeInstances() {

Modified: sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-metadata/src/test/java/org/apache/sis/metadata/iso/AllMetadataTest.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -239,7 +239,7 @@ public final strictfp class AllMetadataT
         } else if (name.equals("stepDateTime")) {
             name = "dateTime";
         } else if (name.equals("defaultLocale+otherLocale") ||
-                   type == FeatureCatalogueDescription.class && name.equals("locale"))
+                type == FeatureCatalogueDescription.class && name.equals("locale"))
         {
             name = "language";
         }

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -447,6 +447,28 @@ public final class CollectionsExt extend
     }
 
     /**
+     * Returns a more compact representation of the given map. This method is similar to
+     * {@link #unmodifiableOrCopy(Map)} except that it does not wrap the map in an unmodifiable
+     * view. The intend is to avoid one level of indirection for performance and memory reasons.
+     * This is okay only if the map is kept in a private field and never escape outside this class.
+     *
+     * @param  <K> The type of keys in the map.
+     * @param  <V> The type of values in the map.
+     * @param  map The map to compact, or {@code null}.
+     * @return A potentially compacted map, or {@code null} if the given map was null.
+     */
+    public static <K,V> Map<K,V> compact(final Map<K,V> map) {
+        if (map != null) {
+            switch (map.size()) {
+                case 0:  return Collections.emptyMap();
+                case 1:  final Map.Entry<K,V> entry = map.entrySet().iterator().next();
+                         return Collections.singletonMap(entry.getKey(), entry.getValue());
+            }
+        }
+        return map;
+    }
+
+    /**
      * Returns a snapshot of the given list. The returned list will not be affected by changes
      * in the given list after this method call. This method makes no guaranteed about whether
      * the returned list is modifiable or not.

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -848,6 +848,11 @@ public final class Errors extends Indexe
         public static final short UnparsableStringForClass_3 = 125;
 
         /**
+         * Feature named “{0}” has not yet been resolved.
+         */
+        public static final short UnresolvedFeatureName_1 = 164;
+
+        /**
          * No format is specified for objects of class ‘{0}’.
          */
         public static final short UnspecifiedFormatForClass_1 = 126;

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Mon Oct 20 05:37:03 2014
@@ -181,6 +181,7 @@ UnmodifiableMetadata              = This
 UnmodifiableObject_1              = Object \u2018{0}\u2019 is unmodifiable.
 UnparsableStringForClass_2        = Text \u201c{1}\u201d can not be parsed as an object of type \u2018{0}\u2019.
 UnparsableStringForClass_3        = Text \u201c{1}\u201d can not be parsed as an object of type \u2018{0}\u2019, because of the \u201c{2}\u201d characters.
+UnresolvedFeatureName_1           = Feature named \u201c{0}\u201d has not yet been resolved.
 UnspecifiedFormatForClass_1       = No format is specified for objects of class \u2018{0}\u2019.
 UnsupportedImplementation_1       = Can not handle instances of \u2018{0}\u2019 because arbitrary implementations are not yet supported.
 UnsupportedOperation_1            = The \u2018{0}\u2019 operation is unsupported.

Modified: sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Mon Oct 20 05:37:03 2014
@@ -171,6 +171,7 @@ UnmodifiableObject_1              = L\u2
 UnspecifiedFormatForClass_1       = Aucun format n\u2019est sp\u00e9cifi\u00e9 pour les objets de classe \u2018{0}\u2019.
 UnparsableStringForClass_2        = Le texte \u00ab\u202f{1}\u202f\u00bb n\u2019est pas reconnu comme un objet de type \u2018{0}\u2019.
 UnparsableStringForClass_3        = Le texte \u00ab\u202f{1}\u202f\u00bb n\u2019est pas reconnu comme un objet de type \u2018{0}\u2019, \u00e0 cause des caract\u00e8res \u00ab\u202f{2}\u202f\u00bb.
+UnresolvedFeatureName_1           = L\u2019entit\u00e9 nomm\u00e9e \u00ab\u202f{0}\u202f\u00bb n\u2019a pas encore \u00e9t\u00e9 r\u00e9solue.
 UnsupportedImplementation_1       = Les instances de \u2018{0}\u2019 ne peuvent pas \u00eatre g\u00e9r\u00e9es parce que les impl\u00e9mentations arbitraires ne sont pas encore support\u00e9es.
 UnsupportedOperation_1            = L\u2019op\u00e9ration \u2018{0}\u2019 n\u2019est pas support\u00e9e.
 UnsupportedType_1                 = Le type \u2018{0}\u2019 n\u2019est pas support\u00e9.

Modified: sis/branches/JDK7/pom.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/pom.xml?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/pom.xml (original)
+++ sis/branches/JDK7/pom.xml Mon Oct 20 05:37:03 2014
@@ -28,7 +28,7 @@
   <parent>
     <groupId>org.apache</groupId>
     <artifactId>apache</artifactId>
-    <version>14</version> <!-- See the "TODO" in this file. -->
+    <version>15</version>
   </parent>
 
 
@@ -449,7 +449,6 @@ Apache SIS is a free software, Java lang
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
-        <version>2.17</version> <!-- TODO: Remove after parent pom.xml has upgraded. -->
         <configuration>
           <includes>
             <include>**/*TestSuite.java</include>

Modified: sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/CodePage.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/CodePage.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/CodePage.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/CodePage.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -23,8 +23,8 @@ package org.apache.sis.storage.shapefile
  * when dealing with codepages, should first check if there is a .cpg file
  *
  * @author  Travis L. Pinney
- * @since   0.4
- * @version 0.4
+ * @since   0.5
+ * @version 0.5
  * @module
  *
  * @see <a href="http://www.clicketyclick.dk/databases/xbase/format/dbf.html#DBF_NOTE_5_TARGET">Xbase Data file</a>

Modified: sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/DataType.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/DataType.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/DataType.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/DataType.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -21,40 +21,79 @@ package org.apache.sis.storage.shapefile
  * Provides a simple DataType class.
  *
  * @author  Travis L. Pinney
- * @since   0.4
- * @version 0.4
+ * @since   0.5
+ * @version 0.5
  * @module
  *
  * @see <a href="http://www.clicketyclick.dk/databases/xbase/format/data_types.html">Xbase Data Types</a>
  */
 public enum DataType {
+    /** Character (less than 254 characters). */
+    Character('C'),
+
+    /** Number (less than 18 characters, can include sign and decimal). */
+    Number('N'),
+
+    /** Logical (3 way, ? Y,y,T,t  N,n,F,f). */
+    Logical('L'),
+
+    /** Date (YYYYMMDD format). */
+    Date('D'),
+
+    /** Memo (Pointer to ASCII text field). */
+    Memo('M'),
+
+    /** Floating point (20 digits). */
+    FloatingPoint('F'),
 
-    Character('C'), // < 254 characters
-    Number('N'), // < 18 characters, can include sign and decimal
-    Logical('L'), // 3 way, ? Y,y,T,t  N,n,F,f
-    Date('D'), // YYYYMMDD
-    Memo('M'), // Pointer to ASCII text field
-    FloatingPoint('F'), // 20 digits
     // CharacterNameVariable("?"),  //1-254 Characters
-    Picture('P'), // Memo
-    Currency('Y'), // Foxpro
-    DateTime('T'), // 32 bit little-endian Julian date, 32 byte little endian milliseconds since midnight
-    Integer('I'), // 4 byte little endian
-    VariField('V'), // ???
-    Variant('X'), // ???
-    TimeStamp('@'), // see url
-    Double('O'), //
-    AutoIncrement('+'); // ???
 
+    /** Picture (memo). */
+    Picture('P'),
+
+    /** Currency (Foxpro). */
+    Currency('Y'),
+
+    /** Date time (32 bit little-endian Julian date, 32 byte little endian milliseconds since midnight). */
+    DateTime('T'),
+
+    /** Integer (4 byte little endian). */
+    Integer('I'),
+
+    /** Varifield (???). */
+    VariField('V'),
+
+    /** Variant (???). */
+    Variant('X'),
+
+    /** Time stamp (see url). */
+    TimeStamp('@'),
+
+    /** Double. */
+    Double('O'),
+
+    /** Auto increment. */
+    AutoIncrement('+');
+
+    /** Data type. */
     public final char datatype;
 
-    DataType(char datatype) {
-        this.datatype = datatype;
+    /**
+     * Construct a datatype.
+     * @param type Data type.
+     */
+    DataType(char type) {
+        this.datatype = type;
     }
 
-    public static DataType valueOfDataType(char datatype) {
+    /**
+     * Return the Datatype enum of a code.
+     * @param code Character code describing the dbf datatype.
+     * @return Datatype.
+     */
+    public static DataType valueOfDataType(char code) {
         for (DataType v : values()) {
-            if (v.datatype == datatype) {
+            if (v.datatype == code) {
                 return v;
             }
         }

Modified: sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/FieldDescriptor.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/FieldDescriptor.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/FieldDescriptor.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/FieldDescriptor.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -18,26 +18,43 @@ package org.apache.sis.storage.shapefile
 
 
 /**
+ * Field descriptor.
  *
  * @author  Travis L. Pinney
- * @since   0.4
- * @version 0.4
+ * @since   0.5
+ * @version 0.5
  * @module
  */
 public class FieldDescriptor {
-
+    /** Field name. */
     public byte[] FieldName = new byte[11];
+
+    /** Field type. */
     public DataType FieldType;
+
     public byte[] FieldAddress = new byte[4];
+
+    /** Field length. */
     public byte FieldLength;
+
+    /** Decimal count. */
     public byte FieldDecimalCount;
+
+    /** Reserved 2. */
     public byte[] DbasePlusLanReserved2 = new byte[2];
+
+    /** Work area id. */
     public byte WorkAreaID;
-    public byte[] DbasePlusLanReserved3 = new byte[2];
-    public byte SetFields;
 
+    /** Reserved 3. */
+    public byte[] DbasePlusLanReserved3 = new byte[2];
 
+    public byte SetFields;
 
+    /**
+     * Return the field name.
+     * @return Field name.
+     */
     public String getName() {
         int length = FieldName.length;
         while (length != 0 && FieldName[length - 1] <= ' ') {
@@ -46,10 +63,17 @@ public class FieldDescriptor {
         return new String(this.FieldName, 0, length);
     }
 
+    /**
+     * Return the field length.
+     * @return field length.
+     */
     public int getLength() {
         return this.FieldLength & 0xFF;
     }
 
+    /**
+     * @see java.lang.Object#toString()
+     */
     @Override
     public String toString() {
 

Modified: sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeFile.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeFile.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeFile.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeFile.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -21,9 +21,10 @@ import java.nio.ByteOrder;
 import java.nio.MappedByteBuffer;
 import java.nio.channels.FileChannel;
 import java.io.FileInputStream;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
+import java.text.MessageFormat;
 import com.esri.core.geometry.Point;
 import com.esri.core.geometry.Polygon;
 import com.esri.core.geometry.Polyline;
@@ -36,7 +37,6 @@ import org.apache.sis.storage.DataStoreE
 // Branch-dependent imports
 import org.opengis.feature.Feature;
 
-
 /**
  * Provides a ShapeFile Reader.
  *
@@ -49,232 +49,285 @@ import org.opengis.feature.Feature;
  * @see <a href="http://ulisse.elettra.trieste.it/services/doc/dbase/DBFstruct.htm">dBASE III File Structure</a>
  */
 public class ShapeFile {
+    /** Name of the Geometry field. */
     private static final String GEOMETRY_NAME = "geometry";
 
-    public int FileCode;   // big
-    public int FileLength;  // big // The value for file length is the total length of the file in 16-bit words
+    /** File code. */
+    public int FileCode; // big
+
+    /** File length. */
+    public int FileLength; // big // The value for file length is the total length of the file in 16-bit words
+
+    /** File version. */
     public int Version; // little
+
+    /** Shapefile type. */
     public ShapeTypeEnum ShapeType; // little
+
+    /** X Min. */
     public double xmin; // little
+
+    /** Y Min. */
     public double ymin; // little
+
+    /** X Max. */
     public double xmax; // little
+
+    /** Y Max. */
     public double ymax; // little
+
+    /** Z Min. */
     public double zmin; // little
+
+    /** Z Max. */
     public double zmax; // little
+
+    /** M Min. */
     public double mmin; // little
+
+    /** M Max. */
     public double mmax; // little
 
+    /** Underlying databasefile content. */
+    private Database dbf;
 
-    // http://ulisse.elettra.trieste.it/services/doc/dbase/DBFstruct.htm
-    public byte DbaseVersion;
-    public byte[] DbaseLastUpdate = new byte[3];
-    public int FeatureCount;
-    public short DbaseHeaderBytes;
-    public short DbaseRecordBytes;
-    // reserve 3 bytes
-    public byte[] DbasePlusLanReserved = new byte[13];
-    // reserve 4 bytes
+    /** Features existing in the shapefile. */
+    public Map<Integer, Feature> FeatureMap = new HashMap<>();
 
-    public ArrayList<FieldDescriptor> FDArray = new ArrayList<FieldDescriptor>();
-    public Map<Integer, Feature> FeatureMap = new HashMap<Integer, Feature>();
+    /**
+     * Construct a Shapefile from a file.
+     * @param shpfile file to read.
+     * @throws IOException if the file cannot be opened.
+     * @throws DataStoreException if the shapefile is not valid.
+     */
+    public ShapeFile(String shpfile) throws IOException, DataStoreException {
+        Objects.requireNonNull(shpfile, "The shapefile to load cannot be null.");
 
+        // Deduct database file name.
+        StringBuilder b = new StringBuilder(shpfile);
+        b.replace(shpfile.length() - 3, shpfile.length(), "dbf");
 
+        dbf = new Database(b.toString());
 
-    public ShapeFile(String shpfile) throws IOException, DataStoreException {
-        FileInputStream fis = new FileInputStream(shpfile);
-        FileChannel fc = fis.getChannel();
-        int fsize = (int) fc.size();
-        MappedByteBuffer rf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fsize);
-
-        this.FileCode = rf.getInt();
-        rf.getInt(); rf.getInt(); rf.getInt(); rf.getInt(); rf.getInt();
-        this.FileLength = rf.getInt() * 2;
-
-        byte [] data = new byte[4];
-
-        rf.order(ByteOrder.LITTLE_ENDIAN);
-        this.Version = rf.getInt();
-        this.ShapeType = ShapeTypeEnum.get(rf.getInt());
-        this.xmin = rf.getDouble();
-        this.ymin = rf.getDouble();
-        this.xmax = rf.getDouble();
-        this.ymax = rf.getDouble();
-        this.zmin = rf.getDouble();
-        this.zmax = rf.getDouble();
-        this.mmin = rf.getDouble();
-        this.mmax = rf.getDouble();
-        rf.order(ByteOrder.BIG_ENDIAN);
+        try (FileInputStream fis = new FileInputStream(shpfile); FileChannel fc = fis.getChannel();) {
+            int fsize = (int) fc.size();
+            MappedByteBuffer rf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fsize);
+
+            this.FileCode = rf.getInt();
+            rf.getInt();
+            rf.getInt();
+            rf.getInt();
+            rf.getInt();
+            rf.getInt();
+            this.FileLength = rf.getInt() * 2;
 
-        StringBuilder b = new StringBuilder(shpfile);
-        b.replace(shpfile.length() - 3, shpfile.length() , "dbf");
-        String file_base = b.toString();
+            rf.order(ByteOrder.LITTLE_ENDIAN);
+            this.Version = rf.getInt();
+            this.ShapeType = ShapeTypeEnum.get(rf.getInt());
+            this.xmin = rf.getDouble();
+            this.ymin = rf.getDouble();
+            this.xmax = rf.getDouble();
+            this.ymax = rf.getDouble();
+            this.zmin = rf.getDouble();
+            this.zmax = rf.getDouble();
+            this.mmin = rf.getDouble();
+            this.mmax = rf.getDouble();
+            rf.order(ByteOrder.BIG_ENDIAN);
 
-        FileInputStream fis2 = new FileInputStream(file_base);
-        FileChannel fc2 = fis2.getChannel();
-        int fsize2 = (int) fc2.size();
-        MappedByteBuffer df = fc2.map(FileChannel.MapMode.READ_ONLY, 0, fsize2);
-
-        this.DbaseVersion = df.get();
-        df.get(this.DbaseLastUpdate);
-
-        df.order(ByteOrder.LITTLE_ENDIAN);
-        this.FeatureCount = df.getInt();
-        this.DbaseHeaderBytes = df.getShort();
-        this.DbaseRecordBytes = df.getShort();
-        df.order(ByteOrder.BIG_ENDIAN);
-        df.getShort(); // reserved
-        df.get(); // reserved
-        df.get(DbasePlusLanReserved);
-        df.getInt();
-
-        while(df.position() <  this.DbaseHeaderBytes - 1) {
-            FieldDescriptor fd = new FieldDescriptor();
-            df.get(fd.FieldName);
-            char dt = (char) df.get();
-            fd.FieldType = DataType.valueOfDataType(dt);
-            df.get(fd.FieldAddress);
-            fd.FieldLength = df.get();
-            fd.FieldDecimalCount = df.get();
-            df.getShort(); // reserved
-            df.get(fd.DbasePlusLanReserved2);
-            fd.WorkAreaID = df.get();
-            df.get(fd.DbasePlusLanReserved3);
-            fd.SetFields = df.get();
-            data = new byte[6];
-            df.get(data); // reserved
+            dbf.loadDescriptor();
+            final DefaultFeatureType featureType = getFeatureType(shpfile);
 
-            this.FDArray.add(fd);
-            // loop until you hit the 0Dh field terminator
+            dbf.getByteBuffer().get(); // should be 0d for field terminator
+            loadFeatures(featureType, rf);
+        } finally {
+            dbf.close();
         }
-        final DefaultFeatureType featureType = getFeatureType(shpfile);
+    }
 
-        df.get(); // should be 0d for field terminator
+    /**
+     * Returns the underlying database file.
+     * @return Underlying database file.
+     */
+    public Database getDatabase() {
+        return this.dbf;
+    }
+
+    /**
+     * Returns the feature count of the shapefile.
+     * @return Feature count.
+     */
+    public int getFeatureCount() {
+        return this.dbf.getRecordCount();
+    }
 
-        for (Integer i = 0; i < this.FeatureCount; i++) {
+    /**
+     * Load the features of a shapefile.
+     * @param featureType Features descriptor.
+     * @param rf byte buffer mapper.
+     * @throws DataStoreException if a validation problem occurs.
+     */
+    private void loadFeatures(DefaultFeatureType featureType, MappedByteBuffer rf) throws DataStoreException {
+        for (Integer i = 0; i < this.dbf.getRecordCount(); i++) {
             // insert points into some type of list
             int RecordNumber = rf.getInt();
+            @SuppressWarnings("unused")
             int ContentLength = rf.getInt();
 
-            data = new byte[4];
             rf.order(ByteOrder.LITTLE_ENDIAN);
-            int ShapeType = rf.getInt();
+            int iShapeType = rf.getInt();
             final Feature f = featureType.newInstance();
 
-            if (ShapeType == ShapeTypeEnum.Point.getValue()) {
-                double x = rf.getDouble();
-                double y = rf.getDouble();
-                Point pnt = new Point(x,y);
-                f.setPropertyValue(GEOMETRY_NAME, pnt);
-
-            } else if (ShapeType == ShapeTypeEnum.Polygon.getValue()) {
-                double xmin = rf.getDouble();
-                double ymin = rf.getDouble();
-                double xmax = rf.getDouble();
-                double ymax = rf.getDouble();
-                int NumParts = rf.getInt();
-                int NumPoints = rf.getInt();
-
-                if (NumParts > 1) {
-                    throw new DataStoreException("Polygons with multiple linear rings have not implemented yet.");
-                }
-
-                // read the one part
-                int Part = rf.getInt();
-                Polygon poly = new Polygon();
-
-                // create a line from the points
-                double xpnt = rf.getDouble();
-                double ypnt = rf.getDouble();
-                //Point oldpnt = new Point(xpnt, ypnt);
-                poly.startPath(xpnt, ypnt);
-                for (int j=0; j < NumPoints-1; j++) {
-                    xpnt = rf.getDouble();
-                    ypnt = rf.getDouble();
-                    poly.lineTo(xpnt, ypnt);
-                }
-                f.setPropertyValue(GEOMETRY_NAME, poly);
-
-            } else if (ShapeType == ShapeTypeEnum.PolyLine.getValue()) {
-                double xmin = rf.getDouble();
-                double ymin = rf.getDouble();
-                double xmax = rf.getDouble();
-                double ymax = rf.getDouble();
-
-                int NumParts = rf.getInt();
-                int NumPoints = rf.getInt();
-
-                int [] NumPartArr = new int[NumParts+1];
-
-                for (int n=0; n < NumParts; n++) {
-                    int idx = rf.getInt();
-                    NumPartArr[n] = idx;
-                }
-                NumPartArr[NumParts] = NumPoints;
-
-                data = new byte[8];
-
-                double xpnt, ypnt;
-                Polyline ply = new Polyline();
-
-                for (int m=0; m < NumParts; m++) {
-                    xpnt = rf.getDouble();
-                    ypnt = rf.getDouble();
-                    ply.startPath(xpnt, ypnt);
-
-                    for (int j=NumPartArr[m]; j < NumPartArr[m+1] - 1; j++) {
-                        xpnt = rf.getDouble();
-                        ypnt = rf.getDouble();
-                        ply.lineTo(xpnt, ypnt);
-                    }
-                }
+            ShapeTypeEnum type = ShapeTypeEnum.get(iShapeType);
 
-                f.setPropertyValue(GEOMETRY_NAME, ply);
+            if (type == null)
+                throw new DataStoreException(MessageFormat.format("The shapefile feature type {0} doesn''t match to any known feature type.", featureType));
 
-            } else {
-                throw new DataStoreException("Unsupported shapefile type: " + this.ShapeType);
+            switch (type) {
+            case Point:
+                loadPointFeature(rf, f);
+                break;
+
+            case Polygon:
+                loadPolygonFeature(rf, f);
+                break;
+
+            case PolyLine:
+                loadPolylineFeature(rf, f);
+                break;
+
+            default:
+                throw new DataStoreException("Unsupported shapefile type: " + iShapeType);
             }
 
             rf.order(ByteOrder.BIG_ENDIAN);
             // read in each Record and Populate the Feature
 
-            // TODO: ignore deleted records
-            df.get(); // denotes whether deleted or current
-            // read first part of record
-
-            for (FieldDescriptor fd: this.FDArray) {
-                data = new byte[fd.getLength()];
-                df.get(data);
-                int length = data.length;
-                while (length != 0 && data[length - 1] <= ' ') {
-                    length--;
-                }
-                String value = new String(data, 0, length);
-                f.setPropertyValue(fd.getName(), value);
-            }
+            dbf.loadRowIntoFeature(f);
 
             this.FeatureMap.put(RecordNumber, f);
         }
+    }
+
+    /**
+     * Load point feature.
+     * @param rf Byte buffer.
+     * @param feature Feature to fill.
+     */
+    private void loadPointFeature(MappedByteBuffer rf, Feature feature) {
+        double x = rf.getDouble();
+        double y = rf.getDouble();
+        Point pnt = new Point(x, y);
+        feature.setPropertyValue(GEOMETRY_NAME, pnt);
+    }
+
+    /**
+     * Load polygon feature.
+     * @param rf Byte buffer.
+     * @param feature Feature to fill.
+     * @throws DataStoreException if the polygon cannot be handled.
+     */
+    private void loadPolygonFeature(MappedByteBuffer rf, Feature feature) throws DataStoreException {
+        /* double xmin = */rf.getDouble();
+        /* double ymin = */rf.getDouble();
+        /* double xmax = */rf.getDouble();
+        /* double ymax = */rf.getDouble();
+        int NumParts = rf.getInt();
+        int NumPoints = rf.getInt();
+
+        if (NumParts > 1) {
+            throw new DataStoreException("Polygons with multiple linear rings have not implemented yet.");
+        }
+
+        // read the one part
+        @SuppressWarnings("unused")
+        int Part = rf.getInt();
+        Polygon poly = new Polygon();
+
+        // create a line from the points
+        double xpnt = rf.getDouble();
+        double ypnt = rf.getDouble();
+        // Point oldpnt = new Point(xpnt, ypnt);
+        poly.startPath(xpnt, ypnt);
+
+        for (int j = 0; j < NumPoints - 1; j++) {
+            xpnt = rf.getDouble();
+            ypnt = rf.getDouble();
+            poly.lineTo(xpnt, ypnt);
+        }
+
+        feature.setPropertyValue(GEOMETRY_NAME, poly);
+    }
+
+    /**
+     * Load polyline feature.
+     * @param rf Byte buffer.
+     * @param feature Feature to fill.
+     */
+    private void loadPolylineFeature(MappedByteBuffer rf, Feature feature) {
+        /* double xmin = */rf.getDouble();
+        /* double ymin = */rf.getDouble();
+        /* double xmax = */rf.getDouble();
+        /* double ymax = */rf.getDouble();
+
+        int NumParts = rf.getInt();
+        int NumPoints = rf.getInt();
+
+        int[] NumPartArr = new int[NumParts + 1];
+
+        for (int n = 0; n < NumParts; n++) {
+            int idx = rf.getInt();
+            NumPartArr[n] = idx;
+        }
+        NumPartArr[NumParts] = NumPoints;
 
-        fc.close();
-        fc2.close();
-        fis.close();
-        fis2.close();
+        double xpnt, ypnt;
+        Polyline ply = new Polyline();
+
+        for (int m = 0; m < NumParts; m++) {
+            xpnt = rf.getDouble();
+            ypnt = rf.getDouble();
+            ply.startPath(xpnt, ypnt);
+
+            for (int j = NumPartArr[m]; j < NumPartArr[m + 1] - 1; j++) {
+                xpnt = rf.getDouble();
+                ypnt = rf.getDouble();
+                ply.lineTo(xpnt, ypnt);
+            }
+        }
+
+        feature.setPropertyValue(GEOMETRY_NAME, ply);
     }
 
+    /**
+     * Create a feature descriptor.
+     * @param name Name of the field.
+     * @return The feature type.
+     */
     private DefaultFeatureType getFeatureType(final String name) {
-        final int n = FDArray.size();
+        Objects.requireNonNull(name, "The feature name cannot be null.");
+
+        final int n = dbf.getFieldsDescriptor().size();
         final DefaultAttributeType<?>[] attributes = new DefaultAttributeType<?>[n + 1];
-        final Map<String,Object> properties = new HashMap<>(4);
-        for (int i=0; i<n; i++) {
-            properties.put(DefaultAttributeType.NAME_KEY, FDArray.get(i).getName());
+        final Map<String, Object> properties = new HashMap<>(4);
+
+        // Load data field.
+        for (int i = 0; i < n; i++) {
+            properties.put(DefaultAttributeType.NAME_KEY, dbf.getFieldsDescriptor().get(i).getName());
             attributes[i] = new DefaultAttributeType<>(properties, String.class, 1, 1, null);
         }
+
+        // Add geometry field.
         properties.put(DefaultAttributeType.NAME_KEY, GEOMETRY_NAME);
         attributes[n] = new DefaultAttributeType<>(properties, Geometry.class, 1, 1, null);
+
+        // Add name.
         properties.put(DefaultAttributeType.NAME_KEY, name);
         return new DefaultFeatureType(properties, false, null, attributes);
     }
 
+    /**
+     * @see java.lang.Object#toString()
+     */
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
@@ -293,12 +346,7 @@ public class ShapeFile {
         s.append("mmin: ").append(mmin).append(lineSeparator);
         s.append("mmax: ").append(mmax).append(lineSeparator);
         s.append("------------------------").append(lineSeparator);
-        s.append("DbaseVersion: ").append(DbaseVersion).append(lineSeparator);
-        s.append("DbaseLastUpdate: ").append(new String(DbaseLastUpdate)).append(lineSeparator);
-        s.append("FeatureCount: ").append(FeatureCount).append(lineSeparator);
-        s.append("DbaseHeaderBytes: ").append(DbaseHeaderBytes).append(lineSeparator);
-        s.append("DbaseRecordBytes: ").append(DbaseRecordBytes).append(lineSeparator);
-        s.append("DbasePlusLanReserved: ").append(DbasePlusLanReserved).append(lineSeparator);
+        s.append(dbf.toString());
 
         return s.toString();
     }

Modified: sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeTypeEnum.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeTypeEnum.java?rev=1633035&r1=1633034&r2=1633035&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeTypeEnum.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-shapefile/src/main/java/org/apache/sis/storage/shapefile/ShapeTypeEnum.java [UTF-8] Mon Oct 20 05:37:03 2014
@@ -25,8 +25,8 @@ import java.util.Map;
  * Provides a ShapefileType Enumeration
  *
  * @author  Travis L. Pinney
- * @since   0.4
- * @version 0.4
+ * @since   0.5
+ * @version 0.5
  * @module
  *
  * @see <a href="http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf">ESRI Shapefile Specification</a>



Mime
View raw message