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 fi.iki.hsivonen.xml.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,v 1.7 2006/12/01 12:34:30 hsivonen Exp $
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    }