/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-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. * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. */ package org.geotools.referencing.factory; // J2SE dependencies import java.util.Set; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; // OpenGIS dependencies import org.opengis.util.GenericName; import org.opengis.metadata.Identifier; import org.opengis.metadata.citation.Citation; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.AuthorityFactory; import org.opengis.referencing.FactoryException; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.referencing.NoSuchAuthorityCodeException; // Geotools dependencies import org.geotools.referencing.CRS; import org.geotools.referencing.AbstractIdentifiedObject; import org.geotools.metadata.iso.citation.Citations; import org.geotools.util.logging.Logging; /** * Looks up an object from an {@linkplain AuthorityFactory authority factory} which is * {@linkplain CRS#equalsIgnoreMetadata equals, ignoring metadata}, to the specified * object. The main purpose of this class is to get a fully {@linkplain IdentifiedObject * identified object} from an incomplete one, for example from an object without * {@linkplain IdentifiedObject#getIdentifiers identifiers} or "{@code AUTHORITY[...]}" * element in Well Known Text terminology. * * @since 2.4 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux */ public class IdentifiedObjectFinder { public static final Logger LOGGER = Logging.getLogger("org.geotools.referencing.factory.finder"); /** * The proxy for object creation. */ private AuthorityFactoryProxy proxy; /** * {@code true} for performing full scans, or {@code false} otherwise. */ private boolean fullScan = true; /** Default constructor, subclass should provide an override for getProxy */ protected IdentifiedObjectFinder() { } /** * Creates a finder using the same proxy than the specified finder. */ IdentifiedObjectFinder(final IdentifiedObjectFinder finder) { this.setProxy(finder.getProxy()); } /** * Creates a finder using the specified factory. This constructor is * protected because instances of this class should not be created directly. * Use {@link AbstractAuthorityFactory#getIdentifiedObjectFinder} instead. * * @param factory The factory to scan for the identified objects. * @param type The type of objects to lookup. */ protected IdentifiedObjectFinder(final AuthorityFactory factory, final Class/* extends IdentifiedObject>*/ type) { setProxy(AuthorityFactoryProxy.getInstance(factory, type)); } /* * Do NOT provide the following method: * * public AuthorityFactory getAuthorityFactory() { * return proxy.getAuthorityFactory(); * } * * because the returned factory may not be the one the user would expect. Some of our * AbstractAuthorityFactory implementations create proxy to the underlying backing * store rather than to the factory on which 'getIdentifiedObjectFinder()' was invoked. */ /** * @return the proxy */ protected AuthorityFactoryProxy getProxy() { return proxy; } /** * If {@code true}, an exhaustive full scan against all registered objects * will be performed (may be slow). Otherwise only a fast lookup based on * embedded identifiers and names will be performed. The default value is * {@code true}. */ public boolean isFullScanAllowed() { return fullScan; } /** * Set whatever an exhaustive scan against all registered objects is allowed. * The default value is {@code true}. */ public void setFullScanAllowed(final boolean fullScan) { this.fullScan = fullScan; } /** * Lookups an object which is * {@linkplain CRS#equalsIgnoreMetadata equals, ignoring metadata}, to the * specified object. The default implementation tries to instantiate some * {@linkplain IdentifiedObject identified objects} from the authority factory * specified at construction time, in the following order: *
*
* The first of the above created objects which is equals to the specified object in the
* the sense of {@link CRS#equalsIgnoreMetadata equalsIgnoreMetadata} is returned.
*
* @param object The object looked up.
* @return The identified object, or {@code null} if not found.
* @throws FactoryException if an error occured while creating an object.
*/
public IdentifiedObject find(final IdentifiedObject object) throws FactoryException {
/*
* First check if one of the identifiers can be used to spot directly an
* identified object (and check it's actually equal to one in the factory).
*/
IdentifiedObject candidate = createFromIdentifiers(object);
if (candidate != null) {
return candidate;
}
/*
* We are unable to find the object from its identifiers. Try a quick name lookup.
* Some implementations like the one backed by the EPSG database are capable to find
* an object from its name.
*/
candidate = createFromNames(object);
if (candidate != null) {
return candidate;
}
/*
* Here we exhausted the quick paths. Bail out if the user does not want a full scan.
*/
return fullScan ? createFromCodes(object) : null;
}
/**
* Returns the identifier of the specified object, or {@code null} if none. The default
* implementation invokes {@linkplain #find find}(object)
and extracts the
* code from the returned {@linkplain IdentifiedObject identified object}.
*/
public String findIdentifier(final IdentifiedObject object) throws FactoryException {
final IdentifiedObject candidate = find(object);
return (candidate != null) ? getIdentifier(candidate) : null;
}
/**
* The Authority for this Finder; used during get Identifier.
* @return Citation for the authority being represented.
*/
protected Citation getAuthority(){
return getProxy().getAuthorityFactory().getAuthority();
}
/**
* Returns the identifier for the specified object.
*/
final String getIdentifier(final IdentifiedObject object) {
Citation authority = getAuthority();
if (ReferencingFactory.ALL.equals(authority)) {
/*
* "All" is a pseudo-authority declared by AllAuthoritiesFactory. This is not a real
* authority, so we will not find any identifier if we search for this authority. We
* will rather pickup the first identifier, regardless its authority.
*/
authority = null;
}
ReferenceIdentifier identifier = AbstractIdentifiedObject.getIdentifier(object, authority);
if (identifier == null) {
identifier = object.getName();
// Should never be null past this point, since 'name' is a mandatory attribute.
}
final String codespace = identifier.getCodeSpace();
final String code = identifier.getCode();
if (codespace != null) {
return codespace + org.geotools.util.GenericName.DEFAULT_SEPARATOR + code;
} else {
return code;
}
}
/**
* Creates an object {@linkplain CRS#equalsIgnoreMetadata equals, ignoring metadata}, to the
* specified object using only the {@linkplain IdentifiedObject#getIdentifiers identifiers}.
* If no such object is found, returns {@code null}.
*
* This method may be used in order to get a fully identified object from a partially * identified one. * * @param object The object looked up. * @return The identified object, or {@code null} if not found. * @see #createFromCodes * @see #createFromNames * @throws FactoryException if an error occured while creating an object. */ final IdentifiedObject createFromIdentifiers(final IdentifiedObject object) throws FactoryException { final Citation authority = getProxy().getAuthorityFactory().getAuthority(); final boolean isAll = ReferencingFactory.ALL.equals(authority); for (final Iterator it=object.getIdentifiers().iterator(); it.hasNext();) { final Identifier id = (Identifier) it.next(); if (!isAll && !Citations.identifierMatches(authority, id.getAuthority())) { // The identifier is not for this authority. Looks the other ones. continue; } IdentifiedObject candidate; try { candidate = getProxy().create(id.getCode()); } catch (NoSuchAuthorityCodeException e) { // The identifier was not recognized. No problem, let's go on. continue; } candidate = deriveEquivalent(candidate, object); if (candidate != null) { return candidate; } } return null; } /** * Creates an object {@linkplain CRS#equalsIgnoreMetadata equals, ignoring metadata}, to * the specified object using only the {@linkplain IdentifiedObject#getName name} and * {@linkplain IdentifiedObject#getAlias aliases}. If no such object is found, returns * {@code null}. *
* This method may be used with some {@linkplain AuthorityFactory authority factory} * implementations like the one backed by the EPSG database, which are capable to find * an object from its name when the identifier is unknown. * * @param object The object looked up. * @return The identified object, or {@code null} if not found. * @see #createFromCodes * @see #createFromIdentifiers * @throws FactoryException if an error occured while creating an object. */ final IdentifiedObject createFromNames(final IdentifiedObject object) throws FactoryException { IdentifiedObject candidate; try { candidate = getProxy().create(object.getName().getCode()); candidate = deriveEquivalent(candidate, object); if (candidate != null) { return candidate; } } catch (FactoryException e) { /* * The identifier was not recognized. No problem, let's go on. * Note: we catch a more generic exception than NoSuchAuthorityCodeException * because this attempt may fail for various reasons (character string * not supported by the underlying database for primary key, duplicated * name found, etc.). */ } for (final Iterator it=object.getAlias().iterator(); it.hasNext();) { final GenericName id = (GenericName) it.next(); try { candidate = getProxy().create(id.toString()); } catch (FactoryException e) { // The name was not recognized. No problem, let's go on. continue; } candidate = deriveEquivalent(candidate, object); if (candidate != null) { return candidate; } } return null; } /** * Creates an object {@linkplain CRS#equalsIgnoreMetadata equals, ignoring metadata}, to the * specified object. This method scans the {@linkplain #getAuthorityCodes authority codes}, * create the objects and returns the first one which is equals to the specified object in * the sense of {@link CRS#equalsIgnoreMetadata equalsIgnoreMetadata}. *
* This method may be used in order to get a fully {@linkplain IdentifiedObject identified * object} from an object without {@linkplain IdentifiedObject#getIdentifiers identifiers}. *
* Scaning the whole set of authority codes may be slow. Users should try
*
* This method is invoked by the default {@link #find find} method implementation. The caller
* may iterates through every returned codes, instantiate the objects and compare them with
* the specified one in order to determine which codes are really applicable.
*
* The default implementation returns the same set than
*
* This method is overriden by factories that may test many flavors of
* {@code candidate}, for example {@link TransformedAuthorityFactory}.
*
* @param candidate An object created by the factory specified at construction time.
* @return {@code candidate}, or an object derived from {@code candidate} (for example with axis
* order forced to (longitude, latitude), or {@code null} if none
* of the above is {@linkplain CRS#equalsIgnoreMetadata equals ignoring metadata} to the
* specified model.
*
* @throws FactoryException if an error occured while creating a derived object.
*/
protected IdentifiedObject deriveEquivalent(final IdentifiedObject candidate,
final IdentifiedObject model)
throws FactoryException
{
return CRS.equalsIgnoreMetadata(candidate, model) ? candidate : null;
}
/**
* Returns a string representation of this finder, for debugging purpose only.
*/
@Override
public String toString() {
return getProxy().toString(IdentifiedObjectFinder.class);
}
/**
* @param proxy the proxy to set
*/
public void setProxy( AuthorityFactoryProxy proxy ) {
this.proxy = proxy;
}
/**
* A finder which delegate part of its work to an other finder. This adapter forwards
* some method calls to the underlying finder. This class should not be public, because
* not all method are overriden. The choice is tuned for {@link BufferedAuthorityFactory}
* and {@link AuthorityFactoryAdapter} needs and may not be appropriate in the general case.
*
* @author Martin Desruisseaux
*/
static class Adapter extends IdentifiedObjectFinder {
/**
* The finder on which to delegate the work.
*/
protected final IdentifiedObjectFinder finder;
/**
* Creates an adapter for the specified finder.
*/
protected Adapter(final IdentifiedObjectFinder finder) {
super(finder);
this.finder = finder;
}
/**
* Set whatever an exhaustive scan against all registered objects is allowed.
*/
@Override
public void setFullScanAllowed(final boolean fullScan) {
finder.setFullScanAllowed(fullScan);
super .setFullScanAllowed(fullScan);
}
/**
* Returns a set of authority codes that may identify the same object
* than the specified one. The default implementation delegates to the backing finder.
*/
@Override
protected Set/*{@linkplain #createFromIdentifiers createFromIdentifiers}(object)
and/or
* {@linkplain #createFromNames createFromNames}(object)
before to fallback
* on this method.
*
* @param object The object looked up.
* @return The identified object, or {@code null} if not found.
* @throws FactoryException if an error occured while scanning through authority codes.
*
* @see #createFromIdentifiers
* @see #createFromNames
*/
final IdentifiedObject createFromCodes(final IdentifiedObject object) throws FactoryException {
final Set/*{@linkplain AuthorityFactory#getAuthorityCodes getAuthorityCodes}(type)
* where {@code type} is the interface specified at construction type. Subclasses should
* override this method in order to return a smaller set, if they can.
*
* @param object The object looked up.
* @return A set of code candidates.
* @throws FactoryException if an error occured while fetching the set of code candidates.
*/
protected Set/*