KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > fop > area > AreaTreeHandler


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: AreaTreeHandler.java 433679 2006-08-22 17:49:26 +0200 (Tue, 22 Aug 2006) cbowditch $ */
19
20 package org.apache.fop.area;
21
22 // Java
23
import java.io.OutputStream JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.Set JavaDoc;
29 import java.util.HashSet JavaDoc;
30 import java.util.Iterator JavaDoc;
31
32 // XML
33
import org.xml.sax.SAXException JavaDoc;
34
35 // Apache
36
import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.fop.apps.FOPException;
39 import org.apache.fop.apps.FOUserAgent;
40 import org.apache.fop.apps.FormattingResults;
41 import org.apache.fop.datatypes.Numeric;
42 import org.apache.fop.fo.FOEventHandler;
43 import org.apache.fop.fo.extensions.ExtensionAttachment;
44 import org.apache.fop.fo.pagination.PageSequence;
45 import org.apache.fop.fo.pagination.Root;
46 import org.apache.fop.fo.pagination.bookmarks.BookmarkTree;
47 import org.apache.fop.layoutmgr.PageSequenceLayoutManager;
48 import org.apache.fop.layoutmgr.LayoutManagerMaker;
49 import org.apache.fop.layoutmgr.LayoutManagerMapping;
50
51 /**
52  * Area tree handler for formatting objects.
53  *
54  * Concepts:
55  * The area tree is to be as small as possible. With minimal classes
56  * and data to fully represent an area tree for formatting objects.
57  * The area tree needs to be simple to render and follow the spec
58  * closely.
59  * This area tree has the concept of page sequences.
60  * Wherever possible information is discarded or optimized to
61  * keep memory use low. The data is also organized to make it
62  * possible for renderers to minimize their output.
63  * A page can be saved if not fully resolved and once rendered
64  * a page contains only size and id reference information.
65  * The area tree pages are organized in a model that depends on the
66  * type of renderer.
67  */

68 public class AreaTreeHandler extends FOEventHandler {
69
70     private static Log log = LogFactory.getLog(AreaTreeHandler.class);
71
72     // show statistics after document complete?
73
private boolean outputStatistics;
74
75     // for statistics gathering
76
private Runtime JavaDoc runtime;
77
78     // heap memory allocated (for statistics)
79
private long initialMemory;
80
81     // time used in rendering (for statistics)
82
private long startTime;
83
84     // the LayoutManager maker
85
private LayoutManagerMaker lmMaker;
86
87     /** AreaTreeModel in use */
88     protected AreaTreeModel model;
89
90     // The fo:root node of the document
91
private Root rootFObj;
92
93     // HashMap of ID's whose area is located on one or more consecutive
94
// PageViewports. Each ID has an arraylist of PageViewports that
95
// form the defined area of this ID
96
private Map JavaDoc idLocations = new HashMap JavaDoc();
97
98     // idref's whose target PageViewports have yet to be identified
99
// Each idref has a HashSet of Resolvable objects containing that idref
100
private Map JavaDoc unresolvedIDRefs = new HashMap JavaDoc();
101     
102     private Set JavaDoc unfinishedIDs = new HashSet JavaDoc();
103     private Set JavaDoc alreadyResolvedIDs = new HashSet JavaDoc();
104
105      // The formatting results to be handed back to the caller.
106
private FormattingResults results = new FormattingResults();
107
108     private PageSequenceLayoutManager prevPageSeqLM;
109
110     private int idGen = 0;
111     
112     /**
113      * Constructor.
114      * @param userAgent FOUserAgent object for process
115      * @param outputFormat the MIME type of the output format to use (ex. "application/pdf").
116      * @param stream OutputStream
117      * @throws FOPException if the RenderPagesModel cannot be created
118      */

119     public AreaTreeHandler (FOUserAgent userAgent, String JavaDoc outputFormat,
120                 OutputStream JavaDoc stream) throws FOPException {
121         super(userAgent);
122
123         setupModel(userAgent, outputFormat, stream);
124             
125         lmMaker = userAgent.getFactory().getLayoutManagerMakerOverride();
126         if (lmMaker == null) {
127             lmMaker = new LayoutManagerMapping();
128         }
129
130         outputStatistics = log.isDebugEnabled();
131
132         if (outputStatistics) {
133             runtime = Runtime.getRuntime();
134         }
135     }
136
137     /**
138      * Sets up the AreaTreeModel instance for use by the AreaTreeHandler.
139      * @param userAgent FOUserAgent object for process
140      * @param outputFormat the MIME type of the output format to use (ex. "application/pdf").
141      * @param stream OutputStream
142      * @throws FOPException if the RenderPagesModel cannot be created
143      */

144     protected void setupModel(FOUserAgent userAgent, String JavaDoc outputFormat,
145             OutputStream JavaDoc stream) throws FOPException {
146         model = new RenderPagesModel(userAgent, outputFormat, fontInfo,
147                 stream);
148     }
149     
150     /**
151      * Get the area tree model for this area tree.
152      *
153      * @return AreaTreeModel the model being used for this area tree
154      */

155     public AreaTreeModel getAreaTreeModel() {
156         return model;
157     }
158
159     /**
160      * Get the LayoutManager maker for this area tree.
161      *
162      * @return LayoutManagerMaker the LayoutManager maker being used for this area tree
163      */

164     public LayoutManagerMaker getLayoutManagerMaker() {
165         return lmMaker;
166     }
167
168     /**
169      * Tie a PageViewport with an ID found on a child area of the PV.
170      * Note that an area with a given ID may be on more than one PV, hence
171      * an ID may have more than one PV associated with it.
172      * @param id the property ID of the area
173      * @param pv a page viewport that contains the area with this ID
174      */

175     public void associateIDWithPageViewport(String JavaDoc id, PageViewport pv) {
176         if (log.isDebugEnabled()) {
177             log.debug("associateIDWithPageViewport(" + id + ", " + pv + ")");
178         }
179         List JavaDoc pvList = (List JavaDoc) idLocations.get(id);
180         if (pvList == null) { // first time ID located
181
pvList = new ArrayList JavaDoc();
182             idLocations.put(id, pvList);
183             pvList.add(pv);
184
185             /*
186              * See if this ID is in the unresolved idref list, if so
187              * resolve Resolvable objects tied to it.
188              */

189             if (!unfinishedIDs.contains(id)) {
190               tryIDResolution(id, pv, pvList);
191             }
192         } else {
193             pvList.add(pv);
194         }
195     }
196     
197     /**
198      * This method tie an ID to the areaTreeHandler until this one is
199      * ready to be processed. This is used in page-number-citation-last processing so we know
200      * when an id can be resolved.
201      * @param id the id of the object being processed
202      */

203     public void signalPendingID(String JavaDoc id) {
204         if (log.isDebugEnabled()) {
205             log.debug("signalPendingID(" + id + ")");
206         }
207         unfinishedIDs.add(id);
208     }
209     
210     /**
211      * Signals that all areas for the formatting object with the given ID have been generated.
212      * This is used to determine when page-number-citation-last ref-ids can be resolved.
213      * @param id the id of the formatting object which was just finished
214      */

215     public void signalIDProcessed(String JavaDoc id) {
216         if (log.isDebugEnabled()) {
217             log.debug("signalIDProcessed(" + id + ")");
218         }
219         
220         alreadyResolvedIDs.add(id);
221         if (!unfinishedIDs.contains(id)) {
222             return;
223         }
224         unfinishedIDs.remove(id);
225         
226         List JavaDoc pvList = (List JavaDoc) idLocations.get(id);
227         Set JavaDoc todo = (Set JavaDoc) unresolvedIDRefs.get(id);
228         if (todo != null) {
229             for (Iterator JavaDoc iter = todo.iterator(); iter.hasNext();) {
230                 Resolvable res = (Resolvable) iter.next();
231                 res.resolveIDRef(id, pvList);
232             }
233             unresolvedIDRefs.remove(id);
234         }
235     }
236     
237     /**
238      * Check if an ID has already been resolved
239      * @param id the id to check
240      * @return true if the ID has been resolved
241      */

242     public boolean alreadyResolvedID(String JavaDoc id) {
243         return (alreadyResolvedIDs.contains(id));
244     }
245
246     /**
247      * Tries to resolve all unresolved ID references on the given page.
248      * @param id ID to resolve
249      * @param pv page viewport whose ID refs to resolve
250      * @param List of PageViewports
251      */

252     private void tryIDResolution(String JavaDoc id, PageViewport pv, List JavaDoc pvList) {
253         Set JavaDoc todo = (Set JavaDoc) unresolvedIDRefs.get(id);
254         if (todo != null) {
255             for (Iterator JavaDoc iter = todo.iterator(); iter.hasNext();) {
256                 Resolvable res = (Resolvable) iter.next();
257                 if (!unfinishedIDs.contains(id)) {
258                     res.resolveIDRef(id, pvList);
259                 } else {
260                     return;
261                 }
262             }
263             alreadyResolvedIDs.add(id);
264             unresolvedIDRefs.remove(id);
265         }
266     }
267
268     /**
269      * Tries to resolve all unresolved ID references on the given page.
270      * @param pv page viewport whose ID refs to resolve
271      */

272     public void tryIDResolution(PageViewport pv) {
273         String JavaDoc[] ids = pv.getIDRefs();
274         if (ids != null) {
275             for (int i = 0; i < ids.length; i++) {
276                 List JavaDoc pvList = (List JavaDoc) idLocations.get(ids[i]);
277                 if (pvList != null) {
278                     tryIDResolution(ids[i], pv, pvList);
279                 }
280             }
281         }
282     }
283     
284     /**
285      * Get the list of page viewports that have an area with a given id.
286      * @param id the id to lookup
287      * @return the list of PageViewports
288      */

289     public List JavaDoc getPageViewportsContainingID(String JavaDoc id) {
290         return (List JavaDoc) idLocations.get(id);
291     }
292
293     /**
294      * Get information about the rendered output, like
295      * number of pages created.
296      * @return the results structure
297      */

298     public FormattingResults getResults() {
299         return this.results;
300     }
301
302     /**
303      * Add an Resolvable object with an unresolved idref
304      * @param idref the idref whose target id has not yet been located
305      * @param res the Resolvable object needing the idref to be resolved
306      */

307     public void addUnresolvedIDRef(String JavaDoc idref, Resolvable res) {
308         Set JavaDoc todo = (Set JavaDoc) unresolvedIDRefs.get(idref);
309         if (todo == null) {
310             todo = new HashSet JavaDoc();
311             unresolvedIDRefs.put(idref, todo);
312         }
313         // add Resolvable object to this HashSet
314
todo.add(res);
315     }
316
317     /**
318      * Prepare AreaTreeHandler for document processing
319      * This is called from FOTreeBuilder.startDocument()
320      *
321      * @throws SAXException if there is an error
322      */

323     public void startDocument() throws SAXException JavaDoc {
324         //Initialize statistics
325
if (outputStatistics) {
326             initialMemory = runtime.totalMemory() - runtime.freeMemory();
327             startTime = System.currentTimeMillis();
328         }
329     }
330
331     /**
332      * finish the previous pageSequence
333      */

334     private void finishPrevPageSequence(Numeric initialPageNumber) {
335         if (prevPageSeqLM != null) {
336             prevPageSeqLM.doForcePageCount(initialPageNumber);
337             prevPageSeqLM.finishPageSequence();
338             prevPageSeqLM = null;
339         }
340     }
341
342     /**
343      * @see org.apache.fop.fo.FOEventHandler
344      * @param pageSequence is the pageSequence being started
345      * */

346     public void startPageSequence(PageSequence pageSequence) {
347         rootFObj = pageSequence.getRoot();
348         finishPrevPageSequence(pageSequence.getInitialPageNumber());
349         pageSequence.initPageNumber();
350         //extension attachments from fo:root
351
wrapAndAddExtensionAttachments(rootFObj.getExtensionAttachments());
352         //extension attachments from fo:declarations
353
if (rootFObj.getDeclarations() != null) {
354             wrapAndAddExtensionAttachments(rootFObj.getDeclarations().getExtensionAttachments());
355         }
356     }
357     
358     private void wrapAndAddExtensionAttachments(List JavaDoc list) {
359         Iterator JavaDoc i = list.iterator();
360         while (i.hasNext()) {
361             ExtensionAttachment attachment = (ExtensionAttachment)i.next();
362             addOffDocumentItem(new OffDocumentExtensionAttachment(attachment));
363         }
364     }
365     
366     /**
367      * End the PageSequence.
368      * The PageSequence formats Pages and adds them to the AreaTree.
369      * The area tree then handles what happens with the pages.
370      *
371      * @param pageSequence the page sequence ending
372      */

373     public void endPageSequence(PageSequence pageSequence) {
374
375         if (outputStatistics) {
376             long memoryNow = runtime.totalMemory() - runtime.freeMemory();
377             log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
378         }
379
380         // If no main flow, nothing to layout!
381
if (pageSequence.getMainFlow() != null) {
382             PageSequenceLayoutManager pageSLM;
383             pageSLM = getLayoutManagerMaker().makePageSequenceLayoutManager(
384                     this, pageSequence);
385             pageSLM.activateLayout();
386             // preserve the current PageSequenceLayoutManger for the
387
// force-page-count check at the beginning of the next PageSequence
388
prevPageSeqLM = pageSLM;
389         }
390     }
391
392     /**
393      * Called by the PageSequenceLayoutManager when it is finished with a page-sequence.
394      * @param pageSequence the page-sequence just finished
395      * @param pageCount The number of pages generated for the page-sequence
396      */

397     public void notifyPageSequenceFinished(PageSequence pageSequence,
398                                            int pageCount) {
399         this.results.haveFormattedPageSequence(pageSequence,
400                                                pageCount);
401         if (log.isDebugEnabled()) {
402             log.debug("Last page-sequence produced "
403                     + pageCount + " pages.");
404         }
405     }
406
407     /**
408      * End the document.
409      *
410      * @throws SAXException if there is some error
411      */

412     public void endDocument() throws SAXException JavaDoc {
413
414         finishPrevPageSequence(null);
415         // process fo:bookmark-tree
416
BookmarkTree bookmarkTree = rootFObj.getBookmarkTree();
417         if (bookmarkTree != null) {
418             BookmarkData data = new BookmarkData(bookmarkTree);
419             addOffDocumentItem(data);
420             if (!data.isResolved()) {
421                 //bookmarks did not fully resolve, add anyway. (hacky? yeah)
422
model.handleOffDocumentItem(data);
423             }
424         }
425
426         model.endDocument();
427
428         if (outputStatistics) {
429             long memoryNow = runtime.totalMemory() - runtime.freeMemory();
430             long memoryUsed = (memoryNow - initialMemory) / 1024L;
431             long timeUsed = System.currentTimeMillis() - startTime;
432             int pageCount = rootFObj.getTotalPagesGenerated();
433             log.debug("Initial heap size: " + (initialMemory / 1024L) + "Kb");
434             log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
435             log.debug("Total memory used: " + memoryUsed + "Kb");
436             log.debug("Total time used: " + timeUsed + "ms");
437             log.debug("Pages rendered: " + pageCount);
438             if (pageCount > 0) {
439                 long perPage = (timeUsed / pageCount);
440                 long ppm = (timeUsed != 0 ? Math.round(60000 * pageCount / (double)timeUsed) : -1);
441                 log.debug("Avg render time: " + perPage + "ms/page (" + ppm + "pages/min)");
442             }
443         }
444     }
445
446     /**
447      * Add a OffDocumentItem to the area tree model
448      * This checks if the OffDocumentItem is resolvable and attempts
449      * to resolve or add the resolvable ids for later resolution.
450      * @param odi the OffDocumentItem to add.
451      */

452     private void addOffDocumentItem(OffDocumentItem odi) {
453         if (odi instanceof Resolvable) {
454             Resolvable res = (Resolvable) odi;
455             String JavaDoc[] ids = res.getIDRefs();
456             for (int count = 0; count < ids.length; count++) {
457                 if (idLocations.containsKey(ids[count])) {
458                     res.resolveIDRef(ids[count], (List JavaDoc) idLocations.get(ids[count]));
459                 } else {
460                     log.warn(odi.getName() + ": Unresolved id reference \""
461                         + ids[count] + "\" found.");
462                     addUnresolvedIDRef(ids[count], res);
463                 }
464             }
465             // check to see if ODI is now fully resolved, if so process it
466
if (res.isResolved()) {
467                 model.handleOffDocumentItem(odi);
468             }
469         } else {
470             model.handleOffDocumentItem(odi);
471         }
472     }
473     
474     /**
475      * Generates and returns a unique key for a page viewport.
476      * @return the generated key.
477      */

478     public String JavaDoc generatePageViewportKey() {
479         this.idGen++;
480         return "P" + this.idGen;
481     }
482     
483 }
484
485
Popular Tags