blob: 7312ab1bf25aab7ab95a14e3f989cb3a9324387f [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.feature;
import java.util.Map;
import java.util.Objects;
import java.util.Collections;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.apache.sis.util.Classes;
import org.apache.sis.internal.util.Numerics;
import static org.apache.sis.util.ArgumentChecks.*;
// Branch-dependent imports
import org.opengis.feature.Attribute;
import org.opengis.feature.AttributeType;
/**
* Definition of an attribute in a feature type.
* The name of attribute type is mandatory. The name {@linkplain org.apache.sis.util.iso.AbstractName#scope() scope}
* is typically the name of the {@linkplain DefaultFeatureType feature type} containing this attribute, but this is
* not mandatory. The scope could also be defined by the ontology for example.
*
* <div class="note"><b>Note:</b>
* Compared to the Java language, {@code AttributeType} is equivalent to {@link java.lang.reflect.Field}
* while {@code FeatureType} is equivalent to {@link Class}.
* Attribute characterization (discussed below) is similar to {@link java.lang.annotation.Annotation}.
* </div>
*
* <h2>Value type</h2>
* Attributes can be used for both spatial and non-spatial properties.
* Some examples are:
*
* <table class="sis">
* <caption>Attribute value type examples</caption>
* <tr><th>Attribute name</th> <th>Value type</th></tr>
* <tr><td>Building shape</td> <td>{@link org.opengis.geometry.Geometry}</td></tr>
* <tr><td>Building owner</td> <td>{@link org.opengis.metadata.citation.ResponsibleParty}</td></tr>
* <tr><td>Horizontal accuracy</td> <td>{@link org.opengis.metadata.quality.PositionalAccuracy}</td></tr>
* </table>
*
* <h2>Attribute characterization</h2>
* An {@code Attribute} can be characterized by other attributes. For example an attribute that carries a measurement
* (e.g. air temperature) may have another attribute that holds the measurement accuracy (e.g. ±0.1°C).
* The accuracy value is often constant for all instances of that attribute
* (e.g. for all temperature measurements in the same dataset), but this is not mandatory.
*
* <div class="note"><b>Design note:</b>
* Such accuracy could be stored as an ordinary, independent, attribute (like an other column in a table),
* but storing accuracy as a {@linkplain #characteristics() characteristic} of the measurement attribute instead
* provides the following advantages:
*
* <ul>
* <li>The same characteristic name (e.g. “accuracy”) can be used for different attributes
* (e.g. “temperature”, “humidity”, <i>etc.</i>) since all characteristics are local to their attribute.</li>
* <li>A reference to an attribute gives also access to its characteristics. For example any method expecting
* an {@code Attribute} argument, when given a measurement, can also get its accuracy in same time.</li>
* <li>In the common case of a {@linkplain DefaultFeatureType#isSimple() simple feature} with characteristics
* that are constants, declaring them as attribute characteristics allows to specify the constants only once.</li>
* </ul>
* </div>
*
* Constant values of characteristics are given by their {@linkplain #getDefaultValue() default value}.
* It is still possible for any specific {@code Attribute} instance to specify their own value,
* but {@linkplain DefaultFeatureType#isSimple() simple feature} usually don't do that.
*
* <h2>Immutability and thread safety</h2>
* Instances of this class are immutable if all properties ({@link GenericName} and {@link InternationalString}
* instances) and all arguments (e.g. {@code defaultValue}) given to the constructor are also immutable.
* Such immutable instances can be shared by many objects and passed between threads without synchronization.
*
* <p>In particular, the {@link #getDefaultValue()} method does <strong>not</strong> clone the returned value.
* This means that the same {@code defaultValue} instance may be shared by many {@link AbstractAttribute} instances.
* Consequently the default value should be immutable for avoiding unexpected behavior.</p>
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
*
* @param <V> the type of attribute values.
*
* @see DefaultFeatureType
* @see AbstractAttribute
*
* @since 0.5
* @module
*/
public class DefaultAttributeType<V> extends FieldType implements AttributeType<V> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -817024213677735239L;
/**
* The class that describe the type of attribute values.
*
* @see #getValueClass()
*/
private final Class<V> valueClass;
/**
* The default value for the attribute, or {@code null} if none.
*
* @see #getDefaultValue()
*/
private final V defaultValue;
/**
* Other attribute types that describes this attribute type, or {@code null} if none.
* This is used for attributes of attribute (e.g. accuracy of a position).
*
* @see #characteristics()
*/
private transient CharacteristicTypeMap characteristics;
/**
* Constructs an attribute type from the given properties. The identification map is given unchanged to
* the {@linkplain AbstractIdentifiedType#AbstractIdentifiedType(Map) super-class constructor}.
* The following table is a reminder of main (not all) recognized map entries:
*
* <table class="sis">
* <caption>Recognized map entries (non exhaustive list)</caption>
* <tr>
* <th>Map key</th>
* <th>Value type</th>
* <th>Returned by</th>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#NAME_KEY}</td>
* <td>{@link GenericName} or {@link String}</td>
* <td>{@link #getName()}</td>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DEFINITION_KEY}</td>
* <td>{@link InternationalString} or {@link String}</td>
* <td>{@link #getDefinition()}</td>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DESIGNATION_KEY}</td>
* <td>{@link InternationalString} or {@link String}</td>
* <td>{@link #getDesignation()}</td>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DESCRIPTION_KEY}</td>
* <td>{@link InternationalString} or {@link String}</td>
* <td>{@link #getDescription()}</td>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DEPRECATED_KEY}</td>
* <td>{@link Boolean}</td>
* <td>{@link #isDeprecated()}</td>
* </tr>
* </table>
*
* @param identification the name and other information to be given to this attribute type.
* @param valueClass the type of attribute values.
* @param minimumOccurs the minimum number of occurrences of the attribute within its containing entity.
* @param maximumOccurs the maximum number of occurrences of the attribute within its containing entity,
* or {@link Integer#MAX_VALUE} if there is no restriction.
* @param defaultValue the default value for the attribute, or {@code null} if none.
* @param characterizedBy other attribute types that describes this attribute type (can be {@code null} for none).
* For example if this new {@code DefaultAttributeType} describes a measurement,
* then {@code characterizedBy} could holds the measurement accuracy.
* See <cite>"Attribute characterization"</cite> in class Javadoc for more information.
*
* @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)
{
super(identification, minimumOccurs, maximumOccurs);
ensureNonNull("valueClass", valueClass);
ensureCanCast("defaultValue", valueClass, defaultValue);
this.valueClass = valueClass;
this.defaultValue = Numerics.cached(defaultValue);
if (characterizedBy != null && characterizedBy.length != 0) {
characteristics = CharacteristicTypeMap.create(this, characterizedBy.clone());
}
}
/**
* Invoked on serialization for saving the {@link #characteristics} field.
*
* @param out The output stream where to serialize this attribute type.
* @throws IOException if an I/O error occurred while writing.
*/
private void writeObject(final ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(characteristics != null ? characteristics.characterizedBy : null);
}
/**
* Invoked on deserialization for restoring the {@link #characteristics} field.
*
* @param in the input stream from which to deserialize an attribute type.
* @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
* @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
*/
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
try {
final AttributeType<?>[] characterizedBy = (AttributeType<?>[]) in.readObject();
if (characterizedBy != null) {
characteristics = CharacteristicTypeMap.create(this, characterizedBy);
}
} catch (RuntimeException e) {
// At least ClassCastException, NullPointerException and IllegalArgumentException.
throw (IOException) new InvalidObjectException(e.getLocalizedMessage()).initCause(e);
}
}
/**
* Returns the type of attribute values.
*
* @return the type of attribute values.
*
* @see Features#getValueClass(PropertyType)
*/
@Override
public final Class<V> getValueClass() {
return valueClass;
}
/*
* ISO 19109 properties omitted for now:
*
* - valueDomain : CharacterString
*
* Rational: a CharacterString is hardly programmatically usable. A Range would be better but too specific.
* We could follow the GeoAPI path and define a "restrictions : Filter" property. That would be more generic,
* but we are probably better to wait for Filter to be implemented in SIS.
*
* Reference: https://issues.apache.org/jira/browse/SIS-175
*/
/**
* Returns the minimum number of attribute values.
* The returned value is greater than or equal to zero.
*
* <p>To be valid, an {@code Attribute} instance of this {@code AttributeType} shall have at least
* this minimum number of elements in its {@link AbstractAttribute#getValues() collection of values}.</p>
*
* @return the minimum number of attribute values.
*/
@Override
public final int getMinimumOccurs() {
return super.getMinimumOccurs();
}
/**
* Returns the maximum number of attribute values.
* The returned value is greater than or equal to the {@link #getMinimumOccurs()} value.
* If there is no maximum, then this method returns {@link Integer#MAX_VALUE}.
*
* <p>To be valid, an {@code Attribute} instance of this {@code AttributeType} shall have no more than
* this maximum number of elements in its {@link AbstractAttribute#getValues() collection of values}.</p>
*
* @return the maximum number of attribute values, or {@link Integer#MAX_VALUE} if none.
*/
@Override
public final int getMaximumOccurs() {
return super.getMaximumOccurs();
}
/**
* Returns the default value for the attribute.
* This value is used when an attribute is created and no value for it is specified.
*
* @return the default value for the attribute, or {@code null} if none.
*/
@Override
public V getDefaultValue() {
return defaultValue;
}
/**
* Other attribute types that describes this attribute type.
* See <cite>"Attribute characterization"</cite> in class Javadoc for more information.
*
* <div class="note"><b>Example:</b>
* An attribute that carries a measurement (e.g. air temperature) may have another attribute that holds the
* measurement accuracy. The accuracy is often constant for all measurements in a dataset, but not necessarily.
* If the accuracy is a constant, then the characteristics {@linkplain #getDefaultValue() default value}
* shall hold that constant.
* </div>
*
* The characteristics are enumerated in the {@linkplain Map#values() map values}.
* The {@linkplain Map#keySet() map keys} are the {@code String} representations
* of characteristics {@linkplain #getName() name}, for more convenient lookups.
*
* @return other attribute types that describes this attribute type, or an empty map if none.
*
* @see AbstractAttribute#characteristics()
*/
@Override
public Map<String,AttributeType<?>> characteristics() {
return (characteristics != null) ? characteristics : Collections.emptyMap();
}
/**
* Creates a new attribute instance of this type initialized to the {@linkplain #getDefaultValue() default value}.
*
* @return a new attribute instance.
*
* @see AbstractAttribute#create(AttributeType)
*/
@Override
public Attribute<V> newInstance() {
return AbstractAttribute.create(this);
}
/**
* Returns a hash code value for this attribute type.
*
* @return {@inheritDoc}
*/
@Override
public int hashCode() {
return super.hashCode() + valueClass.hashCode() + Objects.hashCode(defaultValue)
+ Objects.hashCode(characteristics);
}
/**
* Compares this attribute type with the given object for equality.
*
* @return {@inheritDoc}
*/
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (super.equals(obj)) {
final DefaultAttributeType<?> that = (DefaultAttributeType<?>) obj;
return valueClass == that.valueClass &&
Objects.equals(defaultValue, that.defaultValue) &&
Objects.equals(characteristics, that.characteristics);
}
return false;
}
/**
* Returns a string representation of this attribute type.
* The returned string is for debugging purpose and may change in any future SIS version.
*
* @return a string representation of this attribute type for debugging purpose.
*/
@Override
public String toString() {
return toString(deprecated, "AttributeType", getName(), Classes.getShortName(valueClass)).toString();
}
}