package org.docear.plugin.bibtex.jabref;
import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.swing.JOptionPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.ws.rs.core.MultivaluedMap;
import net.sf.jabref.BasePanel;
import net.sf.jabref.BibtexEntry;
import net.sf.jabref.BibtexEntryType;
import net.sf.jabref.EntryTypeDialog;
import net.sf.jabref.FocusRequester;
import net.sf.jabref.Globals;
import net.sf.jabref.JabRefFrame;
import net.sf.jabref.KeyCollisionException;
import net.sf.jabref.Util;
import net.sf.jabref.export.DocearReferenceUpdateController;
import net.sf.jabref.external.DroppedFileHandler;
import net.sf.jabref.gui.MainTable;
import net.sf.jabref.imports.ImportMenuItem;
import net.sf.jabref.labelPattern.LabelPatternUtil;
import net.sf.jabref.undo.UndoableInsertEntry;
import net.sf.jabref.util.XMPUtil;
import org.docear.plugin.bibtex.Reference;
import org.docear.plugin.bibtex.ReferenceUpdater;
import org.docear.plugin.bibtex.ReferencesController;
import org.docear.plugin.bibtex.dialogs.PdfMetadataListDialog;
import org.docear.plugin.bibtex.dialogs.PdfTitleQuestionDialog;
import org.docear.plugin.core.mindmap.MindmapUpdateController;
import org.docear.plugin.pdfutilities.map.AnnotationController;
import org.docear.plugin.services.communications.CommunicationsController;
import org.docear.plugin.services.communications.features.DocearServiceResponse;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.TextUtils;
import org.freeplane.features.map.MapModel;
import org.freeplane.features.mode.Controller;
import spl.Tools;
import spl.gui.ImportDialog;
import com.sun.jersey.core.util.StringKeyStringValueIgnoreCaseMultivaluedMap;
public abstract class JabRefCommons {
static class MetadataRequestTask implements Callable<MetadataCallableResult> {
private MetadataCallableResult result;
private Runnable task;
private MetadataRequestTask(Runnable task, MetadataCallableResult result) {
this.task = task;
this.result = result;
}
public static Callable<MetadataCallableResult> create(Runnable task, MetadataCallableResult result) {
return new MetadataRequestTask(task, result);
}
public MetadataCallableResult call() throws Exception {
if (task == null) {
return this.result;
}
task.run();
return this.result;
}
}
public static class MetadataCallableResult {
private String result;
private String errorText;
public static MetadataCallableResult newInstance() {
return new MetadataCallableResult();
}
public void setResult(String string) {
if (string == null || string.trim().length() <= 0) {
this.result = null;
} else {
this.result = string;
}
}
public String getResult() {
return this.result;
}
public void setError(String text) {
this.errorText = text;
}
public boolean hasError() {
return this.errorText != null;
}
public String getError() {
return errorText;
}
public String toString() {
return getResult();
}
}
private static void updateEntryInDatabase(File file, BibtexEntry selected, BibtexEntry oldEntry) {
if (selected == null) {
return;
}
BibtexEntryType type = selected.getType();
if (type != null) {
oldEntry.setType(type);
}
addMissingFields(oldEntry, selected);
// insertFields(oldEntry.getRequiredFields(), oldEntry, selected);
// insertFields(oldEntry.getGeneralFields(), oldEntry, selected);
// insertFields(oldEntry.getOptionalFields(), oldEntry, selected);
JabrefWrapper wrapper = ReferencesController.getController().getJabrefWrapper();
if(file != null) {
new JabRefAttributes().removePdfFromBibtexEntry(file, oldEntry);
DroppedFileHandler dfh = new DroppedFileHandler(wrapper.getJabrefFrame(), wrapper.getBasePanel());
// DOCEAR - change file path to relative to bib-library path?
dfh.linkPdfToEntry(file.getPath(), oldEntry);
}
else {
runCurrentMapUpdate();
}
showInReferenceManager(oldEntry);
}
private static void runCurrentMapUpdate() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
if (DocearReferenceUpdateController.isLocked()) {
return;
}
DocearReferenceUpdateController.lock();
MapModel currentMap = Controller.getCurrentController().getMap();
if (currentMap == null) {
return;
}
MindmapUpdateController mindmapUpdateController = new MindmapUpdateController(false);
mindmapUpdateController.addMindmapUpdater(new ReferenceUpdater(TextUtils.getText("update_references_current_mindmap")));
mindmapUpdateController.updateCurrentMindmap(true);
}
finally {
DocearReferenceUpdateController.unlock();
}
}
});
}
private static void addMissingFields(BibtexEntry oldEntry, BibtexEntry newData) {
DocearReferenceUpdateController.lock();
Collection<String> fields = newData.getAllFields();
for (String field : fields) {
if (oldEntry.getField(field) == null) {
oldEntry.setField(field, newData.getField(field));
}
}
DocearReferenceUpdateController.unlock();
}
public static List<String> addOrUpdateRefenceEntry(String[] fileNames, int dropRow, JabRefFrame jabRefFrame, BasePanel basePanel, MainTable entryTable, boolean chooseFirst) {
List<String> unhandledFileNames = new ArrayList<String>();
if (fileNames != null && fileNames.length > 0) {
for (String fileName : fileNames) {
// create document hash and try to extract the title for
// each file that is of type pdf
if (fileName.toLowerCase().endsWith(".pdf")) {
URI fileUri = new File(fileName).toURI();
ImportDialog importDialog = new ImportDialog(dropRow, fileName, (chooseFirst ? (dropRow < 0) : null));
Tools.centerRelativeToWindow(importDialog, UITools.getFrame());
String hash = AnnotationController.getDocumentHash(fileUri);
if(hash == null) {
importDialog.getRadioButtonMrDlib().setEnabled(false);
importDialog.getRadioButtonUpdateEmptyFields().setEnabled(false);
importDialog.getRadioButtonMrDlib().setSelected(false);
importDialog.getRadioButtonNoMeta().setSelected(true);
}
List<BibtexEntry> xmpEntriesInFile = readXmpEntries(fileName);
if ((xmpEntriesInFile == null) || (xmpEntriesInFile.size() == 0)) {
importDialog.getRadioButtonXmp().setEnabled(false);
}
if(chooseFirst) {
importDialog.showDialog();
}
else if (dropRow == -1 && hash != null) { // dropped on a new area
// create new entry (with metadata? empty entry?
try {
showMetadataDialog(fileUri);
} catch (Exception e) {
LogUtils.warn("Exception in org.docear.plugin.bibtex.jabref.JabrefChangeEventListener.processEvent(0): " + e.getMessage());
}
continue;
}
// dropped on an existing entry
if(!chooseFirst) {
importDialog.showDialog();
}
if (importDialog.getResult() == JOptionPane.OK_OPTION) {
// xmp metadata was selected
if (importDialog.getRadioButtonXmp().isSelected()) {
ImportMenuItem importer = new ImportMenuItem(jabRefFrame, false);
importer.automatedImport(new String[] { fileName });
}
// docear services was selected
else if (importDialog.getRadioButtonMrDlib().isSelected()) {
try {
showMetadataDialog(fileUri);
} catch (Exception e) {
LogUtils.warn("Exception in org.docear.plugin.bibtex.jabref.JabrefChangeEventListener.processEvent(1): "
+ e.getMessage());
}
} else {
if (importDialog.getRadioButtonNoMeta().isSelected()) {
BibtexEntry newEntry = JabRefCommons.createNewEntry(jabRefFrame, basePanel);
if (newEntry != null) {
DroppedFileHandler dfh = new DroppedFileHandler(jabRefFrame, basePanel);
dfh.linkPdfToEntry(fileName, newEntry);
}
}
// update was selected
else if (importDialog.getRadioButtonUpdateEmptyFields().isSelected()) {
try {
showMetadataUpdateDialog(fileUri, entryTable.getEntryAt(dropRow));
} catch (Exception e) {
LogUtils.warn("Exception in org.docear.plugin.bibtex.jabref.JabrefChangeEventListener.processEvent(2): "
+ e.getMessage());
}
}
// attach file only was selected
else if (importDialog.getRadioButtononlyAttachPDF().isSelected()) {
DroppedFileHandler dfh = new DroppedFileHandler(jabRefFrame, basePanel);
dfh.linkPdfToEntry(fileName, entryTable.getEntryAt(dropRow));
}
}
}
} else {
// add filename to fallback list
unhandledFileNames.add(fileName);
}
}
}
return unhandledFileNames;
}
private static List<BibtexEntry> readXmpEntries(String fileName) {
List<BibtexEntry> xmpEntriesInFile = null;
try {
xmpEntriesInFile = XMPUtil.readXMP(fileName);
} catch (Exception e) {
LogUtils.info("Exception in org.docear.plugin.bibtex.jabref.JabrefChangeEventListener.readXmpEntries(): " + e.getMessage());
}
return xmpEntriesInFile;
}
public static void showMetadataDialog(URI uri) throws InterruptedException, ExecutionException, IOException {
String userName = CommunicationsController.getController().getUserName();
if (userName == null) {
return;
}
final String hash = AnnotationController.getDocumentHash(uri);
if (hash == null) {
return;
}
// ask for title dialog
String title = searchForTitle(AnnotationController.getDocumentTitle(uri), uri);
if (title == null) {
return;
}
File file = new File(uri);
final MultivaluedMap<String, String> params = new StringKeyStringValueIgnoreCaseMultivaluedMap();
params.add("username", userName);
if (title != null) {
params.add("title", title);
}
PdfMetadataListDialog metadata = new PdfMetadataListDialog();
metadata.runServiceRequest(hash, params);
int response = JOptionPane.showConfirmDialog(UITools.getFrame(), metadata, TextUtils.getText("docear.metadata.import.title"),
JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
if(metadata.wasSuccessful()) {
if (response == JOptionPane.OK_OPTION) {
Util.setAutomaticFields(metadata.getEntries(), true, true, false);
BibtexEntry selected = metadata.getSelectedEntry();
selected.setField("dcr_pdf_hash", hash);
addOrUpdateEntryToDatabase(file, selected);
if (metadata.hasRemoteBib()) {
commit(selected.getField("dcr_bibtex_id"), hash, userName);
}
} else {
if (metadata.hasRemoteBib()) {
rejectAll(hash, userName);
}
}
}
}
public static void showMetadataUpdateDialog(URI uri, BibtexEntry oldEntry) throws InterruptedException, ExecutionException, IOException {
String userName = CommunicationsController.getController().getUserName();
if (userName == null) {
return;
}
final String hash = AnnotationController.getDocumentHash(uri);
if (hash == null) {
return;
}
// ask for title dialog
String title = searchForTitle(AnnotationController.getDocumentTitle(uri), uri);
if (title == null) {
return;
}
File file = new File(uri);
final MultivaluedMap<String, String> params = new StringKeyStringValueIgnoreCaseMultivaluedMap();
params.add("username", userName);
if (title != null) {
params.add("title", title);
}
PdfMetadataListDialog metadata = new PdfMetadataListDialog();
metadata.runServiceRequest(hash, params);
int response = JOptionPane.showConfirmDialog(UITools.getFrame(), metadata, TextUtils.getText("docear.metadata.import.title"),
JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
if(metadata.wasSuccessful()) {
if (response == JOptionPane.OK_OPTION) {
BibtexEntry selected = metadata.getSelectedEntry();
selected.setField("dcr_pdf_hash", hash);
updateEntryInDatabase(file, selected, oldEntry);
if (metadata.hasRemoteBib()) {
commit(selected.getField("dcr_bibtex_id"), hash, userName);
}
} else {
if (metadata.hasRemoteBib()) {
rejectAll(hash, userName);
}
}
}
}
public static String searchForTitle(String title, URI fileUri) {
PdfTitleQuestionDialog titleDialog = new PdfTitleQuestionDialog(title == null ? "" : title, fileUri);
int searchGo = JOptionPane.showConfirmDialog(UITools.getFrame(), titleDialog, TextUtils.getText("docear.metadata.title.title"),
JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE);
if (searchGo == JOptionPane.YES_OPTION) {
return titleDialog.getTitle();
}
return null;
}
private static void commit(final String bibtexID, final String hash, final String userName) {
final MetadataCallableResult result = MetadataCallableResult.newInstance();
Runnable task = new Runnable() {
public void run() {
try {
MultivaluedMap<String, String> params = new StringKeyStringValueIgnoreCaseMultivaluedMap();
params.add("username", userName);
params.add("commit", "true");
params.add("id", bibtexID);
DocearServiceResponse serviceResponse = CommunicationsController.getController().put("/internal/documents/" + hash + "/metadata", params);
if (serviceResponse.getStatus() != DocearServiceResponse.Status.OK) {
LogUtils.info("org.docear.plugin.bibtex.actions.ImportMetadateForNodeLink.commit().TASK: " + serviceResponse.getContentAsString());
}
} catch (Throwable e) {
// JOptionPane.showMessageDialog(UITools.getFrame(),
// e.getLocalizedMessage(),
// TextUtils.getText("docear.metadata.import.error"),
// JOptionPane.ERROR_MESSAGE);
result.setError(e.getLocalizedMessage());
}
}
};
try {
executeTask(MetadataRequestTask.create(task, result));
} catch (Exception e) {
LogUtils.info("org.docear.plugin.bibtex.actions.ImportMetadateForNodeLink.commit(): " + e.getLocalizedMessage());
LogUtils.warn(e);
}
}
private static void rejectAll(final String hash, final String userName) {
final MetadataCallableResult result = MetadataCallableResult.newInstance();
Runnable task = new Runnable() {
public void run() {
try {
MultivaluedMap<String, String> params = new StringKeyStringValueIgnoreCaseMultivaluedMap();
params.add("username", userName);
params.add("commit", "false");
DocearServiceResponse serviceResponse = CommunicationsController.getController().put("/internal/documents/" + hash + "/metadata", params);
if (serviceResponse.getStatus() != DocearServiceResponse.Status.OK) {
LogUtils.info("org.docear.plugin.bibtex.actions.ImportMetadateForNodeLink.rejectAll().TASK: " + serviceResponse.getContentAsString());
}
} catch (Throwable e) {
// JOptionPane.showMessageDialog(UITools.getFrame(),
// e.getLocalizedMessage(),
// TextUtils.getText("docear.metadata.import.error"),
// JOptionPane.ERROR_MESSAGE);
result.setError(e.getLocalizedMessage());
}
}
};
try {
executeTask(MetadataRequestTask.create(task, result));
} catch (Exception e) {
LogUtils.info("org.docear.plugin.bibtex.actions.ImportMetadateForNodeLink.rejectAll(): " + e.getLocalizedMessage());
LogUtils.warn(e);
}
}
public static MetadataCallableResult requestBibTeX(final String hash, final MultivaluedMap<String, String> params) throws InterruptedException, ExecutionException, IOException {
final MetadataCallableResult result = MetadataCallableResult.newInstance();
Runnable task = new Runnable() {
public void run() {
try {
StringBuilder sb = new StringBuilder();
DocearServiceResponse serviceResponse = CommunicationsController.getController().get("/internal/documents/" + hash + "/metadata", params);
if (serviceResponse.getStatus() == DocearServiceResponse.Status.FAILURE) {
// JOptionPane.showMessageDialog(UITools.getFrame(),
// serviceResponse.getContentAsString(),
// TextUtils.getText("docear.metadata.import.error"),
// JOptionPane.ERROR_MESSAGE);
result.setError(serviceResponse.getContentAsString());
return;
}
if (serviceResponse.getStatus() == DocearServiceResponse.Status.NO_CONTENT) {
// JOptionPane.showMessageDialog(UITools.getFrame(),
// TextUtils.getText("docear.metadata.import.infotext"),
// TextUtils.getText("docear.metadata.import.info"),
// JOptionPane.INFORMATION_MESSAGE);
result.setError(TextUtils.getText("docear.metadata.import.infotext"));
return;
}
InputStream is = serviceResponse.getContent();// this.getClass().getResourceAsStream("/bibtex-test.bib");
Reader reader = new InputStreamReader(is);
int c = -1;
while ((c = reader.read()) > -1) {
sb.append((char) c);
}
is.close();
result.setResult(sb.toString());
} catch (Throwable e) {
JOptionPane.showMessageDialog(UITools.getFrame(), e.getLocalizedMessage(), TextUtils.getText("docear.metadata.import.error"),
JOptionPane.ERROR_MESSAGE);
}
}
};
executeTask(MetadataRequestTask.create(task, result));
return result;
}
private static MetadataCallableResult executeTask(Callable<MetadataCallableResult> task) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
MetadataCallableResult taskResult = null;
try {
Future<MetadataCallableResult> future = executor.submit(task);
taskResult = future.get(5, TimeUnit.SECONDS);
future.cancel(true);
} catch (TimeoutException tex) {
}
executor.shutdown();
return taskResult;
}
private static void addOrUpdateEntryToDatabase(File file, BibtexEntry selected) {
if (selected == null) {
return;
}
JabrefWrapper wrapper = ReferencesController.getController().getJabrefWrapper();
BibtexEntry oldEntry = null;
if(file != null) {
for(BibtexEntry entry : wrapper.getDatabase().getEntries()) {
Reference ref = new Reference(entry);
if(ref.containsFile(file)) {
oldEntry = entry;
break;
}
}
}
if(oldEntry == null) {
selected.setId(Util.createNeutralId());
wrapper.getBasePanel().getDatabase().insertEntry(selected);
showInReferenceManager(selected);
DroppedFileHandler dfh = new DroppedFileHandler(wrapper.getJabrefFrame(), wrapper.getBasePanel());
if(file != null) {
// DOCEAR - change file path to relative to bib-library path?
dfh.linkPdfToEntry(file.getPath(), selected);
LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), wrapper.getDatabase(), selected);
}
}
else {
JabRefCommons.updateEntryInDatabase(null, selected, oldEntry);
showInReferenceManager(oldEntry);
}
}
public static void showInReferenceManager(String bibtexKey) {
if (bibtexKey != null && bibtexKey.length()>0) {
BibtexEntry referenceEntry = ReferencesController.getController().getJabrefWrapper().getDatabase().getEntryByKey(bibtexKey);
showInReferenceManager(referenceEntry);
}
}
public static void showInReferenceManager(BibtexEntry referenceEntry) {
if(referenceEntry == null) {
return;
}
MainTable table = ReferencesController.getController().getJabrefWrapper().getBasePanel().getMainTable();
List<BibtexEntry> list = table.getTableRows();
int viewHeight = table.getPane().getHeight()-table.getTableHeader().getHeight();
Rectangle viewRect = new Rectangle(0,((JViewport)table.getParent()).getViewPosition().y, 4, viewHeight);
int pos = 0;
Rectangle rowArea = new Rectangle();
for(BibtexEntry row : list) {
if(row.equals(referenceEntry)) {
rowArea.setBounds(0, (table.getRowHeight()*pos), 2, table.getRowHeight());
table.clearSelection();
table.addRowSelectionInterval(pos,pos);
if(isRowOutsideViewArea(viewRect, rowArea)) {
((JViewport)table.getParent()).setViewPosition(rowArea.getLocation());
}
break;
}
pos++;
}
}
private static boolean isRowOutsideViewArea(final Rectangle viewArea, final Rectangle row) {
if(viewArea.contains(row)) {
return false;
}
return true;
}
public static void addNewRefenceEntry(String[] fileNames, JabRefFrame jabRefFrame, BasePanel basePanel) {
addOrUpdateRefenceEntry(fileNames, -1, jabRefFrame, basePanel, null, true);
}
public static BibtexEntry createNewEntry(JabRefFrame frame, BasePanel panel) {
// Find out what type is wanted.
EntryTypeDialog etd = new EntryTypeDialog(frame);
// We want to center the dialog, to make it look nicer.
Util.placeDialog(etd, UITools.getFrame());
etd.setVisible(true);
BibtexEntryType type = etd.getChoice();
if (type != null) { // Only if the dialog was not cancelled.
String id = Util.createNeutralId();
final BibtexEntry be = new BibtexEntry(id, type);
try {
panel.database().insertEntry(be);
// Set owner/timestamp if options are enabled:
ArrayList<BibtexEntry> list = new ArrayList<BibtexEntry>();
list.add(be);
Util.setAutomaticFields(list, true, true, false);
// Create an UndoableInsertEntry object.
panel.undoManager.addEdit(new UndoableInsertEntry(panel.database(), be, panel));
panel.output(Globals.lang("Added new")+" '"+type.getName().toLowerCase()+"' "
+Globals.lang("entry")+".");
// We are going to select the new entry. Before that, make sure that we are in
// show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR
// mode which makes sure the selection will trigger display of the entry editor
// and adjustment of the splitter.
if (panel.getMode() != BasePanel.SHOWING_EDITOR) {
panel.setMode(BasePanel.WILL_SHOW_EDITOR);
}
panel.showEntry(be);
panel.markBaseChanged(); // The database just changed.
new FocusRequester(panel.getEntryEditor(be));
return be;
} catch (KeyCollisionException ex) {
LogUtils.warn("Exception in org.docear.plugin.bibtex.jabref.JabrefWrapper.createNewEntry(): "+ex.getMessage());
}
}
return null;
}
// private static void insertFields(String[] fields, BibtexEntry entry, BibtexEntry newData) {
// for (String field : fields) {
// if (entry.getField(field) == null) {
// entry.setField(field, newData.getField(field));
// }
// }
// }
}