/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* This is the central class for handling data for HTML tables. Effectively, instance of
* this class are Java object representations of the HTML table structure, and the goal
* is that instances of this class hold all the data that is required for the expected
* HTML table visual layout once the table instance is merged with a Velocity template.
*
* A table has a logical row and column numbering that starts at row <code>row0</code> and column
* <code>col0</code> in the upper left corner. Indices run from <code>row0</code>
* to <code>rowNumber - 1</code> and from
* <code>col0</code> to <code>colNumber - 1</code>, respectively.
*/
public class Table {
private static Cell defaultCell = new Cell("", 1, 1);
private Cell[][] cells = null;
private int rowNumber = 0;
private int row0 = 0;
private int rowEnd = 0;
private int colNumber = 0;
private int col0 = 0;
private int colEnd = 0;
private boolean[][] visible = null;
private boolean[][] def = null; // Mark whether a cell contains the default cell
private Map<BoundaryLocation, BoundaryCondition> boundaryConditions =
new HashMap<BoundaryLocation, BoundaryCondition>();
/**
* Constructor for a table where the logical indexes for rows and columns start at 0
*
* @param rowNumber Number of rows for the table
* @param colNumber Number of columns for the table
*/
public Table(int rowNumber, int colNumber) {
this(0, 0, rowNumber, colNumber);
}
/**
* Constructor for a table
*
* @param row0 First logical index at upper edge of the table
* @param col0 First logical index at left edge of the table
* @param rowNumber Number of rows for the table
* @param colNumber Number of columns for the table
*/
public Table(int row0, int col0, int rowNumber, int colNumber) {
if (rowNumber < 1) {
throw new IllegalArgumentException("rowNumber must be larger than 0");
}
if (colNumber < 1) {
throw new IllegalArgumentException("colNumber must be larger than 0");
}
this.rowNumber = rowNumber;
this.colNumber = colNumber;
this.row0 = row0;
this.col0 = col0;
rowEnd = row0 + rowNumber - 1; // Helper
colEnd = col0 + colNumber - 1;
cells = new Cell[rowNumber][colNumber];
visible = new boolean[rowNumber][colNumber];
def = new boolean[rowNumber][colNumber];
for (int r = 0; r < rowNumber; r++) {
for (int c = 0; c < colNumber; c++) {
visible[r][c] = true;
def[r][c] = true;
cells[r][c] = defaultCell;
}
}
//.... The default boundary conditions
boundaryConditions.put(ColumnLocation.LEFT, BoundaryCondition.FIXED);
boundaryConditions.put(ColumnLocation.RIGHT, BoundaryCondition.FIXED);
boundaryConditions.put(RowLocation.TOP, BoundaryCondition.FIXED);
boundaryConditions.put(RowLocation.BOTTOM, BoundaryCondition.FIXED);
}
/**
* Coalesce cells containing the default cell into one common cell. This is useful to simplify the HTML table
* structure e. g. after all relevant data has been added to a table. Coalescing can either be along rows
* or along columns. For example, when coalescing along rows, each row of the table will be checked for
* consecutive blocks of cells containing the default cell. These blocks will be replaced by one cell covering
* them all.
* <p>
* This method creates new cell instances using the <code>name</code> and
* <code>types</code> arguments provided which can then be used in the renderer
* to react accordingly.
*
* @param internalLocation The location along which to coalesce. Can either be along rows or along columns
* @param name The name to assign for the cell(s) created
* @param types The types to assign to the cell(s) created
*
* @return <code>true</code> if cells were coalesced
*/
public boolean coalese(InternalLocation internalLocation, String name, String... types) {
if (name == null)
throw new IllegalArgumentException("name may not be null");
if (types == null)
throw new IllegalArgumentException("types may not be null");
if (internalLocation == null)
throw new IllegalArgumentException("internalLocation may not be null");
Cell cell = null;
boolean coalesced = false;
switch (internalLocation) {
case ROW:
for (int r = 0; r < rowNumber; r++) {
int cstart = 0;
int c = 0;
boolean scanning = false;
while (c < colNumber) {
if (isDefaultCell(r + row0, c + col0)) {
if (!scanning) {
cstart = c;
scanning = true;
}
} else if (scanning) {
cell = new Cell(name, 1, c - cstart);
for (String type : types)
cell.setType(type);
setCell(cell, r + row0, cstart + col0);
scanning = false;
coalesced = true;
}
c++;
}
//.... Final column
if (scanning) {
cell = new Cell(name, 1, c - cstart);
for (String type : types)
cell.setType(type);
setCell(cell, r + row0, cstart + col0);
coalesced = true;
}
}
break;
case COLUMN:
for (int c = 0; c < colNumber; c++) {
int rstart = 0;
int r = 0;
boolean scanning = false;
while (r < rowNumber) {
if (isDefaultCell(r + row0, c + col0)) {
if (!scanning) {
rstart = r;
scanning = true;
}
} else if (scanning) {
cell = new Cell(name, r - rstart, 1);
for (String type : types)
cell.setType(type);
setCell(cell, rstart + row0, c + col0);
scanning = false;
coalesced = true;
}
r++;
}
//.... Final column
if (scanning) {
cell = new Cell(name, r - rstart, 1);
for (String type : types)
cell.setType(type);
setCell(cell, rstart + row0, c + col0);
coalesced = true;
}
}
}
return coalesced;
}
/**
* Create a shallow copy of the current instance. The clone is identical
* to the original cell in terms of dimensions, logical indices,
* cell visibility, default cells, boundary conditions and cells
* as such, but the cell references in the clone are the same as in the original
* table.
*
* @return A cloned table instance
*/
public Table clone() {
Table clone = new Table(row0, col0, rowNumber, colNumber);
clone.setBoundaryCondition(ColumnLocation.LEFT, getBoundaryCondition(ColumnLocation.LEFT));
clone.setBoundaryCondition(ColumnLocation.RIGHT, getBoundaryCondition(ColumnLocation.RIGHT));
clone.setBoundaryCondition(RowLocation.BOTTOM, getBoundaryCondition(RowLocation.BOTTOM));
clone.setBoundaryCondition(RowLocation.TOP, getBoundaryCondition(RowLocation.TOP));
for (int r = 0; r < rowNumber; r++) {
for (int c = 0; c < colNumber; c++) {
int row = r + row0;
int col = c + col0;
clone.setVisible(r, c, visible[r][c]);
clone.setDefault(r, c, def[r][c]);
clone.setCell(r, c, cells[r][c]);
}
}
return clone;
}
/**
* Internal helper for cloning
*/
private void setVisible(int r, int c, boolean v) {
visible[r][c] = v;
}
/**
* Internal helper for cloning
*/
private void setDefault(int r, int c, boolean d) {
def[r][c] = d;
}
/**
* Internal helper for cloning
*/
private void setCell(int r, int c, Cell cell) {
cells[r][c] = cell;
}
/**
* Retrieve the boundary condition at the given boundary location
*
* @param boundaryLocation The boundary location where the information is to be retrieved
*
* @return The boundary condition at the desired boundary location
*/
public BoundaryCondition getBoundaryCondition(BoundaryLocation boundaryLocation) {
if (boundaryLocation == null) {
throw new IllegalArgumentException("location may not be null");
}
return boundaryConditions.get(boundaryLocation);
}
/**
* Add columns to the table either at the left or at the right end.
*
* If columns are inserted at the left edge of the table, the logical start index
* for the columns is reduced by <code>count</code>. If columns are inserted at the
* right edge of the table, the logical end index of the columns is increased by
* <code>count</code>.
*
* @param location Whether to add the columns at the left or the right edge
* @param count The number of columns to add
*/
public void addColumns(ColumnLocation location, int count) {
if (location == null) {
throw new IllegalArgumentException("location may not be null");
}
if (count <= 0) {
throw new IllegalArgumentException("count must be greater than 0");
}
Cell[][] cells_new = new Cell[rowNumber][colNumber + count];
boolean[][] visible_new = new boolean[rowNumber][colNumber + count];
boolean[][] def_new = new boolean[rowNumber][colNumber + count];
switch (location) {
case LEFT:
for (int r = 0; r < rowNumber; r++) {
for (int c = 0; c < count; c++) {
visible_new[r][c] = true;
def_new[r][c] = true;
cells_new[r][c] = defaultCell;
}
for (int c = 0; c < colNumber; c++) {
visible_new[r][c + count] = visible[r][c];
def_new[r][c + count] = def[r][c];
cells_new[r][c + count] = cells[r][c];
}
}
col0 -= count;
break;
case RIGHT:
for (int r = 0; r < rowNumber; r++) {
for (int c = 0; c < colNumber; c++) {
visible_new[r][c] = visible[r][c];
def_new[r][c] = def[r][c];
cells_new[r][c] = cells[r][c];
}
for (int c = colNumber; c < count + colNumber; c++) {
visible_new[r][c] = true;
def_new[r][c] = true;
cells_new[r][c] = defaultCell;
}
}
colEnd += count;
break;
}
visible = visible_new;
def = def_new;
cells = cells_new;
colNumber += count;
}
/**
* Add one column to the table either at the left or at the right end.
*
* This is a convenience method for adding just one column. See
* {@link #addColumns(ColumnLocation, int)} for more details.
*
* @param location Whether to add the column at the left or the right edge
*/
public void addColumn(ColumnLocation location) {
addColumns(location, 1);
}
/**
* Add one row to the table either at the top or at the bottom end.
*
* This is a convenience method for adding just one row. See
* {@link #addRows(RowLocation, int)} for more details.
*
* @param location Whether to add the row at the top or the bottom edge
*/
public void addRow(RowLocation location) {
addRows(location, 1);
}
/**
* Add rows to the table either at the top or at the bottom end.
*
* If rows are inserted at the top edge of the table, the logical start index
* for the rows is reduced by <code>count</code>. If rows are inserted at the
* bottom edge of the table, the logical end index of the rows is increased by
* <code>count</code>.
*
* @param location Whether to add the rows at the top or the bottom edge
* @param count The number of rows to add
*/
public void addRows(RowLocation location, int count) {
if (location == null) {
throw new IllegalArgumentException("location may not be null");
}
if (count <= 0) {
throw new IllegalArgumentException("count must be greater than 0");
}
Cell[][] cells_new = new Cell[rowNumber + count][colNumber];
boolean[][] visible_new = new boolean[rowNumber + count][colNumber];
boolean[][] def_new = new boolean[rowNumber + count][colNumber];
switch (location) {
case TOP:
for (int c = 0; c < colNumber; c++) {
for (int r = 0; r < count; r++) {
visible_new[r][c] = true;
def_new[r][c] = true;
cells_new[r][c] = defaultCell;
}
for (int r = 0; r < rowNumber; r++) {
visible_new[r + count][c] = visible[r][c];
def_new[r + count][c] = def[r][c];
cells_new[r + count][c] = cells[r][c];
}
}
row0 -= count;
break;
case BOTTOM:
for (int c = 0; c < colNumber; c++) {
for (int r = 0; r < rowNumber; r++) {
visible_new[r][c] = visible[r][c];
def_new[r][c] = def[r][c];
cells_new[r][c] = cells[r][c];
}
for (int r = rowNumber; r < count + rowNumber; r++) {
visible_new[r][c] = true;
def_new[r][c] = true;
cells_new[r][c] = defaultCell;
}
}
rowEnd += count;
break;
}
visible = visible_new;
def = def_new;
cells = cells_new;
rowNumber += count;
}
/**
* Removes empty cells at all four boundary locations.
*
* This is a convenience method comprising four individual
* method calls.
*
* @return <code>true</code> if some cells removed
*/
public boolean compact() {
return compact(ColumnLocation.LEFT) && compact(ColumnLocation.RIGHT)
&& compact(RowLocation.TOP) && compact(RowLocation.BOTTOM);
}
/**
* Removes empty cells at the given locations.
*
* This is a convenience method simplifying individual calls to the methods
* {@link #compact(RowLocation)},
* {@link #compact(ColumnLocation)}, and
* {@link #compact(InternalLocation)}. See these methods for additional details.
*
* @param locations The desired locations where to compact the table
*
* @return <code>true</code> if some cells removed
*/
public boolean compact(Location... locations) {
if (locations == null) {
throw new IllegalArgumentException("locations may not be null");
}
boolean ret = false;
for (Location location : locations) {
if (location instanceof ColumnLocation) {
ret = ret || compact((ColumnLocation)location);
} else if (location instanceof RowLocation) {
ret = ret || compact((RowLocation)location);
} else if (location instanceof InternalLocation) {
ret = ret || compact((InternalLocation)location);
}
}
return ret;
}
/**
* Removes empty cells at the given boundary location.
*
* Empty cells are cells which contain the default cell, i. e. they have not been
* touched as part of a {@link #setCell(Cell, int, int)} method call. This method
* checks for complete columns with default cells at the given boundary location
* and removes them from the table.
*
* @param columnLocation The desired location where to compact the table
*
* @return <code>true</code> if some cells were cut off
*/
public boolean compact(ColumnLocation columnLocation) {
if (columnLocation == null) {
throw new IllegalArgumentException("columnLocation may not be null");
}
int count = 0;
Cell[][] cells_new = null;
boolean[][] visible_new = null;
boolean[][] def_new = null;
//.... Save this for later check for changes
int old_row0 = row0;
int old_col0 = col0;
int old_rowNumber = rowNumber;
int old_colNumber = colNumber;
//.... Left edge
if (columnLocation.equals(ColumnLocation.LEFT)) {
int c = 0;
boolean removable = true;
do {
for (int r = 0; r < rowNumber; r++) {
if (!def[r][c]) {
removable = false;
}
}
if (removable) {
count++;
c++;
}
} while (removable);
if (count > 0) {
cells_new = new Cell[rowNumber][colNumber - count];
visible_new = new boolean[rowNumber][colNumber - count];
def_new = new boolean[rowNumber][colNumber - count];
for (int c2 = 0; c2 < colNumber - count; c2++) {
for (int r = 0; r < rowNumber; r++) {
visible_new[r][c2] = visible[r][c2 + count];
def_new[r][c2] = def[r][c2 + count];
cells_new[r][c2] = cells[r][c2 + count];
}
}
visible = visible_new;
def = def_new;
cells = cells_new;
col0 += count;
}
} else {
//.... Right edge
int c = colNumber - 1;
boolean removable = true;
do {
for (int r = 0; r < rowNumber; r++) {
if (!def[r][c]) {
removable = false;
}
}
if (removable) {
c--;
count++;
}
} while (removable);
if (count > 0) {
cells_new = new Cell[rowNumber][colNumber - count];
visible_new = new boolean[rowNumber][colNumber - count];
def_new = new boolean[rowNumber][colNumber - count];
for (int c2 = 0; c2 < colNumber - count; c2++) {
for (int r = 0; r < rowNumber; r++) {
visible_new[r][c2] = visible[r][c2];
def_new[r][c2] = def[r][c2];
cells_new[r][c2] = cells[r][c2];
}
}
visible = visible_new;
def = def_new;
cells = cells_new;
colEnd -= count;
}
}
colNumber -= count;
//.... Check whether the dimensions of the table have changed
if (row0 != old_row0 || col0 != old_col0
|| rowNumber != old_rowNumber
|| colNumber != old_colNumber) {
return true;
} else {
return false;
}
}
/**
* Removes empty cells at the given boundary location.
*
* Empty cells are cells which contain the default cell, i. e. they have not been
* touched as part of a {@link #setCell(Cell, int, int)} method call. This method
* checks for complete rows with default cells at the given boundary location
* and removes them from the table.
*
* @param rowLocation The desired location where to compact the table
*
* @return <code>true</code> if some cells were cut off
*/
public boolean compact(RowLocation rowLocation) {
if (rowLocation == null) {
throw new IllegalArgumentException("rowLocation may not be null");
}
int count = 0;
Cell[][] cells_new = null;
boolean[][] visible_new = null;
boolean[][] def_new = null;
//.... Save this for later check for changes
int old_row0 = row0;
int old_col0 = col0;
int old_rowNumber = rowNumber;
int old_colNumber = colNumber;
//.... Top edge
if (rowLocation.equals(RowLocation.TOP)) {
int r = 0;
boolean removable = true;
do {
for (int c = 0; c < colNumber; c++) {
if (!def[r][c]) {
removable = false;
}
}
if (removable) {
count++;
r++;
}
} while (removable);
if (count > 0) {
cells_new = new Cell[rowNumber - count][colNumber];
visible_new = new boolean[rowNumber - count][colNumber];
def_new = new boolean[rowNumber - count][colNumber];
for (int c = 0; c < colNumber; c++) {
for (int r2 = 0; r2 < rowNumber - count; r2++) {
visible_new[r2][c] = visible[r2 + count][c];
def_new[r2][c] = def[r2 + count][c];
cells_new[r2][c] = cells[r2 + count][c];
}
}
visible = visible_new;
def = def_new;
cells = cells_new;
row0 += count;
}
} else {
//.... Bottom edge
int r = rowNumber - 1;
boolean removable = true;
do {
for (int c = 0; c < colNumber; c++) {
if (!def[r][c]) {
removable = false;
}
}
if (removable) {
count++;
r--;
}
} while (removable);
if (count > 0) {
cells_new = new Cell[rowNumber - count][colNumber];
visible_new = new boolean[rowNumber - count][colNumber];
def_new = new boolean[rowNumber - count][colNumber];
for (int c = 0; c < colNumber; c++) {
for (int r2 = 0; r2 < rowNumber - count; r2++) {
visible_new[r2][c] = visible[r2][c];
def_new[r2][c] = def[r2][c];
cells_new[r2][c] = cells[r2][c];
}
}
visible = visible_new;
def = def_new;
cells = cells_new;
rowEnd -= count;
}
}
rowNumber -= count;
//.... Check whether the dimensions of the table have changed
if (row0 != old_row0 || col0 != old_col0
|| rowNumber != old_rowNumber
|| colNumber != old_colNumber) {
return true;
} else {
return false;
}
}
/**
* Removes empty cells at the given internal location.
*
* Empty cells are cells which contain the default cell, i. e. they have not been
* touched as part of a {@link #setCell(Cell, int, int)} method call. This method
* checks for complete rows or columns in the table (depending on the
* <code>internalLocation</code> parameter) with default cells
* and removes them from the table.
* <p>
* Note that calls to this method also remove such rows or columns ate the
* table boundaries, and thus a call to this method is a superset to calls
* to {@link #compact(RowLocation)} and {@link #compact(ColumnLocation)}.
*
* @param internalLocation The desired internal location where to compact the table
* (effectively by rows or by columns)
*
* @return <code>true</code> if some cells were removed
*/
public boolean compact(InternalLocation internalLocation) {
if (internalLocation == null) {
throw new IllegalArgumentException("internalLocation may not be null");
}
int count = 0;
Cell[][] cells_new = null;
boolean[][] visible_new = null;
boolean[][] def_new = null;
//.... Save this for later check for changes
int old_row0 = row0;
int old_col0 = col0;
int old_rowNumber = rowNumber;
int old_colNumber = colNumber;
if (internalLocation.equals(InternalLocation.COLUMN)) {
//.... Create an index of columns to retain
List<Integer> columnList = new ArrayList<Integer>();
for (int c = 0; c < colNumber; c++) {
boolean removable = true;
for (int r = 0; r < rowNumber; r++) {
if (!def[r][c]) {
removable = false;
}
}
if (!removable) {
columnList.add(c);
}
}
//.... Remove the columns
count = columnList.size();
if (count > 0) {
cells_new = new Cell[rowNumber][count];
visible_new = new boolean[rowNumber][count];
def_new = new boolean[rowNumber][count];
int c2 = 0;
for (int c = 0; c < count; c++) {
for (int r = 0; r < rowNumber; r++) {
c2 = columnList.get(c);
visible_new[r][c] = visible[r][c2];
def_new[r][c] = def[r][c2];
cells_new[r][c] = cells[r][c2];
}
}
visible = visible_new;
def = def_new;
cells = cells_new;
col0 += columnList.get(0);
colNumber = count;
colEnd = col0 + colNumber - 1;
}
//.... Remove all empty rows (this includes the TOP and BOTTOM cases)
} else if (internalLocation.equals(InternalLocation.ROW)) {
//.... Create an index of rows to retain
List<Integer> rowList = new ArrayList<Integer>();
for (int r = 0; r < rowNumber; r++) {
boolean removable = true;
for (int c = 0; c < colNumber; c++) {
if (!def[r][c]) {
removable = false;
}
}
if (!removable) {
rowList.add(r);
}
}
//.... Remove the rows
count = rowList.size();
if (count > 0) {
cells_new = new Cell[count][colNumber];
visible_new = new boolean[count][colNumber];
def_new = new boolean[count][colNumber];
int r2 = 0;
for (int c = 0; c < colNumber; c++) {
for (int r = 0; r < count; r++) {
r2 = rowList.get(r);
visible_new[r][c] = visible[r2][c];
def_new[r][c] = def[r2][c];
cells_new[r][c] = cells[r2][c];
}
}
visible = visible_new;
def = def_new;
cells = cells_new;
row0 += rowList.get(0);
rowNumber = count;
rowEnd = row0 + rowNumber - 1;
}
}
//.... Check whether the dimensions of the table have changed
if (row0 != old_row0 || col0 != old_col0
|| rowNumber != old_rowNumber
|| colNumber != old_colNumber) {
return true;
} else {
return false;
}
}
/**
* A convenience method to enable clipping at all four table boundaries.
*
* @see BoundaryCondition
*/
public void setClipping() {
boundaryConditions.put(ColumnLocation.LEFT, BoundaryCondition.CLIPPING);
boundaryConditions.put(ColumnLocation.RIGHT, BoundaryCondition.CLIPPING);
boundaryConditions.put(RowLocation.TOP, BoundaryCondition.CLIPPING);
boundaryConditions.put(RowLocation.BOTTOM, BoundaryCondition.CLIPPING);
}
/**
* A convenience method to enable auto-grow at all four table boundaries.
*
* @see BoundaryCondition
*/
public void setGrow() {
boundaryConditions.put(ColumnLocation.LEFT, BoundaryCondition.GROW);
boundaryConditions.put(ColumnLocation.RIGHT, BoundaryCondition.GROW);
boundaryConditions.put(RowLocation.TOP, BoundaryCondition.GROW);
boundaryConditions.put(RowLocation.BOTTOM, BoundaryCondition.GROW);
}
/**
* A convenience method to enable fixed boundaries at all four table boundaries.
*
* @see BoundaryCondition
*/
public void setFixed() {
boundaryConditions.put(ColumnLocation.LEFT, BoundaryCondition.FIXED);
boundaryConditions.put(ColumnLocation.RIGHT, BoundaryCondition.FIXED);
boundaryConditions.put(RowLocation.TOP, BoundaryCondition.FIXED);
boundaryConditions.put(RowLocation.BOTTOM, BoundaryCondition.FIXED);
}
/**
* Retrieve the logical start index for rows in the table.
*
* @return The logical start index for rows
*/
public int getRow0() {
return row0;
}
/**
* Retrieve the logical start index for columns in the table.
*
* @return The logical start index for columns
*/
public int getCol0() {
return col0;
}
/**
* Retrieve the number of rows in the table.
*
* @return The number of rows in the table
*/
public int getRowNumber() {
return rowNumber;
}
/**
* Retrieve the number of columns in the table.
*
* @return The number of columns in the table
*/
public int getColNumber() {
return colNumber;
}
/**
* Retrieve the cell at the given table location.
*
* @param row The logical row index
* @param col The logical column index
*
* @return The cell at the given location
*/
public Cell getCell(int row, int col) {
int r = row - row0;
int c = col - col0;
if (r >= rowNumber || r < 0) {
throw new IllegalArgumentException("row must be between " + row0 + " and " + getRowEnd());
}
if (c >= colNumber || c < 0) {
throw new IllegalArgumentException("col must be between " + col0 + " and " + getColEnd());
}
return cells[r][c];
}
/**
* Check whether the cell at the given table location is visible.
*
* Cells can become invisible when other cells spanning more than one row and/or
* column cover the particular location in the table. This is important for the
* rendering of tables since cells which are invisible are not part of the rendering.
*
* @param row The logical row index
* @param col The logical column index
*
* @return <code>true</code> if the cell at the given location is visible
*/
public boolean isVisible(int row, int col) {
int r = row - row0;
int c = col - col0;
if (r >= rowNumber || r < 0) {
throw new IllegalArgumentException("row must be between " + row0 + " and " + getRowEnd());
}
if (c >= colNumber || c < 0) {
throw new IllegalArgumentException("col must be between " + col0 + " and " + getColEnd());
}
return visible[r][c];
}
/**
* Check whether the cell at the given table location is the default cell.
*
* At table instance creation time, all cells in the table refer to the default cell.
* This may change over time as cells are added to the table.
*
* @param row The logical row index
* @param col The logical column index
*
* @return <code>true</code> if the cell at the given location is the default cell
*/
public boolean isDefaultCell(int row, int col) {
int r = row - row0;
int c = col - col0;
if (r >= rowNumber || r < 0) {
throw new IllegalArgumentException("row must be between " + row0 + " and " + getRowEnd());
}
if (c >= colNumber || c < 0) {
throw new IllegalArgumentException("col must be between " + col0 + " and " + getColEnd());
}
return def[r][c];
}
/**
* Insert a cell into the table at the given location.
*
* Several cases need to be differentiated when adding a cell to the table. This HTML table shows
* the different cases that can occur when inserting a cell (orange) into a table (grey). Note that
* these cases apply both to rows and columns:
* <p>
*
* <table style="text-align: left; width: 500px;" border="1"
* cellpadding="2" cellspacing="2">
* <tbody>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">Case<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td colspan="3" rowspan="1"
* style="vertical-align: top; background-color: rgb(192, 192, 192); text-align: center; font-family: Helvetica,Arial,sans-serif;">Table
* Extent<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">1<br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(255, 204, 51); font-family: Helvetica,Arial,sans-serif; text-align: center;">Cell</td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">2<br>
* </td>
* <td colspan="4" rowspan="1"
* style="vertical-align: top; background-color: rgb(255, 204, 51); font-family: Helvetica,Arial,sans-serif; text-align: center;">Cell<br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">3<br>
* </td>
* <td colspan="7" rowspan="1"
* style="vertical-align: top; background-color: rgb(255, 204, 51); font-family: Helvetica,Arial,sans-serif; text-align: center;">Cell</td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">4<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(255, 204, 51); font-family: Helvetica,Arial,sans-serif; text-align: center;">Cell</td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">5<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td colspan="4" rowspan="1"
* style="vertical-align: top; background-color: rgb(255, 204, 51); font-family: Helvetica,Arial,sans-serif; text-align: center;">Cell<br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">6<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;"> <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">
* <br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;">
* <br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;"><br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;">
* <br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">
* <br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(255, 204, 51); font-family: Helvetica,Arial,sans-serif; text-align: center;">Cell</td>
* </tr>
* </tbody>
* </table>
* <p>
* Depending on the chosen boundary conditions at the boundary locations, the following results occur:
* <p>
* <table style="text-align: left;" border="1" cellpadding="2"
* cellspacing="2">
* <tbody>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">Case<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">FIXED</td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">CLIPPING<br>
* </td>
* <td colspan="1" rowspan="1"
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">GROW<br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">1<br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif; text-align: center;">IllegalArgumentException<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">null<br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">SetResult,
* table expanded<br>
* </td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">3<br>
* </td>
* <td colspan="1" rowspan="1"
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif; text-align: center;">IllegalArgumentException</td>
* <td colspan="1"
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">SetResult,
* cell clipped<br>
* </td>
* <td colspan="1"
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">SetResult,
* table expanded</td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">2<br>
* </td>
* <td colspan="1" rowspan="1"
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif; text-align: center;">IllegalArgumentException</td>
* <td colspan="1"
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">SetResult,
* cell clipped</td>
* <td colspan="1"
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">SetResult,
* table expanded</td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">4<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">SetResult<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">SetResult<br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">SetResult</td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">5<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">IllegalArgumentException</td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">SetResult,
* cell clipped</td>
* <td
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">SetResult,
* table expanded</td>
* </tr>
* <tr>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">6<br>
* </td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">IllegalArgumentException</td>
* <td
* style="vertical-align: top; font-family: Helvetica,Arial,sans-serif; background-color: rgb(255, 255, 255);">null<br>
* </td>
* <td
* style="vertical-align: top; background-color: rgb(255, 255, 255); font-family: Helvetica,Arial,sans-serif;">SetResult,
* table expanded</td>
* </tr>
* </tbody>
* </table>
*
* @param row The logical row index
* @param col The logical column index
*
* @return A {@link SetResult} instance (or <code>null</code>, see above)
*
* @see BoundaryCondition
*/
public SetResult setCell(Cell cell, int row, int col) {
if (cell == null)
throw new IllegalArgumentException("cell may not be null");
int r = row - row0; // Absolute index (row, row0 are logical)
int c = col - col0; // Absolute index (col, col0 are logical)
int rEnd = r + cell.getRowSpan() - 1; // Absolute index
int cEnd = c + cell.getColSpan() - 1; // Absolute index
int rowLimit = row0 + rowNumber - cell.getRowSpan();
int colLimit = col0 + colNumber - cell.getColSpan();
SetResult result = new SetResult(row, col); // The default
//.... Row: Case 1
if (rEnd < 0) {
switch (boundaryConditions.get(RowLocation.TOP)) {
case FIXED:
throw new IllegalArgumentException("Cell lies completely outside of the table");
case CLIPPING:
return null; // Entire contents are clipped
case GROW:
addRows(RowLocation.TOP, -r);
r = 0;
rEnd = r + cell.getRowSpan() - 1;
}
} else if (r < 0) {
//.... Row: Case 2
if (rEnd < rowNumber) {
switch (boundaryConditions.get(RowLocation.TOP)) {
case FIXED:
if (cell.getRowSpan() > rowNumber)
throw new IllegalArgumentException("Cell has too many rows. Maximum row number is " + rowNumber);
throw new IllegalArgumentException("row must be between " + row0 + " and " + rowLimit);
case CLIPPING:
r = 0;
result.setModified(true);
break;
case GROW:
addRows(RowLocation.TOP, -r);
r = 0;
rEnd = r + cell.getRowSpan() - 1;
}
} else {
//.... Row: Case 3
switch (boundaryConditions.get(RowLocation.TOP)) {
case FIXED:
throw new IllegalArgumentException("Cell has too many rows. Maximum row number is " + rowNumber);
case CLIPPING:
r = 0;
result.setModified(true);
break;
case GROW:
addRows(RowLocation.TOP, -r);
r = 0;
rEnd = r + cell.getRowSpan() - 1;
}
switch (boundaryConditions.get(RowLocation.BOTTOM)) {
case FIXED:
throw new IllegalArgumentException("Cell has too many rows. Maximum row number is " + rowNumber);
case CLIPPING:
rEnd = rowNumber - 1;
result.setModified(true);
break;
case GROW:
addRows(RowLocation.BOTTOM, rEnd - getRowEnd() + row0);
rEnd = rowNumber - 1;
}
}
} else if (r < rowNumber) {
//.... Row: Case 4
if (rEnd < rowNumber) {
//.... Row: Case 5
} else {
switch (boundaryConditions.get(RowLocation.BOTTOM)) {
case FIXED:
if (cell.getRowSpan() > rowNumber) {
throw new IllegalArgumentException("Cell has too many rows. Maximum row number is " + rowNumber);
} else {
throw new IllegalArgumentException("row must be between " + row0 + " and " + rowLimit);
}
case CLIPPING:
rEnd = rowNumber - 1;
result.setModified(true);
break;
case GROW:
addRows(RowLocation.BOTTOM, rEnd - getRowEnd() + row0);
rEnd = rowNumber - 1;
}
}
//.... Row: Case 6
} else {
switch (boundaryConditions.get(RowLocation.BOTTOM)) {
case FIXED:
throw new IllegalArgumentException("Cell lies completely outside of the table");
case CLIPPING:
return null;
case GROW:
addRows(RowLocation.BOTTOM, rEnd - getRowEnd() + row0);
rEnd = rowNumber - 1;
}
}
//.... Column: Case 1
if (cEnd < 0) {
switch (boundaryConditions.get(ColumnLocation.LEFT)) {
case FIXED:
throw new IllegalArgumentException("Cell lies completely outside of the table");
case CLIPPING:
return null; // Entire contents are clipped
case GROW:
addColumns(ColumnLocation.LEFT, -c);
c = 0;
cEnd = c + cell.getColSpan() - 1;
}
} else if (c < 0) {
//.... Column: Case 2
if (cEnd < colNumber) {
switch (boundaryConditions.get(ColumnLocation.LEFT)) {
case FIXED:
if (cell.getColSpan() > colNumber) {
throw new IllegalArgumentException("Cell has too many columns. Maximum column number is " + colNumber);
} else {
throw new IllegalArgumentException("col must be between " + col0 + " and " + colLimit);
}
case CLIPPING:
c = 0;
result.setModified(true);
break;
case GROW:
addColumns(ColumnLocation.LEFT, -c);
c = 0;
cEnd = c + cell.getColSpan() - 1;
}
} else {
//.... Column: Case 3
switch (boundaryConditions.get(ColumnLocation.LEFT)) {
case FIXED:
throw new IllegalArgumentException("Cell has too many columns. Maximum column number is " + colNumber);
case CLIPPING:
c = 0;
result.setModified(true);
break;
case GROW:
addColumns(ColumnLocation.LEFT, -c);
c = 0;
cEnd = c + cell.getColSpan() - 1;
}
switch (boundaryConditions.get(ColumnLocation.RIGHT)) {
case FIXED:
throw new IllegalArgumentException("Cell has too many columns. Maximum column number is " + colNumber);
case CLIPPING:
cEnd = colNumber - 1;
result.setModified(true);
break;
case GROW:
addColumns(ColumnLocation.RIGHT, cEnd - getColEnd() + col0);
cEnd = colNumber - 1;
}
}
} else if (c < colNumber) {
//.... Column: Case 4
if (cEnd < colNumber) {
//.... Column: Case 5
} else {
switch (boundaryConditions.get(ColumnLocation.RIGHT)) {
case FIXED:
if (cell.getColSpan() > colNumber) {
throw new IllegalArgumentException("Cell has too many columns. Maximum column number is " + colNumber);
} else {
throw new IllegalArgumentException("col must be between " + col0 + " and " + colLimit);
}
case CLIPPING:
cEnd = colNumber - 1;
result.setModified(true);
break;
case GROW:
addColumns(ColumnLocation.RIGHT, cEnd - getColEnd() + col0);
cEnd = colNumber - 1;
}
}
//.... Column: Case 6
} else {
switch (boundaryConditions.get(ColumnLocation.RIGHT)) {
case FIXED:
throw new IllegalArgumentException("Cell lies completely outside of the table");
case CLIPPING:
return null;
case GROW:
addColumns(ColumnLocation.RIGHT, cEnd - getColEnd() + col0);
cEnd = colNumber - 1;
}
}
//.... The cell may have to be modified to be displayed correctly now (CLIPPING only)
if (result.isModified()) {
cell.setRowSpan(rEnd - r + 1);
cell.setColSpan(cEnd - c + 1);
}
//.... Now actually fill the table where necessary
for (int rIndex = r; rIndex <= rEnd; rIndex++) {
for (int cIndex = c; cIndex <= cEnd; cIndex++) {
if (!def[rIndex][cIndex]) {
throw new IllegalArgumentException("Cell conflict when trying to add cell with name '"
+ cell.getName() + "' at location ("
+ rIndex + "/" + cIndex + "): already covered by cell '" + cells[rIndex][cIndex].getName() + "'");
}
cells[rIndex][cIndex] = cell;
visible[rIndex][cIndex] = false;
def[rIndex][cIndex] = false;
}
}
visible[r][c] = true; // Only this one remains, all others are now hidden
result.setRow(r + row0);
result.setCol(c + col0);
result.setRowEnd(rEnd + row0);
result.setColEnd(cEnd + col0);
return result;
}
/**
* A simple HTML debug output. The table is dumped to STDOUT and the resulting file
* can directly be opened in a browser to get a rough idea of the internal table layout and
* cell structure.
*/
public void dump() {
System.out.println("<html><body>\n");
System.out.println("<table border=1>");
for (int r = 0; r < rowNumber; r++) {
System.out.println("<tr>");
for (int c = 0; c < colNumber; c++) {
if (def[r][c]) {
System.out.println("<td> (" + r + "/" + c + ")");
} else {
if (visible[r][c]) {
System.out.println("<td bgcolor=green> (" + r + "/" + c + ")<br> Cell = " + cells[r][c].getName());
} else {
System.out.println("<td bgcolor=yellow> (" + r + "/" + c + ")<br> Cell = " + cells[r][c].getName());
}
}
}
}
System.out.println("</table>\n");
System.out.println("</body></html>\n");
}
/**
* Get the index of the last row in the table.
*
* @return The index of the last row in the table
*/
public int getRowEnd() {
return rowEnd;
}
/**
* Get the logical index of the last column in the table.
*
* @return The logical index of the last column in the table
*/
public int getColEnd() {
return colEnd;
}
/**
* Set the boundary condition for the given boundary location.
*
* @param boundaryLocation The location for which the boundary condition is to be set
* @param boundaryCondition The boundary condition to establish for this location
*/
public void setBoundaryCondition(BoundaryLocation boundaryLocation, BoundaryCondition boundaryCondition) {
if (boundaryLocation == null) {
throw new IllegalArgumentException("boundaryLocation may not be null");
}
if (boundaryCondition == null) {
throw new IllegalArgumentException("boundaryCondition may not be null");
}
boundaryConditions.put(boundaryLocation, boundaryCondition);
}
}
/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* An enum constant for the two possible boundary locations where rows are of relevance.
*/
enum RowLocation implements BoundaryLocation {
/**
* The top edge of the table
*/
TOP,
/**
* The bottom edge of the table
*/
BOTTOM;
}
/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* Result information of a {@link Table#setCell(Cell, int, int)} operation.
*
* The information returned in an instance of this class is useful in cases where
* the boundaries of the table are managed dynaically since then the cell as such may
* have been clipped and thus, the starting and end indices of rows and columns may
* have changed.
*/
class SetResult {
private int row = 0; // The logical row where the actual insert occurred
private int col = 0; // The logical col where the actual insert occurred
private int rowEnd = 0; // The logical index of the end row for the cell
private int colEnd = 0; // The logical index of the end col for the cell
private boolean modified = false; // True if rowSpan and/or colSpan had to be modified
/**
* Create a new instance with the given row and column information.
*
* @param row The logical row where the actual insert of the cell occurred
* @param col The logical column where the actual insert of the cell occurred
*/
public SetResult(int row, int col) {
this.setRow(row);
this.setCol(col);
}
/**
* Retrieve the logical index of the row where the actual insert of the cell occurred
*
* @return The logical index of the row where the actual insert of the cell occurred
*/
public int getRow() {
return row;
}
/**
* Set the logical index of the row where the actual insert of the cell occurred. Sometimes
* it is necessary to modify the value established in the constructor.
*
* @param row The logical index of the row where the actual insert of the cell occurred
*/
public void setRow(int row) {
this.row = row;
}
/**
* Retrieve the logical index of the column where the actual insert of the cell occurred
*
* @return The logical index of the column where the actual insert of the cell occurred
*/
public int getCol() {
return col;
}
/**
* Set the logical index of the column where the actual insert of the cell occurred. Sometimes
* it is necessary to modify the value established in the constructor.
*
* @param col The logical index of the column where the actual insert of the cell occurred
*/
public void setCol(int col) {
this.col = col;
}
/**
* Returns a boolean indicating whether the original values of the cell (row and
* column number) and /or the insertion point (the arguments to the
* {@link Table#setCell(Cell, int, int)} method) have been modified in the course
* of the insertion process.
*
* @return A boolean indicating whether the original values of the cell have
* been modified in the course of the insertion process
*/
public boolean isModified() {
return modified;
}
/**
* Set the boolean indicating whether the cell parameters have been changed in the course
* of the insertion process into the table
*
* @param modified The desired boolean value
*/
public void setModified(boolean modified) {
this.modified = modified;
}
/**
* Retrieve the actual row end index of the cell in the table after the insertion process.
* This value may be different from he expected value if clipping is activated at the
* boundaries.
*
* @return The actual row end index of the cell in the table
*/
public int getRowEnd() {
return rowEnd;
}
/**
* Set the actual logical row end index of the cell in the table after the insertion process.
*
* @param rowEnd The actual logical row end index of the cell in the table
*/
public void setRowEnd(int rowEnd) {
this.rowEnd = rowEnd;
}
/**
* Retrieve the actual logical end column index of the cell in the table after the insertion process.
* This value may be different from he expected value if clipping is activated at the
* boundaries.
*
* @return The actual logical column end index of the cell in the table
*/
public int getColEnd() {
return colEnd;
}
/**
* Set the actual logical column end index of the cell in the table after the insertion process.
*
* @param colEnd The actual logical column end index of the cell in the table
*/
public void setColEnd(int colEnd) {
this.colEnd = colEnd;
}
/**
* The overridden {@link Object#toString()} method.
*
* @return A string representation of the instance with all relevant data
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SetResult: row = ");
sb.append(row);
sb.append(" / col = ");
sb.append(col);
sb.append(" / rowEnd = ");
sb.append(rowEnd);
sb.append(" / colEnd = ");
sb.append(colEnd);
sb.append(" / modified = ");
sb.append(modified);
return sb.toString();
}
}
/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* An enum constant for the different supported boundary conditions.
*/
enum BoundaryCondition {
/**
* Any cell location outside of the predefined area leads to an exception.
* This is the default setting
*/
FIXED,
/**
* Cells are truncated when necessary
*/
CLIPPING,
/**
* The table grows when necessary to accommodate additional columns/rows
*/
GROW;
}
/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* A marker interface for all locations relating to the outer boundaries of a table.
*/
interface BoundaryLocation extends Location {
;
}
/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* An enum constant for internal locations of a table. This can be used to
* identify whether operations on the table should apply to rows and / or
* columns.
*/
enum InternalLocation implements Location {
/**
* This location relates to all rows of the table
*/
ROW,
/**
* This location relates to all columns of the table
*/
COLUMN;
}
/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* A marker interface for locations where operations or conditions apply for a table.
*/
interface Location {
;
}
/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* This class represents cells in the table. Cells can span more than one row and column.
* Instances of this class are also used to hold all data pertaining to a cell and thus serves
* as a vehicle to transport data into a Velocity template.
*/
class Cell {
private String name = null;
private Map<String, String> properties = null; // For HTML formatting properties
private int rowSpan = 1;
private int colSpan = 1;
private Map<String, Object> content = new HashMap<String, Object>();
private Set<String> types = new TreeSet<String>();
/**
* Constructor for a simple cell with 1 row and 1 column.
*
* @param name The name given to the cell. This can be used as a descriptive text when
* necessary
*/
public Cell(String name) {
this(name, 1, 1);
}
/**
* Constructor for a cell.
*
* @param name The name given to the cell. This can be used as a descriptive text when
* necessary
* @param rowSpan The number of rows that this cell spans
* @param colSpan The number of columns that this cell spans
*/
public Cell(String name, int rowSpan, int colSpan) {
this(new HashMap<String, String>(), name, rowSpan, colSpan);
}
/**
* Constructor for a cell.
*
* @param properties The set of properties for this cell
* @param name The name given to the cell. This can be used as a descriptive text when
* necessary
* @param rowSpan The number of rows that this cell spans
* @param colSpan The number of columns that this cell spans
*/
public Cell(Map<String, String> properties, String name, int rowSpan, int colSpan) {
if (properties == null) {
throw new IllegalArgumentException("properties may not be null");
}
if (name == null) {
throw new IllegalArgumentException("name may not be null");
}
if (rowSpan < 1) {
throw new IllegalArgumentException("rowSpan must be larger than 0");
}
if (colSpan < 1) {
throw new IllegalArgumentException("colSpan must be larger than 0");
}
this.setColSpan(colSpan);
this.setRowSpan(rowSpan);
this.properties = properties;
this.name = name;
}
/**
* Create a deep copy of the current cell.
*
* @return A deep copy with all properties, types and content elements.
*/
public Cell clone() {
Cell clone = new Cell(getName(), getRowSpan(), getColSpan());
for (String key : getProperties().keySet()) {
clone.setProperty(key, getProperty(key));
}
for (String key : getContent().keySet()) {
clone.setContent(key, getContent(key));
}
for (String type : getTypes()) {
clone.setType(type);
}
return clone;
}
/**
* Retrieve the properties defined for this cell.
*
* @return The properties map for this cell
*/
public Map<String, String> getProperties() {
return properties;
}
/**
* Retrieve the content elements defined for this cell.
*
* @return The content element map for this cell
*/
public Map<String, Object> getContent() {
return content;
}
/**
* Retrieve the types defined for this cell.
*
* @return The type set for this cell
*/
public Set<String> getTypes() {
return types;
}
/**
* Retrieve the name of the cell.
*
* @return The name of the cell
*/
public String getName() {
return name;
}
/**
* Retrieve the number of rows that this cell spans.
*
* @return The number of rows that this cell spans
*/
public int getRowSpan() {
return rowSpan;
}
/**
* Retrieve the number of columns that this cell spans.
*
* @return The number of columns that this cell spans
*/
public int getColSpan() {
return colSpan;
}
/**
* Set a type for this cell. Types are string-valued markers, and any number of types
* can be attached to a cell using this method. Inside the Velocity template,
* cells can be checked for types using the {@link #isType(String)} method. This
* allows the template to handle cells with different types differently (e. g. in the
* layout).
*
* @param type The type to add for this cell
*/
public void setType(String type) {
if (type == null) {
throw new IllegalArgumentException("type may not be null");
}
types.add(type);
}
/**
* Check whether a given type is set for this cell. This is useful inside Velocity
* templates to allow for type-specific handling of cell layout.
*
* @param type The type to check for in this cell
*
* @return A boolean indicating whether the given type has been set for this cell
*/
public boolean isType(String type) {
if (type == null) {
throw new IllegalArgumentException("type may not be null");
}
return types.contains(type);
}
/**
* Retrieve a property value.
*
* @param key The key for this peoperty
*
* @return The value for the given key
*/
public String getProperty(String key) {
if (key == null) {
throw new IllegalArgumentException("key may not be null");
}
if (!properties.containsKey(key)) {
throw new IllegalArgumentException("Unknown property key: " + key);
}
return properties.get(key);
}
/**
* Set a property value. Properties are another means to equip a cell with
* configuration information or content data, and any number of key/value pairs
* can be attached to a cell and used in Velocity templates when processing the cell.
*
* @param key The property key
* @param value The property value
*/
public void setProperty(String key, String value) {
if (key == null) {
throw new IllegalArgumentException("key may not be null");
}
if (value == null) {
throw new IllegalArgumentException("value may not be null");
}
properties.put(key, value);
}
/**
* Retrieve the content object associated with the given key.
*
* @param key The key identifying the content object
*
* @return The content object associated with the given key
*/
public Object getContent(String key) {
if (key == null) {
throw new IllegalArgumentException("key may not be null");
}
return content.get(key);
}
/**
* Set a content object. Content objects are used to attach data to a cell
* which can then be used in the template, for example to attach a picture
* or a table with the results of a DB query to an HTML cell. The controller
* program which sets up the table/cell structure would add such content objects
* to the cells, and the Velocity template would retrieve the data using the
* keys and add it to the HTML cell structure.
*
* @param key The key by which this content object is identified
* @param value The actual content object
*/
public void setContent(String key, Object value) {
if (key == null) {
throw new IllegalArgumentException("key may not be null");
}
if (value == null) {
throw new IllegalArgumentException("value may not be null");
}
content.put(key, value);
}
/**
* Set the number of rows that this cell spans. The original value set in the
* constructor my change when cells are clipped during insertion into the table.
*
* @see BoundaryCondition
*
* @param rowSpan The number of rows that this cell spans
*/
public void setRowSpan(int rowSpan) {
if (rowSpan < 1) {
throw new IllegalArgumentException("rowSpan must be greater than 0");
}
this.rowSpan = rowSpan;
}
/**
* Set the number of columns that this cell spans. The original value set in the
* constructor my change when cells are clipped during insertion into the table.
*
* @see BoundaryCondition
*
* @param colSpan The number of columns that this cell spans
*/
public void setColSpan(int colSpan) {
if (colSpan < 1) {
throw new IllegalArgumentException("colSpan must be greater than 0");
}
this.colSpan = colSpan;
}
/**
* The overridden {@link Object#toString()} method.
*
* @return A string representation of the instance with all relevant data
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Cell: name = ");
sb.append(name);
sb.append(" / rowSpan = ");
sb.append(rowSpan);
sb.append(" / colSpan = ");
sb.append(colSpan);
return sb.toString();
}
}
/**
* Copyright 2007 Dr. Matthias Laux
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
/**
* An enum constant for the two possible boundary locations where columns are of relevance.
*/
enum ColumnLocation implements BoundaryLocation {
/**
* The left edge of the table
*/
LEFT,
/**
* The right edge of the table
*/
RIGHT;
}
|