class PdfSmartCopy extends PdfCopy {

    /**
     * the cache with the streams and references.
     */
    private Map<ByteStore, PdfIndirectReference> streamMap = null;

    /**
     * Creates a PdfSmartCopy instance.
     *
     * @param os       the OutputStream
     * @param document the document
     * @throws DocumentException on error
     */
    public PdfSmartCopy(Document document, OutputStream os) throws DocumentException {
        super(document, os);
        this.streamMap = new HashMap<>();
    }

    /**
     * Translate a PRIndirectReference to a PdfIndirectReference In addition, translates the object numbers, and copies
     * the referenced object to the output file if it wasn't available in the cache yet. If it's in the cache, the
     * reference to the already used stream is returned.
     * <p>
     * NB: PRIndirectReferences (and PRIndirectObjects) really need to know what file they came from, because each file
     * has its own namespace. The translation we do from their namespace to ours is *at best* heuristic, and guaranteed
     * to fail under some circumstances.
     */
    protected PdfIndirectReference copyIndirect(PRIndirectReference in) throws IOException, BadPdfFormatException {<FILL_FUNCTION_BODY>}

    static class ByteStore {

        private final int MAX_LEVELS = 100;
        private byte[] b;
        private int hash;
        private MessageDigest md5;

        ByteStore(PRStream str) throws IOException {
            try {
                md5 = MessageDigest.getInstance("MD5");
            } catch (Exception e) {
                throw new ExceptionConverter(e);
            }
            ByteBuffer bb = new ByteBuffer();
            int level = MAX_LEVELS;
            serObject(str, level, bb);
            this.b = bb.toByteArray();
            md5 = null;
        }

        private void serObject(PdfObject obj, int level, ByteBuffer bb) throws IOException {
            if (level <= 0) {
                throw new IOException("Max level reached");
            }
            if (obj == null) {
                bb.append("$Lnull");
                return;
            }
            obj = PdfReader.getPdfObject(obj);
            if (obj.isStream()) {
                bb.append("$B");
                serDic((PdfDictionary) obj, level - 1, bb);
                md5.reset();
                bb.append(md5.digest(PdfReader.getStreamBytesRaw((PRStream) obj)));
            } else if (obj.isDictionary()) {
                serDic((PdfDictionary) obj, level - 1, bb);
            } else if (obj.isArray()) {
                serArray((PdfArray) obj, level - 1, bb);
            } else if (obj.isString()) {
                bb.append("$S").append(obj.toString());
            } else if (obj.isName()) {
                bb.append("$N").append(obj.toString());
            } else {
                bb.append("$L").append(obj.toString());
            }
        }

        private void serDic(PdfDictionary dic, int level, ByteBuffer bb) throws IOException {
            bb.append("$D");
            if (level <= 0) {
                throw new IOException("Max level reached");
            }
            Object[] keys = dic.getKeys().toArray();
            Arrays.sort(keys);
            for (Object key : keys) {
                serObject((PdfObject) key, level, bb);
                serObject(dic.get((PdfName) key), level, bb);
            }
        }

        private void serArray(PdfArray array, int level, ByteBuffer bb) throws IOException {
            bb.append("$A");
            if (level <= 0) {
                throw new IOException("Max level reached");
            }
            for (int k = 0; k < array.size(); ++k) {
                serObject(array.getPdfObject(k), level, bb);
            }
        }


        public boolean equals(Object obj) {
            if (!(obj instanceof ByteStore)) {
                return false;
            }
            if (hashCode() != obj.hashCode()) {
                return false;
            }
            return Arrays.equals(b, ((ByteStore) obj).b);
        }

        public int hashCode() {
            if (hash == 0) {
                for (byte b1 : b) {
                    hash = hash * 31 + (b1 & 0xff);
                }
            }
            return hash;
        }
    }
}

        PdfObject srcObj = PdfReader.getPdfObjectRelease(in);
        ByteStore streamKey = null;
        boolean validStream = false;
        if (srcObj == null) {
            return null;
        }
        if (srcObj.isStream()) {
            try {
                streamKey = new ByteStore((PRStream) srcObj);
                validStream = true;
                PdfIndirectReference streamRef = streamMap.get(streamKey);
                if (streamRef != null) {
                    return streamRef;
                }
            } catch (IOException ioe) {
                //
            }
        }

        PdfIndirectReference theRef;
        RefKey key = new RefKey(in);
        IndirectReferences iRef = indirects.get(key);
        if (iRef != null) {
            theRef = iRef.getRef();
            if (iRef.getCopied()) {
                return theRef;
            }
        } else {
            theRef = body.getPdfIndirectReference();
            iRef = new IndirectReferences(theRef);
            indirects.put(key, iRef);
        }
        if (srcObj.isDictionary()) {
            PdfObject type = PdfReader.getPdfObjectRelease(((PdfDictionary) srcObj).get(PdfName.TYPE));
            if (PdfName.PAGE.equals(type)) {
                return theRef;
            }
        }
        iRef.setCopied();

        if (validStream) {
            streamMap.put(streamKey, theRef);
        }

        PdfObject obj = copyObject(srcObj);
        addToBody(obj, theRef);
        return theRef;
    
<methods>public void <init>(com.lowagie.text.Document, java.io.OutputStream) throws com.lowagie.text.DocumentException,public com.lowagie.text.pdf.PdfIndirectReference add(com.lowagie.text.pdf.PdfOutline) ,public void addAnnotation(com.lowagie.text.pdf.PdfAnnotation) ,public void addPage(com.lowagie.text.pdf.PdfImportedPage) throws java.io.IOException, com.lowagie.text.pdf.BadPdfFormatException,public void addPage(com.lowagie.text.Rectangle, int) ,public void close() ,public void copyAcroForm(com.lowagie.text.pdf.PdfReader) throws java.io.IOException, com.lowagie.text.pdf.BadPdfFormatException,public com.lowagie.text.pdf.PdfCopy.PageStamp createPageStamp(com.lowagie.text.pdf.PdfImportedPage) ,public void freeReader(com.lowagie.text.pdf.PdfReader) throws java.io.IOException,public com.lowagie.text.pdf.PdfImportedPage getImportedPage(com.lowagie.text.pdf.PdfReader, int) ,public boolean isRotateContents() ,public void setRotateContents(boolean) <variables>protected com.lowagie.text.pdf.PdfIndirectReference acroForm,protected com.lowagie.text.pdf.PdfArray fieldArray,protected HashMap<com.lowagie.text.pdf.PdfTemplate,java.lang.Object> fieldTemplates,protected HashMap<com.lowagie.text.pdf.PdfReader,HashMap<com.lowagie.text.pdf.PdfCopy.RefKey,com.lowagie.text.pdf.PdfCopy.IndirectReferences>> indirectMap,protected HashMap<com.lowagie.text.pdf.PdfCopy.RefKey,com.lowagie.text.pdf.PdfCopy.IndirectReferences> indirects,protected int[] namePtr,protected com.lowagie.text.pdf.PdfReader reader,private boolean rotateContents