package org.xpect.text; import java.util.Collections; import java.util.List; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; public class Table { public static class Cell extends Element2 { public static class Provider implements Provider2<Cell> { public Cell get(Table table, Object old, int rowIndex, int columnIndex) { if (old instanceof Cell) return (Cell) old; if (old == null) return new Cell(table, rowIndex, columnIndex); throw new ClassCastException(); } } private ITextBlock text = ITextBlock.EMPTY; protected Cell(Table table, int row, int col) { super(table, row, col); } public Column getColumn() { return table.getColumn(getColIndex()); } public int getHeight() { return text.getLines().size(); } public Row getRow() { return table.getRow(getRowIndex()); } public int getWidth() { int result = 0; for (String line : text.getLines()) { int length = line.length(); if (length > result) result = length; } if (table.maxCellWidth > 0 && result > table.maxCellWidth) result = table.maxCellWidth; return result; } protected void paint(ICanvas canvas) { canvas.print(text); } public Cell setText(Object text) { this.text = TextBlock.get(text); return this; } } public static class CellHSeparator extends Element2 { public static class Provider implements Provider2<CellHSeparator> { public CellHSeparator get(Table table, Object old, int rowIndex, int columnIndex) { if (old instanceof CellHSeparator) return (CellHSeparator) old; if (old == null) return new CellHSeparator(table, rowIndex, columnIndex); throw new ClassCastException(); } } private ITextBlock background = ITextBlock.EMPTY; protected CellHSeparator(Table table, int rowIndex, int colIndex) { super(table, rowIndex, colIndex); } public ITextBlock getBackground() { if (background != ITextBlock.EMPTY) return background; return getRowSeparator().getBackground(); } public RowSeparator getRowSeparator() { return table.idxRowSeparators.get(rowIndex); } public void paint(ICanvas canvas) { ITextBlock b = getBackground(); if (b != ITextBlock.EMPTY) canvas.fill(b); } public CellHSeparator setBackground(ITextBlock background) { this.background = background; return this; } } public static class CellVSeparator extends Element2 { public static class Provider implements Provider2<CellVSeparator> { public CellVSeparator get(Table table, Object old, int rowIndex, int columnIndex) { if (old instanceof CellVSeparator) return (CellVSeparator) old; if (old == null) return new CellVSeparator(table, rowIndex, columnIndex); throw new ClassCastException(); } } private ITextBlock background = ITextBlock.EMPTY; protected CellVSeparator(Table table, int rowIndex, int colIndex) { super(table, rowIndex, colIndex); } public ITextBlock getBackground() { if (background != ITextBlock.EMPTY) return background; return getColumnSeparator().getBackground(); } public ColumnSeparator getColumnSeparator() { return table.idxColumnSeparators.get(colIndex); } public void paint(ICanvas canvas) { ITextBlock b = getBackground(); if (b != ITextBlock.EMPTY) canvas.fill(b); } public CellVSeparator setBackground(ITextBlock background) { this.background = background; return this; } } public static class Column extends Element1 { public static class Provider implements Provider1<Column> { public Column get(Table table, Object old, int index) { if (old instanceof Column) return (Column) old; if (old == null) return new Column(table, index); throw new ClassCastException(); } } protected Column(Table table, int index) { super(table, index); } public Cell getCell(int rowIndex) { return table.getCell(rowIndex, index); } public ColumnSeparator getLeftSeparator() { return table.idxColumnSeparators.get(index); } public ColumnSeparator getRightSeparator() { return table.idxColumnSeparators.get(index + 1); } public int getWidth() { int result = 0; for (Cell cell : table.idxCells.getByColumn(index)) { int width = cell.getWidth(); if (width > result) result = width; } return result; } } public static class ColumnSeparator extends Element1 { public static class Provider implements Provider1<ColumnSeparator> { public ColumnSeparator get(Table table, Object old, int index) { if (old instanceof ColumnSeparator) return (ColumnSeparator) old; if (old == null) return new ColumnSeparator(table, index); throw new ClassCastException(); } } private ITextBlock background = ITextBlock.EMPTY; private int width = UNDEFINED; protected ColumnSeparator(Table table, int index) { super(table, index); } public ITextBlock getBackground() { if (background == ITextBlock.EMPTY) return table.columnSeparatorBackground; return background; } public int getWidth() { if (width != UNDEFINED) return width; if (isBorder()) return table.border; return table.columnSeparatorWidth; } public boolean isBorder() { return isLeftBorder() || isRightBorder(); } public boolean isLeftBorder() { return index == 0; } public boolean isRightBorder() { return index == table.getColumnCount(); } public ColumnSeparator setBackground(Object background) { this.background = TextBlock.get(background); return this; } public ColumnSeparator setWidth(int width) { this.width = width; return this; } } public abstract static class Element { protected final Table table; protected Element(Table table) { super(); this.table = table; } public Table getTable() { return table; } } public abstract static class Element1 extends Element { protected final int index; protected Element1(Table table, int index) { super(table); this.index = index; } public int getIndex() { return index; } } public abstract static class Element2 extends Element { protected final int colIndex; protected final int rowIndex; protected Element2(Table table, int rowIndex, int colIndex) { super(table); this.rowIndex = rowIndex; this.colIndex = colIndex; } public int getColIndex() { return colIndex; } public int getRowIndex() { return rowIndex; } } public class Indexed1<T> { private final Provider1<T> factory; private final List<T> list = Lists.newArrayList(); protected Indexed1(Provider1<T> factory) { super(); this.factory = factory; } public T get(int index) { return get(index, factory); } public <X extends T> X get(int index, Provider1<X> factory) { for (int i = list.size(); i < index; i++) list.add(null); if (list.size() == index) { X result = factory.get(Table.this, null, index); list.add(result); return result; } else { T old = list.get(index); X result = factory.get(Table.this, old, index); if (old != result) list.set(index, result); return result; } } } public class Indexed2<T> { private final List<List<T>> list = Lists.newArrayList(); private final Provider2<T> provider; protected Indexed2(Provider2<T> factory) { super(); this.provider = factory; } public T addToColumn(int colIndex) { return addToColumn(colIndex, provider); } public <X extends T> X addToColumn(int colIndex, Provider2<X> provider) { return get(getCountInColumn(colIndex), colIndex, provider); } public T addToRow(int rowIndex) { return addToRow(rowIndex, provider); } public <X extends T> X addToRow(int rowIndex, Provider2<X> provider) { Preconditions.checkArgument(rowIndex >= 0); for (int i = list.size(); i <= rowIndex; i++) list.add(Lists.<T> newArrayList()); List<T> row = list.get(rowIndex); X result = provider.get(Table.this, null, rowIndex, row.size()); row.add(result); return result; } public T get(int rowIndex, int colIndex) { return get(rowIndex, colIndex, provider); } public <X extends T> X get(int rowIndex, int colIndex, Provider2<X> provider) { Preconditions.checkArgument(colIndex >= 0); Preconditions.checkArgument(rowIndex >= 0); for (int i = list.size(); i <= rowIndex; i++) list.add(Lists.<T> newArrayList()); List<T> row = list.get(rowIndex); for (int i = row.size(); i < colIndex; i++) row.add(null); if (row.size() == colIndex) { X result = provider.get(Table.this, null, rowIndex, colIndex); row.add(result); return result; } else { T old = row.get(colIndex); X result = provider.get(Table.this, old, rowIndex, colIndex); if (old != result) row.set(colIndex, result); return result; } } public Iterable<T> getByColumn(final int columIndex) { Preconditions.checkArgument(columIndex >= 0); return Iterables.filter(Iterables.transform(list, new Function<List<T>, T>() { public T apply(List<T> input) { if (columIndex < input.size()) return input.get(columIndex); return null; } }), Predicates.notNull()); } public Iterable<T> getByRow(int rowIndex) { Preconditions.checkArgument(rowIndex >= 0); if (rowIndex >= list.size()) return Collections.emptyList(); return Iterables.filter(list.get(rowIndex), Predicates.notNull()); } public int getColumnCount() { int result = 0; for (List<T> row : list) { int size = row.size(); if (size > result) result = size; } return result; } public int getCountInColumn(int colIndex) { Preconditions.checkArgument(colIndex > 0); int lastNonemptyRow = list.size() - 1; while (lastNonemptyRow >= 0) { List<T> row = list.get(lastNonemptyRow); if (colIndex < row.size() && row.get(colIndex) == null) lastNonemptyRow--; else break; } return lastNonemptyRow + 1; } public int getRowCount() { return list.size(); } } public interface Provider1<T> { T get(Table table, Object old, int index); } public interface Provider2<T> { T get(Table table, Object old, int rowIndex, int columnIndex); } public static class Row extends Element1 { public static class Provider implements Provider1<Row> { public Row get(Table table, Object old, int index) { if (old instanceof Row) return (Row) old; if (old == null) return new Row(table, index); throw new ClassCastException(); } } protected Row(Table table, int index) { super(table, index); } public Cell addCell() { return table.idxCells.addToRow(index); } public RowSeparator getBottomSeparator() { return table.idxRowSeparators.get(index + 1); } public Cell getCell(int colIndex) { return table.idxCells.get(index, colIndex); } public int getHeight() { int result = 0; for (Cell cell : table.idxCells.getByRow(index)) { int height = cell.getHeight(); if (height > result) result = height; } return result; } public RowSeparator getTopSeparator() { return table.idxRowSeparators.get(index); } } public static class RowSeparator extends Element1 { public static class Provider implements Provider1<RowSeparator> { public RowSeparator get(Table table, Object old, int index) { if (old instanceof RowSeparator) return (RowSeparator) old; if (old == null) return new RowSeparator(table, index); throw new ClassCastException(); } } private ITextBlock background = ITextBlock.EMPTY; private int height = UNDEFINED; protected RowSeparator(Table table, int index) { super(table, index); } public ITextBlock getBackground() { if (background == ITextBlock.EMPTY) return table.rowSeparatorBackground; return background; } public int getHeight() { if (height != UNDEFINED) return height; if (isBorder()) return table.border; return table.rowSeparatorHeight; } public boolean isBorder() { return isTopBorder() || isBottomBorder(); } public boolean isBottomBorder() { return index == table.getRowCount(); } public boolean isTopBorder() { return index == 0; } public RowSeparator setBackground(Object background) { this.background = TextBlock.get(background); return this; } public RowSeparator setHeight(int height) { this.height = height; return this; } } public static class SeparatorCrossing extends Element2 { public static class Provider implements Provider2<SeparatorCrossing> { public SeparatorCrossing get(Table table, Object old, int rowIndex, int columnIndex) { if (old instanceof SeparatorCrossing) return (SeparatorCrossing) old; if (old == null) return new SeparatorCrossing(table, rowIndex, columnIndex); throw new ClassCastException(); } } private ITextBlock background = ITextBlock.EMPTY; protected SeparatorCrossing(Table table, int rowIndex, int colIndex) { super(table, rowIndex, colIndex); } public ITextBlock getBackground() { if (background != ITextBlock.EMPTY) return background; return table.separatorCrossingBackground; } public void paint(ICanvas canvas) { ITextBlock c = getBackground(); if (c != ITextBlock.EMPTY) canvas.fill(c); } public SeparatorCrossing setBackground(Object background) { this.background = TextBlock.get(background); return this; } } protected static final int UNDEFINED = -1; private int border = 0; private ITextBlock columnSeparatorBackground = ITextBlock.EMPTY; private int columnSeparatorWidth = 1; private final Indexed2<CellHSeparator> idxCellHSeparators = new Indexed2<Table.CellHSeparator>(new CellHSeparator.Provider()); private final Indexed2<Cell> idxCells = new Indexed2<Table.Cell>(new Cell.Provider()); private final Indexed2<CellVSeparator> idxCellVSeparators = new Indexed2<CellVSeparator>(new CellVSeparator.Provider()); private final Indexed1<Column> idxColumns = new Indexed1<Column>(new Column.Provider()); private final Indexed1<ColumnSeparator> idxColumnSeparators = new Indexed1<ColumnSeparator>(new ColumnSeparator.Provider()); private final Indexed1<Row> idxRows = new Indexed1<Table.Row>(new Row.Provider()); private final Indexed1<RowSeparator> idxRowSeparators = new Indexed1<RowSeparator>(new RowSeparator.Provider()); private final Indexed2<SeparatorCrossing> idxSeparatorCrossings = new Indexed2<SeparatorCrossing>(new SeparatorCrossing.Provider()); private int maxCellWidth = -1; private ITextBlock rowSeparatorBackground = ITextBlock.EMPTY; private int rowSeparatorHeight = 1; private ITextBlock separatorCrossingBackground = ITextBlock.EMPTY; public Table() { super(); } public Column addColumn() { return getColumn(getColumnCount()); } public Row addRow() { return getRow(getRowCount()); } public int getBorder() { return border; } public RowSeparator getBottomBorder() { return idxRowSeparators.get(getRowCount()); } public Cell getCell(int rowIndex, int colIndex) { return idxCells.get(rowIndex, colIndex); } public <T extends Cell> Cell getCell(int rowIndex, int colIndex, Provider2<T> provider) { return idxCells.get(rowIndex, colIndex, provider); } public Column getColumn(int index) { return idxColumns.get(index); } public <T extends Column> T getColumn(int index, Provider1<T> provider) { return idxColumns.get(index, provider); } public int getColumnCount() { return idxCells.getColumnCount(); } public ITextBlock getColumnSeparatorBackground() { return columnSeparatorBackground; } public int getColumnSeparatorWidth() { return columnSeparatorWidth; } public ColumnSeparator getLeftBorder() { return idxColumnSeparators.get(0); } public int getMaxCellWidth() { return maxCellWidth; } public ColumnSeparator getRightBorder() { return idxColumnSeparators.get(getColumnCount()); } public Row getRow(int index) { return idxRows.get(index); } public <T extends Row> T getRow(int index, Provider1<T> provider) { return idxRows.get(index, provider); } public int getRowCount() { return idxCells.getRowCount(); } public ITextBlock getRowSeparatorBackground() { return rowSeparatorBackground; } public int getRowSeparatorHeight() { return rowSeparatorHeight; } public ITextBlock getSeparatorCrossingBackground() { return separatorCrossingBackground; } public RowSeparator getTopBorder() { return idxRowSeparators.get(0); } public void paint(ICanvas canvas) { int cols = getColumnCount(); int rows = getRowCount(); List<Integer> columnWidths = Lists.newArrayList(); for (int col = 0; col < cols; col++) columnWidths.add(idxColumns.get(col).getWidth()); List<Integer> columnSeparatorWidths = Lists.newArrayList(); for (int col = 0; col <= cols; col++) columnSeparatorWidths.add(idxColumnSeparators.get(col).getWidth()); int top = 0; for (int row = 0; row <= rows; row++) { { int height = idxRowSeparators.get(row).getHeight(); if (height > 0) { int left = 0; for (int col = 0; col <= cols; col++) { { int width = columnSeparatorWidths.get(col); if (width > 0) { idxSeparatorCrossings.get(row, col).paint(canvas.at(top, left).withBounds(height, width).newSubCanvas()); left += width; } } if (col < cols) { int width = columnWidths.get(col); if (width > 0) { idxCellHSeparators.get(row, col).paint(canvas.at(top, left).withBounds(height, width).newSubCanvas()); left += width; } } } top += height; } } if (row < rows) { int height = idxRows.get(row).getHeight(); if (height > 0) { int left = 0; for (int col = 0; col <= cols; col++) { { int width = columnSeparatorWidths.get(col); if (width > 0) { idxCellVSeparators.get(row, col).paint(canvas.at(top, left).withBounds(height, width).newSubCanvas()); left += width; } } if (col < cols) { int width = columnWidths.get(col); if (width > 0) { idxCells.get(row, col).paint(canvas.at(top, left).withBounds(height, width).newSubCanvas()); left += width; } } } top += height; } } } } public Table setBorder(int border) { this.border = border; return this; } public Table setColumnSeparatorBackground(Object ColumnSeparatorBackground) { this.columnSeparatorBackground = TextBlock.get(ColumnSeparatorBackground); return this; } public Table setColumnSeparatorWidth(int columnSeparatorWidth) { this.columnSeparatorWidth = columnSeparatorWidth; return this; } public Table setMaxCellWidth(int maxCellWidth) { this.maxCellWidth = maxCellWidth; return this; } public Table setRowSeparatorBackground(Object RowSeparatorBackground) { this.rowSeparatorBackground = TextBlock.get(RowSeparatorBackground); return this; } public Table setRowSeparatorHeight(int rowSeparatorHeight) { this.rowSeparatorHeight = rowSeparatorHeight; return this; } public Table setSeparatorCrossingBackground(Object text) { this.separatorCrossingBackground = TextBlock.get(text); return this; } @Override public String toString() { ICanvas canvas = Canvas.create(); paint(canvas); return canvas.toString(); } }