blob: 2f9508a9ff13f001f5100f32579293c65b81b387 [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.Collection;
import java.util.Collections;
import java.io.Serializable;
import org.opengis.util.InternationalString;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.extent.GeographicExtent;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.metadata.ModifiableMetadata;
import org.apache.sis.metadata.MetadataCopier;
import org.apache.sis.util.ArgumentChecks;
// Branch-dependent imports
import org.apache.sis.metadata.iso.citation.AbstractParty;
/**
* Unmodifiable description of a location created as a snapshot of another {@code LocationType} instance
* at {@link ReferencingByIdentifiers} construction time. This instance will be set a different reference
* system than the original location type.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
* @since 0.8
* @module
*/
final class FinalLocationType extends AbstractLocationType implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 9032473745502779734L;
/**
* Name of the location type.
*/
private final InternationalString name;
/**
* Property used as the defining characteristic of the location type.
*/
private final InternationalString theme;
/**
* Method(s) of uniquely identifying location instances.
* This list is unmodifiable.
*/
private final List<InternationalString> identifications;
/**
* The way in which location instances are defined.
*/
private final InternationalString definition;
/**
* The reference system that comprises this location type.
*/
private final ReferencingByIdentifiers referenceSystem;
/**
* Geographic area within which the location type occurs.
*/
private final GeographicExtent territoryOfUse;
/**
* Name of organization or class of organization able to create and destroy location instances.
*/
private final AbstractParty owner;
/**
* Parent location types (location types of which this location type is a sub-division).
* This list is unmodifiable.
*/
private final List<AbstractLocationType> parents;
/**
* Child location types (location types which sub-divides this location type).
* This list is unmodifiable.
*/
final List<AbstractLocationType> children;
/**
* Creates a copy of the given location type with the reference system set to the given value.
*
* @param source the location type to copy.
* @param rs the reference system that comprises this location type.
* @param existing other {@code FinalLocationType} instances created before this one.
*/
@SuppressWarnings("ThisEscapedInObjectConstruction")
private FinalLocationType(final AbstractLocationType source, final ReferencingByIdentifiers rs,
final Map<AbstractLocationType, FinalLocationType> existing)
{
/*
* Put 'this' in the map at the beginning in case the parents and children contain cyclic references.
* Cyclic references are not allowed if the source are ModifiableLocationType, but the user could have
* given its own implementation. Having the 'this' reference escaped in object construction should not
* be an issue here because this is a private constructor, and we use it in such a way that if an
* exception is thrown, the whole tree (with all 'this' references) will be discarded.
*/
existing.put(source, this);
/*
* For the following properties, we will fallback on the given reference system if the property value
* from the source location type is null. We do that because those properties are mandatory according
* ISO 19112 and it happen quite often that they have the same value in the location type than in the
* reference system.
*/
InternationalString theme;
GeographicExtent territoryOfUse;
AbstractParty owner;
/*
* Copy the value from the source location type, make them unmodifiable,
* fallback on the ReferenceSystemUsingIdentifiers if necessary.
*/
name = source.getName();
theme = source.getTheme();
identifications = snapshot(source.getIdentifications());
definition = source.getDefinition();
territoryOfUse = (GeographicExtent) unmodifiable(source.getTerritoryOfUse());
owner = (AbstractParty) unmodifiable(source.getOwner());
parents = snapshot(source.getParents(), rs, existing);
children = snapshot(source.getChildren(), rs, existing);
referenceSystem = rs;
if (rs != null) {
if (theme == null) theme = rs.getTheme();
if (owner == null) owner = rs.getOverallOwner();
if (territoryOfUse == null) {
final Extent domainOfValidity = rs.getDomainOfValidity();
if (domainOfValidity instanceof GeographicExtent) {
territoryOfUse = (GeographicExtent) domainOfValidity;
}
}
}
this.theme = theme;
this.territoryOfUse = territoryOfUse;
this.owner = owner;
}
/**
* Creates a snapshot of the given location types. This method returns a new collection within which
* all elements are snapshots (as {@code FinalLocationType} instances) of the given location types,
* except the reference system which is set to the given value.
*
* @param rs the reference system to assign to the new location types.
* @param existing an initially empty identity hash map for internal usage by this method.
*/
static List<AbstractLocationType> snapshot(final Collection<? extends AbstractLocationType> types,
final ReferencingByIdentifiers rs, final Map<AbstractLocationType, FinalLocationType> existing)
{
final AbstractLocationType[] array = types.toArray(new AbstractLocationType[types.size()]);
for (int i=0; i < array.length; i++) {
final AbstractLocationType source = array[i];
ArgumentChecks.ensureNonNullElement("types", i, source);
FinalLocationType copy = existing.get(source);
if (copy == null) {
copy = new FinalLocationType(source, rs, existing);
}
array[i] = copy;
}
switch (array.length) {
case 0: return Collections.emptyList();
case 1: return Collections.singletonList(array[0]);
default: return UnmodifiableArrayList.wrap(array);
}
}
/**
* Returns the given collection as an unmodifiable list.
*/
@SuppressWarnings("unchecked")
private static List<InternationalString> snapshot(final Collection<? extends InternationalString> c) {
if (c instanceof UnmodifiableArrayList<?>) {
return (List<InternationalString>) c; // Unsafe cast okay because we allow only read operations.
} else {
return UnmodifiableArrayList.wrap(c.toArray(new InternationalString[c.size()]));
}
}
/**
* Returns an unmodifiable copy of the given metadata, if necessary and possible.
*
* @param metadata the metadata object to eventually copy, or {@code null}.
* @return an unmodifiable copy of the given metadata object, or {@code null} if the given argument is {@code null}.
*/
private static Object unmodifiable(Object metadata) {
if (metadata instanceof ModifiableMetadata) {
metadata = MetadataCopier.forModifiable(((ModifiableMetadata) metadata).getStandard()).copy(metadata);
((ModifiableMetadata) metadata).transitionTo(ModifiableMetadata.State.FINAL);
}
return metadata;
}
/**
* Returns the name of the location type.
*
* <div class="note"><b>Examples:</b>
* “administrative area”, “town”, “locality”, “street”, “property”.</div>
*
* @return name of the location type.
*/
@Override
public InternationalString getName() {
return name;
}
/**
* Returns the property used as the defining characteristic of the location type.
*
* <div class="note"><b>Examples:</b>
* <cite>“local administration”</cite> for administrative areas,
* <cite>“built environment”</cite> for towns or properties,
* <cite>“access”</cite> for streets,
* <cite>“electoral”</cite>,
* <cite>“postal”</cite>.</div>
*
* @return property used as the defining characteristic of the location type.
*
* @see ReferencingByIdentifiers#getTheme()
*/
@Override
public InternationalString getTheme() {
return theme;
}
/**
* Returns the method(s) of uniquely identifying location instances.
*
* <div class="note"><b>Examples:</b>
* “name”, “code”, “unique street reference number”, “geographic address”.</div>
*
* @return method(s) of uniquely identifying location instances.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField") // Because unmodifiable
public Collection<InternationalString> getIdentifications() {
return identifications;
}
/**
* Returns the way in which location instances are defined.
*
* @return the way in which location instances are defined.
*/
@Override
public InternationalString getDefinition() {
return definition;
}
/**
* Returns the geographic area within which the location type occurs.
*
* <div class="note"><b>Examples:</b>
* the geographic domain for a location type “rivers” might be “North America”.</div>
*
* @return geographic area within which the location type occurs.
*/
@Override
public GeographicExtent getTerritoryOfUse() {
return territoryOfUse;
}
/**
* Returns the reference system that comprises this location type.
*
* @return the reference system that comprises this location type.
*/
@Override
public ReferencingByIdentifiers getReferenceSystem() {
return referenceSystem;
}
/**
* Returns the name of organization or class of organization able to create and destroy location instances.
*
* @return organization or class of organization able to create and destroy location instances.
*/
@Override
public AbstractParty getOwner() {
return owner;
}
/**
* Returns the parent location types (location types of which this location type is a sub-division).
* A location type can have more than one possible parent. For example the parent of a location type named
* <cite>“street”</cite> could be <cite>“locality”</cite>, <cite>“town”</cite> or <cite>“administrative area”</cite>.
*
* @return parent location types, or an empty collection if none.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField") // Because unmodifiable
public Collection<AbstractLocationType> getParents() {
return parents;
}
/**
* Returns the child location types (location types which sub-divides this location type).
*
* @return child location types, or an empty collection if none.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField") // Because unmodifiable
public Collection<AbstractLocationType> getChildren() {
return children;
}
}