/* * 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 <cite>Well Known Text</cite> 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: * <p> * <ul> * <li>If the specified object contains {@linkplain IdentifiedObject#getIdentifiers * identifiers} associated to the same authority than the factory, then those * identifiers are used for {@linkplain AuthorityFactory#createObject creating * objects} to be tested.</li> * <li>If the authority factory can create objects from their {@linkplain * IdentifiedObject#getName name} in addition of identifiers, then the name and * {@linkplain IdentifiedObject#getAlias aliases} are used for creating objects * to be tested.</li> * <li>If {@linkplain #isFullScanAllowed full scan is allowed}, then full * {@linkplain #getCodeCandidates set of authority codes} are used for * creating objects to be tested.</li> * </ul> * <p> * 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 <code>{@linkplain #find find}(object)</code> 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}. * <p> * 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}. * <p> * 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}. * <p> * This method may be used in order to get a fully {@linkplain IdentifiedObject identified * object} from an object without {@linkplain IdentifiedObject#getIdentifiers identifiers}. * <p> * Scaning the whole set of authority codes may be slow. Users should try * <code>{@linkplain #createFromIdentifiers createFromIdentifiers}(object)</code> and/or * <code>{@linkplain #createFromNames createFromNames}(object)</code> 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/*<String>*/ codes = getCodeCandidates(object); for (final Iterator it=codes.iterator(); it.hasNext();) { final String code = (String) it.next(); IdentifiedObject candidate; try { candidate = getProxy().create(code); } catch (FactoryException e) { LOGGER.log( Level.FINEST, "Could not create '"+code+"':"+e ); // Some object cannot be created properly. continue; } catch (Exception problemCode ){ LOGGER.log( Level.FINEST, "Could not create '"+code+"':"+problemCode, problemCode ); continue; } candidate = deriveEquivalent(candidate, object); if (candidate != null) { return candidate; } } return null; } /** * Returns a set of authority codes that <strong>may</strong> identify the same object than * the specified one. The returned set must contains the code of every objects that are * {@linkplain CRS#equalsIgnoreMetadata equals, ignoring metadata}, to the specified one. * However the set is not required to contains only the codes of those objects; it may * conservatively contains the code for more objects if an exact search is too expensive. * <p> * 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. * <p> * The default implementation returns the same set than * <code>{@linkplain AuthorityFactory#getAuthorityCodes getAuthorityCodes}(type)</code> * 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/*<String>*/ getCodeCandidates(final IdentifiedObject object) throws FactoryException { return getProxy().getAuthorityCodes(); } /* * Do NOT define the following method in IdentifiedObjectFinder's API: * * protected IdentifiedObject create(String code) throws FactoryException { * return proxy.create(code); * } * * We may be tempted to put such method in order to allow BufferedAuthorityFactory to * override it with caching service, but it conflicts with AuthorityFactoryAdapter's * work. The later (or to be more accurate, OrderedAxisAuthorityFactory) expects axis * in (latitude,longitude) order first, in order to test this CRS before to switch to * (longitude,latitude) order and test again. If the BufferedAuthorityFactory's cache * is in the way, we get directly (longitude,latitude) order and miss an opportunity * to identify the user's CRS. * * We should invoke directly AuthorityFactoryProxy.create(String) instead. */ /** * Returns {@code candidate}, or an object derived from {@code candidate}, if it is * {@linkplain CRS#equalsIgnoreMetadata equals ignoring metadata} to the specified * model. Otherwise returns {@code null}. * <p> * 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 (<var>longitude</var>, <var>latitude</var>), 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 <strong>may</strong> identify the same object * than the specified one. The default implementation delegates to the backing finder. */ @Override protected Set/*<String>*/ getCodeCandidates(final IdentifiedObject object) throws FactoryException { return finder.getCodeCandidates(object); } /** * Returns {@code candidate}, or an object derived from {@code candidate}, if it is * {@linkplain CRS#equalsIgnoreMetadata equals ignoring metadata} to the specified * model. The default implementation delegates to the backing finder. */ @Override protected IdentifiedObject deriveEquivalent(final IdentifiedObject candidate, final IdentifiedObject model) throws FactoryException { return finder.deriveEquivalent(candidate, model); } } }