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 (&gt; 0 and &lt; 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    }