You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2017/07/26 16:14:14 UTC
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...
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.
}
}