| /* |
| * 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(); |
| } |
| } |