/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2004-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.
*/
package org.geotoolkit.referencing.factory.wkt;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.Collection;
import java.util.Collections;
import org.opengis.util.ScopedName;
import org.opengis.util.GenericName;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.metadata.Identifier;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.io.wkt.WKTFormat;
import org.apache.sis.referencing.NamedIdentifier;
import org.geotoolkit.util.collection.DerivedSet;
import org.apache.sis.util.iso.SimpleInternationalString;
import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.geotoolkit.referencing.factory.DirectAuthorityFactory;
import org.geotoolkit.resources.Vocabulary;
import org.geotoolkit.resources.Errors;
import org.apache.sis.referencing.factory.GeodeticObjectFactory;
import org.apache.sis.util.ArraysExt;
/**
* A CRS Authority Factory that manages object creation by parsing <cite>Well Known Text</cite>
* (WKT) strings. The strings may be loaded from property files or be queried in a database (for
* example the {@code "spatial_ref_sys"} table in a PostGIS database).
* <p>
* This base implementation expects a map of (<var>code</var>, <var>WKT</var>) entries, where the
* authority codes are the keys and WKT strings are the values. If the map is backed by a store
* which may throw checked exceptions (for example a connection to a PostGIS database), then it
* shall wrap the checked exceptions in {@link BackingStoreException}s.
*
* {@section Declaring more than one authority}
* There is usually only one authority for a given instance of {@code WKTParsingAuthorityFactory},
* but more authorities can be given to the constructor if the CRS objects to create should have
* more than one {@linkplain CoordinateReferenceSystem#getIdentifiers identifier}, each with the
* same code but different namespace. For example a
* {@linkplain org.geotoolkit.referencing.factory.epsg.EsriExtension factory for CRS defined
* by ESRI} uses the {@code "ESRI"} namespace, but also the {@code "EPSG"} namespace because
* those CRS are used as extension of the EPSG database. Consequently the same CRS can be
* identified as both {@code "ESRI:53001"} and {@code "EPSG:53001"}, where {@code "53001"}
* is a unused code in the official EPSG database.
*
* {@section Caching of CRS objects}
* This factory doesn't cache any result. Any call to a {@code createFoo} method
* will trig a new WKT parsing. For adding caching service, this factory should
* be wrapped in {@link org.geotoolkit.referencing.factory.CachingAuthorityFactory}.
*
* @author Jody Garnett (Refractions)
* @author Rueben Schulz (UBC)
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.14
*
* @since 3.10 (derived from 3.00)
* @module
*/
public class WKTParsingAuthorityFactory extends DirectAuthorityFactory {
/**
* The authority for this factory. Will be computed by
* {@link #getAuthority()} when first needed.
*/
Citation authority;
/**
* The authorities for this factory, usually as an array of length 1.
*/
Citation[] authorities;
/**
* The properties object for our properties file. Keys are the authority
* code for a coordinate reference system and the associated value is a
* WKT string for the CRS.
*/
final Map<String,String> definitions;
/**
* An unmodifiable view of the authority keys.
* Will be created when first needed.
*/
private transient Set<String> codes;
/**
* Views of {@link #codes} for different types. Views will be constructed only when first
* needed. View are always up to date even if entries are added or removed in the
* {@linkplain #definitions} map.
*/
private transient Map<Class<? extends IdentifiedObject>, Set<String>> filteredCodes;
/**
* A WKT parser. Will be created when first needed.
*/
private transient Parser parser;
/**
* Creates a factory for the specified authorities using the definitions in the given map.
* There is usually only one authority, but more can be given when the objects to create
* should have more than one {@linkplain CoordinateReferenceSystem#getIdentifiers identifier},
* each with the same code but different namespace. See the <a href="#skip-navbar_top">class
* javadoc</a> for more details.
*
* @param userHints
* An optional set of hints, or {@code null} for the default ones.
* @param definitions
* The object definitions as a map with authority codes as keys and WKT strings as values.
* @param authorities
* The organizations or parties responsible for definition and maintenance of the database.
*/
public WKTParsingAuthorityFactory(final Hints userHints, final Map<String,String> definitions, Citation... authorities) {
this(userHints, definitions);
ensureNonNull("authorities", authorities);
if (authorities.length == 0) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArray));
}
this.authorities = authorities = authorities.clone();
for (final Citation authority : authorities) {
ensureNonNull("authority", authority);
}
}
/**
* Creates a factory without authorities. This is subclass responsibility to initialize
* the {@link #authorities} field when first needed.
*/
WKTParsingAuthorityFactory(final Hints userHints, final Map<String,String> definitions) {
super(userHints);
ensureNonNull("definitions", definitions);
this.definitions = definitions;
}
/**
* Returns all authority names. The default implementation returns the authority given
* to the constructor. This method <strong>must</strong> be overridden if the subclass
* used the constructor without authority list. This method is not allowed to return
* {@code null}.
* <p>
* The returned array shall contain the value returned by
* {@linkplain #getPrimaryKeyAuthority()}, if non-null.
*
* @return All authorities known to this factory. <strong>Do not modify</strong>,
* since this method returns a direct reference to the internal array.
*/
Citation[] getAuthorities() {
if (authorities == null) {
throw new IllegalStateException(Errors.format(Errors.Keys.DisposedFactory));
}
return authorities;
}
/**
* Returns the authority. The default implementation returns the first citation given to
* the constructor, or a modified version of that citation if many of them were given to
* the constructor. In the later case, the returned citation will have a set of
* {@linkplain Citation#getIdentifiers() identifiers} which is the union of identifiers
* of all citations given to the constructor.
*/
@Override
public synchronized Citation getAuthority() {
if (authority == null) {
Citation[] authorities = getAuthorities();
final Citation pkAuthority = getPrimaryKeyAuthority();
if (pkAuthority != null) {
for (int i=0; i<authorities.length; i++) {
if (pkAuthority.equals(authorities[i])) {
authorities = ArraysExt.remove(authorities, i, 1);
break;
}
}
}
switch (authorities.length) {
case 0: authority = org.geotoolkit.metadata.Citations.UNKNOWN; break;
case 1: authority = authorities[0]; break;
default: {
final DefaultCitation c = new DefaultCitation(authorities[0]);
final Collection<Identifier> identifiers = c.getIdentifiers();
for (int i=1; i<authorities.length; i++) {
identifiers.addAll(authorities[i].getIdentifiers());
}
c.freeze();
authority = c;
break;
}
}
}
return authority;
}
/**
* Returns the authority which is responsible for the maintenance of the primary keys,
* or {@code null} if none. The returned value (if non-null) shall also be one of the
* elements returned by {@link #getAuthorities()}.
* <p>
* The default implementation returns {@code null} in all cases. This method is overridden
* and made public by implementations that are backed by a SQL database. For example the
* {@link DirectPostgisFactory} overrides this method in order to return
* {@link Citations#POSTGIS}.
*
* @return The authority which is reponsible for the maintenance of primary keys,
* or {@code null} if none.
*
* @see #getPrimaryKey(Class, String)
*/
Citation getPrimaryKeyAuthority() {
return null;
}
/**
* Returns the set of authority codes of the given type. The {@code type} argument specifies
* the base class. For example if this factory is an instance of {@link CRSAuthorityFactory},
* then:
* <p>
* <ul>
* <li>{@code CoordinateReferenceSystem.class} asks for all authority codes accepted by
* {@link #createGeographicCRS createGeographicCRS},
* {@link #createProjectedCRS createProjectedCRS},
* {@link #createVerticalCRS createVerticalCRS},
* {@link #createTemporalCRS createTemporalCRS}
* and any other method returning a sub-type of {@code CoordinateReferenceSystem}.</li>
* <li>{@code ProjectedCRS.class} asks only for authority codes accepted by
* {@link #createProjectedCRS createProjectedCRS}.</li>
* </ul>
* <p>
* The default implementation filters the set of codes based on the
* {@code "PROJCS"} and {@code "GEOGCS"} at the start of the WKT strings.
*
* @param type The spatial reference objects type (can be {@code IdentifiedObject.class}).
* @return The set of authority codes for spatial reference objects of the given type.
* If this factory doesn't contains any object of the given type, then this method
* returns an empty set.
* @throws FactoryException if access to the underlying database failed.
*/
@Override
public synchronized Set<String> getAuthorityCodes(final Class<? extends IdentifiedObject> type)
throws FactoryException
{
if (codes == null) {
codes = Collections.unmodifiableSet(definitions.keySet());
}
if (type == null || type.isAssignableFrom(IdentifiedObject.class)) {
return codes;
}
if (filteredCodes == null) {
filteredCodes = new HashMap<>();
}
Set<String> filtered = filteredCodes.get(type);
if (filtered == null) {
filtered = new Codes(type);
filteredCodes.put(type, filtered);
}
return filtered;
}
/**
* The set of codes for a specific type of CRS. This set filters the codes set in the
* enclosing {@link WKTParsingAuthorityFactory} in order to keep only the codes for the
* specified type. Filtering is performed on the fly. Consequently, this set is cheap
* if the user just wants to check for the existence of a particular code.
*/
private final class Codes extends DerivedSet<String, String> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 2681905294171687900L;
/**
* The spatial reference objects type.
*/
private final Class<? extends IdentifiedObject> type;
/**
* Constructs a set of codes for the specified type.
*/
public Codes(final Class<? extends IdentifiedObject> type) {
super(definitions.keySet(), String.class);
this.type = type;
}
/**
* Returns the code if the associated key is of the expected type, or {@code null}
* otherwise.
*/
@Override
protected String baseToDerived(final String key) {
final String wkt;
try {
wkt = definitions.get(getPrimaryKey(type, key));
} catch (FactoryException e) {
throw new BackingStoreException(e);
}
final int length = wkt.length();
int i=0; while (i<length && Character.isJavaIdentifierPart(wkt.charAt(i))) i++;
Class<?> candidate = WKTFormat.getClassOf(wkt.substring(0,i));
if (candidate == null) {
candidate = IdentifiedObject.class;
}
return type.isAssignableFrom(candidate) ? key : null;
}
/**
* Transforms a value in this set to a value in the base set.
*/
@Override
protected String derivedToBase(final String element) {
return element;
}
}
/**
* Returns the Well Know Text from a code.
*
* @param type The type of the object to be created.
* @param code Value allocated by authority.
* @param parser If non-null, code and primary key will be stored in this parser.
* @return The Well Know Text (WKT) for the specified code.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
*/
private String getWKT(final Class<? extends IdentifiedObject> type, final String code, final Parser parser)
throws FactoryException
{
assert Thread.holdsLock(this);
ensureNonNull("code", code);
final Comparable<?> pk;
final String wkt;
try {
pk = getPrimaryKey(type, code);
wkt = definitions.get(pk);
} catch (BackingStoreException e) {
final Throwable cause = e.getCause();
if (cause instanceof FactoryException) {
throw (FactoryException) cause;
}
throw databaseFailure(type, code, cause);
}
if (wkt == null) {
throw noSuchAuthorityCode(type, code);
}
if (parser != null) {
parser.code = code;
parser.primaryKey = pk;
}
return wkt.trim();
}
/**
* Wraps an {@link Exception} into a {@link FactoryException}.
* The given exception is typically a {@link java.sql.SQLException}.
*
* @param type The type of the object being created, or {@code null} if unknown.
* @param code The code of the object being created, or {@code null} if unknown.
* @param exception The exception that occurred while querying the backing store.
* @return A factory exception wrapping the given exception.
*/
static FactoryException databaseFailure(final Class<?> type, final String code, final Throwable exception) {
String message = exception.getLocalizedMessage();
if (code != null) {
String typeName;
if (type != null) {
typeName = type.getSimpleName();
} else {
typeName = Vocabulary.format(Vocabulary.Keys.Unknown);
}
message = Errors.format(Errors.Keys.DatabaseFailure_2, typeName, code) + ": " + message;
}
return new FactoryException(message, exception);
}
/**
* Gets a description of the object corresponding to a code.
*
* @param code Value allocated by authority.
* @return A description of the object, or {@code null} if the object
* corresponding to the specified {@code code} has no description.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the query failed for some other reason.
*/
@Override
public InternationalString getDescriptionText(final String code)
throws NoSuchAuthorityCodeException, FactoryException
{
final String wkt;
synchronized (this) {
wkt = getWKT(IdentifiedObject.class, code, null);
}
int start = wkt.indexOf('"');
if (start >= 0) {
final int end = wkt.indexOf('"', ++start);
if (end >= 0) {
return new SimpleInternationalString(wkt.substring(start, end).trim());
}
}
return null;
}
/**
* Returns the parser.
*/
private Parser getParser() {
if (parser == null) {
parser = new Parser();
}
return parser;
}
/**
* Returns an arbitrary object from a code. If the object type is know at compile time,
* it is recommended to invoke the most precise method instead of this one.
*
* @param code Value allocated by authority.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the object creation failed for some other reason.
*/
@Override
public synchronized IdentifiedObject createObject(final String code)
throws NoSuchAuthorityCodeException, FactoryException
{
return createCoordinateReferenceSystem(code);
}
/**
* Returns a coordinate reference system from a code. If the object type is know at compile
* time, it is recommended to invoke the most precise method instead of this one.
*
* @param code Value allocated by authority.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the object creation failed for some other reason.
*/
@Override
public synchronized CoordinateReferenceSystem createCoordinateReferenceSystem(final String code)
throws NoSuchAuthorityCodeException, FactoryException
{
final Parser parser = getParser();
final String wkt = getWKT(CoordinateReferenceSystem.class, code, parser);
return parser.createFromWKT(wkt);
}
/**
* Trims the authority scope, if presents. If more than one authority were given at
* {@linkplain #WKTParsingAuthorityFactory construction time}, then any of them may
* appears as the scope in the supplied code.
*
* @param code The code to trim.
* @return The code without the authority scope.
*/
@Override
protected String trimAuthority(String code) {
code = code.trim();
final GenericName name = nameFactory.parseGenericName(null, code);
if (name instanceof ScopedName) {
final GenericName scope = ((ScopedName) name).path();
final String candidate = scope.toString();
final Citation[] authorities = getAuthorities();
for (int i=0; i<authorities.length; i++) {
if (Citations.identifierMatches(authorities[i], candidate)) {
return name.tip().toString().trim();
}
}
}
return code;
}
/**
* Returns the primary key for the specified authority code. The default implementation
* returns the given code with the "authority" part trimmed. This method is overridden by
* {@link DirectPostgisFactory}. Note that {@code DirectPostgisFactory} will trim
* the authority itself, because it needs the authority part of the code.
*
* @param type The type of the object being created.
* @param code The authority code to convert to primary key value.
* @return The primary key for the supplied code.
* @throws FactoryException if an error occurred while querying the database.
*
* @see #getPrimaryKeyAuthority()
*/
Comparable<?> getPrimaryKey(Class<? extends IdentifiedObject> type, String code) throws FactoryException {
return trimAuthority(code);
}
/**
* The WKT parser for this authority factory. This parser add automatically the authority
* code if it was not explicitly specified in the WKT.
*/
private final class Parser extends GeodeticObjectFactory {
/**
* The authority code for the WKT to be parsed. This is the code provided by the user to
* the {@link WKTParsingAuthorityFactory#createCoordinateReferenceSystem(String)} method.
*/
String code;
/**
* The primary key corresponding to the code. For {@link PropertyAuthorityFactory},
* this is equals to {@link #code}. For {@link DirectPostgisFactory}, this is an
* {@link Integer} obtained from the {@code srid} column.
*/
Comparable<?> primaryKey;
/**
* Creates the parser.
*/
public Parser() {
}
/**
* Adds the authority code to the specified properties, if not already present.
* In addition, if a primary key was used for fetching the CRS, adds also that
* primary key to the list of identifiers.
*/
@Override
protected Map<String,?> complete(Map<String,?> properties) {
final Citation pkAuthority = getPrimaryKeyAuthority();
final Citation[] authorities;
final Identifier[] identifiers;
final Identifier declaredIdentifier =
(Identifier) properties.get(IdentifiedObject.IDENTIFIERS_KEY);
/*
* If the WKT does not declare explicitly an authority code, we will adds an
* identifier for all authorities given at construction time. Otherwise we
* will add an identifier only for the primary key (if there is one).
*/
if (declaredIdentifier == null) {
authorities = getAuthorities();
identifiers = new NamedIdentifier[authorities.length];
} else if (pkAuthority != null) {
authorities = new Citation[] {pkAuthority};
identifiers = new Identifier[2];
identifiers[0] = declaredIdentifier;
} else {
authorities = null;
identifiers = null;
}
/*
* Now create an identifier for each authority in the 'authorities' array.
* Note that the 'identifiers' array may be longer, in which case the first
* elements are assumed already initialized.
*/
if (authorities != null) {
String trimmedCode = null, pkCode = null;
final int offset = identifiers.length - authorities.length;
for (int i=0; i<authorities.length; i++) {
final String ci;
final Citation authority = authorities[i];
if (pkAuthority == null || pkAuthority.equals(authority)) {
if (pkCode == null) {
pkCode = primaryKey.toString();
}
ci = pkCode;
} else {
if (trimmedCode == null) {
trimmedCode = trimAuthority(code);
}
ci = trimmedCode;
}
identifiers[i + offset] = new NamedIdentifier(authority, ci);
}
final Map<String,Object> modified = new HashMap<>(properties);
modified.put(IdentifiedObject.IDENTIFIERS_KEY, identifiers);
properties = modified;
}
return super.complete(properties);
}
}
/**
* Releases resources immediately instead of waiting for the garbage collector.
*/
@Override
protected synchronized void dispose(final boolean shutdown) {
authority = null;
authorities = null;
codes = null;
filteredCodes = null;
parser = null;
super.dispose(shutdown);
}
}