001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: TableRowIterator.java 532780 2007-04-26 15:32:37Z vhennebert $ */
019:
020: package org.apache.fop.layoutmgr.table;
021:
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.ListIterator;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.fop.fo.flow.Marker;
029: import org.apache.fop.fo.flow.Table;
030: import org.apache.fop.fo.flow.TableBody;
031: import org.apache.fop.fo.flow.TableCell;
032: import org.apache.fop.fo.flow.TableColumn;
033: import org.apache.fop.fo.flow.TableRow;
034: import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
035:
036: /**
037: * Iterator that lets the table layout manager step over all the rows of a part of the
038: * table (table-header, table-footer or table-body).
039: * <p>Note: This class is not thread-safe.</p>
040: */
041: public class TableRowIterator {
042:
043: /** Selects the table-body elements for iteration. */
044: public static final int BODY = 0;
045: /** Selects the table-header elements for iteration. */
046: public static final int HEADER = 1;
047: /** Selects the table-footer elements for iteration. */
048: public static final int FOOTER = 2;
049:
050: /** Logger **/
051: private static Log log = LogFactory.getLog(TableRowIterator.class);
052:
053: /** The table on which this instance operates. */
054: protected Table table;
055: /** Column setup of the operated table. */
056: private ColumnSetup columns;
057:
058: /** Part of the table over which to iterate. One of BODY, HEADER or FOOTER. */
059: private int tablePart;
060:
061: /** Holds the currently fetched row (TableCell instances). */
062: private List currentRow = new java.util.ArrayList();
063:
064: /**
065: * Holds the grid units of cells from the previous row which will span over the
066: * current row. Should be read "previous row's spanning cells". List of GridUnit
067: * instances.
068: */
069: private List previousRowsSpanningCells = new java.util.ArrayList();
070:
071: /** Index of the row currently being fetched. */
072: private int fetchIndex = -1;
073:
074: /**
075: * Number of spans found on the current row which will also span over the next row.
076: */
077: private int pendingRowSpans;
078:
079: //TODO rows should later be a Jakarta Commons LinkedList so concurrent modifications while
080: //using a ListIterator are possible
081: /** List of cached rows. This is a list of EffRow elements. */
082: private List fetchedRows = new java.util.ArrayList();
083:
084: /**
085: * Index of the row that will be returned at the next iteration step. Note that there
086: * is no direct relation between this field and {@link
087: * TableRowIterator#fetchIndex}. The fetching of rows and the iterating over them are
088: * two different processes. Hence the two indices. */
089: private int iteratorIndex = 0;
090:
091: //prefetch state
092: /**
093: * Iterator over the requested table's part(s) (header, footer, body). Note that
094: * a table may have several table-body children, hence the iterator.
095: */
096: private ListIterator tablePartIterator = null;
097: /** Iterator over a part's child elements (either table-rows or table-cells). */
098: private ListIterator tablePartChildIterator = null;
099:
100: /**
101: * Creates a new TableRowIterator.
102: * @param table the table to iterate over
103: * @param columns the column setup for the table
104: * @param tablePart indicates what part of the table to iterate over (HEADER, FOOTER, BODY)
105: */
106: public TableRowIterator(Table table, ColumnSetup columns,
107: int tablePart) {
108: this .table = table;
109: this .columns = columns;
110: this .tablePart = tablePart;
111: switch (tablePart) {
112: case HEADER: {
113: List bodyList = new java.util.ArrayList();
114: bodyList.add(table.getTableHeader());
115: this .tablePartIterator = bodyList.listIterator();
116: break;
117: }
118: case FOOTER: {
119: List bodyList = new java.util.ArrayList();
120: bodyList.add(table.getTableFooter());
121: this .tablePartIterator = bodyList.listIterator();
122: break;
123: }
124: default: {
125: this .tablePartIterator = table.getChildNodes();
126: }
127: }
128: }
129:
130: /**
131: * Returns the next row group if any. A row group in this context is the minimum number of
132: * consecutive rows which contains all spanned grid units of its cells.
133: * @return the next row group, or null
134: */
135: public EffRow[] getNextRowGroup() {
136: EffRow firstRowInGroup = getNextRow();
137: if (firstRowInGroup == null) {
138: return null;
139: }
140: EffRow lastRowInGroup = firstRowInGroup;
141: int lastIndex = lastRowInGroup.getIndex();
142: boolean allFinished;
143: do {
144: allFinished = true;
145: Iterator iter = lastRowInGroup.getGridUnits().iterator();
146: while (iter.hasNext()) {
147: GridUnit gu = (GridUnit) iter.next();
148: if (!gu.isLastGridUnitRowSpan()) {
149: allFinished = false;
150: break;
151: }
152: }
153: lastIndex = lastRowInGroup.getIndex();
154: if (!allFinished) {
155: lastRowInGroup = getNextRow();
156: if (lastRowInGroup == null) {
157: allFinished = true;
158: }
159: }
160: } while (!allFinished);
161: int rowCount = lastIndex - firstRowInGroup.getIndex() + 1;
162: EffRow[] rowGroup = new EffRow[rowCount];
163: for (int i = 0; i < rowCount; i++) {
164: rowGroup[i] = getCachedRow(i + firstRowInGroup.getIndex());
165: }
166: return rowGroup;
167: }
168:
169: /**
170: * Returns the row at the given index, fetching rows up to the requested one if
171: * necessary.
172: *
173: * @return the requested row, or null if there is no row at the given index (index
174: * < 0 or end of table-part reached)
175: */
176: private EffRow getRow(int index) {
177: boolean moreRows = true;
178: while (moreRows && fetchedRows.size() <= index) {
179: moreRows = prefetchNext();
180: }
181: // Whatever the value of index, getCachedRow will handle it nicely
182: return getCachedRow(index);
183: }
184:
185: /**
186: * Returns the next effective row.
187: * @return the requested effective row or null if there is no more row.
188: */
189: private EffRow getNextRow() {
190: return getRow(iteratorIndex++);
191: }
192:
193: /**
194: * Returns the row preceding the given row, without moving the iterator.
195: *
196: * @param row a row in the iterated table part
197: * @return the preceding row, or null if there is no such row (the given row is the
198: * first one in the table part)
199: */
200: public EffRow getPrecedingRow(EffRow row) {
201: return getRow(row.getIndex() - 1);
202: }
203:
204: /**
205: * Returns the row following the given row, without moving the iterator.
206: *
207: * @param row a row in the iterated table part
208: * @return the following row, or null if there is no more row
209: */
210: public EffRow getFollowingRow(EffRow row) {
211: return getRow(row.getIndex() + 1);
212: }
213:
214: /**
215: * Returns the first effective row.
216: * @return the requested effective row.
217: */
218: public EffRow getFirstRow() {
219: if (fetchedRows.size() == 0) {
220: prefetchNext();
221: }
222: return getCachedRow(0);
223: }
224:
225: /**
226: * Returns the last effective row.
227: * <p>Note:This is inefficient for large tables because the whole table
228: * if preloaded.</p>
229: * @return the requested effective row.
230: */
231: public EffRow getLastRow() {
232: while (prefetchNext()) {
233: //nop
234: }
235: return getCachedRow(fetchedRows.size() - 1);
236: }
237:
238: /**
239: * Returns a cached effective row. If the given index points outside the range of rows
240: * (negative or greater than the number of already fetched rows), this methods
241: * terminates nicely by returning null.
242: *
243: * @param index index of the row (zero-based)
244: * @return the requested effective row or null if (index < 0 || index >= the
245: * number of already fetched rows)
246: */
247: private EffRow getCachedRow(int index) {
248: if (index < 0 || index >= fetchedRows.size()) {
249: return null;
250: } else {
251: return (EffRow) fetchedRows.get(index);
252: }
253: }
254:
255: /**
256: * Fetches the next row.
257: *
258: * @return true if there was a row to fetch; otherwise, false (the end of the
259: * table-part has been reached)
260: */
261: private boolean prefetchNext() {
262: boolean firstInTable = false;
263: boolean firstInTablePart = false;
264: // If we are at the end of the current table part
265: if (tablePartChildIterator != null
266: && !tablePartChildIterator.hasNext()) {
267: //force skip on to next component
268: if (pendingRowSpans > 0) {
269: this .currentRow.clear();
270: this .fetchIndex++;
271: EffRow gridUnits = buildGridRow(this .currentRow, null);
272: log.debug(gridUnits);
273: fetchedRows.add(gridUnits);
274: return true;
275: }
276: tablePartChildIterator = null;
277: if (fetchedRows.size() > 0) {
278: getCachedRow(fetchedRows.size() - 1)
279: .setFlagForAllGridUnits(GridUnit.LAST_IN_PART,
280: true);
281: }
282: }
283: // If the iterating over the current table-part has not started yet
284: if (tablePartChildIterator == null) {
285: if (tablePartIterator.hasNext()) {
286: tablePartChildIterator = ((TableBody) tablePartIterator
287: .next()).getChildNodes();
288: if (fetchedRows.size() == 0) {
289: firstInTable = true;
290: }
291: firstInTablePart = true;
292: } else {
293: //no more rows in that part of the table
294: if (fetchedRows.size() > 0) {
295: getCachedRow(fetchedRows.size() - 1)
296: .setFlagForAllGridUnits(
297: GridUnit.LAST_IN_PART, true);
298: // If the last row is the last of the table
299: if (tablePart == FOOTER
300: || (tablePart == BODY && table
301: .getTableFooter() == null)) {
302: getCachedRow(fetchedRows.size() - 1)
303: .setFlagForAllGridUnits(
304: GridUnit.LAST_IN_TABLE, true);
305: }
306: }
307: return false;
308: }
309: }
310: Object node = tablePartChildIterator.next();
311: while (node instanceof Marker) {
312: node = tablePartChildIterator.next();
313: }
314: this .currentRow.clear();
315: this .fetchIndex++;
316: TableRow rowFO = null;
317: if (node instanceof TableRow) {
318: rowFO = (TableRow) node;
319: ListIterator cellIterator = rowFO.getChildNodes();
320: while (cellIterator.hasNext()) {
321: this .currentRow.add(cellIterator.next());
322: }
323: } else if (node instanceof TableCell) {
324: this .currentRow.add(node);
325: if (!((TableCell) node).endsRow()) {
326: while (tablePartChildIterator.hasNext()) {
327: TableCell cell = (TableCell) tablePartChildIterator
328: .next();
329: if (cell.startsRow()) {
330: //next row already starts here, one step back
331: tablePartChildIterator.previous();
332: break;
333: }
334: this .currentRow.add(cell);
335: if (cell.endsRow()) {
336: break;
337: }
338: }
339: }
340: } else {
341: throw new IllegalStateException("Illegal class found: "
342: + node.getClass().getName());
343: }
344: EffRow gridUnits = buildGridRow(this .currentRow, rowFO);
345: if (firstInTablePart) {
346: gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_PART,
347: true);
348: }
349: if (firstInTable
350: && (tablePart == HEADER || table.getTableHeader() == null)
351: && tablePart != FOOTER) {
352: gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_TABLE,
353: true);
354: }
355: log.debug(gridUnits);
356: fetchedRows.add(gridUnits);
357: return true;
358: }
359:
360: /**
361: * Places the given object at the given position in the list, first extending it if
362: * necessary with null objects to reach the position.
363: *
364: * @param list the list in which to place the object
365: * @param position index at which the object must be placed (0-based)
366: * @param obj the object to place
367: */
368: private void safelySetListItem(List list, int position, Object obj) {
369: while (position >= list.size()) {
370: list.add(null);
371: }
372: list.set(position, obj);
373: }
374:
375: private Object safelyGetListItem(List list, int position) {
376: if (position >= list.size()) {
377: return null;
378: } else {
379: return list.get(position);
380: }
381: }
382:
383: /**
384: * Builds the list of grid units corresponding to the given table row.
385: *
386: * @param cells list of cells starting at the current row
387: * @param rowFO the fo:table-row object containing the row, possibly null
388: * @return the list of grid units
389: */
390: private EffRow buildGridRow(List cells, TableRow rowFO) {
391: EffRow row = new EffRow(this .fetchIndex, tablePart);
392: List gridUnits = row.getGridUnits();
393:
394: TableBody bodyFO = null;
395:
396: //Create all row-spanned grid units based on information from the previous row
397: int colnum = 1;
398: GridUnit[] horzSpan = null; // Grid units horizontally spanned by a single cell
399: if (pendingRowSpans > 0) {
400: ListIterator spanIter = previousRowsSpanningCells
401: .listIterator();
402: while (spanIter.hasNext()) {
403: GridUnit gu = (GridUnit) spanIter.next();
404: if (gu != null) {
405: if (gu.getColSpanIndex() == 0) {
406: horzSpan = new GridUnit[gu.getCell()
407: .getNumberColumnsSpanned()];
408: }
409: GridUnit newGU = gu.createNextRowSpanningGridUnit();
410: newGU.setRow(rowFO);
411: safelySetListItem(gridUnits, colnum - 1, newGU);
412: horzSpan[newGU.getColSpanIndex()] = newGU;
413: if (newGU.isLastGridUnitColSpan()) {
414: //Add the array of row-spanned grid units to the primary grid unit
415: newGU.getPrimary().addRow(horzSpan);
416: horzSpan = null;
417: }
418: if (newGU.isLastGridUnitRowSpan()) {
419: spanIter.set(null);
420: pendingRowSpans--;
421: } else {
422: spanIter.set(newGU);
423: }
424: }
425: colnum++;
426: }
427: }
428: if (pendingRowSpans < 0) {
429: throw new IllegalStateException(
430: "pendingRowSpans must not become negative!");
431: }
432:
433: //Transfer available cells to their slots
434: colnum = 1;
435: ListIterator iter = cells.listIterator();
436: while (iter.hasNext()) {
437: TableCell cell = (TableCell) iter.next();
438:
439: colnum = cell.getColumnNumber();
440:
441: //TODO: remove the check below???
442: //shouldn't happen here, since
443: //overlapping cells already caught in
444: //fo.flow.TableCell.bind()...
445: GridUnit other = (GridUnit) safelyGetListItem(gridUnits,
446: colnum - 1);
447: if (other != null) {
448: String err = "A table-cell (" + cell.getContextInfo()
449: + ") is overlapping with another ("
450: + other.getCell().getContextInfo()
451: + ") in column " + colnum;
452: throw new IllegalStateException(
453: err
454: + " (this should have been catched by FO tree validation)");
455: }
456: TableColumn col = columns.getColumn(colnum);
457:
458: //Add grid unit for primary grid unit
459: PrimaryGridUnit gu = new PrimaryGridUnit(cell, col,
460: colnum - 1, this .fetchIndex);
461: safelySetListItem(gridUnits, colnum - 1, gu);
462: boolean hasRowSpanningLeft = !gu.isLastGridUnitRowSpan();
463: if (hasRowSpanningLeft) {
464: pendingRowSpans++;
465: safelySetListItem(previousRowsSpanningCells,
466: colnum - 1, gu);
467: }
468:
469: if (gu.hasSpanning()) {
470: //Add grid units on spanned slots if any
471: horzSpan = new GridUnit[cell.getNumberColumnsSpanned()];
472: horzSpan[0] = gu;
473: for (int j = 1; j < cell.getNumberColumnsSpanned(); j++) {
474: colnum++;
475: GridUnit guSpan = new GridUnit(gu, columns
476: .getColumn(colnum), colnum - 1, j);
477: //TODO: remove the check below???
478: other = (GridUnit) safelyGetListItem(gridUnits,
479: colnum - 1);
480: if (other != null) {
481: String err = "A table-cell ("
482: + cell.getContextInfo()
483: + ") is overlapping with another ("
484: + other.getCell().getContextInfo()
485: + ") in column " + colnum;
486: throw new IllegalStateException(
487: err
488: + " (this should have been catched by FO tree validation)");
489: }
490: safelySetListItem(gridUnits, colnum - 1, guSpan);
491: if (hasRowSpanningLeft) {
492: pendingRowSpans++;
493: safelySetListItem(previousRowsSpanningCells,
494: colnum - 1, guSpan);
495: }
496: horzSpan[j] = guSpan;
497: }
498: gu.addRow(horzSpan);
499: }
500:
501: //Gather info for empty grid units (used later)
502: if (bodyFO == null) {
503: bodyFO = gu.getBody();
504: }
505:
506: colnum++;
507: }
508:
509: //Post-processing the list (looking for gaps and resolve start and end borders)
510: fillEmptyGridUnits(gridUnits, rowFO, bodyFO);
511: resolveStartEndBorders(gridUnits);
512:
513: return row;
514: }
515:
516: private void fillEmptyGridUnits(List gridUnits, TableRow row,
517: TableBody body) {
518: for (int pos = 1; pos <= gridUnits.size(); pos++) {
519: GridUnit gu = (GridUnit) gridUnits.get(pos - 1);
520:
521: //Empty grid units
522: if (gu == null) {
523: //Add grid unit
524: gu = new EmptyGridUnit(row, columns.getColumn(pos),
525: body, pos - 1);
526: gridUnits.set(pos - 1, gu);
527: }
528:
529: //Set flags
530: gu.setFlag(GridUnit.IN_FIRST_COLUMN, (pos == 1));
531: gu.setFlag(GridUnit.IN_LAST_COLUMN, (pos == gridUnits
532: .size()));
533: }
534: }
535:
536: private void resolveStartEndBorders(List gridUnits) {
537: for (int pos = 1; pos <= gridUnits.size(); pos++) {
538: GridUnit starting = (GridUnit) gridUnits.get(pos - 1);
539:
540: //Border resolution
541: if (table.isSeparateBorderModel()) {
542: starting.assignBorderForSeparateBorderModel();
543: } else {
544: //Neighbouring grid unit at start edge
545: GridUnit start = null;
546: int find = pos - 1;
547: while (find >= 1) {
548: GridUnit candidate = (GridUnit) gridUnits
549: .get(find - 1);
550: if (candidate.isLastGridUnitColSpan()) {
551: start = candidate;
552: break;
553: }
554: find--;
555: }
556:
557: //Ending grid unit for current cell
558: GridUnit ending = null;
559: if (starting.getCell() != null) {
560: pos += starting.getCell().getNumberColumnsSpanned() - 1;
561: }
562: ending = (GridUnit) gridUnits.get(pos - 1);
563:
564: //Neighbouring grid unit at end edge
565: GridUnit end = null;
566: find = pos + 1;
567: while (find <= gridUnits.size()) {
568: GridUnit candidate = (GridUnit) gridUnits
569: .get(find - 1);
570: if (candidate.isPrimary()) {
571: end = candidate;
572: break;
573: }
574: find++;
575: }
576: starting.resolveBorder(start,
577: CommonBorderPaddingBackground.START);
578: ending.resolveBorder(end,
579: CommonBorderPaddingBackground.END);
580: //Only start and end borders here, before and after during layout
581: }
582: }
583: }
584:
585: }
|