/* ** 2015 November 27 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. */ package info.ata4.disunity.cli.util; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; /** * * @author Nico Bergemann <barracuda415 at yahoo.de> */ public class TextTableFormat { private final Map<Integer, Integer> columnWidths = new HashMap<>(); private final Map<Integer, TextTableAlignment> columnAlignments = new HashMap<>(); private final Map<Integer, Function<Object, String>> columnFormatters = new HashMap<>(); private int numColumns; public void columnWidth(int column, int width) { columnWidths.put(column, width); } public int columnWidth(int column) { return columnWidths.get(column); } public void columnAlignment(int column, TextTableAlignment align) { columnAlignments.put(column, align); } public TextTableAlignment columnAlignment(int column) { return columnAlignments.get(column); } public void columnFormatter(int column, Function<Object, String> formatter) { columnFormatters.put(column, formatter); } public Function<Object, String> columnFormatter(int column) { return columnFormatters.get(column); } void configure(TableModel model) { numColumns = 0; model.table().columnKeySet().stream().forEach(columnKey -> { if (!columnFormatters.containsKey(columnKey)) { columnFormatters.put(columnKey, String::valueOf); } // set minimum column width if not already defined if (!columnWidths.containsKey(columnKey)) { columnWidths.put(columnKey, model.table() .column(columnKey) .values() .stream() .map(columnFormatters.get(columnKey)) .mapToInt(String::length) .max() .getAsInt() ); } if (!columnAlignments.containsKey(columnKey) || columnAlignments.get(columnKey) == TextTableAlignment.AUTO) { // count class types Map<Class, Long> columnTypeMap = model.table() .column(columnKey) .values() .stream() .skip(model.columnHeader() ? 1 : 0) // don't include type of column header .map(Object::getClass) .collect(Collectors.groupingBy(o -> o, Collectors.counting())); // get most occurring class Optional<Map.Entry<Class, Long>> topClassEntry = columnTypeMap .entrySet() .stream() .max((v1, v2) -> Long.compare(v1.getValue(), v2.getValue())); Class columnType = Object.class; if (topClassEntry.isPresent()) { columnType = topClassEntry.get().getKey(); } // align number columns to the right for better readability boolean isNumber = Number.class.isAssignableFrom(columnType); TextTableAlignment align; if (isNumber) { align = TextTableAlignment.RIGHT; } else { align = TextTableAlignment.LEFT; } columnAlignments.put(columnKey, align); } numColumns++; }); } int tableWidth(String cellSeparator) { return columnWidths.values().stream() .reduce(0, (a, b) -> a + b) + cellSeparator.length() * (columnWidths.size() - 1); } String formatCell(Object value, int column) { int width = columnWidths.get(column); TextTableAlignment align = columnAlignments.get(column); Function<Object, String> formatter = columnFormatters.get(column); String content = formatter.apply(value); if (content.length() > width) { // truncate content = StringUtils.abbreviate(content, width); } else if (content.length() < width) { // add padding switch (align) { case LEFT: content = StringUtils.rightPad(content, width); break; case RIGHT: content = StringUtils.leftPad(content, width); break; case CENTER: content = StringUtils.center(content, width); break; } } return content; } int numColumns() { return numColumns; } }