/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-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.renderer.lite.gridcoverage2d;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.processing.CoverageProcessingException;
import org.geotools.factory.Hints;
import org.geotools.renderer.i18n.ErrorKeys;
import org.geotools.renderer.i18n.Errors;
import org.geotools.renderer.i18n.Vocabulary;
import org.geotools.renderer.i18n.VocabularyKeys;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.util.InternationalString;
/**
* Base implementation of a {@link CoverageProcessingNode} . <p> This implementation provides convenient methods for managing sinks and source for a {@link CoverageProcessingNode} . The {@link #getOutput()} is used to get the output of this {@link CoverageProcessingNode} (a {@link CoverageProcessingException} is thrown in case something bad happens while processing <p> Implementors must implement the abstract method {@link #execute()} which is guaranteed to run in a critical section where the sources and sinks for this {@link CoverageProcessingNode} will not be touched.
* @author Simone Giannecchini, GeoSolutions.
*
* @source $URL$
*/
public abstract class BaseCoverageProcessingNode implements
CoverageProcessingNode {
/**
* Very simple class to detect cycles in the graph of
* {@link CoverageProcessingNode}s.
*
* @author Simone Giannecchini, GeoSlutions.
*
*/
private final static class CoverageProcessingCycleDetector {
public CoverageProcessingCycleDetector() {
}
public boolean detectCycle(CoverageProcessingNode startPoint) {
return check(startPoint, startPoint);
}
private boolean check(CoverageProcessingNode baseNode,
CoverageProcessingNode currentNode) {
// get the sinks for this node
if (currentNode.getNumberOfSinks() == 0)
return false;
final List<CoverageProcessingNode> sinks = currentNode.getSinks();
for (CoverageProcessingNode node:sinks) {
// check the start point
if (baseNode.equals(node))
return true;
// no cyle here, let's dig
if (check(baseNode, node))
return true;
}
return false;
}
}
/**
* Detects cycle in out graph
*/
private final static CoverageProcessingCycleDetector cycleDetector = new CoverageProcessingCycleDetector();
/**
* Logger for this class.
*/
private final static Logger LOGGER = Logger
.getLogger(BaseCoverageProcessingNode.class.getName());
/**
* {@link List} of sources for this {@link CoverageProcessingNode} .
* @uml.property name="sources"
*/
private final List<CoverageProcessingNode> sources = new ArrayList<CoverageProcessingNode>();
/**
* {@link List} of sinks for this {@link CoverageProcessingNode} .
* @uml.property name="sinks"
*/
private final List<CoverageProcessingNode> sinks = new ArrayList<CoverageProcessingNode>();
/**
* Output of this {@link CoverageProcessingNode}
* @uml.property name="output"
*/
private GridCoverage2D output;
/**
* Tells me if we have executed this node or not.
* @uml.property name="executed"
*/
private boolean executed = false;
/**
* Maximum number of allowed sources.
* @uml.property name="maximumNumberOfSources"
*/
private int maximumNumberOfSources = -1;
/**
* User supplied {@link Hints} to control {@link GridCoverageFactory} creation.
* @uml.property name="hints"
*/
private Hints hints;
/**
* {@link GridCoverageFactory} to build {@link GridCoverage2D} objects as output of this operation.
* @uml.property name="coverageFactory"
*/
final GridCoverageFactory coverageFactory;
/**
* Store the error raised during execution, in case there was one.
*/
private Throwable error;
/**
* Tells me if this coverage has been already disposed or not.
* @uml.property name="disposed"
*/
private boolean disposed;
/**
* Internationalized name for this {@link CoverageProcessingNode} .
* @uml.property name="name"
*/
private InternationalString name = Vocabulary.formatInternational(VocabularyKeys.BASE_COVERAGE_PROCESSING);
/**
* Internationalized description for this {@link CoverageProcessingNode} .
* @uml.property name="description"
*/
private InternationalString description;
/**
* Default constructor
*/
public BaseCoverageProcessingNode(InternationalString name,
InternationalString description) {
this(-1, name, description);
}
/**
* Default constructor that gives users the possibility
*
* @param maxSources
* maximum number of sources allowed for this node.
* @param description
* @param name
*/
public BaseCoverageProcessingNode(int maxSources, InternationalString name,
InternationalString description) {
this(maxSources, null, name, description);
}
/**
* Default constructor that gives users the possibility
*
* @param maxSources
* maximum number of sources allowed for this node.
* @param hints
* instance of {@link Hints} class to control creation of
* internal factories. It can be <code>null</code>.
* @param description
* @param name
*/
public BaseCoverageProcessingNode(int maxSources, Hints hints,
InternationalString name, InternationalString description) {
ensureNotNull(name, "CoverageProcessingNode name ");
ensureNotNull(description, "CoverageProcessingNode descripion ");
maximumNumberOfSources = maxSources;
this.hints = hints != null ? (Hints) hints.clone() : null;
this.coverageFactory = CoverageFactoryFinder.getGridCoverageFactory(hints);
this.name = name;
this.description = description;
}
/**
* Checks whether or not we need to execute this node.
*
* <p>
* This method records all the possible error conditions that may arise
* during execution.
*/
private void checkExecuted() {
// precondition
assert Thread.holdsLock(this);
// /////////////////////////////////////////////////////////////////////
//
// Is this node disposed? If so throw an exception
//
// /////////////////////////////////////////////////////////////////////
if (disposed) {
error = new CoverageProcessingException(
"Trying to process a disposed CoverageProcessingNode.");
return;
}
// /////////////////////////////////////////////////////////////////////
//
// We cannot execute the same node twice in this simpe design
//
// /////////////////////////////////////////////////////////////////////
if (!executed) {
try {
output=null;
// executes this node
final GridCoverage result = execute();
if (result == null)
error = new CoverageProcessingException("Something bad occurred while trying to execute this node.");
if (!(result instanceof GridCoverage2D))
error = new CoverageProcessingException("Something bad occurred while trying to execute this node.");
if(error==null)
output=(GridCoverage2D) result;
} catch (Throwable t) {
// something bad happened
output = null;
error = t;
}
// we have executed, let's register that
executed = true;
}
// postconditions
assert executed;
}
/**
* Subclasses MUST override this method in order to do the actual
* processing.
*
* <p>
* Note that this method is invoked through this framework hence it is
* run within a critical section. Be careful with what you do within
* this method since it is essentially an "alien" method running within
* a synch section, hence all sort of bad things can happen.
*
* @return a {@link GridCoverage2D} which is the result of the
* processing.
*/
protected abstract GridCoverage execute();
/**
* Disposes this {@link CoverageProcessingNode} along with all the resources
* it might have allocated
*
* <p>
* The result for this {@link CoverageProcessingNode} is also disposed.
*
* @param force
* force the disposition of this node.
*/
public synchronized void dispose(boolean force) {
// /////////////////////////////////////////////////////////////////////
//
// Do we need to dispose this node?
//
// /////////////////////////////////////////////////////////////////////
if (disposed)
return;
// /////////////////////////////////////////////////////////////////////
//
// Cleaning the output we have generated
//
// /////////////////////////////////////////////////////////////////////
if (output != null)
output.dispose(force);
// /////////////////////////////////////////////////////////////////////
//
// Removing myself as a sink for my sources
//
// /////////////////////////////////////////////////////////////////////
final Iterator<CoverageProcessingNode>it = sources.iterator();
while (it.hasNext()) {
final CoverageProcessingNode source = it.next();
source.removeSink(this);
}
sources.clear();
// /////////////////////////////////////////////////////////////////////
//
// Cleaning sinks
//
// /////////////////////////////////////////////////////////////////////
sinks.clear();
// /////////////////////////////////////////////////////////////////////
//
// Done
//
// /////////////////////////////////////////////////////////////////////
disposed = true;
if(LOGGER.isLoggable(Level.FINE))
LOGGER.fine("disposed node \n" + this.toString());
}
/**
* This method is responsible for triggering the execution of this {@link CoverageProcessingNode} and also of all its sources. <p> In case something bad happens a {@link CoverageProcessingException} is thrown.
* @uml.property name="output"
*/
public synchronized GridCoverage2D getOutput()
throws CoverageProcessingException {
checkExecuted();
if (error != null)
throw new CoverageProcessingException(error);
return output;
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#addSink(org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode)
*/
public synchronized void addSink(CoverageProcessingNode sink) {
ensureNotNull(sink, "CoverageProcessingNode");
sinks.add(sink);
detectCycle();
}
/**
* Performs proper clean up on this {@link CoverageProcessingNode}.
*/
private void cleanOutput() {
assert Thread.holdsLock(this);
if (executed) {
executed = false;
output.dispose(true);
error = null;
}
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#addSource(org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode)
*/
public synchronized boolean addSource(CoverageProcessingNode source) {
ensureNotNull(source, "CoverageProcessingNode");
checkNumSources(1);
if (this.sources.add(source)) {
cleanOutput();
detectCycle();
return true;
}
return false;
}
private void detectCycle() throws IllegalStateException {
if (cycleDetector.detectCycle(this))
throw new IllegalStateException(Errors
.format(ErrorKeys.CYCLE_DETECTED));
}
private void checkNumSources(final int sourcesToAdd) {
if (maximumNumberOfSources != -1)
if (this.sources.size() + sourcesToAdd > maximumNumberOfSources)
throw new IllegalStateException(Errors.format(
ErrorKeys.TOO_MANY_SOURCES_$1, Integer.valueOf(
maximumNumberOfSources)));
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#getSink(int)
*/
public synchronized CoverageProcessingNode getSink(int index) {
return (CoverageProcessingNode) sinks.get(index);
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#getSinks()
*/
/**
* @return
* @uml.property name="sinks"
*/
public synchronized List<CoverageProcessingNode> getSinks() {
return Collections.unmodifiableList(sinks);
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#getSource(int)
*/
public synchronized CoverageProcessingNode getSource(int index) {
return (CoverageProcessingNode) sources.get(index);
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#getSources()
*/
/**
* @return
* @uml.property name="sources"
*/
public synchronized List<CoverageProcessingNode> getSources() {
return Collections.unmodifiableList(sources);
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#removeSink(org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode)
*/
public synchronized boolean removeSink(CoverageProcessingNode sink) {
ensureNotNull(sink, "CoverageProcessingNode");
// /////////////////////////////////////////////////////////////////////
//
// In case we manage to remove a sink for this node we
//
// /////////////////////////////////////////////////////////////////////
return this.sinks.remove(sink);
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#removeSink(int)
*/
public synchronized CoverageProcessingNode removeSink(int index) {
return (CoverageProcessingNode) this.sinks.remove(index);
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#removeSource(org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode)
*/
public synchronized boolean removeSource(CoverageProcessingNode source) {
ensureNotNull(source, "CoverageProcessingNode");
final boolean success = this.sources.remove(source);
if (success)
cleanOutput();
return success;
}
/**
* Getter for {@link Hints} .
* @return {@link Hints} provided at construction time to control {@link GridCoverageFactory} creation.
* @uml.property name="hints"
*/
public synchronized Hints getHints() {
return new Hints(hints);
}
/**
* retrieves the maximum number of sources we are allowed to set for this {@link CoverageProcessingNode}
* @return the maximum number of sources we are allowed to set for this {@link CoverageProcessingNode}
* @uml.property name="maximumNumberOfSources"
*/
public int getMaximumNumberOfSources() {
return maximumNumberOfSources;
}
/**
* The {@link GridCoverageFactory} we will internally use for build intermediate and output {@link GridCoverage2D} .
* @return a {@link GridCoverageFactory} we will internally use for build intermediate and output {@link GridCoverage2D} .
* @uml.property name="coverageFactory"
*/
public GridCoverageFactory getCoverageFactory() {
return coverageFactory;
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#getNumberOfSinks()
*/
public synchronized int getNumberOfSinks() {
return this.sinks.size();
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#getNumberOfSources()
*/
public synchronized int getNumberOfSources() {
return sources.size();
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#getDescription()
*/
/**
* @return
* @uml.property name="description"
*/
public InternationalString getDescription() {
return description;
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#getName()
*/
/**
* @return
* @uml.property name="name"
*/
public InternationalString getName() {
return name;
}
/**
* Checks whether the provided source object is null or not. If it is null
* it throws an {@link IllegalArgumentException} exception.
*
* @param source
* the object to check.
* @param node
* the operation we are trying to run.
*/
protected static void ensureSourceNotNull(final Object source,
final String name) {
if (source == null)
throw new IllegalArgumentException(Errors.format(
ErrorKeys.SOURCE_CANT_BE_NULL_$1, name));
}
/**
* Checks whether the provided object is null or not. If it is null it
* throws an {@link IllegalArgumentException} exception.
*
* @param source
* the object to check.
* @param node
* the operation we are trying to run.
*/
protected static void ensureNotNull(final Object source, final String name) {
if (source == null)
throw new IllegalArgumentException(Errors.format(
ErrorKeys.NULL_ARGUMENT_$1, name));
}
/*
* (non-Javadoc)
*
* @see org.geotools.renderer.lite.gridcoverage2d.CoverageProcessingNode#removeSource(int)
*/
public synchronized CoverageProcessingNode removeSource(int index)
throws IndexOutOfBoundsException {
return (CoverageProcessingNode) sources.remove(index);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
final StringBuffer buffer = new StringBuffer();
buffer.append("Node Name:").append(this.getName().toString()).append(
"\n");
buffer.append("Node Description:").append(
this.getDescription().toString()).append("\n");
// if(executed&&error==null)
// buffer.append("Node executed fine with
// output:\n\t").append(this.getOutput().toString()).append("\n");
// else
// if(executed&&error!=null)
// buffer.append("Node executed with
// error:\n\t").append(this.error.getLocalizedMessage()).append("\n").append(error.getStackTrace()).append("\n");
// else
// if(!executed)
// buffer.append("Node not yet executed:\n");
return buffer.toString();
}
/**
* Tells me whether or not the node has been already disposed.
* @return <code>true</code> if the node has been already disposed, <code>false</code> otherwise.
* @uml.property name="disposed"
*/
public synchronized boolean isDisposed() {
return disposed;
}
/**
* Tells me whether or not the node has been already executed.
* @return <code>true</code> if the node has been already executed, <code>false</code> otherwise.
* @uml.property name="executed"
*/
public synchronized boolean isExecuted() {
return this.executed ;
}
}