001 /**
002 * Copyright (c) 2010, SIB. All rights reserved.
003 *
004 * SIB (Swiss Institute of Bioinformatics) - http://www.isb-sib.ch Host -
005 * https://sourceforge.net/projects/javaprotlib/
006 *
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions are met:
009 * Redistributions of source code must retain the above copyright notice, this
010 * list of conditions and the following disclaimer. Redistributions in binary
011 * form must reproduce the above copyright notice, this list of conditions and
012 * the following disclaimer in the documentation and/or other materials provided
013 * with the distribution. Neither the name of the SIB/GENEBIO nor the names of
014 * its contributors may be used to endorse or promote products derived from this
015 * software without specific prior written permission.
016 *
017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
020 * ARE DISCLAIMED. IN NO EVENT SHALL SIB/GENEBIO BE LIABLE FOR ANY DIRECT,
021 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
024 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 */
028 package org.expasy.jpl.commons.base.task;
029
030
031 import java.io.PrintStream;
032
033
034 /**
035 * A simple terminal progress bar.
036 *
037 * <h4>Description</h4>
038 * <p>
039 * A progress bar typically communicates the progress of some work by displaying
040 * its percentage of completion and possibly a textual display of this
041 * percentage.
042 * </p>
043 *
044 * <p>
045 * A progression bar has 2 modes of execution. Depending on the
046 * <em>a priori</em> knowledge of the total steps, we have a Determinate mode or
047 * an Indeterminate mode.
048 * </p>
049 *
050 * <p>
051 * The terminal progression bar is constituted of 2 parts:
052 * <ol>
053 * <li>the current processed step (over all steps) found in the Left Margin (LM)
054 * </li>
055 * <li>the Progression Bar (PB) itself</li>
056 * </ol>
057 * Note that each LM and PB space lengths together with the length of the
058 * roaming segment in indeterminate mode are configurable. It is even possible
059 * to set the refreshing period of progression bar in this last mode.
060 * </p>
061 *
062 * <h4>Indeterminate Mode</h4>
063 *
064 * <pre>
065 * LM PB
066 * < -- >< -------- >
067 * 0 [===== ]
068 * 1 [ ===== ]
069 * 2 [ ===== ]
070 * 3 [ ===== ]
071 * 4 [ ===== ]
072 * 5 [ =====]
073 * 6 [ ===== ]
074 * 7 [ ===== ]
075 * ...
076 * </pre>
077 *
078 * <h4>Determinate Mode</h4>
079 *
080 * <pre>
081 * LM PB
082 * < -- >< -------- >
083 * 0/10 [ ]
084 * 1/10 [= ]
085 * 2/10 [== ]
086 * 3/10 [=== ]
087 * 4/10 [==== ]
088 * 5/10 [===== ]
089 * 6/10 [====== ]
090 * 7/10 [======= ]
091 * 8/10 [======== ]
092 * 9/10 [========= ]
093 * 10/10 [==========]
094 * </pre>
095 *
096 * @author nikitin
097 *
098 * @version 1.0
099 *
100 */
101 public final class TerminalProgressBar implements ProgressBar {
102
103 /** the default bar length */
104 private static int DEFAULT_BAR_LENGTH = 50;
105
106 /** the default length of indeterminated segment */
107 private static int INDETERMINATE_SEGMENT_LENGTH_RATIO = 2;
108
109 /** the default refresh period */
110 private static int DEFAULT_INDETERMINATED_BAR_REFRESH_PERIOD = 2;
111
112 private static int DEFAULT_LEFT_MARGIN_LENGTH = 5;
113
114 /** the default bar char segment */
115 private static char DEFAULT_SEGMENT = '=';
116
117 private static String DEFAULT_DONE_MESSAGE = "Done";
118
119 private static String DEFAULT_UNDONE_MESSAGE = "Incomplete";
120
121 /** progress bar length in chars */
122 private int barLength;
123
124 /** the bar char display element */
125 private char segment;
126
127 /** the segment length of indeterminate progress bar */
128 private int indeterminateSegmentLength;
129
130 /** the current number of completed steps */
131 private int completed;
132
133 /** the message displayed when process is over */
134 private String completedMessage;
135
136 /** the message displayed when process has been interrupted */
137 private String incompletedMessage;
138
139 /** the minimum bar value */
140 private int minimum;
141
142 /** the maximum bar value */
143 private int maximum;
144
145 /** true if the maximum value is unknown */
146 private boolean isIndeterminate;
147
148 /** the current position of cursor for indeterminate bar */
149 private int currentCursorPosition;
150 private boolean isCurrentTowardPositiveInfinity;
151
152 /** true if task has been completed */
153 private boolean isCompleted;
154
155 /** the print stream for progress bar display */
156 private PrintStream outStream;
157
158 /** the period of bar refresh for indeterminate bar */
159 private int barRefreshPeriod;
160
161 /** the number of update calls for indeterminate bar */
162 private int updateCount;
163
164 /** the last indetermined bar status */
165 private StringBuilder lastIndeterminedBarSb;
166
167 /** the left margin length */
168 private int leftMarginLength;
169
170 /** the task name appearing in the left margin */
171 private String taskName;
172
173 private TerminalProgressBar() {
174 barLength = DEFAULT_BAR_LENGTH;
175 segment = DEFAULT_SEGMENT;
176 isIndeterminate = false;
177 completedMessage = DEFAULT_DONE_MESSAGE;
178 incompletedMessage = DEFAULT_UNDONE_MESSAGE;
179
180 outStream = System.out;
181 }
182
183 public static TerminalProgressBar newInstance(int minimum, int maximum) {
184 TerminalProgressBar pb = new TerminalProgressBar();
185
186 pb.setMinimum(minimum);
187 pb.setMaximum(maximum);
188
189 return pb;
190 }
191
192 public static TerminalProgressBar indeterminate() {
193 TerminalProgressBar pb = new TerminalProgressBar();
194
195 pb.setMinimum(0);
196 pb.setIndeterminate(true);
197
198 return pb;
199 }
200
201 public void setTaskName(String name) {
202 this.taskName = name;
203 }
204
205 /**
206 * Set the left margin length (with completion infos).
207 *
208 * @param length the left margin length.
209 */
210 public void setLeftMarginLength(int length) {
211 this.leftMarginLength = length;
212 }
213
214 /**
215 * Set the progress bar length.
216 *
217 * @param length the given length.
218 */
219 public void setBarLength(int length) {
220 this.barLength = length;
221 if (isIndeterminate) {
222 computeIndeterminateSegmentlength();
223 }
224 }
225
226 /**
227 * Set the period of bar animation refresh while the bar is in indeterminate
228 * mode.
229 *
230 * @param period the period of refresh for cursor animation.
231 */
232 public void setRefreshBarPeriod(int period) {
233 barRefreshPeriod = period;
234 }
235
236 /**
237 * Set the length of segment constantly animated in indeterminate mode.
238 *
239 * @param length the given length (> 0 and < bar len).
240 */
241 public void setSegmentLength(int length) {
242 if (length <= 0) {
243 indeterminateSegmentLength = 1;
244 } else if (length >= barLength) {
245 indeterminateSegmentLength = barLength - 1;
246 } else {
247 indeterminateSegmentLength = length;
248 }
249 }
250
251 private void computeIndeterminateSegmentlength() {
252 indeterminateSegmentLength =
253 barLength / INDETERMINATE_SEGMENT_LENGTH_RATIO;
254 }
255
256 /**
257 * Set the print stream for bar display.
258 *
259 * @param ps the output stream.
260 */
261 public void setPrintStream(PrintStream ps) {
262 outStream = ps;
263 }
264
265 public final void setMinimum(int minimum) {
266 this.minimum = minimum;
267 }
268
269 public final void setMaximum(int maximum) {
270 this.maximum = maximum;
271
272 leftMarginLength = String.valueOf(maximum).length() + 1;
273 }
274
275 public void setDoneMessage(String message) {
276 this.completedMessage = message;
277 }
278
279 public void setIncompleteMessage(String message) {
280 this.incompletedMessage = message;
281 }
282
283 public void setIndeterminate(boolean bool) {
284 this.isIndeterminate = bool;
285
286 // init indeterminate bar
287 if (bool) {
288 start();
289 }
290 }
291
292 /**
293 * Initialize progress bar (mandatory to restart bar in Indeterminate mode)
294 */
295 public void start() {
296 isCompleted = false;
297
298 if (isIndeterminate) {
299 minimum = 0;
300 maximum = Integer.MAX_VALUE;
301
302 currentCursorPosition = 0;
303 isCurrentTowardPositiveInfinity = true;
304 updateCount = 0;
305 computeIndeterminateSegmentlength();
306
307 if (barRefreshPeriod == 0) {
308 barRefreshPeriod = DEFAULT_INDETERMINATED_BAR_REFRESH_PERIOD;
309 }
310
311 if (leftMarginLength == 0) {
312 leftMarginLength = DEFAULT_LEFT_MARGIN_LENGTH;
313 }
314 } else {
315 completed = minimum;
316 }
317 }
318
319 /**
320 * Interrupt the task (mandatory to complete Inderminate mode) <h4>Note 1</h4>
321 * If you want to restart another progression, you will have to call start()
322 * first.
323 */
324 public void stop() {
325 if (!isIndeterminate) {
326 if (completed < maximum) {
327 outStream.println(" " + incompletedMessage);
328 }
329 } else {
330 if (!isCompleted) {
331 this.isCompleted = true;
332
333 refreshIndeterminateBar();
334 }
335 }
336 }
337
338 public void setValue(int completed) {
339 if (completed < minimum) {
340 completed = minimum;
341 } else if (completed > maximum) {
342 completed = maximum;
343 } else {
344 this.completed = completed;
345 }
346
347 if (isIndeterminate) {
348 refreshIndeterminateBar();
349 } else {
350 refreshDeterminateBar();
351 }
352 }
353
354 public final boolean isIndeterminate() {
355 return isIndeterminate;
356 }
357
358 private void refreshDeterminateBar() {
359 if (maximum == 0) {
360 throw new IllegalStateException("maximum is not defined");
361 }
362
363 double progressPercentage = (double) completed / maximum;
364
365 // Note: "carriage return" returns to the beginning of the line
366 if (taskName != null && taskName.length() > 0) {
367 outStream.print("\r" + taskName + ":"
368 + String.format("%" + leftMarginLength + "s", completed));
369 } else {
370 outStream.print("\r"
371 + String.format("%" + leftMarginLength + "s", completed));
372 }
373 outStream.print("/" + maximum + " [");
374
375 int i = 0;
376 for (; i < (int) (progressPercentage * barLength); i++) {
377 outStream.print(segment);
378 }
379 for (; i < barLength; i++) {
380 outStream.print(" ");
381 }
382 outStream.print("]");
383
384 if (completed == maximum) {
385 outStream.println(" " + completedMessage);
386 }
387 }
388
389 private void refreshIndeterminateBar() {
390
391 if (taskName != null && taskName.length() > 0) {
392 outStream.print("\r" + taskName + ":"
393 + String.format("%" + leftMarginLength + "s", completed));
394 } else {
395 outStream.print("\r"
396 + String.format("%" + leftMarginLength + "s", completed));
397 }
398 int i = 0;
399
400 if (isCompleted) {
401 outStream.print(" [");
402 for (; i < barLength; i++) {
403 outStream.print(segment);
404 }
405 outStream.print("]");
406 outStream.println(" " + completedMessage);
407
408 return;
409 } else if (updateCount % barRefreshPeriod == 0) {
410 lastIndeterminedBarSb = new StringBuilder(" [");
411
412 for (; i < currentCursorPosition; i++) {
413 lastIndeterminedBarSb.append(" ");
414 }
415
416 for (int j = 0; j < indeterminateSegmentLength; j++) {
417 lastIndeterminedBarSb.append(segment);
418 }
419
420 for (; i < barLength - indeterminateSegmentLength; i++) {
421 lastIndeterminedBarSb.append(" ");
422 }
423 lastIndeterminedBarSb.append("]");
424
425 /** has current cursor reached boundaries ? */
426 if (currentCursorPosition == barLength - indeterminateSegmentLength) {
427 isCurrentTowardPositiveInfinity = false;
428 } else if (currentCursorPosition == 0) {
429 isCurrentTowardPositiveInfinity = true;
430 }
431
432 /** next direction */
433 if (isCurrentTowardPositiveInfinity) {
434 currentCursorPosition++;
435 } else {
436 currentCursorPosition--;
437 }
438 }
439
440 outStream.print(lastIndeterminedBarSb);
441
442 updateCount++;
443 }
444 }