Java object representations of the HTML table structure : HTML Output « Servlets « Java






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;
}

   
    
    
  








Related examples in the same category

1.Servlet Output HTML Demo
2.Servlet Display Static HTML
3.Prints a conversion table of miles per gallon to kilometers per liter
4.Servlet: Print Table
5.Html utilities
6.Html Parse Servlet
7.Escape and unescape string
8.Escapes newlines, tabs, backslashes, and quotes in the specified string
9.Web Calendar
10.HTML Helper
11.Escape HTML
12.Convert HTML to text
13.Text To HTML
14.Unescape HTML
15.Entity Decoder
16.Format a color to HTML RGB color format (e.g. #FF0000 for Color.red)
17.Definitions of HTML character entities and conversions between unicode characters and HTML character entities
18.Encode special characters and do formatting for HTML output
19.HTML color names
20.Utility methods for dealing with HTML
21.Filter the specified message string for characters that are sensitive in HTML
22.A collection of all character entites defined in the HTML4 standard.
23.Decode an HTML color string like '#F567BA;' into a Color
24.Normalize Post Data
25.Get HTML Color String from Java Color object
26.HTML Decoder
27.HTML Parser
28.HTML color and Java Color
29.HTML form Utilites
30.Html Dimensions
31.break Lines with HTML
32.insert HTML block dynamically
33.Convert an integer to an HTML RGB value
34.Convert to HTML string