code: package edu.berkeley.nlp.ui;
  
  import java.text.DecimalFormat;
  import java.util.HashMap;
  import java.util.Map;
  
  import edu.berkeley.nlp.util.Counter;
  import edu.berkeley.nlp.util.Pair;
  import edu.berkeley.nlp.util.StrUtils;
  
  public class Table {
  	// row -> column -> entry
  	private final Map<Pair<Integer, Integer>, String[]> entries = new HashMap<Pair<Integer, Integer>, String[]>();
  	private final Counter<Integer> maxHeights = new Counter<Integer>(),
  			maxWidths = new Counter<Integer>();
  	private int nColumns, nRows;
  
  	public Table(Populator populator) {
  		populator.setTable(this);
  		populator.populate();
  	}
  
  	public static abstract class Populator {
  		private void setTable(Table table) {
  			this.table = table;
  		}
  
  		private Table table;
  		private DecimalFormat fmt = EasyFormat.getStdFormat();
  
  		public Populator() {
  		}
  
  		public Populator(DecimalFormat fmt) {
  			this.fmt = fmt;
  		}
  
  		public abstract void populate();
  
  		public final void set(int row, int column, double number) {
  			set(row, column, fmt.format(number));
  		}
  
  		public final void set(int row, int column, String text) {
  			table.nColumns = Math.max(table.nColumns, column + 1);
  			table.nRows = Math.max(table.nRows, row + 1);
  			String[] lines = text.split("\\n");
  			table.maxHeights.setCount(row,
  					Math.max(table.maxHeights.getCount(row), lines.length));
  			for (String line : lines)
  				table.maxWidths.setCount(
  						column,
  						Math.max(table.maxWidths.getCount(column),
  								line.length()));
  			table.entries.put(new Pair<Integer, Integer>(row, column), lines);
  		}
  
  		/** append at the end of the last line */
  		public final void append(int row, int column, String text) {
  			String[] entries = table.entries.get(new Pair<Integer, Integer>(
  					row, column));
  			String lastEntry = (entries != null ? entries[entries.length - 1]
  					: null);
  			set(row, column, (lastEntry != null ? lastEntry : "") + text);
  		}
  
  		public final void addLines(int row, int column, String text) {
  			Pair<Integer, Integer> key = new Pair<Integer, Integer>(row, column);
  			String currentString = StrUtils.join(table.entries.get(key), "\n");
  			if (!currentString.equals(""))
  				currentString = currentString + "\n";
  			currentString += text;
  			set(row, column, currentString);
  		}
  	}
  
  	private boolean borderDefault = true;
  
  	public void setBorder(boolean value) {
  		this.borderDefault = value;
  	}
  
  	@Override
  	public String toString() {
  		return toString(borderDefault);
  	}
  
  	public String toHTML() {
  		return toHTML(borderDefault);
  	}
  
  	public String toHTML(boolean printBorders) {
  		StringBuilder builder = new StringBuilder();
  		builder.append("<table class=\""
  				+ (printBorders ? "with-borders" : "without-borders") + "\">");
  		for (int row = 0; row < nRows; row++) {
  			builder.append("<tr>");
  			for (int col = 0; col < nColumns; col++) {
  				builder.append("<td>");
  				Pair<Integer, Integer> key = new Pair<Integer, Integer>(row,
  						col);
  				String[] entry = entries.get(key);
  				if (entry != null)
  					for (int i = 0; i < entry.length; i++)
  						builder.append(entry[i]
  								+ (i < entry.length - 1 ? "<br/>" : "") + "");
  				builder.append("</td>");
  			}
  			builder.append("</tr>");
  		}
  		builder.append("</table>\n");
  		return builder.toString();
  	}
  
  	public static final String css = "table.with-borders {"
  			+ "border-width: 1px 1px 1px 1px;" + "border-spacing: 2px;"
  			+ "border-style: solid solid solid solid;"
  			+ "border-color: black black black black;"
  			+ "border-collapse: collapse;" + "background-color: white;" + "}"
  			+ "table.with-borders td {" + "border-width: 1px 1px 1px 1px;"
  			+ "padding: 1px 1px 1px 1px;"
  			+ "border-style: dotted dotted dotted dotted;"
  			+ "border-color: gray gray gray gray;" + "background-color: white;"
  			+ "-moz-border-radius: 0px 0px 0px 0px;" + "}"
  			+ "table.without-borders {" + "border-width: 1px 1px 1px 1px;"
  			+ "border-spacing: 2px;" + "border-style: none none none none;"
  			+ "border-collapse: collapse;" + "background-color: white;" + "}"
  			+ "table.without-borders td {" + "border-width: 1px 1px 1px 1px;"
  			+ "padding: 1px 1px 1px 1px;"
  			+ "border-style: none none none none;" + "background-color: white;"
  			+ "-moz-border-radius: 0px 0px 0px 0px;" + "}";
  
  	public String toString(boolean printBorders) {
  		StringBuilder builder = new StringBuilder();
  		// top border
  		if (printBorders)
  			builder.append(horizontalSeparator() + "\n");
  		for (int row = 0; row < nRows; row++) {
  			for (int rowLine = 0; rowLine < maxHeights.getCount(row); rowLine++) {
  				// left border
  				if (printBorders)
  					builder.append("|");
  				for (int col = 0; col < nColumns; col++) {
  					Pair<Integer, Integer> key = new Pair<Integer, Integer>(
  							row, col);
  					String text = (entries.get(key) == null
  							|| rowLine >= entries.get(key).length ? ""
  							: entries.get(key)[rowLine]);
  					builder.append(pad(text, (int) maxWidths.getCount(col), " "));
  					if (printBorders)
  						builder.append("|");
  				}
  				if (rowLine != maxHeights.getCount(row) - 1)
  					builder.append("\n");
  			}
  			if (printBorders)
  				builder.append("\n" + horizontalSeparator());
  			builder.append("\n");
  		}
  		return builder.toString();
  	}
  
  	private StringBuilder horizontalSeparator() {
  		StringBuilder builder = new StringBuilder();
  		builder.append("+");
  		for (int col = 0; col < nColumns; col++)
  			builder.append(pad("", (int) maxWidths.getCount(col), "-") + "+");
  		return builder;
  	}
  
  	private String pad(String s, int finalLength, String pad) {
  		if (s.length() > finalLength)
  			throw new RuntimeException();
  		StringBuilder b = new StringBuilder();
  		b.append(s);
  		while (b.length() < finalLength)
  			b.append(pad);
  		return b.toString();
  	}
  
  	public static void main(String[] args) {
  		Table table = new Table(new Populator() {
  			@Override
  			public void populate() {
  				for (int i = 0; i < 10; i++)
  					for (int j = 0; j < 10; j++)
  						if (i <= j)
  							addLines(i, j, "Sum:" + (i + j));
  				for (int i = 0; i < 10; i++)
  					for (int j = 0; j < 10; j++)
  						if (i <= j)
  							addLines(i, j, "Prod:" + (i * j));
  			}
  		});
  		System.out.println(table.toString());
  		// System.out.println(table.toHTML(false));
  	}
  }
  