This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new a203f47 Make ImageOperations configurable.
a203f47 is described below
commit a203f47f17a9678a7cd29e5b1339753eda204d97
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Mar 3 12:01:11 2020 +0100
Make ImageOperations configurable.
---
.../apache/sis/internal/gui/ImageRenderings.java | 5 +-
.../java/org/apache/sis/image/ImageOperations.java | 242 ++++++++++++++++-----
.../apache/sis/image/StatisticsCalculatorTest.java | 13 +-
3 files changed, 197 insertions(+), 63 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageRenderings.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageRenderings.java
index 2de16be..0b33812 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageRenderings.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/ImageRenderings.java
@@ -37,7 +37,10 @@ public final class ImageRenderings {
* @todo Creates our own instance which listen to logging messages.
* We need to create a logging panel first.
*/
- private static final ImageOperations OPERATIONS = ImageOperations.LENIENT;
+ private static final ImageOperations OPERATIONS = new ImageOperations();
+ static {
+ OPERATIONS.setErrorAction(ImageOperations.ErrorAction.LOG);
+ }
/**
* Do not allow instantiation of this class.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/image/ImageOperations.java b/core/sis-feature/src/main/java/org/apache/sis/image/ImageOperations.java
index e926c3c..9beda4d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ImageOperations.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ImageOperations.java
@@ -29,12 +29,39 @@ import org.apache.sis.internal.coverage.j2d.ImageUtilities;
/**
* A predefined set of operations on images as convenience methods.
- * Operations can be executed in parallel if applied on image with a thread-safe
- * and concurrent implementation of {@link RenderedImage#getTile(int, int)}.
- * Otherwise the same operations can be executed sequentially in the caller thread.
- * Errors during calculation can either be propagated as an {@link ImagingOpException}
- * (in which case no result is available), or notified as a {@link LogRecord}
- * (in which case partial results may be available).
+ * After instantiation, {@code ImageOperations} can be configured for the following aspects:
+ *
+ * <ul class="verbose">
+ * <li>
+ * Whether operations can be executed in parallel. By default operations on unknown
+ * {@link RenderedImage} implementations are executed sequentially in the caller thread,
for safety reasons.
+ * Some operations can be parallelized, but it should be enabled only if the {@link RenderedImage}
is known
+ * to be thread-safe and has concurrent (or fast) implementation of {@link RenderedImage#getTile(int,
int)}.
+ * Apache SIS implementations of {@link RenderedImage} can be parallelized, but it may
not be the case of
+ * images from other libraries.
+ * </li><li>
+ * Whether the operations should fail if an exception is thrown while processing a tile.
+ * By default errors during calculation are propagated as an {@link ImagingOpException},
+ * in which case no result is available. But errors can also be notified as a {@link
LogRecord} instead,
+ * in which case partial results may be available.
+ * </li>
+ * </ul>
+ *
+ * <h2>Error handling</h2>
+ * If an exception occurs during the computation of a tile, then the {@code ImageOperations}
behavior
+ * is controlled by the {@link #getErrorAction() errorAction} property:
+ *
+ * <ul>
+ * <li>If {@link ErrorAction#THROW}, the exception is wrapped in an {@link ImagingOpException}
and thrown.</li>
+ * <li>If {@link ErrorAction#LOG}, the exception is logged and a partial result is
returned.</li>
+ * <li>If any other value, the exception is wrapped in a {@link LogRecord} and sent
to that filter.
+ * The filter can store the log record, for example for showing later in a graphical
user interface (GUI).
+ * If the filter returns {@code true}, the log record is also logged, otherwise it is
silently discarded.
+ * In both cases a partial result is returned.</li>
+ * </ul>
+ *
+ * <h2>Thread-safety</h2>
+ * {@code ImageOperations} is thread-safe if its configuration is not modified after construction.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
@@ -43,85 +70,182 @@ import org.apache.sis.internal.coverage.j2d.ImageUtilities;
*/
public class ImageOperations {
/**
- * The set of operations with default configuration. Operations executed by this instance
- * will be multi-threaded if possible, and failures to compute a value cause an exception
- * to be thrown.
+ * Execution modes specifying whether operations can be executed in parallel.
+ * If {@link #SEQUENTIAL}, operations are executed sequentially in the caller thread.
+ * If {@link #PARALLEL}, some operations may be parallelized using an arbitrary number
of threads.
+ *
+ * @see #getExecutionMode()
+ * @see #setExecutionMode(Mode)
*/
- public static final ImageOperations PARALLEL = new ImageOperations(true, true, null);
+ public enum Mode {
+ /**
+ * Operations executed in multi-threaded mode if possible.
+ * This mode can be used if the {@link RenderedImage} instances are thread-safe and
provide
+ * a concurrent (or very fast) implementation of {@link RenderedImage#getTile(int,
int)}.
+ */
+ PARALLEL,
- /**
- * The set of operations where all executions are constrained to a single thread.
- * Only the caller thread is used, with no parallelization. Sequential operations
- * may be useful for processing {@link RenderedImage} that may not be thread-safe.
- * The error handling policy is the same than {@link #PARALLEL}.
- */
- public static final ImageOperations SEQUENTIAL = new ImageOperations(false, true, null);
+ /**
+ * Operations executed in the caller thread, without parallelization.
+ * Sequential operations may be useful for processing {@link RenderedImage}
+ * implementations that may not be thread-safe.
+ */
+ SEQUENTIAL,
+
+ /**
+ * Operations are executed in multi-threaded mode if the {@link RenderedImage} instance
+ * is an implementation known to be thread-safe. All operations on image implementations
+ * unknown to Apache SIS are executed in sequential mode.
+ */
+ DEFAULT
+ }
/**
- * The set of operations executed without throwing an exception in case of failure.
- * Instead the warnings are logged. Whether the operations are executed in parallel
- * or not is implementation dependent.
+ * Specifies how exceptions occurring during calculation should be handled.
+ * This enumeration provides common actions, but the set of values that can
+ * be specified to {@link #setErrorAction(Filter)} is not limited to this enumeration.
*
- * <p>Users should prefer {@link #PARALLEL} or {@link #SEQUENTIAL} in most cases
since the use
- * of {@code LENIENT} may cause errors to be unnoticed (not everyone read log messages).</p>
+ * @see #getErrorAction()
+ * @see #setErrorAction(Filter)
*/
- public static final ImageOperations LENIENT = new ImageOperations(true, false, null);
+ public enum ErrorAction implements Filter {
+ /**
+ * Exceptions are wrapped in an {@link ImagingOpException} and thrown.
+ * In such case, no result is available. This is the default action.
+ */
+ THROW,
+
+ /**
+ * Exceptions are wrapped in a {@link LogRecord} and logged at {@link java.util.logging.Level#WARNING}.
+ * Only one log record is created for all tiles that failed for the same operation
on the same image.
+ * A partial result may be available.
+ *
+ * <p>Users are encouraged to use {@link #THROW} or to specify their own {@link
Filter}
+ * instead than using this error action, because not everyone read logging records.</p>
+ */
+ LOG;
+
+ /**
+ * Unconditionally returns {@code true} for allowing the given record to be logged.
+ * This method is not useful for this {@code ErrorAction} enumeration, but is useful
+ * for other instances given to {@link #setErrorAction(Filter)}.
+ *
+ * @param record the error that occurred during computation of a tile.
+ * @return always {@code true}.
+ */
+ @Override
+ public boolean isLoggable(final LogRecord record) {
+ return true;
+ }
+ }
/**
* Whether the operations can be executed in parallel.
+ *
+ * @see #getExecutionMode()
+ * @see #setExecutionMode(Mode)
*/
- private final boolean parallel;
+ private Mode executionMode;
/**
- * Whether errors occurring during computation should be propagated instead than wrapped
in a {@link LogRecord}.
+ * Whether errors occurring during computation should be propagated or wrapped in a {@link
LogRecord}.
+ * If errors are wrapped in a {@link LogRecord}, this field specifies what to do with
the record.
+ * Only one log record is created for all tiles that failed for the same operation on
the same image.
+ *
+ * @see #getErrorAction()
+ * @see #setErrorAction(Filter)
*/
- private final boolean failOnException;
+ private Filter errorAction;
/**
- * Where to send exceptions (wrapped in {@link LogRecord}) if an operation failed on
one or more tiles.
- * Only one log record is created for all tiles that failed for the same operation on
the same image.
- * This is always {@code null} if {@link #failOnException} is {@code true}.
+ * Creates a new set of image operations with default configuration.
+ * The execution mode is initialized to {@link Mode#DEFAULT} and the error action to
{@link ErrorAction#THROW}.
*/
- private final Filter errorListener;
+ public ImageOperations() {
+ executionMode = Mode.DEFAULT;
+ errorAction = ErrorAction.THROW;
+ }
/**
- * Creates a new set of image operations.
+ * Returns whether operations can be executed in parallel.
+ * If {@link Mode#SEQUENTIAL}, operations are executed sequentially in the caller thread.
+ * If {@link Mode#PARALLEL}, some operations may be parallelized using an arbitrary number
of threads.
*
- * <h4>Error handling</h4>
- * If an exception occurs during the computation of a tile, then the {@code ImageOperations}
behavior
- * is controlled by the following parameters:
+ * @return whether the operations can be executed in parallel.
+ */
+ public Mode getExecutionMode() {
+ return executionMode;
+ }
+
+ /**
+ * Sets whether operations can be executed in parallel.
+ * This value can be set to {@link Mode#PARALLEL} if the {@link RenderedImage} instances
are thread-safe
+ * and provide a concurrent (or very fast) implementation of {@link RenderedImage#getTile(int,
int)}.
+ * If {@link Mode#SEQUENTIAL}, only the caller thread is used. Sequential operations
may be useful
+ * for processing {@link RenderedImage} implementations that may not be thread-safe.
*
- * <ul>
- * <li>If {@code failOnException} is {@code true}, the exception is thrown as
an {@link ImagingOpException}.</li>
- * <li>If {@code failOnException} is {@code false}, then:<ul>
- * <li>If {@code errorListener} is {@code null}, the exception is logged and
a partial result is returned.</li>
- * <li>If {@code errorListener} is non-null, the exception is wrapped in a
{@link LogRecord} and sent to that handler.
- * The listener can store the log record, for example for showing later in a
graphical user interface (GUI).
- * If the listener returns {@code true}, the log record is also logged, otherwise
it is silently discarded.
- * In both cases a partial result is returned.</li>
- * </ul>
- * </li>
- * </ul>
+ * <p>It is safe to set this flag to {@link Mode#PARALLEL} with {@link java.awt.image.BufferedImage}
+ * (it will actually have no effect in this particular case) or with Apache SIS implementations
of
+ * {@link RenderedImage}.</p>
*
- * @param parallel whether the operations can be executed in parallel.
- * @param failOnException whether exceptions occurring during computation should be
propagated.
- * @param errorListener handler to notify when an operation failed on one or more
tiles,
- * or {@code null} for printing the exceptions with the default
logger.
- * This is ignored if {@code failOnException} is {@code true}.
+ * @param mode whether the operations can be executed in parallel.
*/
- public ImageOperations(final boolean parallel, final boolean failOnException, final Filter
errorListener) {
- this.parallel = parallel;
- this.failOnException = failOnException;
- this.errorListener = failOnException ? null : errorListener;
+ public void setExecutionMode(final Mode mode) {
+ ArgumentChecks.ensureNonNull("mode", mode);
+ executionMode = mode;
}
/**
* Whether the operations can be executed in parallel for the specified image.
- * Should be a method overridden by {@link #LENIENT}, but for this simple need
- * it is not yet worth to do sub-classing.
*/
private boolean parallel(final RenderedImage source) {
- return (this == LENIENT) ? source.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX)
: parallel;
+ switch (executionMode) {
+ case PARALLEL: return true;
+ case SEQUENTIAL: return false;
+ default: return source.getClass().getName().startsWith(Modules.CLASSNAME_PREFIX);
+ }
+ }
+
+ /**
+ * Returns whether exceptions occurring during computation are propagated or logged.
+ * If {@link ErrorAction#THROW} (the default), exceptions are wrapped in {@link ImagingOpException}
and thrown.
+ * If any other value, exceptions are wrapped in a {@link LogRecord}, filtered then eventually
logged.
+ *
+ * @return whether exceptions occurring during computation are propagated or logged.
+ */
+ public Filter getErrorAction() {
+ return errorAction;
+ }
+
+ /**
+ * Sets whether exceptions occurring during computation are propagated or logged.
+ * The default behavior is to wrap exceptions in {@link ImagingOpException} and throw
them.
+ * If this property is set to {@link ErrorAction#LOG} or any other value, then exceptions
will
+ * be wrapped in {@link LogRecord} instead, in which case a partial result may be available.
+ * Only one log record is created for all tiles that failed for the same operation on
the same image.
+ *
+ * @param action filter to notify when an operation failed on one or more tiles,
+ * or {@link ErrorAction#THROW} for propagating the exception.
+ */
+ public void setErrorAction(final Filter action) {
+ ArgumentChecks.ensureNonNull("action", action);
+ errorAction = action;
+ }
+
+ /**
+ * Whether errors occurring during computation should be propagated instead than wrapped
in a {@link LogRecord}.
+ */
+ private boolean failOnException() {
+ return errorAction == ErrorAction.THROW;
+ }
+
+ /**
+ * Where to send exceptions (wrapped in {@link LogRecord}) if an operation failed on
one or more tiles.
+ * Only one log record is created for all tiles that failed for the same operation on
the same image.
+ * This is always {@code null} if {@link #failOnException()} is {@code true}.
+ */
+ private Filter errorListener() {
+ return (errorAction instanceof ErrorAction) ? null : errorAction;
}
/**
@@ -133,9 +257,9 @@ public class ImageOperations {
*/
public Statistics[] statistics(final RenderedImage source) {
ArgumentChecks.ensureNonNull("source", source);
- final StatisticsCalculator calculator = new StatisticsCalculator(source, parallel(source),
failOnException);
+ final StatisticsCalculator calculator = new StatisticsCalculator(source, parallel(source),
failOnException());
final Object property = calculator.getProperty(StatisticsCalculator.PROPERTY_NAME);
- calculator.logAndClearError(ImageOperations.class, "statistics", errorListener);
+ calculator.logAndClearError(ImageOperations.class, "statistics", errorListener());
if (property instanceof Statistics[]) {
return (Statistics[]) property;
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
b/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
index 7be7cc1..47de299 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/StatisticsCalculatorTest.java
@@ -80,9 +80,11 @@ public final strictfp class StatisticsCalculatorTest extends TestCase {
*/
@Test
public void testParallelExecution() {
+ final ImageOperations operations = new ImageOperations();
+ operations.setExecutionMode(ImageOperations.Mode.PARALLEL);
final TiledImageMock image = createImage();
final Statistics[] expected = StatisticsCalculator.computeSequentially(image);
- final Statistics[] actual = ImageOperations.PARALLEL.statistics(image);
+ final Statistics[] actual = operations.statistics(image);
for (int i=0; i<expected.length; i++) {
final Statistics e = expected[i];
final Statistics a = actual [i];
@@ -98,10 +100,12 @@ public final strictfp class StatisticsCalculatorTest extends TestCase
{
*/
@Test
public void testWithFailures() {
+ final ImageOperations operations = new ImageOperations();
+ operations.setExecutionMode(ImageOperations.Mode.PARALLEL);
final TiledImageMock image = createImage();
image.failRandomly(new Random(-8739538736973900203L));
try {
- ImageOperations.PARALLEL.statistics(image);
+ operations.statistics(image);
fail("Expected ImagingOpException.");
} catch (ImagingOpException e) {
final String message = e.getMessage();
@@ -116,9 +120,12 @@ public final strictfp class StatisticsCalculatorTest extends TestCase
{
*/
@Test
public void testWithLoggings() {
+ final ImageOperations operations = new ImageOperations();
+ operations.setExecutionMode(ImageOperations.Mode.PARALLEL);
+ operations.setErrorAction(ImageOperations.ErrorAction.LOG);
final TiledImageMock image = createImage();
image.failRandomly(new Random(8004277484984714811L));
- final Statistics[] stats = ImageOperations.LENIENT.statistics(image);
+ final Statistics[] stats = operations.statistics(image);
for (final Statistics a : stats) {
assertTrue(a.count() > 0);
}
|