it.flavianopetrocchi.jpdfbookmarks.JPdfBookmarks.java Source code

Java tutorial

Introduction

Here is the source code for it.flavianopetrocchi.jpdfbookmarks.JPdfBookmarks.java

Source

/*
 * JPdfBookmarks.java
 *
 * Copyright (c) 2010 Flaviano Petrocchi <flavianopetrocchi at gmail.com>.
 * All rights reserved.
 *
 * This file is part of JPdfBookmarks.
 *
 * JPdfBookmarks 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.
 *
 * JPdfBookmarks 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 JPdfBookmarks.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.flavianopetrocchi.jpdfbookmarks;

import com.lowagie.text.exceptions.BadPasswordException;
import it.flavianopetrocchi.jpdfbookmarks.bookmark.IBookmarksConverter;
import it.flavianopetrocchi.jpdfbookmarks.bookmark.Bookmark;
import it.flavianopetrocchi.utilities.Ut;
import java.awt.EventQueue;
import java.io.BufferedReader;
import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;

/**
 * This is the main class of the application. It parses the command line and
 * chooses the appropriate mode to start. The default mode is GUI mode.
 */
class JPdfBookmarks {

    public static final boolean DEBUG = false;

    //private IBookmarksConverter pdf;
    private enum Mode {

        DUMP, APPLY, HELP, GUI, VERSION, SHOW_ON_OPEN,
    }

    // <editor-fold defaultstate="expanded" desc="Member variables">
    private static String version = "1.0.0";
    private static final String VERSION_FILE = "jpdfbookmarks.properties";
    static {
        Properties prop = new Properties();
        try {
            InputStream in = JPdfBookmarks.class.getResourceAsStream(VERSION_FILE);
            prop.load(in);
            version = prop.getProperty("VERSION");
        } catch (FileNotFoundException ex) {
            Logger.getLogger(JPdfBookmarks.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(JPdfBookmarks.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    public static final String APP_NAME = "JPdfBookmarks";
    protected static final String NEWLINE = System.getProperty("line.separator");
    public static final String DOWNLOAD_URL = "http://flavianopetrocchi.blogspot.com/2008/07/jpsdbookmarks-download-page.html";
    public static final String BLOG_URL = "http://flavianopetrocchi.blogspot.com";
    public static final String ITEXT_URL = "http://www.lowagie.com/iText/";
    //    public static final String LAST_VERSION_URL =
    //            "http://jpdfbookmarks.altervista.org/version/lastVersion";
    public static final String LAST_VERSION_PROPERTIES_URL = "http://jpdfbookmarks.altervista.org/version/jpdfbookmarks.properties";
    public static final String MANUAL_URL = "http://sourceforge.net/apps/mediawiki/jpdfbookmarks/";
    private static final int MAX_PASSWORD_LEN = 32;
    //"http://jpdfbookmarks.altervista.org";
    private Mode mode = Mode.GUI;
    private Options options = createOptions();
    private final PrintWriter out = new PrintWriter(System.out, true);
    private final PrintWriter err = new PrintWriter(System.err, true);
    private String inputFilePath = null;
    private String outputFilePath = "output.pdf";
    private String bookmarksFilePath = null;
    private String pageSeparator = "/";
    private String attributesSeparator = ",";
    private String indentationString = "\t";
    private String firstTargetString = null;
    private boolean silentMode = false;
    private String charset = Charset.defaultCharset().displayName();
    private String showOnOpenArg = null;
    private byte[] userPassword = null;
    private byte[] ownerPassword = null;// </editor-fold>

    //<editor-fold defaultstate="expanded" desc="public methods">
    public static void main(String[] args) {
        localizeExternalModules();
        JPdfBookmarks app = new JPdfBookmarks();
        app.start(args);
    }

    public static String getVersion() {
        return version;
    }

    private static void localizeExternalModules() {
        Bookmark.localizeStrings(Res.getString("DEFAULT_TITLE"), Res.getString("PAGE"),
                Res.getString("PARSE_ERROR"));
    }

    private byte[] askUserPassword() {
        //avoid the use of strings when dealing with passwords they remain in memomory
        Console cons;
        char[] passwdChars = null;
        byte[] passwdBytes = null;
        if ((cons = System.console()) != null
                && (passwdChars = cons.readPassword("[%s:]", Res.getString("PASSWORD"))) != null) {
            passwdBytes = Ut.arrayOfCharsToArrayOfBytes(passwdChars);
            Arrays.fill(passwdChars, ' ');
        } else {
            out.print("[" + Res.getString("LABEL_PASSWORD") + "]");
            out.flush();
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            passwdChars = new char[MAX_PASSWORD_LEN];
            try {
                int charsRead = in.read(passwdChars, 0, MAX_PASSWORD_LEN);
                //remove \r and \n from the password
                for (int i = charsRead - 1; i >= 0; i--) {
                    if (passwdChars[i] == '\r' || passwdChars[i] == '\n') {
                        charsRead--;
                    } else {
                        break;
                    }
                }
                char[] trimmedPasswd = Arrays.copyOf(passwdChars, charsRead);
                Arrays.fill(passwdChars, ' ');
                passwdBytes = Ut.arrayOfCharsToArrayOfBytes(trimmedPasswd);
                Arrays.fill(trimmedPasswd, ' ');
            } catch (IOException ex) {
            }
        }
        return passwdBytes;
    }

    private IBookmarksConverter fatalGetConverterAndOpenPdf(String inputFilePath) {
        IBookmarksConverter pdf = Bookmark.getBookmarksConverter();
        if (pdf != null) {
            try {
                userPassword = null;
                while (true) {
                    try {
                        pdf.open(inputFilePath, userPassword);
                        break;
                    } catch (BadPasswordException e) {
                        userPassword = null;
                        out.println(Res.getString("DIALOG_USER_PASSWORD"));
                        while (userPassword == null) {
                            userPassword = askUserPassword();
                        }
                    }
                }
            } catch (IOException ex) {
                fatalOpenFileError(inputFilePath);
            }
        } else {
            err.println(Res.getString("ERROR_BOOKMARKS_CONVERTER_NOT_FOUND"));
            System.exit(1);
        }
        return pdf;
    }

    private void apply() {
        IBookmarksConverter pdf = fatalGetConverterAndOpenPdf(inputFilePath);

        if (outputFilePath == null || outputFilePath.equals(inputFilePath)) {
            if (getYesOrNo(Res.getString("ERR_INFILE_EQUAL_OUTFILE"))) {
                outputFilePath = inputFilePath;
                applySave(pdf, inputFilePath, outputFilePath);
            }
        } else {
            File f = new File(outputFilePath);
            if (!f.exists() || getYesOrNo(Res.getString("WARNING_OVERWRITE_CMD"))) {
                applySave(pdf, inputFilePath, outputFilePath);
            }
        }

    }

    private byte[] openAsOwner(IBookmarksConverter pdf, String input) {
        //here instead we check for owner password
        boolean ownerPasswordNeeded = false;
        if (userPassword != null) {
            if (!pdf.isBookmarksEditingPermitted()) {
                ownerPasswordNeeded = true;
            }
        } else if (pdf.isEncryped()) {
            ownerPasswordNeeded = true;
        }

        outer: while (ownerPasswordNeeded) {
            out.println(Res.getString("DIALOG_OWNER_PASSWORD"));
            ownerPassword = null;
            while (ownerPassword == null) {
                ownerPassword = askUserPassword();
            }
            try {
                pdf.close();
                pdf.open(input, ownerPassword);
                if (pdf.isBookmarksEditingPermitted()) {
                    ownerPasswordNeeded = false;
                }
            } catch (Exception e) {
                ownerPassword = null;
            }
        }

        return ownerPassword;
    }

    private void resetPasswords() {
        if (userPassword != null) {
            Arrays.fill(userPassword, (byte) 0);
            userPassword = null;
        }
        if (ownerPassword != null) {
            Arrays.fill(ownerPassword, (byte) 0);
            ownerPassword = null;
        }
    }

    private void applySave(IBookmarksConverter pdf, String input, String output) {
        try {
            ownerPassword = openAsOwner(pdf, input);

            Applier applier = new Applier(pdf, indentationString, pageSeparator, attributesSeparator);

            try {
                applier.loadBookmarksFile(bookmarksFilePath, charset);
            } catch (Exception ex) {
                fatalOpenFileError(bookmarksFilePath);
            }

            applier.save(output, userPassword, ownerPassword);
            pdf.close();
            resetPasswords();
        } catch (IOException ex) {
            fatalSaveFileError(output);
        }
    }

    private void dump() {
        IBookmarksConverter pdf = fatalGetConverterAndOpenPdf(inputFilePath);
        resetPasswords();
        Dumper dumper = new Dumper(pdf, indentationString, pageSeparator, attributesSeparator);

        if (outputFilePath == null) {
            dumper.printBookmarksIterative(new OutputStreamWriter(System.out));
        } else {
            File f = new File(outputFilePath);
            if (!f.exists() || getYesOrNo(Res.getString("WARNING_OVERWRITE_CMD"))) {
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(outputFilePath);
                    OutputStreamWriter outStream = new OutputStreamWriter(fos, charset);
                    dumper.printBookmarksIterative(outStream);
                    outStream.close();
                    pdf.close();
                } catch (FileNotFoundException ex) {
                    fatalOpenFileError(outputFilePath);
                } catch (UnsupportedEncodingException ex) {
                    //already checked in command line parsing
                } catch (IOException ex) {
                }
            }
        }
    }

    private void save(IBookmarksConverter ipdf, byte[] ownerPassword) {
        try {
            ipdf.save(outputFilePath, userPassword, ownerPassword);
            ipdf.close();
            if (userPassword != null) {
                Arrays.fill(userPassword, (byte) 0);
            }
            if (ownerPassword != null) {
                Arrays.fill(ownerPassword, (byte) 0);
            }
        } catch (IOException ex) {
            fatalSaveFileError(outputFilePath);
        }
    }

    private void showOnOpen() {
        IBookmarksConverter ipdf = fatalGetConverterAndOpenPdf(inputFilePath);

        if (showOnOpenArg.equalsIgnoreCase("CHECK") || showOnOpenArg.equalsIgnoreCase("c")) {
            if (ipdf.showBookmarksOnOpen()) {
                out.println("YES");
            } else {
                out.println("NO");
            }
        } else {
            ownerPassword = openAsOwner(ipdf, inputFilePath);
            if (showOnOpenArg.equalsIgnoreCase("yes") || showOnOpenArg.equalsIgnoreCase("y")) {
                ipdf.setShowBookmarksOnOpen(true);
            } else if (showOnOpenArg.equalsIgnoreCase("no") || showOnOpenArg.equalsIgnoreCase("n")) {
                ipdf.setShowBookmarksOnOpen(false);
            }
            if (outputFilePath == null || outputFilePath.equals(inputFilePath)) {
                if (getYesOrNo(Res.getString("ERR_INFILE_EQUAL_OUTFILE"))) {
                    outputFilePath = inputFilePath;
                    save(ipdf, ownerPassword);
                }
            } else {
                File f = new File(outputFilePath);
                if (!f.exists() || getYesOrNo(Res.getString("WARNING_OVERWRITE_CMD"))) {
                    save(ipdf, ownerPassword);
                }
            }
        }
        resetPasswords();
    }

    private void fatalOpenFileError(String filePath) {
        err.println(Res.getString("ERROR_OPENING_FILE") + " " + filePath);
        System.exit(1);
    }

    private void fatalSaveFileError(String filePath) {
        err.println(Res.getString("ERROR_SAVING_FILE") + " (" + filePath + ")");
        System.exit(1);
    }

    public void launchNewGuiInstance(String path, Bookmark bookmark) {
        EventQueue.invokeLater(new GuiLauncher(path, bookmark));
    }

    static public File getPath() {
        File f = null;
        try {
            f = new File(JPdfBookmarks.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
        } catch (URISyntaxException ex) {
        }
        return f;
    }

    /**
     * Start the application in the requested mode.
     *
     * @param args Arguments to select mode and pass files to process. Can be
     *             null.
     */
    public void start(final String[] args) {
        if (args != null && args.length > 0) {
            setModeByCommandLine(args);
        }

        switch (mode) {
        case VERSION:
            out.println(version);
            break;
        case DUMP:
            dump();
            break;
        case SHOW_ON_OPEN:
            showOnOpen();
            break;
        case APPLY:
            apply();
            break;
        case GUI:
            //launchNewGuiInstance(inputFilePath, null);
            EventQueue.invokeLater(new GuiLauncher(inputFilePath));
            break;
        case HELP:
        default:
            printHelpMessage();

        }
    }

    public class GuiLauncher implements Runnable {

        private Bookmark firstTarget;
        private String inputPath;

        public GuiLauncher(String inputPath) {
            if (inputPath != null) {
                IBookmarksConverter ipdf = fatalGetConverterAndOpenPdf(inputPath);
                if (firstTargetString != null) {
                    StringBuilder buffer = new StringBuilder("Bookmark");
                    buffer.append(pageSeparator).append(firstTargetString);
                    firstTarget = Bookmark.bookmarkFromString(null, ipdf, buffer.toString(), indentationString,
                            pageSeparator, attributesSeparator);
                }
                this.inputPath = inputPath;
                try {
                    ipdf.close();
                } catch (IOException ex) {
                }
            }
        }

        public GuiLauncher(String inputPath, Bookmark firstTarget) {
            this.firstTarget = firstTarget;
            this.inputPath = inputPath;
        }

        @Override
        public void run() {
            try {
                JPdfBookmarksGui viewer;
                viewer = new JPdfBookmarksGui();
                Prefs prefs = new Prefs();
                //prefs.setGplAccepted(false); //for debugging only remember to comment
                if (!prefs.getGplAccepted()) {
                    LicenseBox licenseBox = new LicenseBox(null, true);
                    licenseBox.setLocationRelativeTo(null);
                    licenseBox.setVisible(true);
                    if (licenseBox.getLicenseAccepted()) {
                        prefs.setGplAccepted(true);
                    } else {
                        return;
                    }
                }
                viewer.setVisible(true);
                if (inputPath != null) {
                    viewer.openFileAsync(new File(new File(inputPath).getAbsolutePath()), this.firstTarget);
                }
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(null, ex.getMessage(), APP_NAME, JOptionPane.INFORMATION_MESSAGE);
            }
        }
    }

    public static void printErrorForDebug(Exception e) {
        if (DEBUG) {
            System.err.println("***** printErrorForDebug Start *****");
            System.err.println(e.getMessage());
            System.err.println("***** printErrorForDebug End *****");
        }
    }

    public void printHelpMessage() {
        HelpFormatter help = new HelpFormatter();
        String header = Res.getString("APP_DESCR");
        String syntax = "jpdfbookmarks <input.pdf> "
                + "[--dump | --apply <bookmarks.txt> | --show-on-open <YES | NO | CHECK> "
                + "| --help | --version] [--out <output.pdf>]";
        int width = 80;
        int leftPad = 1, descPad = 2;
        String footer = Res.getString("BOOKMARKS_DESCR");
        help.printHelp(out, width, syntax, header, options, leftPad, descPad, footer);
    }//</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="private methods">
    /**
     * Sets the mode by the command line arguments and initializes files to
     * process if passed as arguments.
     *
     * @param args Arguments to process
     */
    private void setModeByCommandLine(String[] args) {
        Prefs userPrefs = new Prefs();
        CommandLineParser parser = new PosixParser();
        try {
            CommandLine cmd = parser.parse(options, args);

            if (cmd.hasOption('h')) {
                mode = Mode.HELP;
            } else if (cmd.hasOption('v')) {
                mode = Mode.VERSION;
            } else if (cmd.hasOption('w')) {
                mode = Mode.SHOW_ON_OPEN;
                showOnOpenArg = cmd.getOptionValue('w');
                if (cmd.hasOption('o')) {
                    outputFilePath = cmd.getOptionValue('o');
                } else {
                    outputFilePath = null;
                }
            } else if (cmd.hasOption('a')) {
                mode = Mode.APPLY;
                bookmarksFilePath = cmd.getOptionValue('a');
            } else if (cmd.hasOption('d')) {
                mode = Mode.DUMP;
            } else {
                mode = Mode.GUI;
                if (cmd.hasOption('b')) {
                    firstTargetString = cmd.getOptionValue('b');
                }
            }

            if (cmd.hasOption('o')) {
                outputFilePath = cmd.getOptionValue('o');
            } else {
                outputFilePath = null;
            }

            String[] leftOverArgs = cmd.getArgs();
            if (leftOverArgs.length > 0) {
                inputFilePath = leftOverArgs[0];
            } else if (mode == Mode.DUMP || mode == Mode.APPLY) {
                throw new ParseException(Res.getString("ERR_NO_INPUT_FILE"));
            }

            if (cmd.hasOption("p")) {
                pageSeparator = cmd.getOptionValue("p");
            } else {
                pageSeparator = userPrefs.getPageSeparator();
            }
            if (cmd.hasOption("i")) {
                indentationString = cmd.getOptionValue("i");
            } else {
                indentationString = userPrefs.getIndentationString();
            }
            if (cmd.hasOption("t")) {
                attributesSeparator = cmd.getOptionValue("t");
            } else {
                attributesSeparator = userPrefs.getAttributesSeparator();
            }
            if (cmd.hasOption("f")) {
                silentMode = true;
            }
            if (cmd.hasOption("e")) {
                charset = cmd.getOptionValue("e");
                if (!Charset.isSupported(charset)) {
                    throw new ParseException(Res.getString("ERR_CHARSET_NOT_SUPPORTED"));
                }
            }

            if (pageSeparator.equals(indentationString) || pageSeparator.equals(attributesSeparator)
                    || indentationString.equals(attributesSeparator)) {
                throw new ParseException(Res.getString("ERR_OPTIONS_CONTRAST"));
            }

        } catch (ParseException ex) {
            mode = Mode.GUI;
            err.println(ex.getLocalizedMessage());
            System.exit(1);
        }

    }

    /**
     * Get the user answer yes or no, on the command line. It recognize as a yes
     * y or yes and as a no n or no. Not case sensitive.
     *
     * @param question Question to the user.
     * @return Yes will return true and No will return false.
     */
    private boolean getYesOrNo(String question) {
        if (silentMode) {
            return true;
        }
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter cout = new PrintWriter(System.out, true);
        boolean answer = false;
        boolean validInput = false;
        while (!validInput) {
            cout.println(question);
            try {
                String line = in.readLine();
                if (line.equalsIgnoreCase(Res.getString("SHORT_YES"))
                        || line.equalsIgnoreCase(Res.getString("LONG_YES"))) {
                    answer = true;
                    validInput = true;
                } else if (line.equalsIgnoreCase(Res.getString("SHORT_NO"))
                        || line.equalsIgnoreCase(Res.getString("LONG_NO"))) {
                    answer = false;
                    validInput = true;
                }
            } catch (IOException ex) {
            }
        }
        return answer;
    }

    @SuppressWarnings("static-access")
    private Options createOptions() {
        Options appOptions = new Options();

        appOptions.addOption("f", "force", false, Res.getString("FORCE_DESCR"));
        appOptions.addOption("v", "version", false, Res.getString("VERSION_DESCR"));
        appOptions.addOption("h", "help", false, Res.getString("HELP_DESCR"));
        appOptions.addOption(
                OptionBuilder.withLongOpt("dump").withDescription(Res.getString("DUMP_DESCR")).create('d'));
        appOptions.addOption(OptionBuilder.withLongOpt("apply").hasArg(true).withArgName("bookmarks.txt")
                .withDescription(Res.getString("APPLY_DESCR")).create('a'));
        appOptions.addOption(OptionBuilder.withLongOpt("out").hasArg(true).withArgName("output.pdf")
                .withDescription(Res.getString("OUT_DESCR")).create('o'));
        appOptions.addOption(OptionBuilder.withLongOpt("encoding").hasArg(true).withArgName("UTF-8")
                .withDescription(Res.getString("ENCODING_DESCR")).create('e'));

        appOptions.addOption("b", "bookmark", true, Res.getString("BOOKMARK_ARG_DESCR"));
        appOptions.addOption("p", "page-sep", true, Res.getString("PAGE_SEP_DESCR"));
        appOptions.addOption("t", "attributes-sep", true, Res.getString("ATTRIBUTES_SEP_DESCR"));
        appOptions.addOption("i", "indentation", true, Res.getString("INDENTATION_STRING_DESCR"));
        appOptions.addOption("w", "show-on-open", true, Res.getString("SHOW_ON_OPEN_DESCR"));

        return appOptions;
    }
    //</editor-fold>
}