sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1740141 [1/2] - in /sis/branches/JDK8: core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/main/java/org/apache/sis/internal/feature/ core/sis-feature/src/test/java/org/apache/sis/internal/feature/ core/sis-feature/...
Date Wed, 20 Apr 2016 13:01:55 GMT
Author: desruisseaux
Date: Wed Apr 20 13:01:55 2016
New Revision: 1740141

URL: http://svn.apache.org/viewvc?rev=1740141&view=rev
Log:
Complete (for now) FeatureTypeBuilder refactoring.
All 'addProperty(...)' methods have been replaced by a smaller amount of 'addFoo(...)'
methods, which return a Property builder on which the caller can invoke setter methods.

Removed:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeTypeBuilder.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/AttributeTypeBuilderTest.java
Modified:
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Builder.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java
    sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/NameConvention.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/NameConventionTest.java
    sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXConstants.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXReader.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXWriter100.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPointsAsPolylineOperation.java
    sis/branches/JDK8/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/gpx/GPXReaderTest.java
    sis/branches/JDK8/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/gpx/GPXWriterTest.java

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java?rev=1740141&r1=1740140&r2=1740141&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureOperations.java [UTF-8] Wed Apr 20 13:01:55 2016
@@ -187,7 +187,7 @@ public final class FeatureOperations ext
      * forwarded to the corresponding single property.
      *
      * @param  identification    the name and other information to be given to the operation.
-     * @param  delimiter         the characters to use a delimiter between each single property value.
+     * @param  delimiter         the characters to use as delimiter between each single property value.
      * @param  prefix            characters to use at the beginning of the concatenated string, or {@code null} if none.
      * @param  suffix            characters to use at the end of the concatenated string, or {@code null} if none.
      * @param  singleAttributes  identification of the single attributes (or operations producing attributes) to concatenate.

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Builder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Builder.java?rev=1740141&r1=1740140&r2=1740141&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Builder.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Builder.java [UTF-8] Wed Apr 20 13:01:55 2016
@@ -19,29 +19,30 @@ package org.apache.sis.internal.feature;
 import java.util.HashMap;
 import java.util.Map;
 import org.opengis.util.GenericName;
-import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.ArgumentChecks;
 
-// Branch-dependent imports
-import org.opengis.feature.IdentifiedType;
-
 
 /**
  * Base class of feature and attribute builders.
  * This base class provide the method needed for filling the {@code identification} map.
  *
+ * @param <T> the builder subclass. It is subclass responsibility to ensure that {@code this}
+ *            is assignable to {@code <T>}; this {@code Builder} class can not verify that.
+ *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
  * @version 0.7
  * @module
  */
-abstract class Builder {
+abstract class Builder<T extends Builder<T>> {
     /**
      * The feature name, definition, designation and description.
      * The name is mandatory; all other information are optional.
      */
-    final Map<String,Object> identification = new HashMap<>();
+    final Map<String,Object> identification = new HashMap<>(4);
 
     /**
      * Creates a new builder instance.
@@ -52,49 +53,65 @@ abstract class Builder {
     /**
      * Resets this builder to its initial state. After invocation of this method,
      * this builder is in the same state than after construction.
+     *
+     * @return {@code this} for allowing method calls chaining.
      */
-    public void clear() {
+    @SuppressWarnings("unchecked")
+    public T clear() {
         identification.clear();
+        return (T) this;
     }
 
     /**
-     * Sets this builder state to properties inferred from the given type.
+     * Creates a generic name from the given scope and local part.
+     * An empty scope means no scope. A {@code null} scope means the
+     * {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope}.
+     *
+     * @param scope      the scope of the name to create, or {@code null} if the name is local.
+     * @param localPart  the local part of the generic name (can not be {@code null}).
      */
-    final void copy(final IdentifiedType template) {
-        clear();
-        setName       (template.getName());
-        setDescription(template.getDescription());
-        setDefinition (template.getDefinition());
-        setDesignation(template.getDesignation());
-    }
+    abstract GenericName name(String scope, String localPart);
 
     /**
-     * Sets the feature type name as a simple string without scope.
-     * The name will be an instance of {@link org.opengis.util.LocalName}.
+     * Sets the feature type name as a simple string with the default scope.
+     * The default scope is the value specified by the last call to
+     * {@link FeatureTypeBuilder#setDefaultScope(String)}.
+     *
+     * <p>The name will be an instance of {@link org.opengis.util.LocalName} if no default scope
+     * has been specified, or an instance of {@link org.opengis.util.ScopedName} otherwise.</p>
      *
      * <p>This convenience method creates a {@link GenericName} instance,
      * then delegates to {@link #setName(GenericName)}.</p>
      *
-     * @param localPart  the local part of the generic name (can not be {@code null}).
+     * @param  localPart  the local part of the generic name (can not be {@code null}).
+     * @return {@code this} for allowing method calls chaining.
      */
-    public void setName(String localPart) {
-        ArgumentChecks.ensureNonNull("localPart", localPart);
-        setName(name(null, localPart));
+    public T setName(String localPart) {
+        ArgumentChecks.ensureNonEmpty("localPart", localPart);
+        return setName(name(null, localPart));
     }
 
     /**
      * Sets the feature type name as a string in the given scope.
-     * If the given scope is non-null, then the name will be an instance of {@link org.opengis.util.ScopedName}.
+     * If a {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope} was specified,
+     * this method override it.
+     *
+     * <p>The name will be an instance of {@link org.opengis.util.LocalName} if the given scope
+     * is {@code null} or empty, or an instance of {@link org.opengis.util.ScopedName} otherwise.</p>
      *
      * <p>This convenience method creates a {@link GenericName} instance,
      * then delegates to {@link #setName(GenericName)}.</p>
      *
-     * @param scope      the scope of the name to create, or {@code null} if the name is local.
-     * @param localPart  the local part of the generic name (can not be {@code null}).
-     */
-    public void setName(String scope, String localPart) {
-        ArgumentChecks.ensureNonNull("localPart", localPart);
-        setName(name(scope, localPart));
+     * @param  scope      the scope of the name to create, or {@code null} if the name is local.
+     * @param  localPart  the local part of the generic name (can not be {@code null}).
+     * @return {@code this} for allowing method calls chaining.
+     */
+    public T setName(String scope, String localPart) {
+        ArgumentChecks.ensureNonEmpty("localPart", localPart);
+        if (scope == null) {
+            scope = "";                                 // For preventing the use of default scope.
+        }
+        return setName(name(scope, localPart));
     }
 
     /**
@@ -105,13 +122,16 @@ abstract class Builder {
      * all {@code setName(…)} convenience methods in this builder delegate to this method.
      * Consequently this method can be used as a central place where to control the creation of all names.</div>
      *
-     * @param name  the generic name (can not be {@code null}).
+     * @param  name  the generic name (can not be {@code null}).
+     * @return {@code this} for allowing method calls chaining.
      *
-     * @see DefaultFeatureType#NAME_KEY
+     * @see AbstractIdentifiedType#NAME_KEY
      */
-    public void setName(GenericName name) {
+    @SuppressWarnings("unchecked")
+    public T setName(GenericName name) {
         ArgumentChecks.ensureNonNull("name", name);
-        identification.put(DefaultFeatureType.NAME_KEY, name);
+        identification.put(AbstractIdentifiedType.NAME_KEY, name);
+        return (T) this;
     }
 
     /**
@@ -121,49 +141,58 @@ abstract class Builder {
      * @return the current {@code FeatureType} name, or {@code null} if the name has not yet been specified.
      */
     public GenericName getName() {
-        return (GenericName) identification.get(DefaultFeatureType.NAME_KEY);
+        return (GenericName) identification.get(AbstractIdentifiedType.NAME_KEY);
     }
 
     /**
-     * Sets optional information beyond that required for concise definition of the element.
-     * The description may assist in understanding the feature scope and application.
-     *
-     * @param description  information beyond that required for concise definition of the element, or {@code null} if none.
-     *
-     * @see DefaultFeatureType#DESCRIPTION_KEY
+     * Returns the name to use for displaying error messages.
      */
-    public void setDescription(CharSequence description) {
-        identification.put(DefaultFeatureType.DESCRIPTION_KEY, description);
+    final String getDisplayName() {
+        final GenericName name = getName();
+        return (name != null) ? name.toString() : Vocabulary.getResources(identification).getString(Vocabulary.Keys.Unnamed);
     }
 
     /**
-     * Sets a natural language designator for the element.
-     * This can be used as an alternative to the {@linkplain #getName() name} in user interfaces.
+     * Sets a concise definition of the element.
      *
-     * @param designation a natural language designator for the element, or {@code null} if none.
+     * @param  definition a concise definition of the element, or {@code null} if none.
+     * @return {@code this} for allowing method calls chaining.
      *
-     * @see DefaultFeatureType#DESIGNATION_KEY
+     * @see AbstractIdentifiedType#DEFINITION_KEY
      */
-    public void setDesignation(CharSequence designation) {
-        identification.put(DefaultFeatureType.DESIGNATION_KEY, designation);
+    @SuppressWarnings("unchecked")
+    public T setDefinition(CharSequence definition) {
+        identification.put(AbstractIdentifiedType.DEFINITION_KEY, definition);
+        return (T) this;
     }
 
     /**
-     * Sets a concise definition of the element.
+     * Sets a natural language designator for the element.
+     * This can be used as an alternative to the {@linkplain #getName() name} in user interfaces.
      *
-     * @param definition a concise definition of the element, or {@code null} if none.
+     * @param  designation a natural language designator for the element, or {@code null} if none.
+     * @return {@code this} for allowing method calls chaining.
      *
-     * @see DefaultFeatureType#DEFINITION_KEY
+     * @see AbstractIdentifiedType#DESIGNATION_KEY
      */
-    public void setDefinition(CharSequence definition) {
-        identification.put(DefaultFeatureType.DEFINITION_KEY, definition);
+    @SuppressWarnings("unchecked")
+    public T setDesignation(CharSequence designation) {
+        identification.put(AbstractIdentifiedType.DESIGNATION_KEY, designation);
+        return (T) this;
     }
 
     /**
-     * Creates a generic name from the given scope and local part.
+     * Sets optional information beyond that required for concise definition of the element.
+     * The description may assist in understanding the feature scope and application.
      *
-     * @param scope      the scope of the name to create, or {@code null} if the name is local.
-     * @param localPart  the local part of the generic name (can not be {@code null}).
+     * @param  description  information beyond that required for concise definition of the element, or {@code null} if none.
+     * @return {@code this} for allowing method calls chaining.
+     *
+     * @see AbstractIdentifiedType#DESCRIPTION_KEY
      */
-    abstract GenericName name(String scope, String localPart);
+    @SuppressWarnings("unchecked")
+    public T setDescription(CharSequence description) {
+        identification.put(AbstractIdentifiedType.DESCRIPTION_KEY, description);
+        return (T) this;
+    }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java?rev=1740141&r1=1740140&r2=1740141&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureTypeBuilder.java [UTF-8] Wed Apr 20 13:01:55 2016
@@ -18,15 +18,12 @@ package org.apache.sis.internal.feature;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
-import org.opengis.util.ScopedName;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.feature.AbstractOperation;
@@ -35,15 +32,16 @@ import org.apache.sis.feature.DefaultAtt
 import org.apache.sis.feature.DefaultFeatureType;
 import org.apache.sis.feature.FeatureOperations;
 import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.util.NullArgumentException;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
-
-import static org.apache.sis.internal.feature.NameConvention.*;
+import org.apache.sis.util.ArraysExt;
 
 // Branch-dependent imports
 import org.opengis.feature.AttributeType;
-import org.opengis.feature.FeatureAssociationRole;
+import org.opengis.feature.Feature;
 import org.opengis.feature.FeatureType;
-import org.opengis.feature.Operation;
 import org.opengis.feature.PropertyType;
 
 
@@ -60,23 +58,24 @@ import org.opengis.feature.PropertyType;
  * @since   0.7
  * @version 0.7
  * @module
+ *
+ * @see org.apache.sis.parameter.ParameterBuilder
  */
-public class FeatureTypeBuilder extends Builder {
+public class FeatureTypeBuilder extends Builder<FeatureTypeBuilder> {
     /**
      * The factory to use for creating names.
      */
     private final NameFactory nameFactory;
 
     /**
-     * The feature properties. Entries in this map are added by invoking one of the
-     * {@code addProperty(…)} methods defined in this class.
+     * Builders for the properties of this feature.
      */
-    private final Map<GenericName,PropertyType> properties = new LinkedHashMap<>();
+    private final List<Property<?>> properties;
 
     /**
      * The parent of the feature to create. By default, new features have no parent.
      */
-    private final List<FeatureType> superTypes = new ArrayList<>();
+    private final List<FeatureType> superTypes;
 
     /**
      * Whether the feature type is abstract. The default value is {@code false}.
@@ -84,10 +83,31 @@ public class FeatureTypeBuilder extends
     private boolean isAbstract;
 
     /**
-     * If {@link #idAttributes} is non-null, an optional prefix to insert before the
-     * {@linkplain FeatureOperations#compound compound key} named {@code "@id"}.
+     * The default scope to use when {@link #name(String, String)} is invoked with a null scope.
+     *
+     * @see #setDefaultScope(String)
+     */
+    private String defaultScope;
+
+    /**
+     * The default minimum number of property values.
+     *
+     * @see #setDefaultCardinality(int, int)
+     */
+    private int defaultMinimumOccurs;
+
+    /**
+     * The default maximum number of property values.
+     *
+     * @see #setDefaultCardinality(int, int)
+     */
+    private int defaultMaximumOccurs;
+
+    /**
+     * If {@link #idAttributes} is non-null, an optional prefix or suffix to insert before
+     * or after the {@linkplain FeatureOperations#compound compound key} named {@code "@id"}.
      */
-    private String idPrefix;
+    private String idPrefix, idSuffix;
 
     /**
      * If {@link #idAttributes} is non-null and contains more than one value, the separator to insert between
@@ -96,556 +116,643 @@ public class FeatureTypeBuilder extends
     private String idDelimiter;
 
     /**
-     * The names of attributes to use in a {@linkplain FeatureOperations#compound compound key} named {@code "@id"},
-     * or {@code null} if none. If this array contains only one name and {@link #idPrefix} is null, then {@code "@id"}
-     * will be a {@linkplain FeatureOperations#link link} to the attribute named {@code idAttributes[0]}.
+     * The attributes to use in a {@linkplain FeatureOperations#compound compound key} named {@code "@id"},
+     * or {@code null} if none. If this array contains only one property and {@link #idPrefix} is null,
+     * then {@code "@id"} will be a {@linkplain FeatureOperations#link link} to {@code idAttributes[0]}.
      */
-    private GenericName[] idAttributes;
+    private final List<Property<?>> idAttributes;
 
     /**
-     * The name of the default geometry attribute, or {@code null} if none.
+     * The default geometry attribute, or {@code null} if none.
+     *
+     * @see NameConvention#DEFAULT_GEOMETRY_PROPERTY
      */
-    private GenericName defGeomAttribute;
+    private Property<?> defaultGeometry;
 
     /**
-     * Creates a new builder instance.
+     * Creates a new builder instance using the default name factory.
      */
     public FeatureTypeBuilder() {
-        nameFactory = DefaultFactories.forBuildin(NameFactory.class);
+        this(DefaultFactories.forBuildin(NameFactory.class));
     }
 
     /**
-     * Resets this builder to its initial state. After invocation of this method,
-     * this builder is in the same state than after construction.
+     * Creates a new builder instance using the given name factory.
+     *
+     * @param factory  the factory to use for creating names.
      */
-    @Override
-    public void clear() {
-        super.clear();
-        properties.clear();
-        superTypes.clear();
-        isAbstract       = false;
-        idPrefix         = null;
-        idDelimiter      = null;
-        idAttributes     = null;
-        defGeomAttribute = null;
+    public FeatureTypeBuilder(final NameFactory factory) {
+        nameFactory  = factory;
+        properties   = new ArrayList<>();
+        superTypes   = new ArrayList<>();
+        idAttributes = new ArrayList<>();
+        idDelimiter  = ":";
+        defaultMinimumOccurs = 1;
+        defaultMaximumOccurs = 1;
     }
 
     /**
-     * Sets this builder state to properties inferred from the given feature type.
-     * This method fetches the {@code FeatureType} name, definition, designation, description, super-types,
-     * abstract flag, and list of properties from the given instance.
-     *
-     * <p>This method is useful when an existing instance is to be used as a template.</p>
+     * Resets this builder to its initial state. After invocation of this method,
+     * this builder is in the same state than after construction.
      *
-     * @param template  feature type to copy parameters from.
+     * @return {@code this} for allowing method calls chaining.
      */
-    public void copy(final FeatureType template) {
-        super.copy(template);
-        setAbstract(template.isAbstract());
-        Set<? extends FeatureType> parents = template.getSuperTypes();
-        setSuperTypes(parents.toArray(new FeatureType[parents.size()]));
-        for (PropertyType pt : template.getProperties(false)) {
-            addProperty(pt);
-        }
+    @Override
+    public FeatureTypeBuilder clear() {
+        super.clear();
+        properties  .clear();
+        superTypes  .clear();
+        idAttributes.clear();
+        idDelimiter     = ":";
+        idPrefix        = null;
+        idSuffix        = null;
+        isAbstract      = false;
+        defaultGeometry = null;
+        defaultMinimumOccurs = 1;
+        defaultMaximumOccurs = 1;
+        return this;
     }
 
     /**
      * Sets whether the feature type is abstract.
      * If this method is not invoked, then the default value is {@code false}.
      *
-     * @param isAbstract whether the feature type is abstract.
+     * @param  isAbstract whether the feature type is abstract.
+     * @return {@code this} for allowing method calls chaining.
      */
-    public void setAbstract(final boolean isAbstract) {
+    public FeatureTypeBuilder setAbstract(final boolean isAbstract) {
         this.isAbstract = isAbstract;
+        return this;
     }
 
     /**
      * Sets the parent types (or super-type) from which to inherit properties.
      * If this method is not invoked, then the default value is to have no parent.
      *
-     * @param parents  the parent types from which to inherit properties, or an empty array if none.
+     * @param  parents  the parent types from which to inherit properties, or an empty array if none.
+     * @return {@code this} for allowing method calls chaining.
      */
-    public void setSuperTypes(final FeatureType... parents) {
+    public FeatureTypeBuilder setSuperTypes(final FeatureType... parents) {
         ArgumentChecks.ensureNonNull("parents", parents);
         superTypes.clear();
         superTypes.addAll(Arrays.asList(parents));
+        return this;
     }
 
     /**
-     * Define an id operation composed of the given attributes.
+     * Sets the scope to use by default when {@link #setName(String)} is invoked.
      *
-     * @param prefix generated id prefix
-     * @param separator generated id separator between attribute values
-     * @param attributes attributes used in the id operation
+     * @param  scope  the new default scope, or {@code null} if none.
+     * @return {@code this} for allowing method calls chaining.
      */
-    public void setIdOperation(final String prefix, final String separator, final GenericName... attributes) {
-        idPrefix     = prefix;
-        idDelimiter  = separator;
-        idAttributes = attributes.clone();
-        properties.put(ID_PROPERTY, null);                                  // Add placeholder
+    public FeatureTypeBuilder setDefaultScope(final String scope) {
+        defaultScope = scope;
+        return this;
     }
 
     /**
-     * Define a default geometry link operation.
+     * Sets the default minimum and maximum number of property values.
+     * Those defaults will applied to newly created attributes or associations,
+     * for example in next calls to {@link #addAttribute(Class)}.
      *
-     * @param attribute referenced attribute
-     */
-    public void setDefaultGeometryOperation(final GenericName attribute) {
-        defGeomAttribute = attribute;
-        properties.put(DEFAULT_GEOMETRY_PROPERTY, null);                    // Add placeholder
-        properties.put(ENVELOPE_PROPERTY, null);
-    }
-
-    /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one, no characteristics
-     * and no default value.
+     * <p>If this method is not invoked, then the default cardinality is [1 … 1].</p>
      *
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final String localPart, final Class<V> valueClass) {
-        return addProperty(name(null, localPart), valueClass);
-    }
-
-    /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one, no characteristics
-     * and no default value.
-     *
-     * @param scope property generic name scope part
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final String scope, final String localPart, final Class<V> valueClass) {
-        return addProperty(name(scope, localPart), valueClass);
-    }
-
-    /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one, no characteristics
-     * and no default value.
+     * @param  minimumOccurs  new default minimum number of property values.
+     * @param  maximumOccurs  new default maximum number of property values.
+     * @return {@code this} for allowing method calls chaining.
      *
-     * @param name property name
-     * @param valueClass property value class
-     * @return created property type
+     * @see Property#setCardinality(int, int)
      */
-    public <V> AttributeType<V> addProperty(final GenericName name, final Class<V> valueClass) {
-        return addProperty(name, valueClass, (V) null);
+    public FeatureTypeBuilder setDefaultCardinality(final int minimumOccurs, final int maximumOccurs) {
+        if (minimumOccurs < 0 || maximumOccurs < minimumOccurs) {
+            throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalRange_2, minimumOccurs, maximumOccurs));
+        }
+        defaultMinimumOccurs = minimumOccurs;
+        defaultMaximumOccurs = maximumOccurs;
+        return this;
     }
 
     /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one, no default value
-     * and the given {@code CoordinateReferenceSystem} characteristic.
+     * Sets the prefix, suffix and delimiter to use when formatting a compound identifier made of two or more attributes.
+     * The strings specified to this method will be used only if {@link #addIdentifier(Class)} is invoked more than once.
      *
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @param crs property {@code CoordinateReferenceSystem} characteristic
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final String localPart, final Class<V> valueClass, final CoordinateReferenceSystem crs) {
-        return addProperty(null, localPart, valueClass, crs);
-    }
-
-    /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one, no default value
-     * and the given {@code CoordinateReferenceSystem} characteristic.
+     * <p>If this method is not invoked, then the default values are the {@code ":"} delimiter and no prefix or suffix.</p>
      *
-     * @param scope property generic name scope part
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @param crs property {@code CoordinateReferenceSystem} characteristic
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final String scope, final String localPart,
-            final Class<V> valueClass, final CoordinateReferenceSystem crs)
-    {
-        return addProperty(name(scope, localPart), valueClass, crs);
-    }
-
-    /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one, no default value
-     * and the given {@code CoordinateReferenceSystem} characteristic.
+     * @param  delimiter  the characters to use as delimiter between each single property value.
+     * @param  prefix     characters to use at the beginning of the concatenated string, or {@code null} if none.
+     * @param  suffix     characters to use at the end of the concatenated string, or {@code null} if none.
+     * @return {@code this} for allowing method calls chaining.
      *
-     * @param name property name
-     * @param valueClass property value class
-     * @param crs property {@code CoordinateReferenceSystem} characteristic
-     * @return created property type
+     * @see java.util.StringJoiner
+     * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
      */
-    public <V> AttributeType<V> addProperty(final GenericName name, final Class<V> valueClass, final CoordinateReferenceSystem crs) {
-        return addProperty(name, valueClass, null, crs);
+    public FeatureTypeBuilder setIdentifierDelimiters(final String delimiter, final String prefix, final String suffix) {
+        ArgumentChecks.ensureNonEmpty("delimiter", delimiter);
+        idDelimiter = delimiter;
+        idPrefix    = prefix;
+        idSuffix    = suffix;
+        return this;
     }
 
     /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one and no characteristics.
+     * Creates a new {@code AttributeType} builder for values of the given class which will be used as identifiers.
+     * An arbitrary amount of attributes can be specified as identifiers:
      *
-     * @param scope property generic name scope part
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @param defaultValue property default value
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final String scope, final String localPart,
-            final Class<V> valueClass, final V defaultValue)
-    {
-        return addProperty(name(scope, localPart), valueClass, defaultValue, null);
-    }
-
-    /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one and the given
-     * {@code CoordinateReferenceSystem} characteristic.
+     * <ul>
+     *   <li>If this method is never invoked, no attribute is marked as feature identifier.</li>
+     *   <li>If this method is invoked exactly once, then a new attribute is created in the same way than
+     *       {@link #addAttribute(Class)} and a synthetic attribute named {@code "@id"} will be created as
+     *       a {@linkplain FeatureOperations#link link} to the new attribute.</li>
+     *   <li>If this method is invoked more than once, then new attributes are created in the same way than
+     *       {@link #addAttribute(Class)} and a synthetic attribute named {@code "@id"} will be created as
+     *       a {@linkplain FeatureOperations#compound compound key} made of all identifiers.</li>
+     * </ul>
      *
-     * @param scope property generic name scope part
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @param defaultValue property default value
-     * @param crs property {@code CoordinateReferenceSystem} characteristic
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final String scope, final String localPart,
-            final Class<V> valueClass, final V defaultValue, final CoordinateReferenceSystem crs)
-    {
-        return addProperty(name(scope, localPart), valueClass, 1, 1, defaultValue, crs);
-    }
-
-    /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one and no characteristics.
+     * Callers shall invoke at least one of the {@code Property.setName(…)} methods on the returned instance.
+     * All other methods are optional.
      *
-     * @param name property name
-     * @param valueClass property value class
-     * @param defaultValue property default value
-     * @return created property type
+     * @param  <V>  the compile-time value of {@code valueClass} argument.
+     * @param  valueClass  the class of attribute values.
+     * @return a builder for an {@code AttributeType}.
      */
-    public <V> AttributeType<V> addProperty(final GenericName name, final Class<V> valueClass, final V defaultValue) {
-        return addProperty(name, valueClass, 1, 1, defaultValue);
+    public <V> Property<V> addIdentifier(final Class<V> valueClass) {
+        ensureAttributeType(valueClass);
+        final Property<V> property = new Property<>(valueClass);
+        idAttributes.add(property);
+        properties.add(property);
+        return property;
     }
 
     /**
-     * Add a new property to the feature type.
-     * Property will have a minimum and maximum occurrence of one and the given
-     * {@code CoordinateReferenceSystem} characteristic.
-     *
-     * @param name property name
-     * @param valueClass property value class
-     * @param defaultValue property default value
-     * @param crs property {@code CoordinateReferenceSystem} characteristic
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final GenericName name, final Class<V> valueClass,
-            final V defaultValue, final CoordinateReferenceSystem crs)
-    {
-        return addProperty(name, valueClass, 1, 1, defaultValue, crs);
+     * Creates a new {@code AttributeType} builder for a geometry which will be flagged as the default geometry.
+     * Callers shall invoke at least one of the {@code Property.setName(…)} methods on the returned instance.
+     * All other methods are optional.
+     *
+     * @param  <V>  the compile-time value of {@code valueClass} argument.
+     * @param  valueClass  the geometry class of attribute values.
+     * @return a builder for an {@code AttributeType} which will contain the default geometry.
+     * @throws IllegalArgumentException if the given type is not a supported geometry type.
+     * @throws IllegalStateException if a default geometry has already been specified to this builder.
+     */
+    public <V> Property<V> addDefaultGeometry(final Class<V> valueClass) {
+        ensureAttributeType(valueClass);
+        if (!Geometries.isKnownType(valueClass)) {
+            throw new IllegalArgumentException(errors().getString(Errors.Keys.UnsupportedImplementation_1, valueClass));
+        }
+        if (defaultGeometry != null) {
+            throw new IllegalStateException(errors().getString(Errors.Keys.PropertyAlreadyExists_2,
+                    getDisplayName(), NameConvention.DEFAULT_GEOMETRY_PROPERTY));
+        }
+        final Property<V> property = new Property<>(valueClass);
+        defaultGeometry = property;
+        properties.add(property);
+        return property;
     }
 
     /**
-     * Add a new property to the feature type.
-     *
-     *
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @param minimumOccurs property minimum number of occurrences
-     * @param maximumOccurs property maximum number of occurrences
-     * @param defaultValue property default value
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final String localPart, final Class<V> valueClass,
-            final int minimumOccurs, final int maximumOccurs, final V defaultValue)
-    {
-        return addProperty(name(null, localPart), valueClass, minimumOccurs, maximumOccurs, defaultValue);
+     * Creates a new {@code AttributeType} builder for values of the given class.
+     * Callers shall invoke at least one of the {@code Property.setName(…)} methods on the returned instance.
+     * All other methods are optional.
+     *
+     * <p>Usage example:</p>
+     * {@preformat java
+     *     builder.addAttribute(String.class).setName("City");
+     * }
+     *
+     * @param  <V>  the compile-time value of {@code valueClass} argument.
+     * @param  valueClass  the class of attribute values.
+     * @return a builder for an {@code AttributeType}.
+     */
+    public <V> Property<V> addAttribute(final Class<V> valueClass) {
+        ensureAttributeType(valueClass);
+        final Property<V> property = new Property<>(valueClass);
+        properties.add(property);
+        return property;
     }
 
     /**
-     * Add a new property to the feature type.
-     *
-     * @param scope property generic name scope part
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @param minimumOccurs property minimum number of occurrences
-     * @param maximumOccurs property maximum number of occurrences
-     * @param defaultValue property default value
-     * @return created property type
+     * Ensures that the given value class is not null and not assignable to {@code Feature}.
+     * We disallow {@code Feature.class} because those type shall be handled as associations
+     * instead than attributes.
      */
-    public <V> AttributeType<V> addProperty(final String scope, final String localPart, final Class<V> valueClass,
-            final int minimumOccurs, final int maximumOccurs, final V defaultValue)
-    {
-        return addProperty(name(scope, localPart), valueClass, minimumOccurs, maximumOccurs, defaultValue);
+    private void ensureAttributeType(final Class<?> valueClass) {
+        if (valueClass == null) {
+            throw new NullArgumentException(errors().getString(Errors.Keys.NullArgument_1, "valueClass"));
+        }
+        if (Feature.class.isAssignableFrom(valueClass)) {
+            throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalArgumentValue_2, "valueClass", valueClass));
+        }
     }
 
     /**
-     * Add a new property to the feature type.
+     * Creates a new {@code FeatureAssociationRole} builder for features of the given type.
+     * Callers shall invoke at least one of the {@code Property.setName(…)} methods on the returned instance.
+     * All other methods are optional.
      *
-     * @param scope property generic name scope part
-     * @param localPart property generic name tip part
-     * @param valueClass property value class
-     * @param minimumOccurs property minimum number of occurrences
-     * @param maximumOccurs property maximum number of occurrences
-     * @param defaultValue property default value
-     * @param crs property {@code CoordinateReferenceSystem} characteristic
-     * @return created property type
+     * @param  type  the type of feature values.
+     * @return a builder for a {@code FeatureAssociationRole}.
      */
-    public <V> AttributeType<V> addProperty(final String scope, final String localPart, final Class<V> valueClass,
-            final int minimumOccurs, final int maximumOccurs, final V defaultValue, final CoordinateReferenceSystem crs)
-    {
-        return addProperty(name(scope, localPart), valueClass, minimumOccurs, maximumOccurs, defaultValue, crs);
+    public Property<Feature> addAssociation(final FeatureType type) {
+        ArgumentChecks.ensureNonNull("type", type);
+        final Property<Feature> property = new Property<>(Feature.class, FeatureType.class, type);
+        properties.add(property);
+        return property;
     }
 
     /**
-     * Add a new property to the feature type.
-     *
-     * @param name property name
-     * @param valueClass property value class
-     * @param minimumOccurs property minimum number of occurrences
-     * @param maximumOccurs property maximum number of occurrences
-     * @param defaultValue property default value
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final GenericName name, final Class<V> valueClass,
-            final int minimumOccurs, final int maximumOccurs, final V defaultValue)
-    {
-        return addProperty(name, valueClass, minimumOccurs, maximumOccurs, defaultValue,null);
+     * Creates a new {@code FeatureAssociationRole} builder for features of a type of the given name.
+     * This method can be invoked as an alternative to {@link #addAssociation(FeatureType)} when the
+     * {@code FeatureType} instance is not yet available because of cyclic dependency.
+     *
+     * @param  type  the name of the type of feature values.
+     * @return a builder for a {@code FeatureAssociationRole}.
+     */
+    public Property<Feature> addAssociation(final GenericName type) {
+        ArgumentChecks.ensureNonNull("type", type);
+        final Property<Feature> property = new Property<>(Feature.class, GenericName.class, type);
+        properties.add(property);
+        return property;
     }
 
     /**
-     * Add a new property to the feature type.
+     * Describes one property of the {@code FeatureType} to be built by the enclosing {@code FeatureTypeBuilder}.
+     * A different instance of {@code Property} exists for each property to describe. Those instances are created by:
      *
-     * @param name property name
-     * @param valueClass property value class
-     * @param minimumOccurs property minimum number of occurrences
-     * @param maximumOccurs property maximum number of occurrences
-     * @param defaultValue property default value
-     * @param crs property {@code CoordinateReferenceSystem} characteristic
-     * @return created property type
-     */
-    public <V> AttributeType<V> addProperty(final GenericName name, final Class<V> valueClass,
-            final int minimumOccurs, final int maximumOccurs, final V defaultValue, final CoordinateReferenceSystem crs)
-    {
-        final AttributeType<V> att;
-        if (crs != null) {
-            final AttributeType<CoordinateReferenceSystem> qualifier = new DefaultAttributeType<>(
-                    Collections.singletonMap(DefaultFeatureType.NAME_KEY, CRS_CHARACTERISTIC),
-                    CoordinateReferenceSystem.class, 1, 1, crs);
-            att = new DefaultAttributeType<>(
-                Collections.singletonMap(DefaultFeatureType.NAME_KEY, name),
-                valueClass, minimumOccurs, maximumOccurs, defaultValue, qualifier);
-        } else {
-            att = new DefaultAttributeType<>(
-                Collections.singletonMap(DefaultFeatureType.NAME_KEY, name),
-                valueClass, minimumOccurs, maximumOccurs, defaultValue);
+     * <ul>
+     *   <li>{@link FeatureTypeBuilder#addIdentifier(Class)}</li>
+     *   <li>{@link FeatureTypeBuilder#addDefaultGeometry(Class)}</li>
+     *   <li>{@link FeatureTypeBuilder#addAttribute(Class)}</li>
+     *   <li>{@link FeatureTypeBuilder#addAssociation(FeatureType)}</li>
+     *   <li>{@link FeatureTypeBuilder#addAssociation(GenericName)}</li>
+     * </ul>
+     *
+     * @param <V> the class of property values.
+     */
+    public final class Property<V> extends Builder<Property<V>> {
+        /**
+         * The class of property values. Can not be changed after construction
+         * because this value determines the parameterized type {@code <V>}.
+         */
+        private final Class<V> valueClass;
+
+        /**
+         * The default value for the property, or {@code null} if none.
+         */
+        private V defaultValue;
+
+        /**
+         * The minimum number of property values.
+         * The default value is 1, unless otherwise specified by {@link #setDefaultCardinality(int, int)}.
+         *
+         * @see #setCardinality(int, int)
+         */
+        private int minimumOccurs;
+
+        /**
+         * The maximum number of property values. The default value is 1.
+         * The default value is 1, unless otherwise specified by {@link #setDefaultCardinality(int, int)}.
+         *
+         * @see #setCardinality(int, int)
+         */
+        private int maximumOccurs;
+
+        /**
+         * Builders for the characteristics associated to the attribute.
+         */
+        private final List<Characteristic<?>> characteristics;
+
+        /**
+         * Creates a new {@code AttributeType} or {@code Operation} builder for values of the given class.
+         *
+         * @param valueClass the class of property values.
+         */
+        Property(final Class<V> valueClass) {
+            this.valueClass = valueClass;
+            minimumOccurs   = defaultMinimumOccurs;
+            maximumOccurs   = defaultMaximumOccurs;
+            characteristics = new ArrayList<>();
+        }
+
+        /**
+         * Creates a new {@code AssociationRole} builder for values of the given type.
+         * This constructor arbitrarily stores the feature type as an unnamed characteristic of this property.
+         *
+         * @param valueClass shall be {@code Feature.class}.
+         * @param typeClass  shall be either {@code FeatureType.class} or {@code GenericName.class}.
+         * @param type       the type of associated features.
+         */
+        <C> Property(final Class<V> valueClass, final Class<C> typeClass, final C type) {
+            this.valueClass = valueClass;
+            minimumOccurs   = defaultMinimumOccurs;
+            maximumOccurs   = defaultMaximumOccurs;
+            characteristics = Collections.singletonList(new Characteristic<>(typeClass).setDefaultValue(type));
+        }
+
+        /**
+         * Delegates the creation of a new name to the enclosing builder.
+         */
+        @Override
+        final GenericName name(final String scope, final String localPart) {
+            return FeatureTypeBuilder.this.name(scope, localPart);
+        }
+
+        /**
+         * Sets the minimum and maximum number of property values. Those numbers must be equal or greater than zero.
+         *
+         * <p>If this method is not invoked, then the default values are the cardinality specified by the last call
+         * to {@link #setDefaultCardinality(int, int)} at the time this {@code Property} instance has been created.
+         * If the later method has not invoked neither, then the default cardinality is [1 … 1].</p>
+         *
+         * @param  minimumOccurs  new minimum number of property values.
+         * @param  maximumOccurs  new maximum number of property values.
+         * @return {@code this} for allowing method calls chaining.
+         */
+        public Property<V> setCardinality(final int minimumOccurs, final int maximumOccurs) {
+            if (minimumOccurs < 0 || maximumOccurs < minimumOccurs) {
+                throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalRange_2, minimumOccurs, maximumOccurs));
+            }
+            this.minimumOccurs = minimumOccurs;
+            this.maximumOccurs = maximumOccurs;
+            return this;
+        }
+
+        /**
+         * Sets the default value for the property.
+         *
+         * @param  defaultValue  default property value, or {@code null} if none.
+         * @return {@code this} for allowing method calls chaining.
+         */
+        public Property<V> setDefaultValue(final V defaultValue) {
+            this.defaultValue = defaultValue;
+            return this;
+        }
+
+        /**
+         * Sets an enumeration of valid values for this attribute.
+         *
+         * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
+         * of type {@link Set} and a conventional name.</p>
+         *
+         * @param  values valid values.
+         * @return {@code this} for allowing method calls chaining.
+         * @throws UnsupportedOperationException if this property does not support characteristics.
+         *
+         * @see NameConvention#VALID_VALUES_CHARACTERISTIC
+         */
+        @SafeVarargs
+        public final Property<V> setValidValues(final V... values) {
+            return setCharacteristic(NameConvention.VALID_VALUES_CHARACTERISTIC,
+                    Set.class, CollectionsExt.immutableSet(false, values));
+        }
+
+        /**
+         * Sets the maximal length that characterizes the {@link CharSequence} values of this attribute.
+         * While this characteristic can be applied to any kind of attribute, it is meaningful only with
+         * character sequences.
+         *
+         * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
+         * of type {@link Integer} and a conventional name.</p>
+         *
+         * @param  length  maximal length of {@link CharSequence} attribute values, or {@code null}.
+         * @return {@code this} for allowing method calls chaining.
+         * @throws UnsupportedOperationException if this property does not support length characteristics.
+         *
+         * @see NameConvention#MAXIMAL_LENGTH_CHARACTERISTIC
+         */
+        public Property<V> setMaximalLengthCharacteristic(final Integer length) {
+            return setCharacteristic(NameConvention.MAXIMAL_LENGTH_CHARACTERISTIC, Integer.class, length);
+        }
+
+        /**
+         * Sets the coordinate reference system that characterizes the values of this attribute.
+         * While this characteristic can be applied to any kind of attribute, it is meaningful
+         * only with georeferenced values like geometries or coverages.
+         *
+         * <p>This is a convenience method for {@link #addCharacteristic(Class)} with a value
+         * of type {@link CoordinateReferenceSystem} and a conventional name.</p>
+         *
+         * @param  crs  coordinate reference system associated to attribute values, or {@code null}.
+         * @return {@code this} for allowing method calls chaining.
+         * @throws UnsupportedOperationException if this property does not support CRS characteristics.
+         *
+         * @see NameConvention#CRS_CHARACTERISTIC
+         */
+        public Property<V> setCRSCharacteristic(final CoordinateReferenceSystem crs) {
+            return setCharacteristic(NameConvention.CRS_CHARACTERISTIC, CoordinateReferenceSystem.class, crs);
+        }
+
+        /**
+         * Implementation of all setter methods for characteristics.
+         *
+         * @throws UnsupportedOperationException if this property does not support characteristics.
+         */
+        private <C> Property<V> setCharacteristic(final GenericName name, final Class<C> type, final C value) {
+            for (final Characteristic<?> characteristic : characteristics) {
+                if (name.equals(characteristic.identification.get(DefaultAttributeType.NAME_KEY))) {
+                    characteristic.set(value);
+                    return this;
+                }
+            }
+            addCharacteristic(type).setDefaultValue(value).setName(name);
+            return this;
         }
-        addProperty(att);
-        return att;
-    }
 
-    /**
-     * Add a custom property to the feature type.
-     *
-     * @param property user defined property type
-     * @return created property type
-     */
-    public PropertyType addProperty(final PropertyType property) {
-        properties.put(property.getName(), property);
-        return property;
+        /**
+         * Adds another attribute type that describes this attribute type.
+         * See <cite>"Attribute characterization"</cite> in {@link DefaultAttributeType} Javadoc for more information.
+         *
+         * <p>Usage example:</p>
+         * {@preformat java
+         *     attribute.addCharacteristic(Unit.class).setName("Unit of measurement").setDefaultValue(SI.CELSIUS);
+         * }
+         *
+         * Callers shall invoke at least one of the {@code Characteristic.setName(…)} methods on the returned instance.
+         * All other methods are optional.
+         *
+         * @param  <C>   the compile-time type of {@code type} argument.
+         * @param  type  the class of characteristic values.
+         * @return a builder for a characteristic of this attribute.
+         * @throws UnsupportedOperationException if this property does not support characteristics.
+         */
+        public <C> Characteristic<C> addCharacteristic(final Class<C> type) {
+            if (valueClass == Feature.class) {
+                throw new UnsupportedOperationException(errors().getString(Errors.Keys.IllegalOperationForValueClass_1, valueClass));
+            }
+            ArgumentChecks.ensureNonNull("type", type);
+            final Characteristic<C> characteristic = new Characteristic<>(type);
+            characteristics.add(characteristic);
+            return characteristic;
+        }
+
+        /**
+         * Creates a new property type from the current setting.
+         */
+        final PropertyType build() {
+            final PropertyType property;
+            if (valueClass == Feature.class) {
+                final Object type = CollectionsExt.first(characteristics).defaultValue;
+                if (type instanceof FeatureType) {
+                    property = new DefaultAssociationRole(identification, (FeatureType) type, minimumOccurs, maximumOccurs);
+                } else {
+                    property = new DefaultAssociationRole(identification, (GenericName) type, minimumOccurs, maximumOccurs);
+                }
+            } else {
+                final AttributeType<?>[] chrts = new AttributeType<?>[characteristics.size()];
+                for (int i=0; i<chrts.length; i++) {
+                    chrts[i] = characteristics.get(i).build();
+                }
+                property = new DefaultAttributeType<>(identification, valueClass, minimumOccurs, maximumOccurs, defaultValue, chrts);
+            }
+            return property;
+        }
     }
 
     /**
-     * Add a new association to the feature type.
-     * Association will have a minimum occurrence of zero and maximum occurrence of one.
+     * Describes one characteristic of the {@code AttributeType} to be built by the enclosing {@code FeatureTypeBuilder}.
+     * A different instance of {@code Characteristic} exists for each characteristic to describe.
+     * Those instances are created by:
      *
-     * @param scope property generic name scope part
-     * @param localPart property generic name tip part
-     * @param type associated feature type
-     * @return created association
-     */
-    public FeatureAssociationRole addAssociation(final String scope, final String localPart, final FeatureType type) {
-        return addAssociation(scope, localPart, type,0,1);
-    }
-
-    /**
-     * Add a new association to the feature type.
-     * Association will have a minimum occurrence of zero and maximum occurrence of one.
+     * <ul>
+     *   <li>{@link Property#addCharacteristic(Class)}</li>
+     * </ul>
      *
-     * @param name property name
-     * @param type associated feature type
-     * @return created association
+     * @param <V> the class of characteristic values.
      */
-    public FeatureAssociationRole addAssociation(final GenericName name, final FeatureType type) {
-        return addAssociation(name, type,0,1);
-    }
+    public final class Characteristic<V> extends Builder<Characteristic<V>> {
+        /**
+         * The class of attribute values. Can not be changed after construction
+         * because this value determines the parameterized type {@code <V>}.
+         */
+        private final Class<V> valueClass;
 
-    /**
-     * Add a new association to the feature type.
-     *
-     * @param scope property generic name scope part
-     * @param localPart property generic name tip part
-     * @param type associated feature type
-     * @param minimumOccurs property minimum number of occurrences
-     * @param maximumOccurs property maximum number of occurrences
-     * @return created association
-     */
-    public FeatureAssociationRole addAssociation(final String scope, final String localPart, final FeatureType type,
-            final int minimumOccurs, final int maximumOccurs)
-    {
-        return addAssociation(name(scope, localPart), type,minimumOccurs, maximumOccurs);
-    }
+        /**
+         * The default value for the attribute, or {@code null} if none.
+         */
+        V defaultValue;
 
-    /**
-     * Add a new association to the feature type.
-     *
-     * @param name property name
-     * @param type associated feature type
-     * @param minimumOccurs property minimum number of occurrences
-     * @param maximumOccurs property maximum number of occurrences
-     * @return created association
-     */
-    public FeatureAssociationRole addAssociation(final GenericName name, final FeatureType type,
-            final int minimumOccurs, final int maximumOccurs)
-    {
-        final FeatureAssociationRole role = new DefaultAssociationRole(
-                Collections.singletonMap(DefaultFeatureType.NAME_KEY, name), type, minimumOccurs, maximumOccurs);
-        addProperty(role);
-        return role;
-    }
+        /**
+         * Creates a new characteristic builder for values of the given class.
+         *
+         * @param valueClass the class of characteristic values.
+         */
+        Characteristic(final Class<V> valueClass) {
+            this.valueClass = valueClass;
+        }
 
-    /**
-     * Add a custom properties to the feature type.
-     *
-     * @param properties user defined property types
-     */
-    public void addProperties(final PropertyType ... properties) {
-        for (PropertyType pt : properties) {
-            this.properties.put(pt.getName(), pt);
+        /**
+         * Delegates the creation of a new name to the enclosing builder.
+         */
+        @Override
+        final GenericName name(final String scope, final String localPart) {
+            return FeatureTypeBuilder.this.name(scope, localPart);
         }
-    }
 
-    /**
-     * Add a custom properties to the feature type.
-     *
-     * @param properties user defined property types
-     */
-    public void addProperties(final Collection<? extends PropertyType> properties) {
-        for (PropertyType pt : properties) {
-            this.properties.put(pt.getName(), pt);
+        /**
+         * Sets the default value with check of the value class.
+         */
+        final void set(final Object value) {
+            setDefaultValue(valueClass.cast(value));
         }
-    }
 
-    /**
-     * Remove a property from the feature type.
-     *
-     * @param name property name
-     * @return removed property, can be null if property was not found
-     */
-    public PropertyType removeProperty(final String name) {
-        return properties.remove(valueOf(name));
-    }
+        /**
+         * Sets the default value for the characteristic.
+         *
+         * @param  value  characteristic default value, or {@code null} if none.
+         * @return {@code this} for allowing method calls chaining.
+         */
+        public Characteristic<V> setDefaultValue(final V value) {
+            defaultValue = value;
+            return this;
+        }
 
-    /**
-     * Remove a property from the feature type.
-     *
-     * @param name property name
-     * @return removed property, can be null if property was not found
-     */
-    public PropertyType removeProperty(final GenericName name) {
-        return properties.remove(name);
+        /**
+         * Creates a new characteristic from the current setting.
+         */
+        final AttributeType<V> build() {
+            return new DefaultAttributeType<>(identification, valueClass, 0, 1, defaultValue);
+        }
     }
 
     /**
-     * Build feature type
+     * Builds the feature type from the information and properties specified to this builder.
+     * One of the {@code setName(…)} methods must have been invoked before this {@code build()} method (mandatory).
+     * All other methods are optional, but some calls to a {@code add} method are usually needed.
      *
-     * @return FeatureType
+     * @return the new feature type.
+     * @throws IllegalStateException if the feature type contains incompatible
+     *         {@linkplain Property#setCRSCharacteristic CRS characteristics}.
      */
-    public DefaultFeatureType build() throws IllegalArgumentException {
-        //build id property
-        if (idAttributes != null) {
-            //check id properties exist
-            for (GenericName n : idAttributes) {
-                if (!properties.containsKey(n)) {
-                    throw new IllegalArgumentException("Property "+n+" used in id does not exist");
-                }
-            }
-
-            String prefix = idPrefix;
-            if (idPrefix==null) {
-                prefix = getName().tip().toString();
-            }
-
-            final Operation att = FeatureOperations.compound(
-                    Collections.singletonMap(AbstractOperation.NAME_KEY, ID_PROPERTY),
-                    idDelimiter, prefix, null, properties.get(idAttributes));
-            properties.put(ID_PROPERTY, att);
-        }
-        //build default geometry property
-        if (defGeomAttribute != null) {
-            if (!properties.containsKey(defGeomAttribute)) {
-                throw new IllegalArgumentException("Property "+defGeomAttribute+" used in default geometry does not exist");
+    public FeatureType build() throws IllegalStateException {
+        int numSynthetic;                                   // Number of synthetic properties to be generated.
+        int numSpecified = properties.size();               // Number of explicitely specified properties.
+        final PropertyType[] identifierTypes;
+        if (idAttributes.isEmpty()) {
+            identifierTypes = null;
+            numSynthetic = 0;
+        } else {
+            identifierTypes = new PropertyType[idAttributes.size()];
+            numSynthetic = 1;                               // Reserve one slot for the synthetic property "@id".
+        }
+        if (defaultGeometry != null) {
+            numSynthetic += 2;                              // One slot for "@defaultGeometry" and one for "@envelope".
+        }
+        PropertyType[] propertyTypes = new PropertyType[numSynthetic + numSpecified];
+        for (int i=0,j=numSynthetic; i<numSpecified; i++, j++) {
+            final Property<?>  builder  = properties.get(i);
+            final PropertyType instance = builder.build();
+            propertyTypes[j] = instance;
+            final int id = idAttributes.indexOf(builder);
+            if (id >= 0) {
+                identifierTypes[id] = instance;
             }
-            final PropertyType geomAtt = properties.get(defGeomAttribute);
-            final CoordinateReferenceSystem crs = null; // TODO NameConvention.getCoordinateReferenceSystem(geomAtt);
-            final Operation att = FeatureOperations.link(
-                    Collections.singletonMap(AbstractOperation.NAME_KEY, DEFAULT_GEOMETRY_PROPERTY), geomAtt);
-            properties.put(DEFAULT_GEOMETRY_PROPERTY, att);
-
-            final Operation boundAtt;
-            try {
-                boundAtt = FeatureOperations.envelope(Collections.singletonMap(AbstractOperation.NAME_KEY, ENVELOPE_PROPERTY),
-                        crs, properties.values().toArray(new PropertyType[properties.size()]));
-            } catch (FactoryException e) {
-                throw new IllegalStateException(e);
+            /*
+             * If there is a default geometry, add a link named "@geometry" to that geometry. It may happen
+             * that the property created by the user is already named "@geometry", in which case will will
+             * avoid to duplicate the property by removing the second occurrence.
+             */
+            if (builder == defaultGeometry) {
+                final PropertyType geom;
+                if (NameConvention.DEFAULT_GEOMETRY_PROPERTY.equals(instance.getName())) {
+                    propertyTypes = ArraysExt.remove(propertyTypes, j--, 1);
+                    geom = instance;
+                } else {
+                    geom = FeatureOperations.link(name(NameConvention.DEFAULT_GEOMETRY_PROPERTY), instance);
+                }
+                propertyTypes[numSynthetic - 1] = geom;
             }
-            properties.put(ENVELOPE_PROPERTY, boundAtt);
-
         }
-
-        verifyOperations();
-
-        return new DefaultFeatureType(
-                identification,
-                isAbstract,
-                superTypes.toArray(new FeatureType[superTypes.size()]),
-                properties.values().toArray(new PropertyType[properties.size()]));
+        /*
+         * Create the "envelope" operation only after we created all other properties, except "@id" which is not needed
+         * for envelope. It is okay if the 'propertyTypes' array still contains null elements like the "@id" one, since
+         * FeatureOperations.envelope(…) constructor will ignore any property which is not for a value.
+         */
+        if (defaultGeometry != null) try {
+            propertyTypes[numSynthetic - 2] = FeatureOperations.envelope(name(NameConvention.ENVELOPE_PROPERTY), null, propertyTypes);
+        } catch (FactoryException e) {
+            throw new IllegalStateException(e);
+        }
+        if (identifierTypes != null) {
+            propertyTypes[0] = FeatureOperations.compound(name(NameConvention.ID_PROPERTY), idDelimiter, idPrefix, idSuffix, identifierTypes);
+        }
+        return new DefaultFeatureType(identification, isAbstract, superTypes.toArray(new FeatureType[superTypes.size()]), propertyTypes);
     }
 
     /**
-     * Check operations have the required properties.
-     *
-     * @throws IllegalArgumentException if some properties are missing for an operation.
+     * Helper method for creating identification info of synthetic attributes.
      */
-    private void verifyOperations() throws IllegalArgumentException {
-        for (PropertyType pt : properties.values()) {
-            if (pt instanceof AbstractOperation) {
-                final Set<String> dependencies = ((AbstractOperation)pt).getDependencies();
-                depLoop:
-                for (String dep : dependencies) {
-                    for (GenericName gn : properties.keySet()) {
-                        if (match(gn, dep)) continue depLoop;
-                    }
-                    throw new IllegalArgumentException("Operation "+pt.getName().toString()+" requiere property "+dep+" but this property is missing.");
-                }
-
-            }
-        }
-
+    private static Map<String,?> name(final GenericName name) {
+        return Collections.singletonMap(AbstractOperation.NAME_KEY, name);
     }
 
-    ////////////////////////////////////////////////////////////////////////////
-    // Name utils //////////////////////////////////////////////////////////////
-    ////////////////////////////////////////////////////////////////////////////
-
     /**
-     *
-     * @param parsedNames mandatory
-     * @return GenericName
+     * Invoked by {@link Builder} for creating new {@code LocalName} or {@code GenericName} instances.
      */
     @Override
-    final GenericName name(final String scope, final String localPart) {
+    final GenericName name(String scope, final String localPart) {
         if (scope == null) {
+            scope = defaultScope;
+        }
+        if (scope == null || scope.isEmpty()) {
             return nameFactory.createLocalName(null, localPart);
         } else {
             return nameFactory.createGenericName(null, scope, localPart);
@@ -653,82 +760,9 @@ public class FeatureTypeBuilder extends
     }
 
     /**
-     * Parse a string value that can be expressed in 2 different forms :
-     * JSR-283 extended form : {uri}localpart
-     * Separator form : uri:localpart
-     *
-     * if the given string do not match any, then a Name with no namespace will be
-     * created and the localpart will be the given string.
-     *
-     * @param candidate String to convert to a geoneric name
-     * @return Name
-     */
-    private GenericName valueOf(final String candidate) {
-
-        if (candidate.startsWith("{")) {
-            //name is in extended form
-            return toSessionNamespaceFromExtended(candidate);
-        }
-
-        int index = candidate.lastIndexOf(':');
-
-        if (index <= 0) {
-            return name(null, candidate);
-        } else {
-            final String uri = candidate.substring(0,index);
-            final String name = candidate.substring(index+1,candidate.length());
-            return name(uri, name);
-        }
-
-    }
-
-    private GenericName toSessionNamespaceFromExtended(final String candidate) {
-        final int index = candidate.indexOf('}');
-
-        if (index < 0) throw new IllegalArgumentException("Invalide extended form : "+ candidate);
-
-        final String uri = candidate.substring(1, index);
-        final String name = candidate.substring(index+1, candidate.length());
-
-        return name(uri, name);
-    }
-
-    private static String toExpandedString(final GenericName name) {
-        String ns = getNamespace(name);
-        if (ns == null) {
-            return name.tip().toString();
-        } else {
-            return '{' + ns + '}' + name.tip();
-        }
-    }
-
-    /**
-     * Tests that the given string representation matches the given name.
-     * String can be written with only the local part or in extendedform or JCR
-     * extended form.
-     *
-     * @param name expected generic name
-     * @param candidate name to test
-     * @return true if the string match the name
+     * Returns the resources for error messages.
      */
-    private static boolean match(final GenericName name, final String candidate) {
-        if (candidate.startsWith("{")) {
-            //candidate is in extended form
-            return candidate.equals(toExpandedString(name));
-        }
-
-        final int index = candidate.lastIndexOf(':');
-
-        if (index <= 0) {
-            return candidate.equals(name.tip().toString());
-        } else {
-            final String uri = candidate.substring(0,index);
-            final String local = candidate.substring(index+1,candidate.length());
-            return uri.equals(getNamespace(name)) && local.equals(name.tip().toString());
-        }
-    }
-
-    private static String getNamespace(final GenericName name) {
-        return (name instanceof ScopedName) ? ((ScopedName)name).path().toString() : null;
+    final Errors errors() {
+        return Errors.getResources(identification);
     }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/NameConvention.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/NameConvention.java?rev=1740141&r1=1740140&r2=1740141&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/NameConvention.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/internal/feature/NameConvention.java [UTF-8] Wed Apr 20 13:01:55 2016
@@ -136,6 +136,8 @@ public final class NameConvention extend
      *
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
      * {@link org.opengis.referencing.crs.CoordinateReferenceSystem}.</p>
+     *
+     * @see #getCRSCharacteristic(Property)
      */
     public static final LocalName CRS_CHARACTERISTIC;
 
@@ -148,6 +150,8 @@ public final class NameConvention extend
      *
      * <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
      * {@link Integer}.</p>
+     *
+     * @see #getMaximalLengthCharacteristic(Property)
      */
     public static final LocalName MAXIMAL_LENGTH_CHARACTERISTIC;
 
@@ -165,7 +169,7 @@ public final class NameConvention extend
         NAMESPACE                     = factory.createGenericName(null, "Apache", Constants.SIS);
         NameSpace ns                  = factory.createNameSpace(NAMESPACE, null);
         ID_PROPERTY                   = factory.createLocalName(ns, "@identifier");
-        DEFAULT_GEOMETRY_PROPERTY     = factory.createLocalName(ns, "@defaultGeometry");
+        DEFAULT_GEOMETRY_PROPERTY     = factory.createLocalName(ns, "@geometry");
         ENVELOPE_PROPERTY             = factory.createLocalName(ns, "@envelope");
         CRS_CHARACTERISTIC            = factory.createLocalName(ns, "@crs");
         MAXIMAL_LENGTH_CHARACTERISTIC = factory.createLocalName(ns, "@maximalLength");
@@ -248,8 +252,10 @@ public final class NameConvention extend
      * @return The Coordinate Reference System characteristic of the given attribute, or {@code null} if none.
      * @throws ClassCastException if {@link #CRS_CHARACTERISTIC} has been found but is associated
      *         to an object which is not a {@link CoordinateReferenceSystem} instance.
+     *
+     * @see org.apache.sis.internal.feature.FeatureTypeBuilder.Property#setCRSCharacteristic(CoordinateReferenceSystem)
      */
-    public static CoordinateReferenceSystem getCrsCharacteristic(final Property attribute) {
+    public static CoordinateReferenceSystem getCRSCharacteristic(final Property attribute) {
         return (CoordinateReferenceSystem) getCharacteristic(attribute, CRS_CHARACTERISTIC.toString());
     }
 
@@ -273,6 +279,8 @@ public final class NameConvention extend
      * @return The Coordinate Reference System characteristic of the given attribute, or {@code null} if none.
      * @throws ClassCastException if {@link #MAXIMAL_LENGTH_CHARACTERISTIC} has been found but is associated
      *         to an object which is not an {@link Integer} instance.
+     *
+     * @see org.apache.sis.internal.feature.FeatureTypeBuilder.Property#setMaximalLengthCharacteristic(Integer)
      */
     public static Integer getMaximalLengthCharacteristic(final Property attribute) {
         return (Integer) getCharacteristic(attribute, MAXIMAL_LENGTH_CHARACTERISTIC.toString());



Mime
View raw message