/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.referencing; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.LogRecord; import javax.measure.unit.SI; import javax.measure.unit.Unit; import org.opengis.metadata.Identifier; import org.opengis.metadata.citation.Citation; import org.opengis.parameter.InvalidParameterValueException; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.util.GenericName; import org.opengis.util.InternationalString; import org.opengis.util.ScopedName; import org.geotools.metadata.iso.citation.Citations; import org.geotools.referencing.wkt.Formattable; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Loggings; import org.geotools.resources.i18n.LoggingKeys; import org.geotools.util.logging.Logging; import org.geotools.util.GrowableInternationalString; import org.geotools.util.NameFactory; import org.geotools.util.Utilities; /** * A base class for metadata applicable to reference system objects. When * {@link org.opengis.referencing.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 org.opengis.referencing.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.geotools.referencing.cs.AbstractCS} object may be instantiated. * * @since 2.1 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public class AbstractIdentifiedObject extends Formattable implements IdentifiedObject, Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -5173281694258483264L; /** * An empty array of identifiers. This is usefull for fetching identifiers as an array, * using the following idiom: *
*/ public static final ReferenceIdentifier[] EMPTY_IDENTIFIER_ARRAY = new ReferenceIdentifier[0]; /** * An empty array of alias. This is usefull for fetching alias as an array, * using the following idiom: ** {@linkplain #getIdentifiers()}.toArray(EMPTY_IDENTIFIER_ARRAY); *
*/ public static final GenericName[] EMPTY_ALIAS_ARRAY = new GenericName[0]; /** * A comparator for sorting identified objects by {@linkplain #getName() name}. */ public static final Comparator* {@linkplain #getAlias()}.toArray(EMPTY_ALIAS_ARRAY); *
*
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 String}, {@linkplain String}[] ,
* {@link GenericName} or {@linkplain GenericName}[] |
* {@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 InvalidParameterValueException if a property has an invalid value.
* @throws IllegalArgumentException if a property is invalid for some other reason.
*/
public AbstractIdentifiedObject(final Map 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.
*
* This method returns a mutable map. Consequently, callers can add their own identifiers
* directly to this map if they wish.
*
* @param info The identified object to view as a properties map.
* @param authority The new authority for the object to be created, or {@code null} if it
* is not going to have any declared authority.
* @return An view of the identified object as a mutable map.
*/
public static Map 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#getScope 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#asLocalName local name} of this object.
* Some subclasses (especially {@link org.geotools.referencing.datum.AbstractDatum}
* and {@link org.geotools.parameter.AbstractParameterDescriptor}) will test for the
* {@linkplain #getName() name}, since objects with different name have completly
* different meaning. For example nothing differentiate the {@code "semi_major"} and
* {@code "semi_minor"} parameters except the name. The name comparaison may be loose
* however, i.e. we may accept a name matching an alias.
*
* @param object The object to compare to {@code this}.
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both objects are equal.
*/
public boolean equals(final AbstractIdentifiedObject object, final boolean compareMetadata) {
if (object!=null && object.getClass().equals(getClass())) {
if (!compareMetadata) {
return true;
}
return Utilities.equals(name, object.name ) &&
Utilities.equals(alias, object.alias ) &&
Utilities.equals(identifiers, object.identifiers) &&
Utilities.equals(remarks, object.remarks );
}
return false;
}
/**
* Compares two Geotools's {@code AbstractIdentifiedObject} objects for equality. This
* method is equivalent to {@code object1.equals(object2, compareMetadata)}
* except that one or both arguments may be null. This convenience method is provided for
* implementation of {@code equals} in subclasses.
*
* @param object1 The first object to compare (may be {@code null}).
* @param object2 The second object to compare (may be {@code null}).
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both objects are equal.
*/
static boolean equals(final AbstractIdentifiedObject object1,
final AbstractIdentifiedObject object2,
final boolean compareMetadata)
{
return (object1 == object2) || (object1!=null && object1.equals(object2, compareMetadata));
}
/**
* Compares two OpenGIS's {@code IdentifiedObject} objects for equality. This convenience
* method is provided for implementation of {@code equals} in subclasses.
*
* @param object1 The first object to compare (may be {@code null}).
* @param object2 The second object to compare (may be {@code null}).
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both objects are equal.
*/
protected static boolean equals(final IdentifiedObject object1,
final IdentifiedObject object2,
final boolean compareMetadata)
{
if (!(object1 instanceof AbstractIdentifiedObject)) return Utilities.equals(object1, object2);
if (!(object2 instanceof AbstractIdentifiedObject)) return Utilities.equals(object2, object1);
return equals((AbstractIdentifiedObject) object1,
(AbstractIdentifiedObject) object2, compareMetadata);
}
/**
* Compares two arrays of OpenGIS's {@code IdentifiedObject} objects for equality. This
* convenience method is provided for implementation of {@code equals} method in subclasses.
*
* @param array1 The first array to compare (may be {@code null}).
* @param array2 The second array to compare (may be {@code null}).
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both arrays are equal.
*/
protected static boolean equals(final IdentifiedObject[] array1,
final IdentifiedObject[] array2,
final boolean compareMetadata)
{
if (array1 != array2) {
if ((array1 == null) || (array2 == null) || (array1.length != array2.length)) {
return false;
}
for (int i=array1.length; --i>=0;) {
if (!equals(array1[i], array2[i], compareMetadata)) {
return false;
}
}
}
return true;
}
/**
* Compares two collectionss of OpenGIS's {@code IdentifiedObject} objects for equality. The
* comparaison take order in account, which make it more appropriate for {@link java.util.List}
* or {@link LinkedHashSet} comparaisons. This convenience method is provided for
* implementation of {@code equals} method in subclasses.
*
* @param collection1 The first collection to compare (may be {@code null}).
* @param collection2 The second collection to compare (may be {@code null}).
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both collections are equal.
*/
protected static boolean equals(final Collection extends IdentifiedObject> collection1,
final Collection extends IdentifiedObject> collection2,
final boolean compareMetadata)
{
if (collection1 == collection2) {
return true;
}
if (collection1==null || collection2==null) {
return false;
}
final Iterator extends IdentifiedObject> it1 = collection1.iterator();
final Iterator extends IdentifiedObject> it2 = collection2.iterator();
while (it1.hasNext()) {
if (!it2.hasNext() || !equals(it1.next(), it2.next(), compareMetadata)) {
return false;
}
}
return !it2.hasNext();
}
/**
* Compares two objects for order. Any object may be null. This method is
* used for implementation of {@link #NAME_COMPARATOR} and its friends.
*/
private static {@linkplain #getProperties(IdentifiedObject) getProperties}(info)
), except for
* the following:
*
*
*
*
*
* 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.
* @return The object's name (either a {@linkplain ReferenceIdentifier#getCode code}
* or a {@linkplain GenericName#asLocalName local name}), or {@code null} if
* no name matching the specified authority was found.
*
* @see #getName()
* @see #getAlias()
*
* @since 2.2
*/
public String getName(final Citation authority) {
return getName0(this, authority);
}
/**
* Returns an object's name according the given authority. This method performs the same search
* than {@link #getName(Citation)} on arbitrary implementations of GeoAPI interface.
*
* @param info The object to get the name from.
* @param authority The authority for the name to return.
* @return The object's name (either a {@linkplain ReferenceIdentifier#getCode code}
* or a {@linkplain GenericName#asLocalName local name}), or {@code null} if
* no name matching the specified authority was found.
*
* @since 2.2
*/
public static String getName(final IdentifiedObject info, final Citation authority) {
if (info instanceof AbstractIdentifiedObject) {
// Gives a chances to subclasses to get their overridden method invoked.
return ((AbstractIdentifiedObject) info).getName(authority);
}
return getName0(info, authority);
}
/**
* Implementation of {@link #getName(Citation)}.
*/
private static String getName0(final IdentifiedObject info, final Citation authority) {
Identifier identifier = info.getName();
if (authority == null) {
return identifier.getCode();
}
String name = null;
Citation infoAuthority = identifier.getAuthority();
if (infoAuthority != null) {
if (Citations.identifierMatches(authority, infoAuthority)) {
name = identifier.getCode();
} else {
for (final GenericName alias : info.getAlias()) {
if (alias instanceof Identifier) {
identifier = (Identifier) alias;
infoAuthority = identifier.getAuthority();
if (infoAuthority != null) {
if (Citations.identifierMatches(authority, infoAuthority)) {
name = identifier.getCode();
break;
}
}
} else {
final GenericName scope = alias.scope().name();
if (scope != null) {
if (Citations.identifierMatches(authority, scope.toString())) {
name = alias.tip().toString();
break;
}
}
}
}
}
}
return name;
}
/**
* 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:
*
*
*
* @param name The name to compare.
* @return {@code true} if the primary name of at least one alias
* matches the specified {@code name}.
*/
public boolean nameMatches(final String name) {
return nameMatches(this, alias, name);
}
/**
* 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
* same check than the {@linkplain #nameMatches(String) non-static method} on arbitrary
* object implementing the GeoAPI interface.
*
* @param object The object to check.
* @param name The name.
* @return {@code true} if the primary name of at least one alias
* matches the specified {@code name}.
*/
public static boolean nameMatches(final IdentifiedObject object, final String name) {
if (object instanceof AbstractIdentifiedObject) {
return ((AbstractIdentifiedObject) object).nameMatches(name);
} else {
return nameMatches(object, object.getAlias(), name);
}
}
/**
* Returns {@code true} if the {@linkplain #getName() primary name} of an object matches
* the primary name of one {@linkplain #getAlias alias} of the other object.
*
* @param o1 The first object to compare by name.
* @param o2 The second object to compare by name.
* @return {@code true} if both objects have a common name.
*
* @since 2.4
*/
public static boolean nameMatches(final IdentifiedObject o1, final IdentifiedObject o2) {
return nameMatches(o1, o2.getName().getCode()) ||
nameMatches(o2, o1.getName().getCode());
}
/**
* Implementation of {@code nameMatches} method.
*
* @param object The object to check.
* @param alias The list of alias in {@code object} (may be {@code null}).
* This method will never modify this list. Consequently, it may be a
* direct reference to an internal array.
* @param name The name.
* @return {@code true} if the primary name of at least one alias
* matches the specified {@code name}.
*/
private static boolean nameMatches(final IdentifiedObject object,
final Collection{@linkplain
* #equals(AbstractIdentifiedObject,boolean) equals}(AbstractIdentifiedObject,
* false)
.
*
* @return The hash code value. This value doesn't need to be the same
* in past or future versions of this class.
*/
@Override
public int hashCode() {
// Subclasses need to overrides this!!!!
return (int)serialVersionUID ^ getClass().hashCode();
}
/**
* Returns the specified array as an immutable set, or {@code null} if the
* array is empty or null. This is a convenience method for sub-classes
* constructors.
*
* @param