If you'd like to print this:
One Two Three Four
1 2 3 4
using this:
new Columns()
.addLine("One", "Two", "Three", "Four")
.addLine("1", "2", "3", "4")
// .-={ Optional configuration examples }=-. //
.separateColumnsWith(" ")
.padWith(" ")
.padWith(' ')
.outputWith(System.out)
.alignRight()
.alignLeft(0)
.alignRight(1)
.alignCenter(2)
// '-={ Optional configuration examples }=-' //
.print()
;
all you need is this:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Collections;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.io.PrintStream;
public class Columns {
private final List<Column> columns = new ArrayList<>();
// Defaults
private String columnSeparator = " ";
private String pad = " ";
private PrintStream out = System.out;
private Function<String, Function<String, UnaryOperator<String>>> align;
{
alignLeft(); // Set default alignment
}
public Columns 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 Columns.Column(align) );
}
}
IntStream
.range(0, columns.size())
.forEach( col -> columns.get(col).addWord(line[col]) )
;
return this;
}
public Columns print(){
out.println( toString() );
return this;
}
public String toString(){
final StringBuilder result = new StringBuilder();
final int rows = columns.get(0).size();
IntStream
.range(0, rows)
.forEach(
row -> {
result
.append(
columns
.stream()
.map(col -> col.getCell(row))
.collect( Collectors.joining(columnSeparator) )
)
;
result.append( System.lineSeparator() );
}
)
;
return result.toString();
}
// .-={ Make defaults overridable }=-. //
public Columns outputWith(PrintStream out){
this.out = out;
return this;
}
public Columns separateColumnsWith(String columnSeparator){
this.columnSeparator = columnSeparator;
return this;
}
public Columns padWith(String pad){
if (pad.length() != 1) {
throw new IllegalArgumentException("Expected single character");
}
this.pad = pad;
return this;
}
public Columns padWith(char pad){
this.pad = "" + pad;
return this;
}
// '-={ Make defaults overridable }=-' //
public Columns alignLeft(){
align = getAlignLeft();
columns.forEach(col -> col.setAlign(align));
return this;
}
public Columns alignCenter(){
align = getAlignCenter();
columns.forEach(col -> col.setAlign(align));
return this;
}
public Columns alignRight(){
align = getAlignRight();
columns.forEach(col -> col.setAlign(align));
return this;
}
public Columns alignLeft(int col){
columns.get(col).setAlign( getAlignLeft() );
return this;
}
public Columns alignCenter(int col){
columns.get(col).setAlign( getAlignCenter() );
return this;
}
public Columns alignRight(int col){
columns.get(col).setAlign( getAlignRight() );
return this;
}
private Function<String, Function<String, UnaryOperator<String>>> getAlignLeft(){
return left -> word -> right -> word + left + right;
}
private Function<String, Function<String, UnaryOperator<String>>> getAlignCenter(){
return left -> word -> right -> left + word + right;
}
private Function<String, Function<String, UnaryOperator<String>>> getAlignRight(){
return left -> word -> right -> left + right + word;
}
class Column {
private List<String> words = new ArrayList<>();
private int maxLength = 0;
private Function<String, Function<String, UnaryOperator<String>>> align;
public Column(Function<String, Function<String, UnaryOperator<String>>> align){
this.align = align;
}
public Columns.Column addWord(String word){
maxLength = Math.max(maxLength, word.length());
words.add(word);
return this;
}
public String getCell(int row){
return padCell( words.get(row), maxLength );
}
private String padCell(String word, int newLength){
int padCount = newLength - word.length();
int leftCount = padCount / 2;
int rightCount = padCount - leftCount;
String left = pad.repeat(leftCount);
String right = pad.repeat(rightCount);
return align.apply(left).apply(word).apply(right);
}
public int size(){
return words.size();
}
public String toString(){
return words.toString();
}
public Column setAlign(Function<String, Function<String, UnaryOperator<String>>> align){
this.align = align;
return this;
}
}
}
By waiting until all the lines have been added before printing it can figure out the width each column needs.
Looking for a code review to reveal problems. Anything from better names to fewer bugs to better ideas.
Back on my second attempt @torbenputkonen gave me the idea of using an inner class for each column. As predicted it cleaned up the code and gave the flexibility to manage alignment on each column.
The reviews here have inspired a 5th attempt.