PDFbox说PDDocument关闭时没有

时间:2018-04-23 02:42:33

标签: java pdfbox treemap

我正在尝试使用PDFbox填充重复的表单。我正在使用TreeMap并使用单个记录填充表单。 pdf表单的格式是第一页列出的六个记录和第二页上插入的静态页面。 (对于大于六个记录的TreeMap,重复该过程)。 Im获取的错误特定于TreeMap的大小。这就是我的问题所在。我无法弄清楚为什么当我用超过35个条目填充TreeMap时,我收到此警告:

2018年4月23日上午2:36:25 org.apache.pdfbox.cos.COSDocument敲定 警告:警告:您未关闭PDF文档

public class test {
    public static void main(String[] args) throws IOException,         IOException {
    // TODO Auto-generated method stub
    File dataFile = new File("dataFile.csv");
    File fi = new File("form.pdf");
    Scanner fileScanner = new Scanner(dataFile);
    fileScanner.nextLine();
    TreeMap<String, String[]> assetTable = new TreeMap<String, String[]>();
    int x = 0;
    while (x <= 36) {
        String lineIn = fileScanner.nextLine();
        String[] elements = lineIn.split(",");
        elements[0] = elements[0].toUpperCase().replaceAll(" ", "");
        String key = elements[0];
        key = key.replaceAll(" ", "");
        assetTable.put(key, elements);
        x++;
    }
    PDDocument newDoc = new PDDocument();
    int control = 1;
    PDDocument doc = PDDocument.load(fi);
    PDDocumentCatalog cat = doc.getDocumentCatalog();
    PDAcroForm form = cat.getAcroForm();
    for (String s : assetTable.keySet()) {
        if (control <= 6) {
            PDField IDno1 = (form.getField("IDno" + control));
            PDField Locno1 = (form.getField("locNo" + control));
            PDField serno1 = (form.getField("serNo" + control));
            PDField typeno1 = (form.getField("typeNo" + control));
            PDField maintno1 = (form.getField("maintNo" + control));
            String IDnoOne = assetTable.get(s)[1];
            //System.out.println(IDnoOne);
            IDno1.setValue(assetTable.get(s)[0]);
            IDno1.setReadOnly(true);
            Locno1.setValue(assetTable.get(s)[1]);
            Locno1.setReadOnly(true);
            serno1.setValue(assetTable.get(s)[2]);
            serno1.setReadOnly(true);
            typeno1.setValue(assetTable.get(s)[3]);
            typeno1.setReadOnly(true);
            String type = "";
            if (assetTable.get(s)[5].equals("1"))
                type += "Hydrotest";
            if (assetTable.get(s)[5].equals("6"))
                type += "6 Year Maintenance";
            String maint = assetTable.get(s)[4] + " - " + type;
            maintno1.setValue(maint);
            maintno1.setReadOnly(true);
            control++;
        } else {
            PDField dateIn = form.getField("dateIn");
            dateIn.setValue("1/2019 Yearlies");
            dateIn.setReadOnly(true);
            PDField tagDate = form.getField("tagDate");
            tagDate.setValue("2019 / 2020");
            tagDate.setReadOnly(true);
            newDoc.addPage(doc.getPage(0));
            newDoc.addPage(doc.getPage(1));
            control = 1;
            doc = PDDocument.load(fi);
            cat = doc.getDocumentCatalog();
            form = cat.getAcroForm();
        }
    }
    PDField dateIn = form.getField("dateIn");
    dateIn.setValue("1/2019 Yearlies");
    dateIn.setReadOnly(true);
    PDField tagDate = form.getField("tagDate");
    tagDate.setValue("2019 / 2020");
    tagDate.setReadOnly(true);
    newDoc.addPage(doc.getPage(0));
    newDoc.addPage(doc.getPage(1));
    newDoc.save("PDFtest.pdf");
    Desktop.getDesktop().open(new File("PDFtest.pdf"));

}

我无法弄清楚我的生活中我做错了什么。这是我第一次使用PDFbox,所以我希望它简单。

更新了错误消息

WARNING: Warning: You did not close a PDF Document
Exception in thread "main" java.io.IOException: COSStream has been closed and cannot be read. Perhaps its enclosing PDDocument has been closed?
    at org.apache.pdfbox.cos.COSStream.checkClosed(COSStream.java:77)
    at org.apache.pdfbox.cos.COSStream.createRawInputStream(COSStream.java:125)
    at org.apache.pdfbox.pdfwriter.COSWriter.visitFromStream(COSWriter.java:1200)
    at org.apache.pdfbox.cos.COSStream.accept(COSStream.java:383)
    at org.apache.pdfbox.cos.COSObject.accept(COSObject.java:158)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObject(COSWriter.java:522)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteObjects(COSWriter.java:460)
    at org.apache.pdfbox.pdfwriter.COSWriter.doWriteBody(COSWriter.java:444)
    at org.apache.pdfbox.pdfwriter.COSWriter.visitFromDocument(COSWriter.java:1096)
    at org.apache.pdfbox.cos.COSDocument.accept(COSDocument.java:419)
    at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1367)
    at org.apache.pdfbox.pdfwriter.COSWriter.write(COSWriter.java:1254)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1232)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1204)
    at org.apache.pdfbox.pdmodel.PDDocument.save(PDDocument.java:1192)
    at test.test.main(test.java:87)

2 个答案:

答案 0 :(得分:4)

警告本身

您的警告似乎有误。它说:

  

警告:您未关闭PDF文档

所以与你的想法相反,&#34; PDFbox说PDDocument在不是&#34; 时关闭,PDFBox说你做了关闭文件!

在您进行编辑后,我们发现它确实显示 COSStream 已关闭且 可能原因是封闭的PDDocument已经关闭。这只是一种可能性!

您案例中的警告

话虽如此,通过将一个文档中的页面添加到另一个文档,您可能最终会从这两个文档中引用这些页面。在这种情况下,在关闭两个文档的过程中(例如自动通过垃圾收集),第二个关闭可能确实偶然发现了一些已经关闭的COSStream个实例。

所以我的第一个建议就是最后通过

关闭文件
doc.close();
newDoc.close();

可能不会删除警告,只是改变他们的时间。

实际上,您不仅仅创建了两个文档docnewDoc,您甚至可以创建新的PDDocument个实例,并将它们一次又一次地分配给doc将该变量中的前文档对象设置为垃圾收集的过程。因此,一旦没有引用,你最终会有大量文件被关闭。

我不认为最早在doc关闭所有这些文件是个好主意,特别是在保存newDoc之前。

但是,如果您的代码最终将作为较大应用程序的一部分而不是作为小型一次性测试应用程序运行,那么您应该在某些PDDocument中收集所有Collection个实例并明确关闭保存newDoc后立即清除收藏品。

实际上,您的异常看起来其中一个丢失的PDDocument实例已被垃圾收集关闭,因此即使是简单的一次性实用程序也应该收集文档以防止它们被GC处理掉。

(@ Tilman,如果我错了,请纠正我。)

导入页面

为了防止不同文档共享页面出现问题,您可以尝试 导入 页面到目标文档,然后将导入的页面添加到目标文档页面树。即取代

newDoc.addPage(doc.getPage(0));
newDoc.addPage(doc.getPage(1));

通过

newDoc.addPage(newDoc.importPage(doc.getPage(0)));
newDoc.addPage(newDoc.importPage(doc.getPage(1)));

这应该允许您在丢失之前关闭PDDocument中的每个doc实例。 但是,这有一些缺点,参见方法JavaDoc和this answer here

代码中的实际问题

在合并后的文档中,您将拥有许多具有相同名称的字段(至少在CSV文件中有足够多的条目的情况下),您最初将其设置为不同的值。您可以访问相应原始文档的PDAcroForm字段,但不要将其添加到合并结果文档的PDAcroForm

这是在惹麻烦! PDF格式确实考虑了文档范围内的表单,其中所有字段都是从文档的AcroForm字典中引用(直接或间接),并且它希望具有相同名称的字段实际上是同一字段的不同可视化,因此对所有字段都有相同的价值。

因此,PDF处理器可能会以意想不到的方式处理您的文档字段,例如

  • 在所有具有相同名称的字段中显示相同的值(因为它们具有相同的值)或
  • 忽略您的字段(因为它们不在文档 AcroForm 结构中)。

特别是对PDF字段值的程序化读取将失败,因为在该上下文中,表单最终被视为文档范围并基于 AcroForm 。另一方面,PDF查看器可能首先显示您的设置值并使外观正常。

要防止这种情况,您应该在合并之前重命名字段。您可以考虑使用PDFMergerUtility进行重命名。有关该实用程序类的示例用法,请查看PDFMergerExample

答案 1 :(得分:1)

即使以上答案被标记为该问题的解决方案,由于该解决方案已包含在注释中,因此我想在此级别添加此答案。我花了几个小时寻找解决方案。

我的代码段和注释。

// Collection solely for purpose of preventing premature garbage collection
List<PDDocument> sourceDocuments = new ArrayList<>( );

...

// Source document (actually inside a loop)
PDDocument docIn = PDDocument.load( artifactBytes );

// Add document to collection before using it to prevent the problem
sourceDocuments.add( docIn );

// Extract from source document 
PDPage extractedPage = docIn.getPage( 0 );
// Add page to destination document
docOut.addPage( extractedPage );

...

// This was failing with "COSStream has been closed and cannot be read."
// Now it works.
docOut.save( bundleStream );