/src/main/java/net/sf/jabref/Util.java
Java | 3514 lines | 2282 code | 311 blank | 921 comment | 583 complexity | e0f03a63a637dde3a4cf26453b617b0c MD5 | raw file
- /* Copyright (C) 2003-2012 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.
- */
- // created by : Morten O. Alver 2003
- package net.sf.jabref;
- import java.awt.BorderLayout;
- import java.awt.CardLayout;
- import java.awt.Color;
- import java.awt.Component;
- import java.awt.Desktop;
- import java.awt.Dimension;
- import java.awt.Font;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.*;
- import java.net.MalformedURLException;
- import java.net.URI;
- import java.net.URISyntaxException;
- import java.net.URL;
- import java.net.URLConnection;
- import java.net.URLDecoder;
- import java.nio.charset.Charset;
- import java.nio.charset.CharsetEncoder;
- import java.text.NumberFormat;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Calendar;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.StringTokenizer;
- import java.util.TreeSet;
- import java.util.Vector;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import javax.swing.Action;
- import javax.swing.ActionMap;
- import javax.swing.BorderFactory;
- import javax.swing.Box;
- import javax.swing.InputMap;
- import javax.swing.JButton;
- import javax.swing.JComponent;
- import javax.swing.JDialog;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JOptionPane;
- import javax.swing.JPanel;
- import javax.swing.JProgressBar;
- import javax.swing.JRootPane;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- import javax.swing.SwingUtilities;
- import javax.swing.undo.AbstractUndoableEdit;
- import javax.swing.undo.UndoableEdit;
- import net.sf.jabref.autocompleter.AbstractAutoCompleter;
- import net.sf.jabref.export.SaveSession;
- import net.sf.jabref.export.layout.Layout;
- import net.sf.jabref.export.layout.LayoutHelper;
- import net.sf.jabref.external.ExternalFileType;
- import net.sf.jabref.external.ExternalFileTypeEntryEditor;
- import net.sf.jabref.external.RegExpFileSearch;
- import net.sf.jabref.external.UnknownExternalFileType;
- import net.sf.jabref.groups.AbstractGroup;
- import net.sf.jabref.groups.KeywordGroup;
- import net.sf.jabref.gui.FileListEntry;
- import net.sf.jabref.gui.FileListEntryEditor;
- import net.sf.jabref.gui.FileListTableModel;
- import net.sf.jabref.labelPattern.LabelPatternUtil;
- import net.sf.jabref.net.URLDownload;
- import net.sf.jabref.undo.NamedCompound;
- import net.sf.jabref.undo.UndoableFieldChange;
- import net.sf.jabref.util.FileNameCleaner;
- import com.jgoodies.forms.builder.DefaultFormBuilder;
- import com.jgoodies.forms.layout.FormLayout;
- /**
- * utility functions
- */
- public class Util {
- /**
- * A static Object for date formatting. Please do not create the object
- * here, because there are some references from the Globals class.....
- *
- */
- private static SimpleDateFormat dateFormatter = null;
- /*
- * Colors are defined here.
- *
- */
- public static Color fieldsCol = new Color(180, 180, 200);
- /*
- * Integer values for indicating result of duplicate check (for entries):
- *
- */
- final static int TYPE_MISMATCH = -1, NOT_EQUAL = 0, EQUAL = 1, EMPTY_IN_ONE = 2,
- EMPTY_IN_TWO = 3, EMPTY_IN_BOTH = 4;
-
- final static NumberFormat idFormat;
- public static Pattern remoteLinkPattern = Pattern.compile("[a-z]+://.*");
- public static int MARK_COLOR_LEVELS = 6,
- MAX_MARKING_LEVEL = MARK_COLOR_LEVELS-1,
- IMPORT_MARK_LEVEL = MARK_COLOR_LEVELS;
- public static Pattern markNumberPattern = Pattern.compile(JabRefPreferences.getInstance().MARKING_WITH_NUMBER_PATTERN);
- static {
- idFormat = NumberFormat.getInstance();
- idFormat.setMinimumIntegerDigits(8);
- idFormat.setGroupingUsed(false);
- }
- public static int getMinimumIntegerDigits(){
- return idFormat.getMinimumIntegerDigits();
- }
- public static void pr(String s) {
- Globals.logger(s);
- }
- public static String nCase(String s) {
- // Make first character of String uppercase, and the
- // rest lowercase.
- if (s.length() > 1)
- return s.substring(0, 1).toUpperCase() + s.substring(1, s.length()).toLowerCase();
- else
- return s.toUpperCase();
- }
- public static String checkName(String s) {
- // Append '.bib' to the string unless it ends with that.
- if (s.length() < 4 || !s.substring(s.length() - 4).equalsIgnoreCase(".bib")) {
- return s + ".bib";
- }
- return s;
- }
- private static int idCounter = 0;
- public synchronized static String createNeutralId() {
- return idFormat.format(idCounter++);
- }
- /**
- * This method sets the location of a Dialog such that it is centered with
- * regard to another window, but not outside the screen on the left and the
- * top.
- */
- public static void placeDialog(java.awt.Dialog diag, java.awt.Container win) {
- diag.setLocationRelativeTo(win);
- }
- /**
- * This method translates a field or string from Bibtex notation, with
- * possibly text contained in " " or { }, and string references,
- * concatenated by '#' characters, into Bibkeeper notation, where string
- * references are enclosed in a pair of '#' characters.
- */
- public static String parseField(String content) {
-
- if (content.length() == 0)
- return content;
-
- String[] strings = content.split("#");
- StringBuffer result = new StringBuffer();
- for (String string : strings) {
- String s = string.trim();
- if (s.length() > 0) {
- char c = s.charAt(0);
- // String reference or not?
- if (c == '{' || c == '"') {
- result.append(shaveString(string));
- } else {
- // This part should normally be a string reference, but if it's
- // a pure number, it is not.
- String s2 = shaveString(s);
- try {
- Integer.parseInt(s2);
- // If there's no exception, it's a number.
- result.append(s2);
- } catch (NumberFormatException ex) {
- // otherwise append with hashes...
- result.append("#").append(s2).append("#");
- }
- }
- }
- }
- return result.toString();
- }
- /**
- * Will return the publication date of the given bibtex entry in conformance
- * to ISO 8601, i.e. either YYYY or YYYY-MM.
- *
- * @param entry
- * @return will return the publication date of the entry or null if no year
- * was found.
- */
- public static String getPublicationDate(BibtexEntry entry) {
- Object o = entry.getField("year");
- if (o == null)
- return null;
- String year = toFourDigitYear(o.toString());
- o = entry.getField("month");
- if (o != null) {
- int month = Util.getMonthNumber(o.toString());
- if (month != -1) {
- return year + "-" + (month + 1 < 10 ? "0" : "") + (month + 1);
- }
- }
- return year;
- }
- public static String shaveString(String s) {
- // returns the string, after shaving off whitespace at the beginning
- // and end, and removing (at most) one pair of braces or " surrounding
- // it.
- if (s == null)
- return null;
- char ch, ch2;
- int beg = 0, end = s.length();
- // We start out assuming nothing will be removed.
- boolean begok = false, endok = false;
- while (!begok) {
- if (beg < s.length()) {
- ch = s.charAt(beg);
- if (Character.isWhitespace(ch))
- beg++;
- else
- begok = true;
- } else
- begok = true;
- }
- while (!endok) {
- if (end > beg + 1) {
- ch = s.charAt(end - 1);
- if (Character.isWhitespace(ch))
- end--;
- else
- endok = true;
- } else
- endok = true;
- }
- if (end > beg + 1) {
- ch = s.charAt(beg);
- ch2 = s.charAt(end - 1);
- if (((ch == '{') && (ch2 == '}')) || ((ch == '"') && (ch2 == '"'))) {
- beg++;
- end--;
- }
- }
- s = s.substring(beg, end);
- return s;
- }
-
- public static String rtrim(String s) {
- return s.replaceAll("\\s+$","");
- }
- /**
- * This method returns a String similar to the one passed in, except that it
- * is molded into a form that is acceptable for bibtex.
- *
- * Watch-out that the returned string might be of length 0 afterwards.
- *
- * @param key
- * mayBeNull
- */
- public static String checkLegalKey(String key) {
- if (key == null)
- return null;
- if (!JabRefPreferences.getInstance().getBoolean("enforceLegalBibtexKey")) {
- // User doesn't want us to enforce legal characters. We must still look
- // for whitespace and some characters such as commas, since these would
- // interfere with parsing:
- StringBuilder newKey = new StringBuilder();
- for (int i = 0; i < key.length(); i++) {
- char c = key.charAt(i);
- if (!Character.isWhitespace(c) && (c != '{') && (c != '\\') && (c != '"')
- && (c != '}') && (c != ','))
- newKey.append(c);
- }
- return newKey.toString();
- }
- StringBuilder newKey = new StringBuilder();
- for (int i = 0; i < key.length(); i++) {
- char c = key.charAt(i);
- if (!Character.isWhitespace(c) && (c != '#') && (c != '{') && (c != '\\') && (c != '"')
- && (c != '}') && (c != '~') && (c != ',') && (c != '^') && (c != '\''))
- newKey.append(c);
- }
- // Replace non-english characters like umlauts etc. with a sensible
- // letter or letter combination that bibtex can accept.
- String newKeyS = replaceSpecialCharacters(newKey.toString());
- return newKeyS;
- }
- /**
- * Replace non-english characters like umlauts etc. with a sensible letter
- * or letter combination that bibtex can accept. The basis for replacement
- * is the HashMap GLobals.UNICODE_CHARS.
- */
- public static String replaceSpecialCharacters(String s) {
- for (Map.Entry<String, String> chrAndReplace : Globals.UNICODE_CHARS.entrySet()){
- s = s.replaceAll(chrAndReplace.getKey(), chrAndReplace.getValue());
- }
- return s;
- }
- static public String _wrap2(String in, int wrapAmount) {
- // The following line cuts out all whitespace and replaces them with
- // single
- // spaces:
- // in = in.replaceAll("[ ]+"," ").replaceAll("[\\t]+"," ");
- // StringBuffer out = new StringBuffer(in);
- StringBuffer out = new StringBuffer(in.replaceAll("[ \\t\\r]+", " "));
- int p = in.length() - wrapAmount;
- int lastInserted = -1;
- while (p > 0) {
- p = out.lastIndexOf(" ", p);
- if (p <= 0 || p <= 20)
- break;
- int lbreak = out.indexOf("\n", p);
- System.out.println(lbreak + " " + lastInserted);
- if ((lbreak > p) && ((lastInserted >= 0) && (lbreak < lastInserted))) {
- p = lbreak - wrapAmount;
- } else {
- out.insert(p, "\n\t");
- lastInserted = p;
- p -= wrapAmount;
- }
- }
- return out.toString();
- }
- static public String wrap2(String in, int wrapAmount) {
- return net.sf.jabref.imports.FieldContentParser.wrap(in, wrapAmount);
- }
- static public String __wrap2(String in, int wrapAmount) {
- // The following line cuts out all whitespace except line breaks, and
- // replaces
- // with single spaces. Line breaks are padded with a tab character:
- StringBuffer out = new StringBuffer(in.replaceAll("[ \\t\\r]+", " "));
- int p = 0;
- // int lastInserted = -1;
- while (p < out.length()) {
- int q = out.indexOf(" ", p + wrapAmount);
- if ((q < 0) || (q >= out.length()))
- break;
- int lbreak = out.indexOf("\n", p);
- // System.out.println(lbreak);
- if ((lbreak > p) && (lbreak < q)) {
- p = lbreak + 1;
- int piv = lbreak + 1;
- if ((out.length() > piv) && !(out.charAt(piv) == '\t'))
- out.insert(piv, "\n\t");
- } else {
- // System.out.println(q+" "+out.length());
- out.deleteCharAt(q);
- out.insert(q, "\n\t");
- p = q + 1;
- }
- }
- return out.toString();// .replaceAll("\n", "\n\t");
- }
- public static TreeSet<String> findDeliminatedWordsInField(BibtexDatabase db, String field,
- String deliminator) {
- TreeSet<String> res = new TreeSet<String>();
-
- for (String s : db.getKeySet()){
- BibtexEntry be = db.getEntryById(s);
- Object o = be.getField(field);
- if (o != null) {
- String fieldValue = o.toString().trim();
- StringTokenizer tok = new StringTokenizer(fieldValue, deliminator);
- while (tok.hasMoreTokens())
- res.add(nCase(tok.nextToken().trim()));
- }
- }
- return res;
- }
- /**
- * Returns a HashMap containing all words used in the database in the given
- * field type. Characters in <code>remove</code> are not included.
- *
- * @param db
- * a <code>BibtexDatabase</code> value
- * @param field
- * a <code>String</code> value
- * @param remove
- * a <code>String</code> value
- * @return a <code>HashSet</code> value
- */
- public static TreeSet<String> findAllWordsInField(BibtexDatabase db, String field, String remove) {
- TreeSet<String> res = new TreeSet<String>();
- StringTokenizer tok;
- for (String s : db.getKeySet()){
- BibtexEntry be = db.getEntryById(s);
- Object o = be.getField(field);
- if (o != null) {
- tok = new StringTokenizer(o.toString(), remove, false);
- while (tok.hasMoreTokens())
- res.add(nCase(tok.nextToken().trim()));
- }
- }
- return res;
- }
- /**
- * Finds all authors' last names in all the given fields for the given database.
- * @param db The database.
- * @param fields The fields to look in.
- * @return a set containing the names.
- */
- public static Set<String> findAuthorLastNames(BibtexDatabase db, List<String> fields) {
- Set<String> res = new TreeSet<String>();
- for (String s : db.getKeySet()){
- BibtexEntry be = db.getEntryById(s);
- for (String field : fields) {
- String val = be.getField(field);
- if ((val != null) && (val.length() > 0)) {
- AuthorList al = AuthorList.getAuthorList(val);
- for (int i=0; i<al.size(); i++) {
- AuthorList.Author a = al.getAuthor(i);
- String lastName = a.getLast();
- if ((lastName != null) && (lastName.length() > 0))
- res.add(lastName);
- }
- }
- }
- }
- return res;
- }
-
- /**
- * Takes a String array and returns a string with the array's elements
- * delimited by a certain String.
- *
- * @param strs
- * String array to convert.
- * @param delimiter
- * String to use as delimiter.
- * @return Delimited String.
- */
- public static String stringArrayToDelimited(String[] strs, String delimiter) {
- if ((strs == null) || (strs.length == 0))
- return "";
- if (strs.length == 1)
- return strs[0];
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < strs.length - 1; i++) {
- sb.append(strs[i]);
- sb.append(delimiter);
- }
- sb.append(strs[strs.length - 1]);
- return sb.toString();
- }
- /**
- * Takes a delimited string, splits it and returns
- *
- * @param names
- * a <code>String</code> value
- * @return a <code>String[]</code> value
- */
- public static String[] delimToStringArray(String names, String delimiter) {
- if (names == null)
- return null;
- return names.split(delimiter);
- }
- /**
- * Creates a substring from a text
- *
- * @param text
- * @param i
- * @param terminateOnEndBraceOnly
- * @return
- */
- public static String getPart(String text, int i, boolean terminateOnEndBraceOnly) {
- char c;
- int count = 0;
- StringBuffer part = new StringBuffer();
- // advance to first char and skip whitespace
- i++;
- while (i < text.length() && Character.isWhitespace(text.charAt(i))){
- i++;
- }
- // then grab whathever is the first token (counting braces)
- while (i < text.length()){
- c = text.charAt(i);
- if (!terminateOnEndBraceOnly && count == 0 && Character.isWhitespace(c)) {
- i--; // end argument and leave whitespace for further
- // processing
- break;
- }
- if (c == '}' && --count < 0)
- break;
- else if (c == '{')
- count++;
- part.append(c);
- i++;
- }
- return part.toString();
- }
- /**
- * Open a http/pdf/ps viewer for the given link string.
- */
- public static void openExternalViewer(MetaData metaData, String link, String fieldName)
- throws IOException {
- if (fieldName.equals("ps") || fieldName.equals("pdf")) {
- // Find the default directory for this field type:
- String[] dir = metaData.getFileDirectory(fieldName);
- File file = expandFilename(link, dir);
- // Check that the file exists:
- if ((file == null) || !file.exists()) {
- throw new IOException(Globals.lang("File not found") + " (" + fieldName + "): '"
- + link + "'.");
- }
- link = file.getCanonicalPath();
- // Use the correct viewer even if pdf and ps are mixed up:
- String[] split = file.getName().split("\\.");
- if (split.length >= 2) {
- if (split[split.length - 1].equalsIgnoreCase("pdf"))
- fieldName = "pdf";
- else if (split[split.length - 1].equalsIgnoreCase("ps")
- || (split.length >= 3 && split[split.length - 2].equalsIgnoreCase("ps")))
- fieldName = "ps";
- }
- } else if (fieldName.equals("doi")) {
- fieldName = "url";
-
- // sanitizing is done below at the treatment of "URL"
- // in sanatizeUrl a doi-link is correctly treated
- } else if (fieldName.equals("eprint")) {
- fieldName = "url";
- link = sanitizeUrl(link);
- // Check to see if link field already contains a well formated URL
- if (!link.startsWith("http://")) {
- link = Globals.ARXIV_LOOKUP_PREFIX + link;
- }
- }
- if (fieldName.equals("url")) { // html
- try {
- link = sanitizeUrl(link);
- ExternalFileType fileType = Globals.prefs.getExternalFileTypeByExt("html");
- openExternalFilePlatformIndependent(fileType, link);
- } catch (IOException e) {
- System.err.println(Globals.lang("Error_opening_file_'%0'.", link));
- e.printStackTrace();
- }
- } else if (fieldName.equals("ps")) {
- try {
- if (Globals.ON_MAC) {
- ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("ps");
- String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
- String[] cmd = { "/usr/bin/open", "-a", viewer, link };
- Runtime.getRuntime().exec(cmd);
- } else if (Globals.ON_WIN) {
- openFileOnWindows(link, true);
- /*
- * cmdArray[0] = Globals.prefs.get("psviewer"); cmdArray[1] =
- * link; Process child = Runtime.getRuntime().exec(
- * cmdArray[0] + " " + cmdArray[1]);
- */
- } else {
- ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("ps");
- String viewer = type != null ? type.getOpenWith() : "xdg-open";
- String[] cmdArray = new String[2];
- cmdArray[0] = viewer;
- cmdArray[1] = link;
- Runtime.getRuntime().exec(cmdArray);
- }
- } catch (IOException e) {
- System.err.println("An error occured on the command: "
- + Globals.prefs.get("psviewer") + " " + link);
- }
- } else if (fieldName.equals("pdf")) {
- try {
- if (Globals.ON_MAC) {
- ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("pdf");
- String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
- String[] cmd = { "/usr/bin/open", "-a", viewer, link };
- Runtime.getRuntime().exec(cmd);
- } else if (Globals.ON_WIN) {
- openFileOnWindows(link, true);
- /*
- * String[] spl = link.split("\\\\"); StringBuffer sb = new
- * StringBuffer(); for (int i = 0; i < spl.length; i++) { if
- * (i > 0) sb.append("\\"); if (spl[i].indexOf(" ") >= 0)
- * spl[i] = "\"" + spl[i] + "\""; sb.append(spl[i]); }
- * //pr(sb.toString()); link = sb.toString();
- *
- * String cmd = "cmd.exe /c start " + link;
- *
- * Process child = Runtime.getRuntime().exec(cmd);
- */
- } else {
- ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("pdf");
- String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
- String[] cmdArray = new String[2];
- cmdArray[0] = viewer;
- cmdArray[1] = link;
- // Process child = Runtime.getRuntime().exec(cmdArray[0]+"
- // "+cmdArray[1]);
- Runtime.getRuntime().exec(cmdArray);
- }
- } catch (IOException e) {
- e.printStackTrace();
- System.err.println("An error occured on the command: "
- + Globals.prefs.get("pdfviewer") + " #" + link);
- System.err.println(e.getMessage());
- }
- } else {
- System.err
- .println("Message: currently only PDF, PS and HTML files can be opened by double clicking");
- }
- }
- /**
- * Opens a file on a Windows system, using its default viewer.
- *
- * @param link
- * The file name.
- * @param localFile
- * true if it is a local file, not an URL.
- * @throws IOException
- */
- public static void openFileOnWindows(String link, boolean localFile) throws IOException {
- /*
- * if (localFile) { String[] spl = link.split("\\\\"); StringBuffer sb =
- * new StringBuffer(); for (int i = 0; i < spl.length; i++) { if (i > 0)
- * sb.append("\\"); if (spl[i].indexOf(" ") >= 0) spl[i] = "\"" + spl[i] +
- * "\""; sb.append(spl[i]); } link = sb.toString(); }
- */
- link = link.replaceAll("&", "\"&\"").replaceAll(" ", "\" \"");
- // Bug fix for:
- // http://sourceforge.net/tracker/index.php?func=detail&aid=1489454&group_id=92314&atid=600306
- String cmd;
- if (Globals.osName.startsWith("Windows 9")) {
- cmd = "command.com /c start " + link;
- } else {
- cmd = "cmd.exe /c start " + link;
- }
- Runtime.getRuntime().exec(cmd);
- }
- /**
- * Opens a file on a Windows system, using the given application.
- *
- * @param link The file name.
- * @param application Link to the app that opens the file.
- * @throws IOException
- */
- public static void openFileWithApplicationOnWindows(String link, String application)
- throws IOException {
- link = link.replaceAll("&", "\"&\"").replaceAll(" ", "\" \"");
- Runtime.getRuntime().exec(application + " " + link);
- }
- /**
- * Open an external file, attempting to use the correct viewer for it.
- *
- * @param metaData
- * The MetaData for the database this file belongs to.
- * @param link
- * The file name.
- * @return false if the link couldn't be resolved, true otherwise.
- */
- public static boolean openExternalFileAnyFormat(final MetaData metaData, String link,
- final ExternalFileType fileType) throws IOException {
- boolean httpLink = false;
- if (remoteLinkPattern.matcher(link.toLowerCase()).matches()) {
- httpLink = true;
- }
- /*if (link.toLowerCase().startsWith("file://")) {
- link = link.substring(7);
- }
- final String ln = link;
- if (remoteLinkPattern.matcher(link.toLowerCase()).matches()) {
- (new Thread(new Runnable() {
- public void run() {
- openRemoteExternalFile(metaData, ln, fileType);
- }
- })).start();
- return true;
- }*/
- //boolean httpLink = link.toLowerCase().startsWith("http:")
- // || link.toLowerCase().startsWith("ftp:");
-
- // For other platforms we'll try to find the file type:
- File file = new File(link);
- if (!httpLink) {
- File tmp = expandFilename(metaData, link);
- if (tmp != null)
- file = tmp;
- }
- // Check if we have arrived at a file type, and either an http link or an existing file:
- if ((httpLink || file.exists()) && (fileType != null)) {
- // Open the file:
- String filePath = httpLink ? link : file.getPath();
- openExternalFilePlatformIndependent(fileType, filePath);
- return true;
- } else {
- return false;
- // No file matched the name, or we didn't know the file type.
- }
- }
- private static void openExternalFilePlatformIndependent(ExternalFileType fileType, String filePath) throws IOException {
- if (Globals.ON_MAC) {
- // Use "-a <application>" if the app is specified, and just "open <filename>" otherwise:
- String[] cmd = ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0)) ?
- new String[] { "/usr/bin/open", "-a", fileType.getOpenWith(), filePath } :
- new String[] { "/usr/bin/open", filePath };
- Runtime.getRuntime().exec(cmd);
- } else if (Globals.ON_WIN) {
- if ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0)) {
- // Application is specified. Use it:
- openFileWithApplicationOnWindows(filePath, fileType.getOpenWith());
- } else
- openFileOnWindows(filePath, true);
- } else {
- // Use the given app if specified, and the universal "xdg-open" otherwise:
- String[] openWith;
- if ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0))
- openWith = fileType.getOpenWith().split(" ");
- else
- openWith = new String[] {"xdg-open"};
- String[] cmdArray = new String[openWith.length+1];
- System.arraycopy(openWith, 0, cmdArray, 0, openWith.length);
- cmdArray[cmdArray.length-1] = filePath;
- Runtime.getRuntime().exec(cmdArray);
- }
- }
- public static void openRemoteExternalFile(final MetaData metaData,
- final String link, final ExternalFileType fileType) {
- File temp = null;
- try {
- temp = File.createTempFile("jabref-link", "."+fileType.getExtension());
- temp.deleteOnExit();
- System.out.println("Downloading to '"+temp.getPath()+"'");
- URLDownload ud = new URLDownload(null, new URL(link), temp);
- ud.download();
- System.out.println("Done");
- } catch (MalformedURLException ex) {
- ex.printStackTrace();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- final String ln = temp.getPath();
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- try {
- openExternalFileAnyFormat(metaData, ln, fileType);
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- });
- }
- public static boolean openExternalFileUnknown(JabRefFrame frame, BibtexEntry entry, MetaData metaData,
- String link, UnknownExternalFileType fileType) throws IOException {
- String cancelMessage = Globals.lang("Unable to open file.");
- String[] options = new String[] {Globals.lang("Define '%0'", fileType.getName()),
- Globals.lang("Change file type"), Globals.lang("Cancel")};
- String defOption = options[0];
- int answer = JOptionPane.showOptionDialog(frame, Globals.lang("This external link is of the type '%0', which is undefined. What do you want to do?",
- fileType.getName()),
- Globals.lang("Undefined file type"), JOptionPane.YES_NO_CANCEL_OPTION,
- JOptionPane.QUESTION_MESSAGE, null, options, defOption);
- if (answer == JOptionPane.CANCEL_OPTION) {
- frame.output(cancelMessage);
- return false;
- }
- else if (answer == JOptionPane.YES_OPTION) {
- // User wants to define the new file type. Show the dialog:
- ExternalFileType newType = new ExternalFileType(fileType.getName(), "", "", "", "new");
- ExternalFileTypeEntryEditor editor = new ExternalFileTypeEntryEditor(frame, newType);
- editor.setVisible(true);
- if (editor.okPressed()) {
- // Get the old list of types, add this one, and update the list in prefs:
- List<ExternalFileType> fileTypes = new ArrayList<ExternalFileType>();
- ExternalFileType[] oldTypes = Globals.prefs.getExternalFileTypeSelection();
- Collections.addAll(fileTypes, oldTypes);
- fileTypes.add(newType);
- Collections.sort(fileTypes);
- Globals.prefs.setExternalFileTypes(fileTypes);
- // Finally, open the file:
- return openExternalFileAnyFormat(metaData, link, newType);
- } else {
- // Cancelled:
- frame.output(cancelMessage);
- return false;
- }
- }
- else {
- // User wants to change the type of this link.
- // First get a model of all file links for this entry:
- FileListTableModel tModel = new FileListTableModel();
- String oldValue = entry.getField(GUIGlobals.FILE_FIELD);
- tModel.setContent(oldValue);
- FileListEntry flEntry = null;
- // Then find which one we are looking at:
- for (int i=0; i<tModel.getRowCount(); i++) {
- FileListEntry iEntry = tModel.getEntry(i);
- if (iEntry.getLink().equals(link)) {
- flEntry = iEntry;
- break;
- }
- }
- if (flEntry == null) {
- // This shouldn't happen, so I'm not sure what to put in here:
- throw new RuntimeException("Could not find the file list entry "+link+" in "+entry.toString());
- }
- FileListEntryEditor editor = new FileListEntryEditor(frame, flEntry, false, true, metaData);
- editor.setVisible(true, false);
- if (editor.okPressed()) {
- // Store the changes and add an undo edit:
- String newValue = tModel.getStringRepresentation();
- UndoableFieldChange ce = new UndoableFieldChange(entry, GUIGlobals.FILE_FIELD,
- oldValue, newValue);
- entry.setField(GUIGlobals.FILE_FIELD, newValue);
- frame.basePanel().undoManager.addEdit(ce);
- frame.basePanel().markBaseChanged();
- // Finally, open the link:
- return openExternalFileAnyFormat(metaData, flEntry.getLink(), flEntry.getType());
- } else {
- // Cancelled:
- frame.output(cancelMessage);
- return false;
- }
- }
- }
- /**
- * Make sure an URL is "portable", in that it doesn't contain bad characters
- * that break the open command in some OSes.
- *
- * A call to this method will also remove \\url{} enclosings and clean DOI links.
- *
- * @param link :the URL to sanitize.
- * @return Sanitized URL
- */
- public static String sanitizeUrl(String link) {
- link = link.trim();
- // First check if it is enclosed in \\url{}. If so, remove
- // the wrapper.
- if (link.startsWith("\\url{") && link.endsWith("}"))
- link = link.substring(5, link.length() - 1);
- if (link.matches("^doi:/*.*")){
- // Remove 'doi:'
- link = link.replaceFirst("^doi:/*", "");
- link = Globals.DOI_LOOKUP_PREFIX + link;
- }
- // converts doi-only link to full http address
- // Morten Alver 6 Nov 2012: this extracts a nonfunctional DOI from some complete
- // http addresses (e.g. http://onlinelibrary.wiley.com/doi/10.1002/rra.999/abstract, where
- // the trailing "/abstract" is included but doesn't lead to a resolvable DOI).
- // To prevent mangling of working URLs I'm disabling this check if the link is already
- // a full http link:
- if (checkForPlainDOI(link) && !link.startsWith("http://")) {
- link = Globals.DOI_LOOKUP_PREFIX + getDOI(link);
- }
- link = link.replaceAll("\\+", "%2B");
- try {
- link = URLDecoder.decode(link, "UTF-8");
- } catch (UnsupportedEncodingException ignored) {
- }
- /**
- * Fix for: [ 1574773 ] sanitizeUrl() breaks ftp:// and file:///
- *
- * http://sourceforge.net/tracker/index.php?func=detail&aid=1574773&group_id=92314&atid=600306
- */
- try {
- return new URI(null, link, null).toASCIIString();
- } catch (URISyntaxException e) {
- return link;
- }
- }
- /**
- * Searches the given directory and subdirectories for a pdf file with name
- * as given + ".pdf"
- */
- public static String findPdf(String key, String extension, String directory, OpenFileFilter off) {
- // String filename = key + "."+extension;
- /*
- * Simon Fischer's patch for replacing a regexp in keys before
- * converting to filename:
- *
- * String regex = Globals.prefs.get("basenamePatternRegex"); if ((regex !=
- * null) && (regex.trim().length() > 0)) { String replacement =
- * Globals.prefs.get("basenamePatternReplacement"); key =
- * key.replaceAll(regex, replacement); }
- */
- if (!directory.endsWith(System.getProperty("file.separator")))
- directory += System.getProperty("file.separator");
- String found = findInDir(key, directory, off, 0);
- if (found != null)
- return found.substring(directory.length());
- else
- return null;
- }
- public static Map<BibtexEntry, List<File>> findAssociatedFiles(Collection<BibtexEntry> entries, Collection<String> extensions, Collection<File> directories){
- HashMap<BibtexEntry, List<File>> result = new HashMap<BibtexEntry, List<File>>();
-
- // First scan directories
- Set<File> filesWithExtension = findFiles(extensions, directories);
-
- // Initialize Result-Set
- for (BibtexEntry entry : entries){
- result.put(entry, new ArrayList<File>());
- }
- boolean exactOnly = Globals.prefs.getBoolean(JabRefPreferences.AUTOLINK_EXACT_KEY_ONLY);
- // Now look for keys
- nextFile:
- for (File file : filesWithExtension){
-
- String name = file.getName();
- int dot = name.lastIndexOf('.');
- // First, look for exact matches:
- for (BibtexEntry entry : entries){
- String citeKey = entry.getCiteKey();
- if ((citeKey != null) && (citeKey.length() > 0)) {
- if (dot > 0) {
- if (name.substring(0, dot).equals(citeKey)) {
- result.get(entry).add(file);
- continue nextFile;
- }
- }
- }
- }
- // If we get here, we didn't find any exact matches. If non-exact
- // matches are allowed, try to find one:
- if (!exactOnly) {
- for (BibtexEntry entry : entries){
- String citeKey = entry.getCiteKey();
- if ((citeKey != null) && (citeKey.length() > 0)) {
- if (name.startsWith(citeKey)){
- result.get(entry).add(file);
- continue nextFile;
- }
- }
- }
- }
- }
-
- return result;
- }
-
- public static Set<File> findFiles(Collection<String> extensions, Collection<File> directories) {
- Set<File> result = new HashSet<File>();
-
- for (File directory : directories){
- result.addAll(findFiles(extensions, directory));
- }
-
- return result;
- }
- private static Collection<? extends File> findFiles(Collection<String> extensions, File directory) {
- Set<File> result = new HashSet<File>();
-
- File[] children = directory.listFiles();
- if (children == null)
- return result; // No permission?
- for (File child : children){
- if (child.isDirectory()) {
- result.addAll(findFiles(extensions, child));
- } else {
-
- String extension = getFileExtension(child);
-
- if (extension != null){
- if (extensions.contains(extension)){
- result.add(child);
- }
- }
- }
- }
-
- return result;
- }
- /**
- * Returns the extension of a file or null if the file does not have one (no . in name).
- *
- * @param file
- *
- * @return The extension, trimmed and in lowercase.
- */
- public static String getFileExtension(File file) {
- String name = file.getName();
- int pos = name.lastIndexOf('.');
- String extension = ((pos >= 0) && (pos < name.length() - 1)) ? name.substring(pos + 1)
- .trim().toLowerCase() : null;
- return extension;
- }
- /**
- * New version of findPdf that uses findFiles.
- *
- * The search pattern will be read from the preferences.
- *
- * The [extension]-tags in this pattern will be replace by the given
- * extension parameter.
- *
- */
- public static String findPdf(BibtexEntry entry, String extension, String directory) {
- return findPdf(entry, extension, new String[] { directory });
- }
- /**
- * Convenience method for findPDF. Can search multiple PDF directories.
- */
- public static String findPdf(BibtexEntry entry, String extension, String[] directories) {
- String regularExpression;
- if (Globals.prefs.getBoolean(JabRefPreferences.USE_REG_EXP_SEARCH_KEY)) {
- regularExpression = Globals.prefs.get(JabRefPreferences.REG_EXP_SEARCH_EXPRESSION_KEY);
- } else {
- regularExpression = Globals.prefs
- .get(JabRefPreferences.DEFAULT_REG_EXP_SEARCH_EXPRESSION_KEY);
- }
- regularExpression = regularExpression.replaceAll("\\[extension\\]", extension);
- return findFile(entry, null, directories, regularExpression, true);
- }
- /**
- * Convenience menthod for findPDF. Searches for a file of the given type.
- * @param entry The BibtexEntry to search for a link for.
- * @param fileType The file type to search for.
- * @return The link to the file found, or null if not found.
- */
- public static String findFile(BibtexEntry entry, ExternalFileType fileType, List<String> extraDirs) {
- List<String> dirs = new ArrayList<String>();
- dirs.addAll(extraDirs);
- if (Globals.prefs.hasKey(fileType.getExtension()+"Directory")) {
- dirs.add(Globals.prefs.get(fileType.getExtension()+"Directory"));
- }
- String [] directories = dirs.toArray(new String[dirs.size()]);
- return findPdf(entry, fileType.getExtension(), directories);
- }
- /**
- * Searches the given directory and file name pattern for a file for the
- * bibtexentry.
- *
- * Used to fix:
- *
- * http://sourceforge.net/tracker/index.php?func=detail&aid=1503410&group_id=92314&atid=600309
- *
- * Requirements:
- * - Be able to find the associated PDF in a set of given directories.
- * - Be able to return a relative path or absolute path.
- * - Be fast.
- * - Allow for flexible naming schemes in the PDFs.
- *
- * Syntax scheme for file:
- * <ul>
- * <li>* Any subDir</li>
- * <li>** Any subDir (recursiv)</li>
- * <li>[key] Key from bibtex file and database</li>
- * <li>.* Anything else is taken to be a Regular expression.</li>
- * </ul>
- *
- * @param entry
- * non-null
- * @param database
- * non-null
- * @param directory
- * A set of root directories to start the search from. Paths are
- * returned relative to these directories if relative is set to
- * true. These directories will not be expanded or anything. Use
- * the file attribute for this.
- * @param file
- * non-null
- *
- * @param relative
- * whether to return relative file paths or absolute ones
- *
- * @return Will return the first file found to match the given criteria or
- * null if none was found.
- */
- public static String findFile(BibtexEntry entry, BibtexDatabase database, String[] directory,
- String file, boolean relative) {
- for (String aDirectory : directory) {
- String result = findFile(entry, database, aDirectory, file, relative);
- if (result != null) {
- return result;
- }
- }
- return null;
- }
- /**
- * Removes optional square brackets from the string s
- *
- * @param s
- * @return
- */
- public static String stripBrackets(String s) {
- int beginIndex = (s.startsWith("[") ? 1 : 0);
- int endIndex = (s.endsWith("]") ? s.length() - 1 : s.length());
- return s.substring(beginIndex, endIndex);
- }
- public static ArrayList<String[]> parseMethodsCalls(String calls) throws RuntimeException {
- ArrayList<String[]> result = new ArrayList<String[]>();
- char[] c = calls.toCharArray();
- int i = 0;
- while (i < c.length) {
- int start = i;
- if (Character.isJavaIdentifierStart(c[i])) {
- i++;
- while (i < c.length && (Character.isJavaIdentifierPart(c[i]) || c[i] == '.')) {
- i++;
- }
- if (i < c.length && c[i] == '(') {
- String method = calls.substring(start, i);
- // Skip the brace
- i++;
- if (i < c.length){
- if (c[i] == '"'){
- // Parameter is in format "xxx"
- // Skip "
- i++;
- int startParam = i;
- i++;
- boolean escaped = false;
- while (i + 1 < c.length &&
- !(!escaped && c[i] == '"' && c[i + 1] == ')')) {
- if (c[i] == '\\') {
- escaped = !escaped;
- }
- else
- escaped = false;
- i++;
- }
- String param = calls.substring(startParam, i);
-
- result.add(new String[] { method, param });
- } else {
- // Parameter is in format xxx
- int startParam = i;
- while (i < c.length && c[i] != ')') {
- i++;
- }
- String param = calls.substring(startParam, i);
- result.add(new String[] { method, param });
- }
- } else {
- // Incorrecly terminated open brace
- result.add(new String[] { method });
- }
- } else {
- String method = calls.substring(start, i);
- result.add(new String[] { method });
- }
- }
- i++;
- }
- return result;
- }
- /**
- * Accepts a string like [author:lower] or [title:abbr] or [auth],
- * whereas the first part signifies the bibtex-field to get, or the key generator
- * field marker to use, while the others are the modifiers that will be applied.
- *
- * @param fieldAndFormat
- * @param entry
- * @param database
- * @return
- */
- public static String getFieldAndFormat(String fieldAndFormat, BibtexEntry entry,
- BibtexDatabase database) {
- fieldAndFormat = stripBrackets(fieldAndFormat);
- int colon = fieldAndFormat.indexOf(':');
- String beforeColon, afterColon;
- if (colon == -1) {
- beforeColon = fieldAndFormat;
- afterColon = null;
- } else {
- beforeColon = fieldAndFormat.substring(0, colon);
- afterColon = fieldAndFormat.substring(colon + 1);
- }
- beforeColon = beforeColon.trim();
- if (beforeColon.length() == 0) {
- return null;
- }
- String fieldValue = BibtexDatabase.getResolvedField(beforeColon, entry, database);
- // If no field value was found, try to interpret it as a key generator field marker:
- if (fieldValue == null)
- fieldValue = LabelPatternUtil.makeLabel(entry, beforeColon);
- if (fieldValue == null)
- return null;
- if (afterColon == null || afterColon.length() == 0)
- return fieldValue;
- String[] parts = afterColon.split(":");
- fieldValue = LabelPatternUtil.applyModifiers(fieldValue, parts, 0);
-
- return fieldValue;
- }
- /**
- * Convenience function for absolute search.
- *
- * Uses findFile(BibtexEntry, BibtexDatabase, (String)null, String, false).
- */
- public static String findFile(BibtexEntry entry, BibtexDatabase database, String file) {
- return findFile(entry, database, (String) null, file, false);
- }
- /**
- * Internal Version of findFile, which also accepts a current directory to
- * base the search on.
- *
- */
- public static String findFile(BibtexEntry entry, BibtexDatabase database, String directory,
- String file, boolean relative) {
- File root;
- if (directory == null) {
- root = new File(".");
- } else {
- root = new File(directory);
- }
- if (!root.exists())
- return null;
- String found = findFile(entry, database, root, file);
- if (directory == null || !relative) {
- return found;
- }
- if (found != null) {
- try {
- /**
- * [ 1601651 ] PDF subdirectory - missing first character
- *
- * http://sourceforge.net/tracker/index.php?func=detail&aid=1601651&group_id=92314&atid=600306
- */
- // Changed by M. Alver 2007.01.04:
- // Remove first character if it is a directory separator character:
- String tmp = found.substring(root.getCanonicalPath().length());
- if ((tmp.length() > 1) && (tmp.charAt(0) == File.separatorChar))
- tmp = tmp.substring(1);
- return tmp;
- //return found.substring(root.getCanonicalPath().length());
- } catch (IOException e) {
- return null;
- }
- }
- return null;
- }
- /**
- * The actual work-horse. Will find absolute filepaths starting from the
- * given directory using the given regular expression string for search.
- */
- protected static String findFile(BibtexEntry entry, BibtexDatabase database, File directory,
- String file) {
- if (file.startsWith("/")) {
- directory = new File(".");
- file = file.substring(1);
- }
- // Escape handling...
- Matcher m = Pattern.compile("([^\\\\])\\\\([^\\\\])").matcher(file);
- StringBuffer s = new StringBuffer();
- while (m.find()) {
- m.appendReplacement(s, m.group(1) + "/" + m.group(2));
- }
- m.appendTail(s);
- file = s.toString();
- String[] fileParts = file.split("/");
- if (fileParts.length == 0)
- return null;
- if (fileParts.length > 1) {
- for (int i = 0; i < fileParts.length - 1; i++) {
- String dirToProcess = fileParts[i];
- dirToProcess = expandBrackets(dirToProcess, entry, database);
- if (dirToProcess.matches("^.:$")) { // Windows Drive Letter
- directory = new File(dirToProcess + "/");
- continue;
- }
- if (dirToProcess.equals(".")) { // Stay in current directory
- continue;
- }
- if (dirToProcess.equals("..")) {
- directory = new File(directory.getParent());
- continue;
- }
- if (dirToProcess.equals("*")) { // Do for all direct subdirs
- File[] subDirs = directory.listFiles();
- if (subDirs == null)
- return null; // No permission?
- String restOfFileString = join(fileParts, "/", i + 1, fileParts.length);
- for (File subDir : subDirs) {
- if (subDir.isDirectory()) {
- String result = findFile(entry, database, subDir,
- restOfFileString);
- if (result != null)
- return result;
- }
- }
- return null;
- }
- // Do for all direct and indirect subdirs
- if (dirToProcess.equals("**")) {
- List<File> toDo = new LinkedList<File>();
- toDo.add(directory);
- String restOfFileString = join(fileParts, "/", i + 1, fileParts.length);
- // Before checking the subdirs, we first check the current
- // dir
- String result = findFile(entry, database, directory, restOfFileString);
- if (result != null)
- return result;
- while (!toDo.isEmpty()) {
- // Get all subdirs of each of the elements found in toDo
- File[] subDirs = toDo.remove(0).listFiles();
- if (subDirs == null) // No permission?
- continue;
- toDo.addAll(Arrays.asList(subDirs));
- for (File subDir : subDirs) {
- if (!subDir.isDirectory())
- continue;
- result = findFile(entry, database, subDir, restOfFileString);
- if (result != null)
- return result;
- }
- }
- // We already did the currentDirectory
- return null;
- }
- final Pattern toMatch = Pattern
- .compile(dirToProcess.replaceAll("\\\\\\\\", "\\\\"));
- File[] matches = directory.listFiles(new FilenameFilter() {
- public boolean accept(File arg0, String arg1) {
- return toMatch.matcher(arg1).matches();
- }
- });
- if (matches == null || matches.length == 0)
- return null;
- directory = matches[0];
- if (!directory.exists())
- return null;
- } // End process directory information
- }
- // Last step check if the given file can be found in this directory
- String filenameToLookFor = expandBrackets(fileParts[fileParts.length - 1], entry, database);
- final Pattern toMatch = Pattern.compile("^"
- + filenameToLookFor.replaceAll("\\\\\\\\", "\\\\") + "$");
- File[] matches = directory.listFiles(new FilenameFilter() {
- public boolean accept(File arg0, String arg1) {
- return toMatch.matcher(arg1).matches();
- }
- });
- if (matches == null || matches.length == 0)
- return null;
- try {
- return matches[0].getCanonicalPath();
- } catch (IOException e) {
- return null;
- }
- }
- static Pattern squareBracketsPattern = Pattern.compile("\\[.*?\\]");
- /**
- * Takes a string that contains bracketed expression and expands each of
- * these using getFieldAndFormat.
- *
- * Unknown Bracket expressions are silently dropped.
- *
- * @param bracketString
- * @param entry
- * @param database
- * @return
- */
- public static String expandBrackets(String bracketString, BibtexEntry entry,
- BibtexDatabase database) {
- Matcher m = squareBracketsPattern.matcher(bracketString);
- StringBuffer s = new StringBuffer();
- while (m.find()) {
- String replacement = getFieldAndFormat(m.group(), entry, database);
- if (replacement == null)
- replacement = "";
- m.appendReplacement(s, replacement);
- }
- m.appendTail(s);
- return s.toString();
- }
- /**
- * Concatenate all strings in the array from index 'from' to 'to' (excluding
- * to) with the given separator.
- *
- * Example:
- *
- * String[] s = "ab/cd/ed".split("/"); join(s, "\\", 0, s.length) ->
- * "ab\\cd\\ed"
- *
- * @param strings
- * @param separator
- * @param from
- * @param to
- * Excluding strings[to]
- * @return
- */
- public static String join(String[] strings, String separator, int from, int to) {
- if (strings.length == 0 || from >= to)
- return "";
-
- from = Math.max(from, 0);
- to = Math.min(strings.length, to);
- StringBuffer sb = new StringBuffer();
- for (int i = from; i < to - 1; i++) {
- sb.append(strings[i]).append(separator);
- }
- return sb.append(strings[to - 1]).toString();
- }
- public static String join(String[] strings, String separator) {
- return join(strings, separator, 0, strings.length);
- }
- /**
- * Converts a relative filename to an absolute one, if necessary. Returns
- * null if the file does not exist.<br/>
- *
- * Uses <ul>
- * <li>the default directory associated with the extension of the file</li>
- * <li>the standard file directory</li>
- * <li>the directory of the bib file</li>
- * </ul>
- *
- * @param metaData
- * The MetaData for the database this file belongs to.
- * @param name
- * The file name, may also be a relative path to the file
- */
- public static File expandFilename(final MetaData metaData, String name) {
- int pos = name.lastIndexOf('.');
- String extension = ((pos >= 0) && (pos < name.length() - 1)) ? name
- .substring(pos + 1).trim().toLowerCase() : null;
- // Find the default directory for this field type, if any:
- String[] dir = metaData.getFileDirectory(extension);
- // Include the standard "file" directory:
- String[] fileDir = metaData.getFileDirectory(GUIGlobals.FILE_FIELD);
- // Include the directory of the bib file:
- ArrayList<String> al = new ArrayList<String>();
- for (String aDir : dir)
- if (!al.contains(aDir))
- al.add(aDir);
- for (String aFileDir : fileDir)
- if (!al.contains(aFileDir))
- al.add(aFileDir);
- String[] dirs = al.toArray(new String[al.size()]);
- return expandFilename(name, dirs);
- }
- /**
- * Converts a relative filename to an absolute one, if necessary. Returns
- * null if the file does not exist.
- *
- * Will look in each of the given dirs starting from the beginning and
- * returning the first found file to match if any.
- */
- public static File expandFilename(String name, String[] dir) {
- for (String aDir : dir) {
- if (aDir != null) {
- File result = expandFilename(name, aDir);
- if (result != null) {
- return result;
- }
- }
- }
- return null;
- }
-
- /**
- * Converts a relative filename to an absolute one, if necessary. Returns
- * null if the file does not exist.
- */
- public static File expandFilename(String name, String dir) {
- File file = null;
- if (name == null || name.length() == 0)
- return null;
- else {
- file = new File(name);
- }
- if (!file.exists() && (dir != null)) {
- if (dir.endsWith(System.getProperty("file.separator")))
- name = dir + name;
- else
- name = dir + System.getProperty("file.separator") + name;
- // System.out.println("expanded to: "+name);
- // if (name.startsWith("ftp"))
- file = new File(name);
- if (file.exists())
- return file;
- // Ok, try to fix / and \ problems:
- if (Globals.ON_WIN) {
- // workaround for catching Java bug in regexp replacer
- // and, why, why, why ... I don't get it - wegner 2006/01/22
- try {
- name = name.replaceAll("/", "\\\\");
- } catch (java.lang.StringIndexOutOfBoundsException exc) {
- System.err
- .println("An internal Java error was caused by the entry " +
- "\"" + name + "\"");
- }
- } else
- name = name.replaceAll("\\\\", "/");
- // System.out.println("expandFilename: "+name);
- file = new File(name);
- if (!file.exists())
- file = null;
- }
- return file;
- }
-
- /**
- * Converts an absolute filename to a relative one, if necessary.
- * Returns the parameter fileName itself if no shortening is possible
- *
- * This method works correctly only if dirs are sorted decent in their length
- * i.e. /home/user/literature/important before /home/user/literature
- *
- * @param fileName the file name to be shortened
- * @param dirs directories to check.
- */
- public static File shortenFileName(File fileName, String[] dirs) {
- if (fileName == null || fileName.length() == 0)
- return fileName;
- if (!fileName.isAbsolute() || (dirs == null))
- return fileName;
-
- for (String dir: dirs) {
- if (dir!=null) {
- File result = shortenFileName(fileName, dir);
- if ((result != null) && (!result.equals(fileName)))
- return result;
- }
- }
- return fileName;
- }
- public static File shortenFileName(File fileName, String dir) {
- if (fileName == null || fileName.length() == 0)
- return fileName;
- if (!fileName.isAbsolute() || dir == null)
- return fileName;
- String longName;
- if (Globals.ON_WIN) {
- // case-insensitive matching on Windows
- longName = fileName.toString().toLowerCase();
- dir = dir.toLowerCase();
- } else {
- longName = fileName.toString();
- }
-
- if (!dir.endsWith(System.getProperty("file.separator")))
- dir = dir.concat(System.getProperty("file.separator"));
- if (longName.startsWith(dir)) {
- // result is based on original name, not on lower-cased name
- String newName = fileName.toString().substring(dir.length());
- return new File(newName);
- } else {
- return fileName;
- }
- }
- private static String findInDir(String key, String dir, OpenFileFilter off, int count) {
- if (count > 20)
- return null; // Make sure an infinite loop doesn't occur.
- File f = new File(dir);
- File[] all = f.listFiles();
- if (all == null)
- return null; // An error occured. We may not have
- // permission to list the files.
- int numFiles = all.length;
- for (File curFile : all) {
- if (curFile.isFile()) {
- String name = curFile.getName();
- if (name.startsWith(key + ".") && off.accept(name))
- return curFile.getPath();
- } else if (curFile.isDirectory()) {
- String found = findInDir(key, curFile.getPath(), off, count + 1);
- if (found != null)
- return found;
- }
- }
- return null;
- }
- /**
- * This methods assures all words in the given entry are recorded in their
- * respective Completers, if any.
- */
- public static void updateCompletersForEntry(HashMap<String, AbstractAutoCompleter> autoCompleters, BibtexEntry bibtexEntry) {
- for (Map.Entry<String, AbstractAutoCompleter> entry : autoCompleters.entrySet()){
- AbstractAutoCompleter comp = entry.getValue();
- comp.addBibtexEntry(bibtexEntry);
- }
- }
- /**
- * Sets empty or non-existing owner fields of bibtex entries inside a List
- * to a specified default value. Timestamp field is also set. Preferences
- * are checked to see if these options are enabled.
- *
- * @param bibs
- * List of bibtex entries
- */
- public static void setAutomaticFields(Collection<BibtexEntry> bibs,
- boolean overwriteOwner, boolean overwriteTimestamp, boolean markEntries) {
- String timeStampField = Globals.prefs.get("timeStampField");
- String defaultOwner = Globals.prefs.get("defaultOwner");
- String timestamp = easyDateFormat();
- boolean globalSetOwner = Globals.prefs.getBoolean("useOwner"),
- globalSetTimeStamp = Globals.prefs.getBoolean("useTimeStamp");
- // Do not need to do anything if all options are disabled
- if (!(globalSetOwner || globalSetTimeStamp || markEntries))
- return;
- // Iterate through all entries
- for (BibtexEntry curEntry : bibs){
- boolean setOwner = globalSetOwner &&
- (overwriteOwner || (curEntry.getField(BibtexFields.OWNER)==null));
- boolean setTimeStamp = globalSetTimeStamp &&
- (overwriteTimestamp || (curEntry.getField(timeStampField)==null));
- setAutomaticFields(curEntry, setOwner, defaultOwner, setTimeStamp, timeStampField,
- timestamp);
- if (markEntries)
- Util.markEntry(curEntry, IMPORT_MARK_LEVEL, false, new NamedCompound(""));
- }
- }
- /**
- * Sets empty or non-existing owner fields of a bibtex entry to a specified
- * default value. Timestamp field is also set. Preferences are checked to
- * see if these options are enabled.
- *
- * @param entry
- * The entry to set fields for.
- * @param overwriteOwner
- * Indicates whether owner should be set if it is already set.
- * @param overwriteTimestamp
- * Indicates whether timestamp should be set if it is already set.
- */
- public static void setAutomaticFields(BibtexEntry entry, boolean overwriteOwner,
- boolean overwriteTimestamp) {
- String defaultOwner = Globals.prefs.get("defaultOwner");
- String timestamp = easyDateFormat();
- String timeStampField = Globals.prefs.get("timeStampField");
- boolean setOwner = Globals.prefs.getBoolean("useOwner") &&
- (overwriteOwner || (entry.getField(BibtexFields.OWNER)==null));
- boolean setTimeStamp = Globals.prefs.getBoolean("useTimeStamp") &&
- (overwriteTimestamp || (entry.getField(timeStampField)==null));
- setAutomaticFields(entry, setOwner, defaultOwner, setTimeStamp, timeStampField, timestamp);
- }
- private static void setAutomaticFields(BibtexEntry entry, boolean setOwner, String owner,
- boolean setTimeStamp, String timeStampField, String timeStamp) {
- // Set owner field if this option is enabled:
- if (setOwner) {
- // No or empty owner field?
- // if (entry.getField(Globals.OWNER) == null
- // || ((String) entry.getField(Globals.OWNER)).length() == 0) {
- // Set owner field to default value
- entry.setField(BibtexFields.OWNER, owner);
- // }
- }
- if (setTimeStamp)
- entry.setField(timeStampField, timeStamp);
- }
- /**
- * Copies a file.
- *
- * @param source
- * File Source file
- * @param dest
- * File Destination file
- * @param deleteIfExists
- * boolean Determines whether the copy goes on even if the file
- * exists.
- * @throws IOException
- * @return boolean Whether the copy succeeded, or was stopped due to the
- * file already existing.
- */
- public static boolean copyFile(File source, File dest, boolean deleteIfExists)
- throws IOException {
- BufferedInputStream in = null;
- BufferedOutputStream out = null;
- try {
- // Check if the file already exists.
- if (dest.exists()) {
- if (!deleteIfExists)
- return false;
- // else dest.delete();
- }
- in = new BufferedInputStream(new FileInputStream(source));
- out = new BufferedOutputStream(new FileOutputStream(dest));
- int el;
- // int tell = 0;
- while ((el = in.read()) >= 0) {
- out.write(el);
- }
- } finally {
- if (out != null) {
- out.flush();
- out.close();
- }
- if (in != null) {
- in.close();
- }
- }
- return true;
- }
- /**
- * This method is called at startup, and makes necessary adaptations to
- * preferences for users from an earlier version of Jabref.
- */
- public static void performCompatibilityUpdate() {
- // Make sure "abstract" is not in General fields, because
- // Jabref 1.55 moves the abstract to its own tab.
- String genFields = Globals.prefs.get("generalFields");
- // pr(genFields+"\t"+genFields.indexOf("abstract"));
- if (genFields.contains("abstract")) {
- // pr(genFields+"\t"+genFields.indexOf("abstract"));
- String newGen;
- if (genFields.equals("abstract"))
- newGen = "";
- else if (genFields.contains(";abstract;")) {
- newGen = genFields.replaceAll(";abstract;", ";");
- } else if (genFields.indexOf("abstract;") == 0) {
- newGen = genFields.replaceAll("abstract;", "");
- } else if (genFields.indexOf(";abstract") == genFields.length() - 9) {
- newGen = genFields.replaceAll(";abstract", "");
- } else
- newGen = genFields;
- // pr(newGen);
- Globals.prefs.put("generalFields", newGen);
- }
- }
- /**
- * Collect file links from the given set of fields, and add them to the list contained
- * in the field GUIGlobals.FILE_FIELD.
- * @param database The database to modify.
- * @param fields The fields to find links in.
- * @return A CompoundEdit specifying the undo operation for the whole operation.
- */
- public static NamedCompound upgradePdfPsToFile(BibtexDatabase database, String[] fields) {
- return upgradePdfPsToFile(database.getEntryMap().values(), fields);
- }
- /**
- * Collect file links from the given set of fields, and add them to the list contained
- * in the field GUIGlobals.FILE_FIELD.
- * @param entries The entries to modify.
- * @param fields The fields to find links in.
- * @return A CompoundEdit specifying the undo operation for the whole operation.
- */
- public static NamedCompound upgradePdfPsToFile(Collection<BibtexEntry> entries, String[] fields) {
- NamedCompound ce = new NamedCompound(Globals.lang("Move external links to 'file' field"));
-
- for (BibtexEntry entry : entries){
- FileListTableModel tableModel = new FileListTableModel();
- // If there are already links in the file field, keep those on top:
- String oldFileContent = entry.getField(GUIGlobals.FILE_FIELD);
- if (oldFileContent != null) {
- tableModel.setContent(oldFileContent);
- }
- int oldRowCount = tableModel.getRowCount();
- for (String field : fields) {
- String o = entry.getField(field);
- if (o != null) {
- String s = o;
- if (s.trim().length() > 0) {
- File f = new File(s);
- FileListEntry flEntry = new FileListEntry(f.getName(), s,
- Globals.prefs.getExternalFileTypeByExt(field));
- tableModel.addEntry(tableModel.getRowCount(), flEntry);
- entry.clearField(field);
- ce.addEdit(new UndoableFieldChange(entry, field, o, null));
- }
- }
- }
- if (tableModel.getRowCount() != oldRowCount) {
- String newValue = tableModel.getStringRepresentation();
- entry.setField(GUIGlobals.FILE_FIELD, newValue);
- ce.addEdit(new UndoableFieldChange(entry, GUIGlobals.FILE_FIELD, oldFileContent, newValue));
- }
- }
- ce.end();
- return ce;
- }
- // -------------------------------------------------------------------------------
- /**
- * extends the filename with a default Extension, if no Extension '.x' could
- * be found
- */
- public static String getCorrectFileName(String orgName, String defaultExtension) {
- if (orgName == null)
- return "";
- String back = orgName;
- int t = orgName.indexOf(".", 1); // hidden files Linux/Unix (?)
- if (t < 1)
- back = back + "." + defaultExtension;
- return back;
- }
- /**
- * Quotes each and every character, e.g. '!' as !. Used for verbatim
- * display of arbitrary strings that may contain HTML entities.
- */
- public static String quoteForHTML(String s) {
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < s.length(); ++i) {
- sb.append("&#").append((int) s.charAt(i)).append(";");
- }
- return sb.toString();
- }
- public static String quote(String s, String specials, char quoteChar) {
- return quote(s, specials, quoteChar, 0);
- }
- /**
- * Quote special characters.
- *
- * @param s
- * The String which may contain special characters.
- * @param specials
- * A String containing all special characters except the quoting
- * character itself, which is automatically quoted.
- * @param quoteChar
- * The quoting character.
- * @param linewrap
- * The number of characters after which a linebreak is inserted
- * (this linebreak is undone by unquote()). Set to 0 to disable.
- * @return A String with every special character (including the quoting
- * character itself) quoted.
- */
- public static String quote(String s, String specials, char quoteChar, int linewrap) {
- StringBuffer sb = new StringBuffer();
- char c;
- int linelength = 0;
- boolean isSpecial;
- for (int i = 0; i < s.length(); ++i) {
- c = s.charAt(i);
- isSpecial = specials.indexOf(c) >= 0 || c == quoteChar;
- // linebreak?
- if (linewrap > 0
- && (++linelength >= linewrap || (isSpecial && linelength >= linewrap - 1))) {
- sb.append(quoteChar);
- sb.append('\n');
- linelength = 0;
- }
- if (isSpecial) {
- sb.append(quoteChar);
- ++linelength;
- }
- sb.append(c);
- }
- return sb.toString();
- }
- /**
- * Unquote special characters.
- *
- * @param s
- * The String which may contain quoted special characters.
- * @param quoteChar
- * The quoting character.
- * @return A String with all quoted characters unquoted.
- */
- public static String unquote(String s, char quoteChar) {
- StringBuffer sb = new StringBuffer();
- char c;
- boolean quoted = false;
- for (int i = 0; i < s.length(); ++i) {
- c = s.charAt(i);
- if (quoted) { // append literally...
- if (c != '\n') // ...unless newline
- sb.append(c);
- quoted = false;
- } else if (c != quoteChar) {
- sb.append(c);
- } else { // quote char
- quoted = true;
- }
- }
- return sb.toString();
- }
- /**
- * Quote all regular expression meta characters in s, in order to search for
- * s literally.
- */
- public static String quoteMeta(String s) {
- // work around a bug: trailing backslashes have to be quoted
- // individually
- int i = s.length() - 1;
- StringBuffer bs = new StringBuffer("");
- while ((i >= 0) && (s.charAt(i) == '\\')) {
- --i;
- bs.append("\\\\");
- }
- s = s.substring(0, i + 1);
- return "\\Q" + s.replaceAll("\\\\E", "\\\\E\\\\\\\\E\\\\Q") + "\\E" + bs.toString();
- }
- /**
- * This method "tidies" up e.g. a keyword string, by alphabetizing the words
- * and removing all duplicates.
- *
- * Currently not used anywhere
- */
- public static String sortWordsAndRemoveDuplicates(String text) {
- ArrayList<String> words = getSeparatedKeywords(text);
- // by adding the words to a set, they are automatically sorted
- TreeSet<String> set = new TreeSet<String>(words);
- StringBuffer sb = new StringBuffer();
- for (String aSet : set) {
- sb.append(aSet);
- sb.append(", ");
- }
- if (sb.length() > 2)
- sb.delete(sb.length() - 2, sb.length());
- String result = sb.toString();
- return result.length() > 2 ? result : "";
- }
- /**
- * Warns the user of undesired side effects of an explicit
- * assignment/removal of entries to/from this group. Currently there are
- * four types of groups: AllEntriesGroup, SearchGroup - do not support
- * explicit assignment. ExplicitGroup - never modifies entries. KeywordGroup -
- * only this modifies entries upon assignment/removal. Modifications are
- * acceptable unless they affect a standard field (such as "author") besides
- * the "keywords" field.
- *
- * @param parent
- * The Component used as a parent when displaying a confirmation
- * dialog.
- * @return true if the assignment has no undesired side effects, or the user
- * chose to perform it anyway. false otherwise (this indicates that
- * the user has aborted the assignment).
- */
- public static boolean warnAssignmentSideEffects(AbstractGroup[] groups, BibtexEntry[] entries,
- BibtexDatabase db, Component parent) {
- Vector<String> affectedFields = new Vector<String>();
- for (AbstractGroup group : groups) {
- if (group instanceof KeywordGroup) {
- KeywordGroup kg = (KeywordGroup) group;
- String field = kg.getSearchField().toLowerCase();
- if (field.equals("keywords"))
- continue; // this is not undesired
- for (int i = 0, len = BibtexFields.numberOfPublicFields(); i < len; ++i) {
- if (field.equals(BibtexFields.getFieldName(i))) {
- affectedFields.add(field);
- break;
- }
- }
- }
- }
- if (affectedFields.size() == 0)
- return true; // no side effects
- // show a warning, then return
- StringBuffer message = // JZTODO lyrics...
- new StringBuffer("This action will modify the following field(s)\n"
- + "in at least one entry each:\n");
- for (int i = 0; i < affectedFields.size(); ++i)
- message.append(affectedFields.elementAt(i)).append("\n");
- message.append("This could cause undesired changes to "
- + "your entries, so it is\nrecommended that you change the grouping field "
- + "in your group\ndefinition to \"keywords\" or a non-standard name."
- + "\n\nDo you still want to continue?");
- int choice = JOptionPane.showConfirmDialog(parent, message, Globals.lang("Warning"),
- JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
- return choice != JOptionPane.NO_OPTION;
- // if (groups instanceof KeywordGroup) {
- // KeywordGroup kg = (KeywordGroup) groups;
- // String field = kg.getSearchField().toLowerCase();
- // if (field.equals("keywords"))
- // return true; // this is not undesired
- // for (int i = 0; i < GUIGlobals.ALL_FIELDS.length; ++i) {
- // if (field.equals(GUIGlobals.ALL_FIELDS[i])) {
- // // show a warning, then return
- // String message = Globals // JZTODO lyrics...
- // .lang(
- // "This action will modify the \"%0\" field "
- // + "of your entries.\nThis could cause undesired changes to "
- // + "your entries, so it is\nrecommended that you change the grouping
- // field "
- // + "in your group\ndefinition to \"keywords\" or a non-standard name."
- // + "\n\nDo you still want to continue?",
- // field);
- // int choice = JOptionPane.showConfirmDialog(parent, message,
- // Globals.lang("Warning"), JOptionPane.YES_NO_OPTION,
- // JOptionPane.WARNING_MESSAGE);
- // return choice != JOptionPane.NO_OPTION;
- // }
- // }
- // }
- // return true; // found no side effects
- }
- // ========================================================
- // lot of abreviations in medline
- // PKC etc convert to {PKC} ...
- // ========================================================
- static Pattern titleCapitalPattern = Pattern.compile("[A-Z]+");
- /**
- * Wrap all uppercase letters, or sequences of uppercase letters, in curly
- * braces. Ignore letters within a pair of # character, as these are part of
- * a string label that should not be modified.
- *
- * @param s
- * The string to modify.
- * @return The resulting string after wrapping capitals.
- */
- public static String putBracesAroundCapitals(String s) {
- boolean inString = false, isBracing = false, escaped = false;
- int inBrace = 0;
- StringBuffer buf = new StringBuffer();
- for (int i = 0; i < s.length(); i++) {
- // Update variables based on special characters:
- int c = s.charAt(i);
- if (c == '{')
- inBrace++;
- else if (c == '}')
- inBrace--;
- else if (!escaped && (c == '#'))
- inString = !inString;
- // See if we should start bracing:
- if ((inBrace == 0) && !isBracing && !inString && Character.isLetter((char) c)
- && Character.isUpperCase((char) c)) {
- buf.append('{');
- isBracing = true;
- }
- // See if we should close a brace set:
- if (isBracing && !(Character.isLetter((char) c) && Character.isUpperCase((char) c))) {
- buf.append('}');
- isBracing = false;
- }
- // Add the current character:
- buf.append((char) c);
- // Check if we are entering an escape sequence:
- escaped = (c == '\\') && !escaped;
- }
- // Check if we have an unclosed brace:
- if (isBracing)
- buf.append('}');
- return buf.toString();
- /*
- * if (s.length() == 0) return s; // Protect against ArrayIndexOutOf....
- * StringBuffer buf = new StringBuffer();
- *
- * Matcher mcr = titleCapitalPattern.matcher(s.substring(1)); while
- * (mcr.find()) { String replaceStr = mcr.group();
- * mcr.appendReplacement(buf, "{" + replaceStr + "}"); }
- * mcr.appendTail(buf); return s.substring(0, 1) + buf.toString();
- */
- }
- static Pattern bracedTitleCapitalPattern = Pattern.compile("\\{[A-Z]+\\}");
- /**
- * This method looks for occurences of capital letters enclosed in an
- * arbitrary number of pairs of braces, e.g. "{AB}" or "{{T}}". All of these
- * pairs of braces are removed.
- *
- * @param s
- * The String to analyze.
- * @return A new String with braces removed.
- */
- public static String removeBracesAroundCapitals(String s) {
- String previous = s;
- while ((s = removeSingleBracesAroundCapitals(s)).length() < previous.length()) {
- previous = s;
- }
- return s;
- }
- /**
- * This method looks for occurences of capital letters enclosed in one pair
- * of braces, e.g. "{AB}". All these are replaced by only the capitals in
- * between the braces.
- *
- * @param s
- * The String to analyze.
- * @return A new String with braces removed.
- */
- public static String removeSingleBracesAroundCapitals(String s) {
- Matcher mcr = bracedTitleCapitalPattern.matcher(s);
- StringBuffer buf = new StringBuffer();
- while (mcr.find()) {
- String replaceStr = mcr.group();
- mcr.appendReplacement(buf, replaceStr.substring(1, replaceStr.length() - 1));
- }
- mcr.appendTail(buf);
- return buf.toString();
- }
- /**
- * This method looks up what kind of external binding is used for the given
- * field, and constructs on OpenFileFilter suitable for browsing for an
- * external file.
- *
- * @param fieldName
- * The BibTeX field in question.
- * @return The file filter.
- */
- public static OpenFileFilter getFileFilterForField(String fieldName) {
- String s = BibtexFields.getFieldExtras(fieldName);
- final String ext = "." + fieldName.toLowerCase();
- final OpenFileFilter off;
- if (s.equals("browseDocZip"))
- off = new OpenFileFilter(new String[] { ext, ext + ".gz", ext + ".bz2" });
- else
- off = new OpenFileFilter(new String[] { ext });
- return off;
- }
- /**
- * This method can be used to display a "rich" error dialog which offers the
- * entire stack trace for an exception.
- *
- * @param parent
- * @param e
- */
- public static void showQuickErrorDialog(JFrame parent, String title, Exception e) {
- // create and configure a text area - fill it with exception text.
- final JPanel pan = new JPanel(), details = new JPanel();
- final CardLayout crd = new CardLayout();
- pan.setLayout(crd);
- final JTextArea textArea = new JTextArea();
- textArea.setFont(new Font("Sans-Serif", Font.PLAIN, 10));
- textArea.setEditable(false);
- StringWriter writer = new StringWriter();
- e.printStackTrace(new PrintWriter(writer));
- textArea.setText(writer.toString());
- JLabel lab = new JLabel(e.getMessage());
- JButton flip = new JButton(Globals.lang("Details"));
- FormLayout layout = new FormLayout("left:pref", "");
- DefaultFormBuilder builder = new DefaultFormBuilder(layout);
- builder.append(lab);
- builder.nextLine();
- builder.append(Box.createVerticalGlue());
- builder.nextLine();
- builder.append(flip);
- final JPanel simple = builder.getPanel();
- // stuff it in a scrollpane with a controlled size.
- JScrollPane scrollPane = new JScrollPane(textArea);
- scrollPane.setPreferredSize(new Dimension(350, 150));
- details.setLayout(new BorderLayout());
- details.add(scrollPane, BorderLayout.CENTER);
- flip.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent event) {
- crd.show(pan, "details");
- }
- });
- pan.add(simple, "simple");
- pan.add(details, "details");
- // pass the scrollpane to the joptionpane.
- JOptionPane.showMessageDialog(parent, pan, title, JOptionPane.ERROR_MESSAGE);
- }
- public static String wrapHTML(String s, final int lineWidth) {
- StringBuffer sb = new StringBuffer();
- StringTokenizer tok = new StringTokenizer(s);
- int charsLeft = lineWidth;
- while (tok.hasMoreTokens()) {
- String word = tok.nextToken();
- if (charsLeft == lineWidth) { // fresh line
- sb.append(word);
- charsLeft -= word.length();
- if (charsLeft <= 0) {
- sb.append("<br>\n");
- charsLeft = lineWidth;
- }
- } else { // continue previous line
- if (charsLeft < word.length() + 1) {
- sb.append("<br>\n");
- sb.append(word);
- if (word.length() >= lineWidth - 1) {
- sb.append("<br>\n");
- charsLeft = lineWidth;
- } else {
- sb.append(" ");
- charsLeft = lineWidth - word.length() - 1;
- }
- } else {
- sb.append(' ').append(word);
- charsLeft -= word.length() + 1;
- }
- }
- }
- return sb.toString();
- }
- /**
- * Creates a String containing the current date (and possibly time),
- * formatted according to the format set in preferences under the key
- * "timeStampFormat".
- *
- * @return The date string.
- */
- public static String easyDateFormat() {
- // Date today = new Date();
- return easyDateFormat(new Date());
- }
- /**
- * Creates a readable Date string from the parameter date. The format is set
- * in preferences under the key "timeStampFormat".
- *
- * @return The formatted date string.
- */
- public static String easyDateFormat(Date date) {
- // first use, create an instance
- if (dateFormatter == null) {
- String format = Globals.prefs.get("timeStampFormat");
- dateFormatter = new SimpleDateFormat(format);
- }
- return dateFormatter.format(date);
- }
- public static void markEntry(BibtexEntry be, int markIncrement, boolean increment, NamedCompound ce) {
- Object o = be.getField(BibtexFields.MARKED);
- int prevMarkLevel = 0;
- String newValue = null;
- if (o != null) {
- String s = o.toString();
- int index = s.indexOf(Globals.prefs.WRAPPED_USERNAME);
- if (index >= 0) {
- // Already marked 1 for this user.
- prevMarkLevel = 1;
- newValue = s.substring(0, index)
- + s.substring(index+Globals.prefs.WRAPPED_USERNAME.length())
- + Globals.prefs.WRAPPED_USERNAME.substring(0,
- Globals.prefs.WRAPPED_USERNAME.length()-1)+":"+
- (increment ? Math.min(MAX_MARKING_LEVEL, prevMarkLevel+markIncrement)
- : markIncrement)+"]";
- }
- else {
- Matcher m = markNumberPattern.matcher(s);
- if (m.find()) {
- try {
- prevMarkLevel = Integer.parseInt(m.group(1));
- newValue = s.substring(0, m.start(1))+
- (increment ? Math.min(MAX_MARKING_LEVEL, prevMarkLevel+markIncrement)
- : markIncrement)+
- s.substring(m.end(1));
- } catch (NumberFormatException ex) {
- // Do nothing.
- }
- }
- }
- }
- if (newValue == null)
- newValue = Globals.prefs.WRAPPED_USERNAME.substring(0,
- Globals.prefs.WRAPPED_USERNAME.length()-1)+":"+markIncrement+"]";
- ce.addEdit(new UndoableFieldChange(be, BibtexFields.MARKED, be
- .getField(BibtexFields.MARKED), newValue));
- be.setField(BibtexFields.MARKED, newValue);
- }
- public static void unmarkEntry(BibtexEntry be, boolean onlyMaxLevel,
- BibtexDatabase database, NamedCompound ce) {
- Object o = be.getField(BibtexFields.MARKED);
- if (o != null) {
- String s = o.toString();
- if (s.equals("0")) {
- if (!onlyMaxLevel) {
- unmarkOldStyle(be, database, ce);
- }
- return;
- }
- String newValue = null;
- int index = s.indexOf(Globals.prefs.WRAPPED_USERNAME);
- if (index >= 0) {
- // Marked 1 for this user.
- if (!onlyMaxLevel)
- newValue = s.substring(0, index)
- + s.substring(index+Globals.prefs.WRAPPED_USERNAME.length());
- else return;
- }
- else {
- Matcher m = markNumberPattern.matcher(s);
- if (m.find()) {
- try {
- int prevMarkLevel = Integer.parseInt(m.group(1));
- if (!onlyMaxLevel || (prevMarkLevel == MARK_COLOR_LEVELS)) {
- if (prevMarkLevel > 1)
- newValue = s.substring(0, m.start(1))+
- s.substring(m.end(1));
- else {
- String toRemove = Globals.prefs.WRAPPED_USERNAME.substring(0,
- Globals.prefs.WRAPPED_USERNAME.length()-1)+":1]";
- index = s.indexOf(toRemove);
- if (index >= 0) {
- newValue = s.substring(0, index)
- + s.substring(index+toRemove.length());
- }
- }
- }
- else return;
- } catch (NumberFormatException ex) {
- // Do nothing.
- }
- }
- }
- /*int piv = 0, hit;
- StringBuffer sb = new StringBuffer();
- while ((hit = s.indexOf(G047749118118
- 1110lobals.prefs.WRAPPED_USERNAME, piv)) >= 0) {
- if (hit > 0)
- sb.append(s.substring(piv, hit));
- piv = hit + Globals.prefs.WRAPPED_USERNAME.length();
- }
- if (piv < s.length() - 1) {
- sb.append(s.substring(piv));
- }
- String newVal = sb.length() > 0 ? sb.toString() : null;*/
- ce.addEdit(new UndoableFieldChange(be, BibtexFields.MARKED, be
- .getField(BibtexFields.MARKED), newValue));
- be.setField(BibtexFields.MARKED, newValue);
- }
- }
- /**
- * An entry is marked with a "0", not in the new style with user names. We
- * want to unmark it as transparently as possible. Since this shouldn't
- * happen too often, we do it by scanning the "owner" fields of the entire
- * database, collecting all user names. We then mark the entry for all users
- * except the current one. Thus only the user who unmarks will see that it
- * is unmarked, and we get rid of the old-style marking.
- *
- * @param be
- * @param ce
- */
- private static void unmarkOldStyle(BibtexEntry be, BibtexDatabase database, NamedCompound ce) {
- TreeSet<Object> owners = new TreeSet<Object>();
- for (BibtexEntry entry : database.getEntries()){
- Object o = entry.getField(BibtexFields.OWNER);
- if (o != null)
- owners.add(o);
- // System.out.println("Owner: "+entry.getField(Globals.OWNER));
- }
- owners.remove(Globals.prefs.get("defaultOwner"));
- StringBuffer sb = new StringBuffer();
- for (Object owner : owners) {
- sb.append('[');
- sb.append(owner.toString());
- sb.append(']');
- }
- String newVal = sb.toString();
- if (newVal.length() == 0)
- newVal = null;
- ce.addEdit(new UndoableFieldChange(be, BibtexFields.MARKED, be
- .getField(BibtexFields.MARKED), newVal));
- be.setField(BibtexFields.MARKED, newVal);
- }
- public static int isMarked(BibtexEntry be) {
- Object fieldVal = be.getField(BibtexFields.MARKED);
- if (fieldVal == null)
- return 0;
- String s = (String) fieldVal;
- if (s.equals("0"))
- return 1;
- int index = s.indexOf(Globals.prefs.WRAPPED_USERNAME);
- if (index >= 0)
- return 1;
- Matcher m = markNumberPattern.matcher(s);
- if (m.find()) {
- try {
- int value = Integer.parseInt(m.group(1));
- return value;
- } catch (NumberFormatException ex) {
- return 1;
- }
- }
- else return 0;
-
- }
- /**
- * Set a given field to a given value for all entries in a Collection. This
- * method DOES NOT update any UndoManager, but returns a relevant
- * CompoundEdit that should be registered by the caller.
- *
- * @param entries
- * The entries to set the field for.
- * @param field
- * The name of the field to set.
- * @param text
- * The value to set. This value can be null, indicating that the
- * field should be cleared.
- * @param overwriteValues
- * Indicate whether the value should be set even if an entry
- * already has the field set.
- * @return A CompoundEdit for the entire operation.
- */
- public static UndoableEdit massSetField(Collection<BibtexEntry> entries, String field, String text,
- boolean overwriteValues) {
- NamedCompound ce = new NamedCompound(Globals.lang("Set field"));
- for (BibtexEntry entry : entries){
- String oldVal = entry.getField(field);
- // If we are not allowed to overwrite values, check if there is a
- // nonempty
- // value already for this entry:
- if (!overwriteValues && (oldVal != null) && ((oldVal).length() > 0))
- continue;
- if (text != null)
- entry.setField(field, text);
- else
- entry.clearField(field);
- ce.addEdit(new UndoableFieldChange(entry, field, oldVal, text));
- }
- ce.end();
- return ce;
- }
- /**
- * Move contents from one field to another for a Collection of entries.
- * @param entries The entries to do this operation for.
- * @param field The field to move contents from.
- * @param newField The field to move contents into.
- * @param overwriteValues If true, overwrites any existing values in the new field.
- * If false, makes no change for entries with existing value in the new field.
- * @return A CompoundEdit for the entire operation.
- */
- public static UndoableEdit massRenameField(Collection<BibtexEntry> entries, String field,
- String newField, boolean overwriteValues) {
- NamedCompound ce = new NamedCompound(Globals.lang("Rename field"));
- for (BibtexEntry entry : entries){
- String valToMove = entry.getField(field);
- // If there is no value, do nothing:
- if ((valToMove == null) || (valToMove.length() == 0))
- continue;
- // If we are not allowed to overwrite values, check if there is a
- // nonempy value already for this entry for the new field:
- String valInNewField = entry.getField(newField);
- if (!overwriteValues && (valInNewField != null) && (valInNewField.length() > 0))
- continue;
- entry.setField(newField, valToMove);
- ce.addEdit(new UndoableFieldChange(entry, newField, valInNewField,valToMove));
- entry.clearField(field);
- ce.addEdit(new UndoableFieldChange(entry, field, valToMove, null));
- }
- ce.end();
- return ce;
- }
- /**
- * Make a list of supported character encodings that can encode all
- * characters in the given String.
- *
- * @param characters
- * A String of characters that should be supported by the
- * encodings.
- * @return A List of character encodings
- */
- public static List<String> findEncodingsForString(String characters) {
- List<String> encodings = new ArrayList<String>();
- for (int i = 0; i < Globals.ENCODINGS.length; i++) {
- CharsetEncoder encoder = Charset.forName(Globals.ENCODINGS[i]).newEncoder();
- if (encoder.canEncode(characters))
- encodings.add(Globals.ENCODINGS[i]);
- }
- return encodings;
- }
- /**
- * Will convert a two digit year using the following scheme (describe at
- * http://www.filemaker.com/help/02-Adding%20and%20view18.html):
- *
- * If a two digit year is encountered they are matched against the last 69
- * years and future 30 years.
- *
- * For instance if it is the year 1992 then entering 23 is taken to be 1923
- * but if you enter 23 in 1993 then it will evaluate to 2023.
- *
- * @param year
- * The year to convert to 4 digits.
- * @return
- */
- public static String toFourDigitYear(String year) {
- if (thisYear == 0) {
- thisYear = Calendar.getInstance().get(Calendar.YEAR);
- }
- return toFourDigitYear(year, thisYear);
- }
- public static int thisYear;
- /**
- * Will convert a two digit year using the following scheme (describe at
- * http://www.filemaker.com/help/02-Adding%20and%20view18.html):
- *
- * If a two digit year is encountered they are matched against the last 69
- * years and future 30 years.
- *
- * For instance if it is the year 1992 then entering 23 is taken to be 1923
- * but if you enter 23 in 1993 then it will evaluate to 2023.
- *
- * @param year
- * The year to convert to 4 digits.
- * @return
- */
- public static String toFourDigitYear(String year, int thisYear) {
- if (year.length() != 2)
- return year;
- try {
- int thisYearTwoDigits = thisYear % 100;
- int thisCentury = thisYear - thisYearTwoDigits;
- int yearNumber = Integer.parseInt(year);
- if (yearNumber == thisYearTwoDigits) {
- return String.valueOf(thisYear);
- }
- // 20 , 90
- // 99 > 30
- if ((yearNumber + 100 - thisYearTwoDigits) % 100 > 30) {
- if (yearNumber < thisYearTwoDigits) {
- return String.valueOf(thisCentury + yearNumber);
- } else {
- return String.valueOf(thisCentury - 100 + yearNumber);
- }
- } else {
- if (yearNumber < thisYearTwoDigits) {
- return String.valueOf(thisCentury + 100 + yearNumber);
- } else {
- return String.valueOf(thisCentury + yearNumber);
- }
- }
- } catch (NumberFormatException e) {
- return year;
- }
- }
- /**
- * Will return an integer indicating the month of the entry from 0 to 11.
- *
- * -1 signals a unknown month string.
- *
- * This method accepts three types of months given:
- * - Single and Double Digit months from 1 to 12 (01 to 12)
- * - 3 Digit BibTex strings (jan, feb, mar...)
- * - Full English Month identifiers.
- *
- * @param month
- * @return
- */
- public static int getMonthNumber(String month) {
- month = month.replaceAll("#", "").toLowerCase();
- for (int i = 0; i < Globals.MONTHS.length; i++) {
- if (month.startsWith(Globals.MONTHS[i])) {
- return i;
- }
- }
- try {
- return Integer.parseInt(month) - 1;
- } catch (NumberFormatException ignored) {
- }
- return -1;
- }
- /**
- * Encodes a two-dimensional String array into a single string, using ':' and
- * ';' as separators. The characters ':' and ';' are escaped with '\'.
- * @param values The String array.
- * @return The encoded String.
- */
- public static String encodeStringArray(String[][] values) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < values.length; i++) {
- sb.append(encodeStringArray(values[i]));
- if (i < values.length-1)
- sb.append(';');
- }
- return sb.toString();
- }
- /**
- * Encodes a String array into a single string, using ':' as separator.
- * The characters ':' and ';' are escaped with '\'.
- * @param entry The String array.
- * @return The encoded String.
- */
- public static String encodeStringArray(String[] entry) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < entry.length; i++) {
- sb.append(encodeString(entry[i]));
- if (i < entry.length-1)
- sb.append(':');
- }
- return sb.toString();
- }
- /**
- * Decodes an encoded double String array back into array form. The array
- * is assumed to be square, and delimited by the characters ';' (first dim) and
- * ':' (second dim).
- * @param value The encoded String to be decoded.
- * @return The decoded String array.
- */
- public static String[][] decodeStringDoubleArray(String value) {
- ArrayList<ArrayList<String>> newList = new ArrayList<ArrayList<String>>();
- StringBuilder sb = new StringBuilder();
- ArrayList<String> thisEntry = new ArrayList<String>();
- boolean escaped = false;
- for (int i=0; i<value.length(); i++) {
- char c = value.charAt(i);
- if (!escaped && (c == '\\')) {
- escaped = true;
- continue;
- }
- else if (!escaped && (c == ':')) {
- thisEntry.add(sb.toString());
- sb = new StringBuilder();
- }
- else if (!escaped && (c == ';')) {
- thisEntry.add(sb.toString());
- sb = new StringBuilder();
- newList.add(thisEntry);
- thisEntry = new ArrayList<String>();
- }
- else sb.append(c);
- escaped = false;
- }
- if (sb.length() > 0)
- thisEntry.add(sb.toString());
- if (thisEntry.size() > 0)
- newList.add(thisEntry);
- // Convert to String[][]:
- String[][] res = new String[newList.size()][];
- for (int i = 0; i < res.length; i++) {
- res[i] = new String[newList.get(i).size()];
- for (int j = 0; j < res[i].length; j++) {
- res[i][j] = newList.get(i).get(j);
- }
- }
- return res;
- }
- public static String encodeString(String s) {
- if (s == null)
- return null;
- StringBuilder sb = new StringBuilder();
- for (int i=0; i<s.length(); i++) {
- char c = s.charAt(i);
- if ((c == ';') || (c == ':') || (c == '\\'))
- sb.append('\\');
- sb.append(c);
- }
- return sb.toString();
- }
- /**
- * Static equals that can also return the right result when one of the
- * objects is null.
- *
- * @param one
- * The object whose equals method is called if the first is not
- * null.
- * @param two
- * The object passed to the first one if the first is not null.
- * @return <code>one == null ? two == null : one.equals(two);</code>
- */
- public static boolean equals(Object one, Object two) {
- return one == null ? two == null : one.equals(two);
- }
- /**
- * Returns the given string but with the first character turned into an
- * upper case character.
- *
- * Example: testTest becomes TestTest
- *
- * @param string
- * The string to change the first character to upper case to.
- * @return A string has the first character turned to upper case and the
- * rest unchanged from the given one.
- */
- public static String toUpperFirstLetter(String string){
- if (string == null)
- throw new IllegalArgumentException();
-
- if (string.length() == 0)
- return string;
-
- return Character.toUpperCase(string.charAt(0)) + string.substring(1);
- }
- /**
- * Run an AbstractWorker's methods using Spin features to put each method
- * on the correct thread.
- * @param worker The worker to run.
- * @throws Throwable
- */
- public static void runAbstractWorker(AbstractWorker worker) throws Throwable {
- // This part uses Spin's features:
- Worker wrk = worker.getWorker();
- // The Worker returned by getWorker() has been wrapped
- // by Spin.off(), which makes its methods be run in
- // a different thread from the EDT.
- CallBack clb = worker.getCallBack();
- worker.init(); // This method runs in this same thread, the EDT.
- // Useful for initial GUI actions, like printing a message.
- // The CallBack returned by getCallBack() has been wrapped
- // by Spin.over(), which makes its methods be run on
- // the EDT.
- wrk.run(); // Runs the potentially time-consuming action
- // without freezing the GUI. The magic is that THIS line
- // of execution will not continue until run() is finished.
- clb.update(); // Runs the update() method on the EDT.
- }
- /**
- * This method checks whether there is a lock file for the given file. If
- * there is, it waits for 500 ms. This is repeated until the lock is gone
- * or we have waited the maximum number of times.
- *
- * @param file The file to check the lock for.
- * @param maxWaitCount The maximum number of times to wait.
- * @return true if the lock file is gone, false if it is still there.
- */
- public static boolean waitForFileLock(File file, int maxWaitCount) {
- // Check if the file is locked by another JabRef user:
- int lockCheckCount = 0;
- while (Util.hasLockFile(file)) {
- if (lockCheckCount++ == maxWaitCount) {
- return false;
- }
- try { Thread.sleep(500); } catch (InterruptedException ignored) {}
- }
- return true;
- }
- /**
- * Check whether a lock file exists for this file.
- * @param file The file to check.
- * @return true if a lock file exists, false otherwise.
- */
- public static boolean hasLockFile(File file) {
- File lock = new File(file.getPath()+ SaveSession.LOCKFILE_SUFFIX);
- return lock.exists();
- }
- /**
- * Find the lock file's last modified time, if it has a lock file.
- * @param file The file to check.
- * @return the last modified time if lock file exists, -1 otherwise.
- */
- public static long getLockFileTimeStamp(File file) {
- File lock = new File(file.getPath()+ SaveSession.LOCKFILE_SUFFIX);
- return lock.exists() ? lock.lastModified() : -1;
- }
- /**
- * Check if a lock file exists, and delete it if it does.
- * @return true if the lock file existed, false otherwise.
- * @throws IOException if something goes wrong.
- */
- public static boolean deleteLockFile(File file) {
- File lock = new File(file.getPath()+SaveSession.LOCKFILE_SUFFIX);
- if (!lock.exists()) {
- return false;
- }
- lock.delete();
- return true;
- }
- /**
- * Build a String array containing all those elements of all that are not
- * in subset.
- * @param all The array of all values.
- * @param subset The subset of values.
- * @return The remainder that is not part of the subset.
- */
- public static String[] getRemainder(String[] all, String[] subset) {
- ArrayList<String> al = new ArrayList<String>();
- for (String anAll : all) {
- boolean found = false;
- for (String aSubset : subset) {
- if (aSubset.equals(anAll)) {
- found = true;
- break;
- }
- }
- if (!found) al.add(anAll);
- }
- return al.toArray(new String[al.size()]);
- }
- public static <T> T[] concat(T[] first, T[] second) {
- T[] result = Arrays.copyOf(first, first.length + second.length);
- System.arraycopy(second, 0, result, first.length, second.length);
- return result;
- }
- /**
- * Determines filename provided by an entry in a database
- *
- * @param database the database, where the entry is located
- * @param entry the entry to which the file should be linked to
- * @return a suggested fileName
- */
- public static String getLinkedFileName(BibtexDatabase database, BibtexEntry entry) {
- String targetName = entry.getCiteKey() == null ? "default" : entry.getCiteKey();
- StringReader sr = new StringReader(Globals.prefs.get(ImportSettingsTab.PREF_IMPORT_FILENAMEPATTERN));
- Layout layout = null;
- try {
- layout = new LayoutHelper(sr).getLayoutFromText(Globals.FORMATTER_PACKAGE);
- } catch (Exception e) {
- Globals.logger(Globals.lang("Wrong Format").concat(" ").concat(e.toString()));
- }
- if (layout != null) {
- targetName = layout.doLayout(entry, database);
- }
- //Removes illegal characters from filename
- targetName = FileNameCleaner.cleanFileName(targetName);
- return targetName;
- }
-
- // DOI-regexp provided by http://stackoverflow.com/a/10324802/873282
- // Some DOI's are not caught by the regexp in the above link, i.e. 10.1002/(SICI)1522-2594(199911)42:5<952::AID-MRM16>3.0.CO;2-S
- // Removed <> from non-permitted characters
- private static final String REGEXP_PLAINDOI = "\\b(10[.][0-9]{4,}(?:[.][0-9]+)*/(?:(?![\"&\\'])\\S)+)\\b";
- private static final String REGEXP_DOI_WITH_HTTP_PREFIX = "http[s]?://[^\\s]*?" + REGEXP_PLAINDOI;
- private static final Pattern PATTERN_PLAINDOI = Pattern.compile(REGEXP_PLAINDOI);
- /**
- * Check if the String matches a DOI (with http://...)
- */
- public static boolean checkForDOIwithHTTPprefix(String check) {
- if (check == null)
- return false;
- return check.matches(".*" + REGEXP_DOI_WITH_HTTP_PREFIX + ".*");
- }
-
- /**
- *
- * @param check - string to check
- * @return true if "check" contains a DOI
- */
- public static boolean checkForPlainDOI(String check) {
- if (check == null)
- return false;
- return check.matches(".*" + REGEXP_PLAINDOI + ".*");
- }
-
- /**
- * Remove the http://... from DOI
- *
- * @param doi - may not be null
- * @return first DOI in the given String (without http://... prefix). If no DOI exists, the complete string is returned
- */
- public static String getDOI(String doi) {
- Matcher matcher = PATTERN_PLAINDOI.matcher(doi);
- if (matcher.find()) {
- return matcher.group();
- } else {
- return doi;
- }
- }
- public static void removeDOIfromBibtexEntryField(BibtexEntry bes, String fieldName, NamedCompound ce) {
- String origValue = bes.getField(fieldName);
- String value = origValue;
- value = value.replaceAll(REGEXP_DOI_WITH_HTTP_PREFIX, "");
- value = value.replaceAll(REGEXP_PLAINDOI, "");
- value = value.trim();
- if (value.isEmpty()) value = null;
- if (!origValue.equals(value)) {
- ce.addEdit(new UndoableFieldChange(bes, fieldName, origValue, value));
- bes.setField(fieldName, value);
- }
- }
-
- /**
- *
- * @param fileName
- * @param destFilename
- * @return
- */
- public static boolean renameFile(String fileName, String destFilename)
- {
- // File (or directory) with old name
- File fromFile = new File(fileName);
- // File (or directory) with new name
- File toFile = new File(destFilename);
-
- // Rename file (or directory)
- boolean success = fromFile.renameTo(toFile);
- return success;
- }
- public static ArrayList<String> getSeparatedKeywords(String keywords) {
- ArrayList<String> res = new ArrayList<String>();
- if (keywords == null) return res;
- // _NOSPACE is a hack to support keywords such as "choreography transactions"
- // a more intelligent algorithm would check for the separator chosen (SEPARATING_CHARS_NOSPACE)
- // if nothing is found, " " is likely to be the separating char.
- // solution by RisKeywords.java: s.split(",[ ]*")
- StringTokenizer tok = new StringTokenizer(keywords, Globals.SEPARATING_CHARS_NOSPACE);
- while (tok.hasMoreTokens()) {
- String word = tok.nextToken().trim();
- res.add(word);
- }
- return res;
- }
-
- public static ArrayList<String> getSeparatedKeywords(BibtexEntry be) {
- return getSeparatedKeywords(be.getField("keywords"));
- }
-
- public static void putKeywords(BibtexEntry entry, ArrayList<String> keywords, NamedCompound ce) {
- // Set Keyword Field
- String oldValue = entry.getField("keywords");
- String newValue;
- if (keywords.size() > 0) {
- StringBuilder sb = new StringBuilder();
- for (String keyword: keywords) {
- sb.append(keyword);
- sb.append(", ");
- }
- sb.delete(sb.length()-2, sb.length());
- newValue = sb.toString();
- } else {
- newValue = null;
- }
- if ((oldValue == null) && (newValue == null))
- return;
- if ((oldValue == null) || (!oldValue.equals(newValue))) {
- entry.setField("keywords", newValue);
- if (ce!=null) ce.addEdit(new UndoableFieldChange(entry, "keywords", oldValue, newValue));
- }
- }
-
- /**
- * @param ce indicates the undo named compound. May be null
- */
- public static void updateField(BibtexEntry be, String field, String newValue, NamedCompound ce) {
- updateField(be, field, newValue, ce, false);
- }
- /**
- * @param ce indicates the undo named compound. May be null
- */
- public static void updateField(BibtexEntry be, String field, String newValue, NamedCompound ce, Boolean nullFieldIfValueIsTheSame) {
- String oldValue = be.getField(field);
- if (nullFieldIfValueIsTheSame && (oldValue != null) && (oldValue.equals(newValue))) {
- // if oldValue == newValue then reset field if required by parameter
- newValue = null;
- }
- if ((oldValue == null) && (newValue == null))
- return;
- if ((oldValue==null) || (!oldValue.equals(newValue))) {
- be.setField(field, newValue);
- if (ce!=null) ce.addEdit(new UndoableFieldChange(be, field, oldValue, newValue));
- }
- }
- /**
- * Binds ESC-Key to cancel button
- * @param rootPane the pane to bind the action to. Typically, this variable is retrieved by this.getRootPane();
- * @param cancelAction the action to bind
- */
- public static void bindCloseDialogKeyToCancelAction(JRootPane rootPane,
- Action cancelAction) {
- InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
- ActionMap am = rootPane.getActionMap();
- im.put(Globals.prefs.getKey("Close dialog"), "close");
- am.put("close", cancelAction);
- }
- /**
- * Download the URL and return contents as a String.
- * @param source
- * @return
- * @throws IOException
- */
- public static String getResults(URLConnection source) throws IOException {
-
- return getResultsWithEncoding(source, null);
- }
- /**
- * Download the URL using specified encoding and return contents as a String.
- * @param source
- * encoding
- * @return
- * @throws IOException
- */
- public static String getResultsWithEncoding(URLConnection source, String encoding) throws IOException {
-
- InputStreamReader in;
- if(encoding != null) {
- in = new InputStreamReader(source.getInputStream(), encoding);
- } else {
- in = new InputStreamReader(source.getInputStream());
- }
-
- StringBuilder sb = new StringBuilder();
- while(true) {
- int byteRead = in.read();
- if(byteRead == -1) break;
- sb.append((char)byteRead);
- }
- return sb.toString();
- }
- public static boolean updateTimeStampIsSet() {
- return (Globals.prefs.getBoolean("useTimeStamp") &&
- Globals.prefs.getBoolean(JabRefPreferences.UPDATE_TIMESTAMP));
- }
- /**
- * Updates the timestamp of the given entry,
- * nests the given undaoableEdit in a named compound,
- * and returns that named compound
- */
- public static NamedCompound doUpdateTimeStamp(BibtexEntry entry, AbstractUndoableEdit undoableEdit) {
- NamedCompound ce = new NamedCompound(undoableEdit.getPresentationName());
- ce.addEdit(undoableEdit);
- String timeStampField = Globals.prefs.get("timeStampField");
- String timestamp = Util.easyDateFormat();
- Util.updateField(entry, timeStampField, timestamp, ce);
- return ce;
- }
- /**
- * Automatically add links for this set of entries, based on the globally stored list of
- * external file types. The entries are modified, and corresponding UndoEdit elements
- * added to the NamedCompound given as argument. Furthermore, all entries which are modified
- * are added to the Set of entries given as an argument.
- *
- * The entries' bibtex keys must have been set - entries lacking key are ignored.
- * The operation is done in a new thread, which is returned for the caller to wait for
- * if needed.
- *
- * @param entries A collection of BibtexEntry objects to find links for.
- * @param ce A NamedCompound to add UndoEdit elements to.
- * @param changedEntries MODIFIED, optional. A Set of BibtexEntry objects to which all modified entries is added. This is used for status output and debugging
- * @param singleTableModel UGLY HACK. The table model to insert links into. Already existing links are not duplicated or removed. This parameter has to be null if entries.count() != 1.
- * The hack has been introduced as a bibtexentry does not (yet) support the function getListTableModel() and the FileListEntryEditor editor holds an instance of that table model and does not reconstruct it after the search has succeeded.
- * @param metaData The MetaData providing the relevant file directory, if any.
- * @param callback An ActionListener that is notified (on the event dispatch thread) when the search is
- * finished. The ActionEvent has id=0 if no new links were added, and id=1 if one or more links were added.
- * This parameter can be null, which means that no callback will be notified.
- * @param diag An instantiated modal JDialog which will be used to display the progress of the autosetting.
- * This parameter can be null, which means that no progress update will be shown.
- * @return the thread performing the autosetting
- */
- public static Thread autoSetLinks(final Collection<BibtexEntry> entries,
- final NamedCompound ce,
- final Set<BibtexEntry> changedEntries,
- final FileListTableModel singleTableModel,
- final MetaData metaData,
- final ActionListener callback,
- final JDialog diag) {
- final ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
- if (diag != null) {
- final JProgressBar prog = new JProgressBar(JProgressBar.HORIZONTAL, 0, types.length-1);
- final JLabel label = new JLabel(Globals.lang("Searching for files"));
- prog.setIndeterminate(true);
- prog.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
- diag.setTitle(Globals.lang("Autosetting links"));
- diag.getContentPane().add(prog, BorderLayout.CENTER);
- diag.getContentPane().add(label, BorderLayout.SOUTH);
- diag.pack();
- diag.setLocationRelativeTo(diag.getParent());
- }
- Runnable r = new Runnable() {
- public void run() {
- // determine directories to search in
- ArrayList<File> dirs = new ArrayList<File>();
- String[] dirsS = metaData.getFileDirectory(GUIGlobals.FILE_FIELD);
- for (String dirs1 : dirsS) {
- dirs.add(new File(dirs1));
- }
- // determine extensions
- Collection<String> extensions = new ArrayList<String>();
- for (final ExternalFileType type : types) {
- extensions.add(type.getExtension());
- }
- // Run the search operation:
- Map<BibtexEntry, java.util.List<File>> result;
- if (Globals.prefs.getBoolean(JabRefPreferences.USE_REG_EXP_SEARCH_KEY)) {
- String regExp = Globals.prefs.get(JabRefPreferences.REG_EXP_SEARCH_EXPRESSION_KEY);
- result = RegExpFileSearch.findFilesForSet(entries, extensions, dirs, regExp);
- } else {
- result = Util.findAssociatedFiles(entries, extensions, dirs);
- }
- boolean foundAny = false;
- // Iterate over the entries:
- for (BibtexEntry anEntry : result.keySet()) {
- FileListTableModel tableModel;
- String oldVal = anEntry.getField(GUIGlobals.FILE_FIELD);
- if (singleTableModel == null) {
- tableModel = new FileListTableModel();
- if (oldVal != null)
- tableModel.setContent(oldVal);
- } else {
- assert (entries.size() == 1);
- tableModel = singleTableModel;
- }
- List<File> files = result.get(anEntry);
- for (File f : files) {
- f = Util.shortenFileName(f, dirsS);
- boolean alreadyHas = false;
- //System.out.println("File: "+f.getPath());
- for (int j = 0; j < tableModel.getRowCount(); j++) {
- FileListEntry existingEntry = tableModel.getEntry(j);
- //System.out.println("Comp: "+existingEntry.getLink());
- if (new File(existingEntry.getLink()).equals(f)) {
- alreadyHas = true;
- break;
- }
- }
- if (!alreadyHas) {
- foundAny = true;
- ExternalFileType type;
- int index = f.getPath().lastIndexOf('.');
- if ((index >= 0) && (index < f.getPath().length() - 1)) {
- type = Globals.prefs.getExternalFileTypeByExt
- (f.getPath().substring(index + 1).toLowerCase());
- } else {
- type = new UnknownExternalFileType("");
- }
- FileListEntry flEntry = new FileListEntry(f.getName(), f.getPath(), type);
- tableModel.addEntry(tableModel.getRowCount(), flEntry);
- String newVal = tableModel.getStringRepresentation();
- if (newVal.length() == 0)
- newVal = null;
- if (ce != null) {
- // store undo information
- UndoableFieldChange change = new UndoableFieldChange(anEntry,
- GUIGlobals.FILE_FIELD, oldVal, newVal);
- ce.addEdit(change);
- }
- // hack: if table model is given, do NOT modify entry
- if (singleTableModel == null) {
- anEntry.setField(GUIGlobals.FILE_FIELD, newVal);
- }
- if (changedEntries != null)
- changedEntries.add(anEntry);
- }
- }
- }
-
- // handle callbacks and dialog
- final int id = foundAny ? 1 : 0;
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- if (diag != null)
- diag.dispose();
- if (callback != null)
- callback.actionPerformed(new ActionEvent(this, id, ""));
- }
- });
- }
- };
- Thread t = new Thread(r);
- t.start();
- if (diag != null) {
- diag.setVisible(true);
- }
- return t;
- }
- /**
- * Automatically add links for this entry to the table model given as an argument, based on
- * the globally stored list of external file types. The entry itself is not modified. The entry's
- * bibtex key must have been set.
- * The operation is done in a new thread, which is returned for the caller to wait for
- * if needed.
- *
- * @param entry The BibtexEntry to find links for.
- * @param singleTableModel The table model to insert links into. Already existing links are not duplicated or removed.
- * @param metaData The MetaData providing the relevant file directory, if any.
- * @param callback An ActionListener that is notified (on the event dispatch thread) when the search is
- * finished. The ActionEvent has id=0 if no new links were added, and id=1 if one or more links were added.
- * This parameter can be null, which means that no callback will be notified. The passed ActionEvent is constructed with
- * (this, id, ""), where id is 1 if something has been done and 0 if nothing has been done.
- * @param diag An instantiated modal JDialog which will be used to display the progress of the autosetting.
- * This parameter can be null, which means that no progress update will be shown.
- * @return the thread performing the autosetting
- */
- public static Thread autoSetLinks(
- final BibtexEntry entry,
- final FileListTableModel singleTableModel,
- final MetaData metaData,
- final ActionListener callback,
- final JDialog diag) {
- final Collection<BibtexEntry> entries = new ArrayList<BibtexEntry>();
- entries.add(entry);
-
- return autoSetLinks(entries, null, null, singleTableModel, metaData, callback, diag);
- }
-
- /**
- * Opens a file browser of the folder of the given file. If possible, the file is selected
- * @param fileLink the location of the file
- * @throws IOException
- */
- public static void openFolderAndSelectFile(String fileLink) throws IOException {
- if (Globals.ON_WIN) {
- openFolderAndSelectFileOnWindows(fileLink);
- } else if (Globals.ON_LINUX){
- openFolderAndSelectFileOnLinux(fileLink);
- } else {
- openFolderAndSelectFileGeneric(fileLink);
- }
- }
- private static void openFolderAndSelectFileOnLinux(String fileLink) throws IOException {
- String desktopSession = System.getenv("DESKTOP_SESSION").toLowerCase();
-
- String cmd = "";
-
- if (desktopSession.contains("gnome")) {
- cmd = "nautilus " + fileLink;
- } else if (desktopSession.contains("kde")) {
- cmd = "dolphin --select " + fileLink;
- } else {
- cmd = "xdg-open " + fileLink.substring(0, fileLink.lastIndexOf(File.separator));
- }
-
- Runtime.getRuntime().exec(cmd);
- }
- private static void openFolderAndSelectFileGeneric(String fileLink) throws IOException {
- File f = new File(fileLink);
- Desktop.getDesktop().open(f.getParentFile());
- }
- private static void openFolderAndSelectFileOnWindows(String link) throws IOException {
- link = link.replace("&", "\"&\"");
- String cmd = "explorer.exe /select,\"" + link + "\"";
- Runtime.getRuntime().exec(cmd);
- }
- /**
- * Returns the list of linked files. The files have the absolute filename
- *
- * @param bes list of BibTeX entries
- * @param fileDirs list of directories to try for expansion
- *
- * @return list of files. May be empty
- */
- public static List<File> getListOfLinkedFiles(BibtexEntry[] bes, String[] fileDirs) {
- ArrayList<File> res = new ArrayList<File>();
- for (BibtexEntry entry : bes) {
- FileListTableModel tm = new FileListTableModel();
- tm.setContent(entry.getField("file"));
- for (int i=0; i< tm.getRowCount(); i++) {
- FileListEntry flEntry = tm.getEntry(i);
-
- File f = Util.expandFilename(flEntry.getLink(), fileDirs);
- if (f != null) {
- res.add(f);
- }
- }
- }
- return res;
- }
- }