sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1803070 [3/20] - in /sis/branches/JDK9: ./ application/sis-console/src/main/java/org/apache/sis/console/ core/sis-build-helper/ core/sis-build-helper/src/main/java/org/apache/sis/internal/book/ core/sis-build-helper/src/main/java/org/apach...
Date Wed, 26 Jul 2017 16:14:14 GMT
Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -25,14 +25,18 @@ import java.util.Map;
 import java.util.Locale;
 import java.util.Set;
 import java.util.Objects;
+import org.opengis.util.NameSpace;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
 import org.opengis.util.FactoryException;
+import org.opengis.metadata.acquisition.GeometryType;
+import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.feature.AbstractOperation;
 import org.apache.sis.feature.DefaultFeatureType;
 import org.apache.sis.feature.FeatureOperations;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.feature.AttributeConvention;
+import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.util.CorruptedObjectException;
 import org.apache.sis.util.resources.Errors;
@@ -52,8 +56,47 @@ import org.opengis.feature.Operation;
  * This builder can create the arguments to be given to the
  * {@linkplain DefaultFeatureType#DefaultFeatureType feature type constructor}
  * from simpler parameters given to this builder.
+ * The main methods provided in this class are:
  *
- * <p>{@code FeatureTypeBuilder} should be short lived.
+ * <ul>
+ *   <li>Various {@link #setName(CharSequence) setName(...)} methods for specifying the feature type name (mandatory).</li>
+ *   <li>Methods for optionally setting {@linkplain #setDesignation designation}, {@linkplain #setDefinition definition} or
+ *       {@linkplain #setDescription description} texts, or the {@linkplain #setDeprecated deprecation status}.</li>
+ *   <li>Methods for optionally specifying the feature type hierarchy: its {@linkplain #setSuperTypes super types}
+ *       and whether the feature type is {@linkplain #setAbstract abstract}.</li>
+ *   <li>Convenience methods for setting the {@linkplain #setNameSpace name space} and the
+ *       {@linkplain #setDefaultCardinality default cardinality} of properties to be added to the feature type.</li>
+ *   <li>Methods for {@linkplain #addAttribute(Class) adding an attribute}, {@linkplain #addAssociation(FeatureType)
+ *       an association} or {@linkplain #addProperty an operation}.</li>
+ *   <li>Method for listing the previously added {@linkplain #properties() properties}.</li>
+ *   <li>A {@link #build()} method for creating the {@code FeatureType} instance from all previous information.</li>
+ * </ul>
+ *
+ * The following example creates a city named "Utopia" by default:
+ *
+ * {@preformat java
+ *     FeatureTypeBuilder builder;
+ *
+ *     // Create a feature type for a city, which contains a name and a population.
+ *     builder = new FeatureTypeBuilder() .setName("City");
+ *     builder.addAttribute(String.class) .setName("name").setDefaultValue("Utopia");
+ *     builder.addAttribute(Integer.class).setName("population");
+ *     FeatureType city = builder.build();
+ * }
+ *
+ * A call to {@code System.out.println(city)} prints the following table:
+ *
+ * {@preformat text
+ *   City
+ *   ┌────────────┬─────────┬─────────────┬───────────────┐
+ *   │ Name       │ Type    │ Cardinality │ Default value │
+ *   ├────────────┼─────────┼─────────────┼───────────────┤
+ *   │ name       │ String  │     [1 … 1] │ Utopia        │
+ *   │ population │ Integer │     [1 … 1] │               │
+ *   └────────────┴─────────┴─────────────┴───────────────┘
+ * }
+ *
+ * <p>{@code FeatureTypeBuilder} instances should be short lived.
  * After the {@code FeatureType} has been created, the builder should be discarded.</p>
  *
  * @author  Johann Sorel (Geomatys)
@@ -90,12 +133,12 @@ public class FeatureTypeBuilder extends
     private boolean isAbstract;
 
     /**
-     * The default scope to use when {@link #name(String, String)} is invoked with a null scope.
+     * The namespace to use when a {@link #setName(CharSequence)} method is invoked.
      *
-     * @see #getDefaultScope()
-     * @see #setDefaultScope(String)
+     * @see #getNameSpace()
+     * @see #setNameSpace(CharSequence)
      */
-    private String defaultScope;
+    private NameSpace namespace;
 
     /**
      * The default minimum number of property values.
@@ -140,6 +183,11 @@ public class FeatureTypeBuilder extends
     AttributeTypeBuilder<?> defaultGeometry;
 
     /**
+     * Provides method for creating geometric objects using the library specified by the user.
+     */
+    private final Geometries<?> geometries;
+
+    /**
      * 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.
      */
@@ -156,97 +204,153 @@ public class FeatureTypeBuilder extends
      * Creates a new builder instance using the given feature type as a template.
      *
      * @param template  an existing feature type to use as a template, or {@code null} if none.
+     *
+     * @see #setAll(FeatureType)
      */
     public FeatureTypeBuilder(final FeatureType template) {
-        this(template, null, null);
+        this(null, null, null);
+        if (template != null) {
+            initialize(template);
+        }
     }
 
     /**
-     * Creates a new builder instance using the given name factory, template and locale for formatting error messages.
+     * Creates a new builder instance using the given name factory, geometry library
+     * 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.
+     * @param factory  the factory to use for creating names, or {@code null} for the default factory.
+     * @param library  the library to use for creating geometric objects, or {@code null} for the default.
+     * @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);
+    public FeatureTypeBuilder(NameFactory factory, final GeometryLibrary library, final Locale locale) {
+        super(locale);
         if (factory == null) {
             factory = DefaultFactories.forBuildin(NameFactory.class);
         }
-        nameFactory = factory;
-        properties  = new ArrayList<>();
-        superTypes  = new ArrayList<>();
-        idDelimiter = ":";
+        nameFactory          = factory;
+        geometries           = Geometries.implementation(library);
+        properties           = new ArrayList<>();
+        superTypes           = new ArrayList<>();
+        idDelimiter          = ":";
         defaultMinimumOccurs = 1;
         defaultMaximumOccurs = 1;
+    }
+
+    /**
+     * Clears all setting in this builder. After invoking this method, this {@code FeatureTypeBuilder}
+     * is in same state that after it has been constructed. This method can be invoked for reusing the
+     * same builder for creating other {@code FeatureType} instances after {@link #build()} invocation.
+     *
+     * @return {@code this} for allowing method calls chaining.
+     */
+    public FeatureTypeBuilder clear() {
+        reset();
+        properties.clear();
+        superTypes.clear();
+        isAbstract           = false;
+        namespace            = null;
+        defaultMinimumOccurs = 1;
+        defaultMaximumOccurs = 1;
+        idPrefix             = null;
+        idSuffix             = null;
+        idDelimiter          = ":";
+        identifierCount      = 0;
+        defaultGeometry      = null;
+        clearCache();
+        return this;
+    }
+
+    /**
+     * Sets all properties of this builder to the values of the given feature type.
+     * This builder is {@linkplain #clear() cleared} before the properties of the given type are copied.
+     *
+     * @param  template  an existing feature type to use as a template, or {@code null} if none.
+     * @return {@code this} for allowing method calls chaining.
+     *
+     * @see #FeatureTypeBuilder(FeatureType)
+     */
+    public FeatureTypeBuilder setAll(final FeatureType template) {
+        clear();
         if (template != null) {
-            feature    = template;
-            isAbstract = template.isAbstract();
-            superTypes.addAll(template.getSuperTypes());
+            initialize(template);
+        }
+        return this;
+    }
+
+    /**
+     * Initializes this builder to the value of the given type.
+     * The caller is responsible to invoke {@link #clear()} (if needed) before this method.
+     *
+     * @see #setAll(FeatureType)
+     */
+    private void initialize(final FeatureType template) {
+        super.initialize(template);
+        feature    = template;
+        isAbstract = template.isAbstract();
+        superTypes.addAll(template.getSuperTypes());
+        /*
+         * For each attribute and association, wrap those properties in a builder.
+         * For each operation, wrap them in pseudo-builder only if the operation
+         * is not one of the operations automatically generated by this builder.
+         */
+        final Map<String,Set<AttributeRole>> propertyRoles = new HashMap<>();
+        for (final PropertyType property : template.getProperties(false)) {
+            PropertyTypeBuilder builder;
+            if (property instanceof AttributeType<?>) {
+                builder = new AttributeTypeBuilder<>(this, (AttributeType<?>) property);
+            } else if (property instanceof FeatureAssociationRole) {
+                builder = new AssociationRoleBuilder(this, (FeatureAssociationRole) property);
+            } else {
+                builder = null;                             // Do not create OperationWrapper now - see below.
+            }
             /*
-             * For each attribute and association, wrap those properties in a builder.
-             * For each operation, wrap them in pseudo-builder only if the operation
-             * is not one of the operations automatically generated by this builder.
+             * If the property name is one of our (Apache SIS specific) conventional names, try to reconstitute
+             * the attribute roles that caused FeatureTypeBuilder to produce such property. Those roles usually
+             * need to be applied on the source properties used for calculating the current property. There is
+             * usually at most one role for each source property, but we nevertheless allow an arbitrary amount.
              */
-            final Map<String,Set<AttributeRole>> propertyRoles = new HashMap<>();
-            for (final PropertyType property : template.getProperties(false)) {
-                PropertyTypeBuilder builder;
-                if (property instanceof AttributeType<?>) {
-                    builder = new AttributeTypeBuilder<>(this, (AttributeType<?>) property);
-                } else if (property instanceof FeatureAssociationRole) {
-                    builder = new AssociationRoleBuilder(this, (FeatureAssociationRole) property);
-                } else {
-                    builder = null;                             // Do not create OperationWrapper now - see below.
-                }
-                /*
-                 * If the property name is one of our (Apache SIS specific) conventional names, try to reconstitute
-                 * the attribute roles that caused FeatureTypeBuilder to produce such property. Those roles usually
-                 * need to be applied on the source properties used for calculating the current property. There is
-                 * usually at most one role for each source property, but we nevertheless allow an arbitrary amount.
-                 */
-                final AttributeRole role;
-                final GenericName name = property.getName();
-                if (AttributeConvention.IDENTIFIER_PROPERTY.equals(name)) {
-                    role = AttributeRole.IDENTIFIER_COMPONENT;
-                } else if (AttributeConvention.GEOMETRY_PROPERTY.equals(name)) {
-                    role = AttributeRole.DEFAULT_GEOMETRY;
-                } else if (AttributeConvention.ENVELOPE_PROPERTY.equals(name)) {
-                    // If "sis:envelope" is an operation, skip it completely.
-                    // It will be recreated if a default geometry exists.
-                    role = null;
-                } else {
-                    if (builder == null) {
-                        // For all unknown operation, wrap as-is.
-                        builder = new OperationWrapper(this, property);
-                    }
-                    role = null;
+            final AttributeRole role;
+            final GenericName name = property.getName();
+            if (AttributeConvention.IDENTIFIER_PROPERTY.equals(name)) {
+                role = AttributeRole.IDENTIFIER_COMPONENT;
+            } else if (AttributeConvention.GEOMETRY_PROPERTY.equals(name)) {
+                role = AttributeRole.DEFAULT_GEOMETRY;
+            } else if (AttributeConvention.ENVELOPE_PROPERTY.equals(name)) {
+                // If "sis:envelope" is an operation, skip it completely.
+                // It will be recreated if a default geometry exists.
+                role = null;
+            } else {
+                if (builder == null) {
+                    // For all unknown operation, wrap as-is.
+                    builder = new OperationWrapper(this, property);
                 }
-                if (role != null) {
-                    final Set<AttributeRole> rc = Collections.singleton(role);
-                    if (property instanceof AbstractOperation) {
-                        for (final String dependency : ((AbstractOperation) property).getDependencies()) {
-                            propertyRoles.merge(dependency, rc, AttributeRole::merge);
-                        }
-                    } else {
-                        propertyRoles.merge(name.toString(), rc, AttributeRole::merge);
+                role = null;
+            }
+            if (role != null) {
+                final Set<AttributeRole> rc = Collections.singleton(role);
+                if (property instanceof AbstractOperation) {
+                    for (final String dependency : ((AbstractOperation) property).getDependencies()) {
+                        propertyRoles.merge(dependency, rc, AttributeRole::merge);
                     }
-                }
-                if (builder != null) {
-                    properties.add(builder);
+                } else {
+                    propertyRoles.merge(name.toString(), rc, AttributeRole::merge);
                 }
             }
-            /*
-             * At this point we finished to collect information about the attribute roles.
-             * Now assign those roles to the attribute builders. Note that some roles may
-             * be ignored if we didn't found a suitable builder. The roles inference done
-             * in this constructor is only a "best effort".
-             */
-            if (!propertyRoles.isEmpty()) {
-                for (final Map.Entry<String,Set<AttributeRole>> entry : propertyRoles.entrySet()) {
-                    final PropertyTypeBuilder property = forName(properties, entry.getKey());
-                    if (property instanceof AttributeTypeBuilder<?>) {
-                        ((AttributeTypeBuilder<?>) property).roles().addAll(entry.getValue());
-                    }
+            if (builder != null) {
+                properties.add(builder);
+            }
+        }
+        /*
+         * At this point we finished to collect information about the attribute roles.
+         * Now assign those roles to the attribute builders. Note that some roles may
+         * be ignored if we didn't found a suitable builder. The roles inference done
+         * in this constructor is only a "best effort".
+         */
+        if (!propertyRoles.isEmpty()) {
+            for (final Map.Entry<String,Set<AttributeRole>> entry : propertyRoles.entrySet()) {
+                final PropertyTypeBuilder property = forName(properties, entry.getKey());
+                if (property instanceof AttributeTypeBuilder<?>) {
+                    ((AttributeTypeBuilder<?>) property).roles().addAll(entry.getValue());
                 }
             }
         }
@@ -255,6 +359,8 @@ public class FeatureTypeBuilder extends
     /**
      * If the {@code FeatureType} 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.
+     *
+     * @see #clear()
      */
     @Override
     final void clearCache() {
@@ -318,6 +424,51 @@ public class FeatureTypeBuilder extends
     }
 
     /**
+     * Returns the namespace of the names created by {@code setName(CharSequence...)} method calls.
+     * A {@code null} value means that the names are in the
+     * {@linkplain org.apache.sis.util.iso.DefaultNameSpace#isGlobal() global namespace}.
+     *
+     * @return the namespace to use when {@link #setName(CharSequence)} is invoked, or {@code null} if none.
+     */
+    public CharSequence getNameSpace() {
+        return (namespace != null) ? namespace.name().toString() : null;
+    }
+
+    /**
+     * Sets the namespace of the next names to be created by {@code setName(CharSequence...)} method calls.
+     * This method applies only to the next calls to {@link #setName(CharSequence)} or
+     * {@link #setName(CharSequence...)} methods; the result of all previous calls stay unmodified.
+     * Example:
+     *
+     * {@preformat java
+     *     FeatureTypeBuilder builder = new FeatureTypeBuilder().setNameSpace("MyNameSpace").setName("City");
+     *     FeatureType city = builder.build();
+     *
+     *     System.out.println(city.getName());                              // Prints "City"
+     *     System.out.println(city.getName().toFullyQualifiedName());       // Prints "MyNameSpace:City"
+     * }
+     *
+     * There is different conventions about the use of name spaces. ISO 19109 suggests that the namespace of all
+     * {@code AttributeType} names is the name of the enclosing {@code FeatureType}, but this is not mandatory.
+     * Users who want to apply this convention can invoke {@code setNameSpace(featureName)} after
+     * <code>{@linkplain #setName(CharSequence) FeatureTypeBuilder.setName}(featureName)</code> but before
+     * <code>{@linkplain AttributeTypeBuilder#setName(CharSequence) AttributeTypeBuilder.setName}(attributeName)</code>.
+     *
+     * @param  ns  the new namespace, or {@code null} if none.
+     * @return {@code this} for allowing method calls chaining.
+     */
+    public FeatureTypeBuilder setNameSpace(final CharSequence ns) {
+        if (ns != null && ns.length() != 0) {
+            namespace = nameFactory.createNameSpace(nameFactory.createLocalName(null, ns), null);
+        } else {
+            namespace = null;
+        }
+        // No need to clear the cache because this change affects
+        // only the next names to be created, not the existing ones.
+        return this;
+    }
+
+    /**
      * Sets the {@code FeatureType} name as a generic name.
      * If another name was defined before this method call, that previous value will be discarded.
      *
@@ -334,83 +485,58 @@ public class FeatureTypeBuilder extends
     }
 
     /**
-     * Sets the {@code FeatureType} name as a simple string with the default scope.
-     * The default scope is the value specified by the last call to {@link #setDefaultScope(String)}.
-     * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if no default scope
-     * has been specified, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+     * Sets the {@code FeatureType} name as a simple string.
+     * The namespace will be the value specified by the last call to {@link #setNameSpace(CharSequence)},
+     * but that namespace will not be visible in the {@linkplain org.apache.sis.util.iso.DefaultLocalName#toString()
+     * string representation} unless the {@linkplain org.apache.sis.util.iso.DefaultLocalName#toFullyQualifiedName()
+     * fully qualified name} is requested.
      *
-     * <p>This convenience method creates a {@link GenericName} instance,
-     * then delegates to {@link #setName(GenericName)}.</p>
+     * <p>This convenience method creates a {@link org.opengis.util.LocalName} instance from
+     * the given {@code CharSequence}, then delegates to {@link #setName(GenericName)}.</p>
      *
      * @return {@code this} for allowing method calls chaining.
      */
     @Override
-    public FeatureTypeBuilder setName(final String localPart) {
+    public FeatureTypeBuilder setName(final CharSequence localPart) {
         super.setName(localPart);
         return this;
     }
 
     /**
      * Sets the {@code FeatureType} name as a string in the given scope.
-     * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if the given scope is
-     * {@code null} or empty, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
-     * If a {@linkplain #setDefaultScope(String) default scope} has been specified, then the
-     * {@code scope} argument overrides it.
-     *
-     * <p>This convenience method creates a {@link GenericName} instance,
-     * then delegates to {@link #setName(GenericName)}.</p>
+     * The {@code components} array must contain at least one element.
+     * In addition to the path specified by the {@code components} array, the name may also contain
+     * a namespace specified by the last call to {@link #setNameSpace(CharSequence)}.
+     * But contrarily to the specified components, the namespace will not be visible in the name
+     * {@linkplain org.apache.sis.util.iso.DefaultScopedName#toString() string representation} unless the
+     * {@linkplain org.apache.sis.util.iso.DefaultScopedName#toFullyQualifiedName() fully qualified name} is requested.
+     *
+     * <p>This convenience method creates a {@link org.opengis.util.LocalName} or {@link org.opengis.util.ScopedName}
+     * instance depending on whether the {@code names} array contains exactly 1 element or more than 1 element, then
+     * delegates to {@link #setName(GenericName)}.</p>
      *
      * @return {@code this} for allowing method calls chaining.
      */
     @Override
-    public FeatureTypeBuilder setName(final String scope, final String localPart) {
-        super.setName(scope, localPart);
+    public FeatureTypeBuilder setName(final CharSequence... components) {
+        super.setName(components);
         return this;
     }
 
     /**
-     * Invoked by {@link TypeBuilder} for creating new {@code LocalName} or {@code GenericName} instances.
+     * Creates a local name in the {@linkplain #setNameSpace feature namespace}.
      */
     @Override
-    final GenericName name(String scope, final String localPart) {
-        if (scope == null) {
-            scope = getDefaultScope();
-        }
-        if (scope == null || scope.isEmpty()) {
-            return nameFactory.createLocalName(null, localPart);
-        } else {
-            return nameFactory.createGenericName(null, scope, localPart);
-        }
+    final GenericName createLocalName(final CharSequence name) {
+        return nameFactory.createLocalName(namespace, name);
     }
 
     /**
-     * Returns the scope of the names created by {@code setName(String)} method calls.
-     *
-     * @return the scope to use by default when {@link #setName(String)} is invoked.
+     * Creates a generic name in the {@linkplain #setNameSpace feature namespace}.
      */
-    public String getDefaultScope() {
-        return defaultScope;
-    }
-
-    /**
-     * Sets the scope of the next names created by {@code setName(String)} method calls.
-     * This method applies only to the next calls to {@code setName(String)};
-     * the result of all previous calls stay unmodified.
-     *
-     * <p>There is different conventions about the use of name scopes. ISO 19109 suggests that the scope of all
-     * {@code AttributeType} names is the name of the enclosing {@code FeatureType}, but this is not mandatory.
-     * Users who want to apply this convention can invoke {@code setDefaultScope(featureName)} after
-     * <code>{@linkplain #setName(String) FeatureTypeBuilder.setName}(featureName)</code> but before
-     * <code>{@linkplain AttributeTypeBuilder#setName(String) AttributeTypeBuilder.setName}(attributeName)</code>.</p>
-     *
-     * @param  scope  the new default scope, or {@code null} if none.
-     * @return {@code this} for allowing method calls chaining.
-     */
-    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;
+    @Override
+    final GenericName createGenericName(final CharSequence... names) {
+        return nameFactory.createGenericName(namespace, names);
     }
 
     /**
@@ -551,6 +677,56 @@ public class FeatureTypeBuilder extends
     }
 
     /**
+     * Creates a new attribute for geometries of the given type. This method delegates to {@link #addAttribute(Class)}
+     * with a {@code valueClass} argument inferred from the combination of the {@link GeometryType} argument given to
+     * this method with the {@link GeometryLibrary} argument given at {@linkplain #FeatureTypeBuilder(NameFactory,
+     * GeometryLibrary, Locale) builder creation time}.
+     * The geometry type can be:
+     *
+     * <ul>
+     *   <li>{@link GeometryType#POINT}  for {@code Point} or {@code Point2D} type.</li>
+     *   <li>{@link GeometryType#LINEAR} for {@code Polyline} or {@code LineString} type.</li>
+     *   <li>{@link GeometryType#AREAL}  for {@code Polygon} type.</li>
+     * </ul>
+     *
+     * Geometric objects outside the above list can still be used by declaring their type explicitely.
+     * However in this case there is no isolation level between the geometry types and the library that implement them.
+     *
+     * <div class="note"><b>Example:</b>
+     * the following code creates an attribute named "MyPoint" with values of class
+     * {@link java.awt.geom.Point2D} if the library in use is {@linkplain GeometryLibrary#JAVA2D Java2D}.
+     * The Coordinate Reference System (CRS) uses (<var>longitude</var>, <var>latitude</var>) axes on the WGS 84 datum.
+     * Finally that new attribute is declared the feature <em>default</em> geometry:
+     *
+     * {@preformat java
+     *   builder.addAttribute(GeometryType.POINT).setName("MyPoint")
+     *          .setCRS(CommonCRS.WGS84.normalizedGeographic())
+     *          .addRole(AttributeRole.DEFAULT_GEOMETRY);
+     * }
+     *
+     * If the library in use is JTS or ESRI instead than Java2D,
+     * then the {@code Point} class of those libraries will be used instead of {@code Point2D}.
+     * The fully-qualified class names are given in the {@link GeometryLibrary} javadoc.</div>
+     *
+     * @param  type  kind of geometric object (point, polyline or polygon).
+     * @return a builder for an {@code AttributeType}.
+     */
+    public AttributeTypeBuilder<?> addAttribute(final GeometryType type) {
+        ensureNonNull("type", type);
+        final Class<?> c;
+        if (type.equals(GeometryType.POINT)) {
+            c = geometries.pointClass;
+        } else if (type.equals(GeometryType.LINEAR)) {
+            c = geometries.polylineClass;
+        } else if (type.equals(GeometryType.AREAL)) {
+            c = geometries.polygonClass;
+        } else {
+            throw new IllegalArgumentException(errors().getString(Errors.Keys.UnsupportedArgumentValue_1, type));
+        }
+        return addAttribute(c);
+    }
+
+    /**
      * Creates a new {@code FeatureAssociationRole} builder for features of the given type.
      * The default association name is the name of the given type, but callers should invoke one
      * of the {@code AssociationRoleBuilder.setName(…)} methods on the returned instance with a better name.
@@ -680,6 +856,8 @@ public class FeatureTypeBuilder extends
      *
      * @return the feature type.
      * @throws IllegalStateException if the builder contains inconsistent information.
+     *
+     * @see #clear()
      */
     @Override
     public FeatureType build() throws IllegalStateException {

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/OperationWrapper.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -43,10 +43,11 @@ final class OperationWrapper extends Pro
      * Creates a new wrapper for the given operation.
      */
     OperationWrapper(final FeatureTypeBuilder owner, final PropertyType operation) {
-        super(owner, operation);
+        super(owner);
         this.operation = operation;
         minimumOccurs = 1;
         maximumOccurs = 1;
+        initialize(operation);
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/PropertyTypeBuilder.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -85,13 +85,12 @@ public abstract class PropertyTypeBuilde
     }
 
     /**
-     * Creates a new {@code PropertyType} builder initialized to the values of an existing property.
+     * Creates a new {@code PropertyType} builder.
      *
-     * @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.
+     * @param owner  the builder of the {@code FeatureType} for which to add this property.
      */
-    PropertyTypeBuilder(final FeatureTypeBuilder owner, final PropertyType template) {
-        super(template, owner.getLocale());
+    PropertyTypeBuilder(final FeatureTypeBuilder owner) {
+        super(owner.getLocale());
         this.owner    = owner;
         minimumOccurs = owner.defaultMinimumOccurs;
         maximumOccurs = owner.defaultMaximumOccurs;
@@ -186,12 +185,21 @@ public abstract class PropertyTypeBuilde
     }
 
     /**
-     * Delegates the creation of a new name to the enclosing builder.
+     * Creates a local name in the {@linkplain FeatureTypeBuilder#setNameSpace feature namespace}.
      */
     @Override
-    final GenericName name(final String scope, final String localPart) {
+    final GenericName createLocalName(final CharSequence name) {
         ensureAlive(owner);
-        return owner.name(scope, localPart);
+        return owner.createLocalName(name);
+    }
+
+    /**
+     * Creates a generic name in the {@linkplain FeatureTypeBuilder#setNameSpace feature namespace}.
+     */
+    @Override
+    final GenericName createGenericName(final CharSequence... names) {
+        ensureAlive(owner);
+        return owner.createGenericName(names);
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/TypeBuilder.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -23,6 +23,7 @@ import java.util.Locale;
 import java.util.Objects;
 import org.opengis.util.ScopedName;
 import org.opengis.util.GenericName;
+import org.opengis.util.InternationalString;
 import org.apache.sis.internal.feature.Resources;
 import org.apache.sis.feature.AbstractIdentifiedType;
 import org.apache.sis.util.resources.Vocabulary;
@@ -49,12 +50,28 @@ import org.opengis.feature.PropertyNotFo
  *   <li>the description — information beyond that required for concise definition of the element.</li>
  * </ul>
  *
+ * The name is mandatory and can be specified as either {@link org.opengis.util.LocalName},
+ * {@link org.opengis.util.ScopedName}, {@link String} or {@link InternationalString} instance.
+ * All other properties are optional.
+ *
  * <div class="section">Default namespace</div>
  * In many cases, the names of all {@code AttributeType}s and {@code AssociationRole}s to create
  * within a {@code FeatureType} share the same namespace.
- * For making name creations more convenient, a default namespace can be
- * {@linkplain FeatureTypeBuilder#setDefaultScope specified once} and applied automatically
- * to all names created by the {@link #setName(String)} method.
+ * For making name creations more convenient, the namespace can be
+ * {@linkplain FeatureTypeBuilder#setNameSpace specified once} and applied automatically
+ * to all names created by the {@link #setName(CharSequence)} method.
+ * Note that namespaces will not be visible in the name {@linkplain org.apache.sis.util.iso.DefaultLocalName#toString()
+ * string representation} unless the {@linkplain org.apache.sis.util.iso.DefaultLocalName#toFullyQualifiedName() fully
+ * qualified name} is requested.
+ * Example:
+ *
+ * {@preformat java
+ *     FeatureTypeBuilder builder = new FeatureTypeBuilder().setNameSpace("MyNameSpace").setName("City");
+ *     FeatureType city = builder.build();
+ *
+ *     System.out.println(city.getName());                              // Prints "City"
+ *     System.out.println(city.getName().toFullyQualifiedName());       // Prints "MyNameSpace:City"
+ * }
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -71,6 +88,8 @@ public abstract class TypeBuilder implem
 
     /**
      * Creates a new builder initialized to the values of the given builder.
+     * This constructor is for {@link AttributeTypeBuilder#setValueClass(Class)}
+     * and {@link CharacteristicTypeBuilder#setValueClass(Class)} implementations.
      *
      * @param builder  the builder from which to copy information.
      */
@@ -79,19 +98,36 @@ public abstract class TypeBuilder implem
     }
 
     /**
-     * Creates a new builder initialized to the values of an existing type.
+     * Creates a new builder initialized to the given configuration.
      */
-    TypeBuilder(final IdentifiedType template, final Locale locale) {
+    TypeBuilder(final Locale locale) {
         identification = new HashMap<>(4);
         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());
-            if (template instanceof Deprecable && ((Deprecable) template).isDeprecated()) {
-                identification.put(AbstractIdentifiedType.DEPRECATED_KEY, Boolean.TRUE);
-            }
+    }
+
+    /**
+     * Resets the identification map. After invoking this method, this {@code TypeBuilder}
+     * is in same state that after it has been {@linkplain #TypeBuilder(Locale) constructed}.
+     *
+     * @see #clearCache()
+     */
+    final void reset() {
+        final Object locale = identification.get(Errors.LOCALE_KEY);
+        identification.clear();
+        putIfNonNull(Errors.LOCALE_KEY, locale);
+    }
+
+    /**
+     * Initializes this builder to the value of the given type.
+     * The caller is responsible to invoke {@link #reset()} (if needed) before this method.
+     */
+    final void initialize(final IdentifiedType template) {
+        putIfNonNull(AbstractIdentifiedType.NAME_KEY,        template.getName());
+        putIfNonNull(AbstractIdentifiedType.DEFINITION_KEY,  template.getDefinition());
+        putIfNonNull(AbstractIdentifiedType.DESIGNATION_KEY, template.getDesignation());
+        putIfNonNull(AbstractIdentifiedType.DESCRIPTION_KEY, template.getDescription());
+        if (template instanceof Deprecable && ((Deprecable) template).isDeprecated()) {
+            identification.put(AbstractIdentifiedType.DEPRECATED_KEY, Boolean.TRUE);
         }
     }
 
@@ -126,7 +162,7 @@ public abstract class TypeBuilder implem
                             name = buffer.appendCodePoint(lc).append(name, n, length).toString();
                         }
                     }
-                    identification.put(AbstractIdentifiedType.NAME_KEY, name(null, name));
+                    identification.put(AbstractIdentifiedType.NAME_KEY, createLocalName(name));
                 }
             }
         }
@@ -135,18 +171,20 @@ public abstract class TypeBuilder implem
 
     /**
      * If the object created by the last call to {@code build()} has been cached, clears that cache.
+     *
+     * @see #reset()
      */
     abstract void clearCache();
 
     /**
-     * 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}).
+     * Creates a local name in the {@linkplain FeatureTypeBuilder#setNameSpace feature namespace}.
      */
-    abstract GenericName name(String scope, String localPart);
+    abstract GenericName createLocalName(final CharSequence name);
+
+    /**
+     * Creates a generic name in the {@linkplain FeatureTypeBuilder#setNameSpace feature namespace}.
+     */
+    abstract GenericName createGenericName(final CharSequence... names);
 
     /**
      * Returns the name of the {@code IdentifiedType} to create, or {@code null} if undefined.
@@ -155,8 +193,9 @@ public abstract class TypeBuilder implem
      *
      * @return the name of the {@code IdentifiedType} to create (may be a default name or {@code null}).
      *
-     * @see AbstractIdentifiedType#getName()
      * @see #setName(GenericName)
+     * @see AbstractIdentifiedType#getName()
+     * @see FeatureTypeBuilder#getNameSpace()
      */
     public GenericName getName() {
         return (GenericName) identification().get(AbstractIdentifiedType.NAME_KEY);
@@ -190,7 +229,7 @@ public abstract class TypeBuilder implem
      * @return {@code this} for allowing method calls chaining.
      *
      * @see #getName()
-     * @see #setName(String)
+     * @see #setName(CharSequence)
      * @see AbstractIdentifiedType#NAME_KEY
      */
     public TypeBuilder setName(final GenericName name) {
@@ -202,48 +241,64 @@ public abstract class TypeBuilder implem
     }
 
     /**
-     * Sets the {@code IdentifiedType} 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)}.
-     * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if no default scope
-     * has been specified, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
+     * Sets the {@code IdentifiedType} name as a simple string (local name).
+     * The namespace will be the value specified by the last call to {@link FeatureTypeBuilder#setNameSpace(CharSequence)},
+     * but that namespace will not be visible in the {@linkplain org.apache.sis.util.iso.DefaultLocalName#toString()
+     * string representation} unless the {@linkplain org.apache.sis.util.iso.DefaultLocalName#toFullyQualifiedName()
+     * fully qualified name} is requested.
      *
-     * <p>This convenience method creates a {@link GenericName} instance,
-     * then delegates to {@link #setName(GenericName)}.</p>
+     * <p>This convenience method creates a {@link org.opengis.util.LocalName} instance from
+     * the given {@code CharSequence}, 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 as a {@link String} or {@link InternationalString}.
      * @return {@code this} for allowing method calls chaining.
      *
      * @see #getName()
-     * @see #setName(String, String)
+     * @see #setName(CharSequence...)
+     * @see FeatureTypeBuilder#getNameSpace()
      */
-    public TypeBuilder setName(final String localPart) {
+    public TypeBuilder setName(final CharSequence localPart) {
         ensureNonEmpty("localPart", localPart);
-        return setName(name(null, localPart));
+        return setName(createLocalName(localPart));
     }
 
     /**
      * Sets the {@code IdentifiedType} name as a string in the given scope.
-     * The name will be a {@linkplain org.apache.sis.util.iso.DefaultLocalName local name} if the given scope is
-     * {@code null} or empty, or a {@linkplain org.apache.sis.util.iso.DefaultScopedName scoped name} otherwise.
-     * If a {@linkplain FeatureTypeBuilder#setDefaultScope(String) default scope} has been specified, then the
-     * {@code scope} argument overrides it.
+     * The {@code components} array must contain at least one element.
+     * The last component (the {@linkplain org.apache.sis.util.iso.DefaultScopedName#tip() tip}) will be sufficient
+     * in many cases for calls to the {@link org.apache.sis.feature.AbstractFeature#getProperty(String)} method.
+     * The other elements before the last one are optional and can be used for resolving ambiguity.
+     * They will be visible as the name {@linkplain org.apache.sis.util.iso.DefaultScopedName#path() path}.
+     *
+     * <div class="note"><b>Example:</b>
+     * a call to {@code setName("A", "B", "C")} will create a "A:B:C" name.
+     * A property built with this name can be obtained from a feature by a call to {@code feature.getProperty("C")}
+     * if there is no ambiguity, or otherwise by a call to {@code feature.getProperty("B:C")} (if non-ambiguous) or
+     * {@code feature.getProperty("A:B:C")}.</div>
+     *
+     * In addition to the path specified by the {@code components} array, the name may also contain
+     * a namespace specified by the last call to {@link FeatureTypeBuilder#setNameSpace(CharSequence)}.
+     * But contrarily to the specified components, the namespace will not be visible in the name
+     * {@linkplain org.apache.sis.util.iso.DefaultScopedName#toString() string representation} unless the
+     * {@linkplain org.apache.sis.util.iso.DefaultScopedName#toFullyQualifiedName() fully qualified name} is requested.
+     *
+     * <p>This convenience method creates a {@link org.opengis.util.LocalName} or {@link org.opengis.util.ScopedName}
+     * instance depending on whether the {@code names} array contains exactly 1 element or more than 1 element, then
+     * delegates to {@link #setName(GenericName)}.</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}).
+     * @param  components  the name components as an array of {@link String} or {@link InternationalString} instances.
      * @return {@code this} for allowing method calls chaining.
      *
      * @see #getName()
-     * @see #setName(String)
+     * @see #setName(CharSequence)
+     * @see FeatureTypeBuilder#getNameSpace()
      */
-    public TypeBuilder setName(String scope, final String localPart) {
-        ensureNonEmpty("localPart", localPart);
-        if (scope == null) {
-            scope = "";                                 // For preventing the use of default scope.
+    public TypeBuilder setName(final CharSequence... components) {
+        ensureNonNull("components", components);
+        if (components.length == 0) {
+            throw new IllegalArgumentException(errors().getString(Errors.Keys.EmptyArgument_1, "components"));
         }
-        return setName(name(scope, localPart));
+        return setName(createGenericName(components));
     }
 
     /**
@@ -469,7 +524,7 @@ public abstract class TypeBuilder implem
      * @throws NullArgumentException if {@code text} is null.
      * @throws IllegalArgumentException if {@code text} is empty.
      */
-    final void ensureNonEmpty(final String name, final String text) {
+    final void ensureNonEmpty(final String name, final CharSequence text) {
         if (text == null) {
             throw new NullArgumentException(errors().getString(Errors.Keys.NullArgument_1, name));
         }

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/package-info.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/package-info.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/package-info.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -25,9 +25,11 @@
  * named "Utopia" by default:</p>
  *
  * {@preformat java
+ *     FeatureTypeBuilder builder;
+ *
  *     // Create a feature type for a city, which contains a name and a population.
- *     FeatureTypeBuilder builder = new FeatureTypeBuilder().setName("City");
- *     builder.addAttribute(String.class).setName("name").setDefaultValue("Utopia");
+ *     builder = new FeatureTypeBuilder() .setName("City");
+ *     builder.addAttribute(String.class) .setName("name").setDefaultValue("Utopia");
  *     builder.addAttribute(Integer.class).setName("population");
  *     FeatureType city = builder.build();
  *
@@ -44,9 +46,9 @@
  *   ┌────────────┬─────────┬─────────────┬───────────────┐
  *   │ Name       │ Type    │ Cardinality │ Default value │
  *   ├────────────┼─────────┼─────────────┼───────────────┤
- *   │ name       │ String  │ [1 … 1]     │ Utopia        │
- *   │ population │ Integer │ [1 … 1]     │               │
- *   │ parliament │ String  │ [1 … 1]     │               │
+ *   │ name       │ String  │     [1 … 1] │ Utopia        │
+ *   │ population │ Integer │     [1 … 1] │               │
+ *   │ parliament │ String  │     [1 … 1] │               │
  *   └────────────┴─────────┴─────────────┴───────────────┘
  * }
  *

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/AttributeConvention.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -29,6 +29,8 @@ import org.opengis.feature.AttributeType
 import org.opengis.feature.IdentifiedType;
 import org.opengis.feature.Operation;
 import org.opengis.feature.Property;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.FeatureType;
 
 
 /**
@@ -251,6 +253,25 @@ public final class AttributeConvention e
     }
 
     /**
+     * Returns the Coordinate Reference Systems characteristic for the given property type, or {@code null} if none.
+     * This method gets the default value from the characteristic named {@link #CRS_CHARACTERISTIC}.
+     * If the given property is a link, then this method follows the link in the given feature type (if non-null).
+     *
+     * <p>This method should be used only when the actual property instance is unknown.
+     * Otherwise, {@link #getCRSCharacteristic(Property)} should be used because the CRS
+     * may vary for each property instance.</p>
+     *
+     * @param  feature    the feature type in which to follow links, or {@code null} if none.
+     * @param  attribute  the attribute type for which to get the CRS, or {@code null}.
+     * @return the Coordinate Reference System characteristic of the given property type, 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.
+     */
+    public static CoordinateReferenceSystem getCRSCharacteristic(final FeatureType feature, final PropertyType attribute) {
+        return (CoordinateReferenceSystem) getCharacteristic(feature, attribute, CRS_CHARACTERISTIC.toString());
+    }
+
+    /**
      * Returns whether the given operation or attribute type is characterized by a maximal length.
      * This method verifies whether a characteristic named {@link #MAXIMAL_LENGTH_CHARACTERISTIC}
      * with values of class {@link Integer} exists (directly or indirectly) for the given type.
@@ -266,8 +287,8 @@ public final class AttributeConvention e
      * Returns the maximal length characteristic for the given attribute, or {@code null} if none.
      * This method gets the value or default value from the characteristic named {@link #MAXIMAL_LENGTH_CHARACTERISTIC}.
      *
-     * @param  attribute  the attribute for which to get the CRS, or {@code null}.
-     * @return the Coordinate Reference System characteristic of the given attribute, or {@code null} if none.
+     * @param  attribute  the attribute for which to get the maximal length, or {@code null}.
+     * @return the maximal length 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.
      *
@@ -278,6 +299,25 @@ public final class AttributeConvention e
     }
 
     /**
+     * Returns the maximal length characteristic for the given property type, or {@code null} if none.
+     * This method gets the default value from the characteristic named {@link #MAXIMAL_LENGTH_CHARACTERISTIC}.
+     * If the given property is a link, then this method follows the link in the given feature type (if non-null).
+     *
+     * <p>This method should be used only when the actual property instance is unknown.
+     * Otherwise, {@link #getMaximalLengthCharacteristic(Property)} should be used because
+     * the maximal length may vary for each property instance.</p>
+     *
+     * @param  feature    the feature type in which to follow links, or {@code null} if none.
+     * @param  attribute  the attribute type for which to get the maximal length, or {@code null}.
+     * @return the maximal length characteristic of the given property type, or {@code null} if none.
+     * @throws ClassCastException if {@link #MAXIMAL_LENGTH_CHARACTERISTIC} has been found but is associated
+     *         to an object which is not a {@link CoordinateReferenceSystem} instance.
+     */
+    public static Integer getMaximalLengthCharacteristic(final FeatureType feature, final PropertyType attribute) {
+        return (Integer) getCharacteristic(feature, attribute, MAXIMAL_LENGTH_CHARACTERISTIC.toString());
+    }
+
+    /**
      * Returns {@code true} if the given operation or attribute type has a characteristic of the given name,
      * and the values of that characteristic are assignable to the given {@code valueClass}.
      *
@@ -321,6 +361,30 @@ public final class AttributeConvention e
             if (type != null) {
                 return type.getDefaultValue();
             }
+        }
+        return null;
+    }
+
+    /**
+     * Fetches from the given property the default value of the characteristic of the given name.
+     * If the given property is a link, then this method follows the link in the given feature type
+     * (unless that feature type is null).
+     *
+     * @param  feature         the feature type in which to follow links, or {@code null} if none.
+     * @param  property        the property from which to get the characteristic value, or {@code null}.
+     * @param  characteristic  name of the characteristic from which to get the default value.
+     * @return the default value of the named characteristic in the given property, or {@code null} if none.
+     */
+    private static Object getCharacteristic(final FeatureType feature, PropertyType property, final String characteristic) {
+        final String referent = FeatureUtilities.linkOf(property);
+        if (referent != null && feature != null) {
+            property = feature.getProperty(referent);
+        }
+        if (property instanceof AttributeType<?>) {
+            final AttributeType<?> type = ((AttributeType<?>) property).characteristics().get(characteristic);
+            if (type != null) {
+                return type.getDefaultValue();
+            }
         }
         return null;
     }

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/FeatureUtilities.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -23,8 +23,13 @@ import org.opengis.parameter.ParameterDe
 import org.opengis.parameter.ParameterDescriptorGroup;
 import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
 import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.util.Static;
 
+// Branch-dependent imports
+import org.opengis.feature.PropertyType;
+
 
 /**
  * Non-public utility methods for Apache SIS internal usage.
@@ -37,6 +42,13 @@ import org.apache.sis.util.Static;
  */
 public final class FeatureUtilities extends Static {
     /**
+     * The parameter descriptor for the "Link" operation, which does not take any parameter.
+     * We use those parameters as a way to identify the link operation without making the
+     * {@code LinkOperation} class public.
+     */
+    public static final ParameterDescriptorGroup LINK_PARAMS = parameters("Link");
+
+    /**
      * Do not allow instantiation of this class.
      */
     private FeatureUtilities() {
@@ -56,4 +68,26 @@ public final class FeatureUtilities exte
         properties.put(Identifier.AUTHORITY_KEY, Citations.SIS);
         return new DefaultParameterDescriptorGroup(properties, 1, 1);
     }
+
+    /**
+     * If the given property is a link, returns the name of the referenced property.
+     * Otherwise returns {@code null}.
+     *
+     * @param  property  the property to test, or {@code null} if none.
+     * @return the referenced property name, or {@code null} if none.
+     */
+    static String linkOf(final PropertyType property) {
+        if (property instanceof AbstractOperation) {
+            final AbstractOperation op = (AbstractOperation) property;
+            if (op.getParameters() == LINK_PARAMS) {
+                /*
+                 * The dependencies collection contains exactly one element on Apache SIS implementation.
+                 * However the user could define his own operation with the same parameter descriptor name.
+                 * This is unlikely since it would probably be a bug, but we are paranoiac.
+                 */
+                return CollectionsExt.first(op.getDependencies());
+            }
+        }
+        return null;
+    }
 }

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -16,15 +16,14 @@
  */
 package org.apache.sis.internal.feature;
 
+import java.util.Iterator;
 import java.util.logging.Level;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import com.esri.core.geometry.Geometry;
-import com.esri.core.geometry.Envelope2D;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.util.Static;
+import java.util.logging.LogRecord;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.internal.system.Loggers;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.math.Vector;
 
 
 /**
@@ -34,116 +33,233 @@ import org.apache.sis.internal.system.Lo
  * This gives us a single place to review if we want to support different geometry libraries,
  * or if Apache SIS come with its own implementation.
  *
+ * @param   <G>  the base class of all geometry objects (except point in some implementations).
+ *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  * @since   0.7
  * @module
  */
-public final class Geometries extends Static {
+public abstract class Geometries<G> {
+    /*
+     * Registers all supported library implementations. Those libraries are optional
+     * (users will typically put at most one on their classpath).
+     */
+    static {
+        register("Java2D");
+        register("JTS");
+        register("ESRI");       // Default implementation if other libraries are also present.
+    }
+
     /**
-     * The geometry object from Java Topology Suite (JTS),
-     * or {@code null} if the JTS library is not on the classpath.
+     * The enumeration that identifies the geometry library used.
      */
-    private static final Class<?> JTS;
+    public final GeometryLibrary library;
 
     /**
-     * Getter methods on JTS envelopes, or {@code null} if the JTS library is not on the classpath.
-     * Each methods take no argument and return a {@code double} value.
+     * The root geometry class.
      */
-    private static final Method INTERNAL, MIN_X, MIN_Y, MAX_X, MAX_Y;
+    public final Class<G> rootClass;
 
-    static {
-        Class<?> type;
-        Method genv, xmin, ymin, xmax, ymax;
+    /**
+     * The class for points.
+     */
+    public final Class<?> pointClass;
+
+    /**
+     * The class for polylines and polygons.
+     */
+    public final Class<? extends G> polylineClass, polygonClass;
+
+    /**
+     * The default geometry implementation to use. Unmodifiable after class initialization.
+     */
+    private static Geometries<?> implementation;
+
+    /**
+     * The fallback implementation to use if the default one is not available.
+     */
+    private final Geometries<?> fallback;
+
+    /**
+     * Creates a new adapter for the given root geometry class.
+     */
+    Geometries(final GeometryLibrary library, final Class<G> rootClass, final Class<?> pointClass,
+            final Class<? extends G> polylineClass, final Class<? extends G> polygonClass)
+    {
+        this.library       = library;
+        this.rootClass     = rootClass;
+        this.pointClass    = pointClass;
+        this.polylineClass = polylineClass;
+        this.polygonClass  = polygonClass;
+        fallback = implementation;
+    }
+
+    /**
+     * Registers the library implementation of the given name (JTS or ESRI) if present; ignore otherwise.
+     * The given name shall be the simple name of a {@code Geometries} subclass in the same package.
+     * The last registered library will be the default implementation.
+     */
+    private static void register(final String name) {
+        String classname = Geometries.class.getName();
+        classname = classname.substring(0, classname.lastIndexOf('.')+1).concat(name);
         try {
-            final Class<?> envt;
-            type = Class.forName("com.vividsolutions.jts.geom.Geometry");
-            genv = type.getMethod("getEnvelopeInternal", (Class[]) null);
-            envt = genv.getReturnType();
-            xmin = envt.getMethod("getMinX", (Class[]) null);
-            ymin = envt.getMethod("getMinY", (Class[]) null);
-            xmax = envt.getMethod("getMaxX", (Class[]) null);
-            ymax = envt.getMethod("getMaxY", (Class[]) null);
-        } catch (ClassNotFoundException | NoSuchMethodException e) {
-            Logging.getLogger(Loggers.GEOMETRY).log(Level.CONFIG, e.toString());
-            type = null;
-            genv = null;
-            xmin = null;
-            xmax = null;
-            ymin = null;
-            ymax = null;
-        }
-        JTS = type;
-        INTERNAL = genv;
-        MIN_X = xmin;
-        MIN_Y = ymin;
-        MAX_X = xmax;
-        MAX_Y = ymax;
+            implementation = (Geometries) Class.forName(classname).newInstance();
+        } catch (ReflectiveOperationException | LinkageError e) {
+            LogRecord record = Resources.forLocale(null).getLogRecord(Level.CONFIG,
+                    Resources.Keys.OptionalLibraryNotFound_2, name, e.toString());
+            record.setLoggerName(Loggers.GEOMETRY);
+            Logging.log(Geometries.class, "register", record);
+        }
     }
 
     /**
-     * Do not allow instantiation of this class.
+     * Returns an accessor to the default geometry library implementation in use.
+     *
+     * @param  library  the required library, or {@code null} for the default.
+     * @return the default geometry implementation.
+     * @throws IllegalArgumentException if the given library is non-null but not available.
      */
-    private Geometries() {
+    public static Geometries<?> implementation(final GeometryLibrary library) {
+        if (library == null) {
+            return implementation;
+        }
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            if (g.library == library) return g;
+        }
+        throw new IllegalArgumentException(Resources.format(Resources.Keys.UnavailableGeometryLibrary_1, library));
     }
 
     /**
-     * Returns {@code true} if the given type is one of the type known to Apache SIS.
+     * Returns {@code true} if the given type is one of the types known to Apache SIS.
      *
      * @param  type  the type to verify.
      * @return {@code true} if the given type is one of the geometry type known to SIS.
      */
     public static boolean isKnownType(final Class<?> type) {
-        return Geometry.class.isAssignableFrom(type) || (JTS != null && JTS.isAssignableFrom(type));
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            if (g.rootClass.isAssignableFrom(type)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * If the given point is an implementation of this library, returns its coordinate.
+     * Otherwise returns {@code null}.
+     */
+    abstract double[] tryGetCoordinate(Object point);
+
+    /**
+     * If the given object is one of the recognized point implementation, returns its coordinate.
+     * Otherwise returns {@code null}. If non-null, the returned array may have a length of 2 or 3.
+     * If the CRS is geographic, then the (x,y) values should be (longitude, latitude) for compliance
+     * with usage in ESRI and JTS libraries.
+     *
+     * @param  point  the point from which to get the coordinate, or {@code null}.
+     * @return the coordinate of the given point as an array of length 2 or 3,
+     *         or {@code null} if the given object is not a recognized implementation.
+     *
+     * @see #createPoint(double, double)
+     */
+    public static double[] getCoordinate(final Object point) {
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            double[] coord = g.tryGetCoordinate(point);
+            if (coord != null) return coord;
+        }
+        return null;
     }
 
     /**
+     * If the given geometry is the type supported by this {@code Geometries} instance,
+     * returns its envelope if non-empty. Otherwise returns {@code null}. We currently
+     * do not distinguish the reasons why this method may return null.
+     */
+    abstract GeneralEnvelope tryGetEnvelope(Object geometry);
+
+    /**
      * If the given object is one of the recognized type and its envelope is non-empty,
      * returns that envelope as an Apache SIS implementation. Otherwise returns {@code null}.
      *
      * @param  geometry  the geometry from which to get the envelope, or {@code null}.
-     * @return the envelope of the given object, or {@code null} if the object is not
-     *         a recognized geometry or its envelope is empty.
+     * @return the envelope of the given geometry, or {@code null} if the given object
+     *         is not a recognized geometry or its envelope is empty.
      */
     public static GeneralEnvelope getEnvelope(final Object geometry) {
-        final double xmin, ymin, xmax, ymax;
-        if (geometry instanceof Geometry) {
-            final Envelope2D bounds = new Envelope2D();
-            ((Geometry) geometry).queryEnvelope2D(bounds);
-            if (bounds.isEmpty()) {                                     // Test if there is NaN values.
-                return null;
-            }
-            xmin = bounds.xmin;
-            ymin = bounds.ymin;
-            xmax = bounds.xmax;
-            ymax = bounds.ymax;
-        } else if (JTS != null && JTS.isInstance(geometry)) {
-            try {
-                final Object env = INTERNAL.invoke(geometry, (Object[]) null);
-                xmin = (Double) MIN_X.invoke(env, (Object[]) null);
-                ymin = (Double) MIN_Y.invoke(env, (Object[]) null);
-                xmax = (Double) MAX_X.invoke(env, (Object[]) null);
-                ymax = (Double) MAX_Y.invoke(env, (Object[]) null);
-            } catch (ReflectiveOperationException e) {
-                if (e instanceof InvocationTargetException) {
-                    final Throwable cause = e.getCause();
-                    if (cause instanceof RuntimeException) {
-                        throw (RuntimeException) cause;
-                    }
-                    if (cause instanceof Error) {
-                        throw (Error) cause;
+        for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+            GeneralEnvelope env = g.tryGetEnvelope(geometry);
+            if (env != null) return env;
+        }
+        return null;
+    }
+
+    /**
+     * Creates a two-dimensional point from the given coordinate. If the CRS is geographic, then the
+     * (x,y) values should be (longitude, latitude) for compliance with usage in ESRI and JTS libraries.
+     *
+     * @param  x  the first ordinate value.
+     * @param  y  the second ordinate value.
+     * @return the point for the given ordinate values.
+     *
+     * @see #getCoordinate(Object)
+     */
+    public abstract Object createPoint(double x, double y);
+
+    /**
+     * Creates a path or polyline from the given ordinate values.
+     * The array of ordinate vectors will be handled as if all vectors were concatenated in a single vector,
+     * ignoring {@code null} array elements.
+     * Each {@link Double#NaN} ordinate value in the concatenated vector starts a new path.
+     * The implementation returned by this method is an instance of {@link #rootClass}.
+     *
+     * @param  dimension  the number of dimensions (2 or 3).
+     * @param  ordinates  sequence of (x,y) or (x,y,z) tuples.
+     * @return the geometric object for the given points.
+     * @throws UnsupportedOperationException if the geometry library can not create the requested path.
+     */
+    public abstract G createPolyline(int dimension, Vector... ordinates);
+
+    /**
+     * Merges a sequence of polyline instances if the first instance is an implementation of this library.
+     *
+     * @param  first      the first instance to merge.
+     * @param  polylines  the second and subsequent instances to merge.
+     * @return the merged polyline, or {@code null} if the first instance is not an implementation of this library.
+     * @throws ClassCastException if an element in the iterator is not an implementation of this library.
+     */
+    abstract G tryMergePolylines(Object first, Iterator<?> polylines);
+
+    /**
+     * Merges a sequence of points or polylines into a single polyline instances.
+     * Each previous polyline will be a separated path in the new polyline instances.
+     * The implementation returned by this method is an instance of {@link #rootClass}.
+     *
+     * @param  paths  the points or polylines to merge in a single polyline object.
+     * @return the merged polyline, or {@code null} if the given iterator has no element.
+     * @throws ClassCastException if not all elements in the given iterator are instances of the same library.
+     */
+    public static Object mergePolylines(final Iterator<?> paths) {
+        while (paths.hasNext()) {
+            final Object first = paths.next();
+            if (first != null) {
+                for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+                    final Object merged = g.tryMergePolylines(first, paths);
+                    if (merged != null) {
+                        return merged;
                     }
                 }
-                // Should never happen unless JTS's API changed.
-                throw (Error) new IncompatibleClassChangeError(e.toString()).initCause(e);
+                throw unsupported(2);
             }
-        } else {
-            return null;
         }
-        final GeneralEnvelope env = new GeneralEnvelope(2);
-        env.setRange(0, xmin, xmax);
-        env.setRange(1, ymin, ymax);
-        return env;
+        return null;
+    }
+
+    /**
+     * Returns an error message for an unsupported geometry object.
+     *
+     * @param  dimension  number of dimensions (2 or 3) requested for the geometry object.
+     */
+    static UnsupportedOperationException unsupported(final int dimension) {
+        return new UnsupportedOperationException(Resources.format(Resources.Keys.UnsupportedGeometryObject_1, dimension));
     }
 }

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -133,6 +133,12 @@ public final class Resources extends Ind
         public static final short NotASingleton_1 = 14;
 
         /**
+         * The {0} optional library is not available. Geometric operations will ignore that library.
+         * Cause is {1}.
+         */
+        public static final short OptionalLibraryNotFound_2 = 19;
+
+        /**
          * Property “{1}” already exists in feature “{0}”.
          */
         public static final short PropertyAlreadyExists_2 = 15;
@@ -143,15 +149,30 @@ public final class Resources extends Ind
         public static final short PropertyNotFound_2 = 16;
 
         /**
+         * The {0} geometry library is not available in current runtime environment.
+         */
+        public static final short UnavailableGeometryLibrary_1 = 21;
+
+        /**
          * The “{1}” value given to “{0}” property should be separable in {2} components, but we got
          * {3}.
          */
         public static final short UnexpectedNumberOfComponents_4 = 17;
 
         /**
+         * The “{0}” feature at {1} has a {3} ordinate values, while we expected a multiple of {2}.
+         */
+        public static final short UnexpectedNumberOfOrdinates_4 = 22;
+
+        /**
          * Feature named “{0}” has not yet been resolved.
          */
         public static final short UnresolvedFeatureName_1 = 18;
+
+        /**
+         * Unsupported geometry {0}D object.
+         */
+        public static final short UnsupportedGeometryObject_1 = 20;
     }
 
     /**

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties [ISO-8859-1] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources.properties [ISO-8859-1] Wed Jul 26 16:14:09 2017
@@ -33,7 +33,11 @@ IllegalPropertyValueClass_3       = Prop
 MismatchedPropertyType_1          = Mismatched type for \u201c{0}\u201d property.
 MismatchedValueClass_3            = An attribute for \u2018{1}\u2019 values where expected, but the \u201c{0}\u201d attribute specifies values of type \u2018{2}\u2019.
 NotASingleton_1                   = Property \u201c{0}\u201d contains more than one value.
+OptionalLibraryNotFound_2         = The {0} optional library is not available. Geometric operations will ignore that library.\nCause is {1}.
 PropertyAlreadyExists_2           = Property \u201c{1}\u201d already exists in feature \u201c{0}\u201d.
 PropertyNotFound_2                = No property named \u201c{1}\u201d has been found in \u201c{0}\u201d feature.
+UnavailableGeometryLibrary_1      = The {0} geometry library is not available in current runtime environment.
 UnexpectedNumberOfComponents_4    = The \u201c{1}\u201d value given to \u201c{0}\u201d property should be separable in {2} components, but we got {3}.
+UnexpectedNumberOfOrdinates_4     = The \u201c{0}\u201d feature at {1} has a {3} ordinate values, while we expected a multiple of {2}.
 UnresolvedFeatureName_1           = Feature named \u201c{0}\u201d has not yet been resolved.
+UnsupportedGeometryObject_1       = Unsupported geometry {0}D object.

Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Resources_fr.properties [ISO-8859-1] Wed Jul 26 16:14:09 2017
@@ -38,7 +38,11 @@ IllegalPropertyValueClass_3       = La p
 MismatchedPropertyType_1          = Le type de la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb ne correspond pas.
 MismatchedValueClass_3            = Un attribut pour des valeurs de type \u2018{1}\u2019 \u00e9tait attendu, mais l\u2019attribut \u00ab\u202f{0}\u202f\u00bb sp\u00e9cifie des valeurs de type \u2018{2}\u2019.
 NotASingleton_1                   = La propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb contient plus de une valeur.
+OptionalLibraryNotFound_2         = La biblioth\u00e8que optionnelle {0} n\u2019est pas disponible. Les op\u00e9rations g\u00e9om\u00e9triques ignoreront cette biblioth\u00e8que.\nLa cause est {1}.
 PropertyNotFound_2                = Aucune propri\u00e9t\u00e9 nomm\u00e9e \u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9e dans l\u2019entit\u00e9 \u00ab\u202f{0}\u202f\u00bb.
 PropertyAlreadyExists_2           = La propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb existe d\u00e9j\u00e0 dans l\u2019entit\u00e9 \u00ab\u202f{0}\u202f\u00bb.
+UnavailableGeometryLibrary_1      = La biblioth\u00e8que de g\u00e9om\u00e9tries {0} n\u2019est pas disponible dans l\u2019environnement d\u2019ex\u00e9cution actuel.
 UnexpectedNumberOfComponents_4    = La valeur \u00ab\u202f{1}\u202f\u00bb donn\u00e9e \u00e0 la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb devrait \u00eatre s\u00e9parable en {2} composantes, mais on en a obtenus {3}.
+UnexpectedNumberOfOrdinates_4     = L\u2019entit\u00e9 nomm\u00e9e \u00ab\u202f{0}\u202f\u00bb \u00e0 {1} contient {3} ordonn\u00e9es, alors qu\u2019on attendait un multiple de {2}.
 UnresolvedFeatureName_1           = L\u2019entit\u00e9 nomm\u00e9e \u00ab\u202f{0}\u202f\u00bb n\u2019a pas encore \u00e9t\u00e9 r\u00e9solue.
+UnsupportedGeometryObject_1       = Object g\u00e9om\u00e9trique {0}D non-support\u00e9.

Modified: sis/branches/JDK9/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java?rev=1803070&r1=1803069&r2=1803070&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-feature/src/test/java/org/apache/sis/feature/DefaultFeatureTypeTest.java [UTF-8] Wed Jul 26 16:14:09 2017
@@ -353,6 +353,15 @@ public final strictfp class DefaultFeatu
         } catch (IllegalArgumentException e) {
             final String message = e.getMessage();
             assertTrue(message, message.contains("name"));      // Property name.
+            assertTrue(message, message.contains("ns1:name"));  // Ambiguity 1.
+            assertTrue(message, message.contains("ns2:name"));  // Ambiguity 2.
+        }
+        try {
+            feature.getProperty("other");
+            fail("Expected no property.");
+        } catch (IllegalArgumentException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("other"));     // Property name.
             assertTrue(message, message.contains("City"));      // Feature name.
         }
     }



Mime
View raw message