All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.intarsys.pdf.pd.PDPageTree Maven / Gradle / Ivy

/*
 * Copyright (c) 2007, intarsys consulting GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of intarsys nor the names of its contributors may be used
 *   to endorse or promote products derived from this software without specific
 *   prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package de.intarsys.pdf.pd;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import de.intarsys.pdf.cos.COSArray;
import de.intarsys.pdf.cos.COSBasedObject;
import de.intarsys.pdf.cos.COSCatalog;
import de.intarsys.pdf.cos.COSDictionary;
import de.intarsys.pdf.cos.COSInteger;
import de.intarsys.pdf.cos.COSName;
import de.intarsys.pdf.cos.COSObject;

/**
 * A page tree node is a container for pages (and other page tree nodes) within
 * a PFD document. All pages in the document are direct or indirect children of
 * the root page tree node in the {@link COSCatalog} object
 */
public class PDPageTree extends PDPageNode {
	/**
	 * The meta class implementation
	 */
	static public class MetaClass extends PDPageNode.MetaClass {
		protected MetaClass(Class instanceClass) {
			super(instanceClass);
		}

		@Override
		protected COSBasedObject doCreateCOSBasedObject(COSObject object) {
			return new PDPageTree(object);
		}
	}

	/** The meta class instance */
	static public final MetaClass META = new MetaClass(MetaClass.class
			.getDeclaringClass());

	static public final COSName DK_Kids = COSName.constant("Kids");

	static public final COSName DK_Count = COSName.constant("Count");

	final static private int MAX_KIDS = 1000;

	/** the immediate children of this node */
	private SoftReference cachedKids;

	/**
	 * Create the receiver class from an already defined {@link COSDictionary}.
	 * NEVER use the constructor directly.
	 * 
	 * @param object
	 *            the PDDocument containing the new object
	 */
	protected PDPageTree(COSObject object) {
		super(object);
	}

	/**
	 * Add a page after at the designated index.
	 * 
	 * @param index
	 *            the index which to insert the new child at
	 * @param newNode
	 *            the child page to add
	 */
	public void addNode(int index, PDPageNode newNode) {
		COSArray cosKids = cosGetField(DK_Kids).asArray();
		if (cosKids == null) {
			cosKids = COSArray.create();
			cosSetField(DK_Kids, cosKids);
		}
		index = Math.min(index, cosKids.size());
		if (index < 0) {
			index = cosKids.size();
		}
		newNode.setParent(this);
		cosKids.add(index, newNode.cosGetDict());
		incrementCount(newNode.getCount());
	}

	/**
	 * Add a page as immediate child at last position
	 * 
	 * @param newNode
	 *            the child page to create
	 */
	public void addNode(PDPageNode newNode) {
		addNodeAfter(newNode, null);
	}

	/**
	 * Add a page after the designated destination page. the destination page
	 * must be in the receiver tree node
	 * 
	 * @param newNode
	 *            the child page to add
	 * @param destination
	 *            the page after which to insert the new child
	 */
	public void addNodeAfter(PDPageNode newNode, PDPageNode destination) {
		int destinationIndex = -1;
		COSArray cosKids = cosGetField(DK_Kids).asArray();
		if ((cosKids != null) && (destination != null)) {
			destinationIndex = cosKids.indexOf(destination.cosGetDict());
		}
		int newNodeIndex = -1;
		if (destinationIndex > -1) {
			newNodeIndex = destinationIndex + 1;
		}
		addNode(newNodeIndex, newNode);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDPageNode#collectAnnotations(java.util.List)
	 */
	@Override
	protected void collectAnnotations(List annotations) {
		for (Iterator it = getKids().iterator(); it.hasNext();) {
			PDPageNode childNode = (PDPageNode) it.next();
			childNode.collectAnnotations(annotations);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDObject#cosGetExpectedType()
	 */
	@Override
	protected COSName cosGetExpectedType() {
		return CN_Type_Pages;
	}

	protected COSObject cosSetKids(COSArray newKids) {
		setCount(newKids.size());
		return cosSetField(DK_Kids, newKids);
	}

	protected void exchangeNode(PDPageNode oldNode, PDPageNode newNode) {
		COSArray cosKids = cosGetField(DK_Kids).asArray();
		if (cosKids == null) {
			return;
		}
		int index = cosKids.indexOf(oldNode.cosGetDict());
		if (index >= 0) {
			oldNode.setParent(null);
			newNode.setParent(this);
			cosKids.set(index, newNode.cosGetDict());
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDPageNode#getCount()
	 */
	@Override
	public int getCount() {
		return getFieldInt(DK_Count, 0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDPageNode#getFirstNode()
	 */
	@Override
	public PDPageNode getFirstNode() {
		if (getKids().isEmpty()) {
			return null;
		}
		return (PDPageNode) getKids().get(0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDPageNode#getFirstPage()
	 */
	@Override
	public PDPage getFirstPage() {
		for (Iterator i = getKids().iterator(); i.hasNext();) {
			PDPageNode node = (PDPageNode) i.next();
			PDPage page = node.getFirstPage();
			if (page != null) {
				return page;
			}
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDObject#getGenericChildren()
	 */
	@Override
	public List getGenericChildren() {
		return getKids();
	}

	/**
	 * Get the list of all {@link PDPageNode} instances that are children of the
	 * receiver.
	 * 
	 * @return an ArrayList
	 */
	public List getKids() {
		List kids = null;
		if (cachedKids != null) {
			kids = cachedKids.get();
		}
		if (kids == null) {
			kids = new ArrayList();
			COSArray cosKids = cosGetField(DK_Kids).asArray();
			if (cosKids != null) {
				for (Iterator i = cosKids.iterator(); i.hasNext();) {
					COSBasedObject page = PDPageNode.META
							.createFromCos((COSObject) i.next());
					if (page != null) {
						kids.add(page);
					}
				}
				cosKids.addObjectListener(this);
			}
			cachedKids = new SoftReference(kids);
		}
		return kids;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDPageNode#getLast()
	 */
	@Override
	public PDPageNode getLastNode() {
		List theKids = getKids();
		if (theKids.isEmpty()) {
			return null;
		}
		return (PDPageNode) theKids.get(theKids.size() - 1);
	}

	@Override
	public PDPage getLastPage() {
		List theKids = getKids();
		for (int i = theKids.size() - 1; i >= 0; i--) {
			PDPageNode node = (PDPageNode) theKids.get(i);
			PDPage result = node.getLastPage();
			if (result != null) {
				return result;
			}
		}
		return null;
	}

	/**
	 * Get the successor of node in the collection of children. If node is not a
	 * direct child return null. If page is the last child, lookup the next page
	 * via the parent of this.
	 * 
	 * @param node
	 *            The page whose successor is requested.
	 * 
	 * @return Get the next page in the collection of children after page.
	 */
	protected PDPageNode getNextNode(PDPageNode node) {
		List children = getKids();
		int index = children.indexOf(node);
		if (index < 0) {
			return null;
		}
		if (index < (children.size() - 1)) {
			return (PDPageNode) children.get(index + 1);
		}
		if (getParent() == null) {
			return null;
		}
		return getParent().getNextNode(this);
	}

	/**
	 * Get the next page in the collection of children after page. If page is
	 * not a direct child return null. If page is the last child, lookup the
	 * next page via the parent of this.
	 * 
	 * @param page
	 *            The page whose successor is requested.
	 * 
	 * @return Get the next page in the collection of children after page.
	 */
	protected PDPage getNextPage(PDPage page) {
		PDPage nextPage = null;
		for (PDPageNode nextNode = getNextNode(page); nextNode != null; nextNode = nextNode
				.getNextNode()) {
			nextPage = nextNode.getFirstPage();
			if (nextPage != null) {
				break;
			}
		}
		return nextPage;
	}

	/**
	 * The 0 based index of node within this and all
	 * its ancestors, 0 if not found.
	 * 
	 * @param node
	 *            node whose position is determined.
	 * 
	 * @return The 0 based index of node within this
	 *         and all its ancestors, 0 if not found.
	 */
	protected int getNodeIndex(PDPageNode node) {
		int nodePos = 0;
		for (Iterator i = getKids().iterator(); i.hasNext();) {
			PDPageNode child = (PDPageNode) i.next();
			if (child == node) {
				break;
			}
			nodePos += child.getCount();
		}
		PDPageTree parent = getParent();
		if (parent == null) {
			return nodePos;
		}
		return nodePos + parent.getNodeIndex(this);
	}

	/**
	 * Get the predecessor of node in the collection of children. If node is not
	 * a direct child return null. If page is the first child, lookup the
	 * previous page via the parent of this.
	 * 
	 * @param node
	 *            The page whose predecessor is requested.
	 * 
	 * @return Get the predecessor of node in the collection of children.
	 */
	protected PDPageNode getPreviousNode(PDPageNode node) {
		List children = getKids();
		int index = children.indexOf(node);
		if (index < 0) {
			return null;
		}
		if (index > 0) {
			return (PDPageNode) children.get(index - 1);
		}
		if (getParent() == null) {
			return null;
		}
		return getParent().getPreviousNode(this);
	}

	/**
	 * Get the previous page in the collection of children before page. If page
	 * is not a direct child return null. If page is the first child, lookup the
	 * previous page via the parent of this.
	 * 
	 * @param page
	 *            The page whose predecessor is requested.
	 * 
	 * @return Get the previous page in the collection of children before page.
	 */
	protected PDPage getPreviousPage(PDPage page) {
		PDPage previousPage = null;
		for (PDPageNode previousNode = getPreviousNode(page); previousNode != null; previousNode = previousNode
				.getPreviousNode()) {
			previousPage = previousNode.getLastPage();
			if (previousPage != null) {
				break;
			}
		}
		return previousPage;
	}

	/**
	 * increment the number of pages and propagate change to parent node
	 * 
	 * @param delta
	 *            number of pages to add
	 */
	protected void incrementCount(int delta) {
		int oldValue = getCount();
		cosSetField(DK_Count, COSInteger.create(oldValue + delta));
		if (getParent() != null) {
			getParent().incrementCount(delta);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDComplexBase#initializeFromScratch()
	 */
	@Override
	protected void initializeFromScratch() {
		super.initializeFromScratch();
		cosSetField(DK_Kids, COSArray.create());
		cosSetField(DK_Count, COSInteger.create(0));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDPageNode#invalidateCaches()
	 */
	@Override
	public void invalidateCaches() {
		super.invalidateCaches();
		COSArray cosKids = cosGetField(DK_Kids).asArray();
		if (cosKids != null) {
			cosKids.removeObjectListener(this);
		}
		cachedKids = null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDPageNode#isPage()
	 */
	@Override
	public boolean isPage() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.intarsys.pdf.pd.PDPageNode#isValid()
	 */
	@Override
	public boolean isValid() {
		PDPageTree tempParent = getParent();
		if (tempParent == null) {
			PDDocument tempDoc = getDoc();
			if (tempDoc == null) {
				return false;
			}
			// check root
			return tempDoc.getPageTree() == this;
		}
		return tempParent.isValid();
	}

	/**
	 * Rebalance this.
	 * 
	 * @return The new {@link PDPageTree} created or null if nothing changed.
	 */
	public PDPageTree rebalance() {
		COSArray cosKids = cosGetField(DK_Kids).asArray();
		if (cosKids == null) {
			return null;
		}
		if (cosKids.size() > MAX_KIDS) {
			PDPageTree newNode = (PDPageTree) PDPageTree.META.createNew();
			if (getParent() != null) {
				getParent().exchangeNode(this, newNode);
			}
			newNode.addNode(this);
			return newNode;
		}
		return null;
	}

	/**
	 * Remove a node
	 * 
	 * @param node
	 *            The child node to remove
	 */
	public void removeNode(PDPageNode node) {
		COSArray cosKids = cosGetField(DK_Kids).asArray();
		if (cosKids != null) {
			if (cosKids.remove(node.cosGetObject())) {
				incrementCount(-node.getCount());
				node.setParent(null);
			}
		}
	}

	/**
	 * Set the number of pages.
	 * 
	 * @param newCount
	 *            number of pages to add
	 */
	protected void setCount(int newCount) {
		int oldValue = getCount();
		setFieldInt(DK_Count, newCount);
		if (getParent() != null) {
			getParent().incrementCount(newCount - oldValue);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy