org.nanocom.console.helper.ProgressHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.nanocom.console.helper.ProgressHelper.java

Source

/*
 * This file is part of the Console package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

package org.nanocom.console.helper;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import static org.apache.commons.lang3.StringUtils.*;
import org.nanocom.console.exception.LogicException;
import org.nanocom.console.output.OutputInterface;

/**
 * The Progress class helper to display progress output.
 *
 * @author Arnaud Kleinpeter <arnaud.kleinpeter at gmail dot com>
 */
public class ProgressHelper extends Helper {

    private static final String FORMAT_QUIET = " %percent%%";
    private static final String FORMAT_NORMAL = " %current%/%max% [%bar%] %percent%%";
    private static final String FORMAT_VERBOSE = " %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%";
    private static final String FORMAT_QUIET_NOMAX = " %current%";
    private static final String FORMAT_NORMAL_NOMAX = " %current% [%bar%]";
    private static final String FORMAT_VERBOSE_NOMAX = " %current% [%bar%] Elapsed: %elapsed%";

    private static final char BACKSPACE = ')';

    // Options
    private int barWidth = 28;
    private char barChar = '=';
    private char emptyBarChar = '-';
    private String progressChar = ">";
    private String format = null;
    private int redrawFreq = 1;

    private char barCharOriginal;

    private OutputInterface output;

    /**
     * Current step
     */
    private int current;

    /**
     * Maximum number of steps
     */
    private int max;

    /**
     * Start time of the progress bar
     */
    private Long startTime;

    /**
     * List of formatting variables
     */
    private String[] defaultFormatVars = new String[] { "current", "max", "bar", "percent", "elapsed", };

    /**
     * Available formatting variables
     */
    private Map<String, Boolean> formatVars;

    /**
     * Stored format part widths (used for padding)
     */
    private Map<String, Integer> widths;

    /**
     * Various time formats
     */
    private Object[][] timeFormats = new Object[][] { new Object[] { 0, "???" }, new Object[] { 2, "1 sec" },
            new Object[] { 59, "secs", 1 }, new Object[] { 60, "1 min" }, new Object[] { 3600, "mins", 60 },
            new Object[] { 5400, "1 hr" }, new Object[] { 86400, "hrs", 3600 }, new Object[] { 129600, "1 day" },
            new Object[] { 604800, "days", 86400 } };

    public ProgressHelper() {
        widths = new HashMap<String, Integer>();
        widths.put("current", 4);
        widths.put("max", 4);
        widths.put("percent", 3);
        widths.put("elapsed", 6);
    }

    /**
     * Sets the progress bar width.
     *
     * @param size The progress bar size
     */
    public void setBarWidth(int size) {
        barWidth = size;
    }

    /**
     * Sets the bar character.
     *
     * @param ch A character
     */
    public void setBarCharacter(char ch) {
        barChar = ch;
    }

    /**
     * Sets the empty bar character.
     *
     * @param char A character
     */
    public void setEmptyBarCharacter(char ch) {
        emptyBarChar = ch;
    }

    /**
     * Sets the progress bar character.
     *
     * @param char A character
     */
    public void setProgressCharacter(String ch) {
        progressChar = ch;
    }

    /**
     * Sets the progress bar format.
     *
     * @param format The format
     */
    public void setFormat(String format) {
        this.format = format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param freq The frequency in seconds
     */
    public void setRedrawFrequency(int freq) {
        redrawFreq = freq;
    }

    /**
     * Starts the progress output.
     *
     * @param output An Output instance
     * @param max    Maximum steps
     */
    public void start(OutputInterface output, int max) {
        startTime = System.currentTimeMillis();
        current = 0;
        this.max = max;
        this.output = output;

        if (null == format) {
            switch (output.getVerbosity()) {
            case QUIET:
                format = FORMAT_QUIET_NOMAX;
                if (this.max > 0) {
                    this.format = FORMAT_QUIET;
                }
                break;

            case VERBOSE:
                format = FORMAT_VERBOSE_NOMAX;
                if (this.max > 0) {
                    this.format = FORMAT_VERBOSE;
                }
                break;

            default:
                format = FORMAT_NORMAL_NOMAX;
                if (this.max > 0) {
                    format = FORMAT_NORMAL;
                }
                break;
            }
        }

        initialize();
    }

    /**
     * Starts the progress output.
     *
     * @param output  An Output instance
     */
    public void start(OutputInterface output) {
        start(output, 0);
    }

    /**
     * Advances the progress output X steps.
     *
     * @param step   Number of steps to advance
     * @param redraw Whether to redraw or not
     */
    public void advance(int step, boolean redraw) {
        if (null == startTime) {
            throw new LogicException("You must start the progress bar before calling advance().");
        }

        if (0 == current) {
            redraw = true;
        }

        current += step;

        if (redraw || 0 == current % redrawFreq) {
            display();
        }
    }

    /**
     * Advances the progress output X steps.
     *
     * @param step Number of steps to advance
     */
    public void advance(int step) {
        advance(step, false);
    }

    /**
     * Advances the progress output 1 step.
     */
    public void advance() {
        advance(1);
    }

    /**
     * Outputs the current progress string.
     *
     * @param finish Forces the end result
     */
    public void display(boolean finish) {
        if (null == startTime) {
            throw new LogicException("You must start the progress bar before calling display().");
        }

        String message = format;
        for (Entry<String, String> val : generate(finish).entrySet()) {
            message = message.replaceAll("%" + val.getKey() + "%", val.getValue());
        }

        overwrite(output, message);
    }

    /**
     * Outputs the current progress string.
     */
    public void display() {
        display(false);
    }

    /**
     * Finishes the progress output.
     */
    public void finish() {
        if (null == startTime) {
            throw new LogicException("You must start the progress bar before calling finish().");
        }

        if (null != startTime) {
            if (0 == max) {
                barChar = barCharOriginal;
                display(true);
            }

            startTime = null;
            output.writeln("");
            output = null;
        }
    }

    /**
     * Initializes the progress helper.
     */
    private void initialize() {
        formatVars = new LinkedHashMap<String, Boolean>();
        for (String var : defaultFormatVars) {
            if (format.contains("%" + var + "%")) {
                formatVars.put(var, Boolean.TRUE);
            }
        }

        if (max > 0) {
            int maxWidth = String.valueOf(max).length();
            widths.put("max", maxWidth);
            widths.put("current", maxWidth);
        } else {
            barCharOriginal = barChar;
            barChar = emptyBarChar;
        }
    }

    /**
     * Generates the map of format variables to values.
     *
     * @param finish Forces the end result
     *
     * @return A map of format vars and values
     */
    private Map<String, String> generate(boolean finish) {
        Map<String, String> vars = new LinkedHashMap<String, String>();
        Double percent = Double.valueOf(0);

        if (max > 0) {
            percent = round((double) current / max, 1);
        }

        if (formatVars.containsKey("bar")) {
            int completeBars;

            if (max > 0) {
                completeBars = Double.valueOf(Math.floor(percent * barWidth)).intValue();
            } else {
                completeBars = finish ? barWidth : Double.valueOf(Math.floor(current % barWidth)).intValue();
            }

            int emptyBars = barWidth - completeBars - progressChar.length();
            String bar = repeat(barChar, completeBars);
            if (completeBars < barWidth) {
                bar += progressChar;
                bar += repeat(emptyBarChar, emptyBars);
            }

            vars.put("bar", bar);
        }

        if (formatVars.containsKey("elapsed")) {
            long elapsed = System.currentTimeMillis() - startTime;
            vars.put("elapsed", leftPad(humaneTime(elapsed), widths.get("elapsed"), " "));
        }

        if (formatVars.containsKey("current")) {
            vars.put("current", leftPad(String.valueOf(current), widths.get("current"), " "));
        }

        if (formatVars.containsKey("max")) {
            vars.put("max", String.valueOf(max));
        }

        if (formatVars.containsKey("percent")) {
            vars.put("percent",
                    leftPad(String.valueOf(new Double(percent * 100).intValue()), widths.get("percent"), " "));
        }

        return vars;
    }

    private double round(double value, int decimals) {
        double factor = Math.pow(10, decimals);
        return Math.round(value * factor) / factor;
    }

    /**
     * Converts seconds into human-readable format.
     *
     * @param secs Number of seconds
     *
     * @return Time in readable format
     */
    private String humaneTime(long secs) {
        String text = "???";

        for (Object[] form : timeFormats) {
            if (secs < (Integer) form[0]) {
                if (2 == form.length) {
                    text = (String) form[1];
                    break;
                } else {
                    text = Math.ceil(secs / (Integer) form[2]) + " " + (String) form[1];
                    break;
                }
            }
        }

        return text;
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param output  An Output instance
     * @param message The message
     * @param newline Whether to add a newline or not
     * @param size    The size of line
     */
    private void overwrite(OutputInterface output, String message, boolean newline, int size) {
        output.write(repeat(BACKSPACE, size));
        output.write(message, false);
        output.write(repeat(" ", size - message.length()));

        // Clean up the end line
        output.write(repeat(BACKSPACE, size - message.length()));

        if (newline) {
            output.writeln("");
        }
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param output  An Output instance
     * @param message The message
     * @param newline Whether to add a newline or not
     */
    private void overwrite(OutputInterface output, String message, boolean newline) {
        overwrite(output, message, newline, 80);
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param output  An Output instance
     * @param message The message
     */
    private void overwrite(OutputInterface output, String message) {
        overwrite(output, message, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getName() {
        return "progress";
    }
}