org.pdfsam.console.business.pdf.handlers.SplitCmdExecutor.java Source code

Java tutorial

Introduction

Here is the source code for org.pdfsam.console.business.pdf.handlers.SplitCmdExecutor.java

Source

/*
 * Created on 22-Oct-2007
 * Copyright (C) 2007 by Andrea Vacondio.
 *
 *
 * This library is provided under dual licenses.
 * You may choose the terms of the Lesser General Public License version 2.1 or the General Public License version 2
 * License at your discretion.
 * 
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * 
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the 
 * GNU General Public License as published by the Free Software Foundation; 
 * either version 2 of the License.
 * 
 * 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 General Public License for more details.
 * You should have received a copy of the GNU General Public License along with this program; 
 * if not, write to the Free Software Foundation, Inc., 
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.pdfsam.console.business.pdf.handlers;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.TreeSet;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.pdfsam.console.business.ConsoleServicesFacade;
import org.pdfsam.console.business.dto.WorkDoneDataModel;
import org.pdfsam.console.business.dto.commands.AbstractParsedCommand;
import org.pdfsam.console.business.dto.commands.SplitParsedCommand;
import org.pdfsam.console.business.pdf.bookmarks.BookmarksProcessor;
import org.pdfsam.console.business.pdf.handlers.interfaces.AbstractCmdExecutor;
import org.pdfsam.console.exceptions.console.ConsoleException;
import org.pdfsam.console.exceptions.console.SplitException;
import org.pdfsam.console.utils.FileUtility;
import org.pdfsam.console.utils.PdfUtility;
import org.pdfsam.console.utils.perfix.FileNameRequest;
import org.pdfsam.console.utils.perfix.PrefixParser;

import com.lowagie.text.Document;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfSmartCopy;
import com.lowagie.text.pdf.RandomAccessFileOrArray;
import com.lowagie.text.pdf.SimpleBookmark;

/**
 * Command executor for the split command
 * 
 * @author Andrea Vacondio
 */
public class SplitCmdExecutor extends AbstractCmdExecutor {

    private static final Logger LOG = Logger.getLogger(SplitCmdExecutor.class.getPackage().getName());
    private PrefixParser prefixParser;
    private PdfSmartCopy pdfWriter = null;
    private PdfReader pdfReader = null;

    public void execute(AbstractParsedCommand parsedCommand) throws ConsoleException {

        if ((parsedCommand != null) && (parsedCommand instanceof SplitParsedCommand)) {
            SplitParsedCommand inputCommand = (SplitParsedCommand) parsedCommand;
            setPercentageOfWorkDone(0);
            try {
                prefixParser = new PrefixParser(inputCommand.getOutputFilesPrefix(),
                        inputCommand.getInputFile().getFile().getName());
                if (SplitParsedCommand.S_BURST.equals(inputCommand.getSplitType())) {
                    executeBurst(inputCommand);
                } else if (SplitParsedCommand.S_NSPLIT.equals(inputCommand.getSplitType())) {
                    executeNSplit(inputCommand);
                } else if (SplitParsedCommand.S_SPLIT.equals(inputCommand.getSplitType())) {
                    executeSplit(inputCommand);
                } else if (SplitParsedCommand.S_EVEN.equals(inputCommand.getSplitType())
                        || SplitParsedCommand.S_ODD.equals(inputCommand.getSplitType())) {
                    executeSplitOddEven(inputCommand);
                } else if (SplitParsedCommand.S_SIZE.equals(inputCommand.getSplitType())) {
                    executeSizeSplit(inputCommand);
                } else if (SplitParsedCommand.S_BLEVEL.equals(inputCommand.getSplitType())) {
                    executeBookmarksSplit(inputCommand);
                } else {
                    throw new SplitException(SplitException.ERR_NOT_VALID_SPLIT_TYPE,
                            new String[] { inputCommand.getSplitType() });
                }
            } catch (Exception e) {
                throw new SplitException(e);
            } finally {
                setWorkCompleted();
            }
        } else {
            throw new ConsoleException(ConsoleException.ERR_BAD_COMMAND);
        }
    }

    /**
     * Execute the split of a pdf document when split type is S_BURST
     * 
     * @param inputCommand
     * @throws Exception
     */
    private void executeBurst(SplitParsedCommand inputCommand) throws Exception {
        int currentPage;
        Document currentDocument;
        pdfReader = PdfUtility.readerFor(inputCommand.getInputFile());
        pdfReader.removeUnusedObjects();
        pdfReader.consolidateNamedDestinations();

        // we retrieve the total number of pages
        int n = pdfReader.getNumberOfPages();
        int fileNum = 0;
        LOG.info("Found " + n + " pages in input pdf document.");
        for (currentPage = 1; currentPage <= n; currentPage++) {
            LOG.debug("Creating a new document.");
            fileNum++;
            File tmpFile = FileUtility.generateTmpFile(inputCommand.getOutputFile());
            FileNameRequest request = new FileNameRequest(currentPage, fileNum, null);
            File outFile = new File(inputCommand.getOutputFile().getCanonicalPath(),
                    prefixParser.generateFileName(request));
            currentDocument = new Document(pdfReader.getPageSizeWithRotation(currentPage));

            pdfWriter = new PdfSmartCopy(currentDocument, new FileOutputStream(tmpFile));

            currentDocument.addCreator(ConsoleServicesFacade.CREATOR);
            setCompressionSettingOnWriter(inputCommand, pdfWriter);

            // set pdf version
            setPdfVersionSettingOnWriter(inputCommand, pdfWriter, Character.valueOf(pdfReader.getPdfVersion()));

            currentDocument.open();
            PdfImportedPage importedPage = pdfWriter.getImportedPage(pdfReader, currentPage);
            pdfWriter.addPage(importedPage);
            currentDocument.close();
            FileUtility.renameTemporaryFile(tmpFile, outFile, inputCommand.isOverwrite());
            LOG.debug("File " + outFile.getCanonicalPath() + " created.");
            setPercentageOfWorkDone((currentPage * WorkDoneDataModel.MAX_PERGENTAGE) / n);
        }
        pdfReader.close();
        LOG.info("Burst done.");
    }

    /**
     * Execute the split of a pdf document when split type is S_ODD or S_EVEN
     * 
     * @param inputCommand
     * @throws Exception
     */
    private void executeSplitOddEven(SplitParsedCommand inputCommand) throws Exception {
        pdfReader = PdfUtility.readerFor(inputCommand.getInputFile());
        pdfReader.removeUnusedObjects();
        pdfReader.consolidateNamedDestinations();

        // we retrieve the total number of pages
        int n = pdfReader.getNumberOfPages();
        int fileNum = 0;
        LOG.info("Found " + n + " pages in input pdf document.");
        int currentPage;
        Document currentDocument = new Document(pdfReader.getPageSizeWithRotation(1));
        boolean isTimeToClose = false;
        PdfImportedPage importedPage;
        File tmpFile = null;
        File outFile = null;
        for (currentPage = 1; currentPage <= n; currentPage++) {
            // check if i've to read one more page or to open a new doc
            isTimeToClose = ((currentPage != 1)
                    && ((SplitParsedCommand.S_ODD.equals(inputCommand.getSplitType()) && ((currentPage % 2) != 0))
                            || (SplitParsedCommand.S_EVEN.equals(inputCommand.getSplitType())
                                    && ((currentPage % 2) == 0))));

            if (!isTimeToClose) {
                LOG.debug("Creating a new document.");
                fileNum++;
                tmpFile = FileUtility.generateTmpFile(inputCommand.getOutputFile());
                FileNameRequest request = new FileNameRequest(currentPage, fileNum, null);
                outFile = new File(inputCommand.getOutputFile(), prefixParser.generateFileName(request));
                currentDocument = new Document(pdfReader.getPageSizeWithRotation(currentPage));

                pdfWriter = new PdfSmartCopy(currentDocument, new FileOutputStream(tmpFile));
                // set creator
                currentDocument.addCreator(ConsoleServicesFacade.CREATOR);

                setCompressionSettingOnWriter(inputCommand, pdfWriter);
                setPdfVersionSettingOnWriter(inputCommand, pdfWriter, Character.valueOf(pdfReader.getPdfVersion()));

                currentDocument.open();
            }
            importedPage = pdfWriter.getImportedPage(pdfReader, currentPage);
            pdfWriter.addPage(importedPage);
            // if it's time to close the document
            if ((isTimeToClose) || (currentPage == n)
                    || ((currentPage == 1) && (SplitParsedCommand.S_ODD.equals(inputCommand.getSplitType())))) {
                currentDocument.close();
                FileUtility.renameTemporaryFile(tmpFile, outFile, inputCommand.isOverwrite());
                LOG.debug("File " + outFile.getCanonicalPath() + " created.");
            }
            setPercentageOfWorkDone((currentPage * WorkDoneDataModel.MAX_PERGENTAGE) / n);
        }
        pdfReader.close();
        LOG.info("Split " + inputCommand.getSplitType() + " done.");
    }

    /**
     * Execute the split of a pdf document when split type is S_SPLIT or S_NSPLIT
     * 
     * @param inputCommand
     * @throws Exception
     */
    private void executeSplit(SplitParsedCommand inputCommand) throws Exception {
        executeSplit(inputCommand, null);
    }

    /**
     * Execute the split of a pdf document when split type is S_BLEVEL
     * 
     * @param inputCommand
     * @param bookmarksTable
     *            bookmarks table. It's populated only when splitting by bookmarks. If null or empty it's ignored
     * @throws Exception
     */
    private void executeSplit(SplitParsedCommand inputCommand, Hashtable bookmarksTable) throws Exception {
        pdfReader = PdfUtility.readerFor(inputCommand.getInputFile());
        pdfReader.removeUnusedObjects();
        pdfReader.consolidateNamedDestinations();

        int n = pdfReader.getNumberOfPages();
        BookmarksProcessor bookmarkProcessor = new BookmarksProcessor(SimpleBookmark.getBookmark(pdfReader), n);
        int fileNum = 0;
        LOG.info("Found " + n + " pages in input pdf document.");

        Integer[] limits = inputCommand.getSplitPageNumbers();
        // limits list validation end clean
        TreeSet limitsList = validateSplitLimits(limits, n);
        if (limitsList.isEmpty()) {
            throw new SplitException(SplitException.ERR_NO_PAGE_LIMITS);
        }

        // HERE I'M SURE I'VE A LIMIT LIST WITH VALUES, I CAN START BOOKMARKS
        int currentPage;
        Document currentDocument = new Document(pdfReader.getPageSizeWithRotation(1));
        int relativeCurrentPage = 0;
        int endPage = n;
        int startPage = 1;
        PdfImportedPage importedPage;
        File tmpFile = null;
        File outFile = null;

        Iterator itr = limitsList.iterator();
        if (itr.hasNext()) {
            endPage = ((Integer) itr.next()).intValue();
        }
        for (currentPage = 1; currentPage <= n; currentPage++) {
            relativeCurrentPage++;
            // check if i've to read one more page or to open a new doc
            if (relativeCurrentPage == 1) {
                LOG.debug("Creating a new document.");
                fileNum++;
                tmpFile = FileUtility.generateTmpFile(inputCommand.getOutputFile());
                String bookmark = null;
                if (bookmarksTable != null && bookmarksTable.size() > 0) {
                    bookmark = (String) bookmarksTable.get(new Integer(currentPage));
                }
                FileNameRequest request = new FileNameRequest(currentPage, fileNum, bookmark);
                outFile = new File(inputCommand.getOutputFile(), prefixParser.generateFileName(request));
                startPage = currentPage;
                currentDocument = new Document(pdfReader.getPageSizeWithRotation(currentPage));

                pdfWriter = new PdfSmartCopy(currentDocument, new FileOutputStream(tmpFile));

                // set creator
                currentDocument.addCreator(ConsoleServicesFacade.CREATOR);

                setCompressionSettingOnWriter(inputCommand, pdfWriter);
                setPdfVersionSettingOnWriter(inputCommand, pdfWriter, Character.valueOf(pdfReader.getPdfVersion()));

                currentDocument.open();
            }

            importedPage = pdfWriter.getImportedPage(pdfReader, currentPage);
            pdfWriter.addPage(importedPage);

            // if it's time to close the document
            if (currentPage == endPage) {
                LOG.info("Temporary document " + tmpFile.getName() + " done, now adding bookmarks...");
                // manage bookmarks
                List bookmarks = bookmarkProcessor.processBookmarks(startPage, endPage);
                if (bookmarks != null) {
                    pdfWriter.setOutlines(bookmarks);
                }
                relativeCurrentPage = 0;
                currentDocument.close();
                FileUtility.renameTemporaryFile(tmpFile, outFile, inputCommand.isOverwrite());
                LOG.debug("File " + outFile.getCanonicalPath() + " created.");
                endPage = (itr.hasNext()) ? ((Integer) itr.next()).intValue() : n;
            }
            setPercentageOfWorkDone((currentPage * WorkDoneDataModel.MAX_PERGENTAGE) / n);
        }
        pdfReader.close();
        LOG.info("Split " + inputCommand.getSplitType() + " done.");
    }

    /**
     * Execute the split of a pdf document when split type is S_NSPLIT
     * 
     * @param inputCommand
     * @throws Exception
     */
    private void executeNSplit(SplitParsedCommand inputCommand) throws Exception {
        Integer[] numberPages = inputCommand.getSplitPageNumbers();
        if (numberPages != null && numberPages.length == 1) {
            pdfReader = PdfUtility.readerFor(inputCommand.getInputFile());
            pdfReader.removeUnusedObjects();
            pdfReader.consolidateNamedDestinations();
            int n = pdfReader.getNumberOfPages();
            int numberPage = numberPages[0].intValue();
            if (numberPage < 1 || numberPage > n) {
                pdfReader.close();
                throw new SplitException(SplitException.ERR_NO_SUCH_PAGE, new String[] { "" + numberPage });
            } else {
                ArrayList retVal = new ArrayList();
                for (int i = numberPage; i < n; i += numberPage) {
                    retVal.add(new Integer(i));
                }
                inputCommand.setSplitPageNumbers((Integer[]) retVal.toArray(new Integer[0]));
            }
            pdfReader.close();
        }
        executeSplit(inputCommand);
    }

    /**
     * Execute the split of a pdf document when split type is S_BLEVEL
     * 
     * @param inputCommand
     * @throws Exception
     */
    private void executeBookmarksSplit(SplitParsedCommand inputCommand) throws Exception {
        pdfReader = PdfUtility.readerFor(inputCommand.getInputFile());
        int bLevel = inputCommand.getBookmarksLevel().intValue();
        Hashtable bookmarksTable = new Hashtable();
        if (bLevel > 0) {
            pdfReader.removeUnusedObjects();
            pdfReader.consolidateNamedDestinations();
            List bookmarks = SimpleBookmark.getBookmark(pdfReader);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            SimpleBookmark.exportToXML(bookmarks, out, "UTF-8", false);
            ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray());
            int maxDepth = PdfUtility.getMaxBookmarksDepth(input);
            input.reset();
            if (bLevel <= maxDepth) {
                SAXReader reader = new SAXReader();
                org.dom4j.Document document = reader.read(input);
                // head node
                String headBookmarkXQuery = "/Bookmark/Title[@Action=\"GoTo\"]";
                Node headNode = document.selectSingleNode(headBookmarkXQuery);
                if (headNode != null && headNode.getText() != null && headNode.getText().trim().length() > 0) {
                    bookmarksTable.put(new Integer(1), headNode.getText().trim());
                }
                // bLevel nodes
                StringBuffer buffer = new StringBuffer("/Bookmark");
                for (int i = 0; i < bLevel; i++) {
                    buffer.append("/Title[@Action=\"GoTo\"]");
                }
                String xQuery = buffer.toString();
                List nodes = document.selectNodes(xQuery);
                input.close();
                input = null;
                if (nodes != null && nodes.size() > 0) {
                    LinkedHashSet pageSet = new LinkedHashSet(nodes.size());
                    for (Iterator nodeIter = nodes.iterator(); nodeIter.hasNext();) {
                        Node currentNode = (Node) nodeIter.next();
                        Node pageAttribute = currentNode.selectSingleNode("@Page");
                        if (pageAttribute != null && pageAttribute.getText().length() > 0) {
                            String attribute = pageAttribute.getText();
                            int blankIndex = attribute.indexOf(' ');
                            if (blankIndex > 0) {
                                Integer currentNumber = new Integer(attribute.substring(0, blankIndex));
                                String bookmarkText = currentNode.getText().trim();
                                // fix #2789963
                                if (currentNumber.intValue() > 0) {
                                    // bookmarks regexp matching if any
                                    if (StringUtils.isBlank(inputCommand.getBookmarkRegexp())
                                            || bookmarkText.matches(inputCommand.getBookmarkRegexp())) {
                                        // to split just before the given page
                                        if ((currentNumber.intValue()) > 1) {
                                            pageSet.add(new Integer(currentNumber.intValue() - 1));
                                        }
                                        if (StringUtils.isNotBlank(bookmarkText)) {
                                            bookmarksTable.put(currentNumber, bookmarkText.trim());
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (pageSet.size() > 0) {
                        if (StringUtils.isBlank(inputCommand.getBookmarkRegexp())) {
                            LOG.debug("Found " + pageSet.size() + " destination pages at level " + bLevel);
                        } else {
                            LOG.debug("Found " + pageSet.size() + " destination pages at level " + bLevel
                                    + " matching '" + inputCommand.getBookmarkRegexp() + "'");
                        }
                        inputCommand.setSplitPageNumbers((Integer[]) pageSet.toArray(new Integer[pageSet.size()]));
                    } else {
                        throw new SplitException(SplitException.ERR_BLEVEL_NO_DEST, new String[] { "" + bLevel });
                    }
                } else {
                    throw new SplitException(SplitException.ERR_BLEVEL, new String[] { "" + bLevel });
                }
            } else {
                input.close();
                pdfReader.close();
                throw new SplitException(SplitException.ERR_BLEVEL_OUTOFBOUNDS,
                        new String[] { "" + bLevel, "" + maxDepth });

            }
        } else {
            pdfReader.close();
            throw new SplitException(SplitException.ERR_NOT_VALID_BLEVEL, new String[] { "" + bLevel });
        }
        pdfReader.close();
        executeSplit(inputCommand, bookmarksTable);
    }

    /**
     * Execute the split of a pdf document when split type is S_SIZE
     * 
     * @param inputCommand
     * @throws Exception
     */
    private void executeSizeSplit(SplitParsedCommand inputCommand) throws Exception {
        pdfReader = PdfUtility.readerFor(inputCommand.getInputFile());
        pdfReader.removeUnusedObjects();
        pdfReader.consolidateNamedDestinations();
        int n = pdfReader.getNumberOfPages();
        BookmarksProcessor bookmarkProcessor = new BookmarksProcessor(SimpleBookmark.getBookmark(pdfReader), n);
        int fileNum = 0;
        LOG.info("Found " + n + " pages in input pdf document.");
        int currentPage;
        Document currentDocument = new Document(pdfReader.getPageSizeWithRotation(1));
        PdfImportedPage importedPage;
        File tmpFile = null;
        File outFile = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int startPage = 0;
        int relativeCurrentPage = 0;
        for (currentPage = 1; currentPage <= n; currentPage++) {
            relativeCurrentPage++;
            // time to open a new document?
            if (relativeCurrentPage == 1) {
                LOG.debug("Creating a new document.");
                startPage = currentPage;
                fileNum++;
                tmpFile = FileUtility.generateTmpFile(inputCommand.getOutputFile());
                FileNameRequest request = new FileNameRequest(currentPage, fileNum, null);
                outFile = new File(inputCommand.getOutputFile(), prefixParser.generateFileName(request));
                currentDocument = new Document(pdfReader.getPageSizeWithRotation(currentPage));
                baos = new ByteArrayOutputStream();
                pdfWriter = new PdfSmartCopy(currentDocument, baos);
                // set creator
                currentDocument.addCreator(ConsoleServicesFacade.CREATOR);

                setCompressionSettingOnWriter(inputCommand, pdfWriter);
                setPdfVersionSettingOnWriter(inputCommand, pdfWriter, Character.valueOf(pdfReader.getPdfVersion()));

                currentDocument.open();
            }

            importedPage = pdfWriter.getImportedPage(pdfReader, currentPage);
            pdfWriter.addPage(importedPage);
            // if it's time to close the document
            if ((currentPage == n) || ((relativeCurrentPage > 1) && ((baos.size() / relativeCurrentPage)
                    * (1 + relativeCurrentPage) > inputCommand.getSplitSize().longValue()))) {
                LOG.debug("Current stream size: " + baos.size() + " bytes.");
                // manage bookmarks
                List bookmarks = bookmarkProcessor.processBookmarks(startPage, currentPage);
                if (bookmarks != null) {
                    pdfWriter.setOutlines(bookmarks);
                }
                relativeCurrentPage = 0;
                currentDocument.close();
                FileOutputStream fos = new FileOutputStream(tmpFile);
                baos.writeTo(fos);
                fos.close();
                baos.close();
                LOG.info("Temporary document " + tmpFile.getName() + " done.");
                FileUtility.renameTemporaryFile(tmpFile, outFile, inputCommand.isOverwrite());
                LOG.debug("File " + outFile.getCanonicalPath() + " created.");
            }
            setPercentageOfWorkDone((currentPage * WorkDoneDataModel.MAX_PERGENTAGE) / n);
        }
        pdfReader.close();
        LOG.info("Split " + inputCommand.getSplitType() + " done.");
    }

    /**
     * Validate the limit list in "split after these pages"
     * 
     * @param limits
     *            limits array containing page numbers
     * @param upperLimit
     *            max page number
     * @return purged list
     */
    private TreeSet validateSplitLimits(Integer[] limits, int upperLimit) {
        TreeSet limitsList = new TreeSet();
        /*
         * I check if it contains zero values or double values and i remove them
         */
        for (int j = 0; j < limits.length; j++) {
            if ((limits[j].intValue() <= 0) || (limits[j].intValue() >= upperLimit)) {
                LOG.warn("Cannot split before page number " + limits[j].intValue() + ", limit removed.");
            } else {
                if (!limitsList.add(limits[j])) {
                    LOG.warn(
                            limits[j].intValue() + " found more than once in the page limits list, limit removed.");
                }
            }
        }
        return limitsList;
    }

    public void clean() {
        closePdfReader(pdfReader);
        closePdfWriter(pdfWriter);
    }
}