blob: fc92ae14a0e421c1616c4a6b6e2953a9fae472e0 [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.referencing.gazetteer;
import java.util.Map;
import java.util.List;
import java.util.Objects;
import java.util.HashMap;
import javax.xml.bind.annotation.XmlTransient;
import org.opengis.util.InternationalString;
import org.apache.sis.referencing.AbstractReferenceSystem;
import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.iso.Types;
import org.apache.sis.util.Debug;
import org.apache.sis.io.wkt.Formatter;
import org.apache.sis.io.wkt.ElementKind;
import org.apache.sis.metadata.iso.extent.Extents;
import org.apache.sis.internal.referencing.WKTUtilities;
import org.apache.sis.io.wkt.FormattableObject;
import org.apache.sis.util.resources.Vocabulary;
// Branch-dependent imports
import org.opengis.metadata.citation.Party;
import org.opengis.referencing.gazetteer.LocationType;
import org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers;
/**
* Base class of reference systems that describe locations using geographic identifiers instead than coordinates.
*
* <h2>Immutability and thread safety</h2>
* This base class is immutable and thus thread-safe if the property <em>values</em> (not necessarily the map itself)
* 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, {@code ReferencingByIdentifiers} instances
* created using only SIS factories and static constants can be shared by many objects and passed between threads
* without synchronization.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
*
* @see ModifiableLocationType
* @see AbstractLocation
*
* @since 0.8
* @module
*/
@XmlTransient
public class ReferencingByIdentifiers extends AbstractReferenceSystem implements ReferenceSystemUsingIdentifiers {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = 5353942546043471933L;
/**
* Property used to characterize the spatial reference system.
*
* @see #getTheme()
*/
private final InternationalString theme;
/**
* Authority with overall responsibility for the spatial reference system.
*
* @see #getOverallOwner()
*/
private final Party overallOwner;
/**
* Description of location type(s) in the spatial reference system.
* This collection shall be unmodifiable.
*
* @see #getLocationTypes()
*/
private final List<LocationType> locationTypes;
/**
* Creates a reference system from the given properties.
* The properties given in argument follow the same rules than for the
* {@linkplain AbstractReferenceSystem#AbstractReferenceSystem(Map) super-class constructor}.
* The following table is a reminder of main (not all) properties:
*
* <table class="sis">
* <caption>Recognized properties (non exhaustive list)</caption>
* <tr>
* <th>Property name</th>
* <th>Value type</th>
* <th>Returned by</th>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers#THEME_KEY}</td>
* <td>{@link String} or {@link InternationalString}</td>
* <td>{@link #getTheme()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.gazetteer.ReferenceSystemUsingIdentifiers#OVERALL_OWNER_KEY}</td>
* <td>{@link Party}</td>
* <td>{@link #getOverallOwner()}</td>
* </tr>
* <tr>
* <th colspan="3" class="hsep">Defined in parent class (reminder)</th>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
* <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
* <td>{@link #getName()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
* <td>{@link org.opengis.util.GenericName} or {@link CharSequence} (optionally as array)</td>
* <td>{@link #getAlias()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
* <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
* <td>{@link #getIdentifiers()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getRemarks()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.ReferenceSystem#DOMAIN_OF_VALIDITY_KEY}</td>
* <td>{@link org.opengis.metadata.extent.Extent}</td>
* <td>{@link #getDomainOfValidity()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.ReferenceSystem#SCOPE_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getScope()}</td>
* </tr>
* </table>
*
* This constructor copies the given {@link LocationType} instances as per
* {@link ModifiableLocationType#snapshot(ReferenceSystemUsingIdentifiers, LocationType...)}.
* Changes in the given location types after construction will not affect this {@code ReferencingByIdentifiers}.
*
* @param properties the properties to be given to the reference system.
* @param types description of location type(s) in the spatial reference system.
*/
@SuppressWarnings("ThisEscapedInObjectConstruction")
public ReferencingByIdentifiers(final Map<String,?> properties, final LocationType... types) {
super(properties);
theme = Types.toInternationalString(properties, THEME_KEY);
overallOwner = Containers.property(properties, OVERALL_OWNER_KEY, Party.class);
/*
* Having the 'this' reference escaped in object construction should not be an issue here because
* we invoke package-private method in such a way that if an exception is thrown, the whole tree
* (with all 'this' references) will be discarded.
*/
locationTypes = AbstractLocationType.snapshot(this, types);
}
/**
* Convenience method for helping subclasses to build their argument for the constructor.
* The returned properties have the domain of validity set to the whole word and the theme to "mapping".
*
* @param name the reference system name as an {@link org.opengis.metadata.Identifier} or a {@link String}.
* @param party the overall owner, or {@code null} if none.
*/
static Map<String,Object> properties(final Object name, final Party party) {
final Map<String,Object> properties = new HashMap<>(6);
properties.put(NAME_KEY, name);
properties.put(DOMAIN_OF_VALIDITY_KEY, Extents.WORLD);
properties.put(THEME_KEY, Vocabulary.formatInternational(Vocabulary.Keys.Mapping));
properties.put(OVERALL_OWNER_KEY, party);
return properties;
}
/**
* Returns the GeoAPI interface implemented by this class.
* The default implementation returns {@code ReferenceSystemUsingIdentifiers.class}.
*
* @return the GeoAPI interface implemented by this class.
*/
@Override
public Class<? extends ReferenceSystemUsingIdentifiers> getInterface() {
return ReferenceSystemUsingIdentifiers.class;
}
/**
* Property used to characterize the spatial reference system.
*
* @return property used to characterize the spatial reference system.
*
* @see ModifiableLocationType#getTheme()
*/
@Override
public InternationalString getTheme() {
return theme;
}
/**
* Authority with overall responsibility for the spatial reference system.
*
* @return authority with overall responsibility for the spatial reference system.
*
* @see ModifiableLocationType#getOwner()
* @see AbstractLocation#getAdministrator()
*/
@Override
public Party getOverallOwner() {
return overallOwner;
}
/**
* Description of location type(s) in the spatial reference system.
* The collection returned by this method is unmodifiable.
*
* @return description of location type(s) in the spatial reference system.
*
* @see ModifiableLocationType#getReferenceSystem()
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField") // Because the collection is unmodifiable.
public List<? extends LocationType> getLocationTypes() {
return locationTypes;
}
/**
* Returns the first location type.
*/
final LocationType rootType() {
return locationTypes.get(0);
}
/**
* Compares this reference system with the specified object for equality.
* If the {@code mode} argument value is {@link ComparisonMode#STRICT STRICT} or
* {@link ComparisonMode#BY_CONTRACT BY_CONTRACT}, then all available properties are
* compared including the {@linkplain #getTheme() theme} and
* the {@linkplain #getOverallOwner() overall owner}.
*
* @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 location identifications.
* @return {@code true} if both objects are equal.
*/
@Override
@SuppressWarnings("fallthrough")
public boolean equals(final Object object, final ComparisonMode mode) {
if (!super.equals(object, mode)) {
return false;
}
switch (mode) {
case STRICT: {
final ReferencingByIdentifiers that = (ReferencingByIdentifiers) object;
return Objects.equals(theme, that.theme) &&
Objects.equals(overallOwner, that.overallOwner) &&
locationTypes.equals(that.locationTypes);
}
case BY_CONTRACT: {
final ReferenceSystemUsingIdentifiers that = (ReferenceSystemUsingIdentifiers) object;
if (!Utilities.deepEquals(getTheme(), that.getTheme(), mode) ||
!Utilities.deepEquals(getOverallOwner(), that.getOverallOwner(), mode))
{
return false;
}
// Fall through
}
default: {
// Theme and owner are metadata, so they can be ignored.
final ReferenceSystemUsingIdentifiers that = (ReferenceSystemUsingIdentifiers) object;
return Utilities.deepEquals(getLocationTypes(), that.getLocationTypes(), mode);
}
}
}
/**
* Invoked by {@code hashCode()} for computing the hash code when first needed.
* See {@link org.apache.sis.referencing.AbstractIdentifiedObject#computeHashCode()}
* for more information.
*
* @return the hash code value. This value may change in any future Apache SIS version.
*/
@Override
protected long computeHashCode() {
return super.computeHashCode() + Objects.hash(theme, overallOwner, locationTypes);
}
/**
* Formats a pseudo-<cite>Well Known Text</cite> (WKT) representation for this object.
* The format produced by this method is non-standard and may change in any future Apache SIS version.
*
* @param formatter the formatter where to format the inner content of this pseudo-WKT element.
* @return an arbitrary keyword for the pseudo-WKT element.
*/
@Debug
@Override
protected String formatTo(final Formatter formatter) {
WKTUtilities.appendName(this, formatter, ElementKind.NAME);
if (theme != null) {
formatter.newLine();
formatter.append(new SubElement("Theme", theme));
}
if (overallOwner != null) {
formatter.newLine();
formatter.append(new SubElement("Owner", overallOwner.getName()));
}
for (final LocationType type : locationTypes) {
formatter.newLine();
formatter.append(new SubElement("LocationType", type.getName()));
}
return "ReferenceSystemUsingIdentifiers";
}
/**
* A sub-element inside the pseudo-WKT.
*/
private static final class SubElement extends FormattableObject {
/** The pseudo-WKT name of the element to format. */
private final String name;
/** The value of the element to format. */
private final InternationalString value;
/** Creates a new citation with the given value. */
SubElement(final String name, final InternationalString value) {
this.name = name;
this.value = value;
}
/** Formats the sub-element. */
@Override
protected String formatTo(final Formatter formatter) {
formatter.append(value != null ? value.toString(formatter.getLocale()) : null, null);
return name;
}
}
}