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 4fd2a67 Try to provide a better coverage of error handling during
`CoverageCanvas` rendering: - Replace the various
`ErrorHander.Report.addFoo(…)` methods by a single `add(…)` method giving more
control on the `LogRecord` content. - Make `ErrorHandler.Report` thread-safe
and document the synchronization lock to use when modifying the `LogRecord`
content. - Allow `PrefetchedImage` to perform rendering in a mode where
exceptions are caught and tiles are replaced by place-holders. [...]
4fd2a67 is described below
commit 4fd2a672a76958d103175a7925bc12986571731b
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Feb 8 11:20:16 2021 +0100
Try to provide a better coverage of error handling during `CoverageCanvas`
rendering:
- Replace the various `ErrorHander.Report.addFoo(…)` methods by a single
`add(…)` method giving more control on the `LogRecord` content.
- Make `ErrorHandler.Report` thread-safe and document the synchronization
lock to use when modifying the `LogRecord` content.
- Allow `PrefetchedImage` to perform rendering in a mode where exceptions
are caught and tiles are replaced by place-holders.
- Use above-cited rendering mode in `CoverageCanvas`.
---
.../apache/sis/gui/coverage/CoverageCanvas.java | 37 +++++-
.../java/org/apache/sis/gui/map/MapCanvas.java | 12 +-
.../java/org/apache/sis/image/AnnotatedImage.java | 65 ++++-------
.../java/org/apache/sis/image/ErrorAction.java | 36 +++---
.../java/org/apache/sis/image/ErrorHandler.java | 125 +++++++++-----------
.../java/org/apache/sis/image/PrefetchedImage.java | 84 +++++++++++--
.../internal/coverage/j2d/TileErrorHandler.java | 123 +++++++++++++++++++
.../sis/internal/coverage/j2d/TileOpExecutor.java | 130 ++++++++++-----------
8 files changed, 400 insertions(+), 212 deletions(-)
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
index 949c2ae..ff1a09e 100644
---
a/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
+++
b/application/sis-javafx/src/main/java/org/apache/sis/gui/coverage/CoverageCanvas.java
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Locale;
import java.util.concurrent.Future;
import java.util.function.Function;
+import java.util.logging.LogRecord;
import java.io.IOException;
import java.awt.Graphics2D;
import java.awt.Rectangle;
@@ -55,21 +56,23 @@ import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.ImageRenderer;
-import org.apache.sis.internal.gui.ExceptionReporter;
import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.geometry.Shapes2D;
import org.apache.sis.image.PlanarImage;
+import org.apache.sis.image.ErrorHandler;
import org.apache.sis.image.Interpolation;
import org.apache.sis.coverage.Category;
import org.apache.sis.gui.map.MapCanvas;
import org.apache.sis.gui.map.MapCanvasAWT;
import org.apache.sis.gui.map.StatusBar;
import org.apache.sis.portrayal.RenderException;
+import org.apache.sis.internal.coverage.j2d.TileErrorHandler;
import org.apache.sis.internal.processing.image.Isolines;
import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.ExceptionReporter;
import org.apache.sis.internal.gui.GUIUtilities;
import org.apache.sis.internal.gui.LogHandler;
import org.apache.sis.internal.system.Modules;
@@ -525,7 +528,7 @@ public class CoverageCanvas extends MapCanvasAWT {
* <li>Paint the image.</li>
* </ol>
*/
- private static final class Worker extends Renderer {
+ private static final class Worker extends Renderer implements ErrorHandler
{
/**
* Value of {@link CoverageCanvas#data} at the time this worker has
been initialized.
*/
@@ -608,6 +611,11 @@ public class CoverageCanvas extends MapCanvasAWT {
private IsolineRenderer.Snapshot[] isolines;
/**
+ * If errors occurred during tile computations, details about the
error. Otherwise {@code null}.
+ */
+ private LogRecord errorReport;
+
+ /**
* Creates a new renderer. Shall be invoked in JavaFX thread.
*/
Worker(final CoverageCanvas canvas) {
@@ -721,7 +729,13 @@ public class CoverageCanvas extends MapCanvasAWT {
@Override
protected void paint(final Graphics2D gr) {
gr.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
- gr.drawRenderedImage(prefetchedImage, resampledToDisplay);
+ if (prefetchedImage instanceof TileErrorHandler.Executor) {
+ ((TileErrorHandler.Executor) prefetchedImage).execute(
+ () -> gr.drawRenderedImage(prefetchedImage,
resampledToDisplay),
+ new TileErrorHandler(this, CoverageCanvas.class,
"paint"));
+ } else {
+ gr.drawRenderedImage(prefetchedImage, resampledToDisplay);
+ }
if (isolines != null) {
final AffineTransform at = gr.getTransform();
final Stroke st = gr.getStroke();
@@ -737,6 +751,16 @@ public class CoverageCanvas extends MapCanvasAWT {
}
/**
+ * Invoked if an error occurred during a call to {@link
RenderedImage#getTile(int, int)}.
+ * This method stores information about the error and let the
rendering process continue
+ * with a tile placeholder (by default a cross (X) in a box).
+ */
+ @Override
+ public void handle(final Report details) {
+ errorReport = details.getDescription();
+ }
+
+ /**
* Invoked in JavaFX thread after successful {@link
#paint(Graphics2D)} completion.
* This method stores the computation results.
*/
@@ -796,6 +820,13 @@ public class CoverageCanvas extends MapCanvasAWT {
}
statusBar.setLowestAccuracy(accuracy);
}
+ /*
+ * If error(s) occurred during calls to `RenderedImage.getTile(tx,
ty)`, reports those errors.
+ */
+ final LogRecord errorReport = worker.errorReport;
+ if (errorReport != null) {
+ errorOccurred(errorReport.getThrown());
+ }
}
/**
diff --git
a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
index a5f403b..8c4cf60 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/MapCanvas.java
@@ -1048,8 +1048,11 @@ public abstract class MapCanvas extends PlanarCanvas {
yPanStart = p.getY();
changeInProgress.setToIdentity();
transform.setToTransform(transformOnNewImage);
- error.set(task.getException());
isRendering.set(false);
+ final Throwable ex = task.getException();
+ if (ex != null) {
+ errorOccurred(ex);
+ }
}
/**
@@ -1167,7 +1170,12 @@ public abstract class MapCanvas extends PlanarCanvas {
* @param ex the exception that occurred (can not be null).
*/
protected void errorOccurred(final Throwable ex) {
- error.set(Objects.requireNonNull(ex));
+ final Throwable current = error.get();
+ if (current != null) {
+ current.addSuppressed(ex);
+ } else {
+ error.set(Objects.requireNonNull(ex));
+ }
}
/**
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
b/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
index 8bbae85..7d669fb 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/AnnotatedImage.java
@@ -16,8 +16,10 @@
*/
package org.apache.sis.image;
+import java.util.Locale;
import java.util.Objects;
import java.util.WeakHashMap;
+import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.stream.Collector;
import java.awt.Image;
@@ -29,7 +31,6 @@ import java.awt.image.ImagingOpException;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.collection.Cache;
-import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
import org.apache.sis.internal.coverage.j2d.ImageUtilities;
import org.apache.sis.internal.util.Strings;
@@ -336,23 +337,21 @@ abstract class AnnotatedImage extends ImageAdapter {
if (value == null) value = NULL;
success = (errors == null);
} catch (Exception e) {
- /*
- * Stores the given exception in a log record. We use
a log record in order to initialize
- * the timestamp and thread ID to the values they had
at the time the first error occurred.
- */
if (failOnException) {
throw (ImagingOpException) new ImagingOpException(
Errors.format(Errors.Keys.CanNotCompute_1,
property)).initCause(e);
}
- synchronized (this) {
- ErrorHandler.Report report = errors;
- final boolean create = (report == null);
- if (create) {
- report = new ErrorHandler.Report();
- }
- report.addPropertyError(e, property);
- if (create) setError(report);
+ /*
+ * Stores the exception in a log record. We use a log
record in order to initialize
+ * the timestamp and thread ID to the values they had
at the time the error occurred.
+ * We do not synchronize because all worker threads
should have finished now.
+ */
+ ErrorHandler.Report report = errors;
+ if (report == null) {
+ errors = report = new ErrorHandler.Report();
}
+ report.add(null, e, () -> Errors.getResources((Locale)
null)
+ .getLogRecord(Level.WARNING,
Errors.Keys.CanNotCompute_1, property));
}
} finally {
handler.putAndUnlock(success ? value : null); //
Cache only if no error occurred.
@@ -369,26 +368,6 @@ abstract class AnnotatedImage extends ImageAdapter {
}
/**
- * Invoked by {@link TileOpExecutor} if an error occurred during
calculation on a tiles.
- * Can also be invoked by {@link #getProperty(String)} directly if the
error occurred
- * outside {@link TileOpExecutor}. This method should be invoked at most
once, unless
- * the calculation is attempted again.
- *
- * @param report a description of the error that occurred.
- */
- private void setError(final ErrorHandler.Report report) {
- /*
- * Complete record with source identification as if the error occurred
from
- * above `getProperty(String)` method (this is always the case,
indirectly).
- */
- final LogRecord record = report.getDescription();
- record.setSourceClassName(AnnotatedImage.class.getCanonicalName());
- record.setSourceMethodName("getProperty");
- record.setLoggerName(Modules.RASTER);
- errors = report;
- }
-
- /**
* If an error occurred, logs the message with the specified class and
method as the source.
* The {@code classe} and {@code method} arguments overwrite the {@link
LogRecord#getSourceClassName()}
* and {@link LogRecord#getSourceMethodName()} values. The log record is
cleared by this method call
@@ -404,18 +383,16 @@ abstract class AnnotatedImage extends ImageAdapter {
* @param handler where to send the log message.
*/
final void logAndClearError(final Class<?> classe, final String method,
final ErrorHandler handler) {
- final ErrorHandler.Report report;
- synchronized (this) {
- report = errors;
- if (report == null || report.isEmpty()) {
- return;
+ final ErrorHandler.Report report = errors;
+ if (report != null) {
+ synchronized (report) {
+ final LogRecord record = report.getDescription();
+ record.setSourceClassName(classe.getCanonicalName());
+ record.setSourceMethodName(method);
+ errors = null;
}
- final LogRecord record = report.getDescription();
- record.setSourceClassName(classe.getCanonicalName());
- record.setSourceMethodName(method);
- errors = null; // Make sure that no other thread will use
that `Report` instance.
+ handler.handle(report);
}
- handler.handle(report);
}
/**
@@ -442,7 +419,7 @@ abstract class AnnotatedImage extends ImageAdapter {
final Collector<? super Raster,?,?> collector = collector();
if (collector != null) {
if (!failOnException) {
- executor.setErrorHandler(this::setError);
+ executor.setErrorHandler((e) -> errors = e,
AnnotatedImage.class, "getProperty");
}
return executor.executeOnReadable(source, collector());
}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/image/ErrorAction.java
b/core/sis-feature/src/main/java/org/apache/sis/image/ErrorAction.java
index d441f00..2691f8a 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ErrorAction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ErrorAction.java
@@ -56,24 +56,26 @@ enum ErrorAction implements ErrorHandler {
*/
@Override
public void handle(final Report details) {
- final LogRecord record = details.getDescription();
- if (record != null) {
- if (this == LOG) {
- String logger = record.getLoggerName();
- if (logger == null) {
- logger = Modules.RASTER;
- record.setLoggerName(logger);
- }
- Logging.getLogger(logger).log(record);
- } else {
- final Throwable ex = record.getThrown();
- if (ex instanceof Error) {
- throw (Error) ex;
- } else if (ex instanceof ImagingOpException) {
- throw (ImagingOpException) ex;
+ synchronized (details) {
+ final LogRecord record = details.getDescription();
+ if (record != null) {
+ if (this == LOG) {
+ String logger = record.getLoggerName();
+ if (logger == null) {
+ logger = Modules.RASTER;
+ record.setLoggerName(logger);
+ }
+ Logging.getLogger(logger).log(record);
} else {
- final String message = new
SimpleFormatter().formatMessage(record);
- throw (ImagingOpException) new
ImagingOpException(message).initCause(ex);
+ final Throwable ex = record.getThrown();
+ if (ex instanceof Error) {
+ throw (Error) ex;
+ } else if (ex instanceof ImagingOpException) {
+ throw (ImagingOpException) ex;
+ } else {
+ final String message = new
SimpleFormatter().formatMessage(record);
+ throw (ImagingOpException) new
ImagingOpException(message).initCause(ex);
+ }
}
}
}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/image/ErrorHandler.java
b/core/sis-feature/src/main/java/org/apache/sis/image/ErrorHandler.java
index 0c940b6..3190120 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/ErrorHandler.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/ErrorHandler.java
@@ -18,13 +18,12 @@ package org.apache.sis.image;
import java.awt.Point;
import java.util.Arrays;
-import java.util.Locale;
import java.util.Objects;
+import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.awt.image.ImagingOpException;
import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.resources.Errors;
import org.apache.sis.internal.feature.Resources;
@@ -45,7 +44,7 @@ public interface ErrorHandler {
ErrorHandler THROW = ErrorAction.THROW;
/**
- * Exceptions are wrapped in a {@link LogRecord} and logged at {@link
java.util.logging.Level#WARNING}.
+ * Exceptions are wrapped in a {@link LogRecord} and logged, usually at
{@link 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.
*
@@ -58,13 +57,25 @@ public interface ErrorHandler {
* Invoked after errors occurred in one or many tiles. This method may be
invoked an arbitrary
* time after the error occurred, and may aggregate errors that occurred
in more than one tile.
*
- * @param details information about errors.
+ * <h4>Multi-threading</h4>
+ * If the image processing was splitted between many worker threads, this
method may be invoked
+ * from any of those threads. However the invocation should happen after
all threads terminated,
+ * either successfully or with an error reported in {@code details}.
+ *
+ * @param details information about the first error. If more than one
error occurred, the other
+ * errors are reported as {@linkplain Throwable#getSuppressed()
suppressed exceptions}.
*/
void handle(Report details);
/**
* Information about errors that occurred while reading or writing tiles
in an image.
* A single {@code Report} may be generated for failures in more than one
tiles.
+ *
+ * <h2>Multi-threading</h2>
+ * This class is safe for use in multi-threading. The synchronization lock
is {@code this}.
+ * However the {@link LogRecord} instance returned by {@link
#getDescription()} is not thread-safe.
+ * Operations applied on the {@code LogRecord} should be inside a block
synchronized on the
+ * {@code Report.this} lock.
*/
class Report {
/**
@@ -84,18 +95,18 @@ public interface ErrorHandler {
/**
* Creates an initially empty report.
- * Error reports can be added by calls to {@code add(Throwable, …)}
methods.
+ * Error reports can be added by calls to {@link #add add(…)}.
*/
public Report() {
}
/**
* Returns {@code true} if no error has been reported.
- * This is true only if no {@code add(Throwable, …)} method had been
invoked.
+ * This is true only if the {@link #add add(…)} method has never been
invoked.
*
* @return whether this report is empty.
*/
- public boolean isEmpty() {
+ public synchronized boolean isEmpty() {
return length == 0 && description == null;
}
@@ -104,7 +115,8 @@ public interface ErrorHandler {
* and the same stack trace. The cause and the suppressed exceptions
are ignored.
*/
private static boolean equals(final Throwable t1, final Throwable t2) {
- return t1.getClass() == t2.getClass()
+ if (t1 == t2) return true;
+ return t1 != null && t2 != null && t1.getClass() == t2.getClass()
&& Objects.equals(t1.getMessage(), t2.getMessage())
&& Arrays.equals(t1.getStackTrace(), t2.getStackTrace());
}
@@ -113,7 +125,9 @@ public interface ErrorHandler {
* Adds the {@code more} exception to the list if suppressed
exceptions if not already present.
*/
private static void addSuppressed(final Throwable error, final
Throwable more) {
- if (equals(error, more)) return;
+ if (equals(error, more) || equals(error.getCause(), more)) {
+ return;
+ }
for (final Throwable s : error.getSuppressed()) {
if (equals(s, more)) return;
}
@@ -121,77 +135,50 @@ public interface ErrorHandler {
}
/**
- * Reports an error that occurred while computing an image property.
- * This method can be invoked many times on the same {@code Report}
instance.
- *
- * <h4>Logging information</h4>
- * {@code Report} creates a {@link LogRecord} the first time that an
{@code add(…)} method is invoked.
- * The record will have its
- * {@linkplain LogRecord#getLevel() level},
- * {@linkplain LogRecord#getMessage() message} and
- * {@linkplain LogRecord#getThrown() exception} properties
initialized. But the
- * {@linkplain LogRecord#getSourceClassName() source class name},
- * {@linkplain LogRecord#getSourceMethodName() source method name} and
- * {@linkplain LogRecord#getLoggerName() logger name} may be undefined;
- * they may need to be completed by the caller.
- *
- * @param error the error that occurred.
- * @param property name of the property which was computed, or
{@code null} if none.
- * @return {@code true} if this is the first time that an error is
reported
- * (in which case a {@link LogRecord} instance has been
created),
- * or {@code false} if a {@link LogRecord} already exists.
- */
- public boolean addPropertyError(final Throwable error, final String
property) {
- ArgumentChecks.ensureNonNull("error", error);
- if (description == null) {
- if (property != null) {
- description = Errors.getResources((Locale) null)
- .getLogRecord(Level.WARNING,
Errors.Keys.CanNotCompute_1, property);
- } else {
- description = new LogRecord(Level.WARNING,
error.toString());
- }
- description.setThrown(error);
- return true;
- } else {
- addSuppressed(description.getThrown(), error);
- return false;
- }
- }
-
- /**
* Reports an error that occurred while computing an image tile.
* This method can be invoked many times on the same {@code Report}
instance.
*
* <h4>Logging information</h4>
- * {@code Report} creates a {@link LogRecord} the first time that an
{@code add(…)} method is invoked.
- * The record will have its
+ * {@code Report} creates a {@link LogRecord} the first time that this
{@code add(…)} method is invoked.
+ * The log record is created using the given supplier if non-null.
That supplier should set the log
* {@linkplain LogRecord#getLevel() level},
- * {@linkplain LogRecord#getMessage() message} and
- * {@linkplain LogRecord#getThrown() exception} properties
initialized. But the
+ * {@linkplain LogRecord#getMessage() message},
* {@linkplain LogRecord#getSourceClassName() source class name},
* {@linkplain LogRecord#getSourceMethodName() source method name} and
- * {@linkplain LogRecord#getLoggerName() logger name} may be undefined;
- * they may need to be completed by the caller.
+ * {@linkplain LogRecord#getLoggerName() logger name}.
+ * The {@linkplain LogRecord#getThrown() exception} property will be
set by this method.
*
- * @param error the error that occurred.
- * @param tx column index of the tile where the error occurred.
- * @param ty row index of the tile where the error occurred.
+ * @param tile column (x) and row (y) indices of the tile where
the error occurred, or {@code null} if unknown.
+ * @param error the error that occurred.
+ * @param record the record supplier, invoked only when this method
is invoked for the first time.
+ * If {@code null}, a default {@link LogRecord} will
be created.
* @return {@code true} if this is the first time that an error is
reported
* (in which case a {@link LogRecord} instance has been
created),
* or {@code false} if a {@link LogRecord} already exists.
*/
- public boolean addTileError(final Throwable error, final int tx, final
int ty) {
+ public synchronized boolean add(final Point tile, final Throwable
error, final Supplier<LogRecord> record) {
ArgumentChecks.ensureNonNull("error", error);
- if (indices == null) {
- indices = new int[8];
- } else if (length >= indices.length) {
- indices = Arrays.copyOf(indices, indices.length * 2);
+ if (tile != null) {
+ if (indices == null) {
+ indices = new int[8];
+ } else if (length >= indices.length) {
+ indices = Arrays.copyOf(indices, indices.length * 2);
+ }
+ indices[length++] = tile.x;
+ indices[length++] = tile.y;
}
- indices[length++] = tx;
- indices[length++] = ty;
if (description == null) {
- description = Resources.forLocale(null)
- .getLogRecord(Level.WARNING,
Resources.Keys.CanNotProcessTile_2, tx, ty);
+ if (record != null) {
+ description = record.get();
+ }
+ if (description == null) {
+ if (tile != null) {
+ description = Resources.forLocale(null)
+ .getLogRecord(Level.WARNING,
Resources.Keys.CanNotProcessTile_2, tile.x, tile.y);
+ } else {
+ description = new LogRecord(Level.WARNING,
error.toString());
+ }
+ }
description.setThrown(error);
return true;
} else {
@@ -205,7 +192,7 @@ public interface ErrorHandler {
*
* @return indices of all tiles in error, or an empty array if none.
*/
- public Point[] getTileIndices() {
+ public synchronized Point[] getTileIndices() {
final Point[] p = new Point[length >>> 1];
for (int i=0; i<length;) {
p[i >>> 1] = new Point(indices[i++], indices[i++]);
@@ -214,13 +201,15 @@ public interface ErrorHandler {
}
/**
- * Returns a description of errors as a log record.
+ * Returns a description of the first error as a log record.
* The exception can be obtained by {@link LogRecord#getThrown()}.
+ * If more than one error occurred, the other errors are reported
+ * as {@linkplain Throwable#getSuppressed() suppressed exceptions}.
* The return value is never null unless this report {@linkplain
#isEmpty() is empty}.
*
* @return errors description, or {@code null} if this report is empty.
*/
- public LogRecord getDescription() {
+ public synchronized LogRecord getDescription() {
return description;
}
}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
b/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
index 3dea68e..453cd8d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/image/PrefetchedImage.java
@@ -16,21 +16,24 @@
*/
package org.apache.sis.image;
-import java.awt.Point;
import java.util.Arrays;
import java.util.Vector;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
import java.awt.image.SampleModel;
import java.awt.image.RenderedImage;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.awt.image.RasterFormatException;
import org.apache.sis.internal.coverage.j2d.ImageUtilities;
+import org.apache.sis.internal.coverage.j2d.TileErrorHandler;
import org.apache.sis.internal.coverage.j2d.TileOpExecutor;
import org.apache.sis.internal.util.Numerics;
import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.ArgumentChecks;
/**
@@ -45,7 +48,7 @@ import org.apache.sis.util.resources.Errors;
* @since 1.1
* @module
*/
-final class PrefetchedImage extends PlanarImage {
+final class PrefetchedImage extends PlanarImage implements
TileErrorHandler.Executor {
/**
* The source image from which to prefetch tiles.
*/
@@ -72,6 +75,22 @@ final class PrefetchedImage extends PlanarImage {
private final Raster[] tiles;
/**
+ * If error(s) occurred while computing one or more tiles, data shared by
{@link Raster} placeholders.
+ * This is data for a tile showing a cross (X) in a box.
+ *
+ * @see #createPlaceholder(int, int)
+ */
+ private DataBuffer placeholderPixels;
+
+ /**
+ * Non-null if errors should be handled during {@link #getTile(int, int)}
execution for tiles outside
+ * the area of interest specified at construction time.
+ *
+ * @see #execute(Runnable, TileErrorHandler)
+ */
+ private ErrorHandler.Report errorReport;
+
+ /**
* Creates a new prefetched image.
*
* @param source the image to compute immediately (may be {@code
null}).
@@ -102,7 +121,7 @@ final class PrefetchedImage extends PlanarImage {
numXTiles = ti.width;
numYTiles = ti.height;
tiles = new Raster[Math.multiplyExact(numYTiles, numXTiles)];
- worker.setErrorHandler(errorHandler);
+ worker.setErrorHandler(errorHandler, ImageProcessor.class, "prefetch");
if (parallel) {
worker.parallelReadFrom(source);
} else {
@@ -113,12 +132,11 @@ final class PrefetchedImage extends PlanarImage {
* to that tile still null. Replace them by a placeholder. Note that
it may happen
* only if the error handler is not `ErrorHandler.THROW`.
*/
- Raster previous = null;
for (int i=0; i<tiles.length; i++) {
if (tiles[i] == null) {
final int tileX = (i % numXTiles) + minTileX;
final int tileY = (i / numXTiles) + minTileY;
- tiles[i] = previous = createPlaceholder(tileX, tileY,
previous);
+ tiles[i] = createPlaceholder(tileX, tileY);
}
}
}
@@ -214,24 +232,65 @@ final class PrefetchedImage extends PlanarImage {
return tiles[tx + ty * numXTiles];
}
}
- return source.getTile(tileX, tileY);
+ /*
+ * If the requested tile is not one of the tiles that we computed in
advance,
+ * fetch directly from the source (may imply computation in current
thread).
+ * If an error occurs and this method is invoked inside `execute(…)`
block,
+ * apply a similar error handling than the one applied in constructor.
+ */
+ try {
+ return source.getTile(tileX, tileY);
+ } catch (RuntimeException e) {
+ final ErrorHandler.Report report = errorReport;
+ if (report == null) {
+ throw e;
+ }
+ report.add(new Point(tileX, tileY), e, null);
+ assert Thread.holdsLock(this);
+ return createPlaceholder(tileX, tileY);
+ }
+ }
+
+ /**
+ * Executes the given action in a mode where errors occurring in {@link
RenderedImage#getTile(int, int)}
+ * are reported to the given handler instead of stopping the operation.
The given action is typically
+ * some operation invoking, directly or indirectly, {@link #getTile(int,
int)} with tile indices that
+ * may be outside the area of interest specified at construction time.
Exceptions that occurred inside
+ * the area of interest were caught by the constructor and this method
makes no difference for them.
+ * But exceptions occurring outside that area are interest are redirected
to the {@link #source} image,
+ * which may fail. This method provides a way to catch also those errors.
+ *
+ * @param action the action to execute (for example drawing the
image).
+ * @param errorHandler the handler to notify if errors occur.
+ */
+ @Override
+ public synchronized void execute(final Runnable action, final
TileErrorHandler errorHandler) {
+ ArgumentChecks.ensureNonNull("action", action);
+ ArgumentChecks.ensureNonNull("errorHandler", errorHandler);
+ errorReport = new ErrorHandler.Report();
+ try {
+ action.run();
+ } finally {
+ final ErrorHandler.Report report = errorReport;
+ errorReport = null;
+ errorHandler.publish(report);
+ }
}
/**
* Creates a tile to use as a placeholder when a tile can not be computed.
*
- * @param tileX column index of the tile for which to create a
placeholder.
- * @param tileY row index of the tile for which to create a
placeholder.
- * @param previous tile previously created by this method, or {@code
null} if none.
+ * @param tileX column index of the tile for which to create a
placeholder.
+ * @param tileY row index of the tile for which to create a placeholder.
* @return placeholder for the tile at given indices.
*/
- private Raster createPlaceholder(final int tileX, final int tileY, final
Raster previous) {
+ private Raster createPlaceholder(final int tileX, final int tileY) {
final SampleModel model = getSampleModel();
final Point location = new Point(ImageUtilities.tileToPixelX(source,
tileX),
ImageUtilities.tileToPixelY(source,
tileY));
- if (previous != null) {
+ if (placeholderPixels != null) {
// Reuse same `DataBuffer` with only a different location.
- return Raster.createRaster(model, previous.getDataBuffer(),
location);
+ return Raster.createRaster(model, placeholderPixels, location);
}
final double[] samples = new double[model.getNumBands()];
if (ImageUtilities.isIntegerType(model)) {
@@ -296,6 +355,7 @@ final class PrefetchedImage extends PlanarImage {
tile.setPixel(xmax - x, y, samples);
}
}
+ placeholderPixels = tile.getDataBuffer();
return tile;
}
}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileErrorHandler.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileErrorHandler.java
new file mode 100644
index 0000000..dd13960
--- /dev/null
+++
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileErrorHandler.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.coverage.j2d;
+
+import java.util.logging.LogRecord;
+import java.awt.image.RenderedImage;
+import java.awt.image.ImagingOpException;
+import org.apache.sis.image.ErrorHandler;
+import org.apache.sis.internal.system.Modules;
+
+
+/**
+ * A convenience class for reporting an error during computation of a tile.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
+ * @module
+ */
+public final class TileErrorHandler {
+ /**
+ * Exceptions are wrapped in an {@link ImagingOpException} and thrown.
+ * In such case, no result is available. This is the default handler.
+ */
+ public static final TileErrorHandler THROW = new
TileErrorHandler(ErrorHandler.THROW, null, null);
+
+ /**
+ * Where to report exceptions, or {@link ErrorHandler#THROW} for throwing
them.
+ */
+ final ErrorHandler handler;
+
+ /**
+ * The class to declare in {@link LogRecord} in an error occurred during
calculation.
+ * If non-null, then {@link #sourceMethod} should also be non-null.
+ */
+ private final Class<?> sourceClass;
+
+ /**
+ * Name of the method to declare in {@link LogRecord} in an error occurred
during calculation.
+ * If non-null, then {@link #sourceClass} should also be non-null.
+ */
+ private final String sourceMethod;
+
+ /**
+ * Creates a new tile error handler.
+ *
+ * @param handler where to report exceptions, or {@link
ErrorHandler#THROW} for throwing them.
+ * @param sourceClass the class to declare in {@link LogRecord} in an
error occurred during calculation.
+ * @param sourceMethod name of the method to declare in {@link
LogRecord} in an error occurred during calculation.
+ */
+ public TileErrorHandler(final ErrorHandler handler, final Class<?>
sourceClass, final String sourceMethod) {
+ this.handler = handler;
+ this.sourceClass = sourceClass;
+ this.sourceMethod = sourceMethod;
+ }
+
+ /**
+ * Returns {@code true} if the error handler is {@link ErrorHandler#THROW}.
+ * In such case there is not need to configure the {@link LogRecord} since
+ * nothing will be logged.
+ */
+ final boolean isThrow() {
+ return handler == ErrorHandler.THROW;
+ }
+
+ /**
+ * If the given report is non-empty, sends it to the error handler.
+ * This method sets the logger, source class and source method name
+ * on the {@link LogRecord} instance before to publish it.
+ *
+ * @param report the error report to send if non-empty.
+ */
+ public void publish(final ErrorHandler.Report report) {
+ synchronized (report) {
+ if (report.isEmpty()) {
+ return;
+ }
+ if (!isThrow()) {
+ final LogRecord record = report.getDescription();
+ if (sourceClass != null) {
+ record.setSourceClassName(sourceClass.getCanonicalName());
+ }
+ if (sourceMethod != null) {
+ record.setSourceMethodName(sourceMethod);
+ }
+ record.setLoggerName(Modules.RASTER);
+ }
+ }
+ handler.handle(report);
+ }
+
+ /**
+ * An object executing actions in a way where errors occurring during tile
computation
+ * are reported to an error handler instead than causing the whole
operation to fail.
+ *
+ * <p>This interface is currently used as a workaround for accessing
+ * {@link org.apache.sis.image.PrefetchedImage} without making that class
public.</p>
+ */
+ public interface Executor {
+ /**
+ * Executes the given action in a mode where errors occurring in
{@link RenderedImage#getTile(int, int)}
+ * are reported to the given handler instead of stopping the operation.
+ *
+ * @param action the action to execute (for example drawing
the image).
+ * @param errorHandler the handler to notify if errors occur.
+ */
+ void execute(Runnable action, TileErrorHandler errorHandler);
+ }
+}
diff --git
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
index c81ca58..46e563d 100644
---
a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
+++
b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/j2d/TileOpExecutor.java
@@ -23,6 +23,7 @@ import java.util.function.BinaryOperator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
@@ -80,13 +81,13 @@ public class TileOpExecutor {
private final int minTileX, minTileY, maxTileX, maxTileY;
/**
- * Where to report exceptions, or {@link ErrorHandler#THROW} for throwing
them.
+ * Where to report exceptions, or {@link TileErrorHandler#THROW} for
throwing them.
* If at least one error occurred, then this handler will receive the
{@link Cursor#errors} report
* after all computation {@linkplain Cursor#finish finished}.
*
- * @see #setErrorHandler(ErrorHandler)
+ * @see #setErrorHandler(ErrorHandler, Class, String)
*/
- private ErrorHandler errorHandler;
+ private TileErrorHandler errorHandler;
/**
* Creates a new operation for tiles in the specified region of the
specified image.
@@ -98,7 +99,7 @@ public class TileOpExecutor {
* @throws ArithmeticException if some tile indices are too large.
*/
public TileOpExecutor(final RenderedImage image, final Rectangle aoi) {
- errorHandler = ErrorHandler.THROW;
+ errorHandler = TileErrorHandler.THROW;
if (aoi != null) {
final int tileWidth = image.getTileWidth();
final int tileHeight = image.getTileHeight();
@@ -120,23 +121,22 @@ public class TileOpExecutor {
* Sets the handler where to report exceptions.
* The exception can be obtained by {@link LogRecord#getThrown()}
* on the value returned by {@link ErrorHandler.Report#getDescription()}.
- * In addition the {@code LogRecord} will have the
- * {@linkplain LogRecord#getLevel() level} and
- * {@linkplain LogRecord#getMessage() message} properties set. But the
- * {@linkplain LogRecord#getSourceClassName() source class name},
- * {@linkplain LogRecord#getSourceMethodName() source method name} and
- * {@linkplain LogRecord#getLoggerName() logger name} will be undefined;
- * they should be set by the given {@link ErrorHandler}.
*
* <h4>Limitation</h4>
* In current implementation this is used only during parallel computation.
* A future version may need to use it for sequential computations as well
for consistency.
*
- * @param errorHandler where to report exceptions, or {@link
ErrorHandler#THROW} for throwing them.
+ * @param handler where to report exceptions, or {@link
ErrorHandler#THROW} for throwing them.
+ * @param sourceClass class to declare in {@link LogRecord}, or {@code
null} if none.
+ * @param sourceMethod method to declare in {@link LogRecord}, or {@code
null} if none.
*/
- public final void setErrorHandler(final ErrorHandler errorHandler) {
- ArgumentChecks.ensureNonNull("errorHandler", errorHandler);
- this.errorHandler = errorHandler;
+ public final void setErrorHandler(final ErrorHandler handler, final
Class<?> sourceClass, final String sourceMethod) {
+ ArgumentChecks.ensureNonNull("handler", handler);
+ if (handler == ErrorHandler.THROW) {
+ errorHandler = TileErrorHandler.THROW;
+ } else {
+ errorHandler = new TileErrorHandler(handler, sourceClass,
sourceMethod);
+ }
}
/**
@@ -388,36 +388,34 @@ public class TileOpExecutor {
* </ul>
*
* <h4>Errors management</h4>
- * If an error occurred during the processing of a tile, then there is a
choice:
+ * If an error occurred during the processing of a tile, then there is a
choice depending on the value given
+ * to {@link #setErrorHandler setErrorHandler(…)}:
*
* <ul class="verbose">
* <li>
- * If the {@code errorHandler} is {@code THROW}, then all threads will
finish the tiles they were
- * processing at the time the error occurred, but will not take any
other tile (i.e. remaining tiles
- * will be left unprocessed). The exception that occurred is wrapped
in an {@link ImagingOpException}
- * and thrown.
+ * If {@link ErrorHandler#THROW}, then all threads will finish the
tiles they were processing at the time
+ * the error occurred, but will not take any other tile (i.e.
remaining tiles will be left unprocessed).
+ * The exception that occurred is wrapped in an {@link
ImagingOpException} and thrown.
* </li><li>
- * If the {@code errorHandler} is {@code LOG}, then the exception is
wrapped in a {@link LogRecord} and
- * the processing continues with other tiles. If more exceptions
happen, those subsequent exceptions
- * will be added to the first one by {@link
Exception#addSuppressed(Throwable)}.
+ * If {@link ErrorHandler#LOG}, then the exception is wrapped in a
{@link LogRecord} and the processing
+ * continues with other tiles. If more exceptions happen, those
subsequent exceptions will be added to
+ * the first one with {@link Exception#addSuppressed(Throwable)}.
* After all tiles have been processed, the error handler will be
invoked with that {@link LogRecord}.
- * That {@code LogRecord} has the level, message and exception
properties set, but not the source class name,
- * source method name or logger name. The error handler should set
those properties itself.
* </li>
* </ul>
*
* <h4>Concurrency requirements</h4>
* The {@link RenderedImage#getTile(int, int)} implementation of the given
image must support concurrency.
*
- * @param <A> the type of the thread-local object to be given
to each thread.
- * @param <R> the type of the final result. This is often the
same as <var>A</var>.
- * @param source the image to read. This is usually the image
specified at construction time,
- * but other images are okay if they share the same
pixel and tile coordinate systems.
- * @param collector the action to execute on each {@link Raster},
together with supplier and combiner
- * of thread-local objects of type <var>A</var>. See
above javadoc for more information.
+ * @param <A> the type of the thread-local object to be given to
each thread.
+ * @param <R> the type of the final result. This is often the same
as <var>A</var>.
+ * @param source the image to read. This is usually the image
specified at construction time,
+ * but other images are okay if they share the same
pixel and tile coordinate systems.
+ * @param collector the action to execute on each {@link Raster},
together with supplier and combiner
+ * of thread-local objects of type <var>A</var>. See
above javadoc for more information.
* @return the final result computed by finisher (may be {@code null}).
* @throws ImagingOpException if an exception occurred during {@link
RenderedImage#getTile(int, int)}
- * or {@link #readFrom(Raster)} execution, and {@code
errorHandler} is {@code null}.
+ * or {@link #readFrom(Raster)} execution, and the error handler
is {@link ErrorHandler#THROW}.
* @throws RuntimeException if an exception occurred elsewhere (for
example in the combiner or finisher).
*/
public final <A,R> R executeOnReadable(final RenderedImage source,
@@ -460,13 +458,11 @@ public class TileOpExecutor {
* If an error occurred during the processing of a tile, the exception is
remembered and the processing
* continues with other tiles. If more exceptions happen, those subsequent
exceptions will be added to
* the first one by {@link Exception#addSuppressed(Throwable)}. After all
tiles have been processed,
- * there is a choice:
+ * there is a choice depending on the value given to {@link
#setErrorHandler setErrorHandler(…)}:
*
* <ul>
- * <li>If the {@code errorHandler} is {@code THROW}, the exception is
wrapped in an {@link ImagingOpException} and thrown.</li>
- * <li>If the {@code errorHandler} is {@code LOG}, the exception is
wrapped in a {@link LogRecord} and given to the handler.
- * That {@code LogRecord} has the level, message and exception
properties set, but not the source class name,
- * source method name or logger name. The error handler should set
those properties itself.</li>
+ * <li>If {@link ErrorHandler#THROW}, the exception is wrapped in an
{@link ImagingOpException} and thrown.</li>
+ * <li>If {@link ErrorHandler#LOG}, the exception is wrapped in a {@link
LogRecord} and given to the handler.</li>
* </ul>
*
* <h4>Concurrency requirements</h4>
@@ -474,17 +470,17 @@ public class TileOpExecutor {
* {@link WritableRenderedImage#releaseWritableTile(int, int)}
implementations
* of the given image must support concurrency.
*
- * @param <A> the type of the thread-local object to be given
to each thread.
- * @param <R> the type of the final result. This is often the
same as <var>A</var>.
- * @param target the image where to write. This is usually the
image specified at construction time,
- * but other images are okay if they share the same
pixel and tile coordinate systems.
- * @param collector the action to execute on each {@link
WritableRaster}, together with supplier and combiner
- * of thread-local objects of type <var>A</var>. See
above javadoc for more information.
+ * @param <A> the type of the thread-local object to be given to
each thread.
+ * @param <R> the type of the final result. This is often the same
as <var>A</var>.
+ * @param target the image where to write. This is usually the image
specified at construction time,
+ * but other images are okay if they share the same
pixel and tile coordinate systems.
+ * @param collector the action to execute on each {@link
WritableRaster}, together with supplier and combiner
+ * of thread-local objects of type <var>A</var>. See
above javadoc for more information.
* @return the final result computed by finisher. This is often {@code
null} because the purpose of calling
* {@code executeOnWritable(…)} is more often to update existing
tiles instead than to compute a value.
* @throws ImagingOpException if an exception occurred during {@link
WritableRenderedImage#getWritableTile(int, int)},
* {@link #writeTo(WritableRaster)} or {@link
WritableRenderedImage#releaseWritableTile(int, int)} execution,
- * and {@code errorHandler} is {@code null}.
+ * and the error handler is {@link ErrorHandler#THROW}.
* @throws RuntimeException if an exception occurred elsewhere (for
example in the combiner or finisher).
*/
public final <A,R> R executeOnWritable(final WritableRenderedImage target,
@@ -564,7 +560,7 @@ public class TileOpExecutor {
* after all computation {@linkplain #finish finished}.</p>
*
* @see #stopOnError
- * @see #recordError(Worker, Throwable)
+ * @see #recordError(Point, Throwable)
*/
private final ErrorHandler.Report errors;
@@ -573,7 +569,7 @@ public class TileOpExecutor {
* If {@code false}, processing of all tiles will be completed before
the error is reported.
*
* @see #errors
- * @see #recordError(Worker, Throwable)
+ * @see #recordError(Point, Throwable)
*/
private final boolean stopOnError;
@@ -644,13 +640,13 @@ public class TileOpExecutor {
* @param workers handlers of all worker threads other than the
current threads.
* Content of this array may be modified by this
method.
* @param collector provides the finisher to use for computing
final result of type <var>R</var>.
- * @param errorHandler where to report exceptions, or {@link
ErrorHandler#THROW} for throwing them.
+ * @param errorHandler where to report exceptions, or {@link
TileErrorHandler#THROW} for throwing them.
* @return the final result computed by finisher (may be {@code null}).
* @throws ImagingOpException if an exception occurred during {@link
Worker#executeOnCurrentTile()}
- * and the {@code errorHandler} is {@code null}.
+ * and the {@code errorHandler} is {@code THROW}.
* @throws RuntimeException if an exception occurred elsewhere (for
example in the combiner or finisher).
*/
- final <R> R finish(final Future<?>[] workers, final Collector<?,A,R>
collector, final ErrorHandler errorHandler) {
+ final <R> R finish(final Future<?>[] workers, final Collector<?,A,R>
collector, final TileErrorHandler errorHandler) {
/*
* Before to wait for other threads to complete their work, we
need to remove from executor queue all
* workers that did not yet started their run. Those threads may
be waiting for an executor thread to
@@ -676,9 +672,7 @@ public class TileOpExecutor {
* If someone does not want to let us wait, do not wait for
other worker threads neither.
* We will report that interruption as an error.
*/
- synchronized (this) {
- errors.addPropertyError(ex, null);
- }
+ recordError(null, ex);
break;
}
/*
@@ -689,28 +683,32 @@ public class TileOpExecutor {
final R result;
synchronized (this) {
result = collector.finisher().apply(accumulator);
- if (!errors.isEmpty()) {
- errorHandler.handle(errors);
- }
}
+ /*
+ * If error(s) occurred, report them now. In the default
configuration (`TileErrorHandler.THROW`),
+ * the exception is thrown.
+ */
+ errorHandler.publish(errors);
return result;
}
/**
* Stores the given exception in a log record. We use a log record in
order to initialize
* the timestamp and thread ID to the values they had at the time the
first error occurred.
+ * The error is not notified immediately to the {@link ErrorHandler};
we wait for other errors
+ * in order to aggregate them in a single record. So the given error
is <em>recorded</em>
+ * but not yet <em>reported</em>.
+ *
+ * @param tile indices of the tile where an error occurred, or
{@code null} if unknown.
+ * @param ex the exception that occurred.
*
- * @param indices the worker thread where the exception occurred.
Its {@link Worker#tx}
- * and {@link Worker#ty} indices should identify the
problematic tile.
- * @param ex the exception that occurred.
+ * @see TileOpExecutor#setErrorHandler(ErrorHandler, Class, String)
*/
- final void recordError(final Worker<RI,?,A> indices, final Throwable
ex) {
+ final void recordError(final Point tile, final Throwable ex) {
if (stopOnError) {
set(Integer.MIN_VALUE); // Will cause other threads to
stop fetching tiles.
}
- synchronized (this) {
- errors.addTileError(ex, indices.tx, indices.ty);
- }
+ errors.add(tile, ex, null);
}
/**
@@ -798,7 +796,7 @@ public class TileOpExecutor {
while (cursor.next(this)) try {
executeOnCurrentTile();
} catch (Exception ex) {
- cursor.recordError(this, trimImagingWrapper(ex));
+ cursor.recordError(new Point(tx, ty), trimImagingWrapper(ex));
}
cursor.accumulate(accumulator);
}
@@ -893,9 +891,9 @@ public class TileOpExecutor {
* See the Javadoc of that method for details.
*/
static <A,R> R execute(final TileOpExecutor executor, final
RenderedImage source,
- final Collector<? super Raster, A, R> collector, final
ErrorHandler errorHandler)
+ final Collector<? super Raster, A, R> collector, final
TileErrorHandler errorHandler)
{
- final Cursor<RenderedImage,A> cursor = executor.new
Cursor<>(source, collector, errorHandler == ErrorHandler.THROW);
+ final Cursor<RenderedImage,A> cursor = executor.new
Cursor<>(source, collector, errorHandler.isThrow());
final Future<?>[] workers = new Future<?>[cursor.getNumWorkers()];
for (int i=0; i<workers.length; i++) {
workers[i] = CommonExecutor.instance().submit(new
ReadWork<>(cursor, collector));
@@ -950,7 +948,7 @@ public class TileOpExecutor {
* See the Javadoc of that method for details.
*/
static <A,R> R execute(final TileOpExecutor executor, final
WritableRenderedImage target,
- final Collector<? super WritableRaster,A,R> collector, final
ErrorHandler errorHandler)
+ final Collector<? super WritableRaster,A,R> collector, final
TileErrorHandler errorHandler)
{
final Cursor<WritableRenderedImage,A> cursor = executor.new
Cursor<>(target, collector, false);
final Future<?>[] workers = new Future<?>[cursor.getNumWorkers()];