blob: ed5f5c4583466f9ca3d174903301d3b43ceb589a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.internal.feature;
import org.opengis.util.LocalName;
import org.opengis.util.ScopedName;
import org.opengis.util.GenericName;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.util.iso.Names;
import org.apache.sis.util.Static;
// Branch-dependent imports
import org.apache.sis.feature.AbstractFeature;
import org.apache.sis.feature.AbstractAttribute;
import org.apache.sis.feature.AbstractIdentifiedType;
import org.apache.sis.feature.AbstractOperation;
import org.apache.sis.feature.DefaultAttributeType;
import org.apache.sis.feature.DefaultFeatureType;
/**
* Defines the names of some properties or characteristics for which we assign a conventional usage.
* Properties with the names defined in this {@code AttributeConvention} class are often aliases generated
* by the SIS implementation of various file readers. Those synthetic properties redirect to the most
* appropriate "real" property in the feature.
*
* <div class="note"><b>Example:</b>
* one of the most frequently used synthetic property is {@code "sis:identifier"}, which contains a unique
* identifier (or primary key) for the feature. This property is usually (but not necessarily)
* a {@linkplain org.apache.sis.feature.FeatureOperations#link link to an existing attribute}.
* By using the {@code "sis:identifier"} alias, users do not need to know the name of the "real" attribute.
* </div>
*
* This class defines names for two kinds of usage:
* <ul>
* <li>Names ending with {@code "_PROPERTY"} are used for attributes or operations that are members of the
* collection returned by {@link org.apache.sis.feature.DefaultFeatureType#getProperties(boolean)}.</li>
* <li>Names ending with {@code "_CHARACTERISTIC"} are used for characteristics that are entries of the
* map returned by {@link org.apache.sis.feature.DefaultAttributeType#characteristics()}.</li>
* </ul>
*
* <div class="section">Mixing with other conventions</div>
* The conventions defined in this class are specific to Apache SIS.
* Current implementation does not support any other convention than the SIS one,
* but we may refactor this class in future SIS versions if there is a need to support different conventions.
*
* <p>In order to reduce the risk of name collision with properties in user-defined features
* (e.g. the user may already have an attribute named {@code "identifier"} for his own purpose),
* all names defined in this class begin with the {@code "@"} character.</p>
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 0.7
* @module
*/
public final class AttributeConvention extends Static {
/**
* Scope of all names defined by SIS convention.
*/
private static final LocalName SCOPE;
/**
* Conventional name for a property used as a unique identifier.
* The identifier should be unique in the {@link org.apache.sis.storage.DataStore} instance containing the feature
* (for example a {@code DataStore} opened for a XML file), but does not need to be unique between two independent
* {@code DataStore} instances.
*
* <p>Properties of this name are usually
* {@linkplain org.apache.sis.feature.FeatureOperations#link aliases for existing attributes}, or
* {@linkplain org.apache.sis.feature.FeatureOperations#compound compound keys} made by concatenation
* of two or more other attributes.</p>
*
* <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} is usually
* {@link String}, {@link Integer}, {@link java.util.UUID} or other types commonly used as identifiers.</p>
*/
public static final ScopedName IDENTIFIER_PROPERTY;
/**
* Conventional name for a property containing the geometric object to use by default.
* Some features may contain more than one geometric object; this property tells which
* geometry to render on a map for example.
*
* <p>Properties of this name are usually {@linkplain org.apache.sis.feature.FeatureOperations#link
* operations acting as a redirection to another attribute}.</p>
*
* <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} can be
* the {@link com.esri.core.geometry.Geometry} class from ESRI's API, or the {@code Geometry} class from
* <cite>Java Topology Suite</cite> (JTS) library, or any other class defined in future SIS versions.
* See {@code isGeometryAttribute(IdentifiedType)} for testing whether the value is a supported type.</p>
*
* @see #isGeometryAttribute(AbstractIdentifiedType)
*/
public static final ScopedName GEOMETRY_PROPERTY;
/**
* Conventional name for fetching the envelope encompassing all geometries in a feature. Most {@code FeatureType}s
* have at most one geometry, which is also the {@link #GEOMETRY_PROPERTY default geometry}.
* But if several geometries exist, then the value for this synthetic property is the union of all geometries.
*
* <p>Properties of this name are usually
* {@linkplain org.apache.sis.feature.FeatureOperations#envelope operations}.</p>
*
* <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
* {@link org.opengis.geometry.Envelope}.</p>
*/
public static final ScopedName ENVELOPE_PROPERTY;
/**
* Conventional name for fetching the Coordinate Reference System (CRS) of a geometry or a coverage.
* This characteristic is typically an entry in the map returned by a call to the
* {@link org.apache.sis.feature.DefaultAttributeType#characteristics()} method
* on the attribute referenced by {@link #GEOMETRY_PROPERTY}.
*
* <p>While it is technically possible to have different CRS for different feature instances,
* in most cases the CRS is the same for all geometries found in {@code GEOMETRY_PROPERTY}.
* In such cases, the CRS can be specified only once as the
* {@linkplain org.apache.sis.feature.DefaultAttributeType#getDefaultValue() default value}
* of this {@code CRS_CHARACTERISTIC}.</p>
*
* <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
* {@link org.opengis.referencing.crs.CoordinateReferenceSystem}.</p>
*
* @see #getCRSCharacteristic(Object)
*/
public static final ScopedName CRS_CHARACTERISTIC;
/**
* Conventional name for fetching the maximal length of string values.
* The maximal length is stored as the
* {@linkplain org.apache.sis.feature.DefaultAttributeType#getDefaultValue() default value} of the
* {@linkplain org.apache.sis.feature.DefaultAttributeType#characteristics() characteristic} associated
* to the attribute on which the maximal length applies.
*
* <p>The {@linkplain org.apache.sis.feature.DefaultAttributeType#getValueClass() value class} should be
* {@link Integer}.</p>
*
* @see #getMaximalLengthCharacteristic(Object)
*/
public static final ScopedName MAXIMAL_LENGTH_CHARACTERISTIC;
/**
* Conventional name for fetching the enumeration of valid values.
* The set of valid values is stored stored as the
* {@linkplain org.apache.sis.feature.DefaultAttributeType#getDefaultValue() default value} of the
* {@linkplain org.apache.sis.feature.DefaultAttributeType#characteristics() characteristic} associated
* to the attribute on which the restriction applies.
*/
public static final GenericName VALID_VALUES_CHARACTERISTIC;
static {
SCOPE = Names.createLocalName("Apache", null, "sis");
IDENTIFIER_PROPERTY = Names.createScopedName(SCOPE, null, "identifier");
GEOMETRY_PROPERTY = Names.createScopedName(SCOPE, null, "geometry");
ENVELOPE_PROPERTY = Names.createScopedName(SCOPE, null, "envelope");
CRS_CHARACTERISTIC = Names.createScopedName(SCOPE, null, "crs");
MAXIMAL_LENGTH_CHARACTERISTIC = Names.createScopedName(SCOPE, null, "maximalLength");
VALID_VALUES_CHARACTERISTIC = Names.createScopedName(SCOPE, null, "validValues");
}
/**
* String representation of the {@link #IDENTIFIER_PROPERTY} name.
* This can be used in calls to {@link AbstractFeature#getPropertyValue(String)}.
*/
public static final String IDENTIFIER = "sis:identifier";
/**
* String representation of the {@link #GEOMETRY_PROPERTY} name.
* This can be used in calls to {@link AbstractFeature#getPropertyValue(String)}.
*/
public static final String GEOMETRY = "sis:geometry";
/**
* Do not allow instantiation of this class.
*/
private AttributeConvention() {
}
/**
* Returns {@code true} if the given name stands for one of the synthetic properties defined by convention.
* Conventional properties are properties added by the {@code DataStore} to the {@code FeatureType} in order
* to provide a uniform way to access commonly used information.
*
* <p>Synthetic properties should generally not be written by the user.
* Those properties are calculated most of the time and have only a meaning within SIS.</p>
*
* <p>Current implementation returns {@code true} if the given name is in the SIS namespace.</p>
*
* @param name the name of the property or characteristic to test, or {@code null}.
* @return {@code true} if the given name is non-null and in the SIS namespace.
*/
public static boolean contains(GenericName name) {
while (name instanceof ScopedName) {
if (SCOPE.equals(((ScopedName) name).path())) {
return true;
}
name = ((ScopedName) name).tail();
}
return false;
}
/**
* Returns {@code true} if the given feature type is non-null and has a {@value #IDENTIFIER} property.
*
* @param feature the feature type to test, or {@code null}.
* @return whether the given feature type is non-null and has a {@value #IDENTIFIER} property.
*/
public static boolean hasIdentifier(final DefaultFeatureType feature) {
if (feature != null) try {
return feature.getProperty(IDENTIFIER) != null;
} catch (IllegalArgumentException e) {
// Ignore
}
return false;
}
/**
* Returns {@code true} if the given type is an {@code AttributeType} or an {@code Operation} computing
* an attribute, and the attribute value is one of the geometry types recognized by SIS.
* The types currently recognized by SIS are:
*
* <ul>
* <li>{@link com.esri.core.geometry.Geometry} of the ESRI's API.</li>
* </ul>
*
* The above list may be expanded in any future SIS version.
*
* @param type the type to test, or {@code null}.
* @return {@code true} if the given type is (directly or indirectly) an attribute type
* for one of the recognized geometry types.
*
* @see #GEOMETRY_PROPERTY
*/
public static boolean isGeometryAttribute(AbstractIdentifiedType type) {
while (type instanceof AbstractOperation) {
type = ((AbstractOperation) type).getResult();
}
return (type instanceof DefaultAttributeType<?>) && Geometries.isKnownType(((DefaultAttributeType<?>) type).getValueClass());
}
/**
* Returns whether the given operation or attribute type is characterized by a coordinate reference system.
* This method verifies whether a characteristic named {@link #CRS_CHARACTERISTIC} with values assignable to
* {@link CoordinateReferenceSystem} exists (directly or indirectly) for the given type.
*
* @param type the operation or attribute type for which to get the CRS, or {@code null}.
* @return {@code true} if a characteristic for Coordinate Reference System has been found.
*/
public static boolean characterizedByCRS(final AbstractIdentifiedType type) {
return hasCharacteristic(type, CRS_CHARACTERISTIC.toString(), CoordinateReferenceSystem.class);
}
/**
* Returns the Coordinate Reference Systems characteristic for the given attribute, or {@code null} if none.
* This method gets the value or default value from the characteristic named {@link #CRS_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.
* @throws ClassCastException if {@link #CRS_CHARACTERISTIC} has been found but is associated
* to an object which is not a {@link CoordinateReferenceSystem} instance.
*
* @see org.apache.sis.feature.builder.AttributeTypeBuilder#setCRS(CoordinateReferenceSystem)
*/
public static CoordinateReferenceSystem getCRSCharacteristic(final Object attribute) {
return (CoordinateReferenceSystem) getCharacteristic(attribute, CRS_CHARACTERISTIC.toString());
}
/**
* 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, {@code 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 DefaultFeatureType feature, final AbstractIdentifiedType 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.
*
* @param type the operation or attribute type for which to get the maximal length, or {@code null}.
* @return {@code true} if a characteristic for maximal length has been found.
*/
public static boolean characterizedByMaximalLength(final AbstractIdentifiedType type) {
return hasCharacteristic(type, MAXIMAL_LENGTH_CHARACTERISTIC.toString(), Integer.class);
}
/**
* 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 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.
*
* @see org.apache.sis.feature.builder.AttributeTypeBuilder#setMaximalLength(Integer)
*/
public static Integer getMaximalLengthCharacteristic(final Object attribute) {
return (Integer) getCharacteristic(attribute, MAXIMAL_LENGTH_CHARACTERISTIC.toString());
}
/**
* 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, {@code 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 DefaultFeatureType feature, final AbstractIdentifiedType 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}.
*
* @param type the operation or attribute type for which to test the existence of a characteristic.
* @param name the name of the characteristic to test.
* @param valueClass the expected characteristic values.
* @return {@code true} if a characteristic of the given name exists and has values assignable to the given class.
*/
private static boolean hasCharacteristic(AbstractIdentifiedType type, final String name, final Class<?> valueClass) {
while (type instanceof AbstractOperation) {
type = ((AbstractOperation) type).getResult();
}
if (type instanceof DefaultAttributeType<?>) {
final DefaultAttributeType<?> at = ((DefaultAttributeType<?>) type).characteristics().get(name);
if (at != null) {
return valueClass.isAssignableFrom(at.getValueClass());
}
}
return false;
}
/**
* Fetches from the given property the value or default value of the named characteristic.
* If the given property is null, or is not an attribute, or does not have characteristics
* of the given name, then this method returns {@code null}.
*
* @param attribute the attribute from which to get the characteristic value or default value, or {@code null}.
* @param name name of the characteristic to get.
* @return the value or default value of the given characteristic in the given property, or {@code null} if none.
*/
private static Object getCharacteristic(final Object attribute, final String name) {
if (attribute instanceof AbstractAttribute<?>) {
final AbstractAttribute<?> at = ((AbstractAttribute<?>) attribute).characteristics().get(name);
if (at != null) {
final Object value = at.getValue();
if (value != null) {
return value;
}
}
final DefaultAttributeType<?> type = ((AbstractAttribute<?>) attribute).getType().characteristics().get(name);
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 DefaultFeatureType feature, AbstractIdentifiedType property, final String characteristic) {
final String referent = FeatureUtilities.linkOf(property);
if (referent != null && feature != null) {
property = feature.getProperty(referent);
}
if (property instanceof DefaultAttributeType<?>) {
final DefaultAttributeType<?> type = ((DefaultAttributeType<?>) property).characteristics().get(characteristic);
if (type != null) {
return type.getDefaultValue();
}
}
return null;
}
}