001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: * (C) 1999, Pêches et Océans Canada
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.gui.headless;
019:
020: // J2SE Input/output
021: import java.io.PrintWriter;
022: import java.text.BreakIterator;
023: import java.text.NumberFormat;
024:
025: // Geotools dependencies
026: import org.geotools.resources.Arguments;
027: import org.geotools.resources.Utilities;
028: import org.geotools.resources.i18n.Vocabulary;
029: import org.geotools.resources.i18n.VocabularyKeys;
030: import org.geotools.util.AbstractInternationalString;
031: import org.geotools.util.ProgressListener;
032: import org.geotools.util.SimpleInternationalString;
033: import org.opengis.util.InternationalString;
034:
035: /**
036: * Prints progress report of a lengtly operation to an output stream. Progress are reported
037: * as percentage on a single line. This class can also prints warning, which is useful for
038: * notifications without stoping the lenghtly task.
039: *
040: * @since 2.0
041: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/headless/ProgressPrinter.java $
042: * @version $Id: ProgressPrinter.java 22680 2006-11-09 22:32:27Z jgarnett $
043: * @author Martin Desruisseaux
044: */
045: public class ProgressPrinter implements ProgressListener {
046: /**
047: * Nom de l'opération en cours. Le pourcentage sera écris à la droite de ce nom.
048: */
049: private String description;
050:
051: /**
052: * Flot utilisé pour l'écriture de l'état d'avancement d'un
053: * processus ainsi que pour les écritures des commentaires.
054: */
055: private final PrintWriter out;
056:
057: /**
058: * Indique si le caractère '\r' ramène au début de la ligne courante sur
059: * ce système. On supposera que ce sera le cas si le système n'utilise
060: * pas la paire "\r\n" pour changer de ligne (comme le system VAX-VMS).
061: */
062: private final boolean CR_supported;
063:
064: /**
065: * Longueur maximale des lignes. L'espace utilisable sera un peu
066: * moindre car quelques espaces seront laissés en début de ligne.
067: */
068: private final int maxLength;
069:
070: /**
071: * Nombre de caractères utilisés lors de l'écriture de la dernière ligne.
072: * Ce champ est mis à jour par la méthode {@link #carriageReturn} chaque
073: * fois que l'on déclare que l'on vient de terminer l'écriture d'une ligne.
074: */
075: private int lastLength;
076:
077: /**
078: * Position à laquelle commencer à écrire le pourcentage. Cette information
079: * est gérée automatiquement par la méthode {@link #progress}. La valeur -1
080: * signifie que ni le pourcentage ni la description n'ont encore été écrits.
081: */
082: private int percentPosition = -1;
083:
084: /**
085: * Dernier pourcentage écrit. Cette information est utilisée
086: * afin d'éviter d'écrire deux fois le même pourcentage, ce
087: * qui ralentirait inutilement le système. La valeur -1 signifie
088: * qu'on n'a pas encore écrit de pourcentage.
089: */
090: private float lastPercent = -1;
091:
092: /**
093: * Format à utiliser pour écrire les pourcentages.
094: */
095: private NumberFormat format;
096:
097: /**
098: * Objet utilisé pour couper les lignes correctements lors de l'affichage
099: * de messages d'erreurs qui peuvent prendre plusieurs lignes.
100: */
101: private BreakIterator breaker;
102:
103: /**
104: * Indique si cet objet a déjà écrit des avertissements. Si
105: * oui, on ne réécrira pas le gros titre "avertissements".
106: */
107: private boolean hasPrintedWarning;
108:
109: /**
110: * Source du dernier message d'avertissement. Cette information est
111: * conservée afin d'éviter de répéter la source lors d'éventuels
112: * autres messages d'avertissements.
113: */
114: private String lastSource;
115:
116: /**
117: * {@code true} if the action has been canceled.
118: */
119: private volatile boolean canceled;
120:
121: /**
122: * Constructs a new object sending progress reports to the
123: * {@linkplain java.lang.System#out standard output stream}.
124: * The maximal line length is assumed 80 characters.
125: */
126: public ProgressPrinter() {
127: this (new PrintWriter(Arguments.getWriter(System.out)));
128: }
129:
130: /**
131: * Constructs a new object sending progress reports to the specified stream.
132: * The maximal line length is assumed 80 characters.
133: */
134: public ProgressPrinter(final PrintWriter out) {
135: this (out, 80);
136: }
137:
138: /**
139: * Constructs a new object sending progress reports to the specified stream.
140: *
141: * @param out The output stream.
142: * @param maxLength The maximal line length. This is used by {@link #warningOccurred}
143: * for splitting longer lines into many lines.
144: */
145: public ProgressPrinter(final PrintWriter out, final int maxLength) {
146: this .out = out;
147: this .maxLength = maxLength;
148: final String lineSeparator = System.getProperty(
149: "line.separator", "\n");
150: CR_supported = lineSeparator.equals("\r\n")
151: || lineSeparator.equals("\n");
152: }
153:
154: /**
155: * Efface le reste de la ligne (si nécessaire) puis repositionne le curseur au début
156: * de la ligne. Si les retours chariot ne sont pas supportés, alors cette méthode va
157: * plutôt passer à la ligne suivante. Dans tous les cas, le curseur se trouvera au
158: * début d'une ligne et la valeur {@code length} sera affecté au champ
159: * {@link #lastLength}.
160: *
161: * @param length Nombre de caractères qui ont été écrit jusqu'à maintenant sur cette ligne.
162: * Cette information est utilisée pour ne mettre que le nombre d'espaces nécessaires
163: * à la fin de la ligne.
164: */
165: private void carriageReturn(final int length) {
166: if (CR_supported && length < maxLength) {
167: for (int i = length; i < lastLength; i++) {
168: out.print(' ');
169: }
170: out.print('\r');
171: out.flush();
172: } else {
173: out.println();
174: }
175: lastLength = length;
176: }
177:
178: /**
179: * Ajoute des points à la fin de la ligne jusqu'à représenter
180: * le pourcentage spécifié. Cette méthode est utilisée pour
181: * représenter les progrès sur un terminal qui ne supporte
182: * pas les retours chariots.
183: *
184: * @param percent Pourcentage accompli de l'opération. Cette
185: * valeur doit obligatoirement se trouver entre 0 et
186: * 100 (ça ne sera pas vérifié).
187: */
188: private void completeBar(final float percent) {
189: final int end = (int) ((percent / 100) * ((maxLength - 2) - percentPosition)); // Round toward 0.
190: while (lastLength < end) {
191: out.print('.');
192: lastLength++;
193: }
194: }
195:
196: /**
197: * {@inheritDoc}
198: */
199: public String getDescription() {
200: return description;
201: }
202:
203: /**
204: * {@inheritDoc}
205: */
206: public synchronized void setDescription(final String description) {
207: this .description = description;
208: }
209:
210: /**
211: * {@inheritDoc}
212: */
213: public synchronized void started() {
214: int length = 0;
215: if (description != null) {
216: out.print(description);
217: length = description.length();
218: }
219: if (CR_supported) {
220: carriageReturn(length);
221: }
222: out.flush();
223: percentPosition = length;
224: lastPercent = -1;
225: lastSource = null;
226: hasPrintedWarning = false;
227: }
228:
229: /**
230: * {@inheritDoc}
231: */
232: public synchronized void progress(float percent) {
233: if (percent < 0)
234: percent = 0;
235: if (percent > 100)
236: percent = 100;
237: if (CR_supported) {
238: /*
239: * Si le périphérique de sortie supporte les retours chariot,
240: * on écrira l'état d'avancement comme un pourcentage après
241: * la description, comme dans "Lecture des données (38%)".
242: */
243: if (percent != lastPercent) {
244: if (format == null) {
245: format = NumberFormat.getPercentInstance();
246: }
247: final String text = format.format(percent / 100.0);
248: int length = text.length();
249: percentPosition = 0;
250: if (description != null) {
251: out.print(description);
252: out.print(' ');
253: length += (percentPosition = description.length()) + 1;
254: }
255: out.print('(');
256: out.print(text);
257: out.print(')');
258: length += 2;
259: carriageReturn(length);
260: lastPercent = percent;
261: }
262: } else {
263: /*
264: * Si le périphérique ne supporte par les retours chariots, on
265: * écrira l'état d'avancement comme une série de points placés
266: * après la description, comme dans "Lecture des données......"
267: */
268: completeBar(percent);
269: lastPercent = percent;
270: out.flush();
271: }
272: }
273:
274: /**
275: * Notifies this listener that the operation has finished. The progress indicator will
276: * shows 100% or disaspears. If warning messages were pending, they will be printed now.
277: */
278: public synchronized void complete() {
279: if (!CR_supported) {
280: completeBar(100);
281: }
282: carriageReturn(0);
283: out.flush();
284: }
285:
286: /**
287: * Release any resource hold by this object.
288: */
289: public void dispose() {
290: }
291:
292: /**
293: * {@inheritDoc}
294: */
295: public boolean isCanceled() {
296: return canceled;
297: }
298:
299: /**
300: * {@inheritDoc}
301: */
302: public void setCanceled(final boolean canceled) {
303: this .canceled = canceled;
304: }
305:
306: /**
307: * Prints a warning. The first time this method is invoked, the localized word "WARNING" will
308: * be printed in the middle of a box. If a source is specified, it will be printed only if it
309: * is not the same one than the source of the last warning. If a marging is specified, it will
310: * be printed of the left side of the first line of the warning message.
311: *
312: * @param source The source of the warning, or {@code null} if none. This is typically the
313: * filename in process of being parsed.
314: * @param margin Text to write on the left side of the warning message, or {@code null} if none.
315: * This is typically the line number where the error occured in the {@code source} file.
316: * @param warning The warning message. If this string is longer than the maximal length
317: * specified at construction time (80 characters by default), then it will be splitted
318: * in as many lines as needed and indented according the marging width.
319: */
320: public synchronized void warningOccurred(final String source,
321: String margin, final String warning) {
322: carriageReturn(0);
323: if (!hasPrintedWarning) {
324: printInBox(Vocabulary.format(VocabularyKeys.WARNING));
325: hasPrintedWarning = true;
326: }
327: if (!Utilities.equals(source, lastSource)) {
328: out.println();
329: out.println(source != null ? source : Vocabulary
330: .format(VocabularyKeys.UNTITLED));
331: lastSource = source;
332: }
333: /*
334: * Procède à l'écriture de l'avertissement avec (de façon optionnelle)
335: * quelque chose dans la marge (le plus souvent un numéro de ligne).
336: */
337: String prefix = " ";
338: String second = prefix;
339: if (margin != null) {
340: margin = trim(margin);
341: if (margin.length() != 0) {
342: final StringBuffer buffer = new StringBuffer(prefix);
343: buffer.append('(');
344: buffer.append(margin);
345: buffer.append(") ");
346: prefix = buffer.toString();
347: buffer.setLength(0);
348: second = Utilities.spaces(prefix.length());
349: }
350: }
351: int width = maxLength - prefix.length() - 1;
352: if (breaker == null) {
353: breaker = BreakIterator.getLineInstance();
354: }
355: breaker.setText(warning);
356: int start = breaker.first(), end = start, nextEnd;
357: while ((nextEnd = breaker.next()) != BreakIterator.DONE) {
358: while (nextEnd - start > width) {
359: if (end <= start) {
360: end = Math.min(nextEnd, start + width);
361: }
362: out.print(prefix);
363: out.println(warning.substring(start, end));
364: prefix = second;
365: start = end;
366: }
367: end = Math.min(nextEnd, start + width);
368: }
369: if (end > start) {
370: out.print(prefix);
371: out.println(warning.substring(start, end));
372: }
373: if (!CR_supported && description != null) {
374: out.print(description);
375: completeBar(lastPercent);
376: }
377: out.flush();
378: }
379:
380: /**
381: * Prints an exception stack trace in a box.
382: */
383: public synchronized void exceptionOccurred(final Throwable exception) {
384: carriageReturn(0);
385: printInBox(Vocabulary.format(VocabularyKeys.EXCEPTION));
386: exception.printStackTrace(out);
387: hasPrintedWarning = false;
388: out.flush();
389: }
390:
391: /**
392: * Retourne la chaîne {@code margin} sans les
393: * éventuelles parenthèses qu'elle pourrait avoir
394: * de part et d'autre.
395: */
396: private static String trim(String margin) {
397: margin = margin.trim();
398: int lower = 0;
399: int upper = margin.length();
400: while (lower < upper && margin.charAt(lower + 0) == '(')
401: lower++;
402: while (lower < upper && margin.charAt(upper - 1) == ')')
403: upper--;
404: return margin.substring(lower, upper);
405: }
406:
407: /**
408: * Écrit dans une boîte entouré d'astérix le texte spécifié en argument.
409: * Ce texte doit être sur une seule ligne et ne pas comporter de retour
410: * chariot. Les dimensions de la boîte seront automatiquement ajustées.
411: * @param text Texte à écrire (une seule ligne).
412: */
413: private void printInBox(String text) {
414: int length = text.length();
415: for (int pass = -2; pass <= 2; pass++) {
416: switch (Math.abs(pass)) {
417: case 2:
418: for (int j = -10; j < length; j++)
419: out.print('*');
420: out.println();
421: break;
422:
423: case 1:
424: out.print("**");
425: for (int j = -6; j < length; j++)
426: out.print(' ');
427: out.println("**");
428: break;
429:
430: case 0:
431: out.print("** ");
432: out.print(text);
433: out.println(" **");
434: break;
435: }
436: }
437: }
438:
439: public void setTask(InternationalString task) {
440: setDescription(task.toString());
441: }
442:
443: public InternationalString getTask() {
444: return new SimpleInternationalString(getDescription());
445: }
446: }
|