/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2011-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.process;
import java.util.Locale;
import org.opengis.util.InternationalString;
import org.apache.sis.util.Localized;
import org.apache.sis.util.iso.SimpleInternationalString;
/**
* Monitors the progress of some lengthly operation, and allows cancellation.
* This abstract class makes no assumption about the output device. Additionally,
* this class provides support for non-fatal warning and exception reports.
* <p>
* All implementations should be multi-thread safe, even the ones that provide
* feedback to a user interface thread.
* <p>
* Usage example:
*
* {@preformat java
* float scale = 100f / maximumCount;
* controller.started();
* for (int counter=0; counter<maximumCount; counter++) {
* if (controller.isCanceled()) {
* break;
* }
* controller.progress(scale * counter);
* try {
* // Do some work...
* } catch (NonFatalException e) {
* controller.exceptionOccurred(e);
* }
* }
* controller.complete();
* }
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Jody Garnet (Refractions Research)
* @author Guilhem Legal (Geomatys)
* @version 3.20
*
* @since 3.19 (derived from 2.0)
* @module
*/
public abstract class ProgressController implements Localized, ProcessListener {
/**
* The language to use for formatting messages.
*/
private final Locale locale;
/**
* Name of the lengthly operation.
*/
private volatile InternationalString task;
/**
* The current progress percentage, as a value between 0 and 100.
*/
private volatile float progress;
/**
* {@code true} if the action has been canceled.
*/
private volatile boolean canceled;
/**
* Constructs a progress controller which will format messages in the
* {@linkplain Locale#getDefault() system default locale}.
*/
protected ProgressController() {
locale = Locale.getDefault();
}
/**
* Returns the locale used for formatting messages. The default value is the
* {@linkplain Locale#getDefault() system default} locale.
*
* @return The locale.
*/
@Override
public Locale getLocale() {
return locale;
}
/**
* Returns the description of the current task being performed, or {@code null} if none.
* It is assumed that if the task is {@code null} applications may simply report that the
* process is "<cite>in progress</cite>" or "<cite>working</cite>" as represented in the
* {@linkplain #getLocale() current locale}.
*
* @return Description of the task being performed, or {@code null} if none.
*/
public InternationalString getTask() {
return task;
}
/**
* Sets the description of the current task being performed. This method is usually invoked
* before any progress begins. However, it is legal to invoke this method at any time during
* the operation, in which case the task label is updated without any change to the percentage
* accomplished.
*
* @param task Description of the task being performed as a {@link String} or
* {@link InternationalString}, or {@code null} if none.
*/
public void setTask(CharSequence task) {
if (task != null && !(task instanceof InternationalString)) {
task = new SimpleInternationalString(task.toString());
}
this.task = (InternationalString) task;
}
/**
* Notifies this controller that the operation begins.
*/
public abstract void started();
/**
* Notifies this controller that the operation begins and sets the states of this controller
* according the given event. The default implementation first invokes {@link #started()},
* then invokes <code>{@linkplain #progressing(ProcessEvent) progressing}(event)</code>.
*
* @param event The progress event, or {@code null} if none.
*
* @since 3.19
*/
@Override
public void started(final ProcessEvent event) {
started();
progressing(event);
}
/**
* Notifies this controller that the operation is suspended.
*
* @since 3.20
*/
public abstract void paused();
/**
* Notifies this controller that the operation is suspended and sets the states of this
* controller according the given event. The default implementation first invokes
* <code>{@linkplain #progressing(ProcessEvent) progressing}(event)</code>,
* then invokes {@link #paused()}.
*
* @param event The progress event, or {@code null} if none.
*
* @since 3.20
*/
@Override
public void paused(final ProcessEvent event) {
progressing(event);
paused();
}
/**
* Notifies this controller that the operation is resumed.
*
* @since 3.20
*/
public abstract void resumed();
/**
* Notifies this controller that the operation is resumed and sets the states of this controller
* according the given event. The default implementation first invokes {@link #resumed()},
* then invokes <code>{@linkplain #progressing(ProcessEvent) progressing}(event)</code>.
*
* @param event The progress event, or {@code null} if none.
*
* @since 3.20
*/
@Override
public void resumed(final ProcessEvent event) {
resumed();
progressing(event);
}
/**
* Returns the current progress as a percent completed.
*
* @return Percent completed between 0 and 100 inclusive.
*/
public float getProgress() {
return progress;
}
/**
* Notifies this controller of progress in the lengthly operation. Progresses are reported
* as a value between 0 and 100 inclusive. Values out of bounds will be clamped.
*
* @param percent The progress as a value between 0 and 100 inclusive.
*/
public void setProgress(float percent) {
if (percent < 0 ) percent = 0;
if (percent > 100) percent = 100;
progress = percent;
}
/**
* Updates the {@linkplain #setTask(CharSequence) task} and {@linkplain #setProgress(float)
* progress} state, and {@linkplain #exceptionOccurred(Throwable) reports the exception} if
* any. More specifically:
* <p>
* <ul>
* <li>The new task is set to the {@link ProcessEvent#getTask()} value unless the later
* method returned {@code null}, in which case this {@code ProgressController} task
* is left unchanged.</li>
* <li>The new progress is set to the {@link ProcessEvent#getProgress()} value unless the
* later method returned {@link Float#NaN}, in which case this {@code ProgressController}
* progress state is left unchanged.</li>
* <li>If {@link ProcessEvent#getException()} returns a non-null value, then that value is
* given to {@link #exceptionOccurred(Throwable)}.</li>
* </ul>
*
* @param event The progress event, or {@code null} if none.
*
* @since 3.19
*/
@Override
public void progressing(final ProcessEvent event) {
if (event != null) {
final InternationalString task = event.getTask();
if (task != null) {
setTask(task);
}
final float progress = event.getProgress();
if (!Float.isNaN(progress)) {
setProgress(progress);
}
final Exception ex = event.getException();
if (ex != null) {
exceptionOccurred(ex);
}
}
}
/**
* Notifies this controller that the operation has finished. The progress indicator will
* shows 100% or disappears, at implementor choice. If warning messages were pending,
* they will be displayed now.
*/
public abstract void completed();
/**
* Notifies this controller that the operation has finished and sets the states of this
* controller according the given event. The default implementation first invokes
* <code>{@linkplain #progressing(ProcessEvent) progressing}(event)</code>, then
* invokes {@link #completed()}.
*
* @param event The progress event, or {@code null} if none.
*
* @since 3.19
*/
@Override
public void completed(final ProcessEvent event) {
progressing(event);
completed();
}
/**
* Notifies this controller that the operation has failed and sets the states of this
* controller according the given event. The default implementation just delegates to
* <code>{@linkplain #progressing(ProcessEvent) progressing}(event)</code>. Note that
* the above {@code progressing} method pass the {@linkplain ProcessEvent#getException()
* exception declared by the event} (if any) to the {@link #exceptionOccurred(Throwable)}
* method.
*
* @param event The progress event, or {@code null} if none.
*
* @since 3.19
*/
@Override
public void failed(final ProcessEvent event) {
progressing(event);
}
/**
* Indicates that this job should be canceled.
*/
public void cancel() {
canceled = true;
}
/**
* Returns {@code true} if this job is canceled.
*
* @return {@code true} if this job is canceled.
*/
public boolean isCanceled() {
return canceled;
}
/**
* Reports a warning. This warning may be {@linkplain java.util.logger.Logger logged}, printed
* to the {@linkplain System#err standard error stream}, appears in a windows or be ignored,
* at implementor choice.
*
* @param source
* Name of the warning source, or {@code null} if none. This is typically the
* name of the file being parsed, or the URL of the data being processed.
* @param location
* Text to write on the left side of the warning message, or {@code null} if none.
* This is typically the line number where the error occurred in the {@code source}
* file or the feature ID of the feature that produced the message.
* @param warning
* The warning message.
*/
public abstract void warningOccurred(String source, String location, String warning);
/**
* Reports an exception. This method may prints the stack trace to the {@linkplain System#err
* standard error stream} or display it in a dialog box, at implementor choice.
*
* @param exception The exception to report.
*/
public abstract void exceptionOccurred(Throwable exception);
}