/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-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.operation; import java.util.Map; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.CoordinateOperation; import org.opengis.referencing.operation.CoordinateOperationFactory; import org.opengis.referencing.operation.OperationMethod; import org.opengis.referencing.operation.OperationNotFoundException; import org.geotools.factory.Hints; import org.geotools.factory.BufferedFactory; import org.geotools.util.Utilities; import org.geotools.util.SoftValueHashMap; import org.geotools.referencing.ReferencingFactoryFinder; /** * Caches the {@linkplain CoordinateOperation coordinate operations} created by an other factory. * Those coordinate operations may be expensive to create. During rendering and during data I/O, * some implementations make use a lof of coordinate transformations, hence caching them might * help. * <p> * In most cases, users should not need to create an instance of this class explicitly. An instance * of {@code BufferedCoordinateOperationFactory} should be automatically registered and returned * by {@link ReferencingFactoryFinder} in default Geotools configuration. * * @since 2.3 * @version $Id$ * * @source $URL$ * @author Simone Giannecchini * @author Martin Desruisseaux */ public class BufferedCoordinateOperationFactory extends AbstractCoordinateOperationFactory implements BufferedFactory { /** * The priority level for this factory. */ static final int PRIORITY = AuthorityBackedFactory.PRIORITY + 10; /** * Helper class used in order to build an hashing for a pair of source-destination * {@link CoordinateReferenceSystem} objects. This is used to cache the transformations * that are pretty time-consuming to build each time. */ private static final class CRSPair { /** * The hash code value, computed once for ever at construction time. */ private final int hash; /** * The source and target CRS. */ private final CoordinateReferenceSystem sourceCRS, targetCRS; /** * Creates a {@code CRSPair} for the specified source and target CRS. */ public CRSPair(final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS) { this.sourceCRS = sourceCRS; this.targetCRS = targetCRS; this.hash = (37 * sourceCRS.hashCode()) + targetCRS.hashCode(); } /** * Returns the hash code value. */ @Override public int hashCode() { return hash; } /** * Compares this pair to the specified object for equality. * <p> * <strong>Note:</strong> we perform the CRS comparaison using strict equality, not using * {@code equalsIgnoreMetadata}, because metadata matter since they are attributes of the * {@link CoordinateOperation} object to be created. */ @Override public boolean equals(final Object object) { if (object == this) { return true; } if (object instanceof CRSPair) { final CRSPair that = (CRSPair) object; return Utilities.equals(this.sourceCRS, that.sourceCRS) && Utilities.equals(this.targetCRS, that.targetCRS); } return false; } } /** * The wrapped factory. If {@code null}, will be fetched when first needed. * We should not initialize this field using {@link ReferencingFactoryFinder} from the * no-argument constructor, since this constructor is typically invoked while * {@link ReferencingFactoryFinder} is still iterating over the registered implementations. */ private CoordinateOperationFactory factory; /** * The pool of cached transformations. This map can not be static, because the values may * be different for the same ({@code sourceCRS}, {@code targetCRS}) pair dependending of * hint values like {@link Hints#LENIENT_DATUM_SHIFT}. */ private final Map<CRSPair, CoordinateOperation> pool = new SoftValueHashMap<CRSPair, CoordinateOperation>(); /** * Creates a buffered factory wrapping the {@linkplain AuthorityBackedFactory default one}. */ public BufferedCoordinateOperationFactory() { super(null, PRIORITY); /* * Do not use FactoryFinder here (directly or indirectly through the call * to an other constructor), because this constructor is typically invoked * while FactoryFinder is iterating over registered implementations. We * left the 'factory' field uninitialized and will initialize it when first * needed. */ } /** * Creates a buffered factory wrapping an other factory selected according the specified hints. * * @param userHints The hints to use for choosing a backing factory. */ public BufferedCoordinateOperationFactory(final Hints userHints) { this(userHints, PRIORITY); } /** * Creates a buffered factory wrapping an other factory selected according the specified hints. * * @param userHints The hints to use for choosing a backing factory. * @param priority The priority for this factory, as a number between * {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and * {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive. */ public BufferedCoordinateOperationFactory(final Hints userHints, final int priority) { this(getBackingFactory(userHints), userHints, priority); } /** * Wraps the specified factory. * * @param factory The factory to wrap. * @param priority The priority for this factory, as a number between * {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and * {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive. */ public BufferedCoordinateOperationFactory(final CoordinateOperationFactory factory, final int priority) { this(factory, null, priority); } /** * Work around for RFE #4093999 in Sun's bug database * ("Relax constraint on placement of this()/super() call in constructors"). */ private BufferedCoordinateOperationFactory(final CoordinateOperationFactory factory, final Hints userHints, final int priority) { super(factory, userHints, priority); this.factory = factory; ensureNonNull("factory", factory); } /** * Returns a backing factory from the specified hints. */ private static CoordinateOperationFactory getBackingFactory(final Hints hints) { for (final CoordinateOperationFactory candidate : ReferencingFactoryFinder.getCoordinateOperationFactories(hints)) { if (!(candidate instanceof BufferedCoordinateOperationFactory)) { return candidate; } } // The following is likely to thrown a FactoryNotFoundException, // which is the intended behavior. return ReferencingFactoryFinder.getCoordinateOperationFactory(hints); } /** * Returns the backing factory. Coordinate operation creation will be delegated to this * factory when not available in the cache. */ private final CoordinateOperationFactory getBackingFactory() { assert Thread.holdsLock(hints); // Same lock than the one used by getImplementationHints(). if (factory == null) { factory = getBackingFactory(null); } return factory; } /** * Invoked by {@link #AbstractCoordinateOperationFactory} when the {@link #hints} map should * be initialized. The {@link Hints#COORDINATE_OPERATION_FACTORY} can not always be provided * at construction time, because the backing factory may be lazily created. */ @Override void initializeHints() { super.initializeHints(); hints.put(Hints.COORDINATE_OPERATION_FACTORY, getBackingFactory()); } /** * Returns an operation for conversion or transformation between two coordinate reference * systems. If an operation was already created and still in the cache, the cached operation * is returned. Otherwise the operation creation is delegated to the * {@linkplain CoordinateOperationFactory coordinate operation factory} specified at * construction time and the result is cached. * * @param sourceCRS Input coordinate reference system. * @param targetCRS Output coordinate reference system. * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}. * @throws OperationNotFoundException if no operation path was found from {@code sourceCRS} * to {@code targetCRS}. * @throws FactoryException if the operation creation failed for some other reason. */ public CoordinateOperation createOperation(final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS) throws OperationNotFoundException, FactoryException { ensureNonNull("sourceCRS", sourceCRS); ensureNonNull("targetCRS", targetCRS); final CRSPair key = new CRSPair(sourceCRS, targetCRS); CoordinateOperation op; synchronized (hints) { // This lock is indirectly required by getBackingFactory(). op = pool.get(key); if (op == null) { op = getBackingFactory().createOperation(sourceCRS, targetCRS); pool.put(key, op); } } return op; } /** * Returns an operation for conversion or transformation between two coordinate reference * systems using the specified method. The current implementation delegates to the * {@linkplain CoordinateOperationFactory coordinate operation factory} specified at * construction time with no caching. * * @deprecated Will be removed. */ public CoordinateOperation createOperation(final CoordinateReferenceSystem sourceCRS, final CoordinateReferenceSystem targetCRS, final OperationMethod method) throws OperationNotFoundException, FactoryException { synchronized (hints) { // This lock is indirectly required by getBackingFactory(). return getBackingFactory().createOperation(sourceCRS, targetCRS, method); } } }