Module org.apache.sis.referencing
Package org.apache.sis.referencing

# Class AbstractIdentifiedObject

Object
FormattableObject
AbstractIdentifiedObject

All Implemented Interfaces:
  `Serializable`, `Formattable`, `Deprecable`, `LenientComparable`, `IdentifiedObject`

Direct Known Subclasses:
  `AbstractCoordinateOperation`, `AbstractCS`, `AbstractDatum`, `AbstractParameterDescriptor`, `AbstractReferenceSystem`, `DefaultCoordinateSystemAxis`, `DefaultEllipsoid`, `DefaultOperationMethod`, `DefaultPrimeMeridian`

public class AbstractIdentifiedObject extends FormattableObject implements IdentifiedObject, Formattable, LenientComparable, Deprecable, Serializable
Base class for objects identified by a name or a code. Those objects are typically geodetic datum (e.g. "World Geodetic System 1984"), Coordinate Reference System (e.g. "WGS 84 / World Mercator") or map projection (e.g. "Mercator (variant A)"). Those names, or a code (e.g. `"EPSG:3395"`), can be used for fetching an object from a database. However, it is not sufficient to know the object name. We also need to know who define that name (the authority) since the same objects are often named differently depending on the providers, or conversely the same name is used for different objects depending on the provider.

The main information stored in an `IdentifiedObject` are:

- a primary name, considered by the object creator as the preferred name,
- an arbitrary number of aliases, for example a list of names used by other providers,
- an arbitrary number of identifiers, typically primary keys in the provider database,
- optional remarks.

## Instantiation

This class is conceptually abstract, even if it is technically possible to instantiate it. Applications should instead instantiate the most specific subclass having a name starting by `Default`. However, exceptions to this rule may occur when it is not possible to identify the exact type.
Example: It is sometimes not possible to infer the exact coordinate system type from version 1 of Well Known Text format, for example when parsing a `LOCAL_CS` element. In such exceptional situation, a plain `AbstractCS` object may be instantiated.
`IdentifiedObject` instances are created in two main ways:

- Using an `ObjectFactory`, in which case all properties can be explicitly specified.
- Using an `AuthorityFactory`, in which case only a code (typically a primary key) is specified. The authority and authority code values are set to the authority name of the factory object, and the authority code supplied by the client, respectively. All other information are fetched from the database.

## Immutability and thread safety

This base class is immutable if the `Citation`, `ReferenceIdentifier`, `GenericName` and `InternationalString` instances given to the constructor are also immutable. Most SIS subclasses and related classes are immutable under similar conditions. This means that unless otherwise noted in the javadoc, `IdentifiedObject` instances created using only SIS factories and static constants can be shared by many objects and passed between threads without synchronization.

Since:
  0.4
See Also:
  - Serialized Form

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `static final String`
  `DEPRECATED_KEY`
  Optional key which can be given to the constructor for specifying the object is deprecated.
  `static final String`
  `LOCALE_KEY`
  Optional key which can be given to the constructor for specifying the locale to use for producing error messages.

  ### Fields inherited from interface IdentifiedObject

  `ALIAS_KEY, IDENTIFIERS_KEY, NAME_KEY, REMARKS_KEY`

- ## Constructor Summary

  Constructors
  Modifier
  Constructor
  Description
  ``
  `AbstractIdentifiedObject(Map<String,?> properties)`
  Constructs an object from the given properties.
  `protected`
  `AbstractIdentifiedObject(IdentifiedObject object)`
  Constructs a new identified object with the same values than the specified one.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `static AbstractIdentifiedObject`
  `castOrCopy(IdentifiedObject object)`
  Returns a SIS identified object implementation with the values of the given arbitrary implementation.
  `protected long`
  `computeHashCode()`
  Invoked by `hashCode()` for computing the hash code when first needed.
  `final boolean`
  `equals(Object object)`
  Compares the specified object with this object for equality.
  `boolean`
  `equals(Object object, ComparisonMode mode)`
  Compares this object with the given object for equality.
  `void`
  `formatTo(Formatter formatter, int flags, int width, int precision)`
  Formats the name or identifier of this object using the provider formatter.
  `protected String`
  `formatTo(Formatter formatter)`
  Formats the inner part of the Well Known Text (WKT) representation for this object.
  `Collection<GenericName>`
  `getAlias()`
  Returns alternative names by which this object is identified.
  `InternationalString`
  `getDescription()`
  Returns a narrative explanation of the role of this object.
  `Collection<DefaultObjectDomain>`
  `getDomains()`
  Returns the usage of this CRS-related object.
  `Set<ReferenceIdentifier>`
  `getIdentifiers()`
  Returns identifiers which references elsewhere the object's defining information.
  `Class<? extends IdentifiedObject>`
  `getInterface()`
  Returns the GeoAPI interface implemented by this class.
  `ReferenceIdentifier`
  `getName()`
  Returns the primary name by which this object is identified.
  `InternationalString`
  `getRemarks()`
  Returns comments on or information about this object, including data source information.
  `final int`
  `hashCode()`
  Returns a hash value for this identified object.
  `boolean`
  `isDeprecated()`
  Returns `true` if this object is deprecated.
  `boolean`
  `isHeuristicMatchForName(String name)`
  Returns `true` if either the primary name or at least one alias matches the given string according heuristic rules.

  ### Methods inherited from class FormattableObject

  `print, toString, toString, toWKT`

  ### Methods inherited from class Object

  `clone, finalize, getClass, notify, notifyAll, wait, wait, wait`

  ### Methods inherited from interface IdentifiedObject

  `toWKT`

- ## Field Details

  - ### LOCALE_KEY

    public static final String LOCALE_KEY
    Optional key which can be given to the constructor for specifying the locale to use for producing error messages. Notes:
    - The locale is not stored in any `AbstractIdentifiedObject` property; its value is ignored if no error occurred at construction time.
    - The locale is used on a best effort basis; not all error messages may be localized.
    See Also:
      - Constant Field Values

  - ### DEPRECATED_KEY

    public static final String DEPRECATED_KEY
    Optional key which can be given to the constructor for specifying the object is deprecated. If deprecated, then the replacement should be specified in the remarks.

    #### Example

    "superseded by code XYZ".
    Since:
      0.6
    See Also:
      - Constant Field Values

- ## Constructor Details

  - ### AbstractIdentifiedObject

    public AbstractIdentifiedObject(Map<String,?> properties) throws IllegalArgumentException
    Constructs an object from the given properties. Keys are strings from the table below. The map given in argument shall contain an entry at least for the "name" or "code" key. Other properties listed in the table below are optional. Some properties are shortcuts discussed below the table.

    Recognized properties (non exhaustive list)
    Property name | Value type | Returned by
    --- | --- | ---
    "name" | `ReferenceIdentifier` or `String` | `getName()`
    "authority" | `String` or `Citation` | `ImmutableIdentifier.getAuthority()` on the name
    "code" | `String` | `ImmutableIdentifier.getCode()` on the name
    "codespace" | `String` | `ImmutableIdentifier.getCodeSpace()` on the name
    "version" | `String` | `ImmutableIdentifier.getVersion()` on the name
    "description" | `String` | `ImmutableIdentifier.getDescription()` on the name
    "alias" | `GenericName` or `CharSequence` (optionally as array) | `getAlias()`
    "identifiers" | `ReferenceIdentifier` (optionally as array) | `getIdentifiers()`
    "domains" | `DefaultObjectDomain` (optionally as array) | `getDomains()`
    "scope" | `String` or `InternationalString` | `DefaultObjectDomain.getScope()` on the domain
    "domainOfValidity" | `Extent` | `DefaultObjectDomain.getDomainOfValidity()` on the domain
    "remarks" | `InternationalString` or `String` | `getRemarks()`
    "deprecated" | `Boolean` | `isDeprecated()`
    "locale" | `Locale` | (none)

    #### Shortcuts

    The `"authority"`, `"code"`, `"codespace"` and `"version"` properties are shortcuts for building a name. Their values are ignored if the `"name"` property is already associated to an `Identifier` value instead of a `String`. Likewise, the `"scope"` and `"domainOfValidity"` shortcuts are ignored if the `"domains"` property is provided.

    #### Localization

    All localizable attributes like `"remarks"` may have a language and country code suffix. For example, the `"remarks_fr"` property stands for remarks in French and the `"remarks_fr_CA"` property stands for remarks in French Canadian. They are convenience properties for building the `InternationalString` value.

    The `"locale"` property applies only in case of exception for formatting the error message, and is used only on a best effort basis. The locale is discarded after successful construction since localizations are applied by the `InternationalString​.toString(Locale)` method.

    #### Properties map versus explicit arguments

    Generally speaking, information provided in the `properties` map are considered ignorable metadata while information provided in explicit arguments to the sub-class constructors have an impact on coordinate transformation results. See `equals(Object, ComparisonMode)` for more information.
    Parameters:
      `properties` - the properties to be given to this identified object.
    Throws:
      `IllegalArgumentException` - if a property has an invalid value.

  - ### AbstractIdentifiedObject

    protected AbstractIdentifiedObject(IdentifiedObject object)
    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 SIS 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.

    Parameters:
      `object` - the object to shallow copy.

- ## Method Details

  - ### castOrCopy

    public static AbstractIdentifiedObject castOrCopy(IdentifiedObject object)
    Returns a SIS identified object implementation with the values of the given arbitrary implementation. This method performs the first applicable action in the following choices:
    - If the given object is `null`, then this method returns `null`.
    - Otherwise if the given object is an instance of `CoordinateReferenceSystem`, `CoordinateSystem`, `CoordinateSystemAxis`, `Datum`, `Ellipsoid`, `PrimeMeridian`, `OperationMethod`, `CoordinateOperation`, `ParameterDescriptor` or `ParameterDescriptorGroup`, then this method delegates to the `castOrCopy(…)` method of the corresponding SIS subclass. Note that if the given object implements more than one of the above-cited interfaces, then the `castOrCopy(…)` method to be used is unspecified.
    - Otherwise if the given object is already an instance of `AbstractIdentifiedObject`, then it is returned unchanged.
    - Otherwise a new `AbstractIdentifiedObject` instance is created using the copy constructor and returned. Note that this is a shallow copy operation, because the other properties contained in the given object are not recursively copied.
    Parameters:
      `object` - the object to get as a SIS implementation, or `null` if none.
    Returns:
      a SIS implementation containing the values of the given object (may be the given object itself), or `null` if the argument was null.

  - ### getInterface

    public Class<? extends IdentifiedObject> getInterface()
    Returns the GeoAPI interface implemented by this class. This information is part of the data compared by `equals(Object, ComparisonMode)`.

    The default implementation returns `IdentifiedObject​.class`. Subclasses implementing a more specific GeoAPI interface shall override this method.

    #### Invariants

    The following invariants must hold for all `AbstractIdentifiedObject` instances:
    - `getInterface().isInstance(this)` shall return `true`.
    - If `A​.getClass() == B​.getClass()` is `true`, then `A​.getInterface() == B​.getInterface()` shall be `true`. Note that the converse does not need to hold.
    Returns:
      the GeoAPI interface implemented by this class.

  - ### getName

    public ReferenceIdentifier getName()
    Returns the primary name by which this object is identified.
    Specified by:
      `getName` in interface `IdentifiedObject`
    Returns:
      the primary name.
    See Also:
      - `IdentifiedObjects​.getName(IdentifiedObject, Citation)`

  - ### getAlias

    public Collection<GenericName> getAlias()
    Returns alternative names by which this object is identified.
    Specified by:
      `getAlias` in interface `IdentifiedObject`
    Returns:
      the aliases, or an empty collection if there is none.
    See Also:
      - `getName()`

  - ### getIdentifiers

    public Set<ReferenceIdentifier> getIdentifiers()
    Returns identifiers which references elsewhere the object's defining information. Alternatively, identifiers by which this object can be referenced.
    Specified by:
      `getIdentifiers` in interface `IdentifiedObject`
    Returns:
      this object identifiers, or an empty set if there is none.
    See Also:
      - `IdentifiedObjects​.getIdentifier(IdentifiedObject, Citation)`

  - ### getDomains

    public Collection<DefaultObjectDomain> getDomains()
    Returns the usage of this CRS-related object. The domain includes a scope (description of the primary purpose of this object) together with a domain of validity (spatial and temporal extent in which the object can be used).
    Returns:
      scopes and domains of validity of this object.
    Since:
      1.4

  - ### getDescription

    public InternationalString getDescription()
    Returns a narrative explanation of the role of this object.

    #### Default value

    The default implementation returns the description provided by this object's name.
    Returns:
      a narrative explanation of the role of this object, or `null` if none.
    Since:
      0.6
    See Also:
      - `ImmutableIdentifier​.getDescription()`

  - ### getRemarks

    public InternationalString getRemarks()
    Returns comments on or information about this object, including data source information. If this object is deprecated, then the remarks should give indication about the replacement (e.g. "superceded by …").
    Specified by:
      `getRemarks` in interface `Deprecable`
    Specified by:
      `getRemarks` in interface `IdentifiedObject`
    Returns:
      the remarks, or `null` if none.

  - ### isDeprecated

    public boolean isDeprecated()
    Returns `true` if this object is deprecated. Deprecated objects exist in some authority factories like the EPSG database. If this method returns `true`, then the remarks should give indication about the replacement (e.g. "superceded by …").
    Specified by:
      `isDeprecated` in interface `Deprecable`
    Returns:
      `true` if this object is deprecated.

  - ### isHeuristicMatchForName

    public boolean isHeuristicMatchForName(String name)
    Returns `true` if either the primary name or at least one alias matches the given string according heuristic rules. The default implementation returns `true` if the given `name` is equal, ignoring aspects documented below, to one of the following names:
    - The primary name's code (without codespace).
    - Any alias's tip (without scope and namespace).
    The comparison ignores the following aspects:
    - Lower/upper cases.
    - Some Latin diacritical signs (e.g. `"Réunion"` and `"Reunion"` are considered equal).
    - All characters that are not letters or digits (e.g. `"Mercator (1SP)"` and `"Mercator_1SP"` are considered equal).
    - Namespaces or scopes, because this method is typically invoked with either the value of another `IdentifiedObject​.getName()​.getCode()` or with the Well Known Text (WKT) projection or parameter name.

    #### Usage

    This method is invoked by SIS when comparing in `IGNORE_METADATA` mode two objects that can be differentiated only by some identifier (name or alias), like coordinate system axes, datum, parameters and operation methods. See `equals(Object, ComparisonMode)` for more information.

    This method is also invoked when searching a parameter or operation method for a given name. For example, the same projection is known as `"Mercator (variant A)"` (the primary name according EPSG) and `"Mercator (1SP)"` (the legacy name prior EPSG 7.6). Since the latter is still in frequent use, SIS accepts it as an alias of the Mercator (variant A) projection.

    #### Overriding by subclasses

    Some subclasses add more flexibility to the comparisons:
    - Comparisons of coordinate system axis names consider `"Lat"`, `"Latitude"` and `"Geodetic latitude"` as synonymous, and likewise for longitude.
    - Comparisons of datum names ignore the `"D_"` prefix, if any. This prefix appears in ESRI datum name (e.g. `"D_WGS_1984"`).
    - Comparisons of geodetic datum names may ignore the prime meridian name, if any. Example: "(Paris)" in "Nouvelle Triangulation Française (Paris)".

    #### Future evolutions

    This method implements recommendations from the WKT 2 specification §B.5.2, together with heuristic rules learned from experience while trying to provide inter-operability with different data producers. Those rules may be adjusted in any future SIS version according experience gained while working with more data producers.
    Parameters:
      `name` - the name to compare with the object name or aliases.
    Returns:
      `true` if the primary name or at least one alias matches the specified `name`.
    See Also:
      - `IdentifiedObjects​.isHeuristicMatchForName(IdentifiedObject, String)`
      - `Characters​.Filter​.LETTERS_AND_DIGITS`

  - ### equals

    public boolean equals(Object object, ComparisonMode mode)
    Compares this object with the given object for equality. The strictness level is controlled by the second argument, from stricter to more permissive values:

    Description of comparison modes
    Mode | Description
    --- | ---
    `STRICT`: | Verifies if the two objects are of the same class and compares all public properties, including SIS-specific (non standard) properties.
    `BY_CONTRACT`: | Verifies if the two objects implement the same GeoAPI interface and compares all properties defined by that interface (name, identifiers, remarks, etc). The two objects do not need to be instances of the same implementation class and SIS-specific properties are ignored.
    `IGNORE_METADATA`: | Compares only the properties relevant to coordinate transformations. Generally speaking, the content of the `properties` map given at construction time is considered ignorable metadata while the explicit arguments given to the constructor (if any) are considered non-ignorable. Note that there is some exceptions to this rule of thumb — see When object name matter below.
    `APPROXIMATE`: | Same as `IGNORE_METADATA`, with some tolerance threshold on numerical values.
    `ALLOW_VARIANT`: | Same as `APPROXIMATE`, but ignores coordinate system axes.
    `DEBUG`: | Special mode for figuring out why two objects expected to be equal are not.

    The main guideline is that if `sourceCRS​.equals(targetCRS, IGNORE_METADATA)` returns `true`, then the transformation from `sourceCRS` to `targetCRS` should be the identity transform even if the two CRS do not have the same name.

    #### When object name matter

    Some subclasses (especially `DefaultCoordinateSystemAxis`, `AbstractDatum` and `DefaultParameterDescriptor`) will compare the name even in `IGNORE_METADATA` mode, because objects of those types with different names have completely different meaning. For example, nothing differentiate the `"semi_major"` and `"semi_minor"` parameters except the name. The name comparison may be lenient however, i.e. the rules may accept a name matching an alias. See `isHeuristicMatchForName(String)` for more information.

    #### Conformance to the `equals(Object)` method contract

    `ComparisonMode​.STRICT` is the only mode compliant with the `Object​.equals(Object)` contract. For all other modes, the comparison is not guaranteed to be symmetric neither transitive. See `LenientComparable` for more information.
    Specified by:
      `equals` in interface `LenientComparable`
    Parameters:
      `object` - the object to compare to `this`.
      `mode` - the strictness level of the comparison.
    Returns:
      `true` if both objects are equal according the given comparison mode.
    See Also:
      - `computeHashCode()`
      - `Utilities​.deepEquals(Object, Object, ComparisonMode)`

  - ### equals

    public final boolean equals(Object object)
    Compares the specified object with this object for equality. This method is implemented as below (omitting assertions):

        return equals(other, ComparisonMode.STRICT);

    Subclasses shall override `equals(Object, ComparisonMode)` instead of this method.
    Specified by:
      `equals` in interface `LenientComparable`
    Overrides:
      `equals` in class `Object`
    Parameters:
      `object` - the other object (may be `null`).
    Returns:
      `true` if both objects are equal.
    See Also:
      - `ComparisonMode​.STRICT`

  - ### hashCode

    public final int hashCode()
    Returns a hash value for this identified object. Two `AbstractIdentifiedObject` instances for which `equals(Object)` returns `true` shall have the same hash code value, if the hash codes are computed on the same JVM instance for both objects. The hash code value is not guaranteed to be stable between different versions of the Apache SIS library, or between libraries running on different JVM.

    #### Implementation note

    This method invokes `computeHashCode()` when first needed, then caches the result. Subclasses shall override `computeHashCode()` instead of this method.
    Overrides:
      `hashCode` in class `Object`
    Returns:
      the hash code value. This value may change in any future Apache SIS version.
    Throws:
      `AssertionError` - if assertions are enabled and the value computed by `computeHashCode()` changed.

  - ### computeHashCode

    protected long computeHashCode()
    Invoked by `hashCode()` for computing the hash code when first needed. This method is invoked at most once in normal execution, or an arbitrary number of times if Java assertions are enabled. The hash code value shall never change during the whole lifetime of this object in a JVM. The hash code value does not need to be the same in two different executions of the JVM.

    #### Overriding

    Subclasses can override this method for using more properties in hash code calculation. All `computeHashCode()` methods shall invoke `super​.computeHashCode()`, not `hashCode()`. Example:

            @Override
            protected long computeHashCode() {
                return super.computeHashCode() + 31 * Objects.hash(myProperties);
            }

    Returns:
      the hash code value. This value may change in any future Apache SIS version.

  - ### formatTo

    protected String formatTo(Formatter formatter)
    Formats the inner part of the Well Known Text (WKT) representation for this object. The default implementation writes the following elements:
    - The object name.
    Keywords and metadata (scope, extent, identifier and remarks) shall not be formatted here. For example if this formattable element is for a `GeodeticCRS[…]` element, then subclasses shall write the content starting at the insertion point shown below:

    WKT example

           GeodeticCRS["WGS 84", ID["EPSG", 4326]]
                               ↑
                       (insertion point)

    Java code example

            @Override
            protected String formatTo(final Formatter formatter) {
                super.formatTo(formatter);
                // ... write the elements at the insertion point ...
                return "GeodeticCRS";
            }

    #### Formatting non-standard WKT

    If the implementation cannot represent this object without violating some WKT constraints, it can uses its own (non-standard) keywords but shall declare that it did so by invoking one of the `Formatter​.setInvalidWKT(…)` methods.

    Alternatively, the implementation may also have no WKT keyword for this object. In such case, this method shall return `null`.

    Specified by:
      `formatTo` in class `FormattableObject`
    Parameters:
      `formatter` - the formatter where to format the inner content of this WKT element.
    Returns:
      the CamelCase keyword for the WKT element, or `null` if unknown.
    See Also:
      - WKT 2 specification §7

  - ### formatTo

    public void formatTo(Formatter formatter, int flags, int width, int precision)
    Formats the name or identifier of this object using the provider formatter. This method is invoked when an `IdentifiedObject` object is formatted using the `"%s"` conversion specifier of `Formatter`. Users don't need to invoke this method explicitly.

    If the alternate flags is present (as in `"%#s"`), then this method will format the identifier (if present) instead of the object name.

    Specified by:
      `formatTo` in interface `Formattable`
    Parameters:
      `formatter` - the formatter in which to format this identified object.
      `flags` - whether to apply left alignment, use upper-case letters and/or use alternate form.
      `width` - minimal number of characters to write, padding with `' '` if necessary.
      `precision` - maximal number of characters to write, or -1 if no limit.
    Since:
      1.1
    See Also:
      - `IdentifiedObjects​.getName(IdentifiedObject, Citation)`
      - `IdentifiedObjects​.getIdentifierOrName(IdentifiedObject)`