package org.nextprot.api.commons.utils.app;
import java.io.PrintStream;
import java.util.Objects;
/**
* A simple terminal progress bar.
* <p/>
* <h4>Description</h4>
* <p>
* A progress bar typically communicates the progress of some work by displaying
* its percentage of completion and possibly a textual display of this
* percentage.
* </p>
* <p/>
* <p>
* A progression bar has 2 modes of execution. Depending on the
* <em>a priori</em> knowledge of the total steps, we have a Determinate mode or
* an Indeterminate mode.
* </p>
* <p/>
* <p>
* The terminal progression bar is constituted of 2 parts:
* <ol>
* <li>the current processed step (over all steps) found in the Left Margin (LM)
* </li>
* <li>the Progression Bar (PB) itself</li>
* </ol>
* Note that each LM and PB space lengths together with the length of the
* roaming segment in indeterminate mode are configurable. It is even possible
* to set the refreshing period of progression bar in this last mode.
* </p>
* <p/>
* <h4>Indeterminate Mode</h4>
* <p/>
* <pre>
* LM PB
* < -- >< -------- >
* 0 [===== ]
* 1 [ ===== ]
* 2 [ ===== ]
* 3 [ ===== ]
* 4 [ ===== ]
* 5 [ =====]
* 6 [ ===== ]
* 7 [ ===== ]
* ...
* </pre>
* <p/>
* <h4>Determinate Mode</h4>
* <p/>
* <pre>
* LM PB
* < -- >< -------- >
* 0/10 [ ]
* 1/10 [= ]
* 2/10 [== ]
* 3/10 [=== ]
* 4/10 [==== ]
* 5/10 [===== ]
* 6/10 [====== ]
* 7/10 [======= ]
* 8/10 [======== ]
* 9/10 [========= ]
* 10/10 [==========]
* </pre>
*
* @author nikitin
* @version 1.0
*/
public class ConsoleProgressBar {
/**
* the current number of completed steps
*/
private int value;
/**
* the minimum bar value
*/
private int minimum;
/**
* the maximum bar value
*/
private int maximum;
/**
* true if the maximum value is unknown
*/
private boolean isIndeterminate;
/**
* true if task has been completed
*/
private boolean hasCompleted;
private View view;
private ConsoleProgressBar() {
this.view = new View();
}
public static ConsoleProgressBar determinated(int maximum) {
ConsoleProgressBar pb = new ConsoleProgressBar();
pb.setMinimum(0);
pb.setMaximum(maximum);
pb.setIndeterminate(false);
return pb;
}
public static ConsoleProgressBar indeterminated() {
ConsoleProgressBar pb = new ConsoleProgressBar();
pb.setMinimum(0);
pb.setIndeterminate(true);
return pb;
}
public void setView(View view) {
this.view = view;
}
public void setTaskName(String name) {
view.setTaskName(name);
}
public final void setMinimum(int minimum) {
this.minimum = minimum;
}
public final void setMaximum(int maximum) {
this.maximum = maximum;
view.setLeftMarginLength(String.valueOf(maximum).length() + 1);
isIndeterminate = false;
}
public final void setMaximumDoNoResetLeftMargin(int maximum) {
this.maximum = maximum;
isIndeterminate = false;
}
public void setIndeterminate(boolean bool) {
this.isIndeterminate = bool;
// init indeterminate bar
if (bool) {
start();
}
}
/**
* Initialize progress bar (mandatory to restart bar in Indeterminate mode)
*/
public void start() {
hasCompleted = false;
if (isIndeterminate) {
minimum = value = 0;
maximum = Integer.MAX_VALUE;
view.resetIndeterminateBar();
} else {
value = minimum;
}
}
/**
* Interrupt the task (mandatory to complete Inderminate mode) <h4>Note 1</h4>
* If you want to restart another progression, you will have to call start()
* first.
*/
public void stop() {
if (!isIndeterminate) {
if (value < maximum) {
view.ps.println(" " + view.incompletedMessage);
}
value = minimum;
} else {
if (!hasCompleted) {
this.hasCompleted = true;
view.refreshIndeterminateBar(value, hasCompleted);
}
value = 0;
}
}
public void setValue(int completed) {
if (completed < minimum) {
value = minimum;
} else if (completed > maximum) {
value = maximum;
} else {
this.value = completed;
}
if (isIndeterminate) {
view.refreshIndeterminateBar(value, hasCompleted);
} else {
view.refreshDeterminateBar(value, maximum);
}
}
public void incrementValue() {
setValue(value + 1);
}
public int getValue() {
return value;
}
public final boolean isIndeterminate() {
return isIndeterminate;
}
public void setPrintStream(PrintStream ps) {
view.setPrintStream(ps);
}
public static class View {
/**
* the default bar length
*/
private static final int DEFAULT_BAR_LENGTH = 50;
/**
* the default length of indeterminated segment
*/
private static final int INDETERMINATE_SEGMENT_LENGTH_RATIO = 2;
/**
* the default refresh period
*/
private static final int DEFAULT_INDETERMINATED_BAR_REFRESH_PERIOD = 2;
private static final int DEFAULT_LEFT_MARGIN_LENGTH = 5;
/**
* the default bar char segment
*/
private static final char DEFAULT_SEGMENT = '=';
private static final String DEFAULT_DONE_MESSAGE;
static {
DEFAULT_DONE_MESSAGE = "Done";
}
private static final String DEFAULT_UNDONE_MESSAGE = "Incomplete";
/**
* progress bar length in chars
*/
private int barLength;
/**
* the bar char display element
*/
private final char segment;
/**
* the segment length of indeterminate progress bar
*/
private int indeterminateSegmentLength;
/**
* the message displayed when process is over
*/
private String completedMessage;
/**
* the message displayed when process has been interrupted
*/
private String incompletedMessage;
/**
* the period of bar refresh for indeterminate bar
*/
private int barRefreshPeriod;
/**
* the number of update calls for indeterminate bar
*/
private int updateCount;
/**
* the last indetermined bar status
*/
private StringBuilder lastIndeterminedBarSb;
/**
* the left margin length
*/
private int leftMarginLength;
/**
* the task name appearing in the left margin
*/
private String taskName;
/**
* the current position of cursor for indeterminate bar
*/
private int currentCursorPosition;
private boolean isCurrentTowardPositiveInfinity;
/**
* the print stream for progress bar display
*/
private PrintStream ps;
public View() {
barLength = DEFAULT_BAR_LENGTH;
segment = DEFAULT_SEGMENT;
completedMessage = DEFAULT_DONE_MESSAGE;
incompletedMessage = DEFAULT_UNDONE_MESSAGE;
ps = System.out;
}
public void resetIndeterminateBar() {
currentCursorPosition = 0;
isCurrentTowardPositiveInfinity = true;
updateCount = 0;
computeIndeterminateSegmentlength();
if (barRefreshPeriod == 0) {
barRefreshPeriod = View.DEFAULT_INDETERMINATED_BAR_REFRESH_PERIOD;
}
if (leftMarginLength == 0) {
leftMarginLength = View.DEFAULT_LEFT_MARGIN_LENGTH;
}
}
/**
* Set the left margin length (with completion infos).
*
* @param length the left margin length.
*/
public void setLeftMarginLength(int length) {
this.leftMarginLength = length;
}
/**
* Set the progress bar length.
*
* @param length the given length.
*/
public void setBarLength(int length, boolean isIndetermined) {
this.barLength = length;
if (isIndetermined) {
computeIndeterminateSegmentlength();
}
}
/**
* Set the period of bar animation refresh while the bar is in indeterminate
* mode.
*
* @param period the period of refresh for cursor animation.
*/
public void setRefreshBarPeriod(int period) {
barRefreshPeriod = period;
}
/**
* Set the length of segment constantly animated in indeterminate mode.
*
* @param length the given length (> 0 and < bar len).
*/
public void setSegmentLength(int length) {
if (length <= 0) {
indeterminateSegmentLength = 1;
} else if (length >= barLength) {
indeterminateSegmentLength = barLength - 1;
} else {
indeterminateSegmentLength = length;
}
}
private void computeIndeterminateSegmentlength() {
indeterminateSegmentLength =
barLength / INDETERMINATE_SEGMENT_LENGTH_RATIO;
}
public void setTaskName(String name) {
this.taskName = name;
}
public void setDoneMessage(String message) {
this.completedMessage = message;
}
public void setIncompleteMessage(String message) {
this.incompletedMessage = message;
}
private void updateLeftMargin(PrintStream out, int value, int maximum) {
// Note: "carriage return" returns to the beginning of the line
out.append("\r");
if (taskName != null && taskName.length() > 0) {
out.append(taskName).append(":");
}
if (maximum == 0) {
out.append(String.format(" %" + leftMarginLength + "s", value));
} else {
out.append(String.format(" %" + leftMarginLength + "s", value + "/" + maximum));
}
}
private void refreshDeterminateBar(int value, int maximum) {
if (maximum == 0) {
throw new IllegalStateException("maximum is not defined");
}
double progressPercentage = (double) value / maximum;
updateLeftMargin(ps, value, maximum);
ps.print(" [");
int i = 0;
for (; i < (int) (progressPercentage * barLength); i++) {
ps.print(segment);
}
for (; i < barLength; i++) {
ps.print(" ");
}
ps.print("]");
if (value == maximum) {
ps.println(" " + completedMessage);
}
}
private void refreshIndeterminateBar(int value, boolean hasCompleted) {
updateLeftMargin(ps, value, 0);
int i = 0;
if (hasCompleted) {
ps.print(" [");
for (; i < barLength; i++) {
ps.print(segment);
}
ps.print("]");
ps.println(" " + completedMessage);
return;
} else if (updateCount % barRefreshPeriod == 0) {
lastIndeterminedBarSb = new StringBuilder(" [");
for (; i < currentCursorPosition; i++) {
lastIndeterminedBarSb.append(" ");
}
for (int j = 0; j < indeterminateSegmentLength; j++) {
lastIndeterminedBarSb.append(segment);
}
for (; i < barLength - indeterminateSegmentLength; i++) {
lastIndeterminedBarSb.append(" ");
}
lastIndeterminedBarSb.append("]");
/** has current cursor reached boundaries ? */
if (currentCursorPosition == barLength - indeterminateSegmentLength) {
isCurrentTowardPositiveInfinity = false;
} else if (currentCursorPosition == 0) {
isCurrentTowardPositiveInfinity = true;
}
/** next direction */
if (isCurrentTowardPositiveInfinity) {
currentCursorPosition++;
} else {
currentCursorPosition--;
}
}
ps.print(lastIndeterminedBarSb);
updateCount++;
}
/**
* Set the print stream for bar display.
*
* @param ps the output stream.
*/
public void setPrintStream(PrintStream ps) {
Objects.requireNonNull(ps);
this.ps = ps;
}
public PrintStream getPrintStream() {
return ps;
}
}
}