2
\$\begingroup\$

If you'd like to pivot this:

┌────────────────────┐
│One   Two Three Four│
╞════════════════════╡
│One     2   3      4│
│Five    6   7      8│
│Nine [10]  11   12  │
└────────────────────┘

to this:

┌───────────────────┐
│One   One Five Nine│
╞═══════════════════╡
│Two     2  6   [10]│
│Three   3  7     11│
│Four    4  8   12  │
└───────────────────┘

using this:

Table table = new Table()
    .addLine("One", "Two", "Three", "Four")
    .addLine("One", "2", "3", "4")
    .addLine("Five", "6", "7", "8")
    .addLine("Nine", "[10]", "11", "12")
    .boxWith(
        "┌─┐" +
        "╞═╡" +
        "│ │" +
        "└─┘"
    )
    .alignRight()
    .alignLeft(0)        
    .alignRight(1)
    .alignCenter(2)
    .alignLeft(3,3)
    .boxHeader()
;
System.out.println(table);

Table pivot = table
    .toPivot()
    .alignRight()
    .alignLeft(0)        
    .alignRight(1)
    .alignCenter(2)
    .alignLeft(3,3)
;
System.out.println(pivot);

All you need is:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;  
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.IntStream;
import java.io.PrintStream;

public class Table {

    private final List<Column> columns = new ArrayList<>();

    // Defaults
    private String columnSeparator = " ";
    private char pad = ' ';
    private PrintStream out = System.out;
    
    private Table.Alignment alignment;
    private static final Table.Alignment LEFT = (left, word, right) -> word + left + right;
    private static final Table.Alignment CENTER = (left, word, right) -> left + word + right;
    private static final Table.Alignment RIGHT = (left, word, right) -> left + right + word;

    private String boxCharacters;
    private BoxType boarder;

    {
        alignLeft(); // Set default alignment
        boxWith( // Set default boxing characters
            ".-." +
            "|=|" +
            "| |" +
            "'-'"
        );
        boxOff(); // Set default boxing boarder
    }

    public Table addLine(String... line) {

        if ( columns.size() > 0 && columns.size() != line.length) {
            throw new IllegalArgumentException("Inconsistant arg. count.");
        }

        if ( columns.size() == 0 ) {
            for (int i = 0; i < line.length; i++) {
                columns.add( new Table.Column(alignment) );
            }
        }
        
        IntStream
            .range( 0, columns.size() )
            .forEach( col -> columns.get(col).addWord(line[col]) )
        ;

        return this;
    }
    
    public Table toPivot(){
        Table result = new Table();
        for(Column column : columns){
            result.addLine( column.getWords() );
        }
        result
            .separateColumnsWith(columnSeparator)
            .padWith(pad)
            .boxWith(boxCharacters)
            .boxType(boarder)
            .outputWith(out)
            .alignLeft()
        ;

        return result;
    }

    // .---{ Boxing }---. //    
    public Table box() {
        boarder = BoxType.BOX;
        return this;
    }
    
    public Table boxOff() {
        boarder = BoxType.BOXOFF;
        return this;
    }
    
    public Table boxHeader() {
        boarder = BoxType.HEAD;
        return this;
    }
    
    private Table boxType(BoxType boarder){
        this.boarder = boarder;
        return this;
    }
    
    // '---{ Boxing }---' //    
    
    public void print(){
        out.println( toString() );
    }

    public String toString(){
        
        final int rows = columns.get(0).size();

        return new Table.Bound().box(
            boarder,
            IntStream
            .range(0, rows)
            .mapToObj(row -> columns
                .stream()
                .map( col -> col.getCell(row) )
                .collect( Collectors.joining( columnSeparator ) )
            )
            .collect( Collectors.joining( System.lineSeparator() ) )
        );
    }
    
   // .--{ Make defaults overridable }--. //
   public Table outputWith(PrintStream out){
        this.out = out;
        return this;
    }

    public Table separateColumnsWith(String columnSeparator){
        this.columnSeparator = columnSeparator;
        return this;
    }

    public Table padWith(char pad){
        this.pad = pad;
        return this;
    }

    public Table boxWith(String box){
        this.boxCharacters = box;
        return this;
    }
    // '--{ Make defaults overridable }--' //

    public Table alignLeft(){
        alignment = LEFT;
        columns.forEach(col -> col.setAlignment(alignment));
        return this;
    }

    public Table alignCenter(){
        alignment = CENTER;
        columns.forEach(col -> col.setAlignment(alignment));
        return this;
    }

    public Table alignRight(){
        alignment = RIGHT;
        columns.forEach(col -> col.setAlignment(alignment));
        return this;
    }

    public Table alignLeft(int col){
        columns.get(col).setAlignment( LEFT );
        return this;
    }

    public Table alignCenter(int col){
        columns.get(col).setAlignment( CENTER );
        return this;
    }

    public Table alignRight(int col){
        columns.get(col).setAlignment( RIGHT );
        return this;
    }
    
    public Table alignLeft(int col, int cell){
        columns.get(col).setAlignment( LEFT, cell );
        return this;
    }

    public Table alignCenter(int col, int cell){
        columns.get(col).setAlignment( CENTER, cell );
        return this;
    }

    public Table alignRight(int col, int cell){
        columns.get(col).setAlignment( RIGHT, cell );
        return this;
    }

    @FunctionalInterface 
    interface Alignment { 
        String align(String left, String word, String right); 
    }

    enum BoxType {
        BOXOFF(Table.Bound::boxOff),
        BOX(Table.Bound::box),
        HEAD(Table.Bound::boxHeader);
        
        BiFunction<Table.Bound, String, String> boxLogic;
        
        BoxType(BiFunction<Table.Bound, String, String> boxLogic){
            this.boxLogic = boxLogic;
        }
        
    }
    
    class Bound {

        private String 
            cornerTL, 
            top, 
            cornerTR, 
            sideLH, 
            middleH, 
            sideRH, 
            sideL, 
            middle, 
            sideR, 
            cornerBL, 
            bottom,
            cornerBR 
        ;
    
        { 
            setBoxCharacters( boxCharacters ); 
        }

        public void setBoxCharacters(String boxCharacters) {
            if (boxCharacters.length() != 12) {
                throw new IllegalArgumentException("box characters string unsupported size");
            }

            cornerTL = boxCharacters.substring(0,1);
            top = boxCharacters.substring(1,2);
            cornerTR = boxCharacters.substring(2,3);
            sideLH = boxCharacters.substring(3,4);
            middleH = boxCharacters.substring(4,5);
            sideRH = boxCharacters.substring(5,6);
            sideL = boxCharacters.substring(6,7);
            middle = boxCharacters.substring(7,8);
            sideR = boxCharacters.substring(8,9);
            cornerBL = boxCharacters.substring(9,10);
            bottom = boxCharacters.substring(10,11);
            cornerBR = boxCharacters.substring(11,12);
        }

        public String box(BoxType boxType, String body){
            return boxType.boxLogic.apply(this, body);
        }
        
        public String boxOff(String body){
            return body;
        }
        
        public String box(String body){
            final int width = body.indexOf(System.lineSeparator());
            
            // One Two Three Four
            // 1   2   3     4
            // 5   6   7     8
            
            body = body.replace(System.lineSeparator(), sideR + System.lineSeparator() + sideL);

            // One Two Three Four|
            // |1   2   3     4  |
            // |5   6   7     8  
            
            body = 
                cornerTL + top.repeat(width) + cornerTR + System.lineSeparator() +
                sideL + body + sideR + System.lineSeparator() +
                cornerBL + bottom.repeat(width) + cornerBR
            ;
            
            // .------------------.
            // |One Two Three Four|
            // |1   2   3     4   |
            // |5   6   7     8   |
            // '------------------'
    
            return body;
        }
        
        public String boxHeader(String body){
            final int width = body.indexOf(System.lineSeparator());
            
            // One Two Three Four
            // 1   2   3     4
            // 5   6   7     8

            body = body.replaceFirst(
                System.lineSeparator(), 
                System.lineSeparator() + middleH.repeat(width) + System.lineSeparator()
            );
            
            // One Two Three Four
            // ------------------
            // 1   2   3     4
            // 5   6   7     8

            body = box(body);
            
            // .------------------.
            // |One Two Three Four|
            // |------------------|
            // |1   2   3     4   |
            // |5   6   7     8   |
            // '------------------'

            //Replace header sides
            final int ls = System.lineSeparator().length();
            final int lineLength = 1+width+1+ls;
            
            body = 
                body.substring( 0, 2*lineLength ) + 
                sideLH + middleH.repeat(width) + sideRH + System.lineSeparator() +
                body.substring( 3*lineLength, body.length() )
            ;

            // .------------------.
            // |One Two Three Four|
            // }------------------{
            // |1   2   3     4   |
            // |5   6   7     8   |
            // '------------------'

            return body;
        }
    }

    class Column {
        private List<Cell> words = new ArrayList<>();
        private int maxLength = 0;
        private Table.Alignment alignment;

        public Column(Table.Alignment alignment){
            setAlignment(alignment);
        }
        
        public Table.Column addWord(String word){
            maxLength = Math.max(maxLength, word.length());
            words.add( new Cell(word) );
            return this;
        }
        
        public String getCell(int row){
            return words.get(row).padCell( maxLength );
        }
        
        public String[] getWords(){
            return words
                .stream()
                .map( cell -> cell.getWord() )
                .toArray(size -> new String[size])
            ;
        }
        
        public int size(){
            return words.size();
        }
        
        public String toString(){
            return words.toString();
        }
        
        public Column setAlignment(Table.Alignment alignment){
            this.alignment = alignment;
            words.forEach(cell -> cell.setAlignment(alignment));
            return this;
        }
        
        public Column setAlignment(Table.Alignment alignment, int cell){
            words.get(cell).setAlignment(alignment);
            return this;
        }
        
        class Cell {
            private String word;
            private Table.Alignment alignment;
            
            public Cell(String word){
                this.word = word;
            }
            
            public String getWord(){
                return word;
            }
            
            public void setAlignment(Table.Alignment alignment){
                this.alignment = alignment;
            }
            
            public String toString(){
                return padCell(maxLength);
            }

            private String padCell(int newLength){
                final String padding = "" + pad;
                int padCount = newLength - word.length();
                int leftCount = padCount / 2;
                int rightCount = padCount - leftCount;
                String left = padding.repeat(leftCount);
                String right = padding.repeat(rightCount);
                return alignment.align(left, word, right);
            }
        }
    }
}

Back on my 4th attempt I got many wonderful reviews that inspired changes that made it into my 5th attempt. One idea from the review by @Roman was to pivot (columns become rows). This is my attempt to answer that review.

In addition to adding toPivot() I also changed pad from a String to a char. Made copying it's value from the old table easier. Thoughts most welcome.

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.