KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > fop > layoutmgr > table > TableRowIterator


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 /* $Id: TableRowIterator.java 478928 2006-11-24 17:32:48Z vhennebert $ */
19
20 package org.apache.fop.layoutmgr.table;
21
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.ListIterator JavaDoc;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.fop.fo.flow.Marker;
29 import org.apache.fop.fo.flow.Table;
30 import org.apache.fop.fo.flow.TableBody;
31 import org.apache.fop.fo.flow.TableCell;
32 import org.apache.fop.fo.flow.TableColumn;
33 import org.apache.fop.fo.flow.TableRow;
34 import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
35
36
37 /**
38  * Iterator that lets the table layout manager step over all of the rows of a part of the
39  * table (table-header, table-footer or table-body).
40  * <p>Note: This class is not thread-safe.</p>
41  */

42 public class TableRowIterator {
43
44     /** Selects the table-body elements for iteration. */
45     public static final int BODY = 0;
46     /** Selects the table-header elements for iteration. */
47     public static final int HEADER = 1;
48     /** Selects the table-footer elements for iteration. */
49     public static final int FOOTER = 2;
50
51     /** Logger **/
52     private static Log log = LogFactory.getLog(TableRowIterator.class);
53
54     /** The table on which this instance operates. */
55     protected Table table;
56     /** Column setup of the operated table. */
57     private ColumnSetup columns;
58
59     /** Part of the table over which to iterate. One of BODY, HEADER or FOOTER. */
60     private int tablePart;
61
62     /** Holds the currently fetched row (TableCell instances). */
63     private List JavaDoc currentRow = new java.util.ArrayList JavaDoc();
64
65     /**
66      * Holds the grid units of cells from the previous row which will span over the
67      * current row. Should be read "previous row's spanning cells". List of GridUnit
68      * instances.
69      */

70     private List JavaDoc previousRowsSpanningCells = new java.util.ArrayList JavaDoc();
71
72     /** Index of the row currently being fetched. */
73     private int fetchIndex = -1;
74
75     /** Spans found on the current row which will also span over the next row. */
76     private int pendingRowSpans;
77
78     //TODO rows should later be a Jakarta Commons LinkedList so concurrent modifications while
79
//using a ListIterator are possible
80
/** List of cached rows. This a list of EffRow elements. */
81     private List JavaDoc fetchedRows = new java.util.ArrayList JavaDoc();
82
83     /**
84      * Index of the row that will be returned at the next iteration step. Note that there
85      * is no direct relation between this field and {@link
86      * TableRowIterator#fetchIndex}. The fetching of rows and the iterating over them are
87      * two different processes. Hence the two indices. */

88     private int iteratorIndex = 0;
89
90     //prefetch state
91
/**
92      * Iterator over the requested table's part(s) (header, footer, body). Note that
93      * a table may have several table-body children, hence the iterator.
94      */

95     private ListIterator JavaDoc tablePartIterator = null;
96     /** Iterator over a part's child elements (either table-rows or table-cells). */
97     private ListIterator JavaDoc tablePartChildIterator = null;
98
99     /**
100      * Creates a new TableRowIterator.
101      * @param table the table to iterate over
102      * @param columns the column setup for the table
103      * @param tablePart indicates what part of the table to iterate over (HEADER, FOOTER, BODY)
104      */

105     public TableRowIterator(Table table, ColumnSetup columns, int tablePart) {
106         this.table = table;
107         this.columns = columns;
108         this.tablePart = tablePart;
109         switch(tablePart) {
110             case HEADER: {
111                 List JavaDoc bodyList = new java.util.ArrayList JavaDoc();
112                 bodyList.add(table.getTableHeader());
113                 this.tablePartIterator = bodyList.listIterator();
114                 break;
115             }
116             case FOOTER: {
117                 List JavaDoc bodyList = new java.util.ArrayList JavaDoc();
118                 bodyList.add(table.getTableFooter());
119                 this.tablePartIterator = bodyList.listIterator();
120                 break;
121             }
122             default: {
123                 this.tablePartIterator = table.getChildNodes();
124             }
125         }
126     }
127
128     /**
129      * Preloads the whole table.
130      * <p>Note:This is inefficient for large tables.</p>
131      */

132     public void prefetchAll() {
133         while (prefetchNext()) {
134             log.trace("found row...");
135         }
136     }
137
138     /**
139      * Returns the next row group if any. A row group in this context is the minimum number of
140      * consecutive rows which contains all spanned grid units of its cells.
141      * @return the next row group, or null
142      */

143     public EffRow[] getNextRowGroup() {
144         EffRow firstRowInGroup = getNextRow();
145         if (firstRowInGroup == null) {
146             return null;
147         }
148         EffRow lastRowInGroup = firstRowInGroup;
149         int lastIndex = lastRowInGroup.getIndex();
150         boolean allFinished;
151         do {
152             allFinished = true;
153             Iterator JavaDoc iter = lastRowInGroup.getGridUnits().iterator();
154             while (iter.hasNext()) {
155                 GridUnit gu = (GridUnit)iter.next();
156                 if (!gu.isLastGridUnitRowSpan()) {
157                     allFinished = false;
158                     break;
159                 }
160             }
161             lastIndex = lastRowInGroup.getIndex();
162             if (!allFinished) {
163                 lastRowInGroup = getNextRow();
164                 if (lastRowInGroup == null) {
165                     allFinished = true;
166                 }
167             }
168         } while (!allFinished);
169         int rowCount = lastIndex - firstRowInGroup.getIndex() + 1;
170         EffRow[] rowGroup = new EffRow[rowCount];
171         for (int i = 0; i < rowCount; i++) {
172             rowGroup[i] = getCachedRow(i + firstRowInGroup.getIndex());
173         }
174         return rowGroup;
175     }
176
177     /**
178      * Returns the row at the given index, fetching rows up to the requested one if
179      * necessary.
180      *
181      * @return the requested row, or null if there is no row at the given index (index
182      * &lt; 0 or end of table-part reached)
183      */

184     private EffRow getRow(int index) {
185         boolean moreRows = true;
186         while (moreRows && fetchedRows.size() <= index) {
187             moreRows = prefetchNext();
188         }
189         // Whatever the value of index, getCachedRow will handle it nicely
190
return getCachedRow(index);
191     }
192
193     /**
194      * Returns the next effective row.
195      * @return the requested effective row or null if there is no more row.
196      */

197     private EffRow getNextRow() {
198         return getRow(iteratorIndex++);
199     }
200
201     /**
202      * Returns the row preceding the given row, without moving the iterator.
203      *
204      * @param row a row in the iterated table part
205      * @return the preceding row, or null if there is no such row (the given row is the
206      * first one in the table part)
207      */

208     public EffRow getPrecedingRow(EffRow row) {
209         return getRow(row.getIndex() - 1);
210     }
211
212     /**
213      * Returns the row following the given row, without moving the iterator.
214      *
215      * @param row a row in the iterated table part
216      * @return the following row, or null if there is no more row
217      */

218     public EffRow getFollowingRow(EffRow row) {
219         return getRow(row.getIndex() + 1);
220     }
221
222     /**
223      * Sets the iterator to the previous row.
224      */

225     public void backToPreviousRow() {
226         iteratorIndex--;
227     }
228
229     /**
230      * Returns the first effective row.
231      * @return the requested effective row.
232      */

233     public EffRow getFirstRow() {
234         if (fetchedRows.size() == 0) {
235             prefetchNext();
236         }
237         return getCachedRow(0);
238     }
239
240     /**
241      * Returns the last effective row.
242      * <p>Note:This is inefficient for large tables because the whole table
243      * if preloaded.</p>
244      * @return the requested effective row.
245      */

246     public EffRow getLastRow() {
247         while (prefetchNext()) {
248             //nop
249
}
250         return getCachedRow(fetchedRows.size() - 1);
251     }
252
253     /**
254      * Returns a cached effective row. If the given index points outside the range of rows
255      * (negative or greater than the number of already fetched rows), this methods
256      * terminates nicely by returning null.
257      *
258      * @param index index of the row (zero-based)
259      * @return the requested effective row or null if (index &lt; 0 || index &gt;= the
260      * number of already fetched rows)
261      */

262     public EffRow getCachedRow(int index) {
263         if (index < 0 || index >= fetchedRows.size()) {
264             return null;
265         } else {
266             return (EffRow)fetchedRows.get(index);
267         }
268     }
269
270     /**
271      * Fetches the next row.
272      *
273      * @return true if there was a row to fetch; otherwise, false (the end of the
274      * table-part has been reached)
275      */

276     private boolean prefetchNext() {
277         boolean firstInTable = false;
278         boolean firstInTablePart = false;
279         // If we are at the end of the current table part
280
if (tablePartChildIterator != null && !tablePartChildIterator.hasNext()) {
281             //force skip on to next component
282
if (pendingRowSpans > 0) {
283                 this.currentRow.clear();
284                 this.fetchIndex++;
285                 EffRow gridUnits = buildGridRow(this.currentRow, null);
286                 log.debug(gridUnits);
287                 fetchedRows.add(gridUnits);
288                 return true;
289             }
290             tablePartChildIterator = null;
291             if (fetchedRows.size() > 0) {
292                 getCachedRow(fetchedRows.size() - 1).setFlagForAllGridUnits(
293                         GridUnit.LAST_IN_PART, true);
294             }
295         }
296         // If the iterating over the current table-part has not started yet
297
if (tablePartChildIterator == null) {
298             if (tablePartIterator.hasNext()) {
299                 tablePartChildIterator = ((TableBody)tablePartIterator.next()).getChildNodes();
300                 if (fetchedRows.size() == 0) {
301                     firstInTable = true;
302                 }
303                 firstInTablePart = true;
304             } else {
305                 //no more rows in that part of the table
306
if (fetchedRows.size() > 0) {
307                     getCachedRow(fetchedRows.size() - 1).setFlagForAllGridUnits(
308                             GridUnit.LAST_IN_PART, true);
309                     // If the last row is the last of the table
310
if (tablePart == FOOTER
311                             || (tablePart == BODY && table.getTableFooter() == null)) {
312                         getCachedRow(fetchedRows.size() - 1).setFlagForAllGridUnits(
313                                 GridUnit.LAST_IN_TABLE, true);
314                     }
315                 }
316                 return false;
317             }
318         }
319         Object JavaDoc node = tablePartChildIterator.next();
320         while (node instanceof Marker) {
321             node = tablePartChildIterator.next();
322         }
323         this.currentRow.clear();
324         this.fetchIndex++;
325         TableRow rowFO = null;
326         if (node instanceof TableRow) {
327             rowFO = (TableRow)node;
328             ListIterator JavaDoc cellIterator = rowFO.getChildNodes();
329             while (cellIterator.hasNext()) {
330                 this.currentRow.add(cellIterator.next());
331             }
332         } else if (node instanceof TableCell) {
333             this.currentRow.add(node);
334             if (!((TableCell)node).endsRow()) {
335                 while (tablePartChildIterator.hasNext()) {
336                     TableCell cell = (TableCell)tablePartChildIterator.next();
337                     if (cell.startsRow()) {
338                         //next row already starts here, one step back
339
tablePartChildIterator.previous();
340                         break;
341                     }
342                     this.currentRow.add(cell);
343                     if (cell.endsRow()) {
344                         break;
345                     }
346                 }
347             }
348         } else {
349             throw new IllegalStateException JavaDoc("Illegal class found: " + node.getClass().getName());
350         }
351         EffRow gridUnits = buildGridRow(this.currentRow, rowFO);
352         if (firstInTablePart) {
353             gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_PART, true);
354         }
355         if (firstInTable && (tablePart == HEADER || table.getTableHeader() == null)
356                 && tablePart != FOOTER) {
357             gridUnits.setFlagForAllGridUnits(GridUnit.FIRST_IN_TABLE, true);
358         }
359         log.debug(gridUnits);
360         fetchedRows.add(gridUnits);
361         return true;
362     }
363
364     /**
365      * Places the given object at the given position in the list, first extending it if
366      * necessary with null objects to reach the position.
367      *
368      * @param list the list in which to place the object
369      * @param position index at which the object must be placed (0-based)
370      * @param obj the object to place
371      */

372     private void safelySetListItem(List JavaDoc list, int position, Object JavaDoc obj) {
373         while (position >= list.size()) {
374             list.add(null);
375         }
376         list.set(position, obj);
377     }
378
379     private Object JavaDoc safelyGetListItem(List JavaDoc list, int position) {
380         if (position >= list.size()) {
381             return null;
382         } else {
383             return list.get(position);
384         }
385     }
386
387     /**
388      * Builds the list of grid units corresponding to the given table row.
389      *
390      * @param cells list of cells belonging to the row
391      * @param rowFO the fo:table-row object containing the row, possibly null
392      * @return the list of grid units
393      */

394     private EffRow buildGridRow(List JavaDoc cells, TableRow rowFO) {
395         EffRow row = new EffRow(this.fetchIndex, tablePart);
396         List JavaDoc gridUnits = row.getGridUnits();
397
398         TableBody bodyFO = null;
399
400         //Create all row-spanned grid units based on information from the previous row
401
int colnum = 1;
402         GridUnit[] horzSpan = null; // Grid units horizontally spanned by a single cell
403
if (pendingRowSpans > 0) {
404             ListIterator JavaDoc spanIter = previousRowsSpanningCells.listIterator();
405             while (spanIter.hasNext()) {
406                 GridUnit gu = (GridUnit)spanIter.next();
407                 if (gu != null) {
408                     if (gu.getColSpanIndex() == 0) {
409                         horzSpan = new GridUnit[gu.getCell().getNumberColumnsSpanned()];
410                     }
411                     GridUnit newGU = gu.createNextRowSpanningGridUnit();
412                     newGU.setRow(rowFO);
413                     safelySetListItem(gridUnits, colnum - 1, newGU);
414                     horzSpan[newGU.getColSpanIndex()] = newGU;
415                     if (newGU.isLastGridUnitColSpan()) {
416                         //Add the array of row-spanned grid units to the primary grid unit
417
newGU.getPrimary().addRow(horzSpan);
418                         horzSpan = null;
419                     }
420                     if (newGU.isLastGridUnitRowSpan()) {
421                         spanIter.set(null);
422                         pendingRowSpans--;
423                     } else {
424                         spanIter.set(newGU);
425                     }
426                 }
427                 colnum++;
428             }
429         }
430         if (pendingRowSpans < 0) {
431             throw new IllegalStateException JavaDoc("pendingRowSpans must not become negative!");
432         }
433
434         //Transfer available cells to their slots
435
colnum = 1;
436         ListIterator JavaDoc iter = cells.listIterator();
437         while (iter.hasNext()) {
438             TableCell cell = (TableCell)iter.next();
439
440             colnum = cell.getColumnNumber();
441
442             //TODO: remove the check below???
443
//shouldn't happen here, since
444
//overlapping cells already caught in
445
//fo.flow.TableCell.bind()...
446
GridUnit other = (GridUnit)safelyGetListItem(gridUnits, colnum - 1);
447             if (other != null) {
448                 String JavaDoc err = "A table-cell ("
449                         + cell.getContextInfo()
450                         + ") is overlapping with another ("
451                         + other.getCell().getContextInfo()
452                         + ") in column " + colnum;
453                 throw new IllegalStateException JavaDoc(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, colnum - 1, this.fetchIndex);
460             safelySetListItem(gridUnits, colnum - 1, gu);
461             boolean hasRowSpanningLeft = !gu.isLastGridUnitRowSpan();
462             if (hasRowSpanningLeft) {
463                 pendingRowSpans++;
464                 safelySetListItem(previousRowsSpanningCells, colnum - 1, gu);
465             }
466
467             if (gu.hasSpanning()) {
468                 //Add grid units on spanned slots if any
469
horzSpan = new GridUnit[cell.getNumberColumnsSpanned()];
470                 horzSpan[0] = gu;
471                 for (int j = 1; j < cell.getNumberColumnsSpanned(); j++) {
472                     colnum++;
473                     GridUnit guSpan = new GridUnit(gu, columns.getColumn(colnum), colnum - 1, j);
474                     //TODO: remove the check below???
475
other = (GridUnit)safelyGetListItem(gridUnits, colnum - 1);
476                     if (other != null) {
477                         String JavaDoc err = "A table-cell ("
478                             + cell.getContextInfo()
479                             + ") is overlapping with another ("
480                             + other.getCell().getContextInfo()
481                             + ") in column " + colnum;
482                         throw new IllegalStateException JavaDoc(err
483                             + " (this should have been catched by FO tree validation)");
484                     }
485                     safelySetListItem(gridUnits, colnum - 1, guSpan);
486                     if (hasRowSpanningLeft) {
487                         pendingRowSpans++;
488                         safelySetListItem(previousRowsSpanningCells, colnum - 1, gu);
489                     }
490                     horzSpan[j] = guSpan;
491                 }
492                 gu.addRow(horzSpan);
493             }
494
495             //Gather info for empty grid units (used later)
496
if (bodyFO == null) {
497                 bodyFO = gu.getBody();
498             }
499
500             colnum++;
501         }
502
503         //Post-processing the list (looking for gaps and resolve start and end borders)
504
fillEmptyGridUnits(gridUnits, rowFO, bodyFO);
505         resolveStartEndBorders(gridUnits);
506
507         return row;
508     }
509
510     private void fillEmptyGridUnits(List JavaDoc gridUnits, TableRow row, TableBody body) {
511         for (int pos = 1; pos <= gridUnits.size(); pos++) {
512             GridUnit gu = (GridUnit)gridUnits.get(pos - 1);
513
514             //Empty grid units
515
if (gu == null) {
516                 //Add grid unit
517
gu = new EmptyGridUnit(row, columns.getColumn(pos), body,
518                         pos - 1);
519                 gridUnits.set(pos - 1, gu);
520             }
521
522             //Set flags
523
gu.setFlag(GridUnit.IN_FIRST_COLUMN, (pos == 1));
524             gu.setFlag(GridUnit.IN_LAST_COLUMN, (pos == gridUnits.size()));
525         }
526     }
527
528     private void resolveStartEndBorders(List JavaDoc gridUnits) {
529         for (int pos = 1; pos <= gridUnits.size(); pos++) {
530             GridUnit starting = (GridUnit)gridUnits.get(pos - 1);
531
532             //Border resolution
533
if (table.isSeparateBorderModel()) {
534                 starting.assignBorderForSeparateBorderModel();
535             } else {
536                 //Neighbouring grid unit at start edge
537
GridUnit start = null;
538                 int find = pos - 1;
539                 while (find >= 1) {
540                     GridUnit candidate = (GridUnit)gridUnits.get(find - 1);
541                     if (candidate.isLastGridUnitColSpan()) {
542                         start = candidate;
543                         break;
544                     }
545                     find--;
546                 }
547
548                 //Ending grid unit for current cell
549
GridUnit ending = null;
550                 if (starting.getCell() != null) {
551                     pos += starting.getCell().getNumberColumnsSpanned() - 1;
552                 }
553                 ending = (GridUnit)gridUnits.get(pos - 1);
554
555                 //Neighbouring grid unit at end edge
556
GridUnit end = null;
557                 find = pos + 1;
558                 while (find <= gridUnits.size()) {
559                     GridUnit candidate = (GridUnit)gridUnits.get(find - 1);
560                     if (candidate.isPrimary()) {
561                         end = candidate;
562                         break;
563                     }
564                     find++;
565                 }
566                 starting.resolveBorder(start,
567                         CommonBorderPaddingBackground.START);
568                 ending.resolveBorder(end,
569                         CommonBorderPaddingBackground.END);
570                 //Only start and end borders here, before and after during layout
571
}
572         }
573     }
574
575 }
576
Popular Tags