All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.ceylon.common.tool.WordWrap Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
package com.redhat.ceylon.common.tool;

import java.io.Flushable;
import java.io.IOException;
import java.text.BreakIterator;
import java.util.TreeSet;

/**
 * Facility for producing nicely word-wrapped output.
 * @author tom
 */
public class WordWrap {

    private class LineOutput {
        private final Appendable out;
        /** The current column */
        private int pos = 0;
        /** true if we're at the beginning of a line*/
        private boolean bol = true;
        /** true if this is a first line */
        private boolean fl = true;
        private String prefix = null;
        
        public LineOutput(Appendable out) {
            this.out = out;
        }
        public void append(String word) {
            try {
                if (bol) {
                    spaces(fl ? indentFirstLine : indentRestLines);
                    prefix();
                }
                out.append(word);
            } catch (IOException e) {
                // ignore it
            }
            pos+=word.length();
        }
        private void prefix() throws IOException {
            if (prefix != null) {
                out.append(prefix);
                pos += prefix.length();
                bol = pos == 0;
            }
        }
        private void newline(boolean fl) {
            try {
                out.append(System.getProperty("line.separator"));
                pos = 0;
                bol = true;
                this.fl = fl;
            } catch (IOException e) {
                // ignore it
            } 
        }
        private void newlineHard() {
            newline(true);
        }
        private void newlineSoft() {
            newline(false);
        }
        private void spaces(int num) {
            for (int ii = 0; ii < num; ii++) {
                bol = false;
                try {
                    out.append(' ');
                } catch (IOException e) {
                    // ignore it
                }
            }
            pos += num;
        }
        public void spacesToColumn(int column) {
            spaces(Math.max(column - pos, 0));
        }
        public int column() {
            return pos;
        }
    }
    
    private final int width;
    private int rightIndent = 0;
    private TreeSet tabstops = new TreeSet();
    private int indentFirstLine;
    private int indentRestLines;
    private final LineOutput out;
    
    /** 
     * Creates a wordwrap instance wrapping 
     * {@linkplain #System.out standard output}.
     */
    public WordWrap() {
        this(System.out);
    }
    
    public WordWrap(Appendable out) {
        this(out, Integer.getInteger("com.redhat.ceylon.common.tool.terminal.width", 80));
    }
    
    public WordWrap(Appendable out, int width) {
        if (width <= 0) {
            throw new IllegalArgumentException();
        }
        this.width = width;
        this.out = new LineOutput(out);
    }
    
    private void boundsCheck(int column) {
        if (column < 0 || column >= width) {
            throw new IllegalArgumentException();
        }
    }
    
    /** 
     * Sets the indentation level. Equivalent to calling 
     * {@link #setIndentFirstLine(int) setIndentFirstLine(indent)} followed by
     * {@link #setIndentRestLines(int) setIndentRestLines(indent)}.
     */
    public void setIndent(int indent) {
        setIndentFirstLine(indent);
        setIndentRestLines(indent);
    }
    
    /** 
     * Sets the indentation level of first lines
     * @see #setIndentRestLines(int) 
     */
    public void setIndentFirstLine(int indent) {
        boundsCheck(indent);
        this.indentFirstLine = indent;
    }
    
    public int getIndentFirstLine() {
        return this.indentFirstLine;
    }
    
    /** 
     * Sets the indentation level of non-first lines
     * @see #setIndentFirstLines(int) 
     */
    public void setIndentRestLines(int indent) {
        boundsCheck(indent);
        this.indentRestLines = indent;
    }
    
    public int getIndentRestLines() {
        return this.indentRestLines;
    }
    
    /** 
     * Sets the right margin 
     */
    public void setRightIndent(int indent) {
        boundsCheck(indent);
        this.rightIndent = indent;
    }
    
    public int getRightIndent() {
        return this.rightIndent;
    }

    public void setPrefix(String prefix) {
        out.prefix = prefix;
    }
    
    public String getPrefix() {
        return out.prefix;
    }

    public int getWidth() {
        return width;
    }
    
    /**
     * Adds a tab stop.
     * @param stop
     * @see #tab()
     * @see #removeTabStop(int)
     */
    public void addTabStop(int stop) {
        boundsCheck(stop);
        tabstops.add(stop);
    }
    
    /**
     * Removes a tab stop.
     * @param stop
     */
    public void removeTabStop(int stop) {
        tabstops.remove(stop);
    }
    
    /**
     * Clears all tab stops.
     * @param stop
     */
    public void clearTabStops() {
        tabstops.clear();
    }
    
    private String collapseWs(String s) {
        return s.replaceAll("\\s+", " ");
    }
    
    private String trimRight(String s) {
        return s.replaceAll("\\s+$", "");
    }
    
    private String prepend = "";
    
    /**
     * Appends the given string to the output.
     * @param s The string.
     */
    public WordWrap append(String s) {
        s = collapseWs(s);
        String prependNext = s.endsWith(" ") ? " " : "";
        s = prepend + trimRight(s);
        prepend = prependNext;
        
        // TODO Some control over soft and hard hyphens
        // spaces and newlines
        BreakIterator bi = BreakIterator.getLineInstance();
        bi.setText(s);
        int endOfLast = 0;
        int ii;
        // iterate forwards through characters...
        for (ii = 0; ii < s.length(); ii++) {
            // ...until the line is too long...
            if (exceedsWidth(endOfLast, ii)) {
                endOfLast = addLineBreak(s, bi, endOfLast, ii);
            }
            
        }
        if (exceedsWidth(endOfLast, ii)) {
            ii--;
            endOfLast = addLineBreak(s, bi, endOfLast, ii);
        }
        s = s.substring(endOfLast);
        out.append(s);
        
        return this;
    }

    private int addLineBreak(String s, BreakIterator bi, int endOfLast, int ii) {
        int safe = ii;
        while (!bi.isBoundary(ii)) {
            ii--;
            if (ii < endOfLast) {
                ii = safe;
                break;
            }
        }
        out.append(trimRight(s.substring(endOfLast, ii)));
        out.newlineSoft();
        endOfLast = ii;
        return endOfLast;
    }

    private boolean exceedsWidth(int endOfLast, int ii) {
        return out.column() + (ii-endOfLast) >= width-rightIndent;
    }
    
    /**
     * Prints a hard new line (in other words, starts a new paragraph).
     */
    public WordWrap newline() {
        this.prepend = "";
        out.newlineHard();
        return this;
    }
    
    /**
     * Moves to the next tab stop, that is the one to the right of the 
     * {@linkplain #getColumn() current column}.
     * @see #addTabStop(int)
     */
    public WordWrap tab() {
        out.spacesToColumn(tabstops.higher(out.column()));
        return this;
    }
    
    /**
     * If {@link #getColumn()}{@code < column} then writes sufficient spaces 
     * so that the next chararacter will appear in that column. 
     * Otherwise does nothing.
     * @param column
     * @return
     */
    public WordWrap column(int column) {
        out.spacesToColumn(column);
        return this;
    }
    
    /**
     * Gets the column position where the next character will be written 
     * @return
     */
    public int getColumn() {
        return out.column();
    }
    
    /**
     * Flushes the output, if it is {@link Flushable}.
     */
    public void flush() {
        try {
            if (out instanceof Flushable) {
                ((Flushable)out).flush();
            }
        } catch (IOException e) {
            // ignore
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy