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

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.