Mega Code Archive

 
Categories / Java / Servlets
 

Java object representations of the HTML table structure

/**  * 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;">&nbsp;<br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;<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;">&nbsp;<br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;<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;">&nbsp;<br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;<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;">&nbsp;<br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;<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;">&nbsp;<br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;<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;">&nbsp;<br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;<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;">&nbsp;<br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;<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;">&nbsp;<br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;    * <br>    * </td>    * <td    * style="vertical-align: top; background-color: rgb(192, 192, 192); font-family: Helvetica,Arial,sans-serif;">&nbsp;&nbsp;&nbsp;&nbsp;    * <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;">&nbsp;&nbsp;&nbsp;&nbsp;    * <br>    * </td>    * <td    * style="vertical-align: top; font-family: Helvetica,Arial,sans-serif;">&nbsp;&nbsp;&nbsp;&nbsp;    * &nbsp;<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; }