001 /*
002 * Copyright (c) 2006 Henri Sivonen
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a
005 * copy of this software and associated documentation files (the "Software"),
006 * to deal in the Software without restriction, including without limitation
007 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
008 * and/or sell copies of the Software, and to permit persons to whom the
009 * Software is furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
017 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
019 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
020 * DEALINGS IN THE SOFTWARE.
021 */
022
023 package org.whattf.checker.table;
024
025 import java.util.Arrays;
026 import java.util.Iterator;
027 import java.util.SortedSet;
028 import java.util.TreeSet;
029
030 import org.xml.sax.SAXException;
031
032 /**
033 * Represents a row group (explicit or implicit) for table integrity checking.
034 *
035 * @version $Id: RowGroup.java 169 2007-05-25 07:21:55Z hsivonen $
036 * @author hsivonen
037 */
038 final class RowGroup {
039
040 /**
041 * Runtime type constant.
042 */
043 private final Cell[] EMPTY_CELL_ARRAY = {};
044
045 /**
046 * Keeps track of the current slot row of the insertion point.
047 */
048 private int currentRow = -1;
049
050 /**
051 * The column slot of the insertion point.
052 */
053 private int insertionPoint = 0;
054
055 /**
056 * The index of the next uninspected item in <code>cellsOnCurrentRow</code>.
057 */
058 private int nextOldCell = 0;
059
060 /**
061 * The owning table.
062 */
063 private final Table owner;
064
065 /**
066 * The set of cells from previous rows that are still in effect extending
067 * downwards.
068 */
069 private final SortedSet<Cell> cellsIfEffect = new TreeSet<Cell>(
070 VerticalCellComparator.THE_INSTANCE);
071
072 /**
073 * A temporary copy of <code>cellsIfEffect</code> sorted differently.
074 */
075 private Cell[] cellsOnCurrentRow;
076
077 /**
078 * Whether the current row has had cells.
079 */
080 private boolean rowHadCells;
081
082 /**
083 * The local name of the element that established this row group or
084 * <code>null</code> if this is an implicit row group.
085 */
086 private final String type;
087
088 RowGroup(Table owner, String type) {
089 super();
090 this.owner = owner;
091 this.type = type;
092 }
093
094 public void cell(Cell cell) throws SAXException {
095 rowHadCells = true;
096 findInsertionPoint();
097 cell.setPosition(currentRow, insertionPoint);
098 owner.cell(cell);
099 if (cell.getBottom() > currentRow + 1) {
100 cellsIfEffect.add(cell);
101 }
102 insertionPoint = cell.getRight();
103 for (int i = nextOldCell; i < cellsOnCurrentRow.length; i++) {
104 cellsOnCurrentRow[i].errOnHorizontalOverlap(cell);
105 }
106 }
107
108 /**
109 *
110 */
111 private void findInsertionPoint() {
112 for (;;) {
113 if (nextOldCell == cellsOnCurrentRow.length) {
114 break;
115 }
116 Cell other = cellsOnCurrentRow[nextOldCell];
117 int newInsertionPoint = other.freeSlot(insertionPoint);
118 if (newInsertionPoint == insertionPoint) {
119 break;
120 }
121 nextOldCell++;
122 insertionPoint = newInsertionPoint;
123 }
124 }
125
126 public void end() throws SAXException {
127 for (Cell cell : cellsIfEffect) {
128 cell.errIfNotRowspanZero(type);
129 }
130 }
131
132 public void endRow() throws SAXException {
133 if (!rowHadCells) {
134 owner.err("Row "
135 + (currentRow + 1)
136 + " of "
137 + (type == null ? "an implicit row group"
138 : "a row group established by a \u201C" + type
139 + "\u201D element")
140 + " has no cells beginning on it.");
141 }
142
143 findInsertionPoint();
144 cellsOnCurrentRow = null;
145
146 int columnCount = owner.getColumnCount();
147 if (owner.isHardWidth()) {
148 if (insertionPoint > columnCount) {
149 owner.err("A table row was "
150 + insertionPoint
151 + " columns wide and exceeded the column count established using column markup ("
152 + columnCount + ").");
153 } else if (insertionPoint < columnCount) {
154 owner.err("A table row was "
155 + insertionPoint
156 + " columns wide, which is less than the column count established using column markup ("
157 + columnCount + ").");
158 }
159 } else if (columnCount == -1) {
160 // just saw the first row
161 owner.setColumnCount(insertionPoint);
162 } else {
163 if (insertionPoint > columnCount) {
164 owner.warn("A table row was "
165 + insertionPoint
166 + " columns wide and exceeded the column count established by the first row ("
167 + columnCount + ").");
168 } else if (insertionPoint < columnCount) {
169 owner.warn("A table row was "
170 + insertionPoint
171 + " columns wide, which is less than the column count established by the first row ("
172 + columnCount + ").");
173 }
174 }
175
176 // Get rid of cells that don't span to the next row
177 for (Iterator<Cell> iter = cellsIfEffect.iterator(); iter.hasNext();) {
178 Cell cell = iter.next();
179 if (cell.shouldBeCulled(currentRow + 1)) {
180 iter.remove();
181 }
182 }
183 }
184
185 public void startRow() {
186 currentRow++;
187 insertionPoint = 0;
188 nextOldCell = 0;
189 rowHadCells = false;
190 cellsOnCurrentRow = cellsIfEffect.toArray(EMPTY_CELL_ARRAY);
191 // the array should already be in the right order most of the time
192 Arrays.sort(cellsOnCurrentRow, HorizontalCellComparator.THE_INSTANCE);
193 }
194
195 }