introduce a new datastructure to represent the whole table grid

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/GridArea.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/GridArea.java
new file mode 100644
index 0000000..ef8e75e
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/GridArea.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.boxes;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author Florian Thienel
+ */
+public class GridArea {
+
+	public final int startRow;
+	public final int startColumn;
+	public final int endRow;
+	public final int endColumn;
+
+	public GridArea(final GridPosition position) {
+		this(position.row, position.column, position.row, position.column);
+	}
+
+	public GridArea(final GridPosition startPosition, final GridPosition endPosition) {
+		this(startPosition.row, startPosition.column, endPosition.row, endPosition.column);
+	}
+
+	public GridArea(final int row, final int column) {
+		this(row, column, row, column);
+	}
+
+	public GridArea(final int startRow, final int startColumn, final int endRow, final int endColumn) {
+		this.startRow = Math.min(startRow, endRow);
+		this.startColumn = Math.min(startColumn, endColumn);
+		this.endRow = Math.max(startRow, endRow);
+		this.endColumn = Math.max(startColumn, endColumn);
+	}
+
+	public boolean contains(final int row, final int column) {
+		return row >= startRow && row <= endRow && column >= startColumn && column <= endColumn;
+	}
+
+	public Collection<GridPosition> positions() {
+		final ArrayList<GridPosition> positions = new ArrayList<GridPosition>();
+		for (int row = startRow; row <= endRow; row += 1) {
+			for (int column = startColumn; column <= endColumn; column += 1) {
+				positions.add(new GridPosition(row, column));
+			}
+		}
+		return positions;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + endColumn;
+		result = prime * result + endRow;
+		result = prime * result + startColumn;
+		result = prime * result + startRow;
+		return result;
+	}
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (getClass() != obj.getClass()) {
+			return false;
+		}
+		final GridArea other = (GridArea) obj;
+		if (endColumn != other.endColumn) {
+			return false;
+		}
+		if (endRow != other.endRow) {
+			return false;
+		}
+		if (startColumn != other.startColumn) {
+			return false;
+		}
+		if (startRow != other.startRow) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		return "GridArea [startRow=" + startRow + ", startColumn=" + startColumn + ", endRow=" + endRow + ", endColumn=" + endColumn + "]";
+	}
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/GridPosition.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/GridPosition.java
new file mode 100644
index 0000000..b1e04e4
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/GridPosition.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.boxes;
+
+/**
+ * @author Florian Thienel
+ */
+public class GridPosition {
+
+	public final int row;
+	public final int column;
+
+	public GridPosition(final int row, final int column) {
+		this.row = row;
+		this.column = column;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + column;
+		result = prime * result + row;
+		return result;
+	}
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null) {
+			return false;
+		}
+		if (getClass() != obj.getClass()) {
+			return false;
+		}
+		final GridPosition other = (GridPosition) obj;
+		if (column != other.column) {
+			return false;
+		}
+		if (row != other.row) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public String toString() {
+		return "GridPosition [row=" + row + ", column=" + column + "]";
+	}
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/Table.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/Table.java
index 03f61d8..057f591 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/Table.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/Table.java
@@ -30,6 +30,7 @@
 	private final ArrayList<IStructuralBox> children = new ArrayList<IStructuralBox>();
 
 	private TableColumnLayout columnLayout = new TableColumnLayout();
+	private TableLayoutGrid layoutGrid = new TableLayoutGrid();
 
 	@Override
 	public void setParent(final IBox parent) {
@@ -152,6 +153,18 @@
 		return columnLayout;
 	}
 
+	public TableLayoutGrid getLayoutGrid() {
+		return layoutGrid;
+	}
+
+	public void setLayoutGrid(final TableLayoutGrid layoutGrid) {
+		if (layoutGrid == null) {
+			this.layoutGrid = new TableLayoutGrid();
+		} else {
+			this.layoutGrid = layoutGrid;
+		}
+	}
+
 	public void layout(final Graphics graphics) {
 		layoutColumns(graphics);
 		layoutChildren(graphics);
@@ -173,6 +186,7 @@
 			columnLayout = new TableColumnLayout(columnLayout.getParentLayout());
 		}
 		TableColumnLayout.addColumnLayoutInformationForChildren(graphics, this, columnLayout);
+		TableLayoutGrid.setupLayoutGrid(graphics, this, layoutGrid);
 	}
 
 	@Override
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableCell.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableCell.java
index cdfad2d..bc583ad 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableCell.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableCell.java
@@ -34,6 +34,9 @@
 	private String columnName;
 	private String startColumnName;
 	private String endColumnName;
+	private int verticalSpan = 1;
+
+	private GridArea gridArea;
 
 	private int naturalHeight;
 
@@ -194,6 +197,22 @@
 		this.endColumnName = endColumnName;
 	}
 
+	public int getVerticalSpan() {
+		return verticalSpan;
+	}
+
+	public void setVerticalSpan(final int verticalSpan) {
+		this.verticalSpan = verticalSpan;
+	}
+
+	public GridArea getGridArea() {
+		return gridArea;
+	}
+
+	public void setGridArea(final GridArea gridArea) {
+		this.gridArea = gridArea;
+	}
+
 	public int calculateNaturalHeight(final Graphics graphics, final int width) {
 		naturalHeight = 0;
 		for (int i = 0; i < children.size(); i += 1) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableLayoutGrid.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableLayoutGrid.java
new file mode 100644
index 0000000..ff07257
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableLayoutGrid.java
@@ -0,0 +1,273 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.boxes;
+
+import java.util.HashMap;
+
+import org.eclipse.vex.core.internal.core.Graphics;
+
+/**
+ * @author Florian Thienel
+ */
+public class TableLayoutGrid {
+
+	private final HashMap<GridPosition, TableCell> grid = new HashMap<GridPosition, TableCell>();
+	private int currentRow = 0;
+	private int nextColumn = 1;
+	private int maxColumn = 0;
+
+	public static void setupLayoutGrid(final Graphics graphics, final IStructuralBox parent, final TableLayoutGrid layoutGrid) {
+		// TODO merge with TableColumnLayout
+		parent.accept(new DepthFirstBoxTraversal<Object>() {
+			@Override
+			public Object visit(final Table box) {
+				if (box == parent) {
+					traverseChildren(box);
+				} else {
+					box.setLayoutGrid(layoutGrid);
+				}
+				return null;
+			}
+
+			@Override
+			public Object visit(final TableRowGroup box) {
+				if (box == parent) {
+					traverseChildren(box);
+				} else {
+					box.setLayoutGrid(layoutGrid);
+				}
+				return null;
+			}
+
+			@Override
+			public Object visit(final TableColumnSpec box) {
+				// TODO Auto-generated method stub
+				return super.visit(box);
+			}
+
+			@Override
+			public Object visit(final TableRow box) {
+				if (box == parent) {
+					layoutGrid.addNextRow();
+					traverseChildren(box);
+				} else {
+					box.setLayoutGrid(layoutGrid);
+				}
+				return null;
+			}
+
+			@Override
+			public Object visit(final TableCell box) {
+				if (box.getStartColumnIndex() > 0 && box.getEndColumnIndex() > 0) {
+					layoutGrid.addCellOnCurrentRow(box, box.getStartColumnIndex(), box.getEndColumnIndex());
+				} else {
+					layoutGrid.addNextCellOnCurrentRow(box);
+				}
+				return null;
+			}
+		});
+	}
+
+	public int getRows() {
+		return currentRow;
+	}
+
+	public int getColumns() {
+		return maxColumn;
+	}
+
+	public int addNextRow() {
+		currentRow += 1;
+		nextColumn = 1;
+		return currentRow;
+	}
+
+	public boolean addCellOnCurrentRow(final TableCell cell, final int column) {
+		final GridArea area = new GridArea(currentRow, column, currentRow + cell.getVerticalSpan() - 1, column);
+		if (isOccupied(area)) {
+			return false;
+		}
+		occupy(area, cell);
+		cell.setGridArea(area);
+		updateNextColumn();
+		return true;
+	}
+
+	public boolean addCellOnCurrentRow(final TableCell cell, final int startColumn, final int endColumn) {
+		final GridArea area = new GridArea(currentRow, startColumn, currentRow + cell.getVerticalSpan() - 1, endColumn);
+		if (isOccupied(area)) {
+			return false;
+		}
+		occupy(area, cell);
+		cell.setGridArea(area);
+		updateNextColumn();
+		return true;
+	}
+
+	public void addNextCellOnCurrentRow(final TableCell cell) {
+		final GridArea area = new GridArea(currentRow, nextColumn, currentRow + cell.getVerticalSpan() - 1, nextColumn);
+		occupy(area, cell);
+		cell.setGridArea(area);
+		updateNextColumn();
+	}
+
+	private void updateNextColumn() {
+		while (isOccupied(new GridPosition(currentRow, nextColumn))) {
+			nextColumn += 1;
+		}
+	}
+
+	private void occupy(final GridArea area, final TableCell cell) {
+		for (final GridPosition position : area.positions()) {
+			grid.put(position, cell);
+			maxColumn = Math.max(maxColumn, position.column);
+		}
+	}
+
+	private boolean isOccupied(final GridArea area) {
+		for (final GridPosition position : area.positions()) {
+			if (isOccupied(position)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private boolean isOccupied(final GridPosition position) {
+		return grid.containsKey(position);
+	}
+
+	public TableCell getCell(final GridPosition position) {
+		return grid.get(position);
+	}
+
+	public IStructuralBox getRowChild(final GridPosition position, final TableRow parentRow) {
+		final TableCell cell = grid.get(position);
+		if (cell == null) {
+			return null;
+		}
+
+		return cell.accept(new ParentTraversal<IStructuralBox>() {
+			@Override
+			public IStructuralBox visit(final HorizontalBar box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final List box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final ListItem box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final Paragraph box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final StructuralFrame box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final StructuralNodeReference box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final Table box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final TableCell box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final TableColumnSpec box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final TableRow box) {
+				return null;
+			}
+
+			@Override
+			public IStructuralBox visit(final TableRowGroup box) {
+				return visitStructuralBox(box);
+			}
+
+			@Override
+			public IStructuralBox visit(final VerticalBlock box) {
+				return visitStructuralBox(box);
+			}
+
+			private IStructuralBox visitStructuralBox(final IStructuralBox box) {
+				if (box.getParent() == parentRow) {
+					return box;
+				}
+				return box.getParent().accept(this);
+			}
+		});
+	}
+
+	@Override
+	public String toString() {
+		final StringBuilder builder = new StringBuilder();
+		for (int row = 1; row <= currentRow; row += 1) {
+			final StringBuilder top = new StringBuilder();
+			final StringBuilder middle = new StringBuilder();
+			final StringBuilder bottom = new StringBuilder();
+			for (int column = 1; column <= maxColumn; column += 1) {
+				final GridPosition position = new GridPosition(row, column);
+				final TableCell cell = grid.get(position);
+				if (cell == null) {
+					top.append("     ");
+					middle.append("     ");
+					bottom.append("     ");
+				} else {
+					final GridArea gridArea = cell.getGridArea();
+					if (row == gridArea.startRow) {
+						top.append("-----");
+					} else {
+						top.append("     ");
+					}
+					if (row == gridArea.endRow) {
+						bottom.append("-----");
+					} else {
+						bottom.append("     ");
+					}
+					if (column == gridArea.startColumn) {
+						middle.append("|");
+					} else {
+						middle.append(" ");
+					}
+					middle.append(Long.toString(cell.hashCode()).substring(0, 3));
+					if (column == gridArea.endColumn) {
+						middle.append("|");
+					} else {
+						middle.append(" ");
+					}
+				}
+			}
+			builder.append(top.toString()).append("\n");
+			builder.append(middle.toString()).append("\n");
+			builder.append(bottom.toString()).append("\n");
+		}
+		return builder.toString();
+	}
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableRow.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableRow.java
index b67c49b..03c7759 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableRow.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableRow.java
@@ -30,6 +30,7 @@
 	private final ArrayList<IStructuralBox> children = new ArrayList<IStructuralBox>();
 
 	private TableColumnLayout columnLayout = new TableColumnLayout();
+	private TableLayoutGrid layoutGrid = new TableLayoutGrid();
 
 	@Override
 	public void setParent(final IBox parent) {
@@ -152,8 +153,21 @@
 		}
 	}
 
+	public TableLayoutGrid getLayoutGrid() {
+		return layoutGrid;
+	}
+
+	public void setLayoutGrid(final TableLayoutGrid layoutGrid) {
+		if (layoutGrid == null) {
+			this.layoutGrid = new TableLayoutGrid();
+		} else {
+			this.layoutGrid = layoutGrid;
+		}
+	}
+
 	public void layout(final Graphics graphics) {
 		TableColumnLayout.addColumnLayoutInformationForChildren(graphics, this, columnLayout);
+		TableLayoutGrid.setupLayoutGrid(graphics, this, layoutGrid);
 		int cellHeight = 0;
 		int columnIndex = 1;
 		for (int i = 0; i < children.size(); i += 1) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableRowGroup.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableRowGroup.java
index cce9f7d..59cdf10 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableRowGroup.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/TableRowGroup.java
@@ -30,6 +30,7 @@
 	private final ArrayList<IStructuralBox> children = new ArrayList<IStructuralBox>();
 
 	private TableColumnLayout columnLayout = new TableColumnLayout();
+	private TableLayoutGrid layoutGrid = new TableLayoutGrid();
 
 	@Override
 	public void setParent(final IBox parent) {
@@ -148,6 +149,18 @@
 		this.columnLayout = columnLayout;
 	}
 
+	public TableLayoutGrid getLayoutGrid() {
+		return layoutGrid;
+	}
+
+	public void setLayoutGrid(final TableLayoutGrid layoutGrid) {
+		if (layoutGrid == null) {
+			this.layoutGrid = new TableLayoutGrid();
+		} else {
+			this.layoutGrid = layoutGrid;
+		}
+	}
+
 	public void layout(final Graphics graphics) {
 		layoutColumns(graphics);
 		layoutChildren(graphics);
@@ -158,6 +171,7 @@
 			columnLayout = new TableColumnLayout(columnLayout.getParentLayout());
 		}
 		TableColumnLayout.addColumnLayoutInformationForChildren(graphics, this, columnLayout);
+		TableLayoutGrid.setupLayoutGrid(graphics, this, layoutGrid);
 	}
 
 	private void layoutChildren(final Graphics graphics) {
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/visualization/CssBasedBoxModelBuilder.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/visualization/CssBasedBoxModelBuilder.java
index d9e835d..4c338fb 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/visualization/CssBasedBoxModelBuilder.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/visualization/CssBasedBoxModelBuilder.java
@@ -332,6 +332,9 @@
 				cell.setStartColumnName(nameStart.getValue());
 				cell.setEndColumnName(nameEnd.getValue());
 			}
+
+			final IAttribute moreRows = element.getAttribute("morerows");
+			cell.setVerticalSpan(1 + toInt(moreRows));
 		} else if ("th".equals(element.getLocalName()) || "td".equals(element.getLocalName())) {
 			// TODO HTML table
 		}