001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.coverage.processing;
018:
019: // J2SE dependencies
020: import java.util.Map;
021: import java.util.Collection;
022: import java.awt.RenderingHints;
023: import java.awt.image.RenderedImage;
024: import javax.media.jai.PlanarImage;
025:
026: // OpenGIS dependencies
027: import org.opengis.coverage.Coverage;
028: import org.opengis.coverage.grid.GridCoverage;
029: import org.opengis.coverage.processing.Operation;
030: import org.opengis.coverage.processing.OperationNotFoundException;
031: import org.opengis.parameter.ParameterValueGroup;
032:
033: // Geotools dependencies
034: import org.geotools.util.WeakValueHashMap;
035: import org.geotools.coverage.grid.RenderedCoverage;
036:
037: /**
038: * A coverage processor caching the result of any operations. Since a
039: * {@linkplain GridCoverage grid coverage} may be expensive to compute and consumes a lot of
040: * memory, we can save a lot of resources by returning cached instances every time the same
041: * {@linkplain Operation operation} with the same {@linkplain ParameterValueGroup parameters}
042: * is applied on the same coverage. Coverages are cached using
043: * {@linkplain java.lang.ref.WeakReference weak references}.
044: *
045: * @since 2.2
046: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/processing/BufferedProcessor.java $
047: * @version $Id: BufferedProcessor.java 26655 2007-08-22 13:57:25Z desruisseaux $
048: * @author Martin Desruisseaux
049: */
050: public class BufferedProcessor extends AbstractProcessor {
051: /**
052: * The underlying processor.
053: */
054: protected final AbstractProcessor processor;
055:
056: /**
057: * A set of {@link GridCoverage}s resulting from previous invocations to {@link #doOperation}.
058: * Will be created only when first needed.
059: */
060: private transient Map/*<Coverage>*/cache;
061:
062: /**
063: * Creates a buffered processor backed by a {@linkplain DefaultProcessor default processor}
064: * using the specified hints. Null or empty hints are legal, but consider using the
065: * {@linkplain #getInstance default instance} in such case.
066: */
067: public BufferedProcessor(final RenderingHints hints) {
068: final DefaultProcessor processor = new DefaultProcessor(hints);
069: processor.setProcessor(this );
070: this .processor = processor;
071: }
072:
073: /**
074: * Creates a new buffered processor backed by the specified processor. If the specified
075: * processor is an instance of {@link DefaultProcessor}, consider using the
076: * {@linkplain #BufferedProcessor(RenderingHints) constructor expecting hints} instead,
077: * for efficienty.
078: */
079: public BufferedProcessor(final AbstractProcessor processor) {
080: ensureNonNull("processor", processor);
081: this .processor = processor;
082: }
083:
084: /**
085: * Notifies this processor that it is going to be used as the application-wide default
086: * processor.
087: */
088: void setAsDefault() {
089: processor.setAsDefault();
090: }
091:
092: /**
093: * Retrieves grid processing operations information. The default implementation forward
094: * the call directly to the {@linkplain #processor underlying processor}.
095: */
096: public Collection/*<Operation>*/getOperations() {
097: return processor.getOperations();
098: }
099:
100: /**
101: * Returns the operation for the specified name. The default implementation forward
102: * the call directly to the {@linkplain #processor underlying processor}.
103: *
104: * @param name Name of the operation.
105: * @return The operation for the given name.
106: * @throws OperationNotFoundException if there is no operation for the specified name.
107: */
108: public Operation getOperation(final String name)
109: throws OperationNotFoundException {
110: return processor.getOperation(name);
111: }
112:
113: /**
114: * Applies an operation. The default implementation first checks if a coverage has already
115: * been created from the same parameters. If such a coverage is found, it is returned.
116: * Otherwise, this method forward the call to the {@linkplain #processor underlying processor}
117: * and caches the result.
118: *
119: * @param parameters Parameters required for the operation.
120: * @return The result as a coverage.
121: * @throws OperationNotFoundException if there is no operation for the parameter group name.
122: */
123: public Coverage doOperation(final ParameterValueGroup parameters)
124: throws OperationNotFoundException {
125: synchronized (processor) {
126: final String operationName = getOperationName(parameters);
127: final Operation operation = processor
128: .getOperation(operationName);
129: final CachedOperation cacheKey = new CachedOperation(
130: operation, parameters);
131: if (cache != null) {
132: final Coverage coverage = (Coverage) cache
133: .get(cacheKey);
134: if (coverage != null) {
135: log(getPrimarySource(parameters), coverage,
136: operationName, true);
137: return coverage;
138: }
139: } else {
140: cache = new WeakValueHashMap();
141: }
142: final Coverage coverage = processor.doOperation(parameters);
143: if (coverage instanceof RenderedCoverage) {
144: final RenderedImage image = ((RenderedCoverage) coverage)
145: .getRenderedImage();
146: if (image instanceof PlanarImage) {
147: /*
148: * Adds a sink to the planar image in order to prevent GridCoverage2D.dispose
149: * to dispose this image as long as it still in the cache. Note that the sink
150: * is stored as a weak reference (as well as values in the cache map), so it
151: * will not prevent the garbage collector to collect the coverage. However,
152: * the current approach make GridCoverage2D.dispose(false) useless for cached
153: * coverages. We may need to find a better mechanism later (GEOT-1041).
154: */
155: ((PlanarImage) image).addSink(cacheKey);
156: }
157: }
158: cache.put(cacheKey, coverage);
159: return coverage;
160: }
161: }
162: }
|