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

com.itextpdf.kernel.utils.objectpathitems.ObjectPath Maven / Gradle / Ivy

There is a newer version: 9.0.0
Show newest version
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2023 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
 */
package com.itextpdf.kernel.utils.objectpathitems;

import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.kernel.pdf.PdfIndirectReference;
import com.itextpdf.kernel.pdf.PdfName;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * Class that helps to find two corresponding objects in the compared documents and also keeps track of the
 * already met during comparing process parent indirect objects.
 * 

* You could say that CompareObjectPath instance consists of two parts: direct path and indirect path. * Direct path defines path to the currently comparing objects in relation to base objects. It could be empty, * which would mean that currently comparing objects are base objects themselves. Base objects are the two indirect * objects from the comparing documents which are in the same position in the pdf trees. Another part, indirect path, * defines which indirect objects were met during comparison process to get to the current base objects. Indirect path * is needed to avoid infinite loops during comparison. */ public class ObjectPath { protected Stack path = new Stack<>(); private PdfIndirectReference baseCmpObject; private PdfIndirectReference baseOutObject; private Stack indirects = new Stack<>(); /** * Creates empty ObjectPath. */ public ObjectPath() { } /** * Creates an {@link ObjectPath} object from another {@link ObjectPath} object, passed as argument. * * @param objectPath an {@link ObjectPath} object to create from. */ public ObjectPath(ObjectPath objectPath) { this.baseCmpObject = objectPath.getBaseCmpObject(); this.baseOutObject = objectPath.getBaseOutObject(); this.path = (Stack) objectPath.getLocalPath(); this.indirects = (Stack) objectPath.getIndirectPath(); } /** * Creates CompareObjectPath with corresponding base objects in two documents. * * @param baseCmpObject base object in cmp document. * @param baseOutObject base object in out document. */ public ObjectPath(PdfIndirectReference baseCmpObject, PdfIndirectReference baseOutObject) { this.baseCmpObject = baseCmpObject; this.baseOutObject = baseOutObject; indirects.push(new IndirectPathItem(baseCmpObject, baseOutObject)); } public ObjectPath(PdfIndirectReference baseCmpObject, PdfIndirectReference baseOutObject, Stack path, Stack indirects) { this.baseCmpObject = baseCmpObject; this.baseOutObject = baseOutObject; this.path = (Stack) path.clone(); this.indirects = (Stack) indirects.clone(); } /** * Creates a new ObjectPath instance with two new given base objects, which are supposed to be nested in the base * objects of the current instance of the ObjectPath. This method is used to avoid infinite loop in case of * circular references in pdf documents objects structure. *

* Basically, this method creates copy of the current CompareObjectPath instance, but resets * information of the direct paths, and also adds current CompareObjectPath instance base objects to the indirect * references chain that denotes a path to the new base objects. * * @param baseCmpObject new base object in cmp document. * @param baseOutObject new base object in out document. * @return new ObjectPath instance, which stores chain of the indirect references * which were already met to get to the new base objects. */ public ObjectPath resetDirectPath(PdfIndirectReference baseCmpObject, PdfIndirectReference baseOutObject) { final ObjectPath newPath = new ObjectPath(baseCmpObject, baseOutObject, new Stack(), (Stack) indirects.clone()); newPath.indirects.push(new IndirectPathItem(baseCmpObject, baseOutObject)); return newPath; } /** * This method is used to define if given objects were already met in the path to the current base objects. * If this method returns true it basically means that we found a loop in the objects structure and that we * already compared these objects. * * @param cmpObject cmp object to check if it was already met in base objects path. * @param outObject out object to check if it was already met in base objects path. * @return true if given objects are contained in the path and therefore were already compared. */ public boolean isComparing(PdfIndirectReference cmpObject, PdfIndirectReference outObject) { return indirects.contains(new IndirectPathItem(cmpObject, outObject)); } /** * Adds array item to the direct path. See {@link ArrayPathItem}. * * @param index index in the array of the direct object to be compared. */ public void pushArrayItemToPath(int index) { path.push(new ArrayPathItem(index)); } /** * Adds dictionary item to the direct path. See {@link DictPathItem}. * * @param key key in the dictionary to which corresponds direct object to be compared. */ public void pushDictItemToPath(PdfName key) { path.push(new DictPathItem(key)); } /** * Adds offset item to the direct path. See {@link OffsetPathItem}. * * @param offset offset to the specific byte in the stream that is compared. */ public void pushOffsetToPath(int offset) { path.push(new OffsetPathItem(offset)); } /** * Removes the last path item from the direct path. */ public void pop() { path.pop(); } /** * Gets local (or direct) path that denotes sequence of the path items from base object to the comparing * direct object. * * @return direct path to the comparing object. */ public Stack getLocalPath() { return (Stack) path.clone(); } /** * Gets indirect path which denotes sequence of the indirect references that were passed in comparing process * to get to the current base objects. * * @return indirect path to the current base objects. */ public Stack getIndirectPath() { return (Stack) indirects.clone(); } /** * Method returns current base {@link PdfIndirectReference} object in the cmp document. * * @return current base {@link PdfIndirectReference} object in the cmp document. */ public PdfIndirectReference getBaseCmpObject() { return baseCmpObject; } /** * Method returns current base {@link PdfIndirectReference} object in the out document. * * @return current base object in the out document. */ public PdfIndirectReference getBaseOutObject() { return baseOutObject; } /** * Creates an xml node that describes a direct path stored in this ObjectPath instance. * * @param document xml document, to which this xml node will be added. * @return an xml node describing direct path. */ public Node toXmlNode(Document document) { final Element element = document.createElement("path"); final Element baseNode = document.createElement("base"); baseNode.setAttribute("cmp", MessageFormatUtil.format("{0} {1} obj", baseCmpObject.getObjNumber(), baseCmpObject.getGenNumber())); baseNode.setAttribute("out", MessageFormatUtil.format("{0} {1} obj", baseOutObject.getObjNumber(), baseOutObject.getGenNumber())); element.appendChild(baseNode); final Stack pathClone = (Stack) path.clone(); final List localPathItems = new ArrayList<>(path.size()); for (int i = 0; i < path.size(); ++i) { localPathItems.add(pathClone.pop()); } for (int i = localPathItems.size() - 1; i >= 0; --i) { element.appendChild(localPathItems.get(i).toXmlNode(document)); } return element; } /** * Method returns a string representation of the direct path stored in this {@link ObjectPath} instance. * * @return a string representation of the direct path. */ @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(MessageFormatUtil.format("Base cmp object: {0} obj. Base out object: {1} obj", baseCmpObject, baseOutObject)); final Stack pathClone = (Stack) path.clone(); final List localPathItems = new ArrayList<>(path.size()); for (int i = 0; i < path.size(); ++i) { localPathItems.add(pathClone.pop()); } for (int i = localPathItems.size() - 1; i >= 0; --i) { sb.append('\n'); sb.append(localPathItems.get(i).toString()); } return sb.toString(); } /** * Method returns a hash code of this {@link ObjectPath} instance. * * @return a int hash code of this {@link ObjectPath} instance. */ @Override public int hashCode() { // TODO: DEVSIX-4756 indirect reference hashCode should use hashCode method of indirect // reference. For now we need to write custom logic as some tests rely on sequential // reopening of the same document which affects with not equal indirect reference // hashCodes (after the update which starts counting the document in indirect reference // hashCode) int baseCmpObjectHashCode = 0; if (baseCmpObject != null) { baseCmpObjectHashCode = baseCmpObject.getObjNumber() * 31 + baseCmpObject.getGenNumber(); } int baseOutObjectHashCode = 0; if (baseOutObject != null) { baseOutObjectHashCode = baseOutObject.getObjNumber() * 31 + baseOutObject.getGenNumber(); } int hashCode = baseCmpObjectHashCode * 31 + baseOutObjectHashCode; for (LocalPathItem pathItem : path) { hashCode *= 31; hashCode += pathItem.hashCode(); } return hashCode; } /** * Method returns true if this {@link ObjectPath} instance equals to the passed object. * * @return true - if this {@link ObjectPath} instance equals to the passed object. */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ObjectPath that = (ObjectPath) obj; // TODO: DEVSIX-4756 indirect reference comparing should use equals method of indirect // reference. For now we need to write custom logic as some tests rely on sequential // reopening of the same document which affects with not equal indirect references // (after the update which starts counting the document in indirect reference equality) boolean isBaseCmpObjectEqual; if (baseCmpObject == that.baseCmpObject) { isBaseCmpObjectEqual = true; } else if (baseCmpObject == null || that.baseCmpObject == null || baseCmpObject.getClass() != that.baseCmpObject.getClass()) { isBaseCmpObjectEqual = false; } else { isBaseCmpObjectEqual = baseCmpObject.getObjNumber() == that.baseCmpObject.getObjNumber() && baseCmpObject.getGenNumber() == that.baseCmpObject.getGenNumber(); } boolean isBaseOutObjectEqual; if (baseOutObject == that.baseOutObject) { isBaseOutObjectEqual = true; } else if (baseOutObject == null || that.baseOutObject == null || baseOutObject.getClass() != that.baseOutObject.getClass()) { isBaseOutObjectEqual = false; } else { isBaseOutObjectEqual = baseOutObject.getObjNumber() == that.baseOutObject.getObjNumber() && baseOutObject.getGenNumber() == that.baseOutObject.getGenNumber(); } return isBaseCmpObjectEqual && isBaseOutObjectEqual && path.equals(((ObjectPath) obj).path); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy