net.sf.jabref.plugins.pdftasks.PDFTaskSidePane.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jabref.plugins.pdftasks.PDFTaskSidePane.java

Source

//
// JabRef PDFTasks Plugin
// Copyright (C) 2011--2015  Karl Wette
//
// 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 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

package net.sf.jabref.plugins.pdftasks;

import net.sf.jabref.AbstractWorker;
import net.sf.jabref.BasePanel;
import net.sf.jabref.BibtexDatabase;
import net.sf.jabref.BibtexEntry;
import net.sf.jabref.GUIGlobals;
import net.sf.jabref.Globals;
import net.sf.jabref.JabRefFrame;
import net.sf.jabref.MetaData;
import net.sf.jabref.SidePaneComponent;
import net.sf.jabref.SidePaneManager;
import net.sf.jabref.external.ExternalFileType;
import net.sf.jabref.gui.FileListEntry;
import net.sf.jabref.gui.FileListTableModel;
import net.sf.jabref.util.XMPUtil;

import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.exceptions.CryptographyException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.encryption.BadSecurityHandlerException;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.xml.transform.TransformerException;

// PDFTasks side pane class
public class PDFTaskSidePane extends SidePaneComponent implements ActionListener, ChangeListener {

    private final static String title = "PDF Tasks";

    private final JabRefFrame frame;

    private final JButton do_tasks;
    private final JCheckBox rename_pdfs_chk;
    private final JCheckBox move_to_pdf_dir_chk;
    private final JTextField pdf_dir_txt;
    private final JCheckBox write_pdf_docinfo_chk;
    private final JCheckBox erase_pdf_docinfo_chk;

    public PDFTaskSidePane(JabRefFrame frame, SidePaneManager manager) {

        // initialise SidePaneComponent
        super(manager, GUIGlobals.getIconUrl("pdfSmall"), title);

        // save JabRef frame
        this.frame = frame;

        // create check box for renaming PDFs
        rename_pdfs_chk = new JCheckBox("Rename PDFs", true);

        // create check box and text field for moving PDFs task
        move_to_pdf_dir_chk = new JCheckBox("Move PDFs to directory:", true);
        move_to_pdf_dir_chk.addChangeListener(this);
        pdf_dir_txt = new JTextField();
        {
            Dimension d = pdf_dir_txt.getPreferredSize();
            d.height = (int) Math.ceil(d.height * 1.1);
            pdf_dir_txt.setPreferredSize(d);
        }

        // create check box for writing erasing PDF document info
        write_pdf_docinfo_chk = new JCheckBox("Write PDF document information", true);
        write_pdf_docinfo_chk.addChangeListener(this);

        // create check box for erasing previous PDF document info
        erase_pdf_docinfo_chk = new JCheckBox("Erase previous document information", false);

        // create do task button
        do_tasks = new JButton("Perform PDF Tasks");
        do_tasks.addActionListener(this);

        // create pane and layout components
        JPanel pane = new JPanel();
        {
            GroupLayout grp = new GroupLayout(pane);
            pane.setLayout(grp);
            grp.setAutoCreateGaps(true);
            grp.setAutoCreateContainerGaps(true);
            grp.setHorizontalGroup(grp.createParallelGroup(GroupLayout.Alignment.CENTER)
                    .addGroup(grp.createParallelGroup().addComponent(rename_pdfs_chk)
                            .addComponent(move_to_pdf_dir_chk).addComponent(pdf_dir_txt)
                            .addComponent(write_pdf_docinfo_chk).addComponent(erase_pdf_docinfo_chk))
                    .addComponent(do_tasks));
            grp.setVerticalGroup(grp.createSequentialGroup().addComponent(rename_pdfs_chk)
                    .addPreferredGap(rename_pdfs_chk, move_to_pdf_dir_chk, ComponentPlacement.UNRELATED)
                    .addComponent(move_to_pdf_dir_chk)
                    .addPreferredGap(move_to_pdf_dir_chk, pdf_dir_txt, ComponentPlacement.RELATED)
                    .addComponent(pdf_dir_txt)
                    .addPreferredGap(pdf_dir_txt, write_pdf_docinfo_chk, ComponentPlacement.UNRELATED)
                    .addComponent(write_pdf_docinfo_chk)
                    .addPreferredGap(write_pdf_docinfo_chk, erase_pdf_docinfo_chk, ComponentPlacement.RELATED)
                    .addComponent(erase_pdf_docinfo_chk)
                    .addPreferredGap(erase_pdf_docinfo_chk, do_tasks, ComponentPlacement.UNRELATED)
                    .addComponent(do_tasks));
        }
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        add(pane);

    }

    public void componentOpening() {

        // register this class to listen for when the user changes tabs
        frame.getTabbedPane().addChangeListener(this);

        // update pane
        updatePane();

    }

    public void componentClosing() {

        // deregister this class as a listener
        frame.getTabbedPane().removeChangeListener(this);

    }

    public void stateChanged(ChangeEvent e) {

        // update pane if user changed tabs
        if (e.getSource() == frame.getTabbedPane()) {
            updatePane();
        }

        // enable pdf_dir_txt iff move_to_pdf_dir_chk is selected
        if (e.getSource() == move_to_pdf_dir_chk) {
            pdf_dir_txt.setEnabled(move_to_pdf_dir_chk.isSelected());
        }

        // enable erase_pdf_docinfo_chk iff write_pdf_docinfo_chk is selected
        if (e.getSource() == write_pdf_docinfo_chk) {
            erase_pdf_docinfo_chk.setEnabled(write_pdf_docinfo_chk.isSelected());
        }

    }

    public void actionPerformed(ActionEvent e) {

        // do PDF tasks!
        if (e.getSource() == do_tasks) {
            doTasks();
        }

    }

    private void updatePane() {

        // PDF file type representation
        ExternalFileType pdf_type = Globals.prefs.getExternalFileTypeByExt("pdf");

        // get Bibtex database associated with the current tab
        BasePanel db_panel = frame.basePanel();
        BibtexDatabase db = db_panel.database();

        // build a map of where PDF files are stored
        HashMap<String, Integer> pdf_dir_count = new HashMap<String, Integer>();
        for (BibtexEntry entry : db.getEntries()) {

            // get table of file links for this Bibtex entry
            FileListTableModel files = new FileListTableModel();
            files.setContent(entry.getField(GUIGlobals.FILE_FIELD));

            for (int i = 0; i < files.getRowCount(); ++i) {
                FileListEntry file_entry = files.getEntry(i);

                // skip if this is not a PDF file link
                if (!file_entry.getType().equals(pdf_type))
                    continue;

                // parent directory of PDF file
                String pdf_dir = new File(file_entry.getLink()).getParent();

                // add to map and increment count
                if (pdf_dir_count.containsKey(pdf_dir)) {
                    pdf_dir_count.put(pdf_dir, pdf_dir_count.get(pdf_dir) + 1);
                } else {
                    pdf_dir_count.put(pdf_dir, 1);
                }

            }

        }

        // find directory where the majority of PDF files are stored
        String pdf_dir_max = null;
        int pdf_dir_max_count = 0;
        for (String pdf_dir : pdf_dir_count.keySet()) {
            if (pdf_dir_count.get(pdf_dir) > pdf_dir_max_count) {
                pdf_dir_max_count = pdf_dir_count.get(pdf_dir);
                pdf_dir_max = pdf_dir;
            }
        }

        // set PDF file directory field to this directory
        if (pdf_dir_max == null) {
            pdf_dir_txt.setText("");
        } else {
            pdf_dir_txt.setText(pdf_dir_max);
        }

    }

    private void doTasks() {

        // PDF file type representation
        final ExternalFileType pdf_type = Globals.prefs.getExternalFileTypeByExt("pdf");

        // get Bibtex database associated with the current tab
        final BasePanel db_panel = frame.basePanel();
        final BibtexDatabase db = db_panel.database();
        final MetaData db_meta = db_panel.metaData();

        // get selected Bibtex entries from the current tab
        final BibtexEntry[] db_entries = db_panel.getSelectedEntries();

        // get Bibtex database file for current tab
        final File db_file = db_panel.getFile();
        if (db_file == null || db_file.getParentFile() == null) {
            JOptionPane.showMessageDialog(frame, "Bibtex database must be saved before performing PDF tasks.",
                    title, JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        // get array of directories that PDF files could possibly be in
        final List<File> db_dirs = new LinkedList<File>();
        for (String dir : db_meta.getFileDirectory(GUIGlobals.FILE_FIELD)) {
            db_dirs.add(new File(dir));
        }
        if (db_dirs.size() == 0 || !db_dirs.contains(db_file.getParentFile())) {
            db_dirs.add(db_file.getParentFile());
        }

        // return if no entries are selected
        if (db_entries.length == 0) {
            JOptionPane.showMessageDialog(frame, "No entries selected for PDF tasks.", title,
                    JOptionPane.INFORMATION_MESSAGE);
            return;
        }

        // get PDF file directory
        final File pdf_dir = absoluteFile(pdf_dir_txt.getText(), db_file.getParentFile());

        // do tasks encapsulated in a worker-thread class
        AbstractWorker tasks = new AbstractWorker() {

            boolean cancelled = false;
            boolean confirmed = false;
            boolean erred = false;

            // get user confirmation for PDF file modifications
            private boolean getUserConfirmation() {
                if (!confirmed) {
                    confirmed = JOptionPane.showConfirmDialog(frame,
                            "Are you sure you want to rename, move, and/or modify PDF files?\n"
                                    + "This operations cannot be undone.",
                            title, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
                }
                return confirmed;
            }

            public void init() {

                // block main window
                frame.block();

            }

            public void run() {

                // for debugging purposes
                final boolean modifyDatabase = true;

                // iterate over selected Bibtex entries
                int entry_count = 0;
                for (BibtexEntry entry : db_entries) {
                    ++entry_count;

                    // get Bibtex key, check is not null
                    String key = entry.getCiteKey();
                    if (key == null || key.length() == 0) {
                        JOptionPane.showMessageDialog(frame,
                                "BibTeX entry '" + entry.getId() + "' does not have a key!", title,
                                JOptionPane.ERROR_MESSAGE);
                        erred = true;
                        return;
                    }

                    // update status bar
                    frame.output(String.format("Processing BibTeX entry: %s (%d of %d)...", key, entry_count,
                            db_entries.length));

                    // get table of file links for this Bibtex entry
                    FileListTableModel files = new FileListTableModel();
                    files.setContent(entry.getField(GUIGlobals.FILE_FIELD));

                    for (int fileindex = 0; fileindex < files.getRowCount(); ++fileindex) {
                        FileListEntry file_entry = files.getEntry(fileindex);

                        // skip if this is not a PDF file link
                        if (!file_entry.getType().equals(pdf_type))
                            continue;

                        // get PDF file
                        File pdf_file = null;
                        for (File db_dir : db_dirs) {
                            pdf_file = absoluteFile(file_entry.getLink(), db_dir);
                            if (pdf_file.isFile()) {
                                break;
                            }
                            pdf_file = null;
                        }
                        if (pdf_file == null) {
                            String errmsg = "Could not find PDF file '" + file_entry.getLink() + "' in '"
                                    + db_dirs.get(0);
                            for (int i = 1; i < db_dirs.size(); ++i) {
                                errmsg += "', '" + db_dirs.get(i);
                            }
                            errmsg += "'!";
                            JOptionPane.showMessageDialog(frame, errmsg, title, JOptionPane.ERROR_MESSAGE);
                            erred = true;
                            return;
                        }

                        // get PDF file description
                        String pdf_desc = file_entry.getDescription();

                        // new PDF file
                        File new_pdf_file = pdf_file;

                        // rename PDF file
                        if (rename_pdfs_chk.isSelected()) {

                            // build new PDF name
                            String new_name = key;
                            if (!pdf_desc.isEmpty()) {
                                new_name += "_" + pdf_desc.replace(" ", "_");
                            }
                            new_name += "." + pdf_type.getExtension();

                            // set new PDF file
                            new_pdf_file = absoluteFile(new_name, new_pdf_file.getParentFile());

                        }

                        // move PDF file
                        if (move_to_pdf_dir_chk.isSelected()) {
                            new_pdf_file = absoluteFile(new_pdf_file.getName(), pdf_dir);
                        }

                        // if PDF file needs to be moved
                        if (!new_pdf_file.equals(pdf_file)) {

                            // get user confirmation
                            if (!getUserConfirmation()) {
                                cancelled = true;
                                return;
                            }

                            // perform move/rename operations
                            if (modifyDatabase) {
                                String errmsg = "";
                                try {

                                    // create parent directories
                                    File new_pdf_dir = new_pdf_file.getParentFile();
                                    if (new_pdf_dir != null && !new_pdf_dir.isDirectory()) {
                                        errmsg = "Could not create directory '"
                                                + new_pdf_file.getParentFile().getPath() + "'";
                                        erred = !new_pdf_file.getParentFile().mkdirs();
                                    }
                                    if (!erred) {

                                        // check if PDF file already exists, and ask for confirmation to replace it
                                        if (new_pdf_file.isFile()) {
                                            switch (JOptionPane.showConfirmDialog(frame,
                                                    "PDF file '" + new_pdf_file.getPath() + "' already exists.\n"
                                                            + "Are you sure you want to replace it with "
                                                            + "PDF file '" + pdf_file.getPath() + "'?\n"
                                                            + "This operation cannot be undone.",
                                                    title, JOptionPane.YES_NO_CANCEL_OPTION)) {
                                            case JOptionPane.NO_OPTION:
                                                continue;
                                            case JOptionPane.CANCEL_OPTION:
                                                cancelled = true;
                                                return;
                                            case JOptionPane.YES_OPTION:
                                                errmsg = "Could not delete PDF file '" + new_pdf_file.getPath()
                                                        + "'";
                                                erred = !new_pdf_file.delete();
                                            }
                                        }
                                        // otherwise test that we can create the new PDF file
                                        else {
                                            errmsg = "Could not access PDF file '" + new_pdf_file.getPath() + "'";
                                            erred = !new_pdf_file.createNewFile() || !new_pdf_file.delete();
                                        }

                                        if (!erred) {

                                            // try to move/rename PDF file
                                            errmsg = "Could not rename PDF file '" + pdf_file.getPath() + "' to '"
                                                    + new_pdf_file.getPath() + "'";
                                            erred = !pdf_file.renameTo(new_pdf_file);

                                        }

                                    }
                                }

                                // possible exceptions
                                catch (SecurityException e) {
                                    erred = true;
                                    errmsg += ": insufficient permissions";
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    erred = true;
                                    errmsg += ": an I/O exception occurred";
                                }
                                if (erred) {
                                    JOptionPane.showMessageDialog(frame, errmsg + ".", title,
                                            JOptionPane.ERROR_MESSAGE);
                                    return;
                                }

                                // everything was successful
                                pdf_file = new_pdf_file;

                            }
                        }

                        // update file entry table and Bibtex entry
                        file_entry.setLink(relativePath(pdf_file, db_dirs.get(0)));
                        if (modifyDatabase) {
                            String new_files = files.getStringRepresentation();
                            if (!new_files.equals(entry.getField(GUIGlobals.FILE_FIELD))) {
                                entry.setField(GUIGlobals.FILE_FIELD, new_files);
                                db_panel.markNonUndoableBaseChanged();
                            }
                        }

                        // perform operations on PDF file contents
                        if (write_pdf_docinfo_chk.isSelected()) {

                            if (erase_pdf_docinfo_chk.isSelected()) {

                                // get user confirmation
                                if (!getUserConfirmation()) {
                                    cancelled = true;
                                    return;
                                }

                                // open PDF file
                                PDDocument document = null;
                                try {
                                    document = PDDocument.load(pdf_file);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    erred = true;
                                    JOptionPane.showMessageDialog(frame,
                                            "Could not open PDF file '" + pdf_file.getPath()
                                                    + "': an I/O exception occurred.",
                                            title, JOptionPane.ERROR_MESSAGE);
                                    return;
                                }

                                // erase document information
                                document.setDocumentInformation(new PDDocumentInformation());

                                // erase XML metadata
                                document.getDocumentCatalog().setMetadata(null);

                                // save and close PDF file
                                try {
                                    document.save(pdf_file.getPath());
                                    document.close();
                                } catch (COSVisitorException e) {
                                    e.printStackTrace();
                                    erred = true;
                                    JOptionPane.showMessageDialog(frame,
                                            "Could not save PDF file '" + pdf_file.getPath()
                                                    + "': an exception occurred.",
                                            title, JOptionPane.ERROR_MESSAGE);
                                    return;
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    erred = true;
                                    JOptionPane.showMessageDialog(frame,
                                            "Could not save/close PDF file '" + pdf_file.getPath()
                                                    + "': an I/O exception occurred.",
                                            title, JOptionPane.ERROR_MESSAGE);
                                    return;
                                }

                            }

                            // write XMP / PDF document catalog metadata
                            try {
                                XMPUtil.writeXMP(pdf_file, entry, db);
                            } catch (IOException e) {
                                e.printStackTrace();
                                erred = true;
                                JOptionPane.showMessageDialog(frame,
                                        "Could not write XMP to PDF file '" + pdf_file.getPath()
                                                + "': an I/O exception occurred.",
                                        title, JOptionPane.ERROR_MESSAGE);
                                return;
                            } catch (TransformerException e) {
                                e.printStackTrace();
                                erred = true;
                                JOptionPane
                                        .showMessageDialog(frame,
                                                "Could not write XMP to PDF file '" + pdf_file.getPath()
                                                        + "': an exception occurred.",
                                                title, JOptionPane.ERROR_MESSAGE);
                                return;
                            }

                        }

                    }

                }

            }

            public void update() {

                // unblock main window
                frame.unblock();

                // print to status bar
                if (erred) {
                    frame.output("An error occurred during PDF Tasks");
                } else if (cancelled) {
                    frame.output("Cancelled PDF Tasks");
                } else {
                    frame.output("Completed PDF Tasks");
                }

            }

        };

        // run task thread (based on code in BasePanel.runCommand())
        try {
            tasks.init();
            tasks.getWorker().run();
            tasks.getCallBack().update();
        } catch (Throwable e) {
            frame.unblock();
            e.printStackTrace();
        }

    }

    // return an absolute file for 'path' relative to 'base'
    private File absoluteFile(String path, File base) {
        File file = new File(path);
        if (!file.isAbsolute()) {
            file = new File(base, path);
        }
        try {
            return file.getCanonicalFile();
        } catch (IOException e) {
            return null;
        }
    }

    // determine the path of 'file' relative to 'base'
    private String relativePath(File file, File base) {

        // canonicalise input files, if possible
        try {
            if (file != null) {
                file = file.getCanonicalFile();
            }
            if (base != null) {
                base = base.getCanonicalFile();
            }
        } catch (IOException e) {
            return null;
        }
        if (file == null) {
            return null;
        }

        // split 'file' and 'base' into path components
        LinkedList<String> descend_path = new LinkedList<String>();
        while (base != null) {
            String name = base.getName();
            if (!name.isEmpty()) {
                descend_path.addFirst(name);
            }
            base = base.getParentFile();
        }
        LinkedList<String> ascend_path = new LinkedList<String>();
        while (file != null) {
            String name = file.getName();
            if (!name.isEmpty()) {
                ascend_path.addFirst(name);
            }
            file = file.getParentFile();
        }

        // remove common path components
        boolean common = false;
        while (!descend_path.isEmpty() && !ascend_path.isEmpty()
                && descend_path.getFirst().equals(ascend_path.getFirst())) {
            descend_path.removeFirst();
            ascend_path.removeFirst();
            common = true;
        }

        // cannot make relative path if there are no common path components
        if (!common) {
            return null;
        }

        // build relative path string
        StringBuffer rel_path = new StringBuffer();
        for (int i = 0; i < descend_path.size(); ++i) {
            if (rel_path.length() > 0) {
                rel_path.append(File.separator);
            }
            rel_path.append("..");
        }
        for (String name : ascend_path) {
            if (rel_path.length() > 0) {
                rel_path.append(File.separator);
            }
            rel_path.append(name);
        }

        return rel_path.toString();

    }

}

// Local Variables:
// mode: java
// c-basic-offset: 4
// tab-width: 4
// End: