/***************************************************************************
* Copyright (C) 2012 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* http://hstore.cs.brown.edu/ *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
package edu.brown.utils;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.commons.collections15.map.ListOrderedMap;
import org.apache.log4j.Logger;
public abstract class TableUtil {
private static final Logger LOG = Logger.getLogger(TableUtil.class);
private static Pattern LINE_SPLIT = Pattern.compile("\n");
public static final String CSV_DELIMITER = ",";
public static final String TABLE_DELIMITER = " ";
public static class Format {
public String delimiter_all;
public String delimiter_cols[];
public String delimiter_rows[];
public boolean spacing_col;
public boolean spacing_all;
public boolean right_align;
public boolean quote_rows;
public boolean quote_header;
public boolean trim_all;
public boolean capitalize_header;
public boolean prune_null_rows;
public Object replace_null_cells;
private Format() {
// Nothing
}
/**
* Create a new TableUtil format object
*
* @param delimiter_all
* String used in between all columns
* @param delimiter_cols
* TODO
* @param delimiter_rows
* TODO
* @param spacing_col
* Whether the cells in a single columns will all be the same
* width
* @param spacing_all
* Whether all the cells in the entire table will be the same
* width
* @param right_align
* Whether to right align each cell
* @param quote_rows
* Whether to wrap each non-header cell in quotes (including
* spacing)
* @param quote_header
* Whether to wrap each header cell in quotes (including
* spacing)
* @param trim_all
* Whether to invoke String.trim() on each cell
* @param capitalize_header
* TODO
* @param prune_null_rows
* If true, then any null row will be skipped
* @param replace_null_cells
* TODO
*/
public Format(String delimiter_all, String delimiter_cols[], String[] delimiter_rows, boolean spacing_col, boolean spacing_all, boolean right_align, boolean quote_rows, boolean quote_header,
boolean trim_all, boolean capitalize_header, boolean prune_null_rows, Object replace_null_cells) {
this.delimiter_all = delimiter_all;
this.delimiter_cols = delimiter_cols;
this.delimiter_rows = delimiter_rows;
this.spacing_col = spacing_col;
this.spacing_all = spacing_all;
this.right_align = right_align;
this.quote_rows = quote_rows;
this.quote_header = quote_header;
this.trim_all = trim_all;
this.capitalize_header = capitalize_header;
this.prune_null_rows = prune_null_rows;
this.replace_null_cells = replace_null_cells;
}
public Format clone() {
Format clone = new Format();
clone.delimiter_all = this.delimiter_all;
clone.delimiter_cols = this.delimiter_cols;
clone.delimiter_rows = this.delimiter_rows;
clone.spacing_col = this.spacing_col;
clone.spacing_all = this.spacing_all;
clone.right_align = this.right_align;
clone.quote_rows = this.quote_rows;
clone.quote_header = this.quote_header;
clone.trim_all = this.trim_all;
clone.capitalize_header = this.capitalize_header;
clone.prune_null_rows = this.prune_null_rows;
clone.replace_null_cells = this.replace_null_cells;
return (clone);
}
@Override
public String toString() {
Class<?> confClass = this.getClass();
StringBuilder sb = new StringBuilder();
for (Field f : confClass.getFields()) {
Object obj = null;
try {
obj = f.get(this);
if (ClassUtil.isArray(obj)) {
obj = Arrays.toString((Object[]) obj);
}
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
if (sb.length() > 0)
sb.append(", ");
sb.append(String.format("%s=%s", f.getName(), obj));
} // FOR
return sb.toString();
}
}
private static Format DEFAULT_TABLE = null;
private static Format DEFAULT_CSV = null;
public static Format defaultTableFormat() {
if (DEFAULT_TABLE == null) {
DEFAULT_TABLE = new Format(TABLE_DELIMITER, null, null, true, false, false, false, false, false, false, false, null);
}
return (DEFAULT_TABLE);
}
public static Format defaultCSVFormat() {
if (DEFAULT_CSV == null) {
DEFAULT_CSV = new Format(CSV_DELIMITER, null, null, false, false, false, false, false, false, false, true, null);
}
return (DEFAULT_CSV);
}
/**
* Format the header + rows into a CSV
*
* @param header
* @param rows
* @return
*/
public static String csv(String header[], Object[]... rows) {
return (table(defaultCSVFormat(), header, rows));
}
/**
* Format the header + rows into a neat little table
*
* @param header
* @param rows
* @return
*/
public static String table(String header[], Object[]... rows) {
return (table(defaultTableFormat(), header, rows));
}
/**
* Format the rows into a neat little table
*
* @param rows
* @return
*/
public static String table(Object[]... rows) {
return (table(defaultTableFormat(), null, rows));
}
/**
* @param header
* @param rows
* @return
*/
public static Map<String, String> tableMap(String header[], Object[]... rows) {
return (tableMap(defaultTableFormat(), header, rows));
}
/**
* @param spacing_col
* @param spacing_all
* TODO
* @param right_align
* TODO
* @param header
* @param rows
* @return
*/
public static Map<String, String> tableMap(Format format, String header[], Object[]... rows) {
int num_rows = rows.length;
if (format.prune_null_rows) {
for (int i = 0; i < num_rows; i++) {
if (rows[i] == null)
num_rows--;
} // FOR
}
Object key_cols[] = new String[num_rows + 1];
String new_header[] = new String[header.length - 1];
for (int i = 0; i < header.length; i++) {
if (i == 0) {
key_cols[0] = header[i];
} else {
new_header[i - 1] = header[i];
}
} // FOR
Object new_rows[][] = new String[num_rows][];
int new_row_idx = 0;
for (int i = 0; i < rows.length; i++) {
Object orig_row[] = rows[i];
if (orig_row == null && format.prune_null_rows)
continue;
assert (orig_row != null) : "Null row at " + i;
key_cols[new_row_idx + 1] = orig_row[0];
new_rows[new_row_idx] = new String[orig_row.length - 1];
for (int j = 0; j < new_rows[i].length; j++) {
new_rows[new_row_idx][j] = (orig_row[j + 1] != null ? orig_row[j + 1].toString() : null);
} // FOR
new_row_idx++;
} // FOR
Map<String, String> m = new ListOrderedMap<String, String>();
String lines[] = LINE_SPLIT.split(table(format, new_header, new_rows));
int row_idx = 0;
Integer key_width = null;
boolean last_row_was_delimiter = false;
for (int line_idx = 0; line_idx < lines.length; line_idx++) {
Object key = null;
// ROW DELIMITER
if (row_idx > 0 && last_row_was_delimiter == false && format.delimiter_rows != null && format.delimiter_rows[row_idx - 1] != null) {
if (key_width == null) {
key_width = 0;
for (Object k : key_cols) {
if (k != null)
key_width = Math.max(key_width, k.toString().length());
} // FOR
}
// key = StringUtil.repeat(format.delimiter_rows[row_idx-1],
// key_width);
key = StringUtil.repeat(" ", row_idx);
last_row_was_delimiter = true;
// FIRST COLUMN
} else {
key = key_cols[row_idx++];
last_row_was_delimiter = false;
}
m.put((key != null ? key.toString() : null), lines[line_idx]);
} // FOR
return (m);
}
/**
* Format the header+rows into a table
*
* @param delimiter_all
* the character to use in between cells
* @param spacing_col
* whether to make the width of each column the size of the
* largest cell
* @param spacing_all
* TODO
* @param right_align
* TODO
* @param quote_rows
* whether to surround each cell in quotation marks
* @param header
* @param rows
* @return
*/
public static String table(final Format format, final String header[], final Object[]... rows) {
final boolean debug = LOG.isDebugEnabled();
String replace_null_str = (format.replace_null_cells != null ? format.replace_null_cells.toString() : null);
if (debug) {
LOG.debug("format.replace_null_cells = " + format.replace_null_cells);
LOG.debug("replace_null_str = " + replace_null_str);
}
// First we need to figure out the size for each column
int num_cols = 0;
if (header != null) {
num_cols = header.length;
} else {
for (int row_idx = 0; row_idx < rows.length; row_idx++) {
if (rows[row_idx] != null)
num_cols = Math.max(num_cols, rows[row_idx].length);
} // FOR
}
// Internal data structures about the table
String col_formats[] = new String[num_cols];
String header_formats[] = new String[num_cols];
Integer max_width = 0;
int total_width = 0;
Integer widths[] = new Integer[num_cols];
String row_strs[][] = new String[rows.length][num_cols];
for (int col_idx = 0; col_idx < num_cols; col_idx++) {
Integer width = (header != null && header[col_idx] != null ? header[col_idx].length() : 0);
for (int row_idx = 0; row_idx < rows.length; row_idx++) {
if (rows[row_idx] == null)
continue;
if (col_idx == 0)
row_strs[row_idx] = new String[num_cols];
String val = replace_null_str;
if (col_idx < rows[row_idx].length) {
val = (rows[row_idx][col_idx] != null ? rows[row_idx][col_idx].toString() : replace_null_str);
if (val != null) {
if (format.trim_all)
val = val.trim();
width = Math.max(width, val.length());
}
}
row_strs[row_idx][col_idx] = val;
if (debug)
LOG.debug(String.format("[%d, %d] = %s", row_idx, col_idx, row_strs[row_idx][col_idx]));
} // FOR
widths[col_idx] = width;
total_width += width;
if (format.spacing_all)
max_width = Math.max(width, max_width);
} // FOR
for (int col_idx = 0; col_idx < col_formats.length; col_idx++) {
// FORMAT
Integer width = (format.spacing_all ? max_width : (format.spacing_col ? widths[col_idx] : null));
final String f = "%" + (width != null ? (format.right_align ? "" : "-") + width : "") + "s";
// DELIMITER
String d = "";
if (format.delimiter_cols != null && format.delimiter_cols[col_idx] != null) {
d = format.delimiter_cols[col_idx];
} else if (col_idx > 0) {
d = format.delimiter_all;
}
total_width += d.length();
// COLUMN
String col_f = f;
if (format.quote_rows)
col_f = '"' + col_f + '"';
col_formats[col_idx] = d + col_f;
// HEADER
if (header != null) {
String header_f = f;
if (format.quote_header)
header_f = '"' + header_f + '"';
header_formats[col_idx] = d + header_f;
}
} // FOR
// Create header row
StringBuilder sb = new StringBuilder();
if (header != null) {
for (int col_idx = 0; col_idx < header_formats.length; col_idx++) {
String cell = header[col_idx];
if (format.capitalize_header && cell != null)
cell = cell.toUpperCase();
sb.append(String.format(header_formats[col_idx], cell));
} // FOR
}
// Now dump out the table
for (int row_idx = 0; row_idx < rows.length; row_idx++) {
Object row[] = rows[row_idx];
if (format.prune_null_rows && row == null)
continue;
if (row_idx > 0 || (row_idx == 0 && header != null))
sb.append("\n");
// Add row delimiter if necessary
if (format.delimiter_rows != null && row_idx < format.delimiter_rows.length && format.delimiter_rows[row_idx] != null) {
sb.append(StringUtil.repeat(format.delimiter_rows[row_idx], total_width)).append("\n");
}
for (int col_idx = 0; col_idx < col_formats.length; col_idx++) {
String cell = row_strs[row_idx][col_idx];
if (format.quote_rows && (row_idx > 0 || format.quote_header))
cell = '"' + cell + '"';
sb.append(String.format(col_formats[col_idx], cell));
} // FOR
} // FOR
return (sb.toString());
}
}