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

com.itextpdf.kernel.pdf.PdfObject Maven / Gradle / Ivy

The newest version!
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2024 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.pdf;

import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.exceptions.BadPasswordException;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.utils.ICopyFilter;
import com.itextpdf.kernel.utils.NullCopyFilter;
import com.itextpdf.kernel.validation.context.PdfObjectValidationContext;

import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class PdfObject {


    public static final byte ARRAY = 1;
    public static final byte BOOLEAN = 2;
    public static final byte DICTIONARY = 3;
    public static final byte LITERAL = 4;
    public static final byte INDIRECT_REFERENCE = 5;
    public static final byte NAME = 6;
    public static final byte NULL = 7;
    public static final byte NUMBER = 8;
    public static final byte STREAM = 9;
    public static final byte STRING = 10;

    /**
     * Indicates if the object has been flushed.
     */
    protected static final short FLUSHED = 1;

    /**
     * Indicates that the indirect reference of the object could be reused or have to be marked as free.
     */
    protected static final short FREE = 1 << 1;

    /**
     * Indicates that definition of the indirect reference of the object still not found (e.g. keys in XRefStm).
     */
    protected static final short READING = 1 << 2;

    /**
     * Indicates that object changed (is used in append mode).
     */
    protected static final short MODIFIED = 1 << 3;

    /**
     * Indicates that the indirect reference of the object represents ObjectStream from original document.
     * When PdfReader read ObjectStream reference marked as OriginalObjectStream
     * to avoid further reusing.
     */
    protected static final short ORIGINAL_OBJECT_STREAM = 1 << 4;

    /**
     * Marks objects that shall be written to the output document. Shouldn't be used on purpose
     * since this flag is handled internally: option is needed to build the correct PDF objects
     * tree when closing the document. As a result it avoids writing unused (removed) objects.
     */
    protected static final short MUST_BE_FLUSHED = 1 << 5;

    /**
     * Indicates that the object shall be indirect when it is written to the document.
     * It is used to postpone the creation of indirect reference for the objects that shall be indirect,
     * so it is possible to create such objects without PdfDocument instance.
     */
    protected static final short MUST_BE_INDIRECT = 1 << 6;

    /**
     * Indicates that the object is highly sensitive and we do not want to release it even if release() is called.
     * This flag can be set in stamping mode in object wrapper constructors and is automatically set when setModified
     * flag is set (we do not want to release changed objects).
     * The flag is set automatically for some wrappers that need document even in reader mode (FormFields etc).
     */
    protected static final short FORBID_RELEASE = 1 << 7;

    /**
     * Indicates that we do not want this object to be ever written into the resultant document
     * (because of multiple objects read from the same reference inconsistency).
     */
    protected static final short READ_ONLY = 1 << 8;

    /**
     * Indicates that this object is not encrypted in the encrypted document.
     * E.g. digital signature dictionary /Contents entry shall not be encrypted.
     */
    protected static final short UNENCRYPTED = 1 << 9;

    /**
     * If object is flushed the indirect reference is kept here.
     */
    protected PdfIndirectReference indirectReference = null;

    /**
     * Indicate same special states of PdfIndirectObject or PdfObject like @see Free, @see Reading, @see Modified.
     */
    private short state;

    /**
     * Gets object type.
     *
     * @return object type.
     */
    public abstract byte getType();

    /**
     * Flushes the object to the document.
     */
    public final void flush() {
        flush(true);
    }

    /**
     * Flushes the object to the document.
     *
     * @param canBeInObjStm indicates whether object can be placed into object stream.
     */
    public final void flush(boolean canBeInObjStm) {
        if (isFlushed() || getIndirectReference() == null || getIndirectReference().isFree()) {
// TODO DEVSIX-744: here we should take into account and log the case when object is MustBeIndirect,
//  but has no indirect reference
//            Logger logger = LoggerFactory.getLogger(PdfObject.class);
//            if (isFlushed()) {
//                logger.warn("Meaningless call, the object has already flushed");
//            } else if (isIndirect()){
//                logger.warn("Meaningless call, the object will be transformed into indirect on closing," +
//                " but at the moment it doesn't have an indirect reference and therefore couldn't be flushed. " +
//                        "To flush it now call makeIndirect(PdfDocument) method before calling flush() method.");
//            } else {
//                logger.warn("Meaningless call, the object is direct object. It will be flushed along with" +
//                " the indirect object that contains it.");
//            }
            return;
        }
        try {
            PdfDocument document = getIndirectReference().getDocument();
            if (document != null) {
                if (document.isAppendMode() && !isModified()) {
                    Logger logger = LoggerFactory.getLogger(PdfObject.class);
                    logger.info(IoLogMessageConstant.PDF_OBJECT_FLUSHING_NOT_PERFORMED);
                    return;
                }
                document.checkIsoConformance(new PdfObjectValidationContext(this));
                document.flushObject(this, canBeInObjStm && getType() != STREAM
                        && getType() != INDIRECT_REFERENCE && getIndirectReference().getGenNumber() == 0);
            }
        } catch (IOException e) {
            throw new PdfException(KernelExceptionMessageConstant.CANNOT_FLUSH_OBJECT, e, this);
        }
    }

    /**
     * Gets the indirect reference associated with the object.
     * The indirect reference is used when flushing object to the document.
     *
     * @return indirect reference.
     */
    public PdfIndirectReference getIndirectReference() {
        return indirectReference;
    }

    /**
     * Checks if object is indirect.
     * 
* Note: * Return value {@code true} doesn't necessarily mean that indirect reference of this object * is not null at the moment. Object could be marked as indirect and * be transformed to indirect on flushing. *
* E.g. all PdfStreams are transformed to indirect objects when they are written, but they don't always * have indirect references at any given moment. * * @return returns {@code true} if object is indirect or is to be indirect in the resultant document. */ public boolean isIndirect() { return indirectReference != null || checkState(PdfObject.MUST_BE_INDIRECT); } /** * Marks object to be saved as indirect. * * @param document a document the indirect reference will belong to. * @param reference indirect reference which will be associated with this document * * @return object itself. */ public PdfObject makeIndirect(PdfDocument document, PdfIndirectReference reference) { if (document == null || indirectReference != null) { return this; } if (document.getWriter() == null) { throw new PdfException( KernelExceptionMessageConstant.THERE_IS_NO_ASSOCIATE_PDF_WRITER_FOR_MAKING_INDIRECTS); } if (reference == null) { indirectReference = document.createNextIndirectReference(); indirectReference.setRefersTo(this); } else { reference.setState(MODIFIED); indirectReference = reference; indirectReference.setRefersTo(this); } setState(FORBID_RELEASE); clearState(MUST_BE_INDIRECT); return this; } /** * Marks object to be saved as indirect. * * @param document a document the indirect reference will belong to. * * @return object itself. */ public PdfObject makeIndirect(PdfDocument document) { return makeIndirect(document, null); } /** * Indicates is the object has been flushed or not. * * @return true if object has been flushed, otherwise false. */ public boolean isFlushed() { PdfIndirectReference indirectReference = getIndirectReference(); return (indirectReference != null && indirectReference.checkState(FLUSHED)); } /** * Indicates is the object has been set as modified or not. Useful for incremental updates (e.g. appendMode). * * @return true is object has been set as modified, otherwise false. */ public boolean isModified() { PdfIndirectReference indirectReference = getIndirectReference(); return (indirectReference != null && indirectReference.checkState(MODIFIED)); } /** * Creates clone of the object which belongs to the same document as original object. * New object shall not be used in other documents. * * @return cloned object. */ @SuppressWarnings("CloneDoesntCallSuperClone") @Override public PdfObject clone() { return clone(NullCopyFilter.getInstance()); } /** * Creates clone of the object which belongs to the same document as original object. * New object shall not be used in other documents. * * @param filter Filter what will be copied or not * * @return cloned object. */ public PdfObject clone(ICopyFilter filter) { PdfObject newObject = newInstance(); if (indirectReference != null || checkState(MUST_BE_INDIRECT)) { newObject.setState(MUST_BE_INDIRECT); } newObject.copyContent(this, null, filter); return newObject; } /** * Copies object to a specified document. *

* NOTE: Works only for objects that are read from document opened in reading mode, * otherwise an exception is thrown. * * @param document document to copy object to. * * @return copied object. */ public PdfObject copyTo(PdfDocument document) { return copyTo(document, true, NullCopyFilter.getInstance()); } /** * Copies object to a specified document. *

* NOTE: Works only for objects that are read from document opened in reading mode, * otherwise an exception is thrown. * * @param document document to copy object to. * @param allowDuplicating indicates if to allow copy objects which already have been copied. * If object is associated with any indirect reference and allowDuplicating is * false then already existing reference will be returned instead of copying object. * If allowDuplicating is true then object will be copied and new indirect * reference will be assigned. * * @return copied object. */ public PdfObject copyTo(PdfDocument document, boolean allowDuplicating) { return copyTo(document, allowDuplicating, NullCopyFilter.getInstance()); } /** * Copies object to a specified document. *

* NOTE: Works only for objects that are read from document opened in reading mode, * otherwise an exception is thrown. * * @param document document to copy object to. * @param copyFilter {@link ICopyFilter} a filter to apply while copying arrays and dictionaries * Use {@link NullCopyFilter} for no filtering * * @return copied object. */ public PdfObject copyTo(PdfDocument document, ICopyFilter copyFilter) { return copyTo(document, true, copyFilter); } /** * Copies object to a specified document. *

* NOTE: Works only for objects that are read from document opened in reading mode, * otherwise an exception is thrown. * * @param document document to copy object to. * @param allowDuplicating indicates if to allow copy objects which already have been copied. * If object is associated with any indirect reference and allowDuplicating is false * then already existing reference will be returned instead of copying object. * If allowDuplicating is true then object will be copied and new indirect reference * will be assigned. * @param copyFilter {@link ICopyFilter} a filter to apply while copying arrays and dictionaries * Use {@link NullCopyFilter} for no filtering * * @return copied object. */ public PdfObject copyTo(PdfDocument document, boolean allowDuplicating, ICopyFilter copyFilter) { if (document == null) throw new PdfException(KernelExceptionMessageConstant.DOCUMENT_FOR_COPY_TO_CANNOT_BE_NULL); if (indirectReference != null) { // TODO checkState(MUST_BE_INDIRECT) now is always false, because indirectReference != null. See also // DEVSIX-602 if (indirectReference.getWriter() != null || checkState(MUST_BE_INDIRECT)) { throw new PdfException( KernelExceptionMessageConstant.CANNOT_COPY_INDIRECT_OBJECT_FROM_THE_DOCUMENT_THAT_IS_BEING_WRITTEN); } if (!indirectReference.getReader().isOpenedWithFullPermission()) { throw new BadPasswordException(BadPasswordException.PdfReaderNotOpenedWithOwnerPassword); } } return processCopying(document, allowDuplicating, copyFilter); } /** * Sets the 'modified' flag to the indirect object, the flag denotes that the object was modified since * the document opening. * It is recommended to set this flag after changing any PDF object. *

* For example flag is used in the append mode (see {@link StampingProperties#useAppendMode()}). * In append mode the whole document is preserved as is, and only changes to the document are * appended to the end of the document file. Because of this, only modified objects need to be flushed and are * allowed to be flushed (i.e. to be written). * * @return this {@link PdfObject} instance. */ public PdfObject setModified() { if (indirectReference != null) { indirectReference.setState(MODIFIED); setState(FORBID_RELEASE); } return this; } /** * Checks if it's forbidden to release this {@link PdfObject} instance. * Some objects are vital for the living period of {@link PdfDocument} or may be * prevented from releasing by high-level entities dealing with the objects. * Also it's not possible to release the objects that have been modified. * * @return true if releasing this object is forbidden, otherwise false */ public boolean isReleaseForbidden() { return checkState(FORBID_RELEASE); } public void release() { // In case ForbidRelease flag is set, release will not be performed. if (isReleaseForbidden()) { Logger logger = LoggerFactory.getLogger(PdfObject.class); logger.warn(IoLogMessageConstant.FORBID_RELEASE_IS_SET); } else { if (indirectReference != null && indirectReference.getReader() != null && !indirectReference.checkState(FLUSHED)) { indirectReference.refersTo = null; indirectReference = null; setState(READ_ONLY); } // TODO DEVSIX-4020. Log reasonless call of method } } /** * Checks if this PdfObject is of the type * PdfNull. * * @return true or false */ public boolean isNull() { return getType() == NULL; } /** * Checks if this PdfObject is of the type * PdfBoolean. * * @return true or false */ public boolean isBoolean() { return getType() == BOOLEAN; } /** * Checks if this PdfObject is of the type * PdfNumber. * * @return true or false */ public boolean isNumber() { return getType() == NUMBER; } /** * Checks if this PdfObject is of the type * PdfString. * * @return true or false */ public boolean isString() { return getType() == STRING; } /** * Checks if this PdfObject is of the type * PdfName. * * @return true or false */ public boolean isName() { return getType() == NAME; } /** * Checks if this PdfObject is of the type * PdfArray. * * @return true or false */ public boolean isArray() { return getType() == ARRAY; } /** * Checks if this PdfObject is of the type * PdfDictionary. * * @return true or false */ public boolean isDictionary() { return getType() == DICTIONARY; } /** * Checks if this PdfObject is of the type * PdfStream. * * @return true or false */ public boolean isStream() { return getType() == STREAM; } /** * Checks if this PdfObject is of the type * PdfIndirectReference. * * @return true if this is an indirect reference, * otherwise false */ public boolean isIndirectReference() { return getType() == INDIRECT_REFERENCE; } protected PdfObject setIndirectReference(PdfIndirectReference indirectReference) { this.indirectReference = indirectReference; return this; } /** * Checks if this PdfObject is of the type * PdfLiteral. * * @return true if this is a literal, * otherwise false */ public boolean isLiteral() { return getType() == LITERAL; } /** * Creates new instance of object. * * @return new instance of object. */ protected abstract PdfObject newInstance(); /** * Checks state of the flag of current object. * * @param state special flag to check * * @return true if the state was set. */ protected boolean checkState(short state) { return (this.state & state) == state; } /** * Sets special states of current object. * * @param state special flag of current object * * @return this {@link PdfObject} */ protected PdfObject setState(short state) { this.state |= state; return this; } /** * Clear state of the flag of current object. * * @param state special flag state to clear * * @return this {@link PdfObject} */ protected PdfObject clearState(short state) { this.state &= (short) ~state; return this; } /** * Copies object content from object 'from'. * * @param from object to copy content from. * @param document document to copy object to. */ protected void copyContent(PdfObject from, PdfDocument document) { copyContent(from, document, NullCopyFilter.getInstance()); } /** * Copies object content from object 'from'. * * @param from object to copy content from. * @param document document to copy object to. * @param filter {@link ICopyFilter} a filter that will apply on dictionaries and array * Use {@link NullCopyFilter} for no filtering */ protected void copyContent(PdfObject from, PdfDocument document, ICopyFilter filter) { if (isFlushed()) throw new PdfException(KernelExceptionMessageConstant.CANNOT_COPY_FLUSHED_OBJECT, this); } static boolean equalContent(PdfObject obj1, PdfObject obj2) { PdfObject direct1 = obj1 != null && obj1.isIndirectReference() ? ((PdfIndirectReference) obj1).getRefersTo(true) : obj1; PdfObject direct2 = obj2 != null && obj2.isIndirectReference() ? ((PdfIndirectReference) obj2).getRefersTo(true) : obj2; return direct1 != null && direct1.equals(direct2); } /** * Processes two cases of object copying: *

    *
  1. copying to the other document *
  2. cloning inside of the current document *
*

* This two cases are distinguished by the state of {@code document} parameter: * the second case is processed if {@code document} is {@code null}. * * @param documentTo if not null: document to copy object to; otherwise indicates that object is to be cloned. * @param allowDuplicating indicates if to allow copy objects which already have been copied. * If object is associated with any indirect reference and allowDuplicating is false then * already existing reference will be returned instead of copying object. * If allowDuplicating is true then object will be copied and new indirect * reference will be assigned. * * @return copied object. */ PdfObject processCopying(PdfDocument documentTo, boolean allowDuplicating) { return processCopying(documentTo, allowDuplicating, NullCopyFilter.getInstance()); } /** * Processes two cases of object copying: *

    *
  1. copying to the other document *
  2. cloning inside of the current document *
*

* This two cases are distinguished by the state of {@code document} parameter: * the second case is processed if {@code document} is {@code null}. * * @param documentTo if not null: document to copy object to; otherwise indicates that object is to be cloned. * @param allowDuplicating indicates if to allow copy objects which already have been copied. * If object is associated with any indirect reference and allowDuplicating is false then * already existing reference will be returned instead of copying object. * If allowDuplicating is true then object will be copied and new indirect reference will * be assigned. * @param filter filters what will be copies or not * * @return copied object. */ PdfObject processCopying(PdfDocument documentTo, boolean allowDuplicating, ICopyFilter filter) { if (documentTo != null) { //copyTo case PdfWriter writer = documentTo.getWriter(); if (writer == null) throw new PdfException(KernelExceptionMessageConstant.CANNOT_COPY_TO_DOCUMENT_OPENED_IN_READING_MODE); return writer.copyObject(this, documentTo, allowDuplicating, filter); } else { //clone case PdfObject obj = this; if (obj.isIndirectReference()) { PdfObject refTo = ((PdfIndirectReference) obj).getRefersTo(); obj = refTo != null ? refTo : obj; } if (obj.isIndirect() && !allowDuplicating) { return obj; } return obj.clone(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy