001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.pdf.pd;
031:
032: import java.util.ArrayList;
033: import java.util.Iterator;
034: import java.util.List;
035: import de.intarsys.pdf.cos.COSArray;
036: import de.intarsys.pdf.cos.COSBasedObject;
037: import de.intarsys.pdf.cos.COSCatalog;
038: import de.intarsys.pdf.cos.COSDictionary;
039: import de.intarsys.pdf.cos.COSInteger;
040: import de.intarsys.pdf.cos.COSName;
041: import de.intarsys.pdf.cos.COSObject;
042:
043: /**
044: * A page tree node is a container for pages (and other page tree nodes) within
045: * a PFD document. All pages in the document are direct or indirect children of
046: * the root page tree node in the {@link COSCatalog} object
047: */
048: public class PDPageTree extends PDPageNode {
049: /**
050: * The meta class implementation
051: */
052: static public class MetaClass extends PDPageNode.MetaClass {
053: protected MetaClass(Class instanceClass) {
054: super (instanceClass);
055: }
056:
057: protected COSBasedObject doCreateCOSBasedObject(COSObject object) {
058: return new PDPageTree(object);
059: }
060: }
061:
062: /** The meta class instance */
063: static public final MetaClass META = new MetaClass(MetaClass.class
064: .getDeclaringClass());
065:
066: static public final COSName DK_Kids = COSName.constant("Kids");
067:
068: static public final COSName DK_Count = COSName.constant("Count");
069:
070: final static private int MAX_KIDS = 1000;
071:
072: /** the immediate children of this node */
073: private List cachedKids;
074:
075: /**
076: * Create the receiver class from an already defined {@link COSDictionary}.
077: * NEVER use the constructor directly.
078: *
079: * @param object
080: * the PDDocument containing the new object
081: */
082: protected PDPageTree(COSObject object) {
083: super (object);
084: }
085:
086: /**
087: * Add a page after at the designated index.
088: *
089: * @param index
090: * the index which to insert the new child at
091: * @param newNode
092: * the child page to add
093: */
094: public void addNode(int index, PDPageNode newNode) {
095: COSArray cosKids = cosGetField(DK_Kids).asArray();
096: if (cosKids == null) {
097: cosKids = COSArray.create();
098: cosSetField(DK_Kids, cosKids);
099: }
100: index = Math.min(index, cosKids.size());
101: if (index < 0) {
102: index = cosKids.size();
103: }
104: newNode.setParent(this );
105: cosKids.add(index, newNode.cosGetDict());
106: incrementCount(newNode.getCount());
107: }
108:
109: /**
110: * Add a page as immediate child at last position
111: *
112: * @param newNode
113: * the child page to create
114: */
115: public void addNode(PDPageNode newNode) {
116: addNodeAfter(newNode, null);
117: }
118:
119: /**
120: * Add a page after the designated destination page. the destination page
121: * must be in the receiver tree node
122: *
123: * @param newNode
124: * the child page to add
125: * @param destination
126: * the page after which to insert the new child
127: */
128: public void addNodeAfter(PDPageNode newNode, PDPageNode destination) {
129: int destinationIndex = -1;
130: COSArray cosKids = cosGetField(DK_Kids).asArray();
131: if ((cosKids != null) && (destination != null)) {
132: destinationIndex = cosKids
133: .indexOf(destination.cosGetDict());
134: }
135: int newNodeIndex = -1;
136: if (destinationIndex > -1) {
137: newNodeIndex = destinationIndex + 1;
138: }
139: addNode(newNodeIndex, newNode);
140: }
141:
142: /*
143: * (non-Javadoc)
144: *
145: * @see de.intarsys.pdf.pd.PDPageNode#collectAnnotations(java.util.List)
146: */
147: protected void collectAnnotations(List annotations) {
148: for (Iterator it = getKids().iterator(); it.hasNext();) {
149: PDPageNode childNode = (PDPageNode) it.next();
150: childNode.collectAnnotations(annotations);
151: }
152: }
153:
154: /*
155: * (non-Javadoc)
156: *
157: * @see de.intarsys.pdf.pd.PDObject#cosGetExpectedType()
158: */
159: protected COSName cosGetExpectedType() {
160: return DK_Pages;
161: }
162:
163: protected COSObject cosSetKids(COSArray newKids) {
164: setCount(newKids.size());
165: return cosSetField(DK_Kids, newKids);
166: }
167:
168: protected void exchangeNode(PDPageNode oldNode, PDPageNode newNode) {
169: COSArray cosKids = cosGetField(DK_Kids).asArray();
170: if (cosKids == null) {
171: return;
172: }
173: int index = cosKids.indexOf(oldNode.cosGetDict());
174: if (index >= 0) {
175: oldNode.setParent(null);
176: newNode.setParent(this );
177: cosKids.set(index, newNode.cosGetDict());
178: }
179: }
180:
181: /*
182: * (non-Javadoc)
183: *
184: * @see de.intarsys.pdf.pd.PDPageNode#getCount()
185: */
186: public int getCount() {
187: return getFieldInt(DK_Count, 0);
188: }
189:
190: /*
191: * (non-Javadoc)
192: *
193: * @see de.intarsys.pdf.pd.PDPageNode#getFirstNode()
194: */
195: public PDPageNode getFirstNode() {
196: if (getKids().isEmpty()) {
197: return null;
198: }
199: return (PDPageNode) getKids().get(0);
200: }
201:
202: /*
203: * (non-Javadoc)
204: *
205: * @see de.intarsys.pdf.pd.PDPageNode#getFirstPage()
206: */
207: public PDPage getFirstPage() {
208: for (Iterator i = getKids().iterator(); i.hasNext();) {
209: PDPageNode node = (PDPageNode) i.next();
210: PDPage page = node.getFirstPage();
211: if (page != null) {
212: return page;
213: }
214: }
215: return null;
216: }
217:
218: /*
219: * (non-Javadoc)
220: *
221: * @see de.intarsys.pdf.pd.PDObject#getGenericChildren()
222: */
223: public List getGenericChildren() {
224: return getKids();
225: }
226:
227: /**
228: * Get the list of all page nodes that are children of the receiver.
229: *
230: * @return an ArrayList
231: */
232: public List getKids() {
233: if (cachedKids == null) {
234: cachedKids = new ArrayList();
235: COSArray cosKids = cosGetField(DK_Kids).asArray();
236: if (cosKids != null) {
237: for (Iterator i = cosKids.iterator(); i.hasNext();) {
238: COSBasedObject page = PDPageNode.META
239: .createFromCos((COSObject) i.next());
240: if (page != null) {
241: cachedKids.add(page);
242: }
243: }
244: cosKids.addObjectListener(this );
245: }
246: }
247: return cachedKids;
248: }
249:
250: /*
251: * (non-Javadoc)
252: *
253: * @see de.intarsys.pdf.pd.PDPageNode#getLast()
254: */
255: public PDPageNode getLastNode() {
256: List theKids = getKids();
257: if (theKids.isEmpty()) {
258: return null;
259: }
260: return (PDPageNode) theKids.get(theKids.size() - 1);
261: }
262:
263: /**
264: * Get the successor of node in the collection of children. If node is not a
265: * direct child return null. If page is the last child, lookup the next page
266: * via the parent of this.
267: *
268: * @param node
269: * The page whose successor is requested.
270: *
271: * @return Get the next page in the collection of children after page.
272: */
273: protected PDPageNode getNextNode(PDPageNode node) {
274: List children = getKids();
275: int index = children.indexOf(node);
276: if (index < 0) {
277: return null;
278: }
279: if (index < (children.size() - 1)) {
280: return (PDPageNode) children.get(index + 1);
281: }
282: if (getParent() == null) {
283: return null;
284: }
285: return getParent().getNextNode(this );
286: }
287:
288: /**
289: * Get the next page in the collection of children after page. If page is
290: * not a direct child return null. If page is the last child, lookup the
291: * next page via the parent of this.
292: *
293: * @param page
294: * The page whose successor is requested.
295: *
296: * @return Get the next page in the collection of children after page.
297: */
298: protected PDPage getNextPage(PDPage page) {
299: PDPage nextPage = null;
300: for (PDPageNode nextNode = getNextNode(page); nextNode != null; nextNode = getNextNode(nextNode)) {
301: nextPage = nextNode.getFirstPage();
302: if (nextPage != null) {
303: break;
304: }
305: }
306: return nextPage;
307: }
308:
309: /**
310: * The 0 based index of <code>node</code> within <code>this</code> and
311: * all its ancestors, 0 if not found.
312: *
313: * @param node
314: * node whose position is determined.
315: *
316: * @return The 0 based index of <code>node</code> within <code>this</code>
317: * and all its ancestors, 0 if not found.
318: */
319: protected int getNodeIndex(PDPageNode node) {
320: int nodePos = 0;
321: for (Iterator i = getKids().iterator(); i.hasNext();) {
322: PDPageNode child = (PDPageNode) i.next();
323: if (child == node) {
324: break;
325: }
326: nodePos += child.getCount();
327: }
328: PDPageTree parent = getParent();
329: if (parent == null) {
330: return nodePos;
331: }
332: return nodePos + parent.getNodeIndex(this );
333: }
334:
335: /**
336: * Get the predecessor of node in the collection of children. If node is not
337: * a direct child return null. If page is the first child, lookup the
338: * previous page via the parent of this.
339: *
340: * @param node
341: * The page whose predecessor is requested.
342: *
343: * @return Get the predecessor of node in the collection of children.
344: */
345: protected PDPageNode getPreviousNode(PDPageNode node) {
346: List children = getKids();
347: int index = children.indexOf(node);
348: if (index < 0) {
349: return null;
350: }
351: if (index > 0) {
352: return (PDPageNode) children.get(index - 1);
353: }
354: if (getParent() == null) {
355: return null;
356: }
357: return getParent().getPreviousNode(this );
358: }
359:
360: /**
361: * Get the previous page in the collection of children before page. If page
362: * is not a direct child return null. If page is the first child, lookup the
363: * previous page via the parent of this.
364: *
365: * @param page
366: * The page whose predecessor is requested.
367: *
368: * @return Get the previous page in the collection of children before page.
369: */
370: protected PDPage getPreviousPage(PDPage page) {
371: PDPage previousPage = null;
372: for (PDPageNode previousNode = getPreviousNode(page); previousNode != null; previousNode = getPreviousNode(previousNode)) {
373: previousPage = previousNode.getLastPage();
374: if (previousPage != null) {
375: break;
376: }
377: }
378: return previousPage;
379: }
380:
381: /**
382: * increment the number of pages and propagate change to parent node
383: *
384: * @param delta
385: * number of pages to add
386: */
387: protected void incrementCount(int delta) {
388: int oldValue = getCount();
389: cosSetField(DK_Count, COSInteger.create(oldValue + delta));
390: if (getParent() != null) {
391: getParent().incrementCount(delta);
392: }
393: }
394:
395: /*
396: * (non-Javadoc)
397: *
398: * @see de.intarsys.pdf.pd.PDComplexBase#initializeFromScratch()
399: */
400: protected void initializeFromScratch() {
401: super .initializeFromScratch();
402: cosSetField(DK_Kids, COSArray.create());
403: cosSetField(DK_Count, COSInteger.create(0));
404: }
405:
406: /*
407: * (non-Javadoc)
408: *
409: * @see de.intarsys.pdf.pd.PDPageNode#invalidateCaches()
410: */
411: public void invalidateCaches() {
412: super .invalidateCaches();
413: COSArray cosKids = cosGetField(DK_Kids).asArray();
414: if (cosKids != null) {
415: cosKids.removeObjectListener(this );
416: }
417: cachedKids = null;
418: }
419:
420: /*
421: * (non-Javadoc)
422: *
423: * @see de.intarsys.pdf.pd.PDPageNode#isPage()
424: */
425: public boolean isPage() {
426: return false;
427: }
428:
429: /**
430: * Rebalanve this.
431: *
432: * @return The new {@link PDPageTree} created or null if nothing changed.
433: */
434: public PDPageTree rebalance() {
435: COSArray cosKids = cosGetField(DK_Kids).asArray();
436: if (cosKids == null) {
437: return null;
438: }
439: if (cosKids.size() > MAX_KIDS) {
440: PDPageTree newNode = (PDPageTree) PDPageTree.META
441: .createNew();
442: if (getParent() != null) {
443: getParent().exchangeNode(this , newNode);
444: }
445: newNode.addNode(this );
446: return newNode;
447: }
448: return null;
449: }
450:
451: /**
452: * Remove a node
453: *
454: * @param node
455: * The child node to remove
456: */
457: public void removeNode(PDPageNode node) {
458: COSArray cosKids = cosGetField(DK_Kids).asArray();
459: if (cosKids != null) {
460: if (cosKids.remove(node.cosGetObject())) {
461: incrementCount(-node.getCount());
462: }
463: }
464: }
465:
466: /**
467: * Set the number of pages.
468: *
469: * @param newCount
470: * number of pages to add
471: */
472: protected void setCount(int newCount) {
473: int oldValue = getCount();
474: setFieldInt(DK_Count, newCount);
475: if (getParent() != null) {
476: getParent().incrementCount(newCount - oldValue);
477: }
478: }
479: }
|