/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 1999-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.gui.headless;
import java.io.PrintWriter;
import java.text.BreakIterator;
import java.text.NumberFormat;
import org.opengis.util.InternationalString;
import org.opengis.util.ProgressListener;
import org.geotools.resources.Arguments;
import org.geotools.resources.Utilities;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;
import org.geotools.util.SimpleInternationalString;
/**
* Prints progress report of a lengtly operation to an output stream. Progress are reported
* as percentage on a single line. This class can also prints warning, which is useful for
* notifications without stoping the lenghtly task.
*
* @since 2.0
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (PMO, IRD)
*/
public class ProgressPrinter implements ProgressListener {
/**
* Nom de l'opération en cours. Le pourcentage sera écris à la droite de ce nom.
*/
private String description;
/**
* Flot utilisé pour l'écriture de l'état d'avancement d'un
* processus ainsi que pour les écritures des commentaires.
*/
private final PrintWriter out;
/**
* Indique si le caractère '\r' ramène au début de la ligne courante sur
* ce système. On supposera que ce sera le cas si le système n'utilise
* pas la paire "\r\n" pour changer de ligne (comme le system VAX-VMS).
*/
private final boolean CR_supported;
/**
* Longueur maximale des lignes. L'espace utilisable sera un peu
* moindre car quelques espaces seront laissés en début de ligne.
*/
private final int maxLength;
/**
* Nombre de caractères utilisés lors de l'écriture de la dernière ligne.
* Ce champ est mis à jour par la méthode {@link #carriageReturn} chaque
* fois que l'on déclare que l'on vient de terminer l'écriture d'une ligne.
*/
private int lastLength;
/**
* Position à laquelle commencer à écrire le pourcentage. Cette information
* est gérée automatiquement par la méthode {@link #progress}. La valeur -1
* signifie que ni le pourcentage ni la description n'ont encore été écrits.
*/
private int percentPosition = -1;
/**
* Dernier pourcentage écrit. Cette information est utilisée
* afin d'éviter d'écrire deux fois le même pourcentage, ce
* qui ralentirait inutilement le système. La valeur -1 signifie
* qu'on n'a pas encore écrit de pourcentage.
*/
private float lastPercent = -1;
/**
* Format à utiliser pour écrire les pourcentages.
*/
private NumberFormat format;
/**
* Objet utilisé pour couper les lignes correctements lors de l'affichage
* de messages d'erreurs qui peuvent prendre plusieurs lignes.
*/
private BreakIterator breaker;
/**
* Indique si cet objet a déjà écrit des avertissements. Si
* oui, on ne réécrira pas le gros titre "avertissements".
*/
private boolean hasPrintedWarning;
/**
* Source du dernier message d'avertissement. Cette information est
* conservée afin d'éviter de répéter la source lors d'éventuels
* autres messages d'avertissements.
*/
private String lastSource;
/**
* {@code true} if the action has been canceled.
*/
private volatile boolean canceled;
/**
* Constructs a new object sending progress reports to the
* {@linkplain java.lang.System#out standard output stream}.
* The maximal line length is assumed 80 characters.
*/
public ProgressPrinter() {
this(new PrintWriter(Arguments.getWriter(System.out)));
}
/**
* Constructs a new object sending progress reports to the specified stream.
* The maximal line length is assumed 80 characters.
*/
public ProgressPrinter(final PrintWriter out) {
this(out, 80);
}
/**
* Constructs a new object sending progress reports to the specified stream.
*
* @param out The output stream.
* @param maxLength The maximal line length. This is used by {@link #warningOccurred}
* for splitting longer lines into many lines.
*/
public ProgressPrinter(final PrintWriter out, final int maxLength) {
this.out = out;
this.maxLength = maxLength;
final String lineSeparator = System.getProperty("line.separator", "\n");
CR_supported = lineSeparator.equals("\r\n") || lineSeparator.equals("\n");
}
/**
* Efface le reste de la ligne (si nécessaire) puis repositionne le curseur au début
* de la ligne. Si les retours chariot ne sont pas supportés, alors cette méthode va
* plutôt passer à la ligne suivante. Dans tous les cas, le curseur se trouvera au
* début d'une ligne et la valeur {@code length} sera affecté au champ
* {@link #lastLength}.
*
* @param length Nombre de caractères qui ont été écrit jusqu'à maintenant sur cette ligne.
* Cette information est utilisée pour ne mettre que le nombre d'espaces nécessaires
* à la fin de la ligne.
*/
private void carriageReturn(final int length) {
if (CR_supported && length<maxLength) {
for (int i=length; i<lastLength; i++) {
out.print(' ');
}
out.print('\r');
out.flush();
} else {
out.println();
}
lastLength = length;
}
/**
* Ajoute des points à la fin de la ligne jusqu'à représenter
* le pourcentage spécifié. Cette méthode est utilisée pour
* représenter les progrès sur un terminal qui ne supporte
* pas les retours chariots.
*
* @param percent Pourcentage accompli de l'opération. Cette
* valeur doit obligatoirement se trouver entre 0 et
* 100 (ça ne sera pas vérifié).
*/
private void completeBar(final float percent) {
final int end = (int) ((percent/100)*((maxLength-2)-percentPosition)); // Round toward 0.
while (lastLength < end) {
out.print('.');
lastLength++;
}
}
/**
* {@inheritDoc}
*/
public String getDescription() {
return description;
}
/**
* {@inheritDoc}
*/
public synchronized void setDescription(final String description) {
this.description = description;
}
/**
* {@inheritDoc}
*/
public synchronized void started() {
int length = 0;
if (description != null) {
out.print(description);
length=description.length();
}
if (CR_supported) {
carriageReturn(length);
}
out.flush();
percentPosition = length;
lastPercent = -1;
lastSource = null;
hasPrintedWarning = false;
}
/**
* {@inheritDoc}
*/
public synchronized void progress(float percent) {
if (percent < 0 ) percent = 0;
if (percent > 100) percent = 100;
if (CR_supported) {
/*
* Si le périphérique de sortie supporte les retours chariot,
* on écrira l'état d'avancement comme un pourcentage après
* la description, comme dans "Lecture des données (38%)".
*/
if (percent != lastPercent) {
if (format == null) {
format = NumberFormat.getPercentInstance();
}
final String text = format.format(percent / 100.0);
int length = text.length();
percentPosition = 0;
if (description != null) {
out.print(description);
out.print(' ');
length += (percentPosition=description.length()) + 1;
}
out.print('(');
out.print(text);
out.print(')');
length += 2;
carriageReturn(length);
lastPercent = percent;
}
} else {
/*
* Si le périphérique ne supporte par les retours chariots, on
* écrira l'état d'avancement comme une série de points placés
* après la description, comme dans "Lecture des données......"
*/
completeBar(percent);
lastPercent = percent;
out.flush();
}
}
/**
* Returns the current progress as a percent completed.
*/
public float getProgress() {
return lastPercent;
}
/**
* Notifies this listener that the operation has finished. The progress indicator will
* shows 100% or disaspears. If warning messages were pending, they will be printed now.
*/
public synchronized void complete() {
if (!CR_supported) {
completeBar(100);
}
carriageReturn(0);
out.flush();
}
/**
* Releases any resource hold by this object.
*/
public void dispose() {
}
/**
* {@inheritDoc}
*/
public boolean isCanceled() {
return canceled;
}
/**
* {@inheritDoc}
*/
public void setCanceled(final boolean canceled) {
this.canceled = canceled;
}
/**
* Prints a warning. The first time this method is invoked, the localized word "WARNING" will
* be printed in the middle of a box. If a source is specified, it will be printed only if it
* is not the same one than the source of the last warning. If a marging is specified, it will
* be printed of the left side of the first line of the warning message.
*
* @param source The source of the warning, or {@code null} if none. This is typically the
* filename in process of being parsed.
* @param margin 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 occured in the {@code source} file.
* @param warning The warning message. If this string is longer than the maximal length
* specified at construction time (80 characters by default), then it will be splitted
* in as many lines as needed and indented according the marging width.
*/
public synchronized void warningOccurred(final String source, String margin,
final String warning)
{
carriageReturn(0);
if (!hasPrintedWarning) {
printInBox(Vocabulary.format(VocabularyKeys.WARNING));
hasPrintedWarning=true;
}
if (!Utilities.equals(source, lastSource)) {
out.println();
out.println(source!=null ? source : Vocabulary.format(VocabularyKeys.UNTITLED));
lastSource=source;
}
/*
* Procède à l'écriture de l'avertissement avec (de façon optionnelle)
* quelque chose dans la marge (le plus souvent un numéro de ligne).
*/
String prefix=" ";
String second=prefix;
if (margin != null) {
margin = trim(margin);
if (margin.length() != 0) {
final StringBuffer buffer = new StringBuffer(prefix);
buffer.append('(');
buffer.append(margin);
buffer.append(") ");
prefix=buffer.toString();
buffer.setLength(0);
second=Utilities.spaces(prefix.length());
}
}
int width=maxLength-prefix.length()-1;
if (breaker == null) {
breaker=BreakIterator.getLineInstance();
}
breaker.setText(warning);
int start=breaker.first(), end=start, nextEnd;
while ((nextEnd=breaker.next()) != BreakIterator.DONE) {
while (nextEnd-start > width) {
if (end <= start) {
end=Math.min(nextEnd, start+width);
}
out.print(prefix);
out.println(warning.substring(start, end));
prefix=second;
start=end;
}
end=Math.min(nextEnd, start+width);
}
if (end>start) {
out.print(prefix);
out.println(warning.substring(start, end));
}
if (!CR_supported && description!=null) {
out.print(description);
completeBar(lastPercent);
}
out.flush();
}
/**
* Prints an exception stack trace in a box.
*/
public synchronized void exceptionOccurred(final Throwable exception) {
carriageReturn(0);
printInBox(Vocabulary.format(VocabularyKeys.EXCEPTION));
exception.printStackTrace(out);
hasPrintedWarning = false;
out.flush();
}
/**
* Retourne la chaîne {@code margin} sans les
* éventuelles parenthèses qu'elle pourrait avoir
* de part et d'autre.
*/
private static String trim(String margin) {
margin = margin.trim();
int lower = 0;
int upper = margin.length();
while (lower<upper && margin.charAt(lower+0)=='(') lower++;
while (lower<upper && margin.charAt(upper-1)==')') upper--;
return margin.substring(lower, upper);
}
/**
* Écrit dans une boîte entouré d'astérix le texte spécifié en argument.
* Ce texte doit être sur une seule ligne et ne pas comporter de retour
* chariot. Les dimensions de la boîte seront automatiquement ajustées.
* @param text Texte à écrire (une seule ligne).
*/
private void printInBox(String text) {
int length = text.length();
for (int pass=-2; pass<=2; pass++) {
switch (Math.abs(pass)) {
case 2: for (int j=-10; j<length; j++) out.print('*');
out.println();
break;
case 1: out.print("**");
for (int j=-6; j<length; j++) out.print(' ');
out.println("**");
break;
case 0: out.print("** ");
out.print(text);
out.println(" **");
break;
}
}
}
public void setTask( InternationalString task ) {
setDescription( task.toString() );
}
public InternationalString getTask() {
return new SimpleInternationalString( getDescription() );
}
}