/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-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.
*/
package org.geotools.referencing.factory;
import java.util.*;
import org.opengis.referencing.*;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;
import org.opengis.util.InternationalString;
import org.opengis.metadata.citation.Citation;
import org.geotools.factory.Factory;
import org.geotools.factory.FactoryRegistryException;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.util.GenericName;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;
/**
* An authority factory that delegates the object creation to an other factory determined from the
* authority name in the code. This factory requires that every codes given to a {@code createFoo}
* method are prefixed by the authority name, for example {@code "EPSG:4326"}. This is different
* from using a factory from a known authority, in which case the authority part was optional (for
* example when using the {@linkplain org.geotools.referencing.factory.epsg EPSG authority factory},
* the {@code "EPSG:"} part in {@code "EPSG:4326"} is optional).
*
* This class parses the authority name and delegates the work the corresponding factory. For
* example if any {@code createFoo(...)} method in this class is invoked with a code starting
* by {@code "EPSG:"}, then this class delegates the object creation to one of the authority
* factories provided to the constructor.
*
* This class is not registered in {@link ReferencingFactoryFinder}, because it is not a real
* authority factory. There is not a single authority name associated to this factory, but rather
* a set of names determined from all available authority factories.
*
* @since 2.4
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class ManyAuthoritiesFactory extends AuthorityFactoryAdapter implements CRSAuthorityFactory,
CSAuthorityFactory, DatumAuthorityFactory, CoordinateOperationAuthorityFactory
{
/**
* The types to be recognized for the {@code factories} argument in
* constructors. Must be consistent with the types expected by the
* {@link AllAuthoritiesFactory#fromFactoryRegistry(String, Class)} method.
*/
@SuppressWarnings("unchecked")
private static final Class extends AuthorityFactory>[] FACTORY_TYPES = new Class[] {
CRSAuthorityFactory.class,
DatumAuthorityFactory.class,
CSAuthorityFactory.class,
CoordinateOperationAuthorityFactory.class
};
/**
* The types created by {@link #FACTORY_TYPES}. For each type {@code OBJECT_TYPES[i]},
* the factory to be used must be {@code FACTORY_TYPES[i]}.
*/
@SuppressWarnings("unchecked")
private static final Class extends IdentifiedObject>[] OBJECT_TYPES = new Class[] {
CoordinateReferenceSystem.class,
Datum.class,
CoordinateSystem.class,
CoordinateOperation.class
};
/**
* The set of user-specified factories, or {@code null} if none.
* This field should be modified by {@link #setFactories} only.
*/
private Collection factories;
/**
* Guard against infinite recursivity in {@link #getAuthorityCodes}.
*/
private final ThreadLocal inProgress;
/**
* Creates a new factory using the specified set of user factories. Any call to a
* {@code createFoo(code)} method will scan the supplied factories in their iteration
* order. The first factory implementing the appropriate interface and having the expected
* {@linkplain AuthorityFactory#getAuthority authority name} will be used.
*
* If the {@code factories} collection contains more than one factory for the same authority
* and interface, then all additional factories will be {@linkplain FallbackAuthorityFactory
* fallbacks}, to be tried in iteration order only if the first acceptable factory failed to
* create the requested object.
*
* @param factories A set of user-specified factories to try before to delegate
* to {@link GeometryFactoryFinder}.
*/
public ManyAuthoritiesFactory(final Collection extends AuthorityFactory> factories) {
super(NORMAL_PRIORITY);
inProgress = new ThreadLocal();
if (factories!=null && !factories.isEmpty()) {
for (final AuthorityFactory factory : factories) {
if (factory instanceof Factory) {
hints.putAll(((Factory) factory).getImplementationHints());
}
}
this.factories = createFallbacks(factories);
}
}
/**
* Returns the factories. This method should not be public since it returns directly the
* internal instance. This method is to be overriden by {@link AllAuthoritiesFactory} only.
*/
Collection getFactories() {
return factories;
}
/**
* Sets the factories. This method is invoked by the {@link AllAuthoritiesFactory} subclass
* only. No one else should invoke this method, since factories should be immutable.
*/
synchronized final void setFactories(final Collection factories) {
this.factories = createFallbacks(factories);
}
/**
* If more than one factory is found for the same authority and interface,
* then wraps them as a chain of {@link FallbackAuthorityFactory}.
*/
private static Collection createFallbacks(
final Collection extends AuthorityFactory> factories)
{
/*
* 'authorities' Will contains the set of all authorities found without duplicate values
* in the sense of Citations.identifierMatches(...). 'factoriesByAuthority' will contains
* the collection of factories for each authority.
*/
int authorityCount = 0;
final Citation[] authorities = new Citation[factories.size()];
@SuppressWarnings("unchecked")
final List[] factoriesByAuthority = new List[authorities.length];
for (final AuthorityFactory factory : factories) {
/*
* Check if the authority has already been meet previously. If the authority is found
* then 'authorityIndex' is set to its index. Otherwise the new authority is added to
* the 'authorities' list.
*/
Citation authority = factory.getAuthority();
int authorityIndex;
for (authorityIndex=0; authorityIndex list;
if (authorityIndex == authorityCount) {
authorities[authorityCount++] = authority;
factoriesByAuthority[authorityIndex] = list = new ArrayList(4);
} else {
list = factoriesByAuthority[authorityIndex];
}
if (!list.contains(factory)) {
list.add(factory);
}
}
/*
* For each authority, chains the factories into a FallbackAuthorityFactory object.
*/
final ArrayList result = new ArrayList();
final List buffer = new ArrayList(4);
for (int i=0; i list = factoriesByAuthority[i];
while (!list.isEmpty()) {
AuthorityFactory primary = null;
boolean needOtherChains = false;
for (final Iterator it=list.iterator(); it.hasNext();) {
final AuthorityFactory fallback = it.next();
if (primary == null) {
primary = fallback;
} else if (!FallbackAuthorityFactory.chainable(primary, fallback)) {
needOtherChains = true;
continue;
}
buffer.add(fallback);
if (!needOtherChains) {
it.remove();
}
}
result.add(FallbackAuthorityFactory.create(buffer));
buffer.clear();
}
}
result.trimToSize();
return result;
}
/**
* If this factory is a wrapper for the specified factory that do not add any additional
* {@linkplain #getAuthorityCodes authority codes}, returns {@code true}. This method is
* for {@link FallbackAuthorityFactory} internal use only.
*/
@Override
boolean sameAuthorityCodes(final AuthorityFactory factory) {
// We don't want to inherit AuthorityFactoryAdapter implementation here.
return factory == this;
}
/**
* Returns the character separator for the specified code. The default implementation returns
* the {@linkplain GenericName#DEFAULT_SEPARATOR default name separator} {@code ':'}, except
* if the code looks like a URL (e.g. {@code "http://www.opengis.net/"}), in which case this
* method returns {@code '/'}.
*
* In the current implementation, "looks like a URL" means that the first
* non-{@linkplain Character#isLetterOrDigit(char) aplhanumeric} characters
* are {@code "://"}. But this heuristic rule may change in future implementations.
*/
protected char getSeparator(String code) {
code = code.trim();
final int length = code.length();
for (int i=0; iauthority,
* code) pair at the specified index. The default implementation returns
* {@code true} if the first non-whitespace character on the left and right side are
* valid Java identifiers.
*
* The purpose of this method is to avoid considering the {@code "//"} part in
* {@code "http://www.opengis.net/gml/srs/epsg.xml"} as separators. In case of
* failure to parse the code, this restriction will produce and error message
* like "Unknown http://www.opengis.net
authority"
* instead of "Unknown http:
authority".
*
* We may consider to turn this method into a protected one if the users need to override it.
*/
private static boolean canSeparateAt(final String code, final int index) {
char c;
int i = index;
do {
if (--i < 0) {
return false;
}
c = code.charAt(i);
} while (Character.isWhitespace(c));
if (!Character.isJavaIdentifierPart(c)) {
return false;
}
final int length = code.length();
i = index;
do {
if (++i >= length) {
return false;
}
c = code.charAt(i);
} while (Character.isWhitespace(c));
return Character.isJavaIdentifierPart(c);
}
/**
* Returns the vendor responsible for creating this factory implementation.
* The default implementation returns {@linkplain Citations#GEOTOOLS Geotools}.
*/
@Override
public Citation getVendor() {
return Citations.GEOTOOLS;
}
/**
* Returns the organization or party responsible for definition and maintenance of the
* database. The default implementation returns a citation with title "All".
*/
@Override
public Citation getAuthority() {
return ALL;
}
/**
* Returns the authority names of every factories given at construction time.
*/
public Set getAuthorityNames() {
final Set names = new HashSet();
final Collection factories = getFactories();
if (factories != null) {
for (final AuthorityFactory factory : factories) {
names.add(Citations.getIdentifier(factory.getAuthority()));
}
}
return names;
}
/**
* Returns a description of the underlying backing store, or {@code null} if unknow.
*
* @throws FactoryException if a failure occured while fetching the engine description.
*/
@Override
public String getBackingStoreDescription() throws FactoryException {
// We have no authority code, so we can't pick a particular factory.
return null;
}
/**
* Returns the direct dependencies. Current implementation returns the internal structure
* because we know that this package will not modifies it. But if the method become public,
* we will need to returns a unmodifiable view.
*/
@Override
Collection super AuthorityFactory> dependencies() {
return getFactories();
}
/**
* Returns {@code true} if the specified factory should be excluded from the search.
* We exclude adapters around {@link AllAuthoritiesFactory}. This code actually aims
* to exclude {@link URN_AuthorityFactory} and similar adapters around all factories,
* since it leads to duplicated search and innacurate identifier to be returned by
* {@link #findIdentifier}.
*/
private static boolean exclude(final AuthorityFactory factory) {
if (ManyAuthoritiesFactory.class.isInstance(factory)) {
return true;
}
if (factory instanceof AuthorityFactoryAdapter) {
final AuthorityFactoryAdapter adapter = (AuthorityFactoryAdapter) factory;
return exclude(adapter.crsFactory) ||
exclude(adapter.csFactory) ||
exclude(adapter.datumFactory) ||
exclude(adapter.operationFactory);
}
return false;
}
/**
* Same as {@link #fromFactoryRegistry(String, Class)}, but returns every factories
* that fit the given type. The factories are added to the specified set.
*/
final void fromFactoryRegistry(final String authority,
final Class extends AuthorityFactory> type,
final Set addTo)
{
for (int i=0; i T fromFactoryRegistry(final String authority, final Class type)
throws FactoryRegistryException
{
return null;
}
/**
* Searchs for a factory of the given type. This method first search in user-supplied
* factories. If no user factory is found, then this method request for a factory using
* {@link GeometryFactoryFinder}. The authority name is inferred from the specified code.
*
* @param type The interface to be implemented.
* @param code The code of the object to create.
* @return The factory.
* @throws NoSuchAuthorityCodeException if no suitable factory were found.
*/
@Override
final T getAuthorityFactory(final Class type, final String code)
throws NoSuchAuthorityCodeException
{
ensureNonNull("code", code);
String authority = null;
FactoryRegistryException cause = null;
final Collection factories = getFactories();
final char separator = getSeparator(code);
for (int split = code.lastIndexOf(separator); split >= 0;
split = code.lastIndexOf(separator, split-1))
{
if (!canSeparateAt(code, split)) {
continue;
}
/*
* Try all possible authority names, begining with the most specific ones.
* For example if the code is "urn:ogc:def:crs:EPSG:6.8:4326", then we will
* try "urn:ogc:def:crs:EPSG:6.8" first, "urn:ogc:def:crs:EPSG" next, etc.
* until a suitable factory is found (searching into user-supplied factories
* first).
*/
authority = code.substring(0, split).trim();
if (factories != null) {
for (final AuthorityFactory factory : factories) {
if (type.isAssignableFrom(factory.getClass())) {
if (Citations.identifierMatches(factory.getAuthority(), authority)) {
return type.cast(factory);
}
}
}
}
/*
* No suitable user-supplied factory. Now query FactoryFinder.
*/
final AuthorityFactory factory;
try {
factory = fromFactoryRegistry(authority, type);
} catch (FactoryRegistryException exception) {
cause = exception;
continue;
}
if (factory != null) {
return type.cast(factory);
}
}
/*
* No factory found. Creates an error message from the most global authority name
* (for example "urn" if the code was "urn:ogc:def:crs:EPSG:6.8:4326") and the
* corresponding cause. Both the authority and cause may be null if the code didn't
* had any authority part.
*/
throw noSuchAuthority(code, authority, cause);
}
/**
* Formats the exception to be throw when the user asked for a code from an unknown authority.
*
* @param code The code with an unknown authority.
* @param authority The authority, or {@code null} if none.
* @param cause The cause for the exception to be formatted, or {@code null} if none.
* @return The formatted exception to be throw.
*/
private NoSuchAuthorityCodeException noSuchAuthority(final String code, String authority,
final FactoryRegistryException cause)
{
final String message;
if (authority == null) {
authority = Vocabulary.format(VocabularyKeys.UNKNOW);
message = Errors.format(ErrorKeys.MISSING_AUTHORITY_$1, code);
} else {
message = Errors.format(ErrorKeys.UNKNOW_AUTHORITY_$1, authority);
}
final NoSuchAuthorityCodeException exception;
exception = new NoSuchAuthorityCodeException(message, authority, code);
exception.initCause(cause);
return exception;
}
/**
* Returns a generic object authority factory for the specified {@code "AUTHORITY:NUMBER"}
* code.
*
* @param code The code to parse.
* @return The authority factory.
* @throws NoSuchAuthorityCodeException if no authority name has been found.
*/
@Override
protected AuthorityFactory getAuthorityFactory(final String code)
throws NoSuchAuthorityCodeException
{
return getAuthorityFactory(AuthorityFactory.class, code);
}
/**
* Returns the datum authority factory for the specified {@code "AUTHORITY:NUMBER"} code.
*
* @param code The code to parse.
* @return The authority factory.
* @throws NoSuchAuthorityCodeException if no authority name has been found.
*/
@Override
protected DatumAuthorityFactory getDatumAuthorityFactory(final String code)
throws NoSuchAuthorityCodeException
{
return getAuthorityFactory(DatumAuthorityFactory.class, code);
}
/**
* Returns the CS authority factory for the specified {@code "AUTHORITY:NUMBER"} code.
*
* @param code The code to parse.
* @return The authority factory.
* @throws NoSuchAuthorityCodeException if no authority name has been found.
*/
@Override
protected CSAuthorityFactory getCSAuthorityFactory(final String code)
throws NoSuchAuthorityCodeException
{
return getAuthorityFactory(CSAuthorityFactory.class, code);
}
/**
* Returns the CRS authority factory for the specified {@code "AUTHORITY:NUMBER"} code.
*
* @param code The code to parse.
* @return The authority factory.
* @throws NoSuchAuthorityCodeException if no authority name has been found.
*/
@Override
protected CRSAuthorityFactory getCRSAuthorityFactory(final String code)
throws NoSuchAuthorityCodeException
{
return getAuthorityFactory(CRSAuthorityFactory.class, code);
}
/**
* Returns the operation authority factory for the specified {@code "AUTHORITY:NUMBER"} code.
*
* @param code The code to parse.
* @return The authority factory.
* @throws NoSuchAuthorityCodeException if no authority name has been found.
*/
@Override
protected CoordinateOperationAuthorityFactory getCoordinateOperationAuthorityFactory(final String code)
throws NoSuchAuthorityCodeException
{
return getAuthorityFactory(CoordinateOperationAuthorityFactory.class, code);
}
/**
* Returns the set of authority codes of the given type.
*
* @param type The spatial reference objects type (may 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 {@linkplain java.util.Collections#EMPTY_SET empty set}.
* @throws FactoryException if access to the underlying database failed.
*/
@Override
public Set getAuthorityCodes(final Class extends IdentifiedObject> type)
throws FactoryException
{
if (Boolean.TRUE.equals(inProgress.get())) {
/*
* 'getAuthorityCodes' is invoking itself (indirectly). Returns an empty set in order
* to avoid infinite recursivity. Note that the end result (the output of the caller)
* will usually not be empty.
*/
return Collections.emptySet();
}
final Set codes = new LinkedHashSet();
final Set done = new HashSet();
done.add(this); // Safety for avoiding recursive calls.
try {
inProgress.set(Boolean.TRUE);
for (String authority : getAuthorityNames()) {
authority = authority.trim();
final char separator = getSeparator(authority);
/*
* Prepares a buffer with the "AUTHORITY:" part in "AUTHORITY:NUMBER".
* We will reuse this buffer in order to prefix the authority name in
* front of every codes.
*/
final StringBuilder code = new StringBuilder(authority);
int codeBase = code.length();
if (codeBase != 0 && code.charAt(codeBase - 1) != separator) {
code.append(separator);
codeBase = code.length();
}
code.append("all");
final String dummyCode = code.toString();
/*
* Now scan over all factories. We will process a factory only if this particular
* factory has not already been done in a previous iteration (some implementation
* apply to more than one factory).
*/
scanForType: for (int i=0; i factoryType = FACTORY_TYPES[i];
final AuthorityFactory factory;
try {
factory = getAuthorityFactory(factoryType, dummyCode);
} catch (NoSuchAuthorityCodeException e) {
continue;
}
if (!done.add(factory)) {
continue;
}
AuthorityFactory wrapped = factory;
while (wrapped instanceof AuthorityFactoryAdapter) {
final AuthorityFactoryAdapter adapter = (AuthorityFactoryAdapter) wrapped;
try {
wrapped = adapter.getAuthorityFactory(factoryType, dummyCode);
} catch (NoSuchAuthorityCodeException exception) {
/*
* The factory doesn't understand our dummy code. It happen with
* URN_AuthorityFactory, which expect the type ("CRS", etc.) in the URN.
*/
continue scanForType;
}
if (!done.add(wrapped)) {
/*
* Avoid the factories that are wrapper around an other factory already
* done. If we don't do that, we will duplicate the whole set of EPSG
* identifiers (more than 3000 codes) for OrderedAuthorityFactory,
* HTTP_AuthorityFactory, URN_AuthorityFactory, etc.
*/
continue scanForType;
}
}
for (String candidate : factory.getAuthorityCodes(type)) {
candidate = candidate.trim();
if (candidate.length() < codeBase ||
Character.isLetterOrDigit (candidate.charAt(codeBase-1)) ||
!authority.equalsIgnoreCase(candidate.substring(0, codeBase-1)))
{
// Prepend the authority code if it was not already presents.
code.setLength(codeBase);
code.append(candidate);
candidate = code.toString();
}
codes.add(candidate);
}
}
}
} finally {
inProgress.remove();
}
return codes;
}
/**
* 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 FactoryException {
final Set done = new HashSet();
done.add(this); // Safety for avoiding recursive calls.
FactoryException failure = null;
for (int type=0; type done = new HashSet();
done.add(this); // Safety for avoiding recursive calls.
FactoryException failure = null;
for (int type=0; type type)
throws FactoryException
{
return new Finder(this, type);
}
/**
* A {@link IdentifiedObjectFinder} which tests every factories.
*/
static class Finder extends IdentifiedObjectFinder {
/**
* Creates a finder for the specified type.
*/
protected Finder(final ManyAuthoritiesFactory factory,
final Class extends IdentifiedObject> type)
{
super(factory, type);
}
/**
* Returns the user-supplied factories.
*/
final Collection getFactories() {
return ((ManyAuthoritiesFactory) getProxy().getAuthorityFactory()).getFactories();
}
/**
* Returns the next finder in the specified set of factories, or {@code null} if none.
*/
final IdentifiedObjectFinder next(final Iterator it)
throws FactoryException
{
while (it.hasNext()) {
final AuthorityFactory factory = it.next();
if (exclude(factory)) {
continue;
}
if (factory instanceof AbstractAuthorityFactory) {
final IdentifiedObjectFinder finder = ((AbstractAuthorityFactory) factory).
getIdentifiedObjectFinder(getProxy().getType());
if (finder != null) {
finder.setFullScanAllowed(isFullScanAllowed());
return finder;
}
}
}
return null;
}
/**
* Lookups for the specified object.
*/
@Override
public IdentifiedObject find(final IdentifiedObject object) throws FactoryException {
/*
* Try to create from the identifier before to scan over every factories,
* because the identifier may contains the authority name, in which case
* we can pickup directly the right factory instead of trying them all.
*/
IdentifiedObject candidate = createFromIdentifiers(object);
if (candidate != null) {
return candidate;
}
final Collection factories = getFactories();
if (factories != null) {
IdentifiedObjectFinder finder;
final Iterator it = factories.iterator();
while ((finder = next(it)) != null) {
candidate = finder.find(object);
if (candidate != null) {
break;
}
}
}
return candidate;
}
/**
* Returns the identifier of the specified object, or {@code null} if none.
*/
@Override
public String findIdentifier(final IdentifiedObject object) throws FactoryException {
/*
* Try to create from the identifier for the same reason than find(IdentifiedObject).
* Note that we returns directly the primary name; we don't try to locate a name for
* a given authority since the "All" authority do not really exists.
*/
IdentifiedObject candidate = createFromIdentifiers(object);
if (candidate != null) {
return candidate.getName().toString();
}
final Collection factories = getFactories();
if (factories != null) {
IdentifiedObjectFinder finder;
final Iterator it = factories.iterator();
while ((finder = next(it)) != null) {
final String id = finder.findIdentifier(object);
if (id != null) {
return id;
}
}
}
return null;
}
}
}