package org.docear.plugin.bibtex.jabref; import java.awt.BorderLayout; import java.awt.Component; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Scanner; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; import net.sf.jabref.BasePanel; import net.sf.jabref.BibtexDatabase; import net.sf.jabref.BibtexFields; import net.sf.jabref.Globals; import net.sf.jabref.JabRef; import net.sf.jabref.JabRefFrame; import net.sf.jabref.Util; import net.sf.jabref.export.SaveSession; import net.sf.jabref.external.FileLinksUpgradeWarning; import net.sf.jabref.imports.CheckForNewEntryTypesAction; import net.sf.jabref.imports.OpenDatabaseAction; import net.sf.jabref.imports.ParserResult; import net.sf.jabref.imports.PostOpenAction; import org.docear.plugin.bibtex.ReferencesController; import org.docear.plugin.bibtex.actions.DocearHandleDuplicateWarning; import org.docear.plugin.bibtex.actions.DocearTransformZoteroPathsAction; import org.docear.plugin.bibtex.actions.FilePathValidatorAction; import org.docear.plugin.bibtex.actions.HandleDuplicateKeys; import org.docear.plugin.bibtex.listeners.MapViewListener; import org.docear.plugin.core.DocearController; import org.docear.plugin.core.logger.DocearLogEvent; import org.docear.plugin.core.logging.DocearLogger; import org.docear.plugin.core.util.WinRegistry; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.ui.components.UITools; import org.freeplane.core.util.Compat; import org.freeplane.core.util.LogUtils; import org.freeplane.core.util.TextUtils; import org.freeplane.features.mode.Controller; import org.freeplane.features.ui.IMapViewChangeListener; import org.swingplus.JHyperlink; public class JabrefWrapper extends JabRef implements IMapViewChangeListener { private static final int MAX_TRY_OPEN = 5; private static ArrayList<PostOpenAction> postOpenActions = new ArrayList<PostOpenAction>(); static { postOpenActions.add(new DocearTransformZoteroPathsAction()); // bibtex files exported by mendeley do not contain leading "/" for // absolute paths so we do not know if // the file contaions relative paths or absolute paths postOpenActions.add(new FilePathValidatorAction()); // Add the action for checking for new custom entry types loaded from // the bib file: postOpenActions.add(new CheckForNewEntryTypesAction()); // Add the action for the new external file handling system in version // 2.3: postOpenActions.add(new FileLinksUpgradeWarning()); // Add the action for warning about and handling duplicate BibTeX keys: //postOpenActions.add(new HandleDuplicateWarnings()); //DOCEAR: don't warn, just resolve --> #464 if (ResourceController.getResourceController().getBooleanProperty("docear.reference_manager.resolve_duplicate_keys")) { postOpenActions.add(new HandleDuplicateKeys()); } else { postOpenActions.add(new DocearHandleDuplicateWarning()); } } private static final MapViewListener mapViewListener = new MapViewListener(); private ParserResult parserResult = null; private String encoding = null; private File file; private HashMap<String, String> meta = null; public JabrefWrapper(JFrame frame) { this(frame, null); } /** * @param jFrame * @param file */ public JabrefWrapper(JFrame frame, File file) { // super(frame, new String[]{"true", "-i", "\""+file.toString()+"\""}); super(frame); registerListeners(); try { if(file != null ) { openIt(file, true); } } catch (Exception e) { LogUtils.warn("Exception in org.docear.plugin.bibtex.jabref.JabrefWrapper.constructor(): "+ e.getMessage()); } this.jrf.getPreferences().put("generateKeysBeforeSaving", "true"); this.jrf.getPreferences().put("avoidOverwritingKey", "true"); this.jrf.addJabRefEventListener(new JabrefChangeEventListener()); } public JabRefFrame getJabrefFrame() { return this.jrf; } public JPanel getJabrefFramePanel() { return this.jrf; } public String getLocalizedColumnName(String s) { String disName = BibtexFields.getFieldDisplayName(s); if (disName != null) return disName; else return Util.nCase(s); } private void registerListeners() { Controller.getCurrentController().getMapViewManager().addMapViewChangeListener(this); SwingUtilities.invokeLater(new Runnable() { public void run() { synchronized (Controller.getCurrentModeController().getMapController()) { Controller.getCurrentModeController().getMapController().addNodeSelectionListener(mapViewListener); } } }); } public BasePanel getBasePanel() { return (BasePanel) getJabrefFrame().getTabbedPane().getSelectedComponent(); } public BibtexDatabase getDatabase() { if (getBasePanel() == null) { return null; } return getBasePanel().getDatabase(); } public BasePanel addNewDatabase(ParserResult pr, File file, boolean raisePanel) { this.file = file; String fileName = file.getPath(); BibtexDatabase database = pr.getDatabase(); database.addDatabaseChangeListener(ReferencesController.getJabRefChangeListener()); this.setMeta(pr.getMetaData()); this.setEncoding(pr.getEncoding()); BasePanel bp = new BasePanel(getJabrefFrame(), database, file, meta, pr.getEncoding()); // file is set to null inside the EventDispatcherThread // SwingUtilities.invokeLater(new OpenItSwingHelper(bp, file, // raisePanel)); getJabrefFrame().addTab(bp, file, raisePanel); LogUtils.info(Globals.lang("Opened database") + " '" + fileName + "' " + Globals.lang("with") + " " + database.getEntryCount() + " " + Globals.lang("entries") + "."); return bp; } public void replaceDatabase(File file, boolean raisePanel) { // getJabrefFrame().getTabbedPane().removeAll(); // if(getBasePanel() != null) { // getBasePanel().runCommand("save"); // } while (getJabrefFrame().getTabbedPane().getTabCount() > 0) { getJabrefFrame().closeCurrentTab(); } openIt(file, raisePanel); updateWindowsRegistry(file); DocearController.getController().getDocearEventLogger().appendToLog(this, DocearLogEvent.RM_BIBTEX_FILE_CHANGE, new Object[] {file, this.getDatabase().getEntries().size()}); } private void updateWindowsRegistry(File file) { if(Compat.isWindowsOS()) { try { WinRegistry.createKey(WinRegistry.HKEY_CURRENT_USER, "SOFTWARE\\Docear4Word"); WinRegistry.writeStringValue(WinRegistry.HKEY_CURRENT_USER, "SOFTWARE\\Docear4Word", "BibTexDatabase", file.getAbsolutePath()); WinRegistry.writeStringValue(WinRegistry.HKEY_CURRENT_USER, "ENVIRONMENT", "docear_bibtex_current", file.getAbsolutePath()); } catch (Exception e) { DocearLogger.warn("org.docear.plugin.bibtex.jabref.JabrefWrapper.updateWindowsRegistry(): "+e.getMessage()); } } } public void openIt(File file, boolean raisePanel) { if ((file != null) && (file.exists())) { if (!isCompatibleToJabref(file)) { JHyperlink hyperlink = new JHyperlink("http://www.docear.org/support/user-manual/#docear_and_mendeley ", "http://www.docear.org/support/user-manual/#docear_and_mendeley"); JPanel panel = new JPanel(new BorderLayout()); panel.add(new JLabel(TextUtils.getText("jabref_mendeley_incompatible_1")), BorderLayout.NORTH); panel.add(hyperlink, BorderLayout.CENTER); panel.add(new JLabel(TextUtils.getText("jabref_mendeley_incompatible_2")), BorderLayout.SOUTH); int option = JOptionPane.showConfirmDialog(UITools.getFrame(), panel, TextUtils.getText("jabref_mendeley_incompatible_title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (option == JOptionPane.YES_OPTION) { return; } } File fileToLoad = file; LogUtils.info(Globals.lang("Opening References") + ": '" + file.getPath() + "'"); int tryCounter = 0; boolean done = false; while (!done && tryCounter++ < MAX_TRY_OPEN) { String fileName = file.getPath(); Globals.prefs.put("workingDirectory", file.getPath()); // Should this be done _after_ we know it was successfully // opened? ResourceController resourceController = ResourceController.getResourceController(); String encoding = resourceController.getProperty("docear_bibtex_encoding", Globals.prefs.get("defaultEncoding")); if (Util.hasLockFile(file)) { long modTime = Util.getLockFileTimeStamp(file); if ((modTime != -1) && (System.currentTimeMillis() - modTime > SaveSession.LOCKFILE_CRITICAL_AGE)) { // The lock file is fairly old, so we can offer to // "steal" the file: int answer = JOptionPane.showConfirmDialog( null, "<html>" + Globals.lang("Error opening file") + " '" + fileName + "'. " + Globals.lang("File is locked by another JabRef instance.") + "<p>" + Globals.lang("Do you want to override the file lock?"), Globals.lang("File locked"), JOptionPane.YES_NO_OPTION); if (answer == JOptionPane.YES_OPTION) { Util.deleteLockFile(file); } else return; } else if (!Util.waitForFileLock(file, 10)) { JOptionPane.showMessageDialog(null, Globals.lang("Error opening file") + " '" + fileName + "'. " + Globals.lang("File is locked by another JabRef instance."), Globals.lang("Error"), JOptionPane.ERROR_MESSAGE); return; } } ParserResult pr; try { String source = resourceController.getProperty("docear_bibtex_source", "Jabref"); pr = OpenDatabaseAction.loadDataBase(fileToLoad, encoding, source); } catch (Exception ex) { //__DOCEAR_ ex.printStackTrace(); pr = null; } if ((pr == null) || (pr == ParserResult.INVALID_FORMAT)) { LogUtils.warn("ERROR: Could not load file" + file); continue; } else { done = true; final BasePanel panel = addNewDatabase(pr, file, raisePanel); 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 prf = pr; SwingUtilities.invokeLater(new Runnable() { public void run() { performPostOpenActions(panel, prf, true); } }); } } } DocearController.getController().getDocearEventLogger().appendToLog(this, DocearLogEvent.RM_BIBTEX_FILE_OPEN, new Object[] {file, this.getDatabase().getEntries().size()}); } // JabRef does not use character escaping of "{" and "}" // unfortunately all other escapings are not unambiguously or might be set // in jabref-preferences too public boolean isCompatibleToJabref(File f) { int escapeCount = 0; int allCount = 0; ArrayList<Character> allowedCharsBeforeSlash = new ArrayList<Character>(); allowedCharsBeforeSlash.add('\"'); allowedCharsBeforeSlash.add('\''); allowedCharsBeforeSlash.add('`'); allowedCharsBeforeSlash.add('^'); allowedCharsBeforeSlash.add('~'); Scanner in = null; try { in = new Scanner(new FileReader(f)); while (in.hasNextLine()) { String line = in.nextLine(); String normalized = line.trim().toLowerCase(); if (Compat.isWindowsOS() && normalized.startsWith("file")) { if (normalized.contains("backslash$:")) { return false; } } if (normalized.startsWith("journal") || normalized.startsWith("title") || normalized.startsWith("booktitle")) { int pos = 0; int i = 0; String s = normalized.substring(normalized.indexOf("=") + 1).trim(); while (s.charAt(pos) == '{') { pos++; } while ((i = s.indexOf("{", pos)) >= 0) { pos = (i + 1); if (allowedCharsBeforeSlash.contains(s.charAt(i - 1))) { continue; } allCount++; } pos = 0; i = 0; while ((i = s.indexOf("\\{", pos)) >= 0) { escapeCount++; pos = (i + 1); } } } } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (Exception e) { LogUtils.warn(e); } } // if no escaped and no unescaped char sequence was found in the whole // file we assume it to be ok for usage in jabref if (allCount / 2 >= escapeCount) { return true; } return false; } /** * 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 pr * The result of the bib file parse operation. */ public static void performPostOpenActions(BasePanel panel, ParserResult pr, boolean mustRaisePanel) { for (Iterator<PostOpenAction> iterator = postOpenActions.iterator(); iterator.hasNext();) { PostOpenAction action = iterator.next(); if (action.isActionNecessary(pr)) { if (mustRaisePanel) panel.frame().getTabbedPane().setSelectedComponent(panel); action.performAction(panel, pr); } } } public File getFile() { return file; } public void setFile(File file) { this.file = file; } public ParserResult getParserResult() { return parserResult; } public void setParserResult(ParserResult parserResult) { this.parserResult = parserResult; } public HashMap<String, String> getMeta() { return meta; } public void setMeta(HashMap<String, String> meta) { this.meta = meta; } public String getEncoding() { return encoding; } public void setEncoding(String encoding) { this.encoding = encoding; } public void afterViewChange(Component oldView, Component newView) { } public void afterViewClose(final Component oldView) { oldView.removeMouseListener(mapViewListener); } public void afterViewCreated(final Component mapView) { mapView.addMouseListener(mapViewListener); } public void beforeViewChange(Component oldView, Component newView) { } }