Move cloning into its own class

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop-pdf-images/trunk@1878748 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/fop/render/pdf/pdfbox/PDFBoxAdapter.java b/src/java/org/apache/fop/render/pdf/pdfbox/PDFBoxAdapter.java
index f513b6b..a8ac934 100644
--- a/src/java/org/apache/fop/render/pdf/pdfbox/PDFBoxAdapter.java
+++ b/src/java/org/apache/fop/render/pdf/pdfbox/PDFBoxAdapter.java
@@ -24,7 +24,6 @@
 import java.awt.geom.Rectangle2D;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -45,21 +44,15 @@
 
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSBase;
-import org.apache.pdfbox.cos.COSBoolean;
 import org.apache.pdfbox.cos.COSDictionary;
-import org.apache.pdfbox.cos.COSFloat;
-import org.apache.pdfbox.cos.COSInteger;
 import org.apache.pdfbox.cos.COSName;
-import org.apache.pdfbox.cos.COSNull;
 import org.apache.pdfbox.cos.COSObject;
 import org.apache.pdfbox.cos.COSStream;
-import org.apache.pdfbox.cos.COSString;
 
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.PDResources;
-import org.apache.pdfbox.pdmodel.common.COSObjectable;
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.common.PDStream;
 
@@ -71,7 +64,6 @@
 import org.apache.fop.pdf.PDFDictionary;
 import org.apache.fop.pdf.PDFDocument;
 import org.apache.fop.pdf.PDFFormXObject;
-import org.apache.fop.pdf.PDFName;
 import org.apache.fop.pdf.PDFNumber;
 import org.apache.fop.pdf.PDFObject;
 import org.apache.fop.pdf.PDFPage;
@@ -88,14 +80,14 @@
     /** logging instance */
     protected static final Log log = LogFactory.getLog(PDFBoxAdapter.class);
 
-    private static final Set FILTER_FILTER = new HashSet(
-            Arrays.asList(new String[] {"Filter", "DecodeParms"}));
+    protected static final Set<String> FILTER_FILTER = Collections.unmodifiableSet(
+            new HashSet<String>(Arrays.asList("Filter", "DecodeParms")));
 
     private final PDFPage targetPage;
-    private final PDFDocument pdfDoc;
+    protected final PDFDocument pdfDoc;
 
-    private final Map<Object, Object> clonedVersion;
-    private final Map<Object, Object> objectCache;
+    protected final Map<Object, Object> clonedVersion;
+    protected final Map<Object, Object> objectCache;
     private Map<COSName, String> newXObj = new HashMap<COSName, String>();
     private Map<Integer, PDFArray> pageNumbers;
     private Collection<String> parentFonts = new ArrayList<String>();
@@ -134,157 +126,6 @@
         this.currentMCID = currentMCID;
     }
 
-    protected Object cloneForNewDocument(Object base) throws IOException {
-        return cloneForNewDocument(base, base);
-    }
-
-    protected Object cloneForNewDocument(Object base, Object keyBase) throws IOException {
-        return cloneForNewDocument(base, keyBase, Collections.EMPTY_LIST);
-    }
-
-    protected Object cloneForNewDocument(Object base, Object keyBase, Collection exclude) throws IOException {
-        if (base == null) {
-            return null;
-        }
-        Object cached = getCachedClone(keyBase);
-        if (cached != null) {
-            // we are done, it has already been converted.
-            return cached;
-        } else if (base instanceof List) {
-            PDFArray array = new PDFArray();
-            cacheClonedObject(keyBase, array);
-            List list = (List)base;
-            for (Object o : list) {
-                array.add(cloneForNewDocument(o, o, exclude));
-            }
-            return array;
-        } else if (base instanceof COSObjectable && !(base instanceof COSBase)) {
-            Object o = ((COSObjectable)base).getCOSObject();
-            Object retval = cloneForNewDocument(o, o, exclude);
-            return cacheClonedObject(keyBase, retval);
-        } else if (base instanceof COSObject) {
-            return readCOSObject((COSObject) base, exclude);
-        } else if (base instanceof COSArray) {
-            PDFArray newArray = new PDFArray();
-            cacheClonedObject(keyBase, newArray);
-            COSArray array = (COSArray)base;
-            for (int i = 0; i < array.size(); i++) {
-                newArray.add(cloneForNewDocument(array.get(i), array.get(i), exclude));
-            }
-            return newArray;
-//        } else if (base instanceof COSStreamArray) {
-//            COSStreamArray array = (COSStreamArray)base;
-//            PDFArray newArray = new PDFArray();
-//            cacheClonedObject(keyBase, newArray);
-//            for (int i = 0, c = array.getStreamCount(); i < c; i++) {
-//                newArray.add(cloneForNewDocument(array.get(i)));
-//            }
-//            return newArray;
-        } else if (base instanceof COSStream) {
-            return readCOSStream((COSStream) base, keyBase);
-        } else if (base instanceof COSDictionary) {
-            return readCOSDictionary((COSDictionary) base, keyBase, exclude);
-        } else if (base instanceof COSName) {
-            byte[] name = ((COSName)base).getName().getBytes("ISO-8859-1");
-            PDFName newName = new PDFName(new String(name, "ISO-8859-1"));
-            return cacheClonedObject(keyBase, newName);
-        } else if (base instanceof COSInteger) {
-            PDFNumber number = new PDFNumber();
-            number.setNumber(((COSInteger)base).longValue());
-            return cacheClonedObject(keyBase, number);
-        } else if (base instanceof COSFloat) {
-            PDFNumber number = new PDFNumber();
-            number.setNumber(((COSFloat)base).floatValue());
-            return cacheClonedObject(keyBase, number);
-        } else if (base instanceof COSBoolean) {
-            //TODO Do we need a PDFBoolean here?
-            Boolean retval = ((COSBoolean)base).getValueAsObject();
-            if (keyBase instanceof COSObject) {
-                return cacheClonedObject(keyBase, new PDFBoolean(retval));
-            } else {
-                return cacheClonedObject(keyBase, retval);
-            }
-        } else if (base instanceof COSString) {
-            return readCOSString((COSString) base, keyBase);
-        } else if (base instanceof COSNull) {
-            return cacheClonedObject(keyBase, null);
-        } else {
-            throw new UnsupportedOperationException("NYI: " + base.getClass().getName());
-        }
-    }
-
-    private PDFDictionary readCOSDictionary(COSDictionary dic, Object keyBase, Collection exclude) throws IOException {
-        PDFDictionary newDict = new PDFDictionary();
-        cacheClonedObject(keyBase, newDict);
-        for (Map.Entry<COSName, COSBase> e : dic.entrySet()) {
-            if (!exclude.contains(e.getKey())) {
-                newDict.put(e.getKey().getName(), cloneForNewDocument(e.getValue(), e.getValue(), exclude));
-            }
-        }
-        return newDict;
-    }
-
-    private Object readCOSObject(COSObject object, Collection exclude) throws IOException {
-        if (log.isTraceEnabled()) {
-            log.trace("Cloning indirect object: "
-                    + object.getObjectNumber()
-                    + " " + object.getGenerationNumber());
-        }
-        Object obj = cloneForNewDocument(object.getObject(), object, exclude);
-        if (obj instanceof PDFObject) {
-            PDFObject pdfobj = (PDFObject)obj;
-            //pdfDoc.registerObject(pdfobj);
-            if (!pdfobj.hasObjectNumber()) {
-                throw new IllegalStateException("PDF object was not registered!");
-            }
-            if (log.isTraceEnabled()) {
-                log.trace("Object registered: "
-                        + pdfobj.getObjectNumber()
-                        + " " + pdfobj.getGeneration()
-                        + " for COSObject: "
-                        + object.getObjectNumber()
-                        + " " + object.getGenerationNumber());
-            }
-        }
-        return obj;
-    }
-
-    private Object readCOSString(COSString string, Object keyBase) throws IOException {
-        //retval = ((COSString)base).getString(); //this is unsafe for binary content
-        byte[] bytes = string.getBytes();
-        //Be on the safe side and use the byte array to avoid encoding problems
-        //as PDFBox doesn't indicate whether the string is just
-        //a string (PDF 1.4, 3.2.3) or a text string (PDF 1.4, 3.8.1).
-        if (keyBase instanceof COSObject) {
-            return cacheClonedObject(keyBase, new PDFString(bytes));
-        } else {
-            if (PDFString.isUSASCII(bytes)) {
-                return cacheClonedObject(keyBase, string.getString());
-            } else {
-                return cacheClonedObject(keyBase, bytes);
-            }
-        }
-    }
-
-    private Object readCOSStream(COSStream originalStream, Object keyBase) throws IOException {
-        InputStream in;
-        Set filter;
-        if (pdfDoc.isEncryptionActive()
-                || (originalStream.containsKey(COSName.DECODE_PARMS) && !originalStream.containsKey(COSName.FILTER))) {
-            in = originalStream.getUnfilteredStream();
-            filter = FILTER_FILTER;
-        } else {
-            //transfer encoded data (don't reencode)
-            in = originalStream.getFilteredStream();
-            filter = Collections.EMPTY_SET;
-        }
-        PDFStream stream = new PDFStream();
-        OutputStream out = stream.getBufferOutputStream();
-        IOUtils.copyLarge(in, out);
-        transferDict(originalStream, stream, filter);
-        return cacheClonedObject(keyBase, stream);
-    }
-
     protected Object getCachedClone(Object base) throws IOException {
         Object key = PDFBoxAdapterUtil.getBaseKey(base);
         Object o = clonedVersion.get(key);
@@ -294,27 +135,19 @@
         return o;
     }
 
-    protected Object cacheClonedObject(Object base, Object cloned) throws IOException {
-        Object key = PDFBoxAdapterUtil.getBaseKey(base);
-        if (key == null) {
-            return cloned;
-        }
-        PDFObject pdfobj = (PDFObject) cloned;
-        if (pdfobj != null && !pdfobj.hasObjectNumber() && !(base instanceof COSDictionary)) {
-            pdfDoc.registerObject(pdfobj);
-            if (log.isTraceEnabled()) {
-                log.trace(key + ": " + pdfobj.getClass().getName() + " registered as "
-                        + pdfobj.getObjectNumber() + " " + pdfobj.getGeneration());
-            }
-        }
-        clonedVersion.put(key, cloned);
-        if (key instanceof Integer) {
-            objectCache.put(key, cloned);
-        }
-        return cloned;
+    protected Object cloneForNewDocument(Object base) throws IOException {
+        return new PDFCloner(this).cloneForNewDocument(base);
     }
 
-    private void transferDict(COSDictionary orgDict, PDFStream targetDict, Set filter) throws IOException {
+    protected Object cloneForNewDocument(Object base, Object keyBase, Collection exclude) throws IOException {
+        return new PDFCloner(this).cloneForNewDocument(base, keyBase, exclude);
+    }
+
+    protected void cacheClonedObject(Object base, Object cloned) throws IOException {
+        new PDFCloner(this).cacheClonedObject(base, cloned);
+    }
+
+    protected void transferDict(COSDictionary orgDict, PDFStream targetDict, Set filter) throws IOException {
         transferDict(orgDict, targetDict, filter, false);
     }
 
diff --git a/src/java/org/apache/fop/render/pdf/pdfbox/PDFCloner.java b/src/java/org/apache/fop/render/pdf/pdfbox/PDFCloner.java
new file mode 100644
index 0000000..dae4738
--- /dev/null
+++ b/src/java/org/apache/fop/render/pdf/pdfbox/PDFCloner.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.fop.render.pdf.pdfbox;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSBoolean;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSFloat;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNull;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
+
+import org.apache.fop.pdf.PDFArray;
+import org.apache.fop.pdf.PDFDictionary;
+import org.apache.fop.pdf.PDFName;
+import org.apache.fop.pdf.PDFNumber;
+import org.apache.fop.pdf.PDFObject;
+import org.apache.fop.pdf.PDFStream;
+
+public class PDFCloner {
+    private PDFBoxAdapter adapter;
+
+    PDFCloner(PDFBoxAdapter adapter) {
+        this.adapter = adapter;
+    }
+
+    protected Object cloneForNewDocument(Object base) throws IOException {
+        return cloneForNewDocument(base, base);
+    }
+
+    protected Object cloneForNewDocument(Object base, Object keyBase) throws IOException {
+        return cloneForNewDocument(base, keyBase, Collections.EMPTY_LIST);
+    }
+
+    protected Object cloneForNewDocument(Object base, Object keyBase, Collection exclude) throws IOException {
+        if (base == null) {
+            return null;
+        }
+        Object cached = adapter.getCachedClone(keyBase);
+        if (cached != null) {
+            // we are done, it has already been converted.
+            return cached;
+        } else if (base instanceof COSObjectable && !(base instanceof COSBase)) {
+            Object o = ((COSObjectable)base).getCOSObject();
+            Object retval = cloneForNewDocument(o, o, exclude);
+            return cacheClonedObject(keyBase, retval);
+        } else if (base instanceof COSObject) {
+            return readCOSObject((COSObject) base, exclude);
+        } else if (base instanceof COSArray) {
+            PDFArray newArray = new PDFArray();
+            cacheClonedObject(keyBase, newArray);
+            COSArray array = (COSArray)base;
+            for (int i = 0; i < array.size(); i++) {
+                newArray.add(cloneForNewDocument(array.get(i), array.get(i), exclude));
+            }
+            return newArray;
+//        } else if (base instanceof COSStreamArray) {
+//            COSStreamArray array = (COSStreamArray)base;
+//            PDFArray newArray = new PDFArray();
+//            cacheClonedObject(keyBase, newArray);
+//            for (int i = 0, c = array.getStreamCount(); i < c; i++) {
+//                newArray.add(cloneForNewDocument(array.get(i)));
+//            }
+//            return newArray;
+        } else if (base instanceof COSStream) {
+            return readCOSStream((COSStream) base, keyBase);
+        } else if (base instanceof COSDictionary) {
+            return readCOSDictionary((COSDictionary) base, keyBase, exclude);
+        } else if (base instanceof COSName) {
+            PDFName newName = new PDFName(((COSName)base).getName());
+            return cacheClonedObject(keyBase, newName);
+        } else if (base instanceof COSInteger) {
+            PDFNumber number = new PDFNumber();
+            number.setNumber(((COSInteger)base).longValue());
+            return cacheClonedObject(keyBase, number);
+        } else if (base instanceof COSFloat) {
+            PDFNumber number = new PDFNumber();
+            number.setNumber(((COSFloat)base).floatValue());
+            return cacheClonedObject(keyBase, number);
+        } else if (base instanceof COSBoolean) {
+            //TODO Do we need a PDFBoolean here?
+            Boolean retval = ((COSBoolean)base).getValueAsObject();
+            if (keyBase instanceof COSObject) {
+                return cacheClonedObject(keyBase, new PDFBoolean(retval));
+            } else {
+                return cacheClonedObject(keyBase, retval);
+            }
+        } else if (base instanceof COSString) {
+            return readCOSString((COSString) base, keyBase);
+        } else if (base instanceof COSNull) {
+            return cacheClonedObject(keyBase, null);
+        } else {
+            throw new UnsupportedOperationException("NYI: " + base.getClass().getName());
+        }
+    }
+
+    protected Object readCOSObject(COSObject object, Collection exclude) throws IOException {
+        Object obj = cloneForNewDocument(object.getObject(), object, exclude);
+        if (obj instanceof PDFObject) {
+            PDFObject pdfobj = (PDFObject)obj;
+            //pdfDoc.registerObject(pdfobj);
+            if (!pdfobj.hasObjectNumber()) {
+                throw new IllegalStateException("PDF object was not registered!");
+            }
+        }
+        return obj;
+    }
+
+    private PDFDictionary readCOSDictionary(COSDictionary dic, Object keyBase, Collection exclude) throws IOException {
+        PDFDictionary newDict = new PDFDictionary();
+        cacheClonedObject(keyBase, newDict);
+        for (Map.Entry<COSName, COSBase> e : dic.entrySet()) {
+            if (!exclude.contains(e.getKey())) {
+                newDict.put(e.getKey().getName(), cloneForNewDocument(e.getValue(), e.getValue(), exclude));
+            }
+        }
+        return newDict;
+    }
+
+    private Object readCOSString(COSString string, Object keyBase) throws IOException {
+        //retval = ((COSString)base).getString(); //this is unsafe for binary content
+        byte[] bytes = string.getBytes();
+        //Be on the safe side and use the byte array to avoid encoding problems
+        //as PDFBox doesn't indicate whether the string is just
+        //a string (PDF 1.4, 3.2.3) or a text string (PDF 1.4, 3.8.1).
+        if (keyBase instanceof COSObject) {
+            return cacheClonedObject(keyBase, new PDFString(bytes));
+        } else {
+            if (PDFString.isUSASCII(bytes)) {
+                return cacheClonedObject(keyBase, string.getString());
+            } else {
+                return cacheClonedObject(keyBase, bytes);
+            }
+        }
+    }
+
+    private Object readCOSStream(COSStream originalStream, Object keyBase) throws IOException {
+        InputStream in;
+        Set filter;
+        if (adapter.pdfDoc.isEncryptionActive()
+                || (originalStream.containsKey(COSName.DECODE_PARMS) && !originalStream.containsKey(COSName.FILTER))) {
+            in = originalStream.getUnfilteredStream();
+            filter = adapter.FILTER_FILTER;
+        } else {
+            //transfer encoded data (don't reencode)
+            in = originalStream.getFilteredStream();
+            filter = Collections.EMPTY_SET;
+        }
+        PDFStream stream = new PDFStream();
+        OutputStream out = stream.getBufferOutputStream();
+        IOUtils.copyLarge(in, out);
+        adapter.transferDict(originalStream, stream, filter);
+        return cacheClonedObject(keyBase, stream);
+    }
+
+    protected Object cacheClonedObject(Object base, Object cloned) throws IOException {
+        Object key = PDFBoxAdapterUtil.getBaseKey(base);
+        if (key == null) {
+            return cloned;
+        }
+        PDFObject pdfobj = (PDFObject) cloned;
+        if (pdfobj != null && !pdfobj.hasObjectNumber() && !(base instanceof COSDictionary)) {
+            adapter.pdfDoc.registerObject(pdfobj);
+        }
+        adapter.clonedVersion.put(key, cloned);
+        if (key instanceof Integer) {
+            adapter.objectCache.put(key, cloned);
+        }
+        return cloned;
+    }
+}