package org.docear.plugin.bibtex.jabref; import java.awt.Color; import java.awt.Component; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import net.sf.jabref.BasePanel; import net.sf.jabref.BibtexDatabase; import net.sf.jabref.BibtexEntry; 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.DocearReferenceUpdateController; import net.sf.jabref.export.SaveDatabaseAction; 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.FilePathValidatorAction; import org.docear.plugin.bibtex.actions.HandleDuplicateKeys; import org.docear.plugin.bibtex.actions.IPreOpenAction; import org.docear.plugin.bibtex.listeners.MapViewListener; import org.docear.plugin.core.DocearController; import org.docear.plugin.core.event.DocearEvent; import org.docear.plugin.core.event.DocearEventType; 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.OneTouchCollapseResizer; import org.freeplane.core.util.Compat; import org.freeplane.core.util.LogUtils; import org.freeplane.features.mode.Controller; import org.freeplane.features.ui.IMapViewChangeListener; public class JabrefWrapper extends JabRef implements IMapViewChangeListener { private static final int MAX_TRY_OPEN = 5; private static ArrayList<IPreOpenAction> preOpenActions = new ArrayList<IPreOpenAction>(); private static ArrayList<PostOpenAction> postOpenActions = new ArrayList<PostOpenAction>(); private static ArrayList<PostOpenAction> postParseActions = new ArrayList<PostOpenAction>(); static { preOpenActions.add(new ZoteroAnnoteFieldRemoverAction()); //escape colons and semicolons (not done by Zotero postParseActions.add(new DocearTransformForeignDatabaseAction()); // bibtex files exported by mendeley do not contain leading "/" for // absolute paths so we do not know if // the file contains relative paths or absolute paths postParseActions.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 (DocearController.getPropertiesController().getBooleanProperty("docear.reference_manager.resolve_duplicate_keys")) { postOpenActions.add(new HandleDuplicateKeys()); } else { postOpenActions.add(new DocearHandleDuplicateWarning()); } } private static final MapViewListener mapViewListener = new MapViewListener(); private Map<File, JabRefBaseHandle> baseHandles = new HashMap<File, JabRefBaseHandle>(); private OneTouchCollapseResizer resizer; public JabrefWrapper(JFrame frame) { super(frame); registerListeners(); this.jrf.getPreferences().put("generateKeysBeforeSaving", "true"); this.jrf.getPreferences().put("avoidOverwritingKey", "true"); this.jrf.addJabRefEventListener(new JabrefChangeEventListener()); this.jrf.setBorder(new LineBorder(Color.LIGHT_GRAY, 1)); } 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() { getJabrefFrame().getTabbedPane().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { BasePanel bp = getJabrefFrame().basePanel(); if (bp != null) { updateWindowsRegistry(bp.getFile()); } } }); 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 base, boolean raisePanel) { File file = base.getAbsoluteFile(); String fileName = file.getPath(); BibtexDatabase database = pr.getDatabase(); database.addDatabaseChangeListener(ReferencesController.getJabRefChangeListener()); BasePanel bp; // dirty hack, nimbus is sometimes not loaded fast enough try { bp = new BasePanel(getJabrefFrame(), database, file, pr.getMetaData(), pr.getEncoding()); } catch (Exception e) { LogUtils.info("JabrefWrapper.addNewDatabase(): database could not be loaded, trying again in 500 ms"); try { Thread.sleep(800); } catch (InterruptedException e1) { } bp = new BasePanel(getJabrefFrame(), database, file, pr.getMetaData(), 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 JabRefBaseHandle openDatabase(File baseFile, boolean raisePanel) { long time = System.currentTimeMillis(); JabRefBaseHandle handle = null; if(baseFile == null) { throw new IllegalArgumentException("NULL"); } File file = baseFile.getAbsoluteFile(); //closeDatabase(file, true); if(isOpened(file)) { handle = getBaseHandle(file); if(handle != null && raisePanel) { getJabrefFrame().showBasePanel(handle.getBasePanel()); } } else { handle = openIt(file, raisePanel); if(handle != null) { synchronized (baseHandles ) { baseHandles.put(file, handle); } } } //DOCEAR - todo: how do we deal with multiple files? DocearController.getController().getDocearEventLogger().appendToLog(this, DocearLogEvent.RM_BIBTEX_FILE_CHANGE, new Object[] {file, this.getDatabase().getEntries().size()}); LogUtils.info("database "+baseFile+" loaded in: "+(System.currentTimeMillis()-time)); return handle; } public JabRefBaseHandle getBaseHandle(File baseFile) { if(baseFile == null) { throw new IllegalArgumentException("NULL"); } File file = baseFile.getAbsoluteFile(); synchronized (baseHandles) { if(baseHandles.containsKey(file)) { return baseHandles.get(file); } } return null; } public void closeDatabase(File baseFile) { File file = baseFile.getAbsoluteFile(); closeDatabase(file, false); } public void closeDatabase(JabRefBaseHandle baseHandle) { if(baseHandle == null) { return; } if(!baseHandle.hasMoreConnections()) { synchronized (baseHandles) { baseHandles.remove(baseHandle.getFile().getAbsoluteFile()); } closeDatabase(baseHandle.getFile()); } } private void closeDatabase(File file, boolean silentClose) { for(int i=0; i < getJabrefFrame().baseCount(); i++) { BasePanel panel = getJabrefFrame().baseAt(i); if(panel.getFile().equals(file)) { getJabrefFrame().showBaseAt(i); getJabrefFrame().closeCurrentTab(); if(!silentClose) { //firePanelRemoved(panel, i); } } } } public boolean isOpened(File baseFile) { if(baseFile == null) { throw new IllegalArgumentException("NULL"); } File file = baseFile.getAbsoluteFile(); for(int i=0; i < getJabrefFrame().baseCount(); i++) { BasePanel panel = getJabrefFrame().baseAt(i); if(panel.getFile().equals(file)) { return true; } } return false; } 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()); } } } private JabRefBaseHandle openIt(File file, boolean raisePanel) { JabRefBaseHandle handle = null; for (IPreOpenAction action : preOpenActions) { if (action.isActionNecessary(file)) { action.performAction(file); } } if ((file != null) && (file.exists())) { // if (!isCompatibleToJabref(file)) { // JHyperlink hyperlink = new JHyperlink("http://www.docear.org/faqs/how-to-use-mendeley-together-with-docear/", // "http://www.docear.org/faqs/how-to-use-mendeley-together-with-docear/"); // 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 handle; // } // } 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 = DocearController.getPropertiesController(); 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 handle; } 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 handle; } } ParserResult pr; try { String source = resourceController.getProperty("docear_bibtex_source", "Jabref"); pr = OpenDatabaseAction.loadDataBase(fileToLoad, encoding, source); for (PostOpenAction action : postParseActions) { if (action.isActionNecessary(pr)) { long time = System.currentTimeMillis(); action.performAction(null, pr); System.out.println(action.getClass().toString()+" time: "+(System.currentTimeMillis()-time)+"ms"); System.out.println(); } } } 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(); handle = new JabRefBaseHandle(panel, pr); // After adding the database, go through our list and see if // any post open actions needs 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}); return handle; } // 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('~'); // // RandomAccessFile raf = null; // try { // //in = new Scanner(new FileReader(f)); // raf = new RandomAccessFile(f, "r"); // boolean isWin = Compat.isWindowsOS(); // String line = null; // while ((line = raf.readLine()) != null) { // String normalized = line.trim().toLowerCase(); // if (isWin && 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(); // raf.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) { DocearReferenceUpdateController.lock(); try { 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); } } } finally { DocearReferenceUpdateController.unlock(); } } 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) { } public void shutdown() { for (JabRefBaseHandle handle : baseHandles.values()) { try { BibtexDatabase database = handle.getBasePanel().getDatabase(); if(database == null) { return; } for (BibtexEntry entry : database.getEntries()) { if (entry.getField("docear_add_to_node") != null) { entry.setField("docear_add_to_node", null); } } if(ReferencesController.getController().getJabrefWrapper().getBasePanel().isUpdatedExternally()){ DocearController.getController().addWorkingThreadHandle("ReferenceQuitAction"); SaveDatabaseAction saveAction = new SaveDatabaseAction(handle.getBasePanel()); saveAction.runCommand(); if (saveAction.isCancelled() || !saveAction.isSuccess()) { DocearController.getController().getEventQueue().dispatchEvent(new DocearEvent(this, null, DocearEventType.APPLICATION_CLOSING_ABORTED)); } DocearController.getController().removeWorkingThreadHandle("ReferenceQuitAction"); } else{ handle.getBasePanel().runCommand("save"); } } catch (Throwable t) { LogUtils.warn(t); } } getJabrefFrame().quit(); } public OneTouchCollapseResizer getResizer() { if(this.resizer == null) { this.resizer = OneTouchCollapseResizer.findResizerFor(getJabrefFrame()); } return this.resizer; } // public void addBaseHandleForFile(File baseFile, IJabrefChangeListener listener) { // if(listener == null || baseFile == null) { // return; // } // File file = baseFile.getAbsoluteFile(); // synchronized (baseHandles ) { // List<IJabrefChangeListener> list = baseHandles.get(file); // if(list == null) { // list = new ArrayList<IJabrefChangeListener>(); // baseHandles.put(file, list); // } // if(list.contains(listener)) { // return; // } // list.add(listener); // } // } // public void removeBaseHandleForFile(File baseFile, IJabrefChangeListener listener) { // if(listener == null || baseFile == null) { // return; // } // File file = baseFile.getAbsoluteFile(); // synchronized (baseHandles ) { // List<IJabrefChangeListener> list = baseHandles.get(file); // if(list == null) { // return; // } // list.remove(listener); // } // } }