Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractFeature.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.Collection; import java.util.Collections; import java.io.Serializable; +import org.opengis.util.ScopedName; import org.opengis.util.GenericName; import org.opengis.metadata.quality.DataQuality; import org.opengis.metadata.maintenance.ScopeCode; @@ -242,12 +243,14 @@ public abstract class AbstractFeature im } else if (pt instanceof FeatureAssociationRole) { return ((FeatureAssociationRole) pt).newInstance(); } else { - throw unsupportedPropertyType(pt.getName()); + throw new IllegalArgumentException(unsupportedPropertyType(pt.getName())); } } /** * Executes the parameterless operation of the given name and returns its result. + * + * @see #getOperationValue(String) */ final Property getOperationResult(final String name) { /* @@ -260,41 +263,6 @@ public abstract class AbstractFeature im } /** - * Executes the parameterless operation of the given name and returns the value of its result. - */ - final Object getOperationValue(final String name) { - final Operation operation = (Operation) type.getProperty(name); - if (operation instanceof LinkOperation) { - return getPropertyValue(((LinkOperation) operation).referentName); - } - final Property result = operation.apply(this, null); - if (result instanceof Attribute<?>) { - return getAttributeValue((Attribute<?>) result); - } else if (result instanceof FeatureAssociation) { - return getAssociationValue((FeatureAssociation) result); - } else { - return null; - } - } - - /** - * Executes the parameterless operation of the given name and sets the value of its result. - */ - final void setOperationValue(final String name, final Object value) { - final Operation operation = (Operation) type.getProperty(name); - if (operation instanceof LinkOperation) { - setPropertyValue(((LinkOperation) operation).referentName, value); - } else { - final Property result = operation.apply(this, null); - if (result != null) { - setPropertyValue(result, value); - } else { - throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSetPropertyValue_1, name)); - } - } - } - - /** * Returns the default value to be returned by {@link #getPropertyValue(String)} * for the property of the given name. * @@ -310,7 +278,7 @@ public abstract class AbstractFeature im final int maximumOccurs = ((FeatureAssociationRole) pt).getMaximumOccurs(); return maximumOccurs > 1 ? Collections.EMPTY_LIST : null; // No default value for associations. } else { - throw unsupportedPropertyType(pt.getName()); + throw new IllegalArgumentException(unsupportedPropertyType(pt.getName())); } } @@ -387,6 +355,73 @@ public abstract class AbstractFeature im public abstract void setPropertyValue(final String name, final Object value) throws IllegalArgumentException; /** + * Executes the parameterless operation of the given name and returns the value of its result. + * This is a convenience method for sub-classes where some properties may be operations that + * {@linkplain AbstractOperation#getDependencies() depend} on other properties of this {@code Feature} instance + * (for example a {@linkplain FeatureOperations#link link} to another property value). + * Invoking this method is equivalent to performing the following steps: + * + * {@preformat java + * Operation operation = (Operation) type.getProperty(name); + * Property result = operation.apply(this, null); + * if (result instanceof Attribute<?>) { + * return ...; // the attribute value. + * } else if (result instanceof FeatureAssociation) { + * return ...; // the associated feature. + * } else { + * return null; + * } + * } + * + * @param name the name of the operation to execute. The caller is responsible to ensure that the + * property type for that name is an instance of {@link Operation}. + * @return the result value of the given operation, or {@code null} if none. + * + * @since 0.8 + */ + protected Object getOperationValue(final String name) { + final Operation operation = (Operation) type.getProperty(name); + if (operation instanceof LinkOperation) { + return getPropertyValue(((LinkOperation) operation).referentName); + } + final Property result = operation.apply(this, null); + if (result instanceof Attribute<?>) { + return getAttributeValue((Attribute<?>) result); + } else if (result instanceof FeatureAssociation) { + return getAssociationValue((FeatureAssociation) result); + } else { + return null; + } + } + + /** + * Executes the parameterless operation of the given name and sets the value of its result. + * This method is the complement of {@link #getOperationValue(String)} for subclasses where + * some properties may be operations. Not all operations accept assignments, + * but the {@linkplain FeatureOperations#link link} operation for instance does. + * + * @param name the name of the operation to execute. The caller is responsible to ensure that the + * property type for that name is an instance of {@link Operation}. + * @param value the value to assign to the result of the named operation. + * @throws IllegalStateException if the operation of the given name does not accept assignment. + * + * @since 0.8 + */ + protected void setOperationValue(final String name, final Object value) { + final Operation operation = (Operation) type.getProperty(name); + if (operation instanceof LinkOperation) { + setPropertyValue(((LinkOperation) operation).referentName, value); + } else { + final Property result = operation.apply(this, null); + if (result != null) { + setPropertyValue(result, value); + } else { + throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSetPropertyValue_1, name)); + } + } + } + + /** * Returns the value of the given attribute, as a singleton or as a collection depending * on the maximum number of occurrences. */ @@ -411,7 +446,7 @@ public abstract class AbstractFeature im } else if (property instanceof FeatureAssociation) { setAssociationValue((FeatureAssociation) property, value); } else { - throw unsupportedPropertyType(property.getName()); + throw new IllegalArgumentException(unsupportedPropertyType(property.getName())); } } @@ -439,7 +474,7 @@ public abstract class AbstractFeature im } while ((element = it.next()) == null || base.isInstance(element)); // Found an illegal value. Exeption is thrown below. } - throw illegalValueClass(pt, base, element); // 'element' can not be null here. + throw new ClassCastException(illegalValueClass(pt, base, element)); // 'element' can not be null here. } } ((Attribute) attribute).setValue(value); @@ -457,14 +492,14 @@ public abstract class AbstractFeature im if (value instanceof Feature) { final FeatureType actual = ((Feature) value).getType(); if (base != actual && !DefaultFeatureType.maybeAssignableFrom(base, actual)) { - throw illegalFeatureType(role, base, actual); + throw new InvalidPropertyValueException(illegalFeatureType(role, base, actual)); } } else if (value instanceof Collection<?>) { verifyAssociationValues(role, (Collection<?>) value); association.setValues((Collection<? extends Feature>) value); return; // Skip the setter at the end of this method. } else { - throw illegalValueClass(role, Feature.class, value); + throw new ClassCastException(illegalValueClass(role, Feature.class, value)); } } association.setValue((Feature) value); @@ -531,7 +566,7 @@ public abstract class AbstractFeature im return verifyAssociationValue((FeatureAssociationRole) pt, value); } } else { - throw unsupportedPropertyType(pt.getName()); + throw new IllegalArgumentException(unsupportedPropertyType(pt.getName())); } return value; } @@ -554,7 +589,7 @@ public abstract class AbstractFeature im } else if (!isSingleton && value instanceof Collection<?>) { return CheckedArrayList.castOrCopy((Collection<?>) value, valueClass); } else { - throw illegalValueClass(type, valueClass, value); + throw new ClassCastException(illegalValueClass(type, valueClass, value)); } } @@ -580,13 +615,13 @@ public abstract class AbstractFeature im if (base == valueType || DefaultFeatureType.maybeAssignableFrom(base, valueType)) { return isSingleton ? value : singletonList(Feature.class, role.getMinimumOccurs(), value); } else { - throw illegalFeatureType(role, base, valueType); + throw new InvalidPropertyValueException(illegalFeatureType(role, base, valueType)); } } else if (!isSingleton && value instanceof Collection<?>) { verifyAssociationValues(role, (Collection<?>) value); return CheckedArrayList.castOrCopy((Collection<?>) value, Feature.class); } else { - throw illegalValueClass(role, Feature.class, value); + throw new ClassCastException(illegalValueClass(role, Feature.class, value)); } } @@ -599,11 +634,11 @@ public abstract class AbstractFeature im for (final Object value : values) { ArgumentChecks.ensureNonNullElement("values", index, value); if (!(value instanceof Feature)) { - throw illegalValueClass(role, Feature.class, value); + throw new ClassCastException(illegalValueClass(role, Feature.class, value)); } final FeatureType type = ((Feature) value).getType(); if (base != type && !DefaultFeatureType.maybeAssignableFrom(base, type)) { - throw illegalFeatureType(role, base, type); + throw new InvalidPropertyValueException(illegalFeatureType(role, base, type)); } index++; } @@ -621,34 +656,57 @@ public abstract class AbstractFeature im } /** - * Returns the exception for a property type which is neither an attribute or an association. + * Returns the exception message for a property not found. The message will differ depending + * on whether the property is not found because ambiguous or because it does not exist. + * + * @param feature the name of the feature where a property where searched ({@link String} or {@link GenericName}). + * @param property the name of the property which has not been found. + */ + static String propertyNotFound(final FeatureType type, final Object feature, final String property) { + GenericName ambiguous = null; + for (final IdentifiedType p : type.getProperties(true)) { + final GenericName next = p.getName(); + GenericName name = next; + do { + if (property.equalsIgnoreCase(name.toString())) { + if (ambiguous == null) { + ambiguous = next; + } else { + return Errors.format(Errors.Keys.AmbiguousName_3, ambiguous, next, property); + } + } + } while (name instanceof ScopedName && (name = ((ScopedName) name).tail()) != null); + } + return Resources.format(Resources.Keys.PropertyNotFound_2, feature, property); + } + + /** + * Returns the exception message for a property type which is neither an attribute or an association. * This method is invoked after a {@link PropertyType} has been found for the user-supplied name, * but that property can not be stored in or extracted from a {@link Property} instance. */ - static IllegalArgumentException unsupportedPropertyType(final GenericName name) { - return new IllegalArgumentException(Resources.format(Resources.Keys.CanNotInstantiateProperty_1, name)); + static String unsupportedPropertyType(final GenericName name) { + return Resources.format(Resources.Keys.CanNotInstantiateProperty_1, name); } /** - * Returns the exception for a property value of wrong Java class. + * Returns the exception message for a property value of wrong Java class. * * @param value the value, which shall be non-null. */ - private static ClassCastException illegalValueClass( - final IdentifiedType property, final Class<?> expected, final Object value) - { - return new ClassCastException(Resources.format(Resources.Keys.IllegalPropertyValueClass_3, - property.getName(), expected, value.getClass())); + private static String illegalValueClass(final IdentifiedType property, final Class<?> expected, final Object value) { + return Resources.format(Resources.Keys.IllegalPropertyValueClass_3, + property.getName(), expected, value.getClass()); } /** - * Returns the exception for an association value of wrong type. + * Returns the exception message for an association value of wrong type. */ - private static InvalidPropertyValueException illegalFeatureType( + private static String illegalFeatureType( final FeatureAssociationRole association, final FeatureType expected, final FeatureType actual) { - return new InvalidPropertyValueException(Resources.format(Resources.Keys.IllegalFeatureType_3, - association.getName(), expected.getName(), actual.getName())); + return Resources.format(Resources.Keys.IllegalFeatureType_3, + association.getName(), expected.getName(), actual.getName()); } /**
Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAssociationRole.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.IdentityHashMap; import org.opengis.util.GenericName; import org.opengis.util.InternationalString; +import org.opengis.metadata.Identifier; import org.apache.sis.internal.feature.Resources; import org.apache.sis.util.Debug; @@ -33,6 +34,7 @@ import org.opengis.feature.AttributeType import org.opengis.feature.FeatureType; import org.opengis.feature.FeatureAssociation; import org.opengis.feature.FeatureAssociationRole; +import org.opengis.feature.PropertyNotFoundException; /** @@ -378,35 +380,62 @@ public class DefaultAssociationRole exte /** * Returns the name of the property to use as a title for the associated feature, or {@code null} if none. - * This method searches for the first attribute having a value class assignable to {@link CharSequence}. + * This method applies the following heuristic rules: + * + * <ul> + * <li>If associated feature has a property named {@code "sis:identifier"}, then this method returns that name.</li> + * <li>Otherwise if the associated feature has a mandatory property of type {@link CharSequence}, {@link GenericName} + * or {@link Identifier}, then this method returns the name of that property.</li> + * <li>Otherwise if the associated feature has an optional property of type {@link CharSequence}, {@link GenericName} + * or {@link Identifier}, then this method returns the name of that property.</li> + * <li>Otherwise this method returns {@code null}.</li> + * </ul> + * + * This method should be used only for display purpose, not as a reliable or stable way to get the identifier. + * The heuristic rules implemented in this method may change in any future Apache SIS version. */ static String getTitleProperty(final FeatureAssociationRole role) { if (role instanceof DefaultAssociationRole) { - String p = ((DefaultAssociationRole) role).titleProperty; // No synchronization - not a big deal if computed twice. + String p = ((DefaultAssociationRole) role).titleProperty; // No synchronization - not a big deal if computed twice. if (p != null) { return p.isEmpty() ? null : p; } - p = searchTitleProperty(role); + p = searchTitleProperty(role.getValueType()); ((DefaultAssociationRole) role).titleProperty = (p != null) ? p : ""; return p; } - return searchTitleProperty(role); + return searchTitleProperty(role.getValueType()); } /** * Implementation of {@link #getTitleProperty(FeatureAssociationRole)} for first search, * or for non-SIS {@code FeatureAssociationRole} implementations. */ - private static String searchTitleProperty(final FeatureAssociationRole role) { - for (final PropertyType type : role.getValueType().getProperties(true)) { + private static String searchTitleProperty(final FeatureType ft) { + String fallback = null; + try { + return ft.getProperty("sis:identifier").getName().toString(); + } catch (PropertyNotFoundException e) { + // Ignore. + } + for (final PropertyType type : ft.getProperties(true)) { if (type instanceof AttributeType<?>) { final AttributeType<?> pt = (AttributeType<?>) type; - if (pt.getMaximumOccurs() != 0 && CharSequence.class.isAssignableFrom(pt.getValueClass())) { - return pt.getName().toString(); + final Class<?> valueClass = pt.getValueClass(); + if (CharSequence.class.isAssignableFrom(valueClass) || + GenericName .class.isAssignableFrom(valueClass) || + Identifier .class.isAssignableFrom(valueClass)) + { + final String name = pt.getName().toString(); + if (pt.getMaximumOccurs() != 0) { + return name; + } else if (fallback == null) { + fallback = name; + } } } } - return null; + return fallback; } /** Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -186,6 +186,7 @@ public class DefaultAttributeType<V> ext * * @see org.apache.sis.feature.builder.AttributeTypeBuilder */ + @SuppressWarnings("ThisEscapedInObjectConstruction") // Okay because used only in package-private class. public DefaultAttributeType(final Map<String,?> identification, final Class<V> valueClass, final int minimumOccurs, final int maximumOccurs, final V defaultValue, final AttributeType<?>... characterizedBy) Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultFeatureType.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -712,27 +712,59 @@ public class DefaultFeatureType extends */ private static boolean isAssignableIgnoreName(final PropertyType base, final PropertyType other) { if (base != other) { + /* + * If the base property is an attribute, then the overriding property shall be either an attribute + * or a parameterless operation producing an attribute. The parameterless operation is considered + * has having a [1…1] cardinality. + */ if (base instanceof AttributeType<?>) { - if (!(other instanceof AttributeType<?>)) { + final AttributeType<?> p0 = (AttributeType<?>) base; + final AttributeType<?> p1; + if (other instanceof AttributeType<?>) { + p1 = (AttributeType<?>) other; + } else if (isParameterlessOperation(other)) { + final IdentifiedType result = ((Operation) other).getResult(); + if (result instanceof AttributeType<?>) { + p1 = (AttributeType<?>) result; + } else { + return false; + } + } else { return false; } - final AttributeType<?> p0 = (AttributeType<?>) base; - final AttributeType<?> p1 = (AttributeType<?>) other; - if (!p0.getValueClass().isAssignableFrom(p1.getValueClass()) || - p0.getMinimumOccurs() > p1.getMinimumOccurs() || - p0.getMaximumOccurs() < p1.getMaximumOccurs()) + final int minOccurs, maxOccurs; + if (!p0.getValueClass().isAssignableFrom(p1.getValueClass()) || + (minOccurs = p0.getMinimumOccurs()) > p1.getMinimumOccurs() || + (maxOccurs = p0.getMaximumOccurs()) < p1.getMaximumOccurs() || + (p1 != other && (minOccurs > 1 || maxOccurs < 1))) // [1…1] cardinality for operations. { return false; } } + /* + * Unconditionally test for associations even if we executed the previous block for attributes, + * because an implementation could implement both AttributeType and AssociationRole interfaces. + * This is not recommended, but if it happen we want a behavior as consistent as possible. + */ if (base instanceof FeatureAssociationRole) { - if (!(other instanceof FeatureAssociationRole)) { + final FeatureAssociationRole p0 = (FeatureAssociationRole) base; + final FeatureAssociationRole p1; + if (other instanceof FeatureAssociationRole) { + p1 = (FeatureAssociationRole) other; + } else if (isParameterlessOperation(other)) { + final IdentifiedType result = ((Operation) other).getResult(); + if (result instanceof FeatureAssociationRole) { + p1 = (FeatureAssociationRole) result; + } else { + return false; + } + } else { return false; } - final FeatureAssociationRole p0 = (FeatureAssociationRole) base; - final FeatureAssociationRole p1 = (FeatureAssociationRole) other; - if (p0.getMinimumOccurs() > p1.getMinimumOccurs() || - p0.getMaximumOccurs() < p1.getMaximumOccurs()) + final int minOccurs, maxOccurs; + if ((minOccurs = p0.getMinimumOccurs()) > p1.getMinimumOccurs() || + (maxOccurs = p0.getMaximumOccurs()) < p1.getMaximumOccurs() || + (p1 != other && (minOccurs > 1 || maxOccurs < 1))) // [1…1] cardinality for operations. { return false; } @@ -742,17 +774,26 @@ public class DefaultFeatureType extends return false; } } + /* + * Operations can be overridden by other operations having the same parameters. + * In the special case of parameterless operations, can also be overridden by + * AttributeType or FeatureAssociationRole. + */ if (base instanceof Operation) { - if (!(other instanceof Operation)) { - return false; - } final Operation p0 = (Operation) base; - final Operation p1 = (Operation) other; - if (!Objects.equals(p0.getParameters(), p1.getParameters())) { + final IdentifiedType r1; + if (other instanceof Operation) { + final Operation p1 = (Operation) other; + if (!Objects.equals(p0.getParameters(), p1.getParameters())) { + return false; + } + r1 = p1.getResult(); + } else if (isParameterlessOperation(base)) { + r1 = other; + } else { return false; } final IdentifiedType r0 = p0.getResult(); - final IdentifiedType r1 = p1.getResult(); if (r0 != r1) { if (r0 instanceof FeatureType) { if (!(r1 instanceof FeatureType) || !((FeatureType) r0).isAssignableFrom((FeatureType) r1)) { @@ -764,6 +805,7 @@ public class DefaultFeatureType extends return false; } } + // No need for explicit AttributeType or Operation checks because they are PropertyType. } } } @@ -822,7 +864,7 @@ public class DefaultFeatureType extends if (pt != null) { return pt; } - throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name)); + throw new PropertyNotFoundException(AbstractFeature.propertyNotFound(this, getName(), name)); } /** Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/DenseFeature.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -20,7 +20,6 @@ import java.util.Map; import java.util.Arrays; import org.opengis.metadata.maintenance.ScopeCode; import org.opengis.metadata.quality.DataQuality; -import org.apache.sis.internal.feature.Resources; import org.apache.sis.internal.util.Cloner; import org.apache.sis.util.ArgumentChecks; @@ -72,7 +71,7 @@ final class DenseFeature extends Abstrac /** * Creates a new feature of the given type. * - * @param type Information about the feature (name, characteristics, <i>etc.</i>). + * @param type information about the feature (name, characteristics, <i>etc.</i>). */ public DenseFeature(final DefaultFeatureType type) { super(type); @@ -93,7 +92,7 @@ final class DenseFeature extends Abstrac if (index != null) { return index; } - throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name)); + throw new PropertyNotFoundException(propertyNotFound(type, getName(), name)); } /** @@ -196,7 +195,7 @@ final class DenseFeature extends Abstrac } else if (element instanceof FeatureAssociation) { return getAssociationValue((FeatureAssociation) element); } else { - throw unsupportedPropertyType(((Property) element).getName()); + throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName())); } } } Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -18,7 +18,10 @@ package org.apache.sis.feature; import java.util.List; import java.util.ArrayList; +import java.util.Set; +import java.util.EnumSet; import java.util.Iterator; +import java.util.Collection; import java.util.Locale; import java.util.TimeZone; import java.io.IOException; @@ -33,12 +36,14 @@ import org.opengis.util.GenericName; import org.apache.sis.io.TableAppender; import org.apache.sis.io.TabularFormat; import org.apache.sis.util.Deprecable; +import org.apache.sis.util.Characters; import org.apache.sis.util.CharSequences; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.resources.Vocabulary; import org.apache.sis.internal.util.CollectionsExt; import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.math.MathFunctions; // Branch-dependent imports import java.io.UncheckedIOException; @@ -49,9 +54,9 @@ import org.opengis.feature.Attribute; import org.opengis.feature.AttributeType; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; +import org.opengis.feature.FeatureAssociation; import org.opengis.feature.FeatureAssociationRole; import org.opengis.feature.Operation; -import org.apache.sis.util.Characters; /** @@ -88,7 +93,7 @@ public class FeatureFormat extends Tabul /** * For cross-version compatibility. */ - private static final long serialVersionUID = 8866440357566645070L; + private static final long serialVersionUID = -5792086817264884947L; /** * An instance created when first needed and potentially shared. @@ -101,6 +106,12 @@ public class FeatureFormat extends Tabul private final Locale displayLocale; /** + * The columns to include in the table formatted by this {@code FeatureFormat}. + * By default, all columns having at least one value are included. + */ + private final EnumSet<Column> columns = EnumSet.allOf(Column.class); + + /** * Maximal length of attribute values, in number of characters. * If a value is longer than this length, it will be truncated. * @@ -159,6 +170,104 @@ public class FeatureFormat extends Tabul } /** + * Returns all columns that may be shown in the tables to format. + * The columns included in the set may be shown, but not necessarily; + * some columns will still be omitted if they are completely empty. + * However columns <em>not</em> included in the set are guaranteed to be omitted. + * + * @return all columns that may be shown in the tables to format. + * + * @since 0.8 + */ + public Set<Column> getAllowedColumns() { + return columns.clone(); + } + + /** + * Sets all columns that may be shown in the tables to format. + * Note that the columns specified to this method are not guaranteed to be shown; + * some columns will still be omitted if they are completely empty. + * + * @param inclusion all columns that may be shown in the tables to format. + * + * @since 0.8 + */ + public void setAllowedColumns(final Set<Column> inclusion) { + ArgumentChecks.ensureNonNull("inclusion", inclusion); + columns.clear(); + columns.addAll(inclusion); + } + + /** + * Identifies the columns to include in the table formatted by {@code FeatureFormat}. + * By default, all columns having at least one non-null value are shown. But a smaller + * set of columns can be specified to the {@link FeatureFormat#setAllowedColumns(Set)} + * method for formatting narrower tables. + * + * @see FeatureFormat#setAllowedColumns(Set) + * + * @since 0.8 + */ + public enum Column { + /** + * Natural language designator for the property. + * This is the character sequence returned by {@link PropertyType#getDesignation()}. + * This column is omitted if no property has a designation. + */ + DESIGNATION(Vocabulary.Keys.Designation), + + /** + * Name of the property. + * This is the character sequence returned by {@link PropertyType#getName()}. + */ + NAME(Vocabulary.Keys.Name), + + /** + * Type of property values. This is the type returned by {@link AttributeType#getValueClass()} or + * {@link FeatureAssociationRole#getValueType()}. + */ + TYPE(Vocabulary.Keys.Type), + + /** + * The minimum and maximum occurrences of attribute values. This is made from the numbers returned + * by {@link AttributeType#getMinimumOccurs()} and {@link AttributeType#getMaximumOccurs()}. + */ + CARDINALITY(Vocabulary.Keys.Cardinality), + + /** + * Property value (for properties) or default value (for property types). + * This is the value returned by {@link Attribute#getValue()}, {@link FeatureAssociation#getValue()} + * or {@link AttributeType#getDefaultValue()}. + */ + VALUE(Vocabulary.Keys.Value), + + /** + * Other attributes that describes the attribute. + * This is made from the map returned by {@link Attribute#characteristics()}. + * This column is omitted if no property has characteristics. + */ + CHARACTERISTICS(Vocabulary.Keys.Characteristics), + + /** + * Whether a property is deprecated, or other remarks. + * This column is omitted if no property has remarks. + */ + REMARKS(Vocabulary.Keys.Remarks); + + /** + * The {@link Vocabulary} key to use for formatting the header of this column. + */ + final short resourceKey; + + /** + * Creates a new column enumeration constant. + */ + private Column(final short key) { + resourceKey = key; + } + } + + /** * Invoked when the formatter needs to move to the next column. */ private void nextColumn(final TableAppender table) { @@ -197,18 +306,30 @@ public class FeatureFormat extends Tabul .getString(Errors.Keys.UnsupportedType_1, object.getClass())); } /* - * Check if at least one attribute has at least one characteritic. In many cases there is none. - * In none we will ommit the "characteristics" column, which is the last column. + * Computes the columns to show. We start with the set of columns specified by setAllowedColumns(Set), + * then we check if some of those columns are empty. For example in many cases there is no attribute + * with characteritic, in which case we will ommit the whole "characteristics" column. We perform such + * check only for optional information, not for mandatory information like property names. */ - boolean hasCharacteristics = false; - boolean hasDeprecatedTypes = false; - for (final PropertyType propertyType : featureType.getProperties(true)) { - if (!hasCharacteristics && propertyType instanceof AttributeType<?>) { - hasCharacteristics = !((AttributeType<?>) propertyType).characteristics().isEmpty(); - } - if (!hasDeprecatedTypes && propertyType instanceof Deprecable) { - hasDeprecatedTypes = ((Deprecable) propertyType).isDeprecated(); + final EnumSet<Column> visibleColumns = columns.clone(); + { + boolean hasDesignation = false; + boolean hasCharacteristics = false; + boolean hasDeprecatedTypes = false; + for (final PropertyType propertyType : featureType.getProperties(true)) { + if (!hasDesignation) { + hasDesignation = propertyType.getDesignation() != null; + } + if (!hasCharacteristics && propertyType instanceof AttributeType<?>) { + hasCharacteristics = !((AttributeType<?>) propertyType).characteristics().isEmpty(); + } + if (!hasDeprecatedTypes && propertyType instanceof Deprecable) { + hasDeprecatedTypes = ((Deprecable) propertyType).isDeprecated(); + } } + if (!hasDesignation) visibleColumns.remove(Column.DESIGNATION); + if (!hasCharacteristics) visibleColumns.remove(Column.CHARACTERISTICS); + if (!hasDeprecatedTypes) visibleColumns.remove(Column.REMARKS); } /* * Format the feature type name. In the case of feature type, format also the names of super-type @@ -218,43 +339,44 @@ public class FeatureFormat extends Tabul */ toAppendTo.append(toString(featureType.getName())); if (feature == null) { - String separator = " ⇾ "; // UML symbol for inheritance. + String separator = " ⇾ "; // UML symbol for inheritance. for (final FeatureType parent : featureType.getSuperTypes()) { toAppendTo.append(separator).append(toString(parent.getName())); separator = ", "; } } toAppendTo.append(getLineSeparator()); + /* + * Create a table and format the header. Columns will be shown in Column enumeration order. + */ final Vocabulary resources = Vocabulary.getResources(displayLocale); final TableAppender table = new TableAppender(toAppendTo, columnSeparator); table.setMultiLinesCells(true); table.nextLine('─'); -header: for (int i=0; ; i++) { - final short key; - switch (i) { - case 0: key = Vocabulary.Keys.Name; break; - case 1: nextColumn(table); key = Vocabulary.Keys.Type; break; - case 2: nextColumn(table); key = Vocabulary.Keys.Cardinality; break; - case 3: nextColumn(table); key = (feature != null) ? Vocabulary.Keys.Value : Vocabulary.Keys.DefaultValue; break; - case 4: if (!hasCharacteristics) continue; - nextColumn(table); key = Vocabulary.Keys.Characteristics; break; - case 5: if (!hasDeprecatedTypes) continue; - nextColumn(table); key = Vocabulary.Keys.Remarks; break; - default: break header; + boolean isFirstColumn = true; + for (final Column column : visibleColumns) { + short key = column.resourceKey; + if (key == Vocabulary.Keys.Value && feature == null) { + key = Vocabulary.Keys.DefaultValue; } + if (!isFirstColumn) nextColumn(table); table.append(resources.getString(key)); + isFirstColumn = false; } table.nextLine(); table.nextLine('─'); /* - * Done writing the header. Now write all property rows. - * Rows without value will be skipped only if optional. + * Done writing the header. Now write all property rows. For each row, the first part in the loop + * extracts all information needed without formatting anything yet. If we detect in that part that + * a row has no value, it will be skipped if and only if that row is optional (minimum occurrence + * of zero). */ final StringBuffer buffer = new StringBuffer(); final FieldPosition dummyFP = new FieldPosition(-1); final List<String> remarks = new ArrayList<>(); for (final PropertyType propertyType : featureType.getProperties(true)) { Object value = null; + int cardinality = -1; if (feature != null) { if (!(propertyType instanceof AttributeType<?>) && !(propertyType instanceof FeatureAssociationRole) && @@ -264,16 +386,21 @@ header: for (int i=0; ; i++) { } value = feature.getPropertyValue(propertyType.getName().toString()); if (value == null) { - if (propertyType instanceof AttributeType && - ((AttributeType) propertyType).getMinimumOccurs() == 0) + if (propertyType instanceof AttributeType<?> + && ((AttributeType<?>) propertyType).getMinimumOccurs() == 0) { - continue; // If no value, skip the full row. + continue; // If optional and no value, skip the full row. } - if (propertyType instanceof FeatureAssociationRole && - ((FeatureAssociationRole) propertyType).getMinimumOccurs() == 0) + if (propertyType instanceof FeatureAssociationRole + && ((FeatureAssociationRole) propertyType).getMinimumOccurs() == 0) { - continue; // If no value, skip the full row. + continue; // If optional and no value, skip the full row. } + cardinality = 0; + } else if (value instanceof Collection<?>) { + cardinality = ((Collection<?>) value).size(); + } else { + cardinality = 1; } } else if (propertyType instanceof AttributeType<?>) { value = ((AttributeType<?>) propertyType).getDefaultValue(); @@ -291,14 +418,6 @@ header: for (int i=0; ; i++) { value = CharSequences.trimWhitespaces(buffer).toString(); buffer.setLength(0); } - /* - * Column 0 - Name. - */ - table.append(toString(propertyType.getName())); - nextColumn(table); - /* - * Column 1 and 2 - Type and cardinality. - */ final String valueType; // The value to write in the type column. final Class<?> valueClass; // AttributeType.getValueClass() if applicable. final int minimumOccurs, maximumOccurs; // Negative values mean no cardinality. @@ -327,82 +446,134 @@ header: for (int i=0; ; i++) { minimumOccurs = -1; maximumOccurs = -1; } - table.append(valueType); - nextColumn(table); - if (maximumOccurs >= 0) { - final Format format = getFormat(Integer.class); - table.append('[').append(format.format(minimumOccurs, buffer, dummyFP)).append(" … "); - buffer.setLength(0); - if (maximumOccurs != Integer.MAX_VALUE) { - table.append(format.format(maximumOccurs, buffer, dummyFP)); - } else { - table.append('∞'); - } - buffer.setLength(0); - table.append(']'); - } - nextColumn(table); /* - * Column 3 - Value or default value. + * At this point we determined that the row should not be skipped + * and we got all information to format. */ - if (value != null) { - final Format format = getFormat(valueClass); // Null if valueClass is null. - final Iterator<?> it = CollectionsExt.toCollection(value).iterator(); - String separator = ""; - int length = 0; - while (it.hasNext()) { - value = it.next(); - if (value != null) { - if (format != null && valueClass.isInstance(value)) { - value = format.format(value, buffer, dummyFP); - } else if (value instanceof Feature && propertyType instanceof FeatureAssociationRole) { - final String p = DefaultAssociationRole.getTitleProperty((FeatureAssociationRole) propertyType); - if (p != null) { - value = ((Feature) value).getPropertyValue(p); - if (value == null) continue; + isFirstColumn = true; + for (final Column column : visibleColumns) { + if (!isFirstColumn) nextColumn(table); + isFirstColumn = false; + switch (column) { + case DESIGNATION: { + final InternationalString d = propertyType.getDesignation(); + if (d != null) table.append(d.toString(displayLocale)); + break; + } + case NAME: { + table.append(toString(propertyType.getName())); + break; + } + case TYPE: { + table.append(valueType); + break; + } + case CARDINALITY: { + table.setCellAlignment(TableAppender.ALIGN_RIGHT); + if (cardinality >= 0) { + table.append(getFormat(Integer.class).format(cardinality, buffer, dummyFP)); + buffer.setLength(0); + } + if (maximumOccurs >= 0) { + if (cardinality >= 0) { + table.append(' ') + .append((cardinality >= minimumOccurs && cardinality <= maximumOccurs) ? '∈' : '∉') + .append(' '); } + final Format format = getFormat(Integer.class); + table.append('[').append(format.format(minimumOccurs, buffer, dummyFP)).append(" … "); + buffer.setLength(0); + if (maximumOccurs != Integer.MAX_VALUE) { + table.append(format.format(maximumOccurs, buffer, dummyFP)); + } else { + table.append('∞'); + } + buffer.setLength(0); + table.append(']'); } - length = formatValue(value, table.append(separator), length); - buffer.setLength(0); - separator = ", "; - if (length < 0) break; // Value is too long, abandon remaining iterations. + break; } - } - } - /* - * Column 4 - Characteristics. - */ - if (hasCharacteristics) { - nextColumn(table); - if (propertyType instanceof AttributeType<?>) { - String separator = ""; - for (final AttributeType<?> attribute : ((AttributeType<?>) propertyType).characteristics().values()) { - table.append(separator).append(toString(attribute.getName())); - Object c = attribute.getDefaultValue(); - if (feature != null) { - final Property p = feature.getProperty(propertyType.getName().toString()); - if (p instanceof Attribute<?>) { // Should always be true, but we are paranoiac. - c = ((Attribute<?>) p).characteristics().get(attribute.getName().toString()); + case VALUE: { + table.setCellAlignment(TableAppender.ALIGN_LEFT); + final Format format = getFormat(valueClass); // Null if valueClass is null. + final Iterator<?> it = CollectionsExt.toCollection(value).iterator(); + String separator = ""; + int length = 0; + while (it.hasNext()) { + value = it.next(); + if (value != null) { + if (propertyType instanceof FeatureAssociationRole) { + final String p = DefaultAssociationRole.getTitleProperty((FeatureAssociationRole) propertyType); + if (p != null) { + value = ((Feature) value).getPropertyValue(p); + if (value == null) continue; + } + } else if (format != null && valueClass.isInstance(value)) { // Null safe because of getFormat(valueClass) contract. + /* + * Convert numbers, dates, angles, etc. to character sequences before to append them in the table. + * Note that DecimalFormat writes Not-a-Number as "NaN" in some locales and as "�" in other locales + * (U+FFFD - Unicode replacement character). The "�" seems to be used mostly for historical reasons; + * as of 2017 the Unicode Common Locale Data Repository (CLDR) seems to define "NaN" for all locales. + * We could configure DecimalFormatSymbols for using "NaN", but (for now) we rather substitute "�" by + * "NaN" here for avoiding to change the DecimalFormat configuration and for distinguishing the NaNs. + */ + final StringBuffer t = format.format(value, buffer, dummyFP); + if (value instanceof Number) { + final float f = ((Number) value).floatValue(); + if (Float.isNaN(f)) { + if ("�".contentEquals(t)) { + t.setLength(0); + t.append("NaN"); + } + final int n = MathFunctions.toNanOrdinal(f); + if (n > 0) buffer.append(" #").append(n); + } + } + value = t; + } + /* + * All values: the numbers, dates, angles, etc. formatted above, any other character sequences + * (e.g. InternationalString), or other kind of values - some of them handled in a special way. + */ + length = formatValue(value, table.append(separator), length); + buffer.setLength(0); + if (length < 0) break; // Value is too long, abandon remaining iterations. + separator = ", "; + length += 2; } } - if (c != null) { - formatValue(c, table.append(" = "), 0); + break; + } + case CHARACTERISTICS: { + if (propertyType instanceof AttributeType<?>) { + String separator = ""; + for (final AttributeType<?> attribute : ((AttributeType<?>) propertyType).characteristics().values()) { + table.append(separator).append(toString(attribute.getName())); + Object c = attribute.getDefaultValue(); + if (feature != null) { + final Property p = feature.getProperty(propertyType.getName().toString()); + if (p instanceof Attribute<?>) { // Should always be true, but we are paranoiac. + c = ((Attribute<?>) p).characteristics().get(attribute.getName().toString()); + } + } + if (c != null) { + formatValue(c, table.append(" = "), 0); + } + separator = ", "; + } } - separator = ", "; + break; } - } - } - /* - * Column 5 - Deprecation - */ - if (hasDeprecatedTypes) { - nextColumn(table); - if (org.apache.sis.feature.Field.isDeprecated(propertyType)) { - table.append(resources.getString(Vocabulary.Keys.Deprecated)); - final InternationalString r = ((Deprecable) propertyType).getRemarks(); - if (r != null) { - remarks.add(r.toString(displayLocale)); - appendSuperscript(remarks.size(), table); + case REMARKS: { + if (org.apache.sis.feature.Field.isDeprecated(propertyType)) { + table.append(resources.getString(Vocabulary.Keys.Deprecated)); + final InternationalString r = ((Deprecable) propertyType).getRemarks(); + if (r != null) { + remarks.add(r.toString(displayLocale)); + appendSuperscript(remarks.size(), table); + } + } + break; } } } Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/LinkOperation.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -24,6 +24,7 @@ import org.opengis.parameter.ParameterVa import org.opengis.parameter.ParameterDescriptorGroup; import org.apache.sis.internal.feature.FeatureUtilities; import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.resources.Errors; // Branch-dependent imports import org.opengis.feature.Feature; @@ -48,11 +49,6 @@ final class LinkOperation extends Abstra private static final long serialVersionUID = 765096861589501215L; /** - * The parameter descriptor for the "Link" operation, which does not take any parameter. - */ - private static final ParameterDescriptorGroup EMPTY_PARAMS = FeatureUtilities.parameters("Link"); - - /** * The type of the result. */ private final PropertyType result; @@ -70,10 +66,17 @@ final class LinkOperation extends Abstra * * @see FeatureOperations#link(Map, PropertyType) */ - LinkOperation(final Map<String,?> identification, final PropertyType referent) { + LinkOperation(final Map<String,?> identification, PropertyType referent) { super(identification); + if (referent instanceof LinkOperation) { + referent = ((LinkOperation) referent).result; + // Avoiding links to links may help performance and reduce the risk of circular references. + } result = referent; referentName = referent.getName().toString(); + if (referentName.equals(getName().toString())) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.CircularReference)); + } } /** @@ -81,7 +84,7 @@ final class LinkOperation extends Abstra */ @Override public ParameterDescriptorGroup getParameters() { - return EMPTY_PARAMS; + return FeatureUtilities.LINK_PARAMS; } /** Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/SparseFeature.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -22,7 +22,6 @@ import java.util.Objects; import java.util.ConcurrentModificationException; import org.opengis.metadata.maintenance.ScopeCode; import org.opengis.metadata.quality.DataQuality; -import org.apache.sis.internal.feature.Resources; import org.apache.sis.internal.util.Cloner; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.CorruptedObjectException; @@ -59,7 +58,7 @@ final class SparseFeature extends Abstra /** * A {@link #valuesKind} flag meaning that the {@link #properties} map contains raw values. */ - private static final byte VALUES = 0; // Must be zero, because we want it to be 'valuesKind' default value. + private static final byte VALUES = 0; // Must be zero, because we want it to be 'valuesKind' default value. /** * A {@link #valuesKind} flag meaning that the {@link #properties} map contains {@link Property} instances. @@ -128,7 +127,7 @@ final class SparseFeature extends Abstra if (index != null) { return index; } - throw new PropertyNotFoundException(Resources.format(Resources.Keys.PropertyNotFound_2, getName(), name)); + throw new PropertyNotFoundException(propertyNotFound(type, getName(), name)); } /** @@ -243,7 +242,7 @@ final class SparseFeature extends Abstra } else if (element instanceof FeatureAssociation) { return getAssociationValue((FeatureAssociation) element); } else if (valuesKind == PROPERTIES) { - throw unsupportedPropertyType(((Property) element).getName()); + throw new IllegalArgumentException(unsupportedPropertyType(((Property) element).getName())); } else { throw new CorruptedObjectException(getName()); } @@ -278,7 +277,7 @@ final class SparseFeature extends Abstra * a new value or a value of a different type, then we need to check the name and type validity. */ if (!canSkipVerification(previous, value)) { - Object toStore = previous; // This initial value will restore the previous value if the check fail. + Object toStore = previous; // This initial value will restore the previous value if the check fail. try { toStore = verifyPropertyValue(name, value); } finally { Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AssociationRoleBuilder.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -66,7 +66,7 @@ public final class AssociationRoleBuilde * @param owner the builder of the {@code FeatureType} for which to add this property. */ AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureType type, final GenericName typeName) { - super(owner, null); + super(owner); this.type = type; this.typeName = typeName; } @@ -77,7 +77,7 @@ public final class AssociationRoleBuilde * @param owner the builder of the {@code FeatureType} for which to add this property. */ AssociationRoleBuilder(final FeatureTypeBuilder owner, final FeatureAssociationRole template) { - super(owner, template); + super(owner); property = template; minimumOccurs = template.getMinimumOccurs(); maximumOccurs = template.getMaximumOccurs(); @@ -88,6 +88,7 @@ public final class AssociationRoleBuilde type = template.getValueType(); typeName = type.getName(); } + initialize(template); } /** @@ -130,32 +131,52 @@ public final class AssociationRoleBuilde } /** - * Sets the {@code FeatureAssociationRole} 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 FeatureAssociationRole} 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 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 AssociationRoleBuilder setName(final String localPart) { + public AssociationRoleBuilder setName(final CharSequence localPart) { super.setName(localPart); return this; } /** * Sets the {@code FeatureAssociationRole} 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. + * An association 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> * * @return {@code this} for allowing method calls chaining. */ @Override - public AssociationRoleBuilder setName(final String scope, final String localPart) { - super.setName(scope, localPart); + public AssociationRoleBuilder setName(final CharSequence... components) { + super.setName(components); return this; } Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/AttributeTypeBuilder.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -129,7 +129,7 @@ public final class AttributeTypeBuilder< * @param valueClass the class of attribute values. */ AttributeTypeBuilder(final FeatureTypeBuilder owner, final Class<V> valueClass) { - super(owner, null); + super(owner); this.valueClass = valueClass; characteristics = new ArrayList<>(); } @@ -140,7 +140,7 @@ public final class AttributeTypeBuilder< * @param owner the builder of the {@code FeatureType} for which to add the attribute. */ AttributeTypeBuilder(final FeatureTypeBuilder owner, final AttributeType<V> template) { - super(owner, template); + super(owner); property = template; minimumOccurs = template.getMinimumOccurs(); maximumOccurs = template.getMaximumOccurs(); @@ -151,6 +151,7 @@ public final class AttributeTypeBuilder< for (final AttributeType<?> c : tc.values()) { characteristics.add(new CharacteristicTypeBuilder<>(this, c)); } + initialize(template); } /** @@ -185,32 +186,52 @@ public final class AttributeTypeBuilder< } /** - * Sets the {@code AttributeType} 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 AttributeType} 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 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 AttributeTypeBuilder<V> setName(final String localPart) { + public AttributeTypeBuilder<V> setName(final CharSequence localPart) { super.setName(localPart); return this; } /** * Sets the {@code AttributeType} 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. + * An attribute 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> * * @return {@code this} for allowing method calls chaining. */ @Override - public AttributeTypeBuilder<V> setName(final String scope, final String localPart) { - super.setName(scope, localPart); + public AttributeTypeBuilder<V> setName(final CharSequence... components) { + super.setName(components); return this; } Modified: sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java [UTF-8] (original) +++ sis/branches/JDK9/core/sis-feature/src/main/java/org/apache/sis/feature/builder/CharacteristicTypeBuilder.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -95,7 +95,7 @@ public final class CharacteristicTypeBui * @param valueClass the class of characteristic values. */ CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final Class<V> valueClass) { - super(null, owner.getLocale()); + super(owner.getLocale()); this.owner = owner; this.valueClass = valueClass; } @@ -106,11 +106,12 @@ public final class CharacteristicTypeBui * @param owner the builder of the {@code AttributeType} for which to add this property. */ CharacteristicTypeBuilder(final AttributeTypeBuilder<?> owner, final AttributeType<V> template) { - super(template, owner.getLocale()); + super(owner.getLocale()); this.owner = owner; characteristic = template; valueClass = template.getValueClass(); defaultValue = template.getDefaultValue(); + initialize(template); } /** @@ -146,42 +147,66 @@ public final class CharacteristicTypeBui } /** - * Sets the characteristic 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 characteristic 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 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 CharacteristicTypeBuilder<V> setName(final String localPart) { + public CharacteristicTypeBuilder<V> setName(final CharSequence localPart) { super.setName(localPart); return this; } /** * Sets the characteristic 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 getting values from the {@linkplain org.apache.sis.feature.AbstractAttribute#characteristics() + * characteristics} map. 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}. + * + * <p>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> + * + * <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 CharacteristicTypeBuilder<V> setName(final String scope, final String localPart) { - super.setName(scope, localPart); + public CharacteristicTypeBuilder<V> setName(final CharSequence... components) { + super.setName(components); return this; } /** - * 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 createLocalName(final CharSequence name) { + ensureAlive(owner); + return owner.createLocalName(name); + } + + /** + * Creates a generic name in the {@linkplain FeatureTypeBuilder#setNameSpace feature namespace}. */ @Override - final GenericName name(final String scope, final String localPart) { + final GenericName createGenericName(final CharSequence... names) { ensureAlive(owner); - return owner.name(scope, localPart); + return owner.createGenericName(names); } /**