/**
* Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright ownership. Apereo
* licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the License at the
* following location:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apereo.portal.utils;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.LinkedList;
import java.util.List;
import org.jvnet.jaxb2_commons.lang.Validate;
/**
*/
public class TableFormatter {
public static final class TableEntry<T> {
private final T value;
private final String flags;
private final String conversion;
public TableEntry(T value, String conversion) {
this(value, "", conversion);
}
public TableEntry(T value, String flags, String conversion) {
Validate.notNull(flags);
Validate.notNull(conversion);
this.value = value;
this.flags = flags;
this.conversion = conversion;
}
protected String getFormatString() {
return this.getFormatString(1);
}
protected String getFormatString(int padding) {
return "%" + this.flags + padding + this.conversion;
}
/** @return the value */
public T getValue() {
return value;
}
/** @return the flags */
public String getFlags() {
return flags;
}
/** @return the conversion */
public String getConversion() {
return conversion;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "TableEntry [flags="
+ flags
+ ", conversion="
+ conversion
+ ", value="
+ value
+ "]";
}
}
private final StringBuilder scratchBuilder;
private final Formatter scratchFormatter;
private final List<Integer> columnWidths;
private final List<TableEntry<?>> headerRow;
private final List<List<TableEntry<?>>> rows = new LinkedList<List<TableEntry<?>>>();
public TableFormatter(TableEntry<?> heading, TableEntry<?>... headings) {
this(null, heading, headings);
}
public TableFormatter(Formatter f, TableEntry<?> firstHeading, TableEntry<?>... headings) {
Validate.notNull(firstHeading);
//Setup scratch objects used for formatting data
this.scratchBuilder = new StringBuilder();
this.scratchFormatter = new Formatter(scratchBuilder, f != null ? f.locale() : null);
this.columnWidths = new ArrayList<Integer>(1 + headings.length);
this.headerRow = new ArrayList<TableEntry<?>>(1 + headings.length);
this.updateColumnSize(0, firstHeading);
this.headerRow.add(firstHeading);
for (final TableEntry<?> heading : headings) {
this.updateColumnSize(this.headerRow.size(), heading);
this.headerRow.add(heading);
}
}
public void addRow(TableEntry<?> firstValue, TableEntry<?>... values) {
Validate.notNull(firstValue);
if (1 + values.length != this.columnWidths.size()) {
throw new IllegalArgumentException(
"Inconsistent column count. Expected "
+ this.columnWidths.size()
+ " but was "
+ (1 + values.length));
}
final List<TableEntry<?>> row = new ArrayList<TableEntry<?>>(this.columnWidths.size());
updateColumnSize(row.size(), firstValue);
row.add(firstValue);
for (final TableEntry<?> value : values) {
updateColumnSize(row.size(), value);
row.add(value);
}
this.rows.add(row);
}
private void updateColumnSize(int index, TableEntry<?> value) {
final int valueLength = estimateEntryLength(value);
if (this.columnWidths.size() == index) {
this.columnWidths.add(valueLength);
} else {
this.columnWidths.set(index, Math.max(this.columnWidths.get(index), valueLength));
}
}
public void format(Formatter formatter) {
//Write out table header
this.formatRow(formatter, this.headerRow);
//Write out separator row
this.clearScratchBuilder();
this.scratchBuilder.append('-');
for (int column = 0; column < this.columnWidths.size(); column++) {
if (column > 0) {
this.scratchBuilder.append("-+-");
}
final int width = this.columnWidths.get(column);
for (int i = 0; i < width; i++) {
this.scratchBuilder.append('-');
}
}
this.scratchBuilder.append("-%n");
formatter.format(this.scratchBuilder.toString());
//Write out rows
for (final List<TableEntry<?>> row : this.rows) {
this.formatRow(formatter, row);
}
}
private void formatRow(Formatter formatter, final List<TableEntry<?>> row) {
this.clearScratchBuilder();
this.scratchBuilder.append(' ');
final Object[] formatArgs = new Object[this.columnWidths.size()];
for (int column = 0; column < this.columnWidths.size(); column++) {
if (column > 0) {
this.scratchBuilder.append(" | ");
}
final int width = this.columnWidths.get(column);
final TableEntry<?> header = row.get(column);
formatArgs[column] = header.getValue();
this.scratchBuilder.append(header.getFormatString(width));
}
this.scratchBuilder.append(" %n");
formatter.format(this.scratchBuilder.toString(), formatArgs);
}
private <T> int estimateEntryLength(TableEntry<T> entry) {
clearScratchBuilder();
this.scratchFormatter.format(entry.getFormatString(), entry.getValue());
return this.scratchBuilder.length();
}
private void clearScratchBuilder() {
if (this.scratchBuilder.length() > 0) {
this.scratchBuilder.delete(0, this.scratchBuilder.length());
}
}
}