sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1751913 - in /sis/branches/JDK8: core/sis-feature/src/main/java/org/apache/sis/internal/feature/ core/sis-feature/src/test/java/org/apache/sis/internal/feature/ storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/
Date Fri, 08 Jul 2016 14:23:54 GMT
Author: desruisseaux
Date: Fri Jul  8 14:23:53 2016
New Revision: 1751913

URL: http://svn.apache.org/viewvc?rev=1751913&view=rev
Log:
Allow to create a builder using an existing IdentifiedType as a template.

Modified:
    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/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXConstants.java

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=1751913&r1=1751912&r2=1751913&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] Fri Jul  8 14:23:53 2016
@@ -28,13 +28,17 @@ import org.apache.sis.util.Localized;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Debug;
 
+// Branch-dependent imports
+import java.util.Objects;
+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.
+ * @param <B> the builder subclass. It is subclass responsibility to ensure that {@code this}
+ *            is assignable to {@code <B>}; this {@code Builder} class can not verify that.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -42,7 +46,7 @@ import org.apache.sis.util.Debug;
  * @version 0.8
  * @module
  */
-abstract class Builder<T extends Builder<T>> implements Localized {
+abstract class Builder<B extends Builder<B>> implements Localized {
     /**
      * The feature name, definition, designation and description.
      * The name is mandatory; all other information are optional.
@@ -50,43 +54,33 @@ abstract class Builder<T extends Builder
     private final Map<String,Object> identification = new HashMap<>(4);
 
     /**
-     * Creates a new builder instance which will format error message using the given locale.
+     * Creates a new builder initialized to the values of an existing type.
      */
-    Builder(final Locale locale) {
-        setLocale(locale);
-    }
-
-    /**
-     * Creates a new builder instance.
-     */
-    Builder(final Builder<?> parent) {
-        setLocale(parent.identification.get(Errors.LOCALE_KEY));
+    Builder(final IdentifiedType template, final Locale locale) {
+        putIfNonNull(Errors.LOCALE_KEY, locale);
+        if (template != null) {
+            putIfNonNull(AbstractIdentifiedType.NAME_KEY,        template.getName());
+            putIfNonNull(AbstractIdentifiedType.DEFINITION_KEY,  template.getDefinition());
+            putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation());
+            putIfNonNull(AbstractIdentifiedType.DESCRIPTION_KEY, template.getDescription());
+        }
     }
 
     /**
-     * Sets the locale if non-null. This method should be invoked only when the {@link #identification} map is empty.
-     *
-     * @see #getLocale()
-     */
-    private void setLocale(final Object locale) {
-        if (locale != null) {
-            identification.put(Errors.LOCALE_KEY, locale);
+     * Puts the given value in the {@link #identification} map if the value is non-null.
+     * This method should be invoked only when the {@link #identification} map is known
+     * to not contain any value for the given key.
+     */
+    private void putIfNonNull(final String key, final Object value) {
+        if (value != null) {
+            identification.put(key, value);
         }
     }
 
     /**
-     * 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.
+     * If the object created by the last call to {@code build()} has been cached, clears that cache.
      */
-    @SuppressWarnings("unchecked")
-    public T clear() {
-        final Object locale = identification.get(Errors.LOCALE_KEY);
-        identification.clear();
-        setLocale(locale);
-        return (T) this;
-    }
+    abstract void clearCache();
 
     /**
      * Creates a generic name from the given scope and local part.
@@ -147,7 +141,7 @@ abstract class Builder<T extends Builder
      * @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 localPart) {
+    public B setName(final String localPart) {
         ensureNonEmpty("localPart", localPart);
         return setName(name(null, localPart));
     }
@@ -167,7 +161,7 @@ abstract class Builder<T extends Builder
      * @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) {
+    public B setName(String scope, final String localPart) {
         ensureNonEmpty("localPart", localPart);
         if (scope == null) {
             scope = "";                                 // For preventing the use of default scope.
@@ -189,10 +183,12 @@ abstract class Builder<T extends Builder
      * @see AbstractIdentifiedType#NAME_KEY
      */
     @SuppressWarnings("unchecked")
-    public T setName(GenericName name) {
+    public B setName(final GenericName name) {
         ensureNonNull("name", name);
-        identification.put(AbstractIdentifiedType.NAME_KEY, name);
-        return (T) this;
+        if (!name.equals(identification.put(AbstractIdentifiedType.NAME_KEY, name))) {
+            clearCache();
+        }
+        return (B) this;
     }
 
     /**
@@ -223,9 +219,11 @@ abstract class Builder<T extends Builder
      * @see AbstractIdentifiedType#DEFINITION_KEY
      */
     @SuppressWarnings("unchecked")
-    public T setDefinition(CharSequence definition) {
-        identification.put(AbstractIdentifiedType.DEFINITION_KEY, definition);
-        return (T) this;
+    public B setDefinition(final CharSequence definition) {
+        if (!Objects.equals(definition, identification.put(AbstractIdentifiedType.DEFINITION_KEY, definition))) {
+            clearCache();
+        }
+        return (B) this;
     }
 
     /**
@@ -238,9 +236,11 @@ abstract class Builder<T extends Builder
      * @see AbstractIdentifiedType#DESIGNATION_KEY
      */
     @SuppressWarnings("unchecked")
-    public T setDesignation(CharSequence designation) {
-        identification.put(AbstractIdentifiedType.DESIGNATION_KEY, designation);
-        return (T) this;
+    public B setDesignation(final CharSequence designation) {
+        if (!Objects.equals(designation, identification.put(AbstractIdentifiedType.DESIGNATION_KEY, designation))) {
+            clearCache();
+        }
+        return (B) this;
     }
 
     /**
@@ -253,9 +253,11 @@ abstract class Builder<T extends Builder
      * @see AbstractIdentifiedType#DESCRIPTION_KEY
      */
     @SuppressWarnings("unchecked")
-    public T setDescription(CharSequence description) {
-        identification.put(AbstractIdentifiedType.DESCRIPTION_KEY, description);
-        return (T) this;
+    public B setDescription(final CharSequence description) {
+        if (!Objects.equals(description, identification.put(AbstractIdentifiedType.DESCRIPTION_KEY, description))) {
+            clearCache();
+        }
+        return (B) this;
     }
 
     /**
@@ -270,26 +272,6 @@ abstract class Builder<T extends Builder
     }
 
     /**
-     * Returns a string representation of this object.
-     * The returned string is for debugging purpose only and may change in any future SIS version.
-     *
-     * @return a string representation of this object for debugging purpose.
-     */
-    @Debug
-    @Override
-    public String toString() {
-        final StringBuilder buffer = new StringBuilder(Classes.getShortClassName(this));
-        toStringInternal(buffer.append("[“").append(getDisplayName()).append('”'));
-        return buffer.append(']').toString();
-    }
-
-    /**
-     * Appends a text inside the value returned by {@link #toString()}, before the closing bracket.
-     */
-    void toStringInternal(StringBuilder buffer) {
-    }
-
-    /**
      * Returns the resources for error messages.
      */
     final Errors errors() {
@@ -327,4 +309,31 @@ abstract class Builder<T extends Builder
             throw new IllegalArgumentException(errors().getString(Errors.Keys.EmptyArgument_1, name));
         }
     }
+
+    /**
+     * Returns a string representation of this object.
+     * The returned string is for debugging purpose only and may change in any future SIS version.
+     *
+     * @return a string representation of this object for debugging purpose.
+     */
+    @Debug
+    @Override
+    public String toString() {
+        return toString(new StringBuilder(Classes.getShortClassName(this))).toString();
+    }
+
+    /**
+     * Partial implementation of {@link #toString()}. This method assumes that the class name
+     * has already been written in the buffer.
+     */
+    final StringBuilder toString(final StringBuilder buffer) {
+        toStringInternal(buffer.append("[“").append(getDisplayName()).append('”'));
+        return buffer.append(']');
+    }
+
+    /**
+     * Appends a text inside the value returned by {@link #toString()}, before the closing bracket.
+     */
+    void toStringInternal(StringBuilder buffer) {
+    }
 }

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=1751913&r1=1751912&r2=1751913&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] Fri Jul  8 14:23:53 2016
@@ -34,15 +34,18 @@ import org.apache.sis.feature.DefaultFea
 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.CorruptedObjectException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Classes;
 
 // Branch-dependent imports
+import java.util.Objects;
 import org.opengis.feature.AttributeType;
 import org.opengis.feature.Feature;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
+import org.opengis.feature.FeatureAssociationRole;
 
 
 /**
@@ -64,23 +67,6 @@ import org.opengis.feature.PropertyType;
  */
 public class FeatureTypeBuilder extends Builder<FeatureTypeBuilder> {
     /**
-     * Index of the property where to store the identifier generated by {@link #build()}.
-     */
-    private static final int IDENTIFIER_INDEX = 0;
-
-    /**
-     * Index of the property where to store the envelope generated by {@link #build()}.
-     * Must follow {@link #IDENTIFIER_INDEX}.
-     */
-    private static final int ENVELOPE_INDEX = 1;
-
-    /**
-     * Index of the property where to store the geometry generated by {@link #build()}.
-     * Must follow {@link #ENVELOPE_INDEX}.
-     */
-    private static final int GEOMETRY_INDEX = 2;
-
-    /**
      * The factory to use for creating names.
      */
     private final NameFactory nameFactory;
@@ -129,59 +115,91 @@ public class FeatureTypeBuilder extends
 
     /**
      * The separator to insert between each single component in a {@linkplain FeatureOperations#compound compound key}
-     * named {@code "@identifier"}.
+     * named {@code "@identifier"}. This is ignored if {@link #identifierCount} is zero.
      */
     private String idDelimiter;
 
     /**
+     * Number of attribute that have been flagged as an identifier component.
+     *
+     * @see Attribute.Role#IDENTIFIER_COMPONENT
+     * @see AttributeConvention#IDENTIFIER_PROPERTY
+     */
+    private int identifierCount;
+
+    /**
      * The default geometry attribute, or {@code null} if none.
      *
+     * @see Attribute.Role#DEFAULT_GEOMETRY
      * @see AttributeConvention#GEOMETRY_PROPERTY
      */
     private Attribute<?> defaultGeometry;
 
     /**
+     * The object created by this builder, or {@code null} if not yet created.
+     * This field must be cleared every time that a setter method is invoked on this builder.
+     */
+    private transient FeatureType feature;
+
+    /**
      * Creates a new builder instance using the default name factory.
      */
     public FeatureTypeBuilder() {
-        this(DefaultFactories.forBuildin(NameFactory.class), null);
+        this(null, null, null);
     }
 
     /**
-     * Creates a new builder instance using the given name factory.
+     * Creates a new builder instance using the given feature type as a template.
      *
-     * @param factory  the factory to use for creating names.
-     * @param locale   the locale to use for formatting error messages, or {@code null} for the default locale.
+     * @param template  an existing feature type to use as a template, or {@code null} if none.
      */
-    public FeatureTypeBuilder(final NameFactory factory, final Locale locale) {
-        super(locale);
-        nameFactory  = factory;
-        properties   = new ArrayList<>();
-        superTypes   = new ArrayList<>();
-        idDelimiter  = ":";
+    public FeatureTypeBuilder(final FeatureType template) {
+        this(template, null, null);
+    }
+
+    /**
+     * Creates a new builder instance using the given name factory, template and locale for formatting error messages.
+     *
+     * @param template  an existing feature type to use as a template, or {@code null} if none.
+     * @param factory   the factory to use for creating names, or {@code null} for the default factory.
+     * @param locale    the locale to use for formatting error messages, or {@code null} for the default locale.
+     */
+    public FeatureTypeBuilder(final FeatureType template, NameFactory factory, final Locale locale) {
+        super(template, locale);
+        if (factory == null) {
+            factory = DefaultFactories.forBuildin(NameFactory.class);
+        }
+        nameFactory = factory;
+        properties  = new ArrayList<>();
+        superTypes  = new ArrayList<>();
+        idDelimiter = ":";
         defaultMinimumOccurs = 1;
         defaultMaximumOccurs = 1;
+        if (template != null) {
+            feature    = template;
+            isAbstract = template.isAbstract();
+            superTypes.addAll(template.getSuperTypes());
+            for (final PropertyType p : template.getProperties(false)) {
+                final Property<?> builder;
+                if (p instanceof AttributeType<?>) {
+                    builder = new Attribute<>(this, (AttributeType<?>) p);
+                } else if (p instanceof FeatureAssociationRole) {
+                    builder = new Association(this, (FeatureAssociationRole) p);
+                } else {
+                    continue;           // Skip unknown types.
+                }
+                properties.add(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.
+     * If a {@code FeatureType} has been created by the last call to {@link #build()} has been cached,
+     * clears that cache. This method must be invoked every time that a setter method is invoked.
      */
     @Override
-    public FeatureTypeBuilder clear() {
-        super.clear();
-        properties.clear();
-        superTypes.clear();
-        idDelimiter     = ":";
-        idPrefix        = null;
-        idSuffix        = null;
-        isAbstract      = false;
-        defaultGeometry = null;
-        defaultMinimumOccurs = 1;
-        defaultMaximumOccurs = 1;
-        return this;
+    final void clearCache() {
+        feature = null;
     }
 
     /**
@@ -192,7 +210,10 @@ public class FeatureTypeBuilder extends
      * @return {@code this} for allowing method calls chaining.
      */
     public FeatureTypeBuilder setAbstract(final boolean isAbstract) {
-        this.isAbstract = isAbstract;
+        if (this.isAbstract != isAbstract) {
+            this.isAbstract  = isAbstract;
+            clearCache();
+        }
         return this;
     }
 
@@ -205,8 +226,12 @@ public class FeatureTypeBuilder extends
      */
     public FeatureTypeBuilder setSuperTypes(final FeatureType... parents) {
         ensureNonNull("parents", parents);
-        superTypes.clear();
-        superTypes.addAll(Arrays.asList(parents));
+        final List<FeatureType> asList = Arrays.asList(parents);
+        if (!superTypes.equals(asList)) {
+            superTypes.clear();
+            superTypes.addAll(asList);
+            clearCache();
+        }
         return this;
     }
 
@@ -218,6 +243,8 @@ public class FeatureTypeBuilder extends
      */
     public FeatureTypeBuilder setDefaultScope(final String scope) {
         defaultScope = scope;
+        // No need to clear the cache because this change affects
+        // only the next names to be created, not the existing ones.
         return this;
     }
 
@@ -240,12 +267,14 @@ public class FeatureTypeBuilder extends
         }
         defaultMinimumOccurs = minimumOccurs;
         defaultMaximumOccurs = maximumOccurs;
+        // No need to clear the cache because this change affects only
+        // the next properties to be created, not the existing ones.
         return this;
     }
 
     /**
      * Sets the prefix, suffix and delimiter to use when formatting a compound identifier made of two or more attributes.
-     * The delimiter will be used only if at least two attributes have the {@linkplain AttributeRole#IDENTIFIER_COMPONENT
+     * The delimiter will be used only if at least two attributes have the {@linkplain Attribute.Role#IDENTIFIER_COMPONENT
      * identifier component role}.
      *
      * <p>If this method is not invoked, then the default values are the {@code ":"} delimiter and no prefix or suffix.</p>
@@ -255,14 +284,17 @@ public class FeatureTypeBuilder extends
      * @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.
      *
-     * @see AttributeRole#IDENTIFIER_COMPONENT
+     * @see Attribute.Role#IDENTIFIER_COMPONENT
      * @see FeatureOperations#compound(Map, String, String, String, PropertyType...)
      */
     public FeatureTypeBuilder setIdentifierDelimiters(final String delimiter, final String prefix, final String suffix) {
         ensureNonEmpty("delimiter", delimiter);
-        idDelimiter = delimiter;
-        idPrefix    = prefix;
-        idSuffix    = suffix;
+        if (!delimiter.equals(idDelimiter) || !Objects.equals(prefix, idPrefix) || !Objects.equals(suffix, idSuffix)) {
+            idDelimiter = delimiter;
+            idPrefix    = prefix;
+            idSuffix    = suffix;
+            clearCache();
+        }
         return this;
     }
 
@@ -289,8 +321,24 @@ public class FeatureTypeBuilder extends
             // We disallow Feature.class because that type shall be handled as association instead than attribute.
             throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalArgumentValue_2, "valueClass", valueClass));
         }
-        final Attribute<V> property = new Attribute<>(valueClass);
+        final Attribute<V> property = new Attribute<>(this, valueClass);
         properties.add(property);
+        clearCache();
+        return property;
+    }
+
+    /**
+     * Creates a new {@code AttributeType} builder initialized to the same characteristics than the given template.
+     *
+     * @param  <V>       the compile-time type of values in the {@code template} argument.
+     * @param  template  an existing attribute type to use as a template.
+     * @return a builder for an {@code AttributeType}, initialized with the values of the given template.
+     */
+    public <V> Attribute<V> addAttribute(final AttributeType<V> template) {
+        ensureNonNull("template", template);
+        final Attribute<V> property = new Attribute<>(this, template);
+        properties.add(property);
+        clearCache();
         return property;
     }
 
@@ -304,8 +352,9 @@ public class FeatureTypeBuilder extends
      */
     public Association addAssociation(final FeatureType type) {
         ensureNonNull("type", type);
-        final Association property = new Association(type, type.getName());
+        final Association property = new Association(this, type, type.getName());
         properties.add(property);
+        clearCache();
         return property;
     }
 
@@ -319,8 +368,24 @@ public class FeatureTypeBuilder extends
      */
     public Association addAssociation(final GenericName type) {
         ensureNonNull("type", type);
-        final Association property = new Association(null, type);
+        final Association property = new Association(this, null, type);
+        properties.add(property);
+        clearCache();
+        return property;
+    }
+
+    /**
+     * Creates a new {@code FeatureAssociationRole} builder initialized to the same characteristics
+     * than the given template.
+     *
+     * @param  template  an existing feature association to use as a template.
+     * @return a builder for an {@code FeatureAssociationRole}, initialized with the values of the given template.
+     */
+    public Association addAssociation(final FeatureAssociationRole template) {
+        ensureNonNull("template", template);
+        final Association property = new Association(this, template);
         properties.add(property);
+        clearCache();
         return property;
     }
 
@@ -337,10 +402,19 @@ public class FeatureTypeBuilder extends
      *   <li>{@link FeatureTypeBuilder#addAssociation(GenericName)}</li>
      * </ul>
      *
-     * @param <T> the property subclass. It is subclass responsibility to ensure that {@code this}
-     *            is assignable to {@code <T>}; this {@code Property} class can not verify that.
+     * @param <B> the property subclass. It is subclass responsibility to ensure that {@code this}
+     *            is assignable to {@code <B>}; this {@code Property} class can not verify that.
      */
-    abstract class Property<T extends Property<T>> extends Builder<T> {
+    static abstract class Property<B extends Property<B>> extends Builder<B> {
+        /**
+         * The feature type builder instance that created this {@code Property} builder.
+         *
+         * <div class="note">We could replace this reference by a non-static {@code Property} class.
+         * But we do not for consistency with {@link Characteristic} and for allowing the inner
+         * {@code Attribute.Role} enumeration.</div>
+         */
+        final FeatureTypeBuilder owner;
+
         /**
          * The minimum number of property values.
          * The default value is 1, unless otherwise specified by {@link #setDefaultCardinality(int, int)}.
@@ -358,12 +432,23 @@ public class FeatureTypeBuilder extends
         int maximumOccurs;
 
         /**
-         * Creates a new property initialized to the default cardinality.
+         * The attribute or association created by this builder, or {@code null} if not yet created.
+         * This field must be cleared every time that a setter method is invoked on this builder.
+         */
+        private transient PropertyType property;
+
+        /**
+         * Creates a new {@code PropertyType} builder initialized to the values of an existing property.
+         *
+         * @param owner     the builder of the {@code FeatureType} for which to add this property.
+         * @param template  an existing property to use as a template, or {@code null} if none.
          */
-        Property() {
-            super(FeatureTypeBuilder.this);
-            minimumOccurs = defaultMinimumOccurs;
-            maximumOccurs = defaultMaximumOccurs;
+        Property(final FeatureTypeBuilder owner, final PropertyType template) {
+            super(template, owner.getLocale());
+            this.owner    = owner;
+            minimumOccurs = owner.defaultMinimumOccurs;
+            maximumOccurs = owner.defaultMaximumOccurs;
+            property      = template;
         }
 
         /**
@@ -378,13 +463,23 @@ public class FeatureTypeBuilder extends
          * @return {@code this} for allowing method calls chaining.
          */
         @SuppressWarnings("unchecked")
-        public T 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 (T) this;
+        public B setCardinality(final int minimumOccurs, final int maximumOccurs) {
+            if (this.minimumOccurs != minimumOccurs || this.maximumOccurs != maximumOccurs) {
+                if (minimumOccurs < 0 || maximumOccurs < minimumOccurs) {
+                    throw new IllegalArgumentException(errors().getString(Errors.Keys.IllegalRange_2, minimumOccurs, maximumOccurs));
+                }
+                this.minimumOccurs = minimumOccurs;
+                this.maximumOccurs = maximumOccurs;
+                clearCache();
+            }
+            return (B) this;
+        }
+
+        /**
+         * Returns {@code true} if {@link Attribute.Role#IDENTIFIER_COMPONENT} has been associated to this property.
+         */
+        boolean isIdentifier() {
+            return false;
         }
 
         /**
@@ -392,20 +487,34 @@ public class FeatureTypeBuilder extends
          */
         @Override
         final GenericName name(final String scope, final String localPart) {
-            return FeatureTypeBuilder.this.name(scope, localPart);
+            return owner.name(scope, localPart);
         }
 
         /**
-         * Returns {@code true} if {@link AttributeRole#IDENTIFIER_COMPONENT} has been associated to this property.
+         * If a {@code PropertyType} has been created by the last call to {@link #build()} has been cached,
+         * clears that cache. This method must be invoked every time that a setter method is invoked.
          */
-        boolean isIdentifier() {
-            return false;
+        @Override
+        final void clearCache() {
+            property = null;
+            owner.clearCache();
+        }
+
+        /**
+         * Returns the property type from the current setting.
+         * This method may return an existing property if it was already created.
+         */
+        final PropertyType build() {
+            if (property == null) {
+                property = create();
+            }
+            return property;
         }
 
         /**
          * Creates a new property type from the current setting.
          */
-        abstract PropertyType build();
+        abstract PropertyType create();
     }
 
 
@@ -420,7 +529,7 @@ public class FeatureTypeBuilder extends
      * @see FeatureTypeBuilder#addAssociation(FeatureType)
      * @see FeatureTypeBuilder#addAssociation(GenericName)
      */
-    public final class Association extends Property<Association> {
+    public static final class Association extends Property<Association> {
         /**
          * The target feature type, or {@code null} if unknown.
          */
@@ -434,33 +543,35 @@ public class FeatureTypeBuilder extends
         /**
          * Creates a new {@code AssociationRole} builder for values of the given type.
          * The {@code type} argument can be null if unknown, but {@code typeName} is mandatory.
+         *
+         * @param owner  the builder of the {@code FeatureType} for which to add this property.
          */
-        Association(final FeatureType type, final GenericName typeName) {
+        Association(final FeatureTypeBuilder owner, final FeatureType type, final GenericName typeName) {
+            super(owner, null);
             this.type     = type;
             this.typeName = typeName;
         }
 
         /**
-         * Returns a default name to use if the user did not specified a name. The first letter will be changed to
-         * lower case (unless the name looks like an acronym) for compliance with Java convention on property names.
+         * Creates a new {@code FeatureAssociationRole} builder initialized to the values of an existing association.
+         *
+         * @param owner  the builder of the {@code FeatureType} for which to add this property.
          */
-        @Override
-        final String getDefaultName() {
-            return typeName.tip().toString();
+        Association(final FeatureTypeBuilder owner, final FeatureAssociationRole template) {
+            super(owner, template);
+            minimumOccurs = template.getMinimumOccurs();
+            maximumOccurs = template.getMaximumOccurs();
+            type          = template.getValueType();
+            typeName      = type.getName();
         }
 
         /**
-         * Creates a new property type from the current setting.
+         * Returns a default name to use if the user did not specified a name. The first letter will be changed to
+         * lower case (unless the name looks like an acronym) for compliance with Java convention on property names.
          */
         @Override
-        final PropertyType build() {
-            final PropertyType property;
-            if (type != null) {
-                property = new DefaultAssociationRole(identification(), type, minimumOccurs, maximumOccurs);
-            } else {
-                property = new DefaultAssociationRole(identification(), typeName, minimumOccurs, maximumOccurs);
-            }
-            return property;
+        final String getDefaultName() {
+            return typeName.tip().toString();
         }
 
         /**
@@ -470,53 +581,18 @@ public class FeatureTypeBuilder extends
         final void toStringInternal(final StringBuilder buffer) {
             buffer.append(" → ").append(typeName);
         }
-    }
-
 
-
-
-    /**
-     * Roles that can be associated to some attributes for instructing {@code FeatureTypeBuilder}
-     * how to generate pre-defined operations. Those pre-defined operations are:
-     *
-     * <ul>
-     *   <li>A {@linkplain FeatureOperations#compound compound operation} for generating a unique identifier
-     *       from an arbitrary amount of attribute values.</li>
-     *   <li>A {@linkplain FeatureOperations#link link operation} for referencing a geometry to be used as the
-     *       <em>default</em> geometry.</li>
-     *   <li>An {@linkplain FeatureOperations#envelope operation} for computing the bounding box of all geometries
-     *       found in the feature. This operation is automatically added if the feature contains a default geometry.</li>
-     * </ul>
-     *
-     * This enumeration allows user code to specify which feature attribute to use for creating those operations.
-     *
-     * @see Attribute#addRole(AttributeRole)
-     */
-    public static enum AttributeRole {
         /**
-         * Attribute value will be part of a unique identifier for the feature instance.
-         * An arbitrary amount of attributes can be flagged as identifier components:
-         *
-         * <ul>
-         *   <li>If no attribute has this role, then no attribute is marked as feature identifier.</li>
-         *   <li>If exactly one attribute has this role, then a synthetic attribute named {@code "@identifier"}
-         *       will be created as a {@linkplain FeatureOperations#link link} to the flagged attribute.</li>
-         *   <li>If more than one attribute have this role, then a synthetic attribute named {@code "@identifier"}
-         *       will be created as a {@linkplain FeatureOperations#compound compound key} made of all flagged
-         *       attributes. The separator character can be modified by a call to
-         *       {@link FeatureTypeBuilder#setIdentifierDelimiters(String, String, String)}</li>
-         * </ul>
-         *
-         * @see FeatureTypeBuilder#setIdentifierDelimiters(String, String, String)
-         */
-        IDENTIFIER_COMPONENT,
-
-        /**
-         * Attribute value will be flagged as the <em>default</em> geometry.
-         * Feature can have an arbitrary amount of geometry attributes,
-         * but only one can be flagged as the default geometry.
+         * Creates a new property type from the current setting.
          */
-        DEFAULT_GEOMETRY
+        @Override
+        final PropertyType create() {
+            if (type != null) {
+                return new DefaultAssociationRole(identification(), type, minimumOccurs, maximumOccurs);
+            } else {
+                return new DefaultAssociationRole(identification(), typeName, minimumOccurs, maximumOccurs);
+            }
+        }
     }
 
 
@@ -531,7 +607,7 @@ public class FeatureTypeBuilder extends
      *
      * @see FeatureTypeBuilder#addAttribute(Class)
      */
-    public final class Attribute<V> extends Property<Attribute<V>> {
+    public static final class Attribute<V> extends Property<Attribute<V>> {
         /**
          * The class of property values. Can not be changed after construction
          * because this value determines the parameterized type {@code <V>}.
@@ -549,25 +625,40 @@ public class FeatureTypeBuilder extends
          * {@code isSuffix} are null, then {@code "@identifier"} will be a {@linkplain FeatureOperations#link link}
          * to {@code idAttributes[0]}.
          *
-         * @see #addRole(AttributeRole)
+         * @see #addRole(Role)
          */
         private boolean isIdentifier;
 
         /**
          * Builders for the characteristics associated to the attribute.
          */
-        private final List<Characteristic<?>> characteristics;
+        private final List<Characteristic<?>> characteristics = new ArrayList<>();
 
         /**
-         * Creates a new {@code AttributeType} or {@code Operation} builder for values of the given class.
+         * Creates a new {@code AttributeType} builder for values of the given class.
          *
+         * @param owner      the builder of the {@code FeatureType} for which to add this property.
          * @param valueClass the class of property values.
          */
-        Attribute(final Class<V> valueClass) {
+        Attribute(final FeatureTypeBuilder owner, final Class<V> valueClass) {
+            super(owner, null);
             this.valueClass = valueClass;
-            minimumOccurs   = defaultMinimumOccurs;
-            maximumOccurs   = defaultMaximumOccurs;
-            characteristics = new ArrayList<>();
+        }
+
+        /**
+         * Creates a new {@code AttributeType} builder initialized to the values of an existing attribute.
+         *
+         * @param owner  the builder of the {@code FeatureType} for which to add this property.
+         */
+        Attribute(final FeatureTypeBuilder owner, final AttributeType<V> template) {
+            super(owner, template);
+            minimumOccurs = template.getMinimumOccurs();
+            maximumOccurs = template.getMaximumOccurs();
+            valueClass    = template.getValueClass();
+            defaultValue  = template.getDefaultValue();
+            for (final AttributeType<?> c : template.characteristics().values()) {
+                characteristics.add(new Characteristic<>(this, c));
+            }
         }
 
         /**
@@ -582,11 +673,14 @@ public class FeatureTypeBuilder extends
         /**
          * Sets the default value for the property.
          *
-         * @param  defaultValue  default property value, or {@code null} if none.
+         * @param  value  default property value, or {@code null} if none.
          * @return {@code this} for allowing method calls chaining.
          */
-        public Attribute<V> setDefaultValue(final V defaultValue) {
-            this.defaultValue = defaultValue;
+        public Attribute<V> setDefaultValue(final V value) {
+            if (!Objects.equals(defaultValue, value)) {
+                defaultValue = value;
+                clearCache();
+            }
             return this;
         }
 
@@ -675,46 +769,113 @@ public class FeatureTypeBuilder extends
          * @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));
             }
             ensureNonNull("type", type);
-            final Characteristic<C> characteristic = new Characteristic<>(type);
+            final Characteristic<C> characteristic = new Characteristic<>(this, type);
             characteristics.add(characteristic);
+            clearCache();
             return characteristic;
         }
 
         /**
+         * Adds another attribute type that describes this attribute type, using an existing one as a template.
+         * See <cite>"Attribute characterization"</cite> in {@link DefaultAttributeType} Javadoc for more information.
+         *
+         * @param  <C>       the compile-time type of values in the {@code template} argument.
+         * @param  template  an existing attribute type to use as a template.
+         * @return a builder for a characteristic of this attribute, initialized with the values of the given template.
+         */
+        public <C> Characteristic<C> addCharacteristic(final AttributeType<C> template) {
+            ensureNonNull("template", template);
+            final Characteristic<C> characteristic = new Characteristic<>(this, template);
+            characteristics.add(characteristic);
+            clearCache();
+            return characteristic;
+        }
+
+        /**
+         * Roles that can be associated to some attributes for instructing {@code FeatureTypeBuilder}
+         * how to generate pre-defined operations. Those pre-defined operations are:
+         *
+         * <ul>
+         *   <li>A {@linkplain FeatureOperations#compound compound operation} for generating a unique identifier
+         *       from an arbitrary amount of attribute values.</li>
+         *   <li>A {@linkplain FeatureOperations#link link operation} for referencing a geometry to be used as the
+         *       <em>default</em> geometry.</li>
+         *   <li>An {@linkplain FeatureOperations#envelope operation} for computing the bounding box of all geometries
+         *       found in the feature. This operation is automatically added if the feature contains a default geometry.</li>
+         * </ul>
+         *
+         * This enumeration allows user code to specify which feature attribute to use for creating those operations.
+         *
+         * @see Attribute#addRole(Role)
+         */
+        public static enum Role {
+            /**
+             * Attribute value will be part of a unique identifier for the feature instance.
+             * An arbitrary amount of attributes can be flagged as identifier components:
+             *
+             * <ul>
+             *   <li>If no attribute has this role, then no attribute is marked as feature identifier.</li>
+             *   <li>If exactly one attribute has this role, then a synthetic attribute named {@code "@identifier"}
+             *       will be created as a {@linkplain FeatureOperations#link link} to the flagged attribute.</li>
+             *   <li>If more than one attribute have this role, then a synthetic attribute named {@code "@identifier"}
+             *       will be created as a {@linkplain FeatureOperations#compound compound key} made of all flagged
+             *       attributes. The separator character can be modified by a call to
+             *       {@link FeatureTypeBuilder#setIdentifierDelimiters(String, String, String)}</li>
+             * </ul>
+             *
+             * @see FeatureTypeBuilder#setIdentifierDelimiters(String, String, String)
+             */
+            IDENTIFIER_COMPONENT,
+
+            /**
+             * Attribute value will be flagged as the <em>default</em> geometry.
+             * Feature can have an arbitrary amount of geometry attributes,
+             * but only one can be flagged as the default geometry.
+             */
+            DEFAULT_GEOMETRY
+        }
+
+        /**
          * Flags this attribute as an input of one of the pre-defined operations managed by {@code FeatureTypeBuilder}.
          *
          * @param role the role to add to this attribute (shall not be null).
          */
-        public void addRole(final AttributeRole role) {
+        public void addRole(final Role role) {
             ensureNonNull("role", role);
             switch (role) {
                 case IDENTIFIER_COMPONENT: {
-                    isIdentifier = true;
+                    if (!isIdentifier) {
+                        isIdentifier = true;
+                        owner.identifierCount++;
+                        owner.clearCache();         // The change does not impact this attribute itself.
+                    }
                     break;
                 }
                 case DEFAULT_GEOMETRY: {
-                    if (!Geometries.isKnownType(valueClass)) {
-                        throw new IllegalStateException(errors().getString(Errors.Keys.UnsupportedImplementation_1, valueClass));
-                    }
-                    if (defaultGeometry != null) {
-                        throw new IllegalStateException(errors().getString(Errors.Keys.PropertyAlreadyExists_2,
-                                FeatureTypeBuilder.this.getDisplayName(), AttributeConvention.GEOMETRY_PROPERTY));
+                    if (owner.defaultGeometry != this) {
+                        if (!Geometries.isKnownType(valueClass)) {
+                            throw new IllegalStateException(errors().getString(Errors.Keys.UnsupportedImplementation_1, valueClass));
+                        }
+                        if (owner.defaultGeometry != null) {
+                            throw new IllegalStateException(errors().getString(Errors.Keys.PropertyAlreadyExists_2,
+                                    owner.getDisplayName(), AttributeConvention.GEOMETRY_PROPERTY));
+                        }
+                        owner.defaultGeometry = this;
+                        owner.clearCache();         // The change does not impact this attribute itself.
                     }
-                    defaultGeometry = this;
                     break;
                 }
             }
         }
 
         /**
-         * Returns {@code true} if {@link AttributeRole#IDENTIFIER_COMPONENT} has been associated to this attribute.
+         * Returns {@code true} if {@link Role#IDENTIFIER_COMPONENT} has been associated to this attribute.
          */
         @Override
         boolean isIdentifier() {
@@ -733,7 +894,7 @@ public class FeatureTypeBuilder extends
          * Creates a new property type from the current setting.
          */
         @Override
-        final PropertyType build() {
+        final PropertyType create() {
             final AttributeType<?>[] chrts = new AttributeType<?>[characteristics.size()];
             for (int i=0; i<chrts.length; i++) {
                 chrts[i] = characteristics.get(i).build();
@@ -746,7 +907,7 @@ public class FeatureTypeBuilder extends
 
 
     /**
-     * Describes one characteristic of the {@code AttributeType} to be built by the enclosing {@code FeatureTypeBuilder}.
+     * Describes one characteristic of an {@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:
      *
@@ -756,7 +917,12 @@ public class FeatureTypeBuilder extends
      *
      * @param <V> the class of characteristic values.
      */
-    public final class Characteristic<V> extends Builder<Characteristic<V>> {
+    public static final class Characteristic<V> extends Builder<Characteristic<V>> {
+        /**
+         * The attribute type builder instance that created this {@code Characteristic} builder.
+         */
+        private final Attribute<?> owner;
+
         /**
          * The class of attribute values. Can not be changed after construction
          * because this value determines the parameterized type {@code <V>}.
@@ -769,16 +935,47 @@ public class FeatureTypeBuilder extends
         private V defaultValue;
 
         /**
+         * The characteristic created by this builder, or {@code null} if not yet created.
+         * This field must be cleared every time that a setter method is invoked on this builder.
+         */
+        private transient AttributeType<V> characteristic;
+
+        /**
          * Creates a new characteristic builder for values of the given class.
          *
+         * @param owner      the builder of the {@code AttributeType} for which to add this property.
          * @param valueClass the class of characteristic values.
          */
-        Characteristic(final Class<V> valueClass) {
-            super(FeatureTypeBuilder.this);
+        Characteristic(final Attribute<?> owner, final Class<V> valueClass) {
+            super(null, owner.getLocale());
+            this.owner = owner;
             this.valueClass = valueClass;
         }
 
         /**
+         * Creates a new characteristic builder initialized to the values of an existing attribute.
+         *
+         * @param owner  the builder of the {@code AttributeType} for which to add this property.
+         */
+        Characteristic(final Attribute<?> owner, final AttributeType<V> template) {
+            super(template, owner.getLocale());
+            this.owner     = owner;
+            valueClass     = template.getValueClass();
+            defaultValue   = template.getDefaultValue();
+            characteristic = template;
+        }
+
+        /**
+         * If an {@code AttributeType<V>} has been created by the last call to {@link #build()} has been cached,
+         * clears that cache. This method must be invoked every time that a setter method is invoked.
+         */
+        @Override
+        final void clearCache() {
+            characteristic = null;
+            owner.clearCache();
+        }
+
+        /**
          * Returns a default name to use if the user did not specified a name. The first letter will be changed to
          * lower case (unless the name looks like an acronym) for compliance with Java convention on property names.
          */
@@ -792,7 +989,7 @@ public class FeatureTypeBuilder extends
          */
         @Override
         final GenericName name(final String scope, final String localPart) {
-            return FeatureTypeBuilder.this.name(scope, localPart);
+            return owner.name(scope, localPart);
         }
 
         /**
@@ -809,7 +1006,10 @@ public class FeatureTypeBuilder extends
          * @return {@code this} for allowing method calls chaining.
          */
         public Characteristic<V> setDefaultValue(final V value) {
-            defaultValue = value;
+            if (!Objects.equals(defaultValue, value)) {
+                defaultValue = value;
+                clearCache();
+            }
             return this;
         }
 
@@ -817,7 +1017,10 @@ public class FeatureTypeBuilder extends
          * Creates a new characteristic from the current setting.
          */
         final AttributeType<V> build() {
-            return new DefaultAttributeType<>(identification(), valueClass, 0, 1, defaultValue);
+            if (characteristic == null) {
+                characteristic = new DefaultAttributeType<>(identification(), valueClass, 0, 1, defaultValue);
+            }
+            return characteristic;
         }
     }
 
@@ -834,64 +1037,98 @@ public class FeatureTypeBuilder extends
      *         {@linkplain Attribute#setCRSCharacteristic CRS characteristics}.
      */
     public FeatureType build() throws IllegalStateException {
-        /*
-         * Creates an initial array of property types with up to 3 slots reserved for @identifier, @geometry
-         * and @envelope operations. At first we presume that there is always an identifier.  The identifier
-         * slot will be removed later if there is none.
-         */
-        final int numSpecified = properties.size();             // Number of explicitely specified properties.
-        final int numSynthetic = (defaultGeometry != null)      // Number of synthetic properties to be generated.
-                           ? GEOMETRY_INDEX + 1
-                           : IDENTIFIER_INDEX + 1;
-        PropertyType[] propertyTypes = new PropertyType[numSpecified + numSynthetic];
-        PropertyType[] identifierTypes = new PropertyType[4];   // There is rarely more than 4 identifier components.
-        int idIndex = 0;
-        for (int i=0,j=numSynthetic; i<numSpecified; i++, j++) {
-            final Property<?>  builder  = properties.get(i);
-            final PropertyType instance = builder.build();
-            propertyTypes[j] = instance;
-            if (builder.isIdentifier()) {
-                if (idIndex >= identifierTypes.length) {
-                    identifierTypes = Arrays.copyOf(identifierTypes, idIndex*2);
+        if (feature == null) {
+            /*
+             * Creates an initial array of property types with up to 3 slots reserved for @identifier, @geometry
+             * and @envelope operations. At first we presume that there is always an identifier.  The identifier
+             * slot will be removed later if there is none.
+             */
+            final int numSpecified = properties.size();     // Number of explicitely specified properties.
+            int numSynthetic;                               // Number of synthetic properties that may be generated.
+            int envelopeIndex = -1;
+            int geometryIndex = -1;
+            final PropertyType[] identifierTypes;
+            if (identifierCount == 0) {
+                numSynthetic    = 0;
+                identifierTypes = null;
+            } else {
+                numSynthetic    = 1;
+                identifierTypes = new PropertyType[identifierCount];
+            }
+            if (defaultGeometry != null) {
+                envelopeIndex = numSynthetic;
+                geometryIndex = numSynthetic + 1;
+                numSynthetic += 2;
+            }
+            final PropertyType[] propertyTypes = new PropertyType[numSynthetic + numSpecified];
+            int propertyCursor = numSynthetic;
+            int identifierCursor = 0;
+            for (int i=0; i<numSpecified; i++) {
+                final Property<?>  builder = properties.get(i);
+                final PropertyType instance = builder.build();
+                propertyTypes[propertyCursor] = instance;
+                /*
+                 * Collect the attributes to use as identifier components while we loop over all properties.
+                 * A NullPointerException or an ArrayIndexOutOfBoundsException in this block would mean that
+                 * identifierCount field has not been updated correctly by an Attribute.addRole(Role) method.
+                 */
+                if (builder.isIdentifier()) {
+                    identifierTypes[identifierCursor++] = instance;
                 }
-                identifierTypes[idIndex++] = instance;
+                /*
+                 * 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 we will avoid to duplicate the property.
+                 */
+                if (builder == defaultGeometry) {
+                    if (propertyTypes[geometryIndex] != null) {
+                        // Assuming that there is no bug in our implementation, this error could happen if the user
+                        // has modified this FeatureTypeBuilder in another thread during this build() execution.
+                        throw new CorruptedObjectException();
+                    }
+                    if (AttributeConvention.GEOMETRY_PROPERTY.equals(instance.getName())) {
+                        System.arraycopy(propertyTypes, geometryIndex, propertyTypes, geometryIndex-1, (numSynthetic - geometryIndex) + i);
+                        geometryIndex = -1;
+                        numSynthetic--;
+                        continue;           // Skip the increment of propertyCursor.
+                    }
+                    propertyTypes[geometryIndex] = FeatureOperations.link(name(AttributeConvention.GEOMETRY_PROPERTY), instance);
+                }
+                propertyCursor++;
+            }
+            /*
+             * Create the "envelope" operation only after we created all other properties.
+             * Actually it is okay if the 'propertyTypes' array still contains null elements not needed for envelope calculation
+             * like "@identifier", since FeatureOperations.envelope(…) constructor ignores any property which is not for a value.
+             */
+            if (envelopeIndex >= 0) try {
+                propertyTypes[envelopeIndex] = FeatureOperations.envelope(name(AttributeConvention.ENVELOPE_PROPERTY), null, propertyTypes);
+            } 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 we will avoid to duplicate the property.
+             * If a synthetic identifier need to be created, create it now as the first property.
+             * It may happen that the user provided a single identifier component already named
+             * "@identifier", in which case we avoid to duplicate the property.
              */
-            if (builder == defaultGeometry) {
-                if (AttributeConvention.GEOMETRY_PROPERTY.equals(instance.getName())) {
-                    System.arraycopy(propertyTypes, GEOMETRY_INDEX, propertyTypes, ENVELOPE_INDEX, i);
-                    j--;
+            if (identifierTypes != null) {
+                if (identifierCursor != identifierTypes.length) {
+                    // Assuming that there is no bug in our implementation, this error could happen if the user
+                    // has modified this FeatureTypeBuilder in another thread during this build() execution.
+                    throw new CorruptedObjectException();
+                }
+                if (identifierCursor == 1 && AttributeConvention.IDENTIFIER_PROPERTY.equals(identifierTypes[0].getName())) {
+                    System.arraycopy(propertyTypes, 1, propertyTypes, 0, --propertyCursor);
                 } else {
-                    propertyTypes[GEOMETRY_INDEX] = FeatureOperations.link(name(AttributeConvention.GEOMETRY_PROPERTY), instance);
+                    propertyTypes[0] = FeatureOperations.compound(name(AttributeConvention.IDENTIFIER_PROPERTY),
+                            idDelimiter, idPrefix, idSuffix, identifierTypes);
                 }
             }
+            feature = new DefaultFeatureType(identification(), isAbstract,
+                    superTypes.toArray(new FeatureType[superTypes.size()]),
+                    ArraysExt.resize(propertyTypes, propertyCursor));
         }
-        /*
-         * Create the "envelope" operation only after we created all other properties.
-         * Actually it is okay if the 'propertyTypes' array still contains null elements not needed for envelope calculation
-         * like "@identifier", since FeatureOperations.envelope(…) constructor ignores any property which is not for a value.
-         */
-        if (defaultGeometry != null) try {
-            propertyTypes[ENVELOPE_INDEX] = FeatureOperations.envelope(name(AttributeConvention.ENVELOPE_PROPERTY), null, propertyTypes);
-        } catch (FactoryException e) {
-            throw new IllegalStateException(e);
-        }
-        /*
-         * If a synthetic identifier need to be created, create it now as the first property.
-         * It may happen that the user provided a single identifier component already named
-         * "@identifier", in which case we avoid to duplicate the property.
-         */
-        if (idIndex != 0 && (idIndex != 1 || !AttributeConvention.IDENTIFIER_PROPERTY.equals(identifierTypes[0].getName()))) {
-            propertyTypes[IDENTIFIER_INDEX] = FeatureOperations.compound(name(AttributeConvention.IDENTIFIER_PROPERTY),
-                    idDelimiter, idPrefix, idSuffix, ArraysExt.resize(identifierTypes, idIndex));
-        } else {
-            propertyTypes = ArraysExt.remove(propertyTypes, IDENTIFIER_INDEX, 1);
-        }
-        return new DefaultFeatureType(identification(), isAbstract, superTypes.toArray(new FeatureType[superTypes.size()]), propertyTypes);
+        return feature;
     }
 
     /**
@@ -915,4 +1152,25 @@ public class FeatureTypeBuilder extends
             return nameFactory.createGenericName(null, scope, localPart);
         }
     }
+
+    /**
+     * Formats a string representation of this builder for debugging purpose.
+     */
+    @Override
+    final void toStringInternal(final StringBuilder buffer) {
+        if (isAbstract) {
+            buffer.insert(buffer.indexOf("[") + 1, "abstract ");
+        }
+        String separator = " : ";
+        for (final FeatureType parent : superTypes) {
+            buffer.append(separator).append('“').append(parent.getName()).append('”');
+            separator = ", ";
+        }
+        buffer.append(" {");
+        separator = System.lineSeparator();
+        for (final Property<?> p : properties) {
+            p.toString(buffer.append(separator).append("    ").append(p.getClass().getSimpleName()));
+        }
+        buffer.append(separator).append('}');
+    }
 }

Modified: sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java?rev=1751913&r1=1751912&r2=1751913&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-feature/src/test/java/org/apache/sis/internal/feature/FeatureTypeBuilderTest.java [UTF-8] Fri Jul  8 14:23:53 2016
@@ -30,6 +30,7 @@ import static org.junit.Assert.*;
 import org.opengis.feature.AttributeType;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
+import org.apache.sis.feature.DefaultFeatureTypeTest;
 
 
 /**
@@ -191,18 +192,6 @@ public final strictfp class FeatureTypeB
     }
 
     /**
-     * Test {@link FeatureTypeBuilder#clear()}.
-     */
-    @Test
-    @DependsOnMethod({"testEmptyFeature", "testAddProperties"})
-    public void testClear() {
-        final FeatureTypeBuilder builder = new FeatureTypeBuilder();
-        testAddProperties(builder);
-        builder.clear();
-        testEmptyFeature(builder);
-    }
-
-    /**
      * Tests {@link FeatureTypeBuilder#addIdentifier(Class)}.
      */
     @Test
@@ -212,10 +201,10 @@ public final strictfp class FeatureTypeB
         builder.setName("scope", "test");
         builder.setIdentifierDelimiters("-", "pref.", null);
         builder.addAttribute(String.class).setName("name")
-                .addRole(FeatureTypeBuilder.AttributeRole.IDENTIFIER_COMPONENT);
+                .addRole(FeatureTypeBuilder.Attribute.Role.IDENTIFIER_COMPONENT);
         builder.addAttribute(Geometry.class).setName("shape")
                 .setCRSCharacteristic(HardCodedCRS.WGS84)
-                .addRole(FeatureTypeBuilder.AttributeRole.DEFAULT_GEOMETRY);
+                .addRole(FeatureTypeBuilder.Attribute.Role.DEFAULT_GEOMETRY);
 
         final FeatureType type = builder.build();
         assertEquals("name", "scope:test", type.getName().toString());
@@ -235,4 +224,13 @@ public final strictfp class FeatureTypeB
         assertEquals("name", "name",                                  a3.getName().toString());
         assertEquals("name", "shape",                                 a4.getName().toString());
     }
+
+    /**
+     * Tests creation of a builder from an existing feature type.
+     */
+    @Test
+    public void testCreateFromTemplate() {
+        final FeatureTypeBuilder builder = new FeatureTypeBuilder(DefaultFeatureTypeTest.capital());
+        assertEquals("name", "Capital", builder.getName().toString());
+    }
 }

Modified: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXConstants.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXConstants.java?rev=1751913&r1=1751912&r2=1751913&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXConstants.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXConstants.java [UTF-8] Fri Jul  8 14:23:53 2016
@@ -232,7 +232,7 @@ public final class GPXConstants extends
         final Map<String,?> geomInfo = Collections.singletonMap(AbstractIdentifiedType.NAME_KEY, geomName);
 
         //-------------------- GENERIC GPX ENTITY ------------------------------
-        final FeatureTypeBuilder builder = new FeatureTypeBuilder(factory, null);
+        FeatureTypeBuilder builder = new FeatureTypeBuilder(null, factory, null);
         builder.setDefaultScope(GPX_NAMESPACE).setName("GPXEntity").setAbstract(true);
         builder.addAttribute(Integer.class).setName("index");
         TYPE_GPX_ENTITY = builder.build();
@@ -261,10 +261,11 @@ public final class GPXConstants extends
          * <dgpsid> dgpsStationType </dgpsid> [0..1] ?
          * <extensions> extensionsType </extensions> [0..1] ?
          */
-        builder.clear().setDefaultScope(GPX_NAMESPACE).setName("WayPoint").setSuperTypes(TYPE_GPX_ENTITY);
+        builder = new FeatureTypeBuilder(null, factory, null);
+        builder.setDefaultScope(GPX_NAMESPACE).setName("WayPoint").setSuperTypes(TYPE_GPX_ENTITY);
         builder.addAttribute(Point.class).setName(geomName)
                 .setCRSCharacteristic(CommonCRS.defaultGeographic())
-                .addRole(FeatureTypeBuilder.AttributeRole.DEFAULT_GEOMETRY);
+                .addRole(FeatureTypeBuilder.Attribute.Role.DEFAULT_GEOMETRY);
         builder.setDefaultCardinality(0, 1);
         builder.addAttribute(Double  .class).setName(TAG_WPT_ELE);
         builder.addAttribute(Temporal.class).setName(TAG_WPT_TIME);



Mime
View raw message