001: /**
002: * ===========================================
003: * JFreeReport : a free Java reporting library
004: * ===========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
009: *
010: * This library is free software; you can redistribute it and/or modify it under the terms
011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
012: * either version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: * See the GNU Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public License along with this
019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020: * Boston, MA 02111-1307, USA.
021: *
022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023: * in the United States and other countries.]
024: *
025: * ------------
026: * TableContentProducer.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.modules.output.table.base;
030:
031: import org.jfree.report.layout.model.BlockRenderBox;
032: import org.jfree.report.layout.model.CanvasRenderBox;
033: import org.jfree.report.layout.model.InlineRenderBox;
034: import org.jfree.report.layout.model.LogicalPageBox;
035: import org.jfree.report.layout.model.ParagraphRenderBox;
036: import org.jfree.report.layout.model.RenderBox;
037: import org.jfree.report.layout.output.OutputProcessorFeature;
038: import org.jfree.report.layout.output.OutputProcessorMetaData;
039: import org.jfree.report.layout.process.IterateStructuralProcessStep;
040: import org.jfree.report.layout.process.ProcessUtility;
041: import org.jfree.report.style.BandStyleKeys;
042: import org.jfree.util.Configuration;
043: import org.jfree.util.Log;
044:
045: /**
046: * After the pagination was able to deriveForAdvance the table-structure (all column and row-breaks are now known), this
047: * second step flattens the layout-tree into a two-dimensional table structure.
048: *
049: * @author Thomas Morgner
050: */
051: public class TableContentProducer extends IterateStructuralProcessStep {
052: private SheetLayout sheetLayout;
053: private GenericObjectTable contentBackend;
054:
055: private long maximumHeight;
056: private long maximumWidth;
057:
058: private TableRectangle lookupRectangle;
059: private long pageOffset;
060: private long pageEnd;
061: private String sheetName;
062: private boolean iterativeUpdate;
063: // private boolean performOutput;
064: private int finishedRows;
065: private int filledRows;
066: private long contentOffset;
067: private long effectiveOffset;
068: private boolean unalignedPagebands;
069: private boolean headerProcessed;
070: private boolean ellipseAsBackground;
071: private boolean shapesAsContent;
072:
073: private boolean verboseCellMarkers;
074: private boolean debugReportLayout;
075: private boolean reportCellConflicts;
076:
077: public TableContentProducer(final SheetLayout sheetLayout,
078: final OutputProcessorMetaData metaData) {
079: this .unalignedPagebands = metaData
080: .isFeatureSupported(OutputProcessorFeature.UNALIGNED_PAGEBANDS);
081: this .shapesAsContent = metaData
082: .isFeatureSupported(AbstractTableOutputProcessor.SHAPES_CONTENT);
083: this .ellipseAsBackground = metaData
084: .isFeatureSupported(AbstractTableOutputProcessor.TREAT_ELLIPSE_AS_RECTANGLE);
085: this .sheetLayout = sheetLayout;
086: this .maximumHeight = sheetLayout.getMaxHeight();
087: this .maximumWidth = sheetLayout.getMaxWidth();
088: this .contentBackend = new GenericObjectTable();
089: this .contentBackend.ensureCapacity(sheetLayout.getRowCount(),
090: sheetLayout.getColumnCount());
091:
092: final Configuration config = metaData.getConfiguration();
093: this .debugReportLayout = "true"
094: .equals(config
095: .getConfigProperty("org.jfree.report.modules.output.table.base.DebugReportLayout"));
096: this .verboseCellMarkers = "true"
097: .equals(config
098: .getConfigProperty("org.jfree.report.modules.output.table.base.VerboseCellMarkers"));
099: this .reportCellConflicts = "true"
100: .equals(config
101: .getConfigProperty("org.jfree.report.modules.output.table.base.ReportCellConflicts"));
102: }
103:
104: public String getSheetName() {
105: return sheetName;
106: }
107:
108: public void compute(final LogicalPageBox logicalPage,
109: final boolean iterativeUpdate, final boolean performOutput) {
110: this .iterativeUpdate = iterativeUpdate;
111:
112: // this.performOutput = performOutput;
113: this .sheetName = null;
114: if (unalignedPagebands == false) {
115: // The page-header and footer area are aligned/shifted within the logical pagebox so that all areas
116: // share a common coordinate system. This also implies, that the whole logical page is aligned content.
117: pageOffset = 0;
118: pageEnd = logicalPage.getPageEnd()
119: - logicalPage.getPageOffset();
120: effectiveOffset = 0;
121: //Log.debug ("Content Processing " + pageOffset + " -> " + pageEnd);
122: if (startBlockBox(logicalPage)) {
123: if (headerProcessed == false) {
124: startProcessing(logicalPage.getWatermarkArea());
125: final BlockRenderBox headerArea = logicalPage
126: .getHeaderArea();
127: startProcessing(headerArea);
128: headerProcessed = true;
129: }
130:
131: processBoxChilds(logicalPage);
132: if (iterativeUpdate == false) {
133: startProcessing(logicalPage.getFooterArea());
134: }
135: }
136: finishBlockBox(logicalPage);
137: //ModelPrinter.print(logicalPage);
138: } else {
139: // The page-header and footer area are not aligned/shifted within the logical pagebox.
140: // All areas have their own coordinate system starting at (0,0). We apply a manual shift here
141: // so that we dont have to modify the nodes (which invalidates the cache, and therefore is ugly)
142:
143: //Log.debug ("Content Processing " + pageOffset + " -> " + pageEnd);
144: effectiveOffset = 0;
145: pageOffset = 0;
146: pageEnd = logicalPage.getPageEnd();
147: if (startBlockBox(logicalPage)) {
148: if (headerProcessed == false) {
149: contentOffset = 0;
150: effectiveOffset = 0;
151:
152: final BlockRenderBox watermarkArea = logicalPage
153: .getWatermarkArea();
154: pageEnd = watermarkArea.getHeight();
155: startProcessing(watermarkArea);
156:
157: final BlockRenderBox headerArea = logicalPage
158: .getHeaderArea();
159: pageEnd = headerArea.getHeight();
160: startProcessing(headerArea);
161: contentOffset = headerArea.getHeight();
162: headerProcessed = true;
163: }
164:
165: pageOffset = logicalPage.getPageOffset();
166: pageEnd = logicalPage.getPageEnd();
167: effectiveOffset = contentOffset;
168: processBoxChilds(logicalPage);
169:
170: if (iterativeUpdate == false) {
171: pageOffset = 0;
172: final BlockRenderBox footerArea = logicalPage
173: .getFooterArea();
174: final long footerOffset = contentOffset
175: + (logicalPage.getPageEnd() - logicalPage
176: .getPageOffset());
177: pageEnd = footerOffset + footerArea.getHeight();
178: effectiveOffset = footerOffset;
179: startProcessing(footerArea);
180: }
181: }
182: finishBlockBox(logicalPage);
183: //ModelPrinter.print(logicalPage);
184: }
185:
186: if (iterativeUpdate) {
187: // Log.debug("iterative: Computing commited rows: " + sheetLayout.getRowCount() + " vs. " + contentBackend.getRowCount());
188: updateFilledRows();
189: } else {
190: // Log.debug("Non-iterative: Assuming all rows are commited: " + sheetLayout.getRowCount() + " vs. " + contentBackend.getRowCount());
191: // updateFilledRows();
192: filledRows = getRowCount();
193: }
194:
195: if (iterativeUpdate == false) {
196: headerProcessed = false;
197: }
198:
199: // if (contentBackend.getRowCount() > 10000)
200: // {
201: int counter = 0;
202: //final int rowCount = contentBackend.getRowCount();
203: final int columnCount = contentBackend.getColumnCount();
204: for (int r = 0; r < finishedRows; r++) {
205: for (int c = 0; c < columnCount; c++) {
206: final Object o = contentBackend.getObject(r, c);
207: if (o instanceof ContentMarker) {
208: counter += 1;
209: }
210: }
211: }
212: if (counter > 0) {
213: Log.debug("Counter: " + finishedRows + " -> " + counter);
214: }
215: // }
216: }
217:
218: public TableCellDefinition getBackground(final int row,
219: final int column) {
220: return sheetLayout.getBackgroundAt(row, column);
221: }
222:
223: public RenderBox getContent(final int row, final int column) {
224: final CellMarker marker = (CellMarker) contentBackend
225: .getObject(row, column);
226: if (marker == null) {
227: return null;
228: }
229: return marker.getContent();
230: }
231:
232: public long getContentOffset(final int row, final int column) {
233: final CellMarker marker = (CellMarker) contentBackend
234: .getObject(row, column);
235: if (marker == null) {
236: return 0;
237: }
238: return marker.getContentOffset();
239: }
240:
241: public int getRowCount() {
242: return Math.max(contentBackend.getRowCount(), sheetLayout
243: .getRowCount());
244: }
245:
246: public int getColumnCount() {
247: return Math.max(contentBackend.getColumnCount(), sheetLayout
248: .getColumnCount());
249: }
250:
251: private boolean startBox(final RenderBox box) {
252: if (box.isFinished()) {
253: return true;
254: }
255:
256: // if (box.isOpen())
257: // {
258: // Log.debug("Received open box: " + box);
259: // }
260:
261: final long y = effectiveOffset + box.getY() - pageOffset;
262: final long height = box.getHeight();
263:
264: final long pageHeight = effectiveOffset
265: + (pageEnd - pageOffset);
266:
267: // Log.debug ("Processing Box " + effectiveOffset + " " + pageHeight + " -> " + y + " " + height);
268: // Log.debug ("Processing Box " + box);
269: //
270:
271: if (height > 0) {
272: if ((y + height) <= effectiveOffset) {
273: return false;
274: }
275: if (y >= pageHeight) {
276: return false;
277: }
278: } else {
279: // zero height boxes are always a bit tricky ..
280: if ((y + height) < effectiveOffset) {
281: return false;
282: }
283: if (y > pageHeight) {
284: return false;
285: }
286: }
287:
288: // Always process everything ..
289: final long y1 = Math.max(0, y);
290: final long boxX = box.getX();
291: final long x1 = Math.max(0, boxX);
292: final long y2 = Math.min(y + box.getHeight(), maximumHeight);
293: final long x2 = Math.min(boxX + box.getWidth(), maximumWidth);
294: lookupRectangle = sheetLayout.getTableBounds(x1, y1, x2 - x1,
295: y2 - y1, lookupRectangle);
296:
297: if (ProcessUtility.isContent(box, false, ellipseAsBackground,
298: shapesAsContent) == false) {
299:
300: final String sheetName = (String) box.getStyleSheet()
301: .getStyleProperty(BandStyleKeys.COMPUTED_SHEETNAME);
302: if (sheetName != null) {
303: // if (sheetName.equals(this.sheetName) == false)
304: // {
305: // Log.debug ("Received new sheetname: " + sheetName);
306: // Log.debug (" : " + box);
307: // }
308: this .sheetName = sheetName;
309: }
310:
311: if (box.isCommited()) {
312: box.setFinished(true);
313: }
314:
315: final int rectX2 = lookupRectangle.getX2();
316: final int rectY2 = lookupRectangle.getY2();
317: contentBackend.ensureCapacity(rectY2, rectX2);
318:
319: if (box.isFinished()) {
320: if (box.isCommited() == false) {
321: throw new IllegalStateException();
322: }
323: //Log.debug("Processing box-cell with bounds (" + x1 + ", " + y1 + ")(" + x2 + ", " + y2 + ")");
324: final BandMarker bandMarker;
325: if (verboseCellMarkers) {
326: bandMarker = new BandMarker(box.toString());
327: } else {
328: bandMarker = BandMarker.INSTANCE;
329: }
330: for (int r = lookupRectangle.getY1(); r < rectY2; r++) {
331: for (int c = lookupRectangle.getX1(); c < rectX2; c++) {
332: final Object o = contentBackend.getObject(r, c);
333: if (o == null) {
334: contentBackend.setObject(r, c, bandMarker);
335: }
336: }
337: }
338: }
339: return true;
340: }
341: if (box.isCommited() == false) {
342: // content-box is not finished yet.
343: // if (iterativeUpdate == false)
344: // {
345: // Log.debug("Still Skipping content-cell with bounds (" + x1 + ", " + y1 + ")(" + x2 + ", " + y2 + ")");
346: // }
347: return false;
348: }
349:
350: //Log.debug("Processing content-cell with bounds (" + x1 + ", " + y1 + ")(" + x2 + ", " + y2 + ")");
351: final String sheetName = (String) box.getStyleSheet()
352: .getStyleProperty(BandStyleKeys.COMPUTED_SHEETNAME);
353: if (sheetName != null) {
354: // if (sheetName.equals(this.sheetName) == false)
355: // {
356: // Log.debug ("Received new sheetname: " + sheetName);
357: // Log.debug (" : " + box);
358: // }
359: this .sheetName = sheetName;
360: }
361:
362: if (isCellSpaceOccupied(lookupRectangle) == false) {
363: final int rectX2 = lookupRectangle.getX2();
364: final int rectY2 = lookupRectangle.getY2();
365: contentBackend.ensureCapacity(rectY2, rectX2);
366: final ContentMarker contentMarker = new ContentMarker(box,
367: effectiveOffset - pageOffset);
368: for (int r = lookupRectangle.getY1(); r < rectY2; r++) {
369: for (int c = lookupRectangle.getX1(); c < rectX2; c++) {
370: contentBackend.setObject(r, c, contentMarker);
371: }
372: }
373:
374: // Setting this content-box to finished has to be done in the actual content-generator.
375: } else {
376: if (reportCellConflicts) {
377: Log.debug("LayoutShift: Offending Content: " + box);
378: Log.debug("LayoutShift: Offending Content: "
379: + box.isFinished());
380: }
381: box.setFinished(true);
382: }
383: return true;
384: }
385:
386: private boolean isCellSpaceOccupied(final TableRectangle rect) {
387: final int x2 = rect.getX2();
388: final int y2 = rect.getY2();
389:
390: for (int r = rect.getY1(); r < y2; r++) {
391: for (int c = rect.getX1(); c < x2; c++) {
392: final Object object = contentBackend.getObject(r, c);
393: if (object != null
394: && object instanceof BandMarker == false) {
395: if (reportCellConflicts) {
396: Log.debug("Cell (" + c + ", " + r
397: + ") already filled: Content in cell: "
398: + object);
399: }
400: return true;
401: }
402: }
403: }
404: return false;
405: }
406:
407: public int getFinishedRows() {
408: return finishedRows;
409: }
410:
411: public void clearFinishedBoxes() {
412: final int rowCount = getFilledRows();
413: final int columnCount = getColumnCount();
414: if (debugReportLayout) {
415: Log.debug("Request: Clearing rows from " + finishedRows
416: + " to " + rowCount);
417: }
418:
419: int lastRowCleared = finishedRows - 1;
420: for (int row = finishedRows; row < rowCount; row++) {
421: boolean rowHasContent = false;
422: for (int column = 0; column < columnCount; column++) {
423: final CellMarker o = (CellMarker) contentBackend
424: .getObject(row, column);
425: if (o == null) {
426: if (debugReportLayout) {
427: Log.debug("Cannot clear row: Cell (" + column
428: + ", " + row + ") is undefined.");
429: }
430: return;
431: }
432: final boolean b = o.isFinished();
433: if (b == false) {
434: if (debugReportLayout) {
435: Log.debug("Cannot clear row: Cell (" + column
436: + ", " + row + ") is not finished: "
437: + o);
438: }
439: return;
440: } else {
441: if (rowHasContent == false
442: && o.getContent() != null) {
443: rowHasContent = true;
444: }
445: }
446: }
447:
448: if (rowHasContent) {
449: finishedRows = row + 1;
450: // if (debugReportLayout)
451: // {
452: // Log.debug("Clearing rows from " + (lastRowCleared + 1)+ " to " + finishedRows);
453: // }
454: for (int clearRowNr = lastRowCleared + 1; clearRowNr < finishedRows; clearRowNr++) {
455: if (debugReportLayout) {
456: Log.debug("#Cleared row: " + row + '/'
457: + clearRowNr);
458: }
459: for (int column = 0; column < columnCount; column++) {
460: final Object o = contentBackend.getObject(
461: clearRowNr, column);
462: final FinishedMarker finishedMarker;
463: if (verboseCellMarkers) {
464: finishedMarker = new FinishedMarker(String
465: .valueOf(o));
466: } else {
467: finishedMarker = FinishedMarker.INSTANCE;
468: }
469: contentBackend.setObject(clearRowNr, column,
470: finishedMarker);
471: }
472: }
473: lastRowCleared = row;
474: } else if (debugReportLayout) {
475: lastRowCleared = row;
476: Log.debug("-Cleared row: " + row + '.');
477: }
478: }
479:
480: if (debugReportLayout) {
481: Log.debug("Need to clear row: " + (lastRowCleared + 1)
482: + " - " + filledRows);
483: }
484: finishedRows = filledRows;
485: for (int clearRowNr = lastRowCleared + 1; clearRowNr < finishedRows; clearRowNr++) {
486: if (debugReportLayout) {
487: Log.debug("*Cleared row: " + clearRowNr + '.');
488: }
489: for (int column = 0; column < columnCount; column++) {
490: final Object o = contentBackend.getObject(clearRowNr,
491: column);
492: final FinishedMarker finishedMarker;
493: if (verboseCellMarkers) {
494: finishedMarker = new FinishedMarker(String
495: .valueOf(o));
496: } else {
497: finishedMarker = FinishedMarker.INSTANCE;
498: }
499: contentBackend.setObject(clearRowNr, column,
500: finishedMarker);
501: }
502: }
503:
504: }
505:
506: protected boolean startBlockBox(final BlockRenderBox box) {
507: return startBox(box);
508: }
509:
510: protected boolean startInlineBox(final InlineRenderBox box) {
511: // we should not have come that far ..
512: return false;
513: }
514:
515: protected boolean startOtherBox(final RenderBox box) {
516: return startBox(box);
517: }
518:
519: public boolean startCanvasBox(final CanvasRenderBox box) {
520: return startBox(box);
521: }
522:
523: protected void processParagraphChilds(final ParagraphRenderBox box) {
524: // not needed.
525: }
526:
527: public SheetLayout getSheetLayout() {
528: return sheetLayout;
529: }
530:
531: public int getFilledRows() {
532: return filledRows;
533: }
534:
535: private void updateFilledRows() {
536: final int rowCount = contentBackend.getRowCount();
537: final int columnCount = getColumnCount();
538: filledRows = finishedRows;
539: for (int row = finishedRows; row < rowCount; row++) {
540: for (int column = 0; column < columnCount; column++) {
541: final CellMarker o = (CellMarker) contentBackend
542: .getObject(row, column);
543: if (o == null) {
544: if (debugReportLayout) {
545: Log.debug("Row: Cell (" + column + ", " + row
546: + ") is undefined.");
547: }
548: return;
549: }
550: if (o.isCommited() == false) {
551: if (debugReportLayout) {
552: Log.debug("Row: Cell (" + column + ", " + row
553: + ") is not commited.");
554: }
555: return;
556: }
557: }
558:
559: // Log.debug("Processable Row: " + filledRows + ".");
560: filledRows = row + 1;
561: }
562:
563: if (debugReportLayout) {
564: Log.debug("Processable Rows: " + finishedRows + ' '
565: + filledRows + '.');
566: }
567: }
568:
569: }
|