package org.docear.plugin.bibtex.jabref;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import javax.swing.JOptionPane;
import javax.ws.rs.core.UriBuilder;
import net.sf.jabref.BibtexDatabase;
import net.sf.jabref.BibtexEntry;
import net.sf.jabref.GUIGlobals;
import net.sf.jabref.Globals;
import net.sf.jabref.gui.FileListEntry;
import net.sf.jabref.gui.FileListTableModel;
import net.sf.jabref.labelPattern.LabelPatternUtil;
import org.apache.commons.io.FilenameUtils;
import org.docear.plugin.bibtex.Reference;
import org.docear.plugin.bibtex.Reference.Item;
import org.docear.plugin.bibtex.ReferencesController;
import org.docear.plugin.bibtex.dialogs.DuplicateLinkDialogPanel;
import org.docear.plugin.core.CoreConfiguration;
import org.docear.plugin.core.features.DocearMapModelExtension;
import org.docear.plugin.core.features.MapModificationSession;
import org.docear.plugin.core.util.NodeUtilities;
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.attribute.Attribute;
import org.freeplane.features.attribute.AttributeController;
import org.freeplane.features.attribute.NodeAttributeTableModel;
import org.freeplane.features.link.LinkController;
import org.freeplane.features.link.LinkModel;
import org.freeplane.features.link.NodeLinkModel;
import org.freeplane.features.link.NodeLinks;
import org.freeplane.features.link.mindmapmode.MLinkController;
import org.freeplane.features.map.INodeView;
import org.freeplane.features.map.MapModel;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.mode.Controller;
import org.freeplane.features.mode.mindmapmode.MModeController;
import org.freeplane.plugin.workspace.WorkspaceUtils;
import org.freeplane.view.swing.map.NodeView;
public class JabRefAttributes {
private static Boolean updateNodeLock = false;
private boolean nodeDirty = false;
private HashMap<String, String> valueAttributes = new HashMap<String, String>();
private String keyAttribute;
public JabRefAttributes() {
registerAttributes();
}
public void registerAttributes() {
this.keyAttribute = TextUtils.getText("bibtex_key");
this.valueAttributes.put("authors", "author");
this.valueAttributes.put("title", "title");
this.valueAttributes.put("year", "year");
this.valueAttributes.put("journal", "journal");
}
public String getKeyAttribute() {
return keyAttribute;
}
public HashMap<String, String> getValueAttributes() {
return valueAttributes;
}
public String getBibtexKey(NodeModel node) {
return getAttributeValue(node, this.keyAttribute);
}
public String getAttributeValue(NodeModel node, String attributeName) {
NodeAttributeTableModel attributeTable = (NodeAttributeTableModel) node.getExtension(NodeAttributeTableModel.class);
if (attributeTable == null) {
return null;
}
for (Attribute attribute : attributeTable.getAttributes()) {
if (attribute.getName().equals(attributeName)) {
return attribute.getValue().toString();
}
}
return null;
}
public boolean isReferencing(BibtexEntry entry, NodeModel node) {
String nodeKey = getBibtexKey(node);
String entryKey = entry.getCiteKey();
if (nodeKey != null && entryKey != null && nodeKey.equals(entryKey)) {
return true;
}
return false;
}
public void setReferenceToNode(BibtexEntry entry) throws ResolveDuplicateEntryAbortedException {
NodeModel node = Controller.getCurrentModeController().getMapController().getSelectedNode();
setReferenceToNode(new Reference(entry), node);
}
public void removeReferenceFromNode(NodeModel node) {
for(INodeView nodeView : node.getViewers()) {
if(nodeView instanceof NodeView) {
NodeAttributeTableModel attributeTable = ((NodeView) nodeView).getAttributeView().getAttributes();
if (attributeTable == null) {
continue;
}
List<String> keys = attributeTable.getAttributeKeyList();
for (String attributeKey : keys) {
if (this.valueAttributes.containsKey(attributeKey) || this.keyAttribute.equals(attributeKey)) {
int pos = attributeTable.getAttributePosition(attributeKey);
if(pos > -1) {
try {
AttributeController.getController(MModeController.getMModeController()).performRemoveRow(attributeTable, pos);
}
catch (Exception e) {
//DOCEAR - ignore for now
//the whole attribute removing is completely messed up
//LogUtils.info("not found ("+attributeKey+"): "+pos);
//LogUtils.warn(e);
}
}
}
}
if(attributeTable.getRowCount() <= 0) {
((NodeView) nodeView).getAttributeView().viewRemoved();
}
}
}
// if(attributeTable.getRowCount() <= 0) {
// node.removeExtension(NodeAttributeTableModel.class);
// for(INodeView nodeView : node.getViewers()) {
// if(nodeView instanceof NodeView) {
// ((NodeView) nodeView).getAttributeView().viewRemoved();
// ((NodeView) nodeView).getContent().remove(3);
// ((NodeView) nodeView).update();
// }
// }
// }
}
// public void updateReferenceOnPdf(URI uri, NodeModel node) {
// BibtexEntry entry = findBibtexEntryForPDF(uri, node);
// if (entry != null) {
// setReferenceToNode(new Reference(entry, node), node);
// }
// }
@SuppressWarnings("unchecked")
public boolean updateReferenceToNode(Reference reference, NodeModel node) throws ResolveDuplicateEntryAbortedException {
if (updateNodeLock) {
return false;
}
synchronized (updateNodeLock) {
updateNodeLock = true;
}
boolean changes = false;
try {
MapModificationSession session = node.getMap().getExtension(DocearMapModelExtension.class).getMapModificationSession();
Set<String> ignoresPdf = null;
if (session != null) {
ignoresPdf = (Set<String>) session.getSessionObject(MapModificationSession.FILE_IGNORE_LIST);
if (ignoresPdf == null) {
ignoresPdf = new HashSet<String>();
session.putSessionObject(MapModificationSession.FILE_IGNORE_LIST, ignoresPdf);
}
}
Set<String> ignoresUrl = null;
if (session != null) {
ignoresUrl = (Set<String>) session.getSessionObject(MapModificationSession.URL_IGNORE_LIST);
if (ignoresUrl == null) {
ignoresUrl = new HashSet<String>();
session.putSessionObject(MapModificationSession.URL_IGNORE_LIST, ignoresUrl);
}
}
for (URI uri : reference.getUris()) {
File file = WorkspaceUtils.resolveURI(uri, node.getMap());
URL url = null;
if (file == null) {
try {
url = uri.toURL();
}
catch (MalformedURLException e) {
LogUtils.warn(e);
}
}
try {
if (ignoresPdf != null) {
if (file != null) {
if (ignoresPdf.contains(file.getName())) {
throw new ResolveDuplicateEntryAbortedException(file);
}
}
}
else if (ignoresUrl != null) {
if (url != null) {
if (ignoresUrl.contains(url.toExternalForm())) {
throw new ResolveDuplicateEntryAbortedException(url);
}
}
}
// if (file != null) {
// resolveDuplicateLinks(file);
// }
// else {
// resolveDuplicateLinks(url);
// }
// BibtexEntry entry = findBibtexEntryForPDF(uri, node.getMap());
// if (entry == null) {
// entry = findBibtexEntryForURL(uri, node.getMap(), false);
// }
//
// if (entry != null) {
// reference = new Reference(entry);
// }
}
catch (NullPointerException e) {
LogUtils.warn("org.docear.plugin.bibtex.jabrefe.JabRefAttributes.updateReferenceToNode: " + e.getMessage());
}
catch (ResolveDuplicateEntryAbortedException ex) {
if (ignoresPdf != null) {
if (file != null) {
ignoresPdf.add(file.getName());
}
}
throw ex;
}
}
NodeUtilities.setAttributeValue(node, reference.getKey().getName(), reference.getKey().getValue());
NodeAttributeTableModel attributeTable = (NodeAttributeTableModel) node.getExtension(NodeAttributeTableModel.class);
if (attributeTable == null) {
return false;
}
AttributeController attributeController = AttributeController.getController(MModeController.getMModeController());
Vector<Attribute> attributes = attributeTable.getAttributes();
ArrayList<Item> inserts = new ArrayList<Item>();
for (Item item : reference.getAttributes()) {
boolean found = false;
for (int i = 0; i < attributes.size() && !found; i++) {
try {
Attribute attribute = attributes.get(i);
if (attribute.getName().equals(item.getName())) {
found = true;
if (item.getValue() == null) {
attributeController.performRemoveRow(attributeTable, i);
changes = true;
}
else if (!attribute.getValue().equals(item.getValue())) {
attributeController.performSetValueAt(attributeTable, item.getValue(), i, 1);
attribute.setValue(item.getValue());
changes = true;
}
}
}
catch (Exception e) {
LogUtils.warn("Exception in org.docear.plugin.bibtex.jabref.JabRefAttributes.updateReferenceToNode(): ", e);
}
}
if (!found && item.getValue() != null) {
inserts.add(item);
}
}
int i = attributes.size();
for (Item item : inserts) {
changes = true;
AttributeController.getController(MModeController.getMModeController()).performInsertRow(attributeTable, i, item.getName(), item.getValue());
i++;
}
}
finally {
// do not overwrite existing links
NodeLinks nodeLinks = NodeLinks.getLinkExtension(node);
if (nodeLinks == null || nodeLinks.getHyperLink() == null) {
// add link to node
if (reference.getUris().size() > 0) {
((MLinkController) MLinkController.getController()).setLinkTypeDependantLink(node, reference.getUris().iterator().next());
changes = true;
}
else {
URL url = reference.getUrl();
if (url != null) {
((MLinkController) MLinkController.getController()).setLinkTypeDependantLink(node, URI.create(url.toExternalForm()));
changes = true;
}
}
}
synchronized (updateNodeLock) {
updateNodeLock = false;
}
}
return changes;
}
public boolean setReferenceToNode(BibtexEntry entry, NodeModel node) throws ResolveDuplicateEntryAbortedException {
return setReferenceToNode(new Reference(entry), node);
}
public boolean setReferenceToNode(Reference reference, NodeModel node) throws ResolveDuplicateEntryAbortedException {
return updateReferenceToNode(reference, node);
}
public void removePdfFromBibtexEntry(File file, BibtexEntry entry) {
String filename = file.getName();
FileListTableModel model = new FileListTableModel();
String oldVal = entry.getField(GUIGlobals.FILE_FIELD);
if (oldVal == null) {
return;
}
model.setContent(oldVal);
for (int i = 0; i < model.getRowCount(); i++) {
FileListEntry fle = model.getEntry(i);
File f = new File(fle.getLink());
if (filename.equals(f.getName())) {
model.removeEntry(i);
System.out.println(oldVal + " <--> " + model.getStringRepresentation());
i--;
}
}
entry.setField(GUIGlobals.FILE_FIELD, model.getStringRepresentation());
}
public void removeUrlFromBibtexEntry(URL url, BibtexEntry entry) {
entry.setField("url", null);
}
public List<String> retrieveFileLinksFromEntry(BibtexEntry entry) {
String jabrefFiles = entry.getField(GUIGlobals.FILE_FIELD);
if (jabrefFiles != null) {
// path linked in jabref
return parsePathNames(entry, jabrefFiles);
}
return Collections.emptyList();
}
private void removeDuplicateLinks(File file, BibtexEntry entry) {
BibtexDatabase database = ReferencesController.getController().getJabrefWrapper().getDatabase();
Iterator<BibtexEntry> iter = database.getEntries().iterator();
while (iter.hasNext()) {
BibtexEntry item = iter.next();
if (item != entry) {
ReferencesController.getController().getJabRefAttributes().removePdfFromBibtexEntry(file, item);
}
}
}
private void removeDuplicateLinks(URL url, BibtexEntry entry) {
BibtexDatabase database = ReferencesController.getController().getJabrefWrapper().getDatabase();
Iterator<BibtexEntry> iter = database.getEntries().iterator();
while (iter.hasNext()) {
BibtexEntry item = iter.next();
if (item != entry) {
ReferencesController.getController().getJabRefAttributes().removeUrlFromBibtexEntry(url, item);
}
}
}
// public void resolveDuplicateLinks(BibtexEntry entry) throws
// InterruptedException {
// for (String s : retrieveFileLinksFromEntry(entry)) {
// try {
// resolveDuplicateLinks(new File(s));
// }
// catch (Exception ex) {
// LogUtils.warn("org.docear.plugin.bibtex.jabref.JabRefAttributes.resolveDuplicateLinks: "
// + ex.getMessage());
// }
// }
// }
public void removeLinkFromNode(NodeModel node) {
for (LinkModel linkModel : NodeLinks.getLinkExtension(node).getLinks()) {
if (linkModel instanceof NodeLinkModel) {
((MLinkController) LinkController.getController()).removeArrowLink((NodeLinkModel) linkModel);
}
}
}
public BibtexEntry resolveDuplicateLinks(File file) throws ResolveDuplicateEntryAbortedException {
List<BibtexEntry> entries = new ArrayList<BibtexEntry>();
BibtexDatabase database = ReferencesController.getController().getJabrefWrapper().getDatabase();
for (BibtexEntry entry : database.getEntries()) {
for (String jabrefPath : retrieveFileLinksFromEntry(entry)) {
File jabrefFile = new File(jabrefPath);
if (jabrefFile != null && jabrefFile.getName().equals(file.getName())) {
entries.add(entry);
break;
}
}
}
if (entries.size() == 1) {
return entries.get(0);
}
else if (entries.size() == 0) {
return null;
}
DuplicateLinkDialogPanel panel = new DuplicateLinkDialogPanel(entries, file);
int answer = JOptionPane.showConfirmDialog(UITools.getFrame(), panel, TextUtils.getText("docear.reference.duplicate_file.title"),
JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if (answer != JOptionPane.OK_OPTION) {
throw new ResolveDuplicateEntryAbortedException(file);
}
else {
BibtexEntry entry = panel.getSelectedEntry();
removeDuplicateLinks(file, entry);
ReferencesController.getController().getJabrefWrapper().getBasePanel().runCommand("save");
setNodeDirty(true);
return entry;
}
}
public BibtexEntry resolveDuplicateLinks(URL url) throws ResolveDuplicateEntryAbortedException {
List<BibtexEntry> entries = new ArrayList<BibtexEntry>();
BibtexDatabase database = ReferencesController.getController().getJabrefWrapper().getDatabase();
for (BibtexEntry entry : database.getEntries()) {
URL entryUrl = null;
String urlString = entry.getField("url");
try {
if (urlString != null) {
entryUrl = new URL(urlString);
}
}
catch (MalformedURLException e) {
LogUtils.info(urlString + ": " + e.getMessage());
}
if (url.equals(entryUrl)) {
entries.add(entry);
}
}
if (entries.size() == 1) {
return entries.get(0);
}
else if (entries.size() == 0) {
return null;
}
DuplicateLinkDialogPanel panel = new DuplicateLinkDialogPanel(entries, url);
int answer = JOptionPane.showConfirmDialog(UITools.getFrame(), panel, TextUtils.getText("docear.reference.duplicate_url.title"),
JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if (answer != JOptionPane.OK_OPTION) {
throw new ResolveDuplicateEntryAbortedException(url);
}
else {
BibtexEntry entry = panel.getSelectedEntry();
removeDuplicateLinks(url, entry);
ReferencesController.getController().getJabrefWrapper().getBasePanel().runCommand("save");
setNodeDirty(true);
return entry;
}
}
// FIXME: not used yet --> implement functionality into
// findBibtexEntryForPDF
public BibtexEntry findBibtexEntryForURL(URI nodeUri, MapModel map, boolean ignoreDuplicates) throws ResolveDuplicateEntryAbortedException {
BibtexDatabase database = ReferencesController.getController().getJabrefWrapper().getDatabase();
if (database == null || nodeUri == null) {
return null;
}
MapModificationSession session = map.getExtension(DocearMapModelExtension.class).getMapModificationSession();
Set<String> ignores = null;
if (session != null) {
ignores = (Set<String>) session.getSessionObject(MapModificationSession.URL_IGNORE_LIST);
if (ignores == null) {
ignores = new HashSet<String>();
session.putSessionObject(MapModificationSession.URL_IGNORE_LIST, ignores);
}
}
URL nodeUrl = null;
try {
nodeUrl = nodeUri.toURL();
}
catch (Exception e1) {
LogUtils.info(e1.getMessage());
return null;
}
try {
if (ignores != null) {
if (nodeUrl != null) {
if (ignores.contains(nodeUrl.toExternalForm())) {
throw new ResolveDuplicateEntryAbortedException(nodeUrl);
}
}
}
if (!ignoreDuplicates) {
resolveDuplicateLinks(nodeUrl);
}
for (BibtexEntry entry : database.getEntries()) {
String entryUrlField = entry.getField("url");
if (entryUrlField != null) {
URI entryUri = null;
try {
entryUri = URI.create(entryUrlField);
}
catch (Exception e) {
LogUtils.warn("org.docear.plugin.bibtex.jabref.JabRefAttributes.findBibtexEntryForURL: " + e.getMessage());
continue;
}
String entryScheme = entryUri.getScheme();
String nodeScheme = nodeUri.getScheme();
if (entryScheme != null && nodeScheme != null && !entryScheme.equals(nodeScheme)) {
continue;
}
String entryUriString = entryUri.toString();
if (entryScheme != null) {
entryUriString = entryUriString.substring(entryScheme.length() + 3);
}
String nodeUriString = nodeUri.toString();
if (nodeScheme != null) {
nodeUriString = nodeUriString.substring(nodeScheme.length() + 3);
}
if (entryUriString.equals(nodeUriString)) {
return entry;
}
}
}
}
catch (ResolveDuplicateEntryAbortedException e) {
if (ignores != null) {
if (nodeUrl != null) {
ignores.add(nodeUrl.toExternalForm());
}
}
throw e;
}
return null;
}
public BibtexEntry findBibtexEntryForPDF(URI uri, MapModel map) throws ResolveDuplicateEntryAbortedException {
return findBibtexEntryForPDF(uri, map, false);
}
public BibtexEntry findBibtexEntryForPDF(URI uri, MapModel map, boolean ignoreDuplicates) throws ResolveDuplicateEntryAbortedException {
BibtexDatabase database = ReferencesController.getController().getJabrefWrapper().getDatabase();
if (database == null) {
return null;
}
// file name linked in a node
File nodeFile = WorkspaceUtils.resolveURI(uri, map);
if (nodeFile == null) {
return null;
}
String nodeFileName = nodeFile.getName();
String baseName = FilenameUtils.removeExtension(nodeFileName);
MapModificationSession session = map.getExtension(DocearMapModelExtension.class).getMapModificationSession();
Set<String> ignores = null;
if (session != null) {
ignores = (Set<String>) session.getSessionObject(MapModificationSession.FILE_IGNORE_LIST);
if (ignores == null) {
ignores = new HashSet<String>();
session.putSessionObject(MapModificationSession.FILE_IGNORE_LIST, ignores);
}
}
try {
if (ignores != null) {
if (nodeFileName != null) {
if (ignores.contains(nodeFileName)) {
throw new ResolveDuplicateEntryAbortedException(nodeFile);
}
}
}
if (!ignoreDuplicates) {
resolveDuplicateLinks(nodeFile);
}
for (BibtexEntry entry : database.getEntries()) {
String jabrefFiles = entry.getField(GUIGlobals.FILE_FIELD);
if (jabrefFiles != null) {
// path linked in jabref
for (String jabrefFile : parsePathNames(entry, jabrefFiles)) {
if (jabrefFile.endsWith(nodeFileName)) {
return entry;
}
}
}
}
}
catch (ResolveDuplicateEntryAbortedException e) {
if (ignores != null) {
if (nodeFileName != null) {
ignores.add(nodeFileName);
}
}
throw e;
}
BibtexEntry entry = database.getEntryByKey(baseName);
return entry;
}
public ArrayList<String> parsePathNames(BibtexEntry entry, String path) {
ArrayList<String> fileNames = new ArrayList<String>();
ArrayList<String> paths = extractPaths(path);
if (path == null) {
LogUtils.warn("Could not extract path from: " + entry.getCiteKey());
return fileNames;
}
if (paths == null || paths.size() == 0) {
return fileNames;
}
for (String s : paths) {
try {
if (Compat.isWindowsOS()) {
fileNames.add(new File(s).getPath());
}
else {
// DOCEAR - maybe no escape removal -> could cause problems
// like in win os
fileNames.add(new File(removeEscapingCharacter(s)).getPath());
}
}
catch (Exception e) {
continue;
}
}
return fileNames;
}
public ArrayList<URI> parsePaths(BibtexEntry entry, String pathInBibtexFile) {
ArrayList<URI> uris = new ArrayList<URI>();
ArrayList<String> paths = extractPaths(pathInBibtexFile);
for (String path : paths) {
if (path == null) {
LogUtils.warn("Could not extract path from: " + entry.getCiteKey());
continue;
}
path = removeEscapingCharacter(path);
if (isAbsolutePath(path) && (new File(path)).exists()) {
uris.add(new File(path).toURI());
}
else {
URI uri = CoreConfiguration.referencePathObserver.getUri();
URI absUri = WorkspaceUtils.absoluteURI(uri);
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
URI pdfUri = absUri.resolve(UriBuilder.fromPath(path).build());
Thread.currentThread().setContextClassLoader(contextClassLoader);
File file = null;
try {
file = new File(pdfUri);
}
catch (IllegalArgumentException e) {
LogUtils.warn(e.getMessage() + " for: " + path);
}
if (file != null && file.exists()) {
uris.add(pdfUri);
}
}
}
return uris;
}
private static boolean isAbsolutePath(String path) {
return path.matches("^/.*") || path.matches("^[a-zA-Z]:.*");
}
private static String removeEscapingCharacter(String string) {
return string.replaceAll("([^\\\\]{1,1})[\\\\]{1}", "$1");
}
public static ArrayList<String> extractPaths(String fileField) {
ArrayList<String> paths = new ArrayList<String>();
if (fileField != null) {
FileListTableModel model = new FileListTableModel();
model.setContent(fileField);
for (int i = 0; i < model.getRowCount(); i++) {
paths.add(model.getEntry(i).getLink());
}
}
return paths;
}
public void generateBibtexEntry(BibtexEntry entry) {
BibtexDatabase database = ReferencesController.getController().getJabrefWrapper().getDatabase();
LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, entry);
}
public boolean isNodeDirty() {
return nodeDirty;
}
public void setNodeDirty(boolean nodeDirty) {
this.nodeDirty = nodeDirty;
}
}