org.geotoolkit.referencing.AbstractIdentifiedObject Maven / Gradle / Ivy
/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2001-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotoolkit.referencing;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Level;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.opengis.util.LocalName;
import org.opengis.util.ScopedName;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.ObjectFactory;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.parameter.InvalidParameterValueException;
import org.geotoolkit.util.Utilities;
import org.geotoolkit.util.Deprecable;
import org.geotoolkit.util.ComparisonMode;
import org.geotoolkit.util.LenientComparable;
import org.geotoolkit.util.DefaultInternationalString;
import org.geotoolkit.util.logging.Logging;
import org.geotoolkit.util.converter.Classes;
import org.geotoolkit.internal.Citations;
import org.geotoolkit.internal.jaxb.gco.StringConverter;
import org.geotoolkit.internal.jaxb.referencing.RS_Identifier;
import org.geotoolkit.io.wkt.FormattableObject;
import org.geotoolkit.resources.Loggings;
import org.geotoolkit.resources.Errors;
import net.jcip.annotations.ThreadSafe;
import net.jcip.annotations.Immutable;
import org.geotoolkit.xml.Namespaces;
import static org.geotoolkit.util.Utilities.deepEquals;
import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;
import static org.geotoolkit.internal.InternalUtilities.nonEmptySet;
/**
* A base class for metadata applicable to reference system objects. When {@link AuthorityFactory}
* is used to create an object, the {@linkplain ReferenceIdentifier#getAuthority authority} and
* {@linkplain ReferenceIdentifier#getCode authority code} values are set to the authority
* name of the factory object, and the authority code supplied by the client, respectively. When
* {@link ObjectFactory} creates an object, the {@linkplain #getName() name} is set to the value
* supplied by the client and all of the other metadata items are left empty.
*
* This class is conceptually abstract, even if it is technically possible to
* instantiate it. Typical applications should create instances of the most specific subclass with
* {@code Default} prefix instead. An exception to this rule may occurs when it is not possible to
* identify the exact type. For example it is not possible to infer the exact coordinate system from
* Well
* Known Text is some cases (e.g. in a {@code LOCAL_CS} element). In such exceptional
* situation, a plain {@link org.geotoolkit.referencing.cs.AbstractCS} object may be instantiated.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.20
*
* @since 1.2
* @module
*/
@Immutable
@ThreadSafe
@XmlType(name="IdentifiedObjectType", propOrder={
"identifier",
"name"
})
public class AbstractIdentifiedObject extends FormattableObject implements IdentifiedObject,
LenientComparable, Deprecable, Serializable
{
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -5173281694258483264L;
/**
* The name for this object or code. Should never be {@code null}.
*/
@XmlElement
@XmlJavaTypeAdapter(RS_Identifier.ToString.class)
private final ReferenceIdentifier name;
/**
* An alternative name by which this object is identified.
*/
private final Collection alias;
/**
* An identifier which references elsewhere the object's defining information.
* Alternatively an identifier by which this object can be referenced.
*/
private final Set identifiers;
/**
* Comments on or information about this object, or {@code null} if none.
*/
private final InternationalString remarks;
/**
* The cached hash code value, or 0 if not yet computed. This field is calculated only when
* first needed. We do not declare it {@code volatile} because it is not a big deal if this
* field is calculated many time, and the same value should be produced by all computations.
* The only possible outdated value is 0, which is okay.
*
* @since 3.18
*/
private transient int hashCode;
/**
* Constructs a new object in which every attributes are set to a default value.
* This is not a valid object. This constructor is strictly
* reserved to JAXB, which will assign values to the fields using reflexion.
*/
private AbstractIdentifiedObject() {
this(org.geotoolkit.internal.referencing.NilReferencingObject.INSTANCE);
}
/**
* Constructs a new identified object with the same values than the specified one.
* This copy constructor provides a way to convert an arbitrary implementation into a
* Geotk one or a user-defined one (as a subclass), usually in order to leverage
* some implementation-specific API. This constructor performs a shallow copy,
* i.e. the properties are not cloned.
*
* @param object The object to copy.
*/
public AbstractIdentifiedObject(final IdentifiedObject object) {
ensureNonNull("object", object);
Collection as;
Set id;
name = object.getName();
as = object.getAlias();
id = object.getIdentifiers();
remarks = object.getRemarks();
if (as != null && as.isEmpty()) as = null;
if (id != null && id.isEmpty()) id = null;
alias = as;
identifiers = id;
}
/**
* Constructs an object from a set of properties. Keys are strings from the table below.
* Key are case-insensitive, and leading and trailing spaces are ignored. The map given in
* argument shall contains at least a {@code "name"} property. Other properties listed
* in the table below are optional.
*
*
*
* Property name
* Value type
* Value given to
*
*
* {@value org.opengis.referencing.IdentifiedObject#NAME_KEY}
* {@link String} or {@link ReferenceIdentifier}
* {@link #getName()}
*
*
* {@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}
* {@link CharSequence}, {@link GenericName} or an array of those
* {@link #getAlias()}
*
*
* {@value org.opengis.metadata.Identifier#AUTHORITY_KEY}
* {@link String} or {@link Citation}
* {@link ReferenceIdentifier#getAuthority()} on the {@linkplain #getName() name}
*
*
* {@value org.opengis.referencing.ReferenceIdentifier#CODESPACE_KEY}
* {@link String}
* {@link ReferenceIdentifier#getCodeSpace()} on the {@linkplain #getName() name}
*
*
* {@value org.opengis.referencing.ReferenceIdentifier#VERSION_KEY}
* {@link String}
* {@link ReferenceIdentifier#getVersion()} on the {@linkplain #getName() name}
*
*
* {@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}
* {@link ReferenceIdentifier} or {@linkplain ReferenceIdentifier}[]
* {@link #getIdentifiers()}
*
*
* {@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}
* {@link String} or {@link InternationalString}
* {@link #getRemarks()}
*
*
*
* Additionally, all localizable attributes like {@code "remarks"} may have a language and
* country code suffix. For example the {@code "remarks_fr"} property stands for remarks in
* {@linkplain java.util.Locale#FRENCH French} and the {@code "remarks_fr_CA"} property stands
* for remarks in {@linkplain java.util.Locale#CANADA_FRENCH French Canadian}.
*
* Note that the {@code "authority"} and {@code "version"} properties are ignored if the
* {@code "name"} property is already a {@link Citation} object instead of a {@link String}.
*
* @param properties The properties to be given to this identified object.
* @throws IllegalArgumentException if a property has an invalid value.
*/
public AbstractIdentifiedObject(final Map properties) throws IllegalArgumentException {
this(properties, null, null);
}
/**
* Constructs an object from a set of properties and copy unrecognized properties in the
* specified map. The {@code properties} argument is treated as in the {@linkplain
* AbstractIdentifiedObject#AbstractIdentifiedObject(Map) one argument constructor}. All
* properties unknown to this {@code AbstractIdentifiedObject} constructor are copied
* in the {@code subProperties} map, after their key has been normalized (usually
* lower case, leading and trailing space removed).
*
* If {@code localizables} is non-null, then all keys listed in this argument are
* treated as localizable one (i.e. may have a suffix like "_fr", "_de", etc.). Localizable
* properties are stored in the {@code subProperties} map as {@link InternationalString}
* objects.
*
* @param properties Set of properties. Should contains at least {@code "name"}.
* @param subProperties The map in which to copy unrecognized properties.
* @param localizables Optional list of localized properties.
* @throws IllegalArgumentException if a property has an invalid value.
*/
protected AbstractIdentifiedObject(final Map properties,
final Map subProperties,
final String[] localizables)
throws IllegalArgumentException
{
ensureNonNull("properties", properties);
Object name = null;
Object alias = null;
Object identifiers = null;
Object remarks = null;
DefaultInternationalString i18n = null;
DefaultInternationalString[] sub_i18n = null;
/*
* Iterate through each map entry. This have two purposes:
*
* 1) Ignore case (a call to properties.get("foo") can't do that)
* 2) Find localized remarks.
*
* This algorithm is sub-optimal if the map contains a lot of entries of no interest to
* this object. Hopefully, most users will fill a map only with useful entries.
*/
nextKey:for (final Map.Entry entry : properties.entrySet()) {
String key = entry.getKey().trim().toLowerCase();
Object value = entry.getValue();
/*
* Note: String.hashCode() is part of J2SE specification,
* so it should not change across implementations.
*/
switch (key.hashCode()) {
// Fix case for common keywords. They are not used
// by this class, but are used by some subclasses.
case -1528693765: if (key.equalsIgnoreCase("anchorPoint")) key="anchorPoint"; break;
case -1805658881: if (key.equalsIgnoreCase("bursaWolf")) key="bursaWolf"; break;
case 109688209: if (key.equalsIgnoreCase("operationVersion")) key="operationVersion"; break;
case 1479434472: if (key.equalsIgnoreCase("coordinateOperationAccuracy")) key="coordinateOperationAccuracy"; break;
case 1126917133: if (key.equalsIgnoreCase("positionalAccuracy")) key="positionalAccuracy"; break;
case 1127093059: if (key.equalsIgnoreCase("realizationEpoch")) key="realizationEpoch"; break;
case 1790520781: if (key.equalsIgnoreCase("domainOfValidity")) key="domainOfValidity"; break;
case -1109785975: if (key.equalsIgnoreCase("validArea")) key="validArea"; break;
// -------------------------------------
// "name": String or ReferenceIdentifier
// -------------------------------------
case 3373707: {
if (key.equals(NAME_KEY)) {
if (value instanceof String) {
name = new NamedIdentifier(properties, false);
assert value.equals(((Identifier) name).getCode()) : name;
} else {
// Should be an instance of ReferenceIdentifier, but we don't check
// here. The type will be checked at the end of this method, which
// will thrown an exception with detailed message in case of mismatch.
name = value;
}
continue nextKey;
}
break;
}
// -------------------------------------------------------------------
// "alias": CharSequence, CharSequence[], GenericName or GenericName[]
// -------------------------------------------------------------------
case 92902992: {
if (key.equals(ALIAS_KEY)) {
alias = NamedIdentifier.getNameFactory().toArray(value);
continue nextKey;
}
break;
}
// -----------------------------------------------------------
// "identifiers": ReferenceIdentifier or ReferenceIdentifier[]
// -----------------------------------------------------------
case 1368189162: {
if (key.equals(IDENTIFIERS_KEY)) {
if (value != null) {
if (value instanceof ReferenceIdentifier) {
identifiers = new ReferenceIdentifier[] {
(ReferenceIdentifier) value
};
} else {
identifiers = value;
}
}
continue nextKey;
}
break;
}
// ----------------------------------------
// "remarks": String or InternationalString
// ----------------------------------------
case 1091415283: {
if (key.equals(REMARKS_KEY)) {
if (value instanceof InternationalString) {
remarks = value;
continue nextKey;
}
}
break;
}
}
/*
* Search for additional locales for remarks (e.g. "remarks_fr").
* 'growable.add(...)' will add the value only if the key starts
* with the "remarks" prefix.
*/
if (value instanceof String) {
if (i18n == null) {
if (remarks instanceof DefaultInternationalString) {
i18n = (DefaultInternationalString) remarks;
} else {
i18n = new DefaultInternationalString();
}
}
if (i18n.add(REMARKS_KEY, key, value.toString())) {
continue nextKey;
}
}
/*
* Search for user-specified localizable properties.
*/
if (subProperties == null) {
continue nextKey;
}
if (localizables != null) {
for (int i=0; i",
Loggings.format(Level.WARNING, Loggings.Keys.LOCALES_DISCARTED));
}
}
/*
* Get the localized user-defined properties.
*/
if (subProperties!=null && sub_i18n!=null) {
for (int i=0; i",
Loggings.format(Level.WARNING, Loggings.Keys.LOCALES_DISCARTED));
}
}
}
}
/*
* Stores the definitive reference to the attributes. Note that casts are performed only
* there (not before). This is a wanted feature, since we want to catch ClassCastExceptions
* are rethrown them as a more informative exception.
*/
String key=null; Object value=null;
try {
key = NAME_KEY; this.name = (ReferenceIdentifier) (value = name);
key = ALIAS_KEY; this.alias = nonEmptySet((GenericName[]) (value = alias));
key = IDENTIFIERS_KEY; this.identifiers = nonEmptySet((ReferenceIdentifier[]) (value = identifiers));
key = REMARKS_KEY; this.remarks = (InternationalString) (value = remarks);
} catch (ClassCastException exception) {
InvalidParameterValueException e = new InvalidParameterValueException(Errors.format(
Errors.Keys.ILLEGAL_ARGUMENT_$2, key, value), key, value);
e.initCause(exception);
throw e;
}
ensureNonNull(NAME_KEY, name);
ensureNonNull(NAME_KEY, name.toString());
}
/**
* The {@code gml:id}, which is mandatory. The current implementation searches for the first
* identifier, regardless its authority. If no identifier is found, then the name is used.
* If no name is found (which should not occur for valid objects), then this method returns
* {@code null}.
*
* When an identifier has been found, this method returns the concatenation of its code
* space with its code, without separator. For example this method may return
* {@code "EPSG4326"}, not {@code "EPSG:4326"}.
*/
@XmlID
@XmlAttribute(name = "id", namespace = Namespaces.GML, required = true)
@XmlJavaTypeAdapter(StringConverter.class)
final String getID() {
ReferenceIdentifier id = getIdentifier(null);
if (id == null) {
id = getName();
if (id == null) {
return null;
}
}
String code = id.getCodeSpace();
if (code != null) {
code += id.getCode();
} else {
code = id.getCode();
}
code = code.replace(":", ""); // Usually not needed, but done as a paranoiac safety.
return code;
}
/**
* The primary name by which this object is identified.
*
* @see #getName(Citation)
*/
@Override
public ReferenceIdentifier getName() {
return name;
}
/**
* Returns this object name according the given authority. This method checks first the
* {@linkplain #getName() primary name}, then all {@linkplain #getAlias() alias} in their
* iteration order.
*
*
* If the name or alias implements the {@link ReferenceIdentifier} interface,
* then this method compares the {@linkplain ReferenceIdentifier#getAuthority()
* identifier authority} against the specified citation using the
* {@link Citations#identifierMatches(Citation,Citation) identifierMatches}
* method. If a matching is found, then this method returns the
* {@linkplain ReferenceIdentifier#getCode identifier code} of this object.
*
* Otherwise, if the alias implements the {@link GenericName} interface, then this
* method compares the {@linkplain GenericName#scope name scope} against the specified
* citation using the {@linkplain Citations#identifierMatches(Citation,String)
* identifierMatches} method. If a matching is found, then this method returns the
* {@linkplain GenericName#tip name tip} of this object.
*
*
* Note that alias may implement both the {@link ReferenceIdentifier} and {@link GenericName}
* interfaces (for example {@link NamedIdentifier}). In such cases, the identifier view has
* precedence.
*
* @param authority The authority for the name to return, or {@code null} for any authority.
* @return The object's name (either a {@linkplain ReferenceIdentifier#getCode code}
* or a {@linkplain GenericName#tip name tip}), or {@code null} if
* no name matching the specified authority was found.
*
* @see #getName()
* @see #getAlias()
* @see IdentifiedObjects#getName(IdentifiedObject, Citation)
*
* @since 2.2
*/
public String getName(final Citation authority) {
return IdentifiedObjects.name(this, authority, null);
}
/**
* An alternative name by which this object is identified.
*
* @return The aliases, or an empty array if there is none.
*
* @see #getName(Citation)
*/
@Override
public Collection getAlias() {
if (alias == null) {
return Collections.emptySet();
}
return alias;
}
/**
* An identifier which references elsewhere the object's defining information.
* Alternatively an identifier by which this object can be referenced.
*
* @return This object identifiers, or an empty array if there is none.
*
* @see #getIdentifier(Citation)
*/
@Override
public Set getIdentifiers() {
if (identifiers == null) {
return Collections.emptySet();
}
return identifiers;
}
/**
* Returns the first identifier found, or {@code null} if none.
* This method is invoked by JAXB at marshalling time.
*/
@XmlElement
final ReferenceIdentifier getIdentifier() {
final Set identifiers = this.identifiers;
if (identifiers != null) {
final Iterator it = identifiers.iterator();
if (it.hasNext()) {
return it.next();
}
}
return null;
}
/**
* Returns an identifier according the given authority. This method checks all
* {@linkplain #getIdentifiers identifiers} in their iteration order. It returns the first
* identifier with an {@linkplain ReferenceIdentifier#getAuthority authority} citation
* {@linkplain org.geotoolkit.metadata.iso.citation.Citations#identifierMatches(Citation,
* Citation) matching} the specified authority.
*
* @param authority The authority for the identifier to return, or {@code null} for
* the first identifier regardless its authority.
* @return The object's identifier, or {@code null} if no identifier matching the specified
* authority was found.
*
* @see IdentifiedObjects#getIdentifier(IdentifiedObject, Citation)
*
* @since 2.2
*/
public ReferenceIdentifier getIdentifier(final Citation authority) {
return IdentifiedObjects.identifier(this, authority);
}
/**
* Comments on or information about this object, including data source information.
*/
@Override
public InternationalString getRemarks(){
return remarks;
}
/**
* Returns {@code true} if either the {@linkplain #getName() primary name} or at least
* one {@linkplain #getAlias alias} matches the specified string. This method performs
* the search in the following order, regardless of any authority:
*
*
* - The {@linkplain #getName() primary name} of this object
* - The {@linkplain ScopedName fully qualified name} of an alias
* - The {@linkplain LocalName local name} of an alias
*
*
* @param name The name to compare.
* @return {@code true} if the primary name of at least one alias
* matches the specified {@code name}.
*
* @see IdentifiedObjects#nameMatches(IdentifiedObject, String)
*/
public boolean nameMatches(final String name) {
return IdentifiedObjects.nameMatches(this, alias, name);
}
/**
* Returns {@code true} if this object is deprecated. Deprecated objects exist in some
* {@linkplain org.opengis.referencing.AuthorityFactory authority factories} like the
* EPSG database. Deprecated objects are usually obtained from a deprecated authority
* code. For this reason, the default implementation applies the following rules:
*
*
* - If the {@linkplain #getName() name}
* {@linkplain DefaultReferenceIdentifier#isDeprecated() is deprecated},
* then returns {@code true}.
* - Otherwise if every {@linkplain #getIdentifiers() identifiers}
* {@linkplain DefaultReferenceIdentifier#isDeprecated() are deprecated}, ignoring
* the identifiers that are not instance of {@link DefaultReferenceIdentifier}
* (because they can not be tested), then returns {@code true}.
* - Otherwise returns {@code false}.
*
*
* @return {@code true} if this object is deprecated.
*
* @see DefaultReferenceIdentifier#isDeprecated()
*
* @since 3.20
*/
@Override
public boolean isDeprecated() {
if (name instanceof DefaultReferenceIdentifier) {
if (((DefaultReferenceIdentifier) name).isDeprecated()) {
return true;
}
}
boolean isDeprecated = false;
for (final ReferenceIdentifier identifier : identifiers) {
if (identifier instanceof DefaultReferenceIdentifier) {
isDeprecated = ((DefaultReferenceIdentifier) identifier).isDeprecated();
if (!isDeprecated) break;
}
}
return isDeprecated;
}
/**
* Compares the specified object with this object for equality.
* This method is implemented as below (omitting assertions):
*
* {@preformat java
* return equals(other, ComparisonMode.STRICT);
* }
*
* @param object The other object (may be {@code null}).
* @return {@code true} if both objects are equal.
*/
@Override
public final boolean equals(final Object object) {
final boolean eq = equals(object, ComparisonMode.STRICT);
// If objects are equal, then they must have the same hash code value.
assert !eq || hashCode() == object.hashCode() : this;
return eq;
}
/**
* Compares this object with the specified object for equality.
* The strictness level is controlled by the second argument:
*
*
* - If {@code mode} is {@link ComparisonMode#STRICT STRICT}, then all available properties
* are compared including {@linkplain #getName() name}, {@linkplain #getRemarks remarks},
* {@linkplain #getIdentifiers identifiers code}, etc.
* - If {@code mode} is {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA}, then this
* method compare only the properties needed for computing transformations. In other
* words, {@code sourceCS.equals(targetCS, false)} returns {@code true} only if the
* transformation from {@code sourceCS} to {@code targetCS} is the identity transform,
* no matter what {@link #getName()} said.
*
*
* Some subclasses (especially {@link org.geotoolkit.referencing.datum.AbstractDatum}
* and {@link org.geotoolkit.parameter.AbstractParameterDescriptor}) will test for the
* {@linkplain #getName() name}, since objects with different name have completely
* different meaning. For example nothing differentiate the {@code "semi_major"} and
* {@code "semi_minor"} parameters except the name. The name comparison may be loose
* however, i.e. we may accept a name matching an alias.
*
* @param object The object to compare to {@code this}.
* @param mode {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or
* {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for comparing only properties
* relevant to transformations.
* @return {@code true} if both objects are equal.
*
* @since 3.14
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == null) {
return false;
}
final Class> thisType = getClass();
final Class> thatType = object.getClass();
if (thisType == thatType) {
/*
* If the classes are the same, then the hash codes should be computed in the same
* way. Since those codes are cached, this is an efficient way to quickly check if
* the two objects are different. Note that using the hash codes for comparisons
* that ignore metadata is okay only if the implementation note described in the
* 'computeHashCode()' javadoc hold (metadata not used in hash code computation).
*/
if (mode.ordinal() < ComparisonMode.APPROXIMATIVE.ordinal()) {
final int tc = hashCode;
if (tc != 0) {
final int oc = ((AbstractIdentifiedObject) object).hashCode;
if (oc != 0 && tc != oc) {
return false;
}
}
}
} else if (mode == ComparisonMode.STRICT || // Same classes was required for this mode.
!Classes.implementSameInterfaces(thisType, thatType, IdentifiedObject.class))
{
return false;
}
switch (mode) {
case STRICT: {
final AbstractIdentifiedObject that = (AbstractIdentifiedObject) object;
return Utilities.equals(name, that.name) &&
Utilities.equals(alias, that.alias) &&
Utilities.equals(identifiers, that.identifiers) &&
Utilities.equals(remarks, that.remarks);
}
case BY_CONTRACT: {
final IdentifiedObject that = (IdentifiedObject) object;
return deepEquals(getName(), that.getName(), mode) &&
deepEquals(getAlias(), that.getAlias(), mode) &&
deepEquals(getIdentifiers(), that.getIdentifiers(), mode) &&
deepEquals(getRemarks(), that.getRemarks(), mode);
}
case IGNORE_METADATA:
case APPROXIMATIVE:
case DEBUG: {
return true;
}
default: {
throw new IllegalArgumentException(Errors.format(Errors.Keys.UNKNOWN_ENUM_$1, mode));
}
}
}
/**
* Returns a hash value for this identified object. This method invokes {@link #computeHashCode()}
* when first needed and caches the value for future invocations. Subclasses should override
* {@code computeHashCode()} instead than this method.
*
* {@section Implementation specific feature}
* In the Geotk implementation, the {@linkplain #getName() name}, {@linkplain #getIdentifiers()
* identifiers} and {@linkplain #getRemarks() remarks} are not used for hash code computation.
* Consequently two identified objects will return the same hash value if they are equal in the
* sense of {@linkplain #equals(Object, ComparisonMode) equals}(…,
* {@linkplain ComparisonMode#IGNORE_METADATA})
. This feature allows users to
* implement metadata-insensitive {@link java.util.HashMap}.
*
* @return The hash code value. This value may change between different execution of the
* Geotk library.
*/
@Override
public final int hashCode() { // No need to synchronize; ok if invoked twice.
int hash = hashCode;
if (hash == 0) {
hash = computeHashCode();
if (hash == 0) {
hash = -1;
}
hashCode = hash;
}
assert hash == -1 || hash == computeHashCode() : this;
return hash;
}
/**
* Computes a hash value for this identified object. This method is invoked by
* {@link #hashCode()} when first needed.
*
* {@section Implementation specific feature}
* In the Geotk implementation, the {@linkplain #getName() name}, {@linkplain #getIdentifiers()
* identifiers} and {@linkplain #getRemarks() remarks} are not used for hash code computation.
* Consequently two identified objects will return the same hash value if they are equal in the
* sense of {@linkplain #equals(Object, ComparisonMode) equals}(…,
* {@linkplain ComparisonMode#IGNORE_METADATA})
. This feature allows users to
* implement metadata-insensitive {@link java.util.HashMap}.
*
* @return The hash code value. This value may change between different execution of the
* Geotk library.
*
* @since 3.18
*/
protected int computeHashCode() {
// Subclasses need to overrides this!!!!
int code = (int) serialVersionUID;
final Class>[] types = Classes.getLeafInterfaces(getClass(), IdentifiedObject.class);
if (types != null) {
for (final Class> type : types) {
// Use a plain addition in order to be insensitive to array element order.
code += type.hashCode();
}
}
return code;
}
}