net.sf.jabref.importer.OpenDatabaseAction.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jabref.importer.OpenDatabaseAction.java

Source

/*  Copyright (C) 2003-2015 JabRef contributors.
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, 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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package net.sf.jabref.importer;

import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;

import javax.swing.Action;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import net.sf.jabref.BibDatabaseContext;
import net.sf.jabref.Defaults;
import net.sf.jabref.Globals;
import net.sf.jabref.JabRefExecutorService;
import net.sf.jabref.MetaData;
import net.sf.jabref.gui.BasePanel;
import net.sf.jabref.gui.FileDialogs;
import net.sf.jabref.gui.IconTheme;
import net.sf.jabref.gui.JabRefFrame;
import net.sf.jabref.gui.ParserResultWarningDialog;
import net.sf.jabref.gui.actions.MnemonicAwareAction;
import net.sf.jabref.gui.exporter.AutoSaveManager;
import net.sf.jabref.gui.keyboard.KeyBinding;
import net.sf.jabref.gui.undo.NamedCompound;
import net.sf.jabref.importer.fileformat.BibtexImporter;
import net.sf.jabref.logic.l10n.Localization;
import net.sf.jabref.logic.util.io.FileBasedLock;
import net.sf.jabref.logic.util.strings.StringUtil;
import net.sf.jabref.migrations.FileLinksUpgradeWarning;
import net.sf.jabref.model.database.BibDatabase;
import net.sf.jabref.model.database.BibDatabaseMode;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.preferences.JabRefPreferences;
import net.sf.jabref.specialfields.SpecialFieldsUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

// The action concerned with opening an existing database.

public class OpenDatabaseAction extends MnemonicAwareAction {

    private static final Log LOGGER = LogFactory.getLog(OpenDatabaseAction.class);

    private final boolean showDialog;
    private final JabRefFrame frame;

    // List of actions that may need to be called after opening the file. Such as
    // upgrade actions etc. that may depend on the JabRef version that wrote the file:
    private static final List<PostOpenAction> POST_OPEN_ACTIONS = new ArrayList<>();

    static {
        // Add the action for checking for new custom entry types loaded from the bib file:
        POST_OPEN_ACTIONS.add(new CheckForNewEntryTypesAction());
        // Add the action for converting legacy entries in ExplicitGroup
        POST_OPEN_ACTIONS.add(new ConvertLegacyExplicitGroups());
        // Add the action for the new external file handling system in version 2.3:
        POST_OPEN_ACTIONS.add(new FileLinksUpgradeWarning());
        // Add the action for warning about and handling duplicate BibTeX keys:
        POST_OPEN_ACTIONS.add(new HandleDuplicateWarnings());
    }

    public OpenDatabaseAction(JabRefFrame frame, boolean showDialog) {
        super(IconTheme.JabRefIcon.OPEN.getIcon());
        this.frame = frame;
        this.showDialog = showDialog;
        putValue(Action.NAME, Localization.menuTitle("Open database"));
        putValue(Action.ACCELERATOR_KEY, Globals.getKeyPrefs().getKey(KeyBinding.OPEN_DATABASE));
        putValue(Action.SHORT_DESCRIPTION, Localization.lang("Open BibTeX database"));
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        List<File> filesToOpen = new ArrayList<>();

        if (showDialog) {
            List<String> chosenStrings = FileDialogs.getMultipleFiles(frame,
                    new File(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)),
                    Collections.singletonList(".bib"), true);
            for (String chosen : chosenStrings) {
                if (chosen != null) {
                    filesToOpen.add(new File(chosen));
                }
            }
        } else {
            LOGGER.info(Action.NAME + " " + e.getActionCommand());
            filesToOpen.add(new File(StringUtil.getCorrectFileName(e.getActionCommand(), "bib")));
        }

        openFiles(filesToOpen, true);
    }

    /**
     * Opens the given file. If null or 404, nothing happens
     *
     * @param file the file, may be null or not existing
     */
    public void openFile(File file, boolean raisePanel) {
        List<File> filesToOpen = new ArrayList<>();
        filesToOpen.add(file);
        openFiles(filesToOpen, raisePanel);
    }

    public void openFilesAsStringList(List<String> fileNamesToOpen, boolean raisePanel) {
        List<File> filesToOpen = new ArrayList<>();
        for (String fileName : fileNamesToOpen) {
            filesToOpen.add(new File(fileName));
        }
        openFiles(filesToOpen, raisePanel);
    }

    /**
     * Opens the given files. If one of it is null or 404, nothing happens
     *
     * @param filesToOpen the filesToOpen, may be null or not existing
     */
    public void openFiles(List<File> filesToOpen, boolean raisePanel) {
        BasePanel toRaise = null;
        int initialCount = filesToOpen.size();
        int removed = 0;

        // Check if any of the files are already open:
        for (Iterator<File> iterator = filesToOpen.iterator(); iterator.hasNext();) {
            File file = iterator.next();
            for (int i = 0; i < frame.getTabbedPane().getTabCount(); i++) {
                BasePanel basePanel = frame.getBasePanelAt(i);
                if ((basePanel.getBibDatabaseContext().getDatabaseFile() != null)
                        && basePanel.getBibDatabaseContext().getDatabaseFile().equals(file)) {
                    iterator.remove();
                    removed++;
                    // See if we removed the final one. If so, we must perhaps
                    // raise the BasePanel in question:
                    if (removed == initialCount) {
                        toRaise = basePanel;
                    }
                    // no more bps to check, we found a matching one
                    break;
                }
            }
        }

        // Run the actual open in a thread to prevent the program
        // locking until the file is loaded.
        if (!filesToOpen.isEmpty()) {
            final List<File> theFiles = Collections.unmodifiableList(filesToOpen);
            JabRefExecutorService.INSTANCE.execute(() -> {
                for (File theFile : theFiles) {
                    openTheFile(theFile, raisePanel);
                }
            });
            for (File theFile : theFiles) {
                frame.getFileHistory().newFile(theFile.getPath());
            }
        }
        // If no files are remaining to open, this could mean that a file was
        // already open. If so, we may have to raise the correct tab:
        else if (toRaise != null) {
            frame.output(Localization.lang("File '%0' is already open.",
                    toRaise.getBibDatabaseContext().getDatabaseFile().getPath()));
            frame.getTabbedPane().setSelectedComponent(toRaise);
        }

        frame.output(Localization.lang("Files opened") + ": " + (filesToOpen.size()));
    }

    /**
     * @param file the file, may be null or not existing
     */
    private void openTheFile(File file, boolean raisePanel) {
        if ((file != null) && file.exists()) {
            File fileToLoad = file;
            frame.output(Localization.lang("Opening") + ": '" + file.getPath() + "'");
            boolean tryingAutosave = false;
            boolean autoSaveFound = AutoSaveManager.newerAutoSaveExists(file);
            if (autoSaveFound && !Globals.prefs.getBoolean(JabRefPreferences.PROMPT_BEFORE_USING_AUTOSAVE)) {
                // We have found a newer autosave, and the preferences say we should load
                // it without prompting, so we replace the fileToLoad:
                fileToLoad = AutoSaveManager.getAutoSaveFile(file);
                tryingAutosave = true;
            } else if (autoSaveFound) {
                // We have found a newer autosave, but we are not allowed to use it without
                // prompting.
                int answer = JOptionPane.showConfirmDialog(null, "<html>"
                        + Localization.lang("An autosave file was found for this database. This could indicate "
                                + "that JabRef didn't shut down cleanly last time the file was used.")
                        + "<br>" + Localization.lang("Do you want to recover the database from the autosave file?")
                        + "</html>", Localization.lang("Recover from autosave"), JOptionPane.YES_NO_OPTION);
                if (answer == JOptionPane.YES_OPTION) {
                    fileToLoad = AutoSaveManager.getAutoSaveFile(file);
                    tryingAutosave = true;
                }
            }

            boolean done = false;
            while (!done) {
                String fileName = file.getPath();
                Globals.prefs.put(JabRefPreferences.WORKING_DIRECTORY, file.getPath());
                // Should this be done _after_ we know it was successfully opened?

                if (FileBasedLock.hasLockFile(file.toPath())) {
                    Optional<FileTime> modificationTime = FileBasedLock.getLockFileTimeStamp(file.toPath());
                    if ((modificationTime.isPresent()) && ((System.currentTimeMillis()
                            - modificationTime.get().toMillis()) > FileBasedLock.LOCKFILE_CRITICAL_AGE)) {
                        // The lock file is fairly old, so we can offer to "steal" the file:
                        int answer = JOptionPane.showConfirmDialog(null,
                                "<html>" + Localization.lang("Error opening file") + " '" + fileName + "'. "
                                        + Localization.lang("File is locked by another JabRef instance.") + "<p>"
                                        + Localization.lang("Do you want to override the file lock?"),
                                Localization.lang("File locked"), JOptionPane.YES_NO_OPTION);
                        if (answer == JOptionPane.YES_OPTION) {
                            FileBasedLock.deleteLockFile(file.toPath());
                        } else {
                            return;
                        }
                    } else if (!FileBasedLock.waitForFileLock(file.toPath(), 10)) {
                        JOptionPane.showMessageDialog(null,
                                Localization.lang("Error opening file") + " '" + fileName + "'. "
                                        + Localization.lang("File is locked by another JabRef instance."),
                                Localization.lang("Error"), JOptionPane.ERROR_MESSAGE);
                        return;
                    }

                }

                Charset encoding = Globals.prefs.getDefaultEncoding();
                ParserResult result;
                String errorMessage = null;
                try {
                    result = OpenDatabaseAction.loadDatabase(fileToLoad, encoding);
                } catch (IOException ex) {
                    LOGGER.error("Error loading database " + fileToLoad, ex);
                    result = ParserResult.getNullResult();
                }
                if (result.isNullResult()) {
                    JOptionPane.showMessageDialog(null,
                            Localization.lang("Error opening file") + " '" + fileName + "'",
                            Localization.lang("Error"), JOptionPane.ERROR_MESSAGE);

                    String message = "<html>" + errorMessage + "<p>"
                            + (tryingAutosave ? Localization.lang(
                                    "Error opening autosave of '%0'. Trying to load '%0' instead.", file.getName())
                                    : ""/*Globals.lang("Error opening file '%0'.", file.getName())*/)
                            + "</html>";
                    JOptionPane.showMessageDialog(null, message, Localization.lang("Error opening file"),
                            JOptionPane.ERROR_MESSAGE);

                    if (tryingAutosave) {
                        tryingAutosave = false;
                        fileToLoad = file;
                    } else {
                        done = true;
                    }
                    continue;
                } else {
                    done = true;
                }

                final BasePanel panel = addNewDatabase(result, file, raisePanel);
                if (tryingAutosave) {
                    panel.markNonUndoableBaseChanged();
                }

                // After adding the database, go through our list and see if
                // any post open actions need to be done. For instance, checking
                // if we found new entry types that can be imported, or checking
                // if the database contents should be modified due to new features
                // in this version of JabRef:
                final ParserResult finalReferenceToResult = result;
                SwingUtilities.invokeLater(
                        () -> OpenDatabaseAction.performPostOpenActions(panel, finalReferenceToResult, true));
            }

        }
    }

    /**
     * Go through the list of post open actions, and perform those that need to be performed.
     *
     * @param panel  The BasePanel where the database is shown.
     * @param result The result of the bib file parse operation.
     */
    public static void performPostOpenActions(BasePanel panel, ParserResult result, boolean mustRaisePanel) {
        for (PostOpenAction action : OpenDatabaseAction.POST_OPEN_ACTIONS) {
            if (action.isActionNecessary(result)) {
                if (mustRaisePanel) {
                    panel.frame().getTabbedPane().setSelectedComponent(panel);
                }
                action.performAction(panel, result);
            }
        }
    }

    private BasePanel addNewDatabase(ParserResult result, final File file, boolean raisePanel) {

        String fileName = file.getPath();
        BibDatabase database = result.getDatabase();
        MetaData meta = result.getMetaData();

        if (result.hasWarnings()) {
            JabRefExecutorService.INSTANCE
                    .execute(() -> ParserResultWarningDialog.showParserResultWarningDialog(result, frame));
        }

        Defaults defaults = new Defaults(
                BibDatabaseMode.fromPreference(Globals.prefs.getBoolean(JabRefPreferences.BIBLATEX_DEFAULT_MODE)));
        BasePanel basePanel = new BasePanel(frame, new BibDatabaseContext(database, meta, file, defaults));

        // file is set to null inside the EventDispatcherThread
        SwingUtilities.invokeLater(() -> frame.addTab(basePanel, raisePanel));

        frame.output(Localization.lang("Opened database") + " '" + fileName + "' " + Localization.lang("with") + " "
                + database.getEntryCount() + " " + Localization.lang("entries") + ".");

        return basePanel;
    }

    /**
     * Opens a new database.
     */
    public static ParserResult loadDatabase(File fileToOpen, Charset defaultEncoding) throws IOException {
        // Open and parse file
        ParserResult result = new BibtexImporter().importDatabase(fileToOpen.toPath(), defaultEncoding);

        if (SpecialFieldsUtils.keywordSyncEnabled()) {
            NamedCompound compound = new NamedCompound("SpecialFieldSync");
            for (BibEntry entry : result.getDatabase().getEntries()) {
                SpecialFieldsUtils.syncSpecialFieldsFromKeywords(entry, compound);
            }
            LOGGER.debug("Synchronized special fields based on keywords");
        }

        return result;
    }

    /**
     * Load database (bib-file) or, if there exists, a newer autosave version, unless the flag is set to ignore the autosave
     *
     * @param name Name of the bib-file to open
     * @param ignoreAutosave true if autosave version of the file should be ignored
     * @return ParserResult which never is null
     */

    public static ParserResult loadDatabaseOrAutoSave(String name, boolean ignoreAutosave) {
        // String in OpenDatabaseAction.java
        LOGGER.info("Opening: " + name);
        File file = new File(name);
        if (!file.exists()) {
            ParserResult pr = new ParserResult(null, null, null);
            pr.setFile(file);
            pr.setInvalid(true);
            LOGGER.error(Localization.lang("Error") + ": " + Localization.lang("File not found"));
            return pr;

        }
        try {

            if (!ignoreAutosave) {
                boolean autoSaveFound = AutoSaveManager.newerAutoSaveExists(file);
                if (autoSaveFound) {
                    // We have found a newer autosave. Make a note of this, so it can be
                    // handled after startup:
                    ParserResult postp = new ParserResult(null, null, null);
                    postp.setPostponedAutosaveFound(true);
                    postp.setFile(file);
                    return postp;
                }
            }

            if (!FileBasedLock.waitForFileLock(file.toPath(), 10)) {
                LOGGER.error(Localization.lang("Error opening file") + " '" + name + "'. "
                        + "File is locked by another JabRef instance.");
                return ParserResult.getNullResult();
            }

            Charset encoding = Globals.prefs.getDefaultEncoding();
            ParserResult pr = OpenDatabaseAction.loadDatabase(file, encoding);
            pr.setFile(file);
            if (pr.hasWarnings()) {
                for (String aWarn : pr.warnings()) {
                    LOGGER.warn(aWarn);
                }
            }
            return pr;
        } catch (Throwable ex) {
            ParserResult pr = new ParserResult(null, null, null);
            pr.setFile(file);
            pr.setInvalid(true);
            pr.setErrorMessage(ex.getMessage());
            LOGGER.info("Problem opening .bib-file", ex);
            return pr;
        }

    }
}