/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-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.coverage.processing;
import java.util.Collection;
import java.awt.image.RenderedImage;
import javax.media.jai.PlanarImage;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.processing.Operation;
import org.opengis.coverage.processing.OperationNotFoundException;
import org.opengis.parameter.ParameterValueGroup;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.lang.Buffered;
import org.geotoolkit.lang.Decorator;
import org.apache.sis.util.collection.Cache;
import org.geotoolkit.coverage.grid.RenderedCoverage;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
/**
* A coverage processor that cache the result of operations. Given that {@linkplain GridCoverage
* grid coverages} may be expensive to compute and consume a lot of memory, we can save a lot of
* resources by returning cached instances every time the same {@linkplain Operation operation}
* with the same {@linkplain ParameterValueGroup parameters} is applied on the same coverage.
* Coverages are cached using {@linkplain java.lang.ref.WeakReference weak references}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.00
*
* @since 2.2
* @module
*/
@Buffered
@Decorator(AbstractCoverageProcessor.class)
public class CachingCoverageProcessor extends AbstractCoverageProcessor {
/**
* The underlying processor.
*/
protected final AbstractCoverageProcessor processor;
/**
* A set of {@link GridCoverage}s resulting from previous invocations to {@link #doOperation}.
* Current implementation retains the coverage by weak references only.
*
* @todo Use the capability of {@link Cache} to evict entries based on cost calculation.
*/
private final Cache<CachedOperationParameters, Coverage> cache = new Cache<>(12, 0, false);
/**
* Creates a default caching processor.
* <p>
* This constructor should not be invoked directly - consider using
* {@link org.geotoolkit.coverage.CoverageFactoryFinder#getCoverageProcessor(Hints)} instead.
*/
public CachingCoverageProcessor() {
this(EMPTY_HINTS);
}
/**
* Creates a caching processor using the specified hints.
* <p>
* This constructor should not be invoked directly - consider using
* {@link org.geotoolkit.coverage.CoverageFactoryFinder#getCoverageProcessor(Hints)} instead.
*
* @param userHints An optional set of hints, or {@code null} if none.
*/
public CachingCoverageProcessor(final Hints userHints) {
AbstractCoverageProcessor processor = null;
if (userHints != null) {
Object candidate = userHints.get(Hints.GRID_COVERAGE_PROCESSOR);
if (candidate instanceof AbstractCoverageProcessor) {
processor = (AbstractCoverageProcessor) candidate;
}
}
if (processor == null) {
processor = new DefaultCoverageProcessor(userHints, this);
}
this.processor = processor;
hints.put(Hints.GRID_COVERAGE_PROCESSOR, processor);
}
/**
* Creates a new buffered processor backed by the specified processor.
*
* @param processor The coverage processor for which to cache the results.
*/
public CachingCoverageProcessor(final AbstractCoverageProcessor processor) {
ensureNonNull("processor", processor);
this.processor = processor;
hints.put(Hints.GRID_COVERAGE_PROCESSOR, processor);
}
/**
* Retrieves grid processing operations information. The default implementation forward
* the call directly to the {@linkplain #processor underlying processor}.
*/
@Override
public Collection<Operation> getOperations() {
return processor.getOperations();
}
/**
* Returns the operation for the specified name. The default implementation forward
* the call directly to the {@linkplain #processor underlying processor}.
*
* @param name Name of the operation.
* @return The operation for the given name.
* @throws OperationNotFoundException if there is no operation for the specified name.
*/
@Override
public Operation getOperation(final String name) throws OperationNotFoundException {
return processor.getOperation(name);
}
/**
* Applies an operation. The default implementation first checks if a coverage has already
* been created from the same parameters. If such a coverage is found, it is returned.
* Otherwise, this method forward the call to the {@linkplain #processor underlying processor}
* and caches the result.
*
* @param parameters Parameters required for the operation.
* @return The result as a coverage.
* @throws OperationNotFoundException if there is no operation for the parameter group name.
* @throws CoverageProcessingException if the operation can not be executed.
*/
@Override
public Coverage doOperation(final ParameterValueGroup parameters)
throws OperationNotFoundException, CoverageProcessingException
{
final String operationName = getOperationName(parameters);
final Operation operation = processor.getOperation(operationName);
final CachedOperationParameters key = new CachedOperationParameters(operation, parameters);
Coverage coverage = cache.peek(key);
if (coverage != null) {
log(getPrimarySource(parameters), coverage, operationName, true);
return coverage;
}
final Cache.Handler<Coverage> handler = cache.lock(key);
try {
coverage = handler.peek();
if (coverage != null) {
log(getPrimarySource(parameters), coverage, operationName, true);
return coverage;
}
coverage = processor.doOperation(parameters);
if (coverage instanceof RenderedCoverage) {
final RenderedImage image = ((RenderedCoverage) coverage).getRenderedImage();
if (image instanceof PlanarImage) {
/*
* Adds a sink to the planar image in order to prevent GridCoverage2D.dispose
* to dispose this image as long as it still in the cache. Note that the sink
* is stored as a weak reference (as well as values in the cache map), so it
* will not prevent the garbage collector to collect the coverage. However,
* the current approach make GridCoverage2D.dispose(false) useless for cached
* coverages. We may need to find a better mechanism later (GEOT-1041).
*/
((PlanarImage) image).addSink(key);
}
}
return coverage;
} finally {
handler.putAndUnlock(coverage);
}
}
}