From 3d131af43a89662a6cb5d045371256f184f04fea Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 14:33:07 +0100 Subject: [PATCH 01/21] Remove deprecated method --- .../jabref/gui/externalfiles/DownloadExternalFile.java | 3 +-- .../jabref/gui/groups/EntryTableTransferHandler.java | 2 +- src/main/java/org/jabref/logic/net/URLDownload.java | 10 +--------- .../java/org/jabref/logic/net/URLDownloadTest.java | 2 +- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java index cff803cde35..af4273dca6b 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java @@ -117,7 +117,7 @@ public void download(URL url, final DownloadCallback callback) throws IOExceptio JabRefExecutorService.INSTANCE.execute(() -> { try { - udlF.downloadToFile(tmp); + udlF.downloadToFile(tmp.toPath()); } catch (IOException e2) { dontShowDialog = true; if ((editor != null) && editor.isVisible()) { @@ -344,7 +344,6 @@ private String getSuffix(final String link) { */ @FunctionalInterface public interface DownloadCallback { - void downloadComplete(FileListEntry file); } } diff --git a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java index 2b6fcf131f1..7729e891a01 100644 --- a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java +++ b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java @@ -380,7 +380,7 @@ private boolean handleDropTransfer(URL dropLink) throws IOException { // System.out.println("Import url: " + dropLink.toString()); // System.out.println("Temp file: "+tmpfile.getAbsolutePath()); - MonitoredURLDownload.buildMonitoredDownload(entryTable, dropLink).downloadToFile(tmpfile); + MonitoredURLDownload.buildMonitoredDownload(entryTable, dropLink).downloadToFile(tmpfile.toPath()); // Import into new if entryTable==null, otherwise into current library: ImportMenuItem importer = new ImportMenuItem(frame, entryTable == null); diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 09429d320cf..e7759e30112 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -46,7 +46,7 @@ * Example: * URLDownload dl = new URLDownload(URL); * String content = dl.downloadToString(ENCODING); - * dl.downloadToFile(FILE); // available in FILE + * dl.downloadToFile(Path); // available in FILE * String contentType = dl.determineMimeType(); * * Each call to a public method creates a new HTTP connection. Nothing is cached. @@ -197,14 +197,6 @@ private void copy(InputStream in, Writer out, Charset encoding) throws IOExcepti } } - /** - * @deprecated use {@link #downloadToFile(Path)} - */ - @Deprecated - public void downloadToFile(File destination) throws IOException { - downloadToFile(destination.toPath()); - } - public void downloadToFile(Path destination) throws IOException { try (InputStream input = monitorInputStream(new BufferedInputStream(openConnection().getInputStream()))) { diff --git a/src/test/java/org/jabref/logic/net/URLDownloadTest.java b/src/test/java/org/jabref/logic/net/URLDownloadTest.java index d46fe25aafb..0f5f6b240ab 100644 --- a/src/test/java/org/jabref/logic/net/URLDownloadTest.java +++ b/src/test/java/org/jabref/logic/net/URLDownloadTest.java @@ -34,7 +34,7 @@ public void testFileDownload() throws IOException { File destination = File.createTempFile("jabref-test", ".html"); try { URLDownload dl = new URLDownload(new URL("http://www.google.com")); - dl.downloadToFile(destination); + dl.downloadToFile(destination.toPath()); Assert.assertTrue("file must exist", destination.exists()); } finally { // cleanup From 88010dd5dfba82bbbc388897f64cfeb19e3873d0 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 14:42:32 +0100 Subject: [PATCH 02/21] Monitored URLDownload is currently not use anywhere --- .../externalfiles/DownloadExternalFile.java | 3 +-- .../gui/groups/EntryTableTransferHandler.java | 7 ++---- .../jabref/gui/net/MonitoredURLDownload.java | 22 ------------------- .../org/jabref/logic/net/URLDownload.java | 9 ++------ 4 files changed, 5 insertions(+), 36 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/net/MonitoredURLDownload.java diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java index af4273dca6b..7fa0e9edcab 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java @@ -18,7 +18,6 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.filelist.FileListEntry; import org.jabref.gui.filelist.FileListEntryEditor; -import org.jabref.gui.net.MonitoredURLDownload; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.OS; @@ -100,7 +99,7 @@ public void download(URL url, final DownloadCallback callback) throws IOExceptio final File tmp = File.createTempFile("jabref_download", "tmp"); tmp.deleteOnExit(); - URLDownload udl = MonitoredURLDownload.buildMonitoredDownload(frame, url); + URLDownload udl = new URLDownload(url); try { // TODO: what if this takes long time? diff --git a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java index 7729e891a01..44d442d9f83 100644 --- a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java +++ b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java @@ -32,7 +32,7 @@ import org.jabref.gui.importer.ImportMenuItem; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.maintable.MainTable; -import org.jabref.gui.net.MonitoredURLDownload; +import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.io.FileUtil; import org.jabref.pdfimport.PdfImporter; import org.jabref.pdfimport.PdfImporter.ImportPdfFilesResult; @@ -377,10 +377,7 @@ private boolean handleDropTransfer(URL dropLink) throws IOException { File tmpfile = File.createTempFile("jabrefimport", ""); tmpfile.deleteOnExit(); - // System.out.println("Import url: " + dropLink.toString()); - // System.out.println("Temp file: "+tmpfile.getAbsolutePath()); - - MonitoredURLDownload.buildMonitoredDownload(entryTable, dropLink).downloadToFile(tmpfile.toPath()); + new URLDownload(dropLink).downloadToFile(tmpfile.toPath()); // Import into new if entryTable==null, otherwise into current library: ImportMenuItem importer = new ImportMenuItem(frame, entryTable == null); diff --git a/src/main/java/org/jabref/gui/net/MonitoredURLDownload.java b/src/main/java/org/jabref/gui/net/MonitoredURLDownload.java deleted file mode 100644 index 9558b39941d..00000000000 --- a/src/main/java/org/jabref/gui/net/MonitoredURLDownload.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.jabref.gui.net; - -import java.awt.Component; -import java.io.InputStream; -import java.net.URL; - -import javax.swing.ProgressMonitorInputStream; - -import org.jabref.logic.net.URLDownload; - -public class MonitoredURLDownload { - - public static URLDownload buildMonitoredDownload(final Component component, URL source) { - return new URLDownload(source) { - - @Override - protected InputStream monitorInputStream(InputStream in) { - return new ProgressMonitorInputStream(component, "Downloading " + this.getSource(), in); - } - }; - } -} diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index e7759e30112..9540977f588 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -3,7 +3,6 @@ import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.DataOutputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -185,7 +184,7 @@ public List getCookieFromUrl() throws IOException { } private void copy(InputStream in, Writer out, Charset encoding) throws IOException { - InputStream monitoredInputStream = monitorInputStream(in); + InputStream monitoredInputStream = in; Reader r = new InputStreamReader(monitoredInputStream, encoding); try (BufferedReader read = new BufferedReader(r)) { @@ -199,7 +198,7 @@ private void copy(InputStream in, Writer out, Charset encoding) throws IOExcepti public void downloadToFile(Path destination) throws IOException { - try (InputStream input = monitorInputStream(new BufferedInputStream(openConnection().getInputStream()))) { + try (InputStream input = new BufferedInputStream(openConnection().getInputStream())) { Files.copy(input, destination, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { LOGGER.warn("Could not copy input", e); @@ -227,10 +226,6 @@ public Path downloadToTemporaryFile() throws IOException { return file; } - protected InputStream monitorInputStream(InputStream in) { - return in; - } - @Override public String toString() { return "URLDownload{" + "source=" + source + '}'; From 300ea8b965e74574d7781b49d66c4ed2b2467959 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 14:46:45 +0100 Subject: [PATCH 03/21] Always use browser user agent --- .../jabref/logic/importer/fetcher/GoogleScholar.java | 7 +++---- src/main/java/org/jabref/logic/net/URLDownload.java | 11 +---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java index 1984611b148..5be996eea31 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java @@ -142,8 +142,7 @@ public List performSearch(String query) throws FetcherException { } private void addHitsFromQuery(List entryList, String queryURL) throws IOException, FetcherException { - String content = URLDownload.createURLDownloadWithBrowserUserAgent(queryURL) - .downloadToString(StandardCharsets.UTF_8); + String content = new URLDownload(queryURL).downloadToString(StandardCharsets.UTF_8); Matcher matcher = LINK_TO_BIB_PATTERN.matcher(content); while (matcher.find()) { @@ -154,7 +153,7 @@ private void addHitsFromQuery(List entryList, String queryURL) throws } private BibEntry downloadEntry(String link) throws IOException, FetcherException { - String downloadedContent = URLDownload.createURLDownloadWithBrowserUserAgent(link).downloadToString(StandardCharsets.UTF_8); + String downloadedContent = new URLDownload(link).downloadToString(StandardCharsets.UTF_8); BibtexParser parser = new BibtexParser(importFormatPreferences); ParserResult result = parser.parse(new StringReader(downloadedContent)); if ((result == null) || (result.getDatabase() == null)) { @@ -173,7 +172,7 @@ private BibEntry downloadEntry(String link) throws IOException, FetcherException private void obtainAndModifyCookie() throws FetcherException { try { - URLDownload downloader = URLDownload.createURLDownloadWithBrowserUserAgent("https://scholar.google.com"); + URLDownload downloader = new URLDownload("https://scholar.google.com"); List cookies = downloader.getCookieFromUrl(); for (HttpCookie cookie : cookies) { // append "CF=4" which represents "Citation format bibtex" diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 9540977f588..26c63a53b13 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -49,26 +49,17 @@ * String contentType = dl.determineMimeType(); * * Each call to a public method creates a new HTTP connection. Nothing is cached. - * - * @author Erik Putrycz erik.putrycz-at-nrc-cnrc.gc.ca - * @author Simon Harrer */ public class URLDownload { private static final Log LOGGER = LogFactory.getLog(URLDownload.class); - private static final String USER_AGENT= "JabRef"; + private static final String USER_AGENT= "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"; private final URL source; private final Map parameters = new HashMap<>(); private String postData = ""; - public static URLDownload createURLDownloadWithBrowserUserAgent(String address) throws MalformedURLException { - URLDownload downloader = new URLDownload(address); - downloader.addParameters("User-Agent", "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"); - return downloader; - } - /** * @param address the URL to download from * @throws MalformedURLException if no protocol is specified in the address, or an unknown protocol is found From f2a553378ddfc787c4c21f630054626c5c2bbe88 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 14:53:44 +0100 Subject: [PATCH 04/21] Unused methods and refactoring --- .../importer/fetcher/IEEEXploreFetcher.java | 4 +- .../logic/importer/fetcher/DoiFetcher.java | 2 +- .../org/jabref/logic/net/URLDownload.java | 82 ++++++++----------- 3 files changed, 36 insertions(+), 52 deletions(-) diff --git a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java index 5109975ddcf..6b8c1d6dc38 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java @@ -103,8 +103,8 @@ public boolean processQuery(String query, ImportInspector dialog, OutputPrinter URLDownload dl = new URLDownload(IEEEXploreFetcher.URL_SEARCH); //add request header - dl.addParameters("Accept", "application/json"); - dl.addParameters("Content-Type", "application/json"); + dl.addHeader("Accept", "application/json"); + dl.addHeader("Content-Type", "application/json"); // set post data dl.setPostData(postData); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java index 79bbc8d73d2..027bcdac560 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java @@ -52,7 +52,7 @@ public Optional performSearchById(String identifier) throws FetcherExc // BibTeX data URLDownload download = new URLDownload(doiURL); - download.addParameters("Accept", "application/x-bibtex"); + download.addHeader("Accept", "application/x-bibtex"); String bibtexString = download.downloadToString(StandardCharsets.UTF_8); // BibTeX entry diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 26c63a53b13..77dfa010ec3 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; @@ -53,7 +54,7 @@ public class URLDownload { private static final Log LOGGER = LogFactory.getLog(URLDownload.class); - private static final String USER_AGENT= "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"; + private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"; private final URL source; private final Map parameters = new HashMap<>(); @@ -73,17 +74,12 @@ public URLDownload(String address) throws MalformedURLException { */ public URLDownload(URL source) { this.source = source; - addParameters("User-Agent", USER_AGENT); + this.addHeader("User-Agent", URLDownload.USER_AGENT); } - public URL getSource() { - return source; - } - - public String determineMimeType() throws IOException { // this does not cause a real performance issue as the underlying HTTP/TCP connection is reused - URLConnection urlConnection = openConnection(); + URLConnection urlConnection = this.openConnection(); try { return urlConnection.getContentType(); } finally { @@ -95,8 +91,8 @@ public String determineMimeType() throws IOException { } } - public void addParameters(String key, String value) { - parameters.put(key, value); + public void addHeader(String key, String value) { + this.parameters.put(key, value); } public void setPostData(String postData) { @@ -106,14 +102,14 @@ public void setPostData(String postData) { } private URLConnection openConnection() throws IOException { - URLConnection connection = source.openConnection(); - for (Map.Entry entry : parameters.entrySet()) { + URLConnection connection = this.source.openConnection(); + for (Entry entry : this.parameters.entrySet()) { connection.setRequestProperty(entry.getKey(), entry.getValue()); } - if (!postData.isEmpty()) { + if (!this.postData.isEmpty()) { connection.setDoOutput(true); try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { - wr.writeBytes(postData); + wr.writeBytes(this.postData); } } @@ -122,9 +118,9 @@ private URLConnection openConnection() throws IOException { // normally, 3xx is redirect int status = ((HttpURLConnection) connection).getResponseCode(); if (status != HttpURLConnection.HTTP_OK) { - if ((status == HttpURLConnection.HTTP_MOVED_TEMP) - || (status == HttpURLConnection.HTTP_MOVED_PERM) - || (status == HttpURLConnection.HTTP_SEE_OTHER)) { + if (status == HttpURLConnection.HTTP_MOVED_TEMP + || status == HttpURLConnection.HTTP_MOVED_PERM + || status == HttpURLConnection.HTTP_SEE_OTHER) { // get redirect url from "location" header field String newUrl = connection.getHeaderField("Location"); // open the new connnection again @@ -144,15 +140,14 @@ private URLConnection openConnection() throws IOException { * @return the downloaded string * @throws IOException */ - public String downloadToString(Charset encoding) throws IOException { - try (InputStream input = new BufferedInputStream(openConnection().getInputStream()); + try (InputStream input = new BufferedInputStream(this.openConnection().getInputStream()); Writer output = new StringWriter()) { - copy(input, output, encoding); + this.copy(input, output, encoding); return output.toString(); } catch (IOException e) { - LOGGER.warn("Could not copy input", e); + URLDownload.LOGGER.warn("Could not copy input", e); throw e; } } @@ -162,16 +157,15 @@ public List getCookieFromUrl() throws IOException { CookieHandler.setDefault(cookieManager); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); - URLConnection con = openConnection(); + URLConnection con = this.openConnection(); con.getHeaderFields(); // must be read to store the cookie try { - return cookieManager.getCookieStore().get(source.toURI()); + return cookieManager.getCookieStore().get(this.source.toURI()); } catch (URISyntaxException e) { - LOGGER.error("Unable to convert download URL to URI", e); + URLDownload.LOGGER.error("Unable to convert download URL to URI", e); return Collections.emptyList(); } - } private void copy(InputStream in, Writer out, Charset encoding) throws IOException { @@ -189,10 +183,10 @@ private void copy(InputStream in, Writer out, Charset encoding) throws IOExcepti public void downloadToFile(Path destination) throws IOException { - try (InputStream input = new BufferedInputStream(openConnection().getInputStream())) { + try (InputStream input = new BufferedInputStream(this.openConnection().getInputStream())) { Files.copy(input, destination, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { - LOGGER.warn("Could not copy input", e); + URLDownload.LOGGER.warn("Could not copy input", e); throw e; } } @@ -204,7 +198,7 @@ public void downloadToFile(Path destination) throws IOException { */ public Path downloadToTemporaryFile() throws IOException { // Determine file name and extension from source url - String sourcePath = source.getPath(); + String sourcePath = this.source.getPath(); // Take everything after the last '/' as name + extension String fileNameWithExtension = sourcePath.substring(sourcePath.lastIndexOf('/') + 1); @@ -213,49 +207,39 @@ public Path downloadToTemporaryFile() throws IOException { // Create temporary file and download to it Path file = Files.createTempFile(fileName, extension); - downloadToFile(file); + this.downloadToFile(file); return file; } @Override public String toString() { - return "URLDownload{" + "source=" + source + '}'; + return "URLDownload{" + "source=" + this.source + '}'; } - public void fixSSLVerification() { - + public void bypassSSLVerification() { // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() { - + TrustManager[] trustAllCerts = { new X509TrustManager() { @Override - public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { - // TODO Auto-generated method stub - + public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override - public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) - throws java.security.cert.CertificateException { - // TODO Auto-generated method stub - + public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - // TODO Auto-generated method stub + public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } - }}; // Install the all-trusting trust manager try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, trustAllCerts, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); } catch (Exception e) { - LOGGER.error("SSL problem", e); + LOGGER.error("A problem occurred when bypassing SSL verification", e); } } } From 0d43f6d029343f351d909a5b3c248d89456e80c1 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 15:04:27 +0100 Subject: [PATCH 05/21] Bypass SSL functionality --- .../logic/importer/fetcher/MrDLibFetcher.java | 3 +-- .../org/jabref/logic/net/URLDownload.java | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java index 9d3bdf4380d..50b41cdad8e 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java @@ -86,8 +86,7 @@ public List performSearch(BibEntry entry) throws FetcherException { */ private String makeServerRequest(String queryByTitle) throws FetcherException { try { - URLDownload urlDownload = new URLDownload(constructQuery(queryByTitle)); - urlDownload.fixSSLVerification(); + URLDownload urlDownload = new URLDownload(constructQuery(queryByTitle), false); String response = urlDownload.downloadToString(StandardCharsets.UTF_8); //Conversion of < and > diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 77dfa010ec3..90c36ab4634 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -62,19 +62,33 @@ public class URLDownload { private String postData = ""; /** - * @param address the URL to download from - * @throws MalformedURLException if no protocol is specified in the address, or an unknown protocol is found + * @param source the URL to download from + * @throws MalformedURLException if no protocol is specified in the source, or an unknown protocol is found */ - public URLDownload(String address) throws MalformedURLException { - this(new URL(address)); + public URLDownload(String source) throws MalformedURLException { + this(new URL(source), true); + } + + /** + * @param source the URL to download from + * @param validateSSL if set to false, bypasses the SSL vlaidation + * @throws MalformedURLException if no protocol is specified in the source, or an unknown protocol is found + */ + public URLDownload(String source, boolean validateSSL) throws MalformedURLException { + this(new URL(source), validateSSL); } /** * @param source The URL to download. + * @param validateSSL if set to false, bypasses the SSL vlaidation */ - public URLDownload(URL source) { + public URLDownload(URL source, boolean validateSSL) { this.source = source; this.addHeader("User-Agent", URLDownload.USER_AGENT); + + if (!validateSSL) { + bypassSSLVerification(); + } } public String determineMimeType() throws IOException { @@ -216,7 +230,7 @@ public String toString() { return "URLDownload{" + "source=" + this.source + '}'; } - public void bypassSSLVerification() { + private void bypassSSLVerification() { // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = { new X509TrustManager() { @Override From 4cb44552440b00083299d7b4ba65784e960b8ae6 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 15:09:40 +0100 Subject: [PATCH 06/21] Extract SSL bypassing --- .../logic/importer/fetcher/MrDLibFetcher.java | 3 +- .../jabref/logic/importer/fetcher/zbMATH.java | 49 +------------------ .../org/jabref/logic/net/URLDownload.java | 34 ++++++------- 3 files changed, 21 insertions(+), 65 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java index 50b41cdad8e..aa04a33ce40 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java @@ -86,7 +86,8 @@ public List performSearch(BibEntry entry) throws FetcherException { */ private String makeServerRequest(String queryByTitle) throws FetcherException { try { - URLDownload urlDownload = new URLDownload(constructQuery(queryByTitle), false); + URLDownload urlDownload = new URLDownload(constructQuery(queryByTitle)); + urlDownload.bypassSSLVerification(); String response = urlDownload.downloadToString(StandardCharsets.UTF_8); //Conversion of < and > diff --git a/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java b/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java index 244340d80a1..ac4d21817c9 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java @@ -20,6 +20,7 @@ import org.jabref.logic.importer.Parser; import org.jabref.logic.importer.SearchBasedParserFetcher; import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.net.URLDownload; import org.jabref.model.cleanup.FieldFormatterCleanup; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; @@ -32,7 +33,6 @@ * Fetches data from the Zentralblatt Math (https://www.zbmath.org/) */ public class zbMATH implements SearchBasedParserFetcher { - private static final Log LOGGER = LogFactory.getLog(zbMATH.class); private final ImportFormatPreferences preferences; @@ -65,56 +65,11 @@ public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLE uriBuilder.addParameter("start", "0"); // start index uriBuilder.addParameter("count", "200"); // should return up to 200 items (instead of default 100) - fixSSLVerification(); + URLDownload.bypassSSLVerification(); return uriBuilder.build().toURL(); } - /** - * Older java VMs does not automatically trust the zbMATH certificate. In this case the following exception is thrown: - * sun.security.validator.ValidatorException: PKIX path building failed: - * sun.security.provider.certpath.SunCertPathBuilderException: unable to find - * valid certification path to requested target - * JM > 8u101 may trust the certificate by default according to http://stackoverflow.com/a/34111150/873661 - * - * We will fix this issue by accepting all (!) certificates. This is ugly; but as JabRef does not rely on - * security-relevant information this is kind of OK (no, actually it is not...). - * - * Taken from http://stackoverflow.com/a/6055903/873661 - */ - private void fixSSLVerification() { - - LOGGER.warn("Fix SSL exception by accepting ALL certificates"); - - // Create a trust manager that does not validate certificate chains - TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { - - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } }; - - // Install the all-trusting trust manager - try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - } catch (Exception e) { - LOGGER.error("SSL problem", e); - } - } - @Override public Parser getParser() { return new BibtexParser(preferences); diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 90c36ab4634..4055683a60d 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -66,29 +66,15 @@ public class URLDownload { * @throws MalformedURLException if no protocol is specified in the source, or an unknown protocol is found */ public URLDownload(String source) throws MalformedURLException { - this(new URL(source), true); - } - - /** - * @param source the URL to download from - * @param validateSSL if set to false, bypasses the SSL vlaidation - * @throws MalformedURLException if no protocol is specified in the source, or an unknown protocol is found - */ - public URLDownload(String source, boolean validateSSL) throws MalformedURLException { - this(new URL(source), validateSSL); + this(new URL(source)); } /** * @param source The URL to download. - * @param validateSSL if set to false, bypasses the SSL vlaidation */ - public URLDownload(URL source, boolean validateSSL) { + public URLDownload(URL source) { this.source = source; this.addHeader("User-Agent", URLDownload.USER_AGENT); - - if (!validateSSL) { - bypassSSLVerification(); - } } public String determineMimeType() throws IOException { @@ -230,7 +216,21 @@ public String toString() { return "URLDownload{" + "source=" + this.source + '}'; } - private void bypassSSLVerification() { + /** + * Older java VMs does not automatically trust the zbMATH certificate. In this case the following exception is thrown: + * sun.security.validator.ValidatorException: PKIX path building failed: + * sun.security.provider.certpath.SunCertPathBuilderException: unable to find + * valid certification path to requested target + * JM > 8u101 may trust the certificate by default according to http://stackoverflow.com/a/34111150/873661 + * + * We will fix this issue by accepting all (!) certificates. This is ugly; but as JabRef does not rely on + * security-relevant information this is kind of OK (no, actually it is not...). + * + * Taken from http://stackoverflow.com/a/6055903/873661 + */ + public static void bypassSSLVerification() { + LOGGER.warn("Fix SSL exceptions by accepting ALL certificates"); + // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = { new X509TrustManager() { @Override From 89c290c785a9ed52035a287aa9ff02bc6c0385d3 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 15:37:41 +0100 Subject: [PATCH 07/21] Fix imports --- .../java/org/jabref/logic/importer/fetcher/zbMATH.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java b/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java index ac4d21817c9..f4aca185790 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/zbMATH.java @@ -3,16 +3,8 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.security.SecureRandom; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.util.Objects; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - import org.jabref.logic.cleanup.MoveFieldCleanup; import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; import org.jabref.logic.importer.FetcherException; From 33a57b38d3926585ed23ef77013c25dc40f411e3 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 16:27:53 +0100 Subject: [PATCH 08/21] Inline Mime type detection --- .../externalfiles/DownloadExternalFile.java | 2 +- .../logic/importer/MimeTypeDetector.java | 58 ------------------ .../logic/importer/fetcher/DoiResolution.java | 4 +- .../org/jabref/logic/net/URLDownload.java | 56 +++++++++++++---- .../MimeTypeDetectorTest.java | 37 +++++------ .../org/jabref/logic/net/URLDownloadTest.java | 2 +- .../jabref/logic/{importer => net}/empty.pdf | Bin 7 files changed, 63 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/org/jabref/logic/importer/MimeTypeDetector.java rename src/test/java/org/jabref/logic/{importer => net}/MimeTypeDetectorTest.java (71%) rename src/test/resources/org/jabref/logic/{importer => net}/empty.pdf (100%) diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java index 7fa0e9edcab..d3f242bcf15 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java @@ -104,7 +104,7 @@ public void download(URL url, final DownloadCallback callback) throws IOExceptio try { // TODO: what if this takes long time? // TODO: stop editor dialog if this results in an error: - mimeType = udl.determineMimeType(); // Read MIME type + mimeType = udl.getMimeType(); // Read MIME type } catch (IOException ex) { JOptionPane.showMessageDialog(frame, Localization.lang("Invalid URL") + ": " + ex.getMessage(), Localization.lang("Download file"), JOptionPane.ERROR_MESSAGE); diff --git a/src/main/java/org/jabref/logic/importer/MimeTypeDetector.java b/src/main/java/org/jabref/logic/importer/MimeTypeDetector.java deleted file mode 100644 index 08f38940461..00000000000 --- a/src/main/java/org/jabref/logic/importer/MimeTypeDetector.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.jabref.logic.importer; - -import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; -import java.util.Optional; - -import com.mashape.unirest.http.Unirest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -public class MimeTypeDetector { - private static final Log LOGGER = LogFactory.getLog(MimeTypeDetector.class); - - public static boolean isPdfContentType(String url) { - Optional contentType = getMimeType(url); - - return contentType.isPresent() && contentType.get().toLowerCase().startsWith("application/pdf"); - } - - private static Optional getMimeType(String url) { - Unirest.setDefaultHeader("User-Agent", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); - - // Try to use HEAD request to avoid donloading the whole file - String contentType; - try { - contentType = Unirest.head(url).asString().getHeaders().get("Content-Type").get(0); - - if (contentType != null) { - return Optional.of(contentType); - } - } catch (Exception e) { - LOGGER.debug("Error getting MIME type of URL via HEAD request", e); - } - - // Use GET request as alternative if no HEAD request is available - try { - contentType = Unirest.get(url).asString().getHeaders().get("Content-Type").get(0); - - if (contentType != null) { - return Optional.of(contentType); - } - } catch (Exception e) { - LOGGER.debug("Error getting MIME type of URL via GET request", e); - } - - // Try to resolve local URIs - try { - URLConnection connection = new URL(url).openConnection(); - - return Optional.ofNullable(connection.getContentType()); - } catch (IOException e) { - LOGGER.debug("Error trying to get MIME type of local URI", e); - } - - return Optional.empty(); - } -} diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java index 086d0ece524..69c987fee06 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java @@ -9,7 +9,7 @@ import java.util.Optional; import org.jabref.logic.importer.FulltextFetcher; -import org.jabref.logic.importer.MimeTypeDetector; +import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.DOI; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; @@ -61,7 +61,7 @@ public Optional findFullText(BibEntry entry) throws IOException { // Only check if pdf is included in the link or inside the text // ACM uses tokens without PDF inside the link // See https://github.com/lehner/LocalCopy for more scrape ideas - if ((href.contains("pdf") || hrefText.contains("pdf")) && MimeTypeDetector.isPdfContentType(href)) { + if ((href.contains("pdf") || hrefText.contains("pdf")) && new URLDownload(href).isMimeType("application/pdf")) { links.add(Optional.of(new URL(href))); } } diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 4055683a60d..8c4682d461d 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -37,6 +37,7 @@ import org.jabref.logic.util.io.FileUtil; +import com.mashape.unirest.http.Unirest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -47,7 +48,7 @@ * URLDownload dl = new URLDownload(URL); * String content = dl.downloadToString(ENCODING); * dl.downloadToFile(Path); // available in FILE - * String contentType = dl.determineMimeType(); + * String contentType = dl.getMimeType(); * * Each call to a public method creates a new HTTP connection. Nothing is cached. */ @@ -77,18 +78,48 @@ public URLDownload(URL source) { this.addHeader("User-Agent", URLDownload.USER_AGENT); } - public String determineMimeType() throws IOException { - // this does not cause a real performance issue as the underlying HTTP/TCP connection is reused - URLConnection urlConnection = this.openConnection(); + public String getMimeType() throws IOException { + Unirest.setDefaultHeader("User-Agent", "Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6"); + + String contentType = ""; + // Try to use HEAD request to avoid downloading the whole file try { - return urlConnection.getContentType(); - } finally { - try { - urlConnection.getInputStream().close(); - } catch (IOException ignored) { - // Ignored - } + contentType = Unirest.head(source.toString()).asString().getHeaders().get("Content-Type").get(0); + } catch (Exception e) { + LOGGER.debug("Error getting MIME type of URL via HEAD request", e); } + + // Use GET request as alternative if no HEAD request is available + try { + contentType = Unirest.get(source.toString()).asString().getHeaders().get("Content-Type").get(0); + } catch (Exception e) { + LOGGER.debug("Error getting MIME type of URL via GET request", e); + } + + // Try to resolve local URIs + try { + URLConnection connection = new URL(source.toString()).openConnection(); + + contentType = connection.getContentType(); + } catch (IOException e) { + LOGGER.debug("Error trying to get MIME type of local URI", e); + } + + if (contentType != null) { + return contentType; + } else { + return ""; + } + } + + public boolean isMimeType(String type) throws IOException { + String mime = getMimeType(); + + if (mime.isEmpty()) { + return false; + } + + return mime.startsWith(type); } public void addHeader(String key, String value) { @@ -182,11 +213,10 @@ private void copy(InputStream in, Writer out, Charset encoding) throws IOExcepti } public void downloadToFile(Path destination) throws IOException { - try (InputStream input = new BufferedInputStream(this.openConnection().getInputStream())) { Files.copy(input, destination, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { - URLDownload.LOGGER.warn("Could not copy input", e); + LOGGER.warn("Could not copy input", e); throw e; } } diff --git a/src/test/java/org/jabref/logic/importer/MimeTypeDetectorTest.java b/src/test/java/org/jabref/logic/net/MimeTypeDetectorTest.java similarity index 71% rename from src/test/java/org/jabref/logic/importer/MimeTypeDetectorTest.java rename to src/test/java/org/jabref/logic/net/MimeTypeDetectorTest.java index fc1b7ee01be..ce6efd9f64b 100644 --- a/src/test/java/org/jabref/logic/importer/MimeTypeDetectorTest.java +++ b/src/test/java/org/jabref/logic/net/MimeTypeDetectorTest.java @@ -1,5 +1,6 @@ -package org.jabref.logic.importer; +package org.jabref.logic.net; +import java.io.IOException; import java.net.URISyntaxException; import com.github.tomakehurst.wiremock.junit.WireMockRule; @@ -20,7 +21,7 @@ public class MimeTypeDetectorTest { public WireMockRule wireMockRule = new WireMockRule(); @Test - public void handlePermanentRedirections() { + public void handlePermanentRedirections() throws IOException { String redirectedUrl = "http://localhost:8080/redirection"; stubFor(any(urlEqualTo("/redirection")) @@ -31,35 +32,29 @@ public void handlePermanentRedirections() { ) ); - assertTrue(MimeTypeDetector.isPdfContentType(redirectedUrl)); + assertTrue(new URLDownload(redirectedUrl).isMimeType("application/pdf")); } @Test - public void beFalseForInvalidUrl() { - String invalidUrl = "thisisnourl"; - assertFalse(MimeTypeDetector.isPdfContentType(invalidUrl)); - } - - @Test - public void beFalseForUnreachableUrl() { + public void beFalseForUnreachableUrl() throws IOException { String invalidUrl = "http://idontknowthisurlforsure.de"; - assertFalse(MimeTypeDetector.isPdfContentType(invalidUrl)); + assertFalse(new URLDownload(invalidUrl).isMimeType("application/pdf")); } @Test - public void beTrueForPdfMimeType() { + public void beTrueForPdfMimeType() throws IOException { String pdfUrl = "http://docs.oasis-open.org/wsbpel/2.0/OS/wsbpel-v2.0-OS.pdf"; - assertTrue(MimeTypeDetector.isPdfContentType(pdfUrl)); + assertTrue(new URLDownload(pdfUrl).isMimeType("application/pdf")); } @Test - public void beTrueForLocalPdfUri() throws URISyntaxException { + public void beTrueForLocalPdfUri() throws URISyntaxException, IOException { String localPath = MimeTypeDetectorTest.class.getResource("empty.pdf").toURI().toASCIIString(); - assertTrue(MimeTypeDetector.isPdfContentType(localPath)); + assertTrue(new URLDownload(localPath).isMimeType("application/pdf")); } @Test - public void beTrueForPDFMimeTypeVariations() { + public void beTrueForPDFMimeTypeVariations() throws IOException { String mimeTypeVariation = "http://localhost:8080/mimevariation"; stubFor(any(urlEqualTo("/mimevariation")) @@ -68,11 +63,11 @@ public void beTrueForPDFMimeTypeVariations() { ) ); - assertTrue(MimeTypeDetector.isPdfContentType(mimeTypeVariation)); + assertTrue(new URLDownload(mimeTypeVariation).isMimeType("application/pdf")); } @Test - public void beAbleToUseHeadRequest() { + public void beAbleToUseHeadRequest() throws IOException { String mimeTypeVariation = "http://localhost:8080/mimevariation"; stubFor(head(urlEqualTo("/mimevariation")) @@ -81,11 +76,11 @@ public void beAbleToUseHeadRequest() { ) ); - assertTrue(MimeTypeDetector.isPdfContentType(mimeTypeVariation)); + assertTrue(new URLDownload(mimeTypeVariation).isMimeType("application/pdf")); } @Test - public void beAbleToUseGetRequest() { + public void beAbleToUseGetRequest() throws IOException { String mimeTypeVariation = "http://localhost:8080/mimevariation"; stubFor(head(urlEqualTo("/mimevariation")) @@ -99,6 +94,6 @@ public void beAbleToUseGetRequest() { ) ); - assertTrue(MimeTypeDetector.isPdfContentType(mimeTypeVariation)); + assertTrue(new URLDownload(mimeTypeVariation).isMimeType("application/pdf")); } } diff --git a/src/test/java/org/jabref/logic/net/URLDownloadTest.java b/src/test/java/org/jabref/logic/net/URLDownloadTest.java index 0f5f6b240ab..29caaac0c74 100644 --- a/src/test/java/org/jabref/logic/net/URLDownloadTest.java +++ b/src/test/java/org/jabref/logic/net/URLDownloadTest.java @@ -48,7 +48,7 @@ public void testFileDownload() throws IOException { public void testDetermineMimeType() throws IOException { URLDownload dl = new URLDownload(new URL("http://www.google.com")); - Assert.assertTrue(dl.determineMimeType().startsWith("text/html")); + Assert.assertTrue(dl.getMimeType().startsWith("text/html")); } @Test diff --git a/src/test/resources/org/jabref/logic/importer/empty.pdf b/src/test/resources/org/jabref/logic/net/empty.pdf similarity index 100% rename from src/test/resources/org/jabref/logic/importer/empty.pdf rename to src/test/resources/org/jabref/logic/net/empty.pdf From 01d9608e151fd6e05dfa7140c08e955bb3f63a1a Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 16:33:08 +0100 Subject: [PATCH 09/21] Refactoring method names --- src/main/java/org/jabref/cli/ArgumentProcessor.java | 2 +- .../gui/externalfiles/DownloadExternalFile.java | 2 +- .../gui/groups/EntryTableTransferHandler.java | 2 +- .../org/jabref/logic/importer/FulltextFetchers.java | 3 ++- src/main/java/org/jabref/logic/net/URLDownload.java | 13 +++++++------ .../java/org/jabref/logic/net/URLDownloadTest.java | 12 ++++++------ 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index a321718c9b0..1d11dcc723f 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -91,7 +91,7 @@ private static Optional importFile(String argument) { if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { // Download web resource to temporary file try { - file = new URLDownload(address).downloadToTemporaryFile(); + file = new URLDownload(address).toTemporaryFile(); } catch (IOException e) { System.err.println(Localization.lang("Problem downloading from %1", address) + e.getLocalizedMessage()); return Optional.empty(); diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java index d3f242bcf15..ea2b6ff4d1f 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadExternalFile.java @@ -116,7 +116,7 @@ public void download(URL url, final DownloadCallback callback) throws IOExceptio JabRefExecutorService.INSTANCE.execute(() -> { try { - udlF.downloadToFile(tmp.toPath()); + udlF.toFile(tmp.toPath()); } catch (IOException e2) { dontShowDialog = true; if ((editor != null) && editor.isVisible()) { diff --git a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java index 44d442d9f83..213436683f9 100644 --- a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java +++ b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java @@ -377,7 +377,7 @@ private boolean handleDropTransfer(URL dropLink) throws IOException { File tmpfile = File.createTempFile("jabrefimport", ""); tmpfile.deleteOnExit(); - new URLDownload(dropLink).downloadToFile(tmpfile.toPath()); + new URLDownload(dropLink).toFile(tmpfile.toPath()); // Import into new if entryTable==null, otherwise into current library: ImportMenuItem importer = new ImportMenuItem(frame, entryTable == null); diff --git a/src/main/java/org/jabref/logic/importer/FulltextFetchers.java b/src/main/java/org/jabref/logic/importer/FulltextFetchers.java index d7df16071ac..9404c65f205 100644 --- a/src/main/java/org/jabref/logic/importer/FulltextFetchers.java +++ b/src/main/java/org/jabref/logic/importer/FulltextFetchers.java @@ -14,6 +14,7 @@ import org.jabref.logic.importer.fetcher.IEEE; import org.jabref.logic.importer.fetcher.ScienceDirect; import org.jabref.logic.importer.fetcher.SpringerLink; +import org.jabref.logic.net.URLDownload; import org.jabref.logic.util.DOI; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; @@ -59,7 +60,7 @@ public Optional findFullTextPDF(BibEntry entry) { try { Optional result = finder.findFullText(clonedEntry); - if (result.isPresent() && MimeTypeDetector.isPdfContentType(result.get().toString())) { + if (result.isPresent() && new URLDownload(result.get().toString()).isMimeType("application/pdf")) { return result; } } catch (IOException | FetcherException e) { diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 8c4682d461d..d4dbd5cb5d5 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -47,7 +47,7 @@ * Example: * URLDownload dl = new URLDownload(URL); * String content = dl.downloadToString(ENCODING); - * dl.downloadToFile(Path); // available in FILE + * dl.toFile(Path); // available in FILE * String contentType = dl.getMimeType(); * * Each call to a public method creates a new HTTP connection. Nothing is cached. @@ -212,7 +212,7 @@ private void copy(InputStream in, Writer out, Charset encoding) throws IOExcepti } } - public void downloadToFile(Path destination) throws IOException { + public void toFile(Path destination) throws IOException { try (InputStream input = new BufferedInputStream(this.openConnection().getInputStream())) { Files.copy(input, destination, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { @@ -224,11 +224,11 @@ public void downloadToFile(Path destination) throws IOException { /** * Downloads the web resource to a temporary file. * - * @return the path to the downloaded file. + * @return the path of the temporary file. */ - public Path downloadToTemporaryFile() throws IOException { + public Path toTemporaryFile() throws IOException { // Determine file name and extension from source url - String sourcePath = this.source.getPath(); + String sourcePath = source.getPath(); // Take everything after the last '/' as name + extension String fileNameWithExtension = sourcePath.substring(sourcePath.lastIndexOf('/') + 1); @@ -237,7 +237,8 @@ public Path downloadToTemporaryFile() throws IOException { // Create temporary file and download to it Path file = Files.createTempFile(fileName, extension); - this.downloadToFile(file); + toFile(file); + return file; } diff --git a/src/test/java/org/jabref/logic/net/URLDownloadTest.java b/src/test/java/org/jabref/logic/net/URLDownloadTest.java index 29caaac0c74..6586e48415b 100644 --- a/src/test/java/org/jabref/logic/net/URLDownloadTest.java +++ b/src/test/java/org/jabref/logic/net/URLDownloadTest.java @@ -34,7 +34,7 @@ public void testFileDownload() throws IOException { File destination = File.createTempFile("jabref-test", ".html"); try { URLDownload dl = new URLDownload(new URL("http://www.google.com")); - dl.downloadToFile(destination.toPath()); + dl.toFile(destination.toPath()); Assert.assertTrue("file must exist", destination.exists()); } finally { // cleanup @@ -55,7 +55,7 @@ public void testDetermineMimeType() throws IOException { public void downloadToTemporaryFilePathWithoutFileSavesAsTmpFile() throws IOException { URLDownload google = new URLDownload(new URL("http://www.google.com")); - String path = google.downloadToTemporaryFile().toString(); + String path = google.toTemporaryFile().toString(); Assert.assertTrue(path, path.endsWith(".tmp")); } @@ -63,7 +63,7 @@ public void downloadToTemporaryFilePathWithoutFileSavesAsTmpFile() throws IOExce public void downloadToTemporaryFileKeepsName() throws IOException { URLDownload google = new URLDownload(new URL("https://github.com/JabRef/jabref/blob/master/LICENSE.md")); - String path = google.downloadToTemporaryFile().toString(); + String path = google.toTemporaryFile().toString(); Assert.assertTrue(path, path.contains("LICENSE") && path.endsWith(".md")); } @@ -71,7 +71,7 @@ public void downloadToTemporaryFileKeepsName() throws IOException { public void downloadOfFTPSucceeds() throws IOException { URLDownload ftp = new URLDownload(new URL("ftp://ftp.informatik.uni-stuttgart.de/pub/library/ncstrl.ustuttgart_fi/INPROC-2016-15/INPROC-2016-15.pdf")); - Path path = ftp.downloadToTemporaryFile(); + Path path = ftp.toTemporaryFile(); Assert.assertNotNull(path); } @@ -79,7 +79,7 @@ public void downloadOfFTPSucceeds() throws IOException { public void downloadOfHttpSucceeds() throws IOException { URLDownload ftp = new URLDownload(new URL("http://www.jabref.org")); - Path path = ftp.downloadToTemporaryFile(); + Path path = ftp.toTemporaryFile(); Assert.assertNotNull(path); } @@ -87,7 +87,7 @@ public void downloadOfHttpSucceeds() throws IOException { public void downloadOfHttpsSucceeds() throws IOException { URLDownload ftp = new URLDownload(new URL("https://www.jabref.org")); - Path path = ftp.downloadToTemporaryFile(); + Path path = ftp.toTemporaryFile(); Assert.assertNotNull(path); } From 55864a44e4416488808b6163c930945daecc1034 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Thu, 23 Feb 2017 16:47:02 +0100 Subject: [PATCH 10/21] Rename downloadToString method to asString --- .../importer/fetcher/ACMPortalFetcher.java | 4 +- .../importer/fetcher/CiteSeerXFetcher.java | 4 +- .../importer/fetcher/IEEEXploreFetcher.java | 4 +- .../importer/fetcher/BibsonomyScraper.java | 2 +- .../logic/importer/fetcher/DoiFetcher.java | 2 +- .../logic/importer/fetcher/GoogleScholar.java | 4 +- .../jabref/logic/importer/fetcher/IEEE.java | 4 +- .../logic/importer/fetcher/MrDLibFetcher.java | 2 +- .../org/jabref/logic/net/URLDownload.java | 114 +++++++++--------- .../org/jabref/logic/net/URLDownloadTest.java | 4 +- 10 files changed, 75 insertions(+), 69 deletions(-) diff --git a/src/main/java/org/jabref/gui/importer/fetcher/ACMPortalFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/ACMPortalFetcher.java index c30eb6cf0ab..c318c4f6675 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/ACMPortalFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/ACMPortalFetcher.java @@ -133,7 +133,7 @@ public boolean processQueryGetPreview(String query, FetcherPreviewDialog preview try { URLDownload dl = new URLDownload(address); - String page = dl.downloadToString(Globals.prefs.getDefaultEncoding()); + String page = dl.asString(Globals.prefs.getDefaultEncoding()); int hits = getNumberOfHits(page, RESULTS_FOUND_PATTERN, ACMPortalFetcher.HITS_PATTERN); @@ -336,7 +336,7 @@ private static Optional downloadEntryBibTeX(String id, boolean downloa // get abstract if (downloadAbstract) { URLDownload dl = new URLDownload(ACMPortalFetcher.START_URL + ACMPortalFetcher.ABSTRACT_URL + id); - String page = dl.downloadToString(Globals.prefs.getDefaultEncoding()); + String page = dl.asString(Globals.prefs.getDefaultEncoding()); Matcher absM = ACMPortalFetcher.ABSTRACT_PATTERN.matcher(page); if (absM.find()) { diff --git a/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java index 2409ee9cbed..44bbcc2fd31 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java @@ -115,7 +115,7 @@ private List getCitations(String query) throws IOException { } private static String getCitationsFromUrl(String urlQuery, List ids) throws IOException { - String cont = new URLDownload(urlQuery).downloadToString(Globals.prefs.getDefaultEncoding()); + String cont = new URLDownload(urlQuery).asString(Globals.prefs.getDefaultEncoding()); Matcher m = CiteSeerXFetcher.CITE_LINK_PATTERN.matcher(cont); while (m.find()) { ids.add(CiteSeerXFetcher.URL_START + m.group(1)); @@ -127,7 +127,7 @@ private static String getCitationsFromUrl(String urlQuery, List ids) thr private static BibEntry getSingleCitation(String urlString) throws IOException { - String cont = new URLDownload(urlString).downloadToString(StandardCharsets.UTF_8); + String cont = new URLDownload(urlString).asString(StandardCharsets.UTF_8); // Find title, and create entry if we do. Otherwise assume we did not get an entry: Matcher m = CiteSeerXFetcher.TITLE_PATTERN.matcher(cont); diff --git a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java index 6b8c1d6dc38..7ca46e27e66 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java @@ -110,7 +110,7 @@ public boolean processQuery(String query, ImportInspector dialog, OutputPrinter dl.setPostData(postData); //retrieve the search results - String page = dl.downloadToString(StandardCharsets.UTF_8); + String page = dl.asString(StandardCharsets.UTF_8); //the page can be blank if the search did not work (not sure the exact conditions that lead to this, but declaring it an invalid search for now) if (page.isEmpty()) { @@ -141,7 +141,7 @@ public boolean processQuery(String query, ImportInspector dialog, OutputPrinter //fetch the raw Bibtex results from IEEEXplore String bibtexPage = new URLDownload(createBibtexQueryURL(searchResultsJson)) - .downloadToString(Globals.prefs.getDefaultEncoding()); + .asString(Globals.prefs.getDefaultEncoding()); //preprocess the result (eg. convert HTML escaped characters to latex and do other formatting not performed by BibtexParser) bibtexPage = preprocessBibtexResultsPage(bibtexPage); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java b/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java index dc709afc5f6..ee4b8c202d9 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java @@ -37,7 +37,7 @@ public static Optional getEntry(String entryUrl, ImportFormatPreferenc .replace("&", "%26").replace("=", "%3D"); URL url = new URL(BibsonomyScraper.BIBSONOMY_SCRAPER + cleanURL + BibsonomyScraper.BIBSONOMY_SCRAPER_POST); - String bibtex = new URLDownload(url).downloadToString(StandardCharsets.UTF_8); + String bibtex = new URLDownload(url).asString(StandardCharsets.UTF_8); return BibtexParser.singleFromString(bibtex, importFormatPreferences); } catch (IOException ex) { LOGGER.warn("Could not download entry", ex); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java index 027bcdac560..c2d90d2b7e3 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java @@ -53,7 +53,7 @@ public Optional performSearchById(String identifier) throws FetcherExc // BibTeX data URLDownload download = new URLDownload(doiURL); download.addHeader("Accept", "application/x-bibtex"); - String bibtexString = download.downloadToString(StandardCharsets.UTF_8); + String bibtexString = download.asString(StandardCharsets.UTF_8); // BibTeX entry Optional fetchedEntry = BibtexParser.singleFromString(bibtexString, preferences); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java index 5be996eea31..a3406537080 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java @@ -142,7 +142,7 @@ public List performSearch(String query) throws FetcherException { } private void addHitsFromQuery(List entryList, String queryURL) throws IOException, FetcherException { - String content = new URLDownload(queryURL).downloadToString(StandardCharsets.UTF_8); + String content = new URLDownload(queryURL).asString(StandardCharsets.UTF_8); Matcher matcher = LINK_TO_BIB_PATTERN.matcher(content); while (matcher.find()) { @@ -153,7 +153,7 @@ private void addHitsFromQuery(List entryList, String queryURL) throws } private BibEntry downloadEntry(String link) throws IOException, FetcherException { - String downloadedContent = new URLDownload(link).downloadToString(StandardCharsets.UTF_8); + String downloadedContent = new URLDownload(link).asString(StandardCharsets.UTF_8); BibtexParser parser = new BibtexParser(importFormatPreferences); ParserResult result = parser.parse(new StringReader(downloadedContent)); if ((result == null) || (result.getDatabase() == null)) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index e64032d243e..eccafb2c1bf 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -54,7 +54,7 @@ public Optional findFullText(BibEntry entry) throws IOException { if (doi.isPresent() && doi.get().getDOI().startsWith(IEEE_DOI) && doi.get().getURI().isPresent()) { // Download the HTML page from IEEE String resolvedDOIPage = new URLDownload(doi.get().getURI().get().toURL()) - .downloadToString(StandardCharsets.UTF_8); + .asString(StandardCharsets.UTF_8); // Try to find the link Matcher matcher = STAMP_PATTERN.matcher(resolvedDOIPage); if (matcher.find()) { @@ -70,7 +70,7 @@ public Optional findFullText(BibEntry entry) throws IOException { } // Download the HTML page containing a frame with the PDF - String framePage = new URLDownload(BASE_URL + stampString).downloadToString(StandardCharsets.UTF_8); + String framePage = new URLDownload(BASE_URL + stampString).asString(StandardCharsets.UTF_8); // Try to find the direct PDF link Matcher matcher = PDF_PATTERN.matcher(framePage); if (matcher.find()) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java index aa04a33ce40..09c8591b7d5 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java @@ -88,7 +88,7 @@ private String makeServerRequest(String queryByTitle) throws FetcherException { try { URLDownload urlDownload = new URLDownload(constructQuery(queryByTitle)); urlDownload.bypassSSLVerification(); - String response = urlDownload.downloadToString(StandardCharsets.UTF_8); + String response = urlDownload.asString(StandardCharsets.UTF_8); //Conversion of < and > response = response.replaceAll(">", ">"); diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index d4dbd5cb5d5..1e71577d210 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -46,7 +46,7 @@ *

* Example: * URLDownload dl = new URLDownload(URL); - * String content = dl.downloadToString(ENCODING); + * String content = dl.asString(ENCODING); * dl.toFile(Path); // available in FILE * String contentType = dl.getMimeType(); * @@ -132,53 +132,19 @@ public void setPostData(String postData) { } } - private URLConnection openConnection() throws IOException { - URLConnection connection = this.source.openConnection(); - for (Entry entry : this.parameters.entrySet()) { - connection.setRequestProperty(entry.getKey(), entry.getValue()); - } - if (!this.postData.isEmpty()) { - connection.setDoOutput(true); - try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { - wr.writeBytes(this.postData); - } - - } - - if (connection instanceof HttpURLConnection) { - // normally, 3xx is redirect - int status = ((HttpURLConnection) connection).getResponseCode(); - if (status != HttpURLConnection.HTTP_OK) { - if (status == HttpURLConnection.HTTP_MOVED_TEMP - || status == HttpURLConnection.HTTP_MOVED_PERM - || status == HttpURLConnection.HTTP_SEE_OTHER) { - // get redirect url from "location" header field - String newUrl = connection.getHeaderField("Location"); - // open the new connnection again - connection = new URLDownload(newUrl).openConnection(); - } - } - } - - // this does network i/o: GET + read returned headers - connection.connect(); - - return connection; - } - /** + * Downloads the web resource to a String. * + * @param encoding the desired String encoding * @return the downloaded string - * @throws IOException */ - public String downloadToString(Charset encoding) throws IOException { - + public String asString(Charset encoding) throws IOException { try (InputStream input = new BufferedInputStream(this.openConnection().getInputStream()); Writer output = new StringWriter()) { - this.copy(input, output, encoding); + copy(input, output, encoding); return output.toString(); } catch (IOException e) { - URLDownload.LOGGER.warn("Could not copy input", e); + LOGGER.warn("Could not copy input", e); throw e; } } @@ -194,24 +160,16 @@ public List getCookieFromUrl() throws IOException { try { return cookieManager.getCookieStore().get(this.source.toURI()); } catch (URISyntaxException e) { - URLDownload.LOGGER.error("Unable to convert download URL to URI", e); + LOGGER.error("Unable to convert download URL to URI", e); return Collections.emptyList(); } } - private void copy(InputStream in, Writer out, Charset encoding) throws IOException { - InputStream monitoredInputStream = in; - Reader r = new InputStreamReader(monitoredInputStream, encoding); - try (BufferedReader read = new BufferedReader(r)) { - - String line; - while ((line = read.readLine()) != null) { - out.write(line); - out.write("\n"); - } - } - } - + /** + * Downloads the web resource to a file. + * + * @param destination the destination file path. + */ public void toFile(Path destination) throws IOException { try (InputStream input = new BufferedInputStream(this.openConnection().getInputStream())) { Files.copy(input, destination, StandardCopyOption.REPLACE_EXISTING); @@ -287,4 +245,52 @@ public X509Certificate[] getAcceptedIssuers() { LOGGER.error("A problem occurred when bypassing SSL verification", e); } } + + private void copy(InputStream in, Writer out, Charset encoding) throws IOException { + InputStream monitoredInputStream = in; + Reader r = new InputStreamReader(monitoredInputStream, encoding); + try (BufferedReader read = new BufferedReader(r)) { + + String line; + while ((line = read.readLine()) != null) { + out.write(line); + out.write("\n"); + } + } + } + + private URLConnection openConnection() throws IOException { + URLConnection connection = this.source.openConnection(); + for (Entry entry : this.parameters.entrySet()) { + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + if (!this.postData.isEmpty()) { + connection.setDoOutput(true); + try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { + wr.writeBytes(this.postData); + } + + } + + if (connection instanceof HttpURLConnection) { + // normally, 3xx is redirect + int status = ((HttpURLConnection) connection).getResponseCode(); + if (status != HttpURLConnection.HTTP_OK) { + if (status == HttpURLConnection.HTTP_MOVED_TEMP + || status == HttpURLConnection.HTTP_MOVED_PERM + || status == HttpURLConnection.HTTP_SEE_OTHER) { + // get redirect url from "location" header field + String newUrl = connection.getHeaderField("Location"); + // open the new connnection again + connection = new URLDownload(newUrl).openConnection(); + } + } + } + + // this does network i/o: GET + read returned headers + connection.connect(); + + return connection; + } + } diff --git a/src/test/java/org/jabref/logic/net/URLDownloadTest.java b/src/test/java/org/jabref/logic/net/URLDownloadTest.java index 6586e48415b..5bc8f59a920 100644 --- a/src/test/java/org/jabref/logic/net/URLDownloadTest.java +++ b/src/test/java/org/jabref/logic/net/URLDownloadTest.java @@ -18,7 +18,7 @@ public void testStringDownloadWithSetEncoding() throws IOException { URLDownload dl = new URLDownload(new URL("http://www.google.com")); Assert.assertTrue("google.com should contain google", - dl.downloadToString(StandardCharsets.UTF_8).contains("Google")); + dl.asString(StandardCharsets.UTF_8).contains("Google")); } @Test @@ -26,7 +26,7 @@ public void testStringDownload() throws IOException { URLDownload dl = new URLDownload(new URL("http://www.google.com")); Assert.assertTrue("google.com should contain google", - dl.downloadToString(JabRefPreferences.getInstance().getDefaultEncoding()).contains("Google")); + dl.asString(JabRefPreferences.getInstance().getDefaultEncoding()).contains("Google")); } @Test From 1973386305a06235c3870485a5eee645e67a10df Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 23 Feb 2017 17:35:56 +0100 Subject: [PATCH 11/21] Make automatic groups a first-class citizien (#2563) * Add automatic groups * Add option for automatic groups in group dialog * Fix build and language files * Fix architecture tests * Update GroupNodeViewModel.java --- .../jabref/gui/groups/AutoGroupDialog.java | 264 ------------------ .../org/jabref/gui/groups/GroupDialog.java | 136 ++++++--- .../jabref/gui/groups/GroupNodeViewModel.java | 52 +++- .../org/jabref/gui/groups/GroupSelector.java | 11 - .../gui/groups/GroupTreeController.java | 2 +- .../logic/cleanup/MoveFieldCleanup.java | 2 +- .../jabref/logic/integrity/FieldChecker.java | 2 +- .../org/jabref/logic/util/OptionalUtil.java | 23 -- .../jabref/model/database/BibDatabase.java | 87 +++--- .../jabref/model/groups/AutomaticGroup.java | 23 ++ .../model/groups/AutomaticKeywordGroup.java | 44 +++ .../model/groups/AutomaticPersonsGroup.java | 43 +++ .../org/jabref/model/util/OptionalUtil.java | 44 +++ src/main/resources/l10n/JabRef_da.properties | 9 - src/main/resources/l10n/JabRef_de.properties | 9 - src/main/resources/l10n/JabRef_en.properties | 9 - src/main/resources/l10n/JabRef_es.properties | 9 - src/main/resources/l10n/JabRef_fa.properties | 9 - src/main/resources/l10n/JabRef_fr.properties | 9 - src/main/resources/l10n/JabRef_in.properties | 9 - src/main/resources/l10n/JabRef_it.properties | 9 - src/main/resources/l10n/JabRef_ja.properties | 9 - src/main/resources/l10n/JabRef_nl.properties | 9 - src/main/resources/l10n/JabRef_no.properties | 9 - .../resources/l10n/JabRef_pt_BR.properties | 9 - src/main/resources/l10n/JabRef_ru.properties | 9 - src/main/resources/l10n/JabRef_sv.properties | 9 - src/main/resources/l10n/JabRef_tr.properties | 9 - src/main/resources/l10n/JabRef_vi.properties | 9 - src/main/resources/l10n/JabRef_zh.properties | 9 - .../java/org/jabref/ArchitectureTests.java | 30 +- .../gui/groups/GroupNodeViewModelTest.java | 40 +++ .../org/jabref/gui/groups/GroupsUtilTest.java | 53 ---- .../groups/AutomaticKeywordGroupTest.java | 24 ++ 34 files changed, 421 insertions(+), 612 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/groups/AutoGroupDialog.java delete mode 100644 src/main/java/org/jabref/logic/util/OptionalUtil.java create mode 100644 src/main/java/org/jabref/model/groups/AutomaticGroup.java create mode 100644 src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java create mode 100644 src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java create mode 100644 src/main/java/org/jabref/model/util/OptionalUtil.java create mode 100644 src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java delete mode 100644 src/test/java/org/jabref/gui/groups/GroupsUtilTest.java create mode 100644 src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java diff --git a/src/main/java/org/jabref/gui/groups/AutoGroupDialog.java b/src/main/java/org/jabref/gui/groups/AutoGroupDialog.java deleted file mode 100644 index 0fe42dd5b6d..00000000000 --- a/src/main/java/org/jabref/gui/groups/AutoGroupDialog.java +++ /dev/null @@ -1,264 +0,0 @@ -package org.jabref.gui.groups; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.TreeSet; -import java.util.stream.Collectors; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.BorderFactory; -import javax.swing.ButtonGroup; -import javax.swing.InputMap; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JTextField; -import javax.swing.event.CaretEvent; -import javax.swing.event.CaretListener; - -import org.jabref.Globals; -import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.keyboard.KeyBinding; -import org.jabref.gui.undo.NamedCompound; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.layout.format.LatexToUnicodeFormatter; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.entry.Author; -import org.jabref.model.entry.AuthorList; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.FieldName; -import org.jabref.model.groups.ExplicitGroup; -import org.jabref.model.groups.GroupHierarchyType; -import org.jabref.model.groups.GroupTreeNode; -import org.jabref.model.groups.WordKeywordGroup; -import org.jabref.model.strings.StringUtil; - -import com.jgoodies.forms.builder.ButtonBarBuilder; -import com.jgoodies.forms.builder.FormBuilder; -import com.jgoodies.forms.layout.FormLayout; - -/** - * Dialog for creating or modifying groups. Operates directly on the Vector containing group information. - */ -class AutoGroupDialog extends JDialog implements CaretListener { - - private final JTextField remove = new JTextField(60); - private final JTextField field = new JTextField(60); - private final JTextField deliminator = new JTextField(60); - private final JRadioButton keywords = new JRadioButton( - Localization.lang("Generate groups from keywords in a BibTeX field")); - private final JRadioButton authors = new JRadioButton(Localization.lang("Generate groups for author last names")); - private final JRadioButton editors = new JRadioButton(Localization.lang("Generate groups for editor last names")); - private final JCheckBox useCustomDelimiter = new JCheckBox( - Localization.lang("Use the following delimiter character(s):")); - private final JButton ok = new JButton(Localization.lang("OK")); - private final GroupTreeNodeViewModel m_groupsRoot; - private final JabRefFrame frame; - private final BasePanel panel; - - - /** - * @param groupsRoot The original set of groups, which is required as undo information when all groups are cleared. - */ - public AutoGroupDialog(JabRefFrame jabrefFrame, BasePanel basePanel, - GroupTreeNodeViewModel groupsRoot, String defaultField, String defaultRemove, String defaultDeliminator) { - super(jabrefFrame, Localization.lang("Automatically create groups"), true); - frame = jabrefFrame; - panel = basePanel; - m_groupsRoot = groupsRoot; - field.setText(defaultField); - remove.setText(defaultRemove); - deliminator.setText(defaultDeliminator); - useCustomDelimiter.setSelected(true); - ActionListener okListener = e -> { - dispose(); - - try { - GroupTreeNode autoGroupsRoot = GroupTreeNode.fromGroup( - new ExplicitGroup(Localization.lang("Automatically created groups"), - GroupHierarchyType.INCLUDING, - Globals.prefs.getKeywordDelimiter())); - Set keywords; - String fieldText = field.getText().toLowerCase().trim(); - if (this.keywords.isSelected()) { - if (useCustomDelimiter.isSelected()) { - keywords = findDeliminatedWordsInField(panel.getDatabase(), fieldText, - deliminator.getText()); - } else { - keywords = findAllWordsInField(panel.getDatabase(), fieldText, remove.getText()); - - } - } else if (authors.isSelected()) { - List fields = new ArrayList<>(2); - fields.add(FieldName.AUTHOR); - keywords = findAuthorLastNames(panel.getDatabase(), fields); - fieldText = FieldName.AUTHOR; - } else { // editors.isSelected() as it is a radio button group. - List fields = new ArrayList<>(2); - fields.add(FieldName.EDITOR); - keywords = findAuthorLastNames(panel.getDatabase(), fields); - fieldText = FieldName.EDITOR; - } - - LatexToUnicodeFormatter formatter = new LatexToUnicodeFormatter(); - - for (String keyword : keywords) { - WordKeywordGroup group = new WordKeywordGroup( - formatter.format(keyword), GroupHierarchyType.INDEPENDENT, fieldText, keyword, false, Globals.prefs.getKeywordDelimiter(), false); - autoGroupsRoot.addChild(GroupTreeNode.fromGroup(group)); - } - - autoGroupsRoot.moveTo(m_groupsRoot.getNode()); - NamedCompound ce = new NamedCompound(Localization.lang("Automatically create groups")); - UndoableAddOrRemoveGroup undo = new UndoableAddOrRemoveGroup(m_groupsRoot, new GroupTreeNodeViewModel(autoGroupsRoot), UndoableAddOrRemoveGroup.ADD_NODE); - ce.addEdit(undo); - - panel.markBaseChanged(); // a change always occurs - frame.output(Localization.lang("Created groups.")); - ce.end(); - panel.getUndoManager().addEdit(ce); - } catch (IllegalArgumentException exception) { - frame.showMessage(exception.getLocalizedMessage()); - } - }; - remove.addActionListener(okListener); - field.addActionListener(okListener); - field.addCaretListener(this); - AbstractAction cancelAction = new AbstractAction() { - - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }; - JButton cancel = new JButton(Localization.lang("Cancel")); - cancel.addActionListener(cancelAction); - ok.addActionListener(okListener); - // Key bindings: - JPanel main = new JPanel(); - ActionMap am = main.getActionMap(); - InputMap im = main.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); - im.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_DIALOG), "close"); - am.put("close", cancelAction); - - ButtonGroup bg = new ButtonGroup(); - bg.add(keywords); - bg.add(authors); - bg.add(editors); - keywords.setSelected(true); - - FormBuilder b = FormBuilder.create(); - b.layout(new FormLayout("left:20dlu, 4dlu, left:pref, 4dlu, fill:60dlu", - "p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p")); - b.add(keywords).xyw(1, 1, 5); - b.add(Localization.lang("Field to group by") + ":").xy(3, 3); - b.add(field).xy(5, 3); - b.add(Localization.lang("Characters to ignore") + ":").xy(3, 5); - b.add(remove).xy(5, 5); - b.add(useCustomDelimiter).xy(3, 7); - b.add(deliminator).xy(5, 7); - b.add(authors).xyw(1, 9, 5); - b.add(editors).xyw(1, 11, 5); - b.build(); - b.border(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - - JPanel opt = new JPanel(); - ButtonBarBuilder bb = new ButtonBarBuilder(opt); - bb.addGlue(); - bb.addButton(ok); - bb.addButton(cancel); - bb.addGlue(); - - main.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - opt.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - getContentPane().add(main, BorderLayout.CENTER); - getContentPane().add(b.getPanel(), BorderLayout.CENTER); - getContentPane().add(opt, BorderLayout.SOUTH); - - updateComponents(); - pack(); - setLocationRelativeTo(frame); - } - - public static Set findDeliminatedWordsInField(BibDatabase db, String field, String deliminator) { - Set res = new TreeSet<>(); - - for (BibEntry be : db.getEntries()) { - be.getField(field).ifPresent(fieldValue -> { - StringTokenizer tok = new StringTokenizer(fieldValue.trim(), deliminator); - while (tok.hasMoreTokens()) { - res.add(StringUtil.capitalizeFirst(tok.nextToken().trim())); - } - }); - } - return res; - } - - /** - * Returns a Set containing all words used in the database in the given field type. Characters in - * remove are not included. - * - * @param db a BibDatabase value - * @param field a String value - * @param remove a String value - * @return a Set value - */ - public static Set findAllWordsInField(BibDatabase db, String field, String remove) { - Set res = new TreeSet<>(); - for (BibEntry be : db.getEntries()) { - be.getField(field).ifPresent(o -> { - StringTokenizer tok = new StringTokenizer(o, remove, false); - while (tok.hasMoreTokens()) { - res.add(StringUtil.capitalizeFirst(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 findAuthorLastNames(BibDatabase db, List fields) { - Set res = new TreeSet<>(); - for (BibEntry be : db.getEntries()) { - for (String field : fields) { - be.getField(field).ifPresent(val -> { - if (!val.isEmpty()) { - AuthorList al = AuthorList.parse(val); - res.addAll(al.getAuthors().stream().map(Author::getLast).filter(Optional::isPresent) - .map(Optional::get).filter(lastName -> !lastName.isEmpty()) - .collect(Collectors.toList())); - } - }); - } - } - - return res; - } - - @Override - public void caretUpdate(CaretEvent e) { - updateComponents(); - } - - private void updateComponents() { - String groupField = field.getText().trim(); - ok.setEnabled(groupField.matches("\\w+")); - } -} diff --git a/src/main/java/org/jabref/gui/groups/GroupDialog.java b/src/main/java/org/jabref/gui/groups/GroupDialog.java index 45fd491992d..e5713c36c45 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialog.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialog.java @@ -38,6 +38,8 @@ import org.jabref.logic.search.SearchQuery; import org.jabref.model.entry.FieldName; import org.jabref.model.groups.AbstractGroup; +import org.jabref.model.groups.AutomaticKeywordGroup; +import org.jabref.model.groups.AutomaticPersonsGroup; import org.jabref.model.groups.ExplicitGroup; import org.jabref.model.groups.GroupHierarchyType; import org.jabref.model.groups.RegexKeywordGroup; @@ -48,6 +50,7 @@ import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.builder.DefaultFormBuilder; +import com.jgoodies.forms.builder.FormBuilder; import com.jgoodies.forms.layout.FormLayout; /** @@ -59,6 +62,7 @@ class GroupDialog extends JDialog implements Dialog { private static final int INDEX_EXPLICIT_GROUP = 0; private static final int INDEX_KEYWORD_GROUP = 1; private static final int INDEX_SEARCH_GROUP = 2; + private static final int INDEX_AUTO_GROUP = 3; private static final int TEXTFIELD_LENGTH = 30; // for all types private final JTextField nameField = new JTextField(GroupDialog.TEXTFIELD_LENGTH); @@ -68,6 +72,8 @@ class GroupDialog extends JDialog implements Dialog { Localization.lang("Dynamically group entries by searching a field for a keyword")); private final JRadioButton searchRadioButton = new JRadioButton( Localization.lang("Dynamically group entries by a free-form search expression")); + private final JRadioButton autoRadioButton = new JRadioButton( + Localization.lang("Automatically create groups")); private final JRadioButton independentButton = new JRadioButton( Localization.lang("Independent group: When selected, view only this group's entries")); private final JRadioButton intersectionButton = new JRadioButton( @@ -83,6 +89,15 @@ class GroupDialog extends JDialog implements Dialog { private final JTextField searchGroupSearchExpression = new JTextField(GroupDialog.TEXTFIELD_LENGTH); private final JCheckBox searchGroupCaseSensitive = new JCheckBox(Localization.lang("Case sensitive")); private final JCheckBox searchGroupRegExp = new JCheckBox(Localization.lang("regular expression")); + // for AutoGroup + private final JRadioButton autoGroupKeywordsOption = new JRadioButton( + Localization.lang("Generate groups from keywords in a BibTeX field")); + private final JTextField autoGroupKeywordsField = new JTextField(60); + private final JTextField autoGroupKeywordsDeliminator = new JTextField(60); + private final JRadioButton autoGroupPersonsOption = new JRadioButton( + Localization.lang("Generate groups for author last names")); + private final JTextField autoGroupPersonsField = new JTextField(60); + // for all types private final JButton okButton = new JButton(Localization.lang("OK")); private final JPanel optionsPanel = new JPanel(); @@ -119,6 +134,7 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { groupType.add(explicitRadioButton); groupType.add(keywordsRadioButton); groupType.add(searchRadioButton); + groupType.add(autoRadioButton); ButtonGroup groupHierarchy = new ButtonGroup(); groupHierarchy.add(independentButton); groupHierarchy.add(intersectionButton); @@ -154,6 +170,31 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { builderSG.nextLine(); builderSG.append(searchGroupRegExp, 3); optionsPanel.add(builderSG.getPanel(), String.valueOf(GroupDialog.INDEX_SEARCH_GROUP)); + + // for auto group + ButtonGroup bg = new ButtonGroup(); + bg.add(autoGroupKeywordsOption); + bg.add(autoGroupPersonsOption); + + FormLayout layoutAutoGroup = new FormLayout("left:20dlu, 4dlu, left:pref, 4dlu, fill:60dlu", + "p, 2dlu, p, 2dlu, p, 2dlu, p, 2dlu, p"); + FormBuilder builderAutoGroup = FormBuilder.create(); + builderAutoGroup.layout(layoutAutoGroup); + builderAutoGroup.add(autoGroupKeywordsOption).xyw(1, 1, 5); + builderAutoGroup.add(Localization.lang("Field to group by") + ":").xy(3, 3); + builderAutoGroup.add(autoGroupKeywordsField).xy(5, 3); + builderAutoGroup.add(Localization.lang("Use the following delimiter character(s):")).xy(3, 5); + builderAutoGroup.add(autoGroupKeywordsDeliminator).xy(5, 5); + builderAutoGroup.add(autoGroupPersonsOption).xyw(1, 7, 5); + builderAutoGroup.add(Localization.lang("Field to group by") + ":").xy(3, 9); + builderAutoGroup.add(autoGroupPersonsField).xy(5, 9); + optionsPanel.add(builderAutoGroup.build(), String.valueOf(GroupDialog.INDEX_AUTO_GROUP)); + + autoGroupKeywordsOption.setSelected(true); + autoGroupKeywordsField.setText(Globals.prefs.get(JabRefPreferences.GROUPS_DEFAULT_FIELD)); + autoGroupKeywordsDeliminator.setText(Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR)); + autoGroupPersonsField.setText(FieldName.AUTHOR); + // ... for buttons panel FormLayout layoutBP = new FormLayout("pref, 4dlu, pref", "p"); layoutBP.setColumnGroups(new int[][] {{1, 3}}); @@ -168,7 +209,7 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { // create layout FormLayout layoutAll = new FormLayout( "right:pref, 4dlu, fill:600px, 4dlu, fill:pref", - "p, 3dlu, p, 3dlu, p, 0dlu, p, 0dlu, p, 3dlu, p, 3dlu, p, " + "p, 3dlu, p, 3dlu, p, 0dlu, p, 0dlu, p, 0dlu, p, 3dlu, p, 3dlu, p, " + "0dlu, p, 0dlu, p, 3dlu, p, 3dlu, " + "p, 3dlu, p, 3dlu, top:80dlu, 9dlu, p, 9dlu, p"); @@ -189,6 +230,9 @@ public GroupDialog(JabRefFrame jabrefFrame, AbstractGroup editedGroup) { builderAll.append(searchRadioButton, 5); builderAll.nextLine(); builderAll.nextLine(); + builderAll.append(autoRadioButton, 5); + builderAll.nextLine(); + builderAll.nextLine(); builderAll.appendSeparator(Localization.lang("Hierarchical context")); builderAll.nextLine(); builderAll.nextLine(); @@ -243,6 +287,7 @@ public Dimension getPreferredSize() { explicitRadioButton.addItemListener(radioButtonItemListener); keywordsRadioButton.addItemListener(radioButtonItemListener); searchRadioButton.addItemListener(radioButtonItemListener); + autoRadioButton.addItemListener(radioButtonItemListener); Action cancelAction = new AbstractAction() { @@ -284,6 +329,15 @@ public void actionPerformed(ActionEvent e) { } catch (Exception e1) { // should never happen } + } else if (autoRadioButton.isSelected()) { + if (autoGroupKeywordsOption.isSelected()) { + resultingGroup = new AutomaticKeywordGroup(nameField.getText().trim(), getContext(), + autoGroupKeywordsField.getText().trim(), + autoGroupKeywordsDeliminator.getText().charAt(0)); + } else { + resultingGroup = new AutomaticPersonsGroup(nameField.getText().trim(), getContext(), + autoGroupPersonsField.getText().trim()); + } } dispose(); } catch (IllegalArgumentException exception) { @@ -304,39 +358,55 @@ public void actionPerformed(ActionEvent e) { searchGroupCaseSensitive.addItemListener(itemListener); // configure for current type - if ((editedGroup != null) && (editedGroup.getClass() == WordKeywordGroup.class)) { - WordKeywordGroup group = (WordKeywordGroup) editedGroup; - nameField.setText(group.getName()); - keywordGroupSearchField.setText(group.getSearchField()); - keywordGroupSearchTerm.setText(group.getSearchExpression()); - keywordGroupCaseSensitive.setSelected(group.isCaseSensitive()); - keywordGroupRegExp.setSelected(false); - keywordsRadioButton.setSelected(true); - setContext(editedGroup.getHierarchicalContext()); - } else if ((editedGroup != null) && (editedGroup.getClass() == RegexKeywordGroup.class)) { - RegexKeywordGroup group = (RegexKeywordGroup) editedGroup; - nameField.setText(group.getName()); - keywordGroupSearchField.setText(group.getSearchField()); - keywordGroupSearchTerm.setText(group.getSearchExpression()); - keywordGroupCaseSensitive.setSelected(group.isCaseSensitive()); - keywordGroupRegExp.setSelected(true); - keywordsRadioButton.setSelected(true); - setContext(editedGroup.getHierarchicalContext()); - } else if ((editedGroup != null) && (editedGroup.getClass() == SearchGroup.class)) { - SearchGroup group = (SearchGroup) editedGroup; - nameField.setText(group.getName()); - searchGroupSearchExpression.setText(group.getSearchExpression()); - searchGroupCaseSensitive.setSelected(group.isCaseSensitive()); - searchGroupRegExp.setSelected(group.isRegularExpression()); - searchRadioButton.setSelected(true); - setContext(editedGroup.getHierarchicalContext()); - } else if ((editedGroup != null) && (editedGroup.getClass() == ExplicitGroup.class)) { - nameField.setText(editedGroup.getName()); - explicitRadioButton.setSelected(true); - setContext(editedGroup.getHierarchicalContext()); - } else { // creating new group -> defaults! + if (editedGroup == null) { + // creating new group -> defaults! explicitRadioButton.setSelected(true); setContext(GroupHierarchyType.INDEPENDENT); + } else { + if (editedGroup.getClass() == WordKeywordGroup.class) { + WordKeywordGroup group = (WordKeywordGroup) editedGroup; + nameField.setText(group.getName()); + keywordGroupSearchField.setText(group.getSearchField()); + keywordGroupSearchTerm.setText(group.getSearchExpression()); + keywordGroupCaseSensitive.setSelected(group.isCaseSensitive()); + keywordGroupRegExp.setSelected(false); + keywordsRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + } else if (editedGroup.getClass() == RegexKeywordGroup.class) { + RegexKeywordGroup group = (RegexKeywordGroup) editedGroup; + nameField.setText(group.getName()); + keywordGroupSearchField.setText(group.getSearchField()); + keywordGroupSearchTerm.setText(group.getSearchExpression()); + keywordGroupCaseSensitive.setSelected(group.isCaseSensitive()); + keywordGroupRegExp.setSelected(true); + keywordsRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + } else if (editedGroup.getClass() == SearchGroup.class) { + SearchGroup group = (SearchGroup) editedGroup; + nameField.setText(group.getName()); + searchGroupSearchExpression.setText(group.getSearchExpression()); + searchGroupCaseSensitive.setSelected(group.isCaseSensitive()); + searchGroupRegExp.setSelected(group.isRegularExpression()); + searchRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + } else if (editedGroup.getClass() == ExplicitGroup.class) { + nameField.setText(editedGroup.getName()); + explicitRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + } else if (editedGroup.getClass() == AutomaticKeywordGroup.class) { + nameField.setText(editedGroup.getName()); + autoRadioButton.setSelected(true); + setContext(editedGroup.getHierarchicalContext()); + + if (editedGroup.getClass() == AutomaticKeywordGroup.class) { + AutomaticKeywordGroup group = (AutomaticKeywordGroup) editedGroup; + autoGroupKeywordsDeliminator.setText(group.getKeywordSeperator().toString()); + autoGroupKeywordsField.setText(group.getField()); + } else if (editedGroup.getClass() == AutomaticPersonsGroup.class) { + AutomaticPersonsGroup group = (AutomaticPersonsGroup) editedGroup; + autoGroupPersonsField.setText(group.getField()); + } + } } } @@ -385,6 +455,8 @@ private void setLayoutForSelectedGroup() { optionsLayout.show(optionsPanel, String.valueOf(GroupDialog.INDEX_KEYWORD_GROUP)); } else if (searchRadioButton.isSelected()) { optionsLayout.show(optionsPanel, String.valueOf(GroupDialog.INDEX_SEARCH_GROUP)); + } else if (autoRadioButton.isSelected()) { + optionsLayout.show(optionsPanel, String.valueOf(GroupDialog.INDEX_AUTO_GROUP)); } } diff --git a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index bca890ca228..61d8f72742f 100644 --- a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -1,21 +1,31 @@ package org.jabref.gui.groups; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.jabref.gui.StateManager; import org.jabref.gui.util.BindingsHelper; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.layout.format.LatexToUnicodeFormatter; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.EntryEvent; import org.jabref.model.groups.AbstractGroup; import org.jabref.model.groups.AllEntriesGroup; +import org.jabref.model.groups.AutomaticGroup; import org.jabref.model.groups.GroupTreeNode; import com.google.common.eventbus.Subscribe; @@ -23,7 +33,7 @@ public class GroupNodeViewModel { - private final String name; + private final String displayName; private final boolean isRoot; private final String iconCode; private final ObservableList children; @@ -38,10 +48,24 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state this.databaseContext = Objects.requireNonNull(databaseContext); this.groupNode = Objects.requireNonNull(groupNode); - name = groupNode.getName(); + LatexToUnicodeFormatter formatter = new LatexToUnicodeFormatter(); + displayName = formatter.format(groupNode.getName()); isRoot = groupNode.isRoot(); iconCode = ""; - children = EasyBind.map(groupNode.getChildren(), child -> new GroupNodeViewModel(databaseContext, stateManager, child)); + if (groupNode.getGroup() instanceof AutomaticGroup) { + AutomaticGroup automaticGroup = (AutomaticGroup) groupNode.getGroup(); + + // TODO: Update on changes to entry list (however: there is no flatMap and filter as observable TransformationLists) + children = databaseContext.getDatabase() + .getEntries().stream() + .flatMap(stream -> createSubgroups(databaseContext, stateManager, automaticGroup, stream)) + .filter(distinctByKey(group -> group.getGroupNode().getName())) + .sorted((group1, group2) -> group1.getDisplayName().compareToIgnoreCase(group2.getDisplayName())) + .collect(Collectors.toCollection(FXCollections::observableArrayList)); + } else { + children = EasyBind.map(groupNode.getChildren(), + child -> new GroupNodeViewModel(databaseContext, stateManager, child)); + } hasChildren = new SimpleBooleanProperty(); hasChildren.bind(Bindings.isNotEmpty(children)); hits = new SimpleIntegerProperty(0); @@ -59,10 +83,20 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state this(databaseContext, stateManager, new GroupTreeNode(group)); } + private static Predicate distinctByKey(Function keyExtractor) { + Map seen = new ConcurrentHashMap<>(); + return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; + } + static GroupNodeViewModel getAllEntriesGroup(BibDatabaseContext newDatabase, StateManager stateManager) { return new GroupNodeViewModel(newDatabase, stateManager, new AllEntriesGroup(Localization.lang("All entries"))); } + private Stream createSubgroups(BibDatabaseContext databaseContext, StateManager stateManager, AutomaticGroup automaticGroup, BibEntry entry) { + return automaticGroup.createSubgroups(entry).stream() + .map(child -> new GroupNodeViewModel(databaseContext, stateManager, child)); + } + public SimpleBooleanProperty expandedProperty() { return expandedProperty; } @@ -79,8 +113,8 @@ public SimpleBooleanProperty hasChildrenProperty() { return hasChildren; } - public String getName() { - return name; + public String getDisplayName() { + return displayName; } public boolean isRoot() { @@ -88,7 +122,7 @@ public boolean isRoot() { } public String getDescription() { - return "Some group named " + getName(); + return "Some group named " + getDisplayName(); } public SimpleIntegerProperty getHits() { @@ -103,7 +137,7 @@ public boolean equals(Object o) { GroupNodeViewModel that = (GroupNodeViewModel) o; if (isRoot != that.isRoot) return false; - if (!name.equals(that.name)) return false; + if (!displayName.equals(that.displayName)) return false; if (!iconCode.equals(that.iconCode)) return false; if (!children.equals(that.children)) return false; if (!databaseContext.equals(that.databaseContext)) return false; @@ -114,7 +148,7 @@ public boolean equals(Object o) { @Override public String toString() { return "GroupNodeViewModel{" + - "name='" + name + '\'' + + "displayName='" + displayName + '\'' + ", isRoot=" + isRoot + ", iconCode='" + iconCode + '\'' + ", children=" + children + @@ -126,7 +160,7 @@ public String toString() { @Override public int hashCode() { - int result = name.hashCode(); + int result = displayName.hashCode(); result = 31 * result + (isRoot ? 1 : 0); result = 31 * result + iconCode.hashCode(); result = 31 * result + children.hashCode(); diff --git a/src/main/java/org/jabref/gui/groups/GroupSelector.java b/src/main/java/org/jabref/gui/groups/GroupSelector.java index 16cf18acb44..f94467717e3 100644 --- a/src/main/java/org/jabref/gui/groups/GroupSelector.java +++ b/src/main/java/org/jabref/gui/groups/GroupSelector.java @@ -205,7 +205,6 @@ public void stateChanged(ChangeEvent event) { JButton helpButton = new HelpAction(Localization.lang("Help on groups"), HelpFile.GROUP) .getHelpButton(); - JButton autoGroup = new JButton(IconTheme.JabRefIcon.AUTO_GROUP.getSmallIcon()); Insets butIns = new Insets(0, 0, 0, 0); helpButton.setMargin(butIns); openSettings.setMargin(butIns); @@ -213,20 +212,12 @@ public void stateChanged(ChangeEvent event) { orCb.addActionListener(e -> valueChanged(null)); invCb.addActionListener(e -> valueChanged(null)); showOverlappingGroups.addActionListener(e -> valueChanged(null)); - autoGroup.addActionListener(e -> { - AutoGroupDialog gd = new AutoGroupDialog(frame, panel, groupsRoot, - Globals.prefs.get(JabRefPreferences.GROUPS_DEFAULT_FIELD), " .,", - Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR)); - gd.setVisible(true); - // gd does the operation itself - }); floatCb.addActionListener(e -> valueChanged(null)); highlCb.addActionListener(e -> valueChanged(null)); hideNonHits.addActionListener(e -> valueChanged(null)); grayOut.addActionListener(e -> valueChanged(null)); andCb.setToolTipText(Localization.lang("Display only entries belonging to all selected groups.")); orCb.setToolTipText(Localization.lang("Display all entries belonging to one or more of the selected groups.")); - autoGroup.setToolTipText(Localization.lang("Automatically create groups for library.")); openSettings.setToolTipText(Localization.lang("Settings")); invCb.setToolTipText("" + Localization.lang("Show entries not in group selection") + ""); showOverlappingGroups.setToolTipText( @@ -253,8 +244,6 @@ public void stateChanged(ChangeEvent event) { con.gridx = 0; con.gridx = 1; - gbl.setConstraints(autoGroup, con); - rootPanel.add(autoGroup); con.gridx = 2; gbl.setConstraints(openSettings, con); diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeController.java b/src/main/java/org/jabref/gui/groups/GroupTreeController.java index 529784d5d2e..590a8b76820 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeController.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeController.java @@ -54,7 +54,7 @@ public void initialize() { // Icon and group name mainColumn.setCellValueFactory(cellData -> cellData.getValue().valueProperty()); mainColumn.setCellFactory(new ViewModelTreeTableCellFactory() - .withText(GroupNodeViewModel::getName) + .withText(GroupNodeViewModel::getDisplayName) .withIcon(GroupNodeViewModel::getIconCode) .withTooltip(GroupNodeViewModel::getDescription) ); diff --git a/src/main/java/org/jabref/logic/cleanup/MoveFieldCleanup.java b/src/main/java/org/jabref/logic/cleanup/MoveFieldCleanup.java index 1763b9d139b..f59fec41718 100644 --- a/src/main/java/org/jabref/logic/cleanup/MoveFieldCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/MoveFieldCleanup.java @@ -3,10 +3,10 @@ import java.util.List; import java.util.Optional; -import org.jabref.logic.util.OptionalUtil; import org.jabref.model.FieldChange; import org.jabref.model.cleanup.CleanupJob; import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.OptionalUtil; /** * Moves the content of one field to another field. diff --git a/src/main/java/org/jabref/logic/integrity/FieldChecker.java b/src/main/java/org/jabref/logic/integrity/FieldChecker.java index b0856bdf12a..fe4301e9a0f 100644 --- a/src/main/java/org/jabref/logic/integrity/FieldChecker.java +++ b/src/main/java/org/jabref/logic/integrity/FieldChecker.java @@ -5,8 +5,8 @@ import java.util.Objects; import java.util.Optional; -import org.jabref.logic.util.OptionalUtil; import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.OptionalUtil; public class FieldChecker implements IntegrityCheck.Checker { protected final String field; diff --git a/src/main/java/org/jabref/logic/util/OptionalUtil.java b/src/main/java/org/jabref/logic/util/OptionalUtil.java deleted file mode 100644 index ba0d00ce0aa..00000000000 --- a/src/main/java/org/jabref/logic/util/OptionalUtil.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.jabref.logic.util; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class OptionalUtil { - - public static List toList(Optional value) { - if (value.isPresent()) { - return Collections.singletonList(value.get()); - } else { - return Collections.emptyList(); - } - } - - @SafeVarargs - public static List toList(Optional... values) { - return Stream.of(values).flatMap(optional -> toList(optional).stream()).collect(Collectors.toList()); - } -} diff --git a/src/main/java/org/jabref/model/database/BibDatabase.java b/src/main/java/org/jabref/model/database/BibDatabase.java index 59fae7fb306..ff9a1593141 100644 --- a/src/main/java/org/jabref/model/database/BibDatabase.java +++ b/src/main/java/org/jabref/model/database/BibDatabase.java @@ -4,7 +4,6 @@ import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; @@ -17,6 +16,9 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + import org.jabref.model.database.event.EntryAddedEvent; import org.jabref.model.database.event.EntryRemovedEvent; import org.jabref.model.entry.BibEntry; @@ -39,37 +41,47 @@ */ public class BibDatabase { private static final Log LOGGER = LogFactory.getLog(BibDatabase.class); - + private static final Pattern RESOLVE_CONTENT_PATTERN = Pattern.compile(".*#[^#]+#.*"); /** * State attributes */ - private final List entries = Collections.synchronizedList(new ArrayList<>()); - - private String preamble; - // All file contents below the last entry in the file - private String epilog = ""; + private final ObservableList entries = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); private final Map bibtexStrings = new ConcurrentHashMap<>(); - /** * this is kept in sync with the database (upon adding/removing an entry, it is updated as well) */ private final DuplicationChecker duplicationChecker = new DuplicationChecker(); - /** * contains all entry.getID() of the current database */ private final Set internalIDs = new HashSet<>(); - private final EventBus eventBus = new EventBus(); - + private String preamble; + // All file contents below the last entry in the file + private String epilog = ""; private String sharedDatabaseID; - public BibDatabase() { this.eventBus.register(duplicationChecker); this.registerListener(new KeyChangeListener(this)); } + /** + * @param toResolve maybenull The text to resolve. + * @param database maybenull The database to use for resolving the text. + * @return The resolved text or the original text if either the text or the database are null + * @deprecated use {@link BibDatabase#resolveForStrings(String)} + * + * Returns a text with references resolved according to an optionally given database. + */ + @Deprecated + public static String getText(String toResolve, BibDatabase database) { + if ((toResolve != null) && (database != null)) { + return database.resolveForStrings(toResolve); + } + return toResolve; + } + /** * Returns the number of entries. */ @@ -99,8 +111,8 @@ public boolean containsEntryWithId(String id) { return internalIDs.contains(id); } - public List getEntries() { - return Collections.unmodifiableList(entries); + public ObservableList getEntries() { + return FXCollections.unmodifiableObservableList(entries); } /** @@ -222,13 +234,6 @@ public synchronized void removeEntry(BibEntry toBeDeleted, EntryEventSource even } } - /** - * Sets the database's preamble. - */ - public synchronized void setPreamble(String preamble) { - this.preamble = preamble; - } - /** * Returns the database's preamble. * If the preamble text consists only of whitespace, then also an empty optional is returned. @@ -241,6 +246,13 @@ public synchronized Optional getPreamble() { } } + /** + * Sets the database's preamble. + */ + public synchronized void setPreamble(String preamble) { + this.preamble = preamble; + } + /** * Inserts a Bibtex String. */ @@ -467,10 +479,6 @@ private String resolveString(String label, Set usedIds, Set allU } } - - private static final Pattern RESOLVE_CONTENT_PATTERN = Pattern.compile(".*#[^#]+#.*"); - - private String resolveContent(String result, Set usedIds, Set allUsedIds) { String res = result; if (RESOLVE_CONTENT_PATTERN.matcher(res).matches()) { @@ -519,31 +527,14 @@ private String resolveContent(String result, Set usedIds, Set al return res; } - /** - * @deprecated use {@link BibDatabase#resolveForStrings(String)} - * - * Returns a text with references resolved according to an optionally given database. - * - * @param toResolve maybenull The text to resolve. - * @param database maybenull The database to use for resolving the text. - * @return The resolved text or the original text if either the text or the database are null - */ - @Deprecated - public static String getText(String toResolve, BibDatabase database) { - if ((toResolve != null) && (database != null)) { - return database.resolveForStrings(toResolve); - } - return toResolve; + public String getEpilog() { + return epilog; } public void setEpilog(String epilog) { this.epilog = epilog; } - public String getEpilog() { - return epilog; - } - /** * Registers an listener object (subscriber) to the internal event bus. * The following events are posted: @@ -584,14 +575,14 @@ public Optional getSharedDatabaseID() { return Optional.ofNullable(this.sharedDatabaseID); } - public boolean isShared() { - return getSharedDatabaseID().isPresent(); - } - public void setSharedDatabaseID(String sharedDatabaseID) { this.sharedDatabaseID = sharedDatabaseID; } + public boolean isShared() { + return getSharedDatabaseID().isPresent(); + } + public void clearSharedDatabaseID() { this.sharedDatabaseID = null; } diff --git a/src/main/java/org/jabref/model/groups/AutomaticGroup.java b/src/main/java/org/jabref/model/groups/AutomaticGroup.java new file mode 100644 index 00000000000..62d619fa5cf --- /dev/null +++ b/src/main/java/org/jabref/model/groups/AutomaticGroup.java @@ -0,0 +1,23 @@ +package org.jabref.model.groups; + +import java.util.Set; + +import org.jabref.model.entry.BibEntry; + +public abstract class AutomaticGroup extends AbstractGroup { + public AutomaticGroup(String name, GroupHierarchyType context) { + super(name, context); + } + + @Override + public boolean contains(BibEntry entry) { + return false; + } + + @Override + public boolean isDynamic() { + return false; + } + + public abstract Set createSubgroups(BibEntry entry); +} diff --git a/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java b/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java new file mode 100644 index 00000000000..48641d24915 --- /dev/null +++ b/src/main/java/org/jabref/model/groups/AutomaticKeywordGroup.java @@ -0,0 +1,44 @@ +package org.jabref.model.groups; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.KeywordList; +import org.jabref.model.util.OptionalUtil; + +public class AutomaticKeywordGroup extends AutomaticGroup { + + private Character keywordSeperator; + private String field; + + public AutomaticKeywordGroup(String name, GroupHierarchyType context, String field, Character keywordSeperator) { + super(name, context); + this.field = field; + this.keywordSeperator = keywordSeperator; + } + + public Character getKeywordSeperator() { + return keywordSeperator; + } + + public String getField() { + return field; + } + + @Override + public AbstractGroup deepCopy() { + return new AutomaticKeywordGroup(this.name, this.context, field, this.keywordSeperator); + } + + @Override + public Set createSubgroups(BibEntry entry) { + Optional keywordList = entry.getLatexFreeField(field) + .map(fieldValue -> KeywordList.parse(fieldValue, keywordSeperator)); + return OptionalUtil.flatMap(keywordList, KeywordList::toStringList) + .map(keyword -> new WordKeywordGroup(keyword, GroupHierarchyType.INDEPENDENT, field, keyword, true, keywordSeperator, true)) + .map(GroupTreeNode::new) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java b/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java new file mode 100644 index 00000000000..5e4be74b20a --- /dev/null +++ b/src/main/java/org/jabref/model/groups/AutomaticPersonsGroup.java @@ -0,0 +1,43 @@ +package org.jabref.model.groups; + +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.model.entry.Author; +import org.jabref.model.entry.AuthorList; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.OptionalUtil; + +public class AutomaticPersonsGroup extends AutomaticGroup { + + private String field; + + public AutomaticPersonsGroup(String name, GroupHierarchyType context, String field) { + super(name, context); + this.field = field; + } + + @Override + public AbstractGroup deepCopy() { + return new AutomaticPersonsGroup(this.name, this.context, this.field); + } + + @Override + public Set createSubgroups(BibEntry entry) { + Optional authorList = entry.getLatexFreeField(field) + .map(AuthorList::parse); + return OptionalUtil.flatMap(authorList, AuthorList::getAuthors) + .map(Author::getLast) + .filter(Optional::isPresent) + .map(Optional::get) + .filter(lastName -> !lastName.isEmpty()) + .map(lastName -> new WordKeywordGroup(lastName, GroupHierarchyType.INDEPENDENT, field, lastName, true, ' ', true)) + .map(GroupTreeNode::new) + .collect(Collectors.toSet()); + } + + public String getField() { + return field; + } +} diff --git a/src/main/java/org/jabref/model/util/OptionalUtil.java b/src/main/java/org/jabref/model/util/OptionalUtil.java new file mode 100644 index 00000000000..d2b482b80ee --- /dev/null +++ b/src/main/java/org/jabref/model/util/OptionalUtil.java @@ -0,0 +1,44 @@ +package org.jabref.model.util; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class OptionalUtil { + + public static List toList(Optional value) { + if (value.isPresent()) { + return Collections.singletonList(value.get()); + } else { + return Collections.emptyList(); + } + } + + /** + * No longer needed in Java 9 where {@code Optional.stream()} is added. + */ + public static Stream toStream(Optional value) { + if (value.isPresent()) { + return Stream.of(value.get()); + } else { + return Stream.empty(); + } + } + + @SafeVarargs + public static List toList(Optional... values) { + return Stream.of(values).flatMap(optional -> toList(optional).stream()).collect(Collectors.toList()); + } + + public static Stream flatMapFromStream(Optional value, Function> mapper) { + return toStream(value).flatMap(mapper); + } + + public static Stream flatMap(Optional value, Function> mapper) { + return toStream(value).flatMap(element -> mapper.apply(element).stream()); + } +} diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index 8c55ffb62ae..b6e068ca3b6 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Autolink_kun_filer_med_navn_som_sv Automatically_create_groups=Generer_grupper_automatisk -Automatically_create_groups_for_library.=Generer_grupper_for_libraryn. - -Automatically_created_groups=Genererede_grupper_automatisk - Automatically_remove_exact_duplicates=Fjern_eksakte_dubletter_automatisk Allow_overwriting_existing_links.=Tillad_overskrivning_af_eksisterende_links. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Ændrede_brugerfladeindstillinger Changed_preamble=Ændrede_præambel -Characters_to_ignore=Ignorer_følgende_tegn - Check_existing_file_links=Tjek_eksisterende_fil-links Check_links=Tjek_eksterne_links @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Kunne_ikke_køre_'vim'-programmet Could_not_save_file.=Kunne_ikke_gemme_fil. Character_encoding_'%0'_is_not_supported.=Tegnkodingen_'%0'_er_ikke_understøttet. -Created_groups.=Oprettede_grupper. - crossreferenced_entries_included=refererede_poster_inkluderet Current_content=Nuværende_indhold @@ -1469,7 +1461,6 @@ Metadata_change=Metadata-ændring Changes_have_been_made_to_the_following_metadata_elements=Der_er_ændringer_i_følgende_metadata-elementer Generate_groups_for_author_last_names=Generer_grupper_for_forfatteres_efternavne -Generate_groups_for_editor_last_names=Generer_grupper_for_redaktørers_efternavne Generate_groups_from_keywords_in_a_BibTeX_field=Generer_grupper_ud_fra_nøgleord_i_et_BibTeX-felt Enforce_legal_characters_in_BibTeX_keys=Håndhæv_tilladte_tegn_i_BibTeX-nøgler diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 50c0fa3fcb8..00c0a3050a1 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Nur_Dateien_verlinken,_deren_Namen Automatically_create_groups=Gruppen_automatisch_erstellen -Automatically_create_groups_for_library.=Automatisch_Gruppen_für_die_Datenbank_anlegen. - -Automatically_created_groups=Automatisch_erzeugte_Gruppen - Automatically_remove_exact_duplicates=Exakte_Duplikate_automatisch_löschen Allow_overwriting_existing_links.=Vorhandene_Links_überschreiben. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings="Look_and_Feel"-Einstellungen_geändert Changed_preamble=Präambel_geändert -Characters_to_ignore=Folgende_Zeichen_ignorieren - Check_existing_file_links=Existierende_Datei-Links_überprüfen Check_links=Links_überprüfen @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Das_Programm_'vim'_konnte_nicht_gestartet_werde Could_not_save_file.=Datei_konnte_nicht_gespeichert_werden. Character_encoding_'%0'_is_not_supported.=Die_Zeichenkodierung_'%0'_wird_nicht_unterstützt. -Created_groups.=Gruppen_erstellt. - crossreferenced_entries_included=Inklusive_querverwiesenen_Einträgen Current_content=Aktueller_Inhalt @@ -1469,7 +1461,6 @@ Metadata_change=Metadaten-Änderung Changes_have_been_made_to_the_following_metadata_elements=An_den_folgenden_Metadaten_wurden_Änderungen_vorgenommen Generate_groups_for_author_last_names=Erstelle_Gruppen_für_Nachnamen_der_Autoren -Generate_groups_for_editor_last_names=Erstelle_Gruppen_für_Nachnamen_der_Herausgeber Generate_groups_from_keywords_in_a_BibTeX_field=Erstelle_Gruppen_aus_den_Stichwörtern_eines_BibTeX-Feldes Enforce_legal_characters_in_BibTeX_keys=Erzwinge_erlaubte_Zeichen_in_BibTeX-Keys diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 131b629de02..88f7a007f3c 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Autolink_only_files_that_match_the Automatically_create_groups=Automatically_create_groups -Automatically_create_groups_for_library.=Automatically_create_groups_for_library. - -Automatically_created_groups=Automatically_created_groups - Automatically_remove_exact_duplicates=Automatically_remove_exact_duplicates Allow_overwriting_existing_links.=Allow_overwriting_existing_links. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Changed_look_and_feel_settings Changed_preamble=Changed_preamble -Characters_to_ignore=Characters_to_ignore - Check_existing_file_links=Check_existing_file_links Check_links=Check_links @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Could_not_run_the_'vim'_program. Could_not_save_file.=Could_not_save_file. Character_encoding_'%0'_is_not_supported.=Character_encoding_'%0'_is_not_supported. -Created_groups.=Created_groups. - crossreferenced_entries_included=crossreferenced_entries_included Current_content=Current_content @@ -1469,7 +1461,6 @@ Metadata_change=Metadata_change Changes_have_been_made_to_the_following_metadata_elements=Changes_have_been_made_to_the_following_metadata_elements Generate_groups_for_author_last_names=Generate_groups_for_author_last_names -Generate_groups_for_editor_last_names=Generate_groups_for_editor_last_names Generate_groups_from_keywords_in_a_BibTeX_field=Generate_groups_from_keywords_in_a_BibTeX_field Enforce_legal_characters_in_BibTeX_keys=Enforce_legal_characters_in_BibTeX_keys diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 0a138611357..f617ae0f51d 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Autoenlazar_sólo_archivos_cuyo_no Automatically_create_groups=Crear_grupos_automáticamente -Automatically_create_groups_for_library.=Crear_grupos_para_base_de_datos_automáticamente - -Automatically_created_groups=Grupos_automáticamente_creados - Automatically_remove_exact_duplicates=Eliminar_automáticamente_duplicados_exactos Allow_overwriting_existing_links.=Permitir_sobreescribir_enlaces_existentes. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Ajustes_de_aspecto_cambiados Changed_preamble=Preámbulo_cambiado -Characters_to_ignore=Caracteres_a_ignorar - Check_existing_file_links=Comprobar_archivo_enlaces_existentes Check_links=Comprobar_enlaces @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=No_se_puede_ejecutar_Vim Could_not_save_file.=No_se_puede_guardar_el_archivo. Character_encoding_'%0'_is_not_supported.=La_codificaciónd_de_caracteres_'%0'_no_está_soportada. -Created_groups.=Grupos_creados. - crossreferenced_entries_included=entradas_de_referencia_cruzada_incluídas Current_content=Contenido_actual @@ -1469,7 +1461,6 @@ Metadata_change=Cambio_de_metadatos Changes_have_been_made_to_the_following_metadata_elements=Se_han_efectuado_los_cambios_a_los_siguientes_elementos_de_los_metadatos Generate_groups_for_author_last_names=Generar_grupos_para_apellidos_de_autor -Generate_groups_for_editor_last_names=Generar_grupos_para_apellidos_de_editor Generate_groups_from_keywords_in_a_BibTeX_field=Generar_grupos_desde_palabras_claves_de_un_campo_BibTeX Enforce_legal_characters_in_BibTeX_keys=Uso_obligado_de_caracteres_legales_en_claves_BibTeX diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index d0492fc8d43..b2652a9d534 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key= Automatically_create_groups= -Automatically_create_groups_for_library.= - -Automatically_created_groups= - Automatically_remove_exact_duplicates= Allow_overwriting_existing_links.= @@ -209,8 +205,6 @@ Changed_look_and_feel_settings= Changed_preamble= -Characters_to_ignore= - Check_existing_file_links= Check_links= @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.= Could_not_save_file.= Character_encoding_'%0'_is_not_supported.= -Created_groups.= - crossreferenced_entries_included= Current_content= @@ -1469,7 +1461,6 @@ Metadata_change= Changes_have_been_made_to_the_following_metadata_elements= Generate_groups_for_author_last_names= -Generate_groups_for_editor_last_names= Generate_groups_from_keywords_in_a_BibTeX_field= Enforce_legal_characters_in_BibTeX_keys= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index c49b15d82cb..0f04bfa1bde 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Lier_automatiquement_les_fichiers_ Automatically_create_groups=Créer_automatiquement_des_groupes -Automatically_create_groups_for_library.=Créer_automatiquement_des_groupes_pour_le_fichier. - -Automatically_created_groups=Groupes_créés_automatiquement - Automatically_remove_exact_duplicates=Supprimer_automatiquement_les_doublons_identiques Allow_overwriting_existing_links.=Ecraser_les_liens_existants. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Changer_les_paramètres_d'apparence Changed_preamble=Préambule_modifié -Characters_to_ignore=Caractères_à_ignorer - Check_existing_file_links=Vérifier_les_liens_fichier_existants Check_links=Vérifier_les_liens @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Le_programme_'vim'_n'a_pas_pu_être_lancé. Could_not_save_file.=Le_fichier_n'a_pas_pu_être_enregistré_. Character_encoding_'%0'_is_not_supported.=L'encodage_de_caractères_'%0'_n'est_pas_supporté. -Created_groups.=Groupes_créés. - crossreferenced_entries_included=Entrées_avec_références_croisées_incluses Current_content=Contenu_actuel @@ -1469,7 +1461,6 @@ Metadata_change=Changement_dans_les_métadonnées Changes_have_been_made_to_the_following_metadata_elements=Des_modifications_ont_été_faites_aux_éléments_de_métadonnées_suivants Generate_groups_for_author_last_names=Création_de_groupes_pour_les_noms_d'auteurs -Generate_groups_for_editor_last_names=Création_de_groupes_pour_les_noms_d'éditeurs Generate_groups_from_keywords_in_a_BibTeX_field=Création_de_groupes_à_partir_de_mots-clefs_d'un_champ_BibTeX Enforce_legal_characters_in_BibTeX_keys=Imposer_des_caractères_légaux_dans_les_clefs_BibTeX diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 18e6d4fe6a3..10af11423fa 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Tautan_otomatis_hanya_pada_berkas_ Automatically_create_groups=Otomatis_membuat_grup -Automatically_create_groups_for_library.=Otomatis_membuat_grup_untuk_basisdata. - -Automatically_created_groups=Grup_yang_dibuat_otomatis - Automatically_remove_exact_duplicates=Otomatis_menghapus_yang_sama Allow_overwriting_existing_links.=Mengijinkan_menindih_tautan_yang_ada. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Pengaturan_penampilan_berubah Changed_preamble=Preamble_berubah -Characters_to_ignore=Karakter_diabaikan - Check_existing_file_links=Periksa_berkas_tautan_yang_sudah_ada Check_links=Periksa_tautan @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Tidak_bisa_menjalankan_program_'vim'. Could_not_save_file.=Tidak_bisa_membuka_berkas. Character_encoding_'%0'_is_not_supported.=Enkoding_karakter_'%0'_tidak_didukung. -Created_groups.=Grup_dibuat. - crossreferenced_entries_included=entri_referensi_silang_diikutkan Current_content=Isi_sekarang @@ -1469,7 +1461,6 @@ Metadata_change=Perubahan_Metadata Changes_have_been_made_to_the_following_metadata_elements=Perubahan_telah_dilakukan_pada_elemen_metadata_berikut Generate_groups_for_author_last_names=Membuat_grup_untuk_nama_belakang_penulis -Generate_groups_for_editor_last_names=Membuat_grup_untuk_nama_belakang_penyunting Generate_groups_from_keywords_in_a_BibTeX_field=Membuat_grup_dari_katakunci_di_bidang_BibTeX Enforce_legal_characters_in_BibTeX_keys=Menggunakan_karakter_legal_untuk_kunci_BibTeX diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 85023fe3a7a..e3c11025a97 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Collegare_automaticamente_solo_i_f Automatically_create_groups=Crea_automaticamente_i_gruppi -Automatically_create_groups_for_library.=Crea_automaticamente_i_gruppi_per_il_library - -Automatically_created_groups=Gruppi_creati_automaticamente - Automatically_remove_exact_duplicates=Rimuovi_automaticamente_i_duplicati_esatti Allow_overwriting_existing_links.=Consenti_la_sovrascrittura_dei_collegamenti_esistenti. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Parametri_del_"Look-and-Feel"_modificati Changed_preamble=Preambolo_modificato -Characters_to_ignore=Caratteri_da_ignorare - Check_existing_file_links=Verificare_i_collegamenti_a_file_esistenti Check_links=Verifica_i_collegamenti @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Impossibile_eseguire_il_programma_'vim'. Could_not_save_file.=Impossibile_salvare_il_file. Character_encoding_'%0'_is_not_supported.=La_codifica_dei_caratteri_'%0'_non_è_supportata. -Created_groups.=Gruppi_creati - crossreferenced_entries_included=Incluse_le_voci_con_riferimenti_incrociati Current_content=Contenuto_corrente @@ -1469,7 +1461,6 @@ Metadata_change=Modifica_dei_metadati Changes_have_been_made_to_the_following_metadata_elements=Sono_stati_modificati_i_seguenti_elementi_dei_metadati Generate_groups_for_author_last_names=Genera_gruppi_in_base_al_cognome_dell'autore -Generate_groups_for_editor_last_names=Genera_gruppi_in_base_al_cognome_del_curatore Generate_groups_from_keywords_in_a_BibTeX_field=Genera_gruppi_in_base_alle_parole_chiave_in_un_campo_BibTeX Enforce_legal_characters_in_BibTeX_keys=Imponi_l'utilizzo_dei_soli_caratteri_conformi_alla_sintassi_nelle_chiavi_BibTeX diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 431fa668158..e5c29977e43 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=BibTeX鍵に一致するファイ Automatically_create_groups=グループを自動生成 -Automatically_create_groups_for_library.=データベースのグループを自動生成 - -Automatically_created_groups=グループを自動生成しました - Automatically_remove_exact_duplicates=完全に同一な重複を自動削除 Allow_overwriting_existing_links.=既存リンクの上書きを許可する。 @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=操作性設定を変更しました Changed_preamble=プリアンブルを変更しました -Characters_to_ignore=無視する文字 - Check_existing_file_links=既存のファイルリンクを確認 Check_links=リンクを確認 @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=「vim」プログラムを実行できませ Could_not_save_file.=ファイルを保存できませんでした Character_encoding_'%0'_is_not_supported.=。文字エンコーディング「%0」はサポートされていません。 -Created_groups.=グループを生成しました。 - crossreferenced_entries_included=相互参照している項目を取り込みました Current_content=現在の内容 @@ -1469,7 +1461,6 @@ Metadata_change=メタデータの変更 Changes_have_been_made_to_the_following_metadata_elements=以下のメタデータ要素に変更を加えました Generate_groups_for_author_last_names=著者の姓でグループを生成する -Generate_groups_for_editor_last_names=編集者の姓でグループを生成する Generate_groups_from_keywords_in_a_BibTeX_field=BibTeXフィールドのキーワードからグループを生成する Enforce_legal_characters_in_BibTeX_keys=BibTeX鍵で規則に則った文字の使用を強制する diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index a5dc46e721e..1a6b3c7970d 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key= Automatically_create_groups=Groepen_automatisch_aanmaken -Automatically_create_groups_for_library.=Automatisch_groepen_voor_library_aanmaken - -Automatically_created_groups=Automatisch_aangemaakte_groepen - Automatically_remove_exact_duplicates=Automatisch_exacte_kopie\u00ebn_verwijderen Allow_overwriting_existing_links.=Overschrijven_van_bestaande_snelkoppelingen_toestaan. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Gewijzigde_"Look_and_Feel"-instellingen Changed_preamble=Gewijzigde_inleiding -Characters_to_ignore=Tekens_die_genegeerd_worden - Check_existing_file_links=Controleer_bestaande_bestand_snelkoppelingen Check_links=Controleer_snelkoppelingen @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.= Could_not_save_file.=Kon_het_bestand_niet_opslaan. Character_encoding_'%0'_is_not_supported.= -Created_groups.=Groepen_aangemaakt. - crossreferenced_entries_included=inclusief_kruisgerefereerde_entries Current_content=Huidige_inhoud @@ -1469,7 +1461,6 @@ Metadata_change= Changes_have_been_made_to_the_following_metadata_elements= Generate_groups_for_author_last_names= -Generate_groups_for_editor_last_names= Generate_groups_from_keywords_in_a_BibTeX_field= Enforce_legal_characters_in_BibTeX_keys= diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index f8b2cc42ec0..343a78e73ee 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Autolink_bare_filer_med_navn_som_s Automatically_create_groups=Generer_grupper_automatisk -Automatically_create_groups_for_library.=Generer_grupper_for_libraryn. - -Automatically_created_groups=Genererte_grupper_automatisk - Automatically_remove_exact_duplicates=Fjern_eksakte_duplikater_automatisk Allow_overwriting_existing_links.=Tillat_overskriving_av_eksisterende_linker. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Endret_oppsett_av_grensesnitt Changed_preamble=Endret_preamble -Characters_to_ignore=Ignorer_f\u00f8lgende_tegn - Check_existing_file_links=Sjekk_eksisterende_fil-linker Check_links=Sjekk_eksterne_linker @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Kunne_ikke_kj\u00f8re_'vim'-programmet Could_not_save_file.= Character_encoding_'%0'_is_not_supported.=Tegnkodingen_'%0'_er_ikke_st\u00f8ttet. -Created_groups.=Opprettet_grupper. - crossreferenced_entries_included=refererte_enheter_inkludert Current_content=N\u00e5v\u00e6rende_innhold @@ -1469,7 +1461,6 @@ Metadata_change=Endring_av_metadata Changes_have_been_made_to_the_following_metadata_elements=Endringer_er_gjort_for_de_f\u00b8lgende_metadata-elementene Generate_groups_for_author_last_names=Generer_grupper_for_etternavn_fra_author-feltet -Generate_groups_for_editor_last_names=Generer_grupper_for_etternavn_fra_editor-feltet Generate_groups_from_keywords_in_a_BibTeX_field=Generer_grupper_fra_n\u00b8kkelord_i_et_BibTeX-felt Enforce_legal_characters_in_BibTeX_keys=Forby_tegn_i_BibTeX-n\u00b8kler_som_ikke_aksepteres_av_BibTeX diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 27d5562c89f..b426247f1ed 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Criar_links_automaticamente_soment Automatically_create_groups=Criar_grupos_automaticamente -Automatically_create_groups_for_library.=Criar_grupos_automaticamente_para_a_base_de_dados. - -Automatically_created_groups=Grupos_criados_automaticamente - Automatically_remove_exact_duplicates=Remover_automaticamente_duplicatas_exatas Allow_overwriting_existing_links.=Sobrescrever_links_existentes. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Configurações_do_esquema_de_cores_modificadas Changed_preamble=Preâmbulo_modificado -Characters_to_ignore=Caracteres_para_ignorar - Check_existing_file_links=Verificar_links_de_arquivos_existentes Check_links=Verificar_links @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Não_foi_possível_executar_o_programa_'vim'. Could_not_save_file.=Não_foi_possível_salvar_o_arquivo. Character_encoding_'%0'_is_not_supported.=A_codificação_de_caracteres_'%0'_não_é_suportada. -Created_groups.=Grupos_criados. - crossreferenced_entries_included=Registros_com_referências_cruzadas_incluídos Current_content=Conteúdo_atual @@ -1469,7 +1461,6 @@ Metadata_change=Mudança_de_metadados Changes_have_been_made_to_the_following_metadata_elements=Mudanças_foram_realizadas_nos_seguintes_elementos_de_metadados Generate_groups_for_author_last_names=Gerar_grupos_a_partir_dos_últimos_nomes_dos_autores -Generate_groups_for_editor_last_names=Gerar_grupos_pelos_últimos_nomes_dos_editores Generate_groups_from_keywords_in_a_BibTeX_field=Gerar_grupos_a_partir_de_palavras_chaves_em_um_campo_BibTeX Enforce_legal_characters_in_BibTeX_keys=Forçar_caracteres_permitidos_em_chaves_BibTeX diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 8919d2d4da5..926106d5bb7 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Автоматическая_пр Automatically_create_groups=Автоматическое_создание_групп -Automatically_create_groups_for_library.=Автоматическое_создание_групп_для_БД. - -Automatically_created_groups=Автоматически_созданные_группы - Automatically_remove_exact_duplicates=Автоматически_удалять_полные_дубликаты Allow_overwriting_existing_links.=Разрешить_перезапись_текущих_ссылок. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Измененные_настройки_инте Changed_preamble=Измененная_преамбула -Characters_to_ignore=Не_учитывать_знаки - Check_existing_file_links=Проверить_текущие_ссылки_файл Check_links=Проверить_ссылки @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Не_удалось_запустить_прил Could_not_save_file.=Не_удалось_сохранить_файл. Character_encoding_'%0'_is_not_supported.=Кодировка_'%0'_не_поддерживается. -Created_groups.=Созданные_группы. - crossreferenced_entries_included=записи_с_перекрестными_ссылками Current_content=Текущее_содержимое @@ -1469,7 +1461,6 @@ Metadata_change=Изменение_метаданных Changes_have_been_made_to_the_following_metadata_elements=Произведены_изменения_для_следующих_элементов_метаданных Generate_groups_for_author_last_names=Создание_групп_для_фамилий_авторов -Generate_groups_for_editor_last_names=Создание_групп_для_фамилий_редакторов Generate_groups_from_keywords_in_a_BibTeX_field=Создание_групп_из_ключевых_слов_в_поле_BibTeX Enforce_legal_characters_in_BibTeX_keys=Принудительное_использование_допустимого_набора_символов_в_ключах_BibTeX diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index ed524e9f606..7b8ce00c7ae 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Länka_bara_filer_vars_namn_är_Bi Automatically_create_groups=Skapa_grupper_automatiskt -Automatically_create_groups_for_library.=Skapa_grupper_automatiskt_för_libraryn. - -Automatically_created_groups=Skapade_grupper_automatiskt - Automatically_remove_exact_duplicates=Ta_bort_exakta_dubbletter_automatiskt Allow_overwriting_existing_links.=Tillåt_att_befintliga_länkar_skrivs_över. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Ändra_inställningar_för_look-and-feel Changed_preamble=Ändrade_preamble -Characters_to_ignore=Bokstäver_att_ignorera - Check_existing_file_links=Kontrollera_befintliga_fillänkar Check_links=Kontrollera_länkar @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Kunde_inte_köra_'vim'. Could_not_save_file.=Kunde_inte_spara_fil. Character_encoding_'%0'_is_not_supported.=Teckenkodningen_'%0'_stöds_inte. -Created_groups.=Skapade_grupper. - crossreferenced_entries_included=korsrefererade_poster_inkluderade Current_content=Nuvarande_innehåll @@ -1469,7 +1461,6 @@ Metadata_change=Ändring_i_metadata Changes_have_been_made_to_the_following_metadata_elements=Ändringar_har_gjorts_i_följande_metadata-element Generate_groups_for_author_last_names=Generera_grupper_baserat_på_författarefternamn -Generate_groups_for_editor_last_names=Generera_grupper_baserat_på_editorefternamn Generate_groups_from_keywords_in_a_BibTeX_field=Generera_grupper_baserat_på_nyckelord_i_ett_fält Enforce_legal_characters_in_BibTeX_keys=Framtvinga_giltiga_tecken_i_BibTeX-nycklar diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 6d0eeb976ab..2d113e3e6b0 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Yalnızca_BibTeX_anahtarıyla_eşl Automatically_create_groups=Grupları_otomatik_oluştur -Automatically_create_groups_for_library.=Veritabanı_için_grupları_otomatik_oluştur - -Automatically_created_groups=Otomatik_oluşturulmuş_gruplar - Automatically_remove_exact_duplicates=Tıpkı_çift_nüshaları_otomatik_sil Allow_overwriting_existing_links.=Mevcut_linklerin_üzerine_yazmaya_izin_ver. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Görünüm_ve_tema_ayarları_değişti Changed_preamble=Öncül_değişti -Characters_to_ignore=Yoksayılacak_karakterler - Check_existing_file_links=Mevcut_dosya_linki_kontrol_ediniz Check_links=Linkleri_kontrol_ediniz @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.='Vim'_programı_çalıştırılamıyor. Could_not_save_file.=Dosya_kaydedilemiyor. Character_encoding_'%0'_is_not_supported.='%0'_karakter_kodlaması_desteklenmiyor. -Created_groups.=Oluşturulmuş_gruplar. - crossreferenced_entries_included=çapraz_bağlantılı_girdiler_dahil_edildi Current_content=Güncel_içerik @@ -1469,7 +1461,6 @@ Metadata_change=Metadata_değişikliği Changes_have_been_made_to_the_following_metadata_elements=Aşağıdaki_metadata_ögelerinde_değişiklik_yapıldı Generate_groups_for_author_last_names=Yazar_soyadları_için_grup_oluştur -Generate_groups_for_editor_last_names=Editör_soyadları_için_grup_oluştur Generate_groups_from_keywords_in_a_BibTeX_field=Bir_BibTeX_alanındaki_anahtar_sözcüklerden_grup_oluştur Enforce_legal_characters_in_BibTeX_keys=BibTeX_anahtarlarında_yasal_karakterleri_zorla diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 9fb8fcc7b1d..0d6ca424a35 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=Chỉ_tự_động_liên_kết_cá Automatically_create_groups=Tự_động_tạo_các_nhóm -Automatically_create_groups_for_library.=Tự_động_tạo_các_nhóm_dùng_cho_CSDL. - -Automatically_created_groups=Các_nhóm_được_tạo_ra_tự_động - Automatically_remove_exact_duplicates=Tự_động_loại_bỏ_các_mục_trùng_nhau Allow_overwriting_existing_links.=Cho_phép_ghi_đè_các_liên_kết_hiện_có. @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=Các_thiết_lập_diện_mạo_được_thay_đ Changed_preamble=Phần_mở_đầu_được_thay_đổi -Characters_to_ignore=Các_ký_tự_bỏ_qua - Check_existing_file_links=Kiểm_tra_tập_tin_liên_kết_hiện_có Check_links=Kiểm_tra_các_liên_kết @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=Không_thể_chạy_chương_trình_'vim'. Could_not_save_file.=Không_thể_lưu_tập_tin. Character_encoding_'%0'_is_not_supported.=Mã_hóa_ký_tự_'%0'_không_được_hỗ_trợ. -Created_groups.=Các_nhóm_được_tạo_ra. - crossreferenced_entries_included=các_mục_có_tham_chiếu_chéo_được_đưa_vào Current_content=Nội_dung_hiện_tại @@ -1469,7 +1461,6 @@ Metadata_change=Thay_đổi_đặc_tả_dữ_liệu Changes_have_been_made_to_the_following_metadata_elements=Các_thay_đổi_đã_được_thực_hiện_cho_những_thành_phần_đặc_tả_CSDL_sau Generate_groups_for_author_last_names=Tạo_các_nhóm_cho_họ_của_tác_giả -Generate_groups_for_editor_last_names=Tạo_các_nhóm_cho_tên_họ_của_người_biên_tập Generate_groups_from_keywords_in_a_BibTeX_field=Tạo_các_nhóm_theo_từ_khóa_trong_một_dữ_liệu_BibTeX Enforce_legal_characters_in_BibTeX_keys=Buộc_phải_dùng_những_ký_tự_hợp_lệ_trong_khóa_BibTeX diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index 4dd0c1d9f09..b53b323eadc 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -133,10 +133,6 @@ Autolink_only_files_that_match_the_BibTeX_key=自动链接文件名匹配_BibTeX Automatically_create_groups=自动创建分组 -Automatically_create_groups_for_library.=自动为数据库创建分组。 - -Automatically_created_groups=自动创建的分组 - Automatically_remove_exact_duplicates=自动移除完全重复的项 Allow_overwriting_existing_links.=允许覆盖已有的链接。 @@ -209,8 +205,6 @@ Changed_look_and_feel_settings=已修改显示效果_(look_and_feel)_设置 Changed_preamble=已修改导言区_(preamble) -Characters_to_ignore=要忽略的字符 - Check_existing_file_links=检查存在的文件链接 Check_links=核对链接 @@ -287,8 +281,6 @@ Could_not_run_the_'vim'_program.=无法运行_'vim'_程序。 Could_not_save_file.=无法保存文件 Character_encoding_'%0'_is_not_supported.=,不支持编码_'%0'。 -Created_groups.=建立分组 - crossreferenced_entries_included=包含交叉引用的记录 Current_content=当前内容 @@ -1469,7 +1461,6 @@ Metadata_change=元数据改变 Changes_have_been_made_to_the_following_metadata_elements=下列元数据元素被改变 Generate_groups_for_author_last_names=用作者的姓_(last_name)_创建分组 -Generate_groups_for_editor_last_names=用编者的姓_(last_name)_创建分组 Generate_groups_from_keywords_in_a_BibTeX_field=用_BibTeX_域中的关键词创建分组 Enforce_legal_characters_in_BibTeX_keys=强制在_BibTeX_键值中使用合法字符 diff --git a/src/test/java/org/jabref/ArchitectureTests.java b/src/test/java/org/jabref/ArchitectureTests.java index ac7caa38b17..d3b8b1decac 100644 --- a/src/test/java/org/jabref/ArchitectureTests.java +++ b/src/test/java/org/jabref/ArchitectureTests.java @@ -29,21 +29,19 @@ public class ArchitectureTests { private static final String CLASS_ORG_JABREF_GLOBALS = "org.jabref.Globals"; private static final String EXCEPTION_PACKAGE_JAVA_AWT_GEOM = "java.awt.geom"; - - private Map> exceptionStrings; - private final String firstPackage; private final String secondPackage; + private Map> exceptions; public ArchitectureTests(String firstPackage, String secondPackage) { this.firstPackage = firstPackage; this.secondPackage = secondPackage; - //add exceptions for the architectural test here - //Note that bending the architectural constraints should not be done inconsiderately - exceptionStrings = new HashMap<>(); - exceptionStrings.put(PACKAGE_ORG_JABREF_LOGIC, - Arrays.asList(EXCEPTION_PACKAGE_JAVA_AWT_GEOM)); + // Add exceptions for the architectural test here + // Note that bending the architectural constraints should not be done inconsiderately + exceptions = new HashMap<>(); + exceptions.put(PACKAGE_ORG_JABREF_LOGIC, + Collections.singletonList(EXCEPTION_PACKAGE_JAVA_AWT_GEOM)); } @@ -67,9 +65,10 @@ public static Iterable data() { @Test public void firstPackageIsIndependentOfSecondPackage() throws IOException { - Predicate isExceptionPackage = (s) -> s.startsWith("import " + secondPackage) && !(exceptionStrings.get(firstPackage).stream() - .filter(exception -> s.startsWith("import " + exception)).findAny().isPresent() - ); + Predicate isExceptionPackage = (s) -> + s.startsWith("import " + secondPackage) + && exceptions.getOrDefault(firstPackage, Collections.emptyList()).stream() + .noneMatch(exception -> s.startsWith("import " + exception)); Predicate isPackage = (s) -> s.startsWith("package " + firstPackage); @@ -77,22 +76,21 @@ public void firstPackageIsIndependentOfSecondPackage() throws IOException { .filter(p -> p.toString().endsWith(".java")) .filter(p -> { try { - return Files.readAllLines(p, StandardCharsets.UTF_8).stream() - .filter(isPackage).findAny().isPresent(); + return Files.readAllLines(p, StandardCharsets.UTF_8).stream().anyMatch(isPackage); } catch (IOException e) { return false; } }) .filter(p -> { try { - return Files.readAllLines(p, StandardCharsets.UTF_8).stream() - .filter(isExceptionPackage).findAny().isPresent(); + return Files.readAllLines(p, StandardCharsets.UTF_8).stream().anyMatch(isExceptionPackage); } catch (IOException e) { return false; } }).collect(Collectors.toList()); - Assert.assertEquals(Collections.emptyList(), files); + Assert.assertEquals("The following classes are not allowed to depend on " + secondPackage, + Collections.emptyList(), files); } } diff --git a/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java new file mode 100644 index 00000000000..07e626f5144 --- /dev/null +++ b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java @@ -0,0 +1,40 @@ +package org.jabref.gui.groups; + +import javafx.collections.FXCollections; + +import org.jabref.gui.StateManager; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.groups.AbstractGroup; +import org.jabref.model.groups.GroupHierarchyType; +import org.jabref.model.groups.WordKeywordGroup; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class GroupNodeViewModelTest { + + private StateManager stateManager; + private BibDatabaseContext databaseContext; + + @Before + public void setUp() throws Exception { + stateManager = mock(StateManager.class); + when(stateManager.getSelectedEntries()).thenReturn(FXCollections.emptyObservableList()); + databaseContext = new BibDatabaseContext(); + } + + @Test + public void getDisplayNameConvertsLatexToUnicode() throws Exception { + GroupNodeViewModel viewModel = getViewModelForGroup( + new WordKeywordGroup("\\beta", GroupHierarchyType.INDEPENDENT, "test", "search", true, ',', false)); + assertEquals("β", viewModel.getDisplayName()); + } + + private GroupNodeViewModel getViewModelForGroup(AbstractGroup group) { + return new GroupNodeViewModel(databaseContext, stateManager, group); + } +} diff --git a/src/test/java/org/jabref/gui/groups/GroupsUtilTest.java b/src/test/java/org/jabref/gui/groups/GroupsUtilTest.java deleted file mode 100644 index 9d608506498..00000000000 --- a/src/test/java/org/jabref/gui/groups/GroupsUtilTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.jabref.gui.groups; - -import java.io.BufferedReader; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.importer.fileformat.BibtexParser; -import org.jabref.model.database.BibDatabase; -import org.jabref.preferences.JabRefPreferences; - -import org.junit.Assert; -import org.junit.Test; - -public class GroupsUtilTest { - - - @Test - public void test() throws IOException { - try (BufferedReader fr = Files.newBufferedReader(Paths.get("src/test/resources/testbib/testjabref.bib"), - StandardCharsets.UTF_8)) { - - ParserResult result = new BibtexParser(JabRefPreferences.getInstance().getImportFormatPreferences()).parse(fr); - - BibDatabase db = result.getDatabase(); - - List fieldList = new ArrayList<>(); - fieldList.add("author"); - - Set authorSet = AutoGroupDialog.findAuthorLastNames(db, fieldList); - Assert.assertTrue(authorSet.contains("Brewer")); - Assert.assertEquals(15, authorSet.size()); - - Set keywordSet = AutoGroupDialog.findDeliminatedWordsInField(db, "keywords", ";"); - Assert.assertTrue(keywordSet.contains("Brain")); - Assert.assertEquals(60, keywordSet.size()); - - Set wordSet = AutoGroupDialog.findAllWordsInField(db, "month", ""); - Assert.assertTrue(wordSet.contains("Feb")); - Assert.assertTrue(wordSet.contains("Mar")); - Assert.assertTrue(wordSet.contains("May")); - Assert.assertTrue(wordSet.contains("Jul")); - Assert.assertTrue(wordSet.contains("Dec")); - Assert.assertEquals(5, wordSet.size()); - } - } - -} diff --git a/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java b/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java new file mode 100644 index 00000000000..aa9b80c496e --- /dev/null +++ b/src/test/java/org/jabref/model/groups/AutomaticKeywordGroupTest.java @@ -0,0 +1,24 @@ +package org.jabref.model.groups; + +import java.util.HashSet; +import java.util.Set; + +import org.jabref.model.entry.BibEntry; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class AutomaticKeywordGroupTest { + + @Test + public void createSubgroupsForTwoKeywords() throws Exception { + AutomaticKeywordGroup keywordsGroup = new AutomaticKeywordGroup("Keywords", GroupHierarchyType.INDEPENDENT, "keywords", ','); + BibEntry entry = new BibEntry().withField("keywords", "A, B"); + + Set expected = new HashSet<>(); + expected.add(GroupTreeNode.fromGroup(new WordKeywordGroup("A", GroupHierarchyType.INDEPENDENT, "keywords", "A", true, ',', true))); + expected.add(GroupTreeNode.fromGroup(new WordKeywordGroup("B", GroupHierarchyType.INDEPENDENT, "keywords", "B", true, ',', true))); + assertEquals(expected, keywordsGroup.createSubgroups(entry)); + } +} From 83219486e310c0bd942483550bd7fa6b00645d2c Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Fri, 24 Feb 2017 12:48:56 +0100 Subject: [PATCH 12/21] Fix names of groups in "add/move to group" dialog --- src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java b/src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java index eef25a3dc91..811b46499fc 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeCellRenderer.java @@ -70,7 +70,7 @@ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean if (viewModel.printInItalics()) { sb.append(""); } - sb.append("Group Name"); + sb.append(viewModel.getName()); if (viewModel.printInItalics()) { sb.append(""); } From 921621dfb43b17122022a7eb6cd61c080380d875 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Mon, 27 Feb 2017 09:54:49 +0100 Subject: [PATCH 13/21] Add asString with UTF-8 as standard encoding --- .../jabref/gui/importer/fetcher/CiteSeerXFetcher.java | 2 +- .../gui/importer/fetcher/IEEEXploreFetcher.java | 2 +- .../logic/importer/fetcher/BibsonomyScraper.java | 2 +- .../org/jabref/logic/importer/fetcher/DoiFetcher.java | 2 +- .../jabref/logic/importer/fetcher/GoogleScholar.java | 4 ++-- .../java/org/jabref/logic/importer/fetcher/IEEE.java | 5 ++--- .../jabref/logic/importer/fetcher/MrDLibFetcher.java | 2 +- src/main/java/org/jabref/logic/net/URLDownload.java | 11 +++++++++++ .../java/org/jabref/logic/net/URLDownloadTest.java | 3 +-- 9 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java index 44bbcc2fd31..16c5983469b 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/CiteSeerXFetcher.java @@ -127,7 +127,7 @@ private static String getCitationsFromUrl(String urlQuery, List ids) thr private static BibEntry getSingleCitation(String urlString) throws IOException { - String cont = new URLDownload(urlString).asString(StandardCharsets.UTF_8); + String cont = new URLDownload(urlString).asString(); // Find title, and create entry if we do. Otherwise assume we did not get an entry: Matcher m = CiteSeerXFetcher.TITLE_PATTERN.matcher(cont); diff --git a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java index 7ca46e27e66..37b9b7136f9 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java @@ -110,7 +110,7 @@ public boolean processQuery(String query, ImportInspector dialog, OutputPrinter dl.setPostData(postData); //retrieve the search results - String page = dl.asString(StandardCharsets.UTF_8); + String page = dl.asString(); //the page can be blank if the search did not work (not sure the exact conditions that lead to this, but declaring it an invalid search for now) if (page.isEmpty()) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java b/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java index ee4b8c202d9..f2f98130088 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java @@ -37,7 +37,7 @@ public static Optional getEntry(String entryUrl, ImportFormatPreferenc .replace("&", "%26").replace("=", "%3D"); URL url = new URL(BibsonomyScraper.BIBSONOMY_SCRAPER + cleanURL + BibsonomyScraper.BIBSONOMY_SCRAPER_POST); - String bibtex = new URLDownload(url).asString(StandardCharsets.UTF_8); + String bibtex = new URLDownload(url).asString(); return BibtexParser.singleFromString(bibtex, importFormatPreferences); } catch (IOException ex) { LOGGER.warn("Could not download entry", ex); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java index c2d90d2b7e3..7f27f30faf3 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java @@ -53,7 +53,7 @@ public Optional performSearchById(String identifier) throws FetcherExc // BibTeX data URLDownload download = new URLDownload(doiURL); download.addHeader("Accept", "application/x-bibtex"); - String bibtexString = download.asString(StandardCharsets.UTF_8); + String bibtexString = download.asString(); // BibTeX entry Optional fetchedEntry = BibtexParser.singleFromString(bibtexString, preferences); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java index a3406537080..1df5c0ec37e 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java @@ -142,7 +142,7 @@ public List performSearch(String query) throws FetcherException { } private void addHitsFromQuery(List entryList, String queryURL) throws IOException, FetcherException { - String content = new URLDownload(queryURL).asString(StandardCharsets.UTF_8); + String content = new URLDownload(queryURL).asString(); Matcher matcher = LINK_TO_BIB_PATTERN.matcher(content); while (matcher.find()) { @@ -153,7 +153,7 @@ private void addHitsFromQuery(List entryList, String queryURL) throws } private BibEntry downloadEntry(String link) throws IOException, FetcherException { - String downloadedContent = new URLDownload(link).asString(StandardCharsets.UTF_8); + String downloadedContent = new URLDownload(link).asString(); BibtexParser parser = new BibtexParser(importFormatPreferences); ParserResult result = parser.parse(new StringReader(downloadedContent)); if ((result == null) || (result.getDatabase() == null)) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index eccafb2c1bf..0c015b7ac53 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -53,8 +53,7 @@ public Optional findFullText(BibEntry entry) throws IOException { Optional doi = entry.getField(FieldName.DOI).flatMap(DOI::build); if (doi.isPresent() && doi.get().getDOI().startsWith(IEEE_DOI) && doi.get().getURI().isPresent()) { // Download the HTML page from IEEE - String resolvedDOIPage = new URLDownload(doi.get().getURI().get().toURL()) - .asString(StandardCharsets.UTF_8); + String resolvedDOIPage = new URLDownload(doi.get().getURI().get().toURL()).asString(); // Try to find the link Matcher matcher = STAMP_PATTERN.matcher(resolvedDOIPage); if (matcher.find()) { @@ -70,7 +69,7 @@ public Optional findFullText(BibEntry entry) throws IOException { } // Download the HTML page containing a frame with the PDF - String framePage = new URLDownload(BASE_URL + stampString).asString(StandardCharsets.UTF_8); + String framePage = new URLDownload(BASE_URL + stampString).asString(); // Try to find the direct PDF link Matcher matcher = PDF_PATTERN.matcher(framePage); if (matcher.find()) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java index 09c8591b7d5..74245597ac4 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java @@ -88,7 +88,7 @@ private String makeServerRequest(String queryByTitle) throws FetcherException { try { URLDownload urlDownload = new URLDownload(constructQuery(queryByTitle)); urlDownload.bypassSSLVerification(); - String response = urlDownload.asString(StandardCharsets.UTF_8); + String response = urlDownload.asString(); //Conversion of < and > response = response.replaceAll(">", ">"); diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 1e71577d210..b6edd1f7464 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -19,6 +19,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -149,6 +150,16 @@ public String asString(Charset encoding) throws IOException { } } + /** + * Downloads the web resource to a String. + * Uses UTF-8 as encoding. + * + * @return the downloaded string + */ + public String asString() throws IOException { + return asString(StandardCharsets.UTF_8); + } + public List getCookieFromUrl() throws IOException { CookieManager cookieManager = new CookieManager(); CookieHandler.setDefault(cookieManager); diff --git a/src/test/java/org/jabref/logic/net/URLDownloadTest.java b/src/test/java/org/jabref/logic/net/URLDownloadTest.java index 5bc8f59a920..2340f0dddc6 100644 --- a/src/test/java/org/jabref/logic/net/URLDownloadTest.java +++ b/src/test/java/org/jabref/logic/net/URLDownloadTest.java @@ -17,8 +17,7 @@ public class URLDownloadTest { public void testStringDownloadWithSetEncoding() throws IOException { URLDownload dl = new URLDownload(new URL("http://www.google.com")); - Assert.assertTrue("google.com should contain google", - dl.asString(StandardCharsets.UTF_8).contains("Google")); + Assert.assertTrue("google.com should contain google", dl.asString().contains("Google")); } @Test From a31fab384b8bcc3b7dfe3156058f5c2d453da142 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Mon, 27 Feb 2017 10:00:27 +0100 Subject: [PATCH 14/21] isPdf helper method --- src/main/java/org/jabref/logic/importer/FulltextFetchers.java | 2 +- .../java/org/jabref/logic/importer/fetcher/DoiResolution.java | 2 +- src/main/java/org/jabref/logic/net/URLDownload.java | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/FulltextFetchers.java b/src/main/java/org/jabref/logic/importer/FulltextFetchers.java index 9404c65f205..a139925b594 100644 --- a/src/main/java/org/jabref/logic/importer/FulltextFetchers.java +++ b/src/main/java/org/jabref/logic/importer/FulltextFetchers.java @@ -60,7 +60,7 @@ public Optional findFullTextPDF(BibEntry entry) { try { Optional result = finder.findFullText(clonedEntry); - if (result.isPresent() && new URLDownload(result.get().toString()).isMimeType("application/pdf")) { + if (result.isPresent() && new URLDownload(result.get().toString()).isPdf()) { return result; } } catch (IOException | FetcherException e) { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java index 69c987fee06..5bd6653edfa 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiResolution.java @@ -61,7 +61,7 @@ public Optional findFullText(BibEntry entry) throws IOException { // Only check if pdf is included in the link or inside the text // ACM uses tokens without PDF inside the link // See https://github.com/lehner/LocalCopy for more scrape ideas - if ((href.contains("pdf") || hrefText.contains("pdf")) && new URLDownload(href).isMimeType("application/pdf")) { + if ((href.contains("pdf") || hrefText.contains("pdf")) && new URLDownload(href).isPdf()) { links.add(Optional.of(new URL(href))); } } diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index b6edd1f7464..8e46a4eb74f 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -123,6 +123,10 @@ public boolean isMimeType(String type) throws IOException { return mime.startsWith(type); } + public boolean isPdf() throws IOException { + return isMimeType("application/pdf"); + } + public void addHeader(String key, String value) { this.parameters.put(key, value); } From b87fa2c8fa32523aba54738ef788acac915f5da1 Mon Sep 17 00:00:00 2001 From: MLEP Date: Mon, 27 Feb 2017 14:17:49 +0100 Subject: [PATCH 15/21] Localization: MainFile: French: update (#2591) --- src/main/resources/l10n/JabRef_fr.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 0f04bfa1bde..6c83a69f20c 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -335,15 +335,15 @@ delete_entry=effacer_l'entrée Delete_multiple_entries=Effacer_plusieurs_entrées -Delete_rows=Supprimer_des_lignes +Delete_rows=Supprimer_les_lignes Delete_strings=Supprimer_les_chaînes Deleted=Supprimé Permanently_delete_local_file=Supprimer_le_fichier_local -Delete_file= -Delete_'%0'?= +Delete_file=Supprimer_le_fichier +Delete_'%0'?=Supprimer_'%0'_? Delimit_fields_with_semicolon,_ex.=Délimiter_les_champs_par_des_points-virgules,_ex. Descending=Descendant From 96b0fe011fc92cac3680256abfbae37fbcff20d1 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Mon, 27 Feb 2017 10:06:43 +0100 Subject: [PATCH 16/21] Imports --- .../java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java | 1 - .../java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java | 1 - src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java | 1 - .../java/org/jabref/logic/importer/fetcher/GoogleScholar.java | 1 - src/main/java/org/jabref/logic/importer/fetcher/IEEE.java | 1 - .../java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java | 1 - src/test/java/org/jabref/logic/net/URLDownloadTest.java | 1 - 7 files changed, 7 deletions(-) diff --git a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java index 37b9b7136f9..fc5e4dd108e 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/IEEEXploreFetcher.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.net.CookieHandler; import java.net.CookieManager; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java b/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java index f2f98130088..370867fe699 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/BibsonomyScraper.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.Optional; import org.jabref.logic.importer.ImportFormatPreferences; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java index 7f27f30faf3..fb5553bc2f8 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DoiFetcher.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java index 1df5c0ec37e..4878c61341b 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java @@ -5,7 +5,6 @@ import java.net.HttpCookie; import java.net.URISyntaxException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index 0c015b7ac53..4d9d1ae79f1 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java index 74245597ac4..bfe46bae381 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/MrDLibFetcher.java @@ -8,7 +8,6 @@ import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; diff --git a/src/test/java/org/jabref/logic/net/URLDownloadTest.java b/src/test/java/org/jabref/logic/net/URLDownloadTest.java index 2340f0dddc6..8af5396a089 100644 --- a/src/test/java/org/jabref/logic/net/URLDownloadTest.java +++ b/src/test/java/org/jabref/logic/net/URLDownloadTest.java @@ -3,7 +3,6 @@ import java.io.File; import java.io.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import org.jabref.preferences.JabRefPreferences; From b610356388ce57f6e908048f5bb760e9c9655997 Mon Sep 17 00:00:00 2001 From: Stefan Kolb Date: Mon, 27 Feb 2017 14:52:53 +0100 Subject: [PATCH 17/21] Fix test --- .../java/org/jabref/logic/net/URLDownload.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/logic/net/URLDownload.java b/src/main/java/org/jabref/logic/net/URLDownload.java index 8e46a4eb74f..7313fa3a485 100644 --- a/src/main/java/org/jabref/logic/net/URLDownload.java +++ b/src/main/java/org/jabref/logic/net/URLDownload.java @@ -82,10 +82,13 @@ public URLDownload(URL source) { public String getMimeType() throws IOException { Unirest.setDefaultHeader("User-Agent", "Mozilla/5.0 (Windows; U; WindowsNT 5.1; en-US; rv1.8.1.6) Gecko/20070725 Firefox/2.0.0.6"); - String contentType = ""; + String contentType; // Try to use HEAD request to avoid downloading the whole file try { contentType = Unirest.head(source.toString()).asString().getHeaders().get("Content-Type").get(0); + if (contentType != null && !contentType.isEmpty()) { + return contentType; + } } catch (Exception e) { LOGGER.debug("Error getting MIME type of URL via HEAD request", e); } @@ -93,6 +96,9 @@ public String getMimeType() throws IOException { // Use GET request as alternative if no HEAD request is available try { contentType = Unirest.get(source.toString()).asString().getHeaders().get("Content-Type").get(0); + if (contentType != null && !contentType.isEmpty()) { + return contentType; + } } catch (Exception e) { LOGGER.debug("Error getting MIME type of URL via GET request", e); } @@ -102,15 +108,14 @@ public String getMimeType() throws IOException { URLConnection connection = new URL(source.toString()).openConnection(); contentType = connection.getContentType(); + if (contentType != null && !contentType.isEmpty()) { + return contentType; + } } catch (IOException e) { LOGGER.debug("Error trying to get MIME type of local URI", e); } - if (contentType != null) { - return contentType; - } else { - return ""; - } + return ""; } public boolean isMimeType(String type) throws IOException { From 0a5c8b770322aaaea4a13ebc00d0e0c6d4d38daf Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 28 Feb 2017 14:11:37 +0100 Subject: [PATCH 18/21] Fix pdf file import when only one file selected and no entry created (#2592) * Fix pdf file import when only one file selected and no entry * Add changelog entry --- CHANGELOG.md | 2 +- .../java/org/jabref/pdfimport/PdfImporter.java | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed83bb94f8d..5d073a77117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - ArXiV fetcher now checks similarity of entry when using DOI retrieval to avoid false positives [#2575](https://github.com/JabRef/jabref/issues/2575) - Sciencedirect/Elsevier fetcher is now able to scrape new HTML structure [#2576](https://github.com/JabRef/jabref/issues/2576) - Fixed the synchronization logic of keywords and special fields and vice versa [#2580](https://github.com/JabRef/jabref/issues/2580) - + - We fixed an issue where the "find unlinked files" functionality threw an error when only one PDF was imported but not assigned to an entry [#2577](https://github.com/JabRef/jabref/issues/2577) ### Removed diff --git a/src/main/java/org/jabref/pdfimport/PdfImporter.java b/src/main/java/org/jabref/pdfimport/PdfImporter.java index 0e6d67f4ec8..e7cf29f8d6e 100644 --- a/src/main/java/org/jabref/pdfimport/PdfImporter.java +++ b/src/main/java/org/jabref/pdfimport/PdfImporter.java @@ -64,30 +64,25 @@ public PdfImporter(JabRefFrame frame, BasePanel panel, MainTable entryTable, int this.dropRow = dropRow; } - public class ImportPdfFilesResult { private final List noPdfFiles; private final List entries; - public ImportPdfFilesResult(List noPdfFiles, List entries) { this.noPdfFiles = noPdfFiles; this.entries = entries; } - public List getNoPdfFiles() { return noPdfFiles; } - public List getEntries() { return entries; } } - /** * * Imports the PDF files given by fileNames @@ -128,7 +123,6 @@ private List importPdfFilesInternal(List fileNames) { boolean neverShow = Globals.prefs.getBoolean(JabRefPreferences.IMPORT_ALWAYSUSE); int globalChoice = Globals.prefs.getInt(JabRefPreferences.IMPORT_DEFAULT_PDF_IMPORT_STYLE); - List res = new ArrayList<>(); for (String fileName : fileNames) { @@ -156,7 +150,11 @@ private List importPdfFilesInternal(List fileNames) { break; case ImportDialog.ONLYATTACH: DroppedFileHandler dfh = new DroppedFileHandler(frame, panel); - dfh.linkPdfToEntry(fileName, entryTable, dropRow); + if (dropRow >= 0) { + dfh.linkPdfToEntry(fileName, entryTable, dropRow); + } else { + dfh.linkPdfToEntry(fileName, entryTable, entryTable.getSelectedRow()); + } break; default: break; @@ -236,7 +234,8 @@ private void doContentImport(String fileName, List res) { panel.getDatabase().insertEntry(entry); panel.markBaseChanged(); BibtexKeyPatternUtil.makeAndSetLabel(panel.getBibDatabaseContext().getMetaData() - .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), panel.getDatabase(), entry, + .getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()), panel.getDatabase(), + entry, Globals.prefs.getBibtexKeyPatternPreferences()); DroppedFileHandler dfh = new DroppedFileHandler(frame, panel); dfh.linkPdfToEntry(fileName, entry); From bf12dff243f53f989d20f1926bbd3b9c3ba14092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lenhard?= Date: Tue, 28 Feb 2017 21:43:10 +0100 Subject: [PATCH 19/21] Improve braces checking (#2593) * Do not count escaped braces for brace calculation * Add changelog entry --- CHANGELOG.md | 3 ++- .../logic/bibtex/LatexFieldFormatter.java | 18 +++++++++++++----- .../logic/bibtex/LatexFieldFormatterTests.java | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d073a77117..ba31c920e6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,8 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Sciencedirect/Elsevier fetcher is now able to scrape new HTML structure [#2576](https://github.com/JabRef/jabref/issues/2576) - Fixed the synchronization logic of keywords and special fields and vice versa [#2580](https://github.com/JabRef/jabref/issues/2580) - We fixed an issue where the "find unlinked files" functionality threw an error when only one PDF was imported but not assigned to an entry [#2577](https://github.com/JabRef/jabref/issues/2577) - + - We fixed issue where escaped braces were incorrectly counted when calculating brace balance in a field [#2561](https://github.com/JabRef/jabref/issues/2561) + ### Removed diff --git a/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java b/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java index 74c896d0ebd..74ca79d9014 100644 --- a/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java +++ b/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java @@ -259,11 +259,19 @@ private static void checkBraces(String text) throws IllegalArgumentException { int current = -1; // First we collect all occurrences: - while ((current = text.indexOf('{', current + 1)) != -1) { - left.add(current); - } - while ((current = text.indexOf('}', current + 1)) != -1) { - right.add(current); + for (int i = 0; i < text.length(); i++) { + char item = text.charAt(i); + + boolean charBeforeIsEscape = false; + if(i > 0 && text.charAt(i - 1) == '\\') { + charBeforeIsEscape = true; + } + + if(!charBeforeIsEscape && item == '{') { + left.add(current); + } else if (!charBeforeIsEscape && item == '{') { + right.add(current); + } } // Then we throw an exception if the error criteria are met. diff --git a/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java b/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java index a8422abfd34..541604a68ca 100644 --- a/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java +++ b/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java @@ -81,4 +81,18 @@ public void removeWhitespaceFromNonMultiLineFields() throws Exception { assertEquals(expected, title); assertEquals(expected, any); } + + @Test(expected = IllegalArgumentException.class) + public void reportUnbalancedBracing() { + String unbalanced = "{"; + + formatter.format(unbalanced, "anyfield"); + } + + @Test(expected = IllegalArgumentException.class) + public void reportUnbalancedBracingWithEscapedBraces() { + String unbalanced = "{\\}"; + + formatter.format(unbalanced, "anyfield"); + } } From 36ccd643440fc78c734b6cd0fee95be2bc61414c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lenhard?= Date: Wed, 1 Mar 2017 23:40:45 +0100 Subject: [PATCH 20/21] Braces checking followup (#2598) * Fix error in braces checking * Remove unused imports --- .../logic/bibtex/LatexFieldFormatter.java | 20 ++++++++----------- .../bibtex/LatexFieldFormatterTests.java | 14 +++++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java b/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java index 74ca79d9014..36edd61616b 100644 --- a/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java +++ b/src/main/java/org/jabref/logic/bibtex/LatexFieldFormatter.java @@ -1,8 +1,5 @@ package org.jabref.logic.bibtex; -import java.util.ArrayList; -import java.util.List; - import org.jabref.logic.util.OS; import org.jabref.model.entry.InternalBibtexFields; import org.jabref.model.strings.StringUtil; @@ -254,9 +251,8 @@ private void putIn(String s) { } private static void checkBraces(String text) throws IllegalArgumentException { - List left = new ArrayList<>(5); - List right = new ArrayList<>(5); - int current = -1; + int left = 0; + int right = 0; // First we collect all occurrences: for (int i = 0; i < text.length(); i++) { @@ -268,20 +264,20 @@ private static void checkBraces(String text) throws IllegalArgumentException { } if(!charBeforeIsEscape && item == '{') { - left.add(current); - } else if (!charBeforeIsEscape && item == '{') { - right.add(current); + left++; + } else if (!charBeforeIsEscape && item == '}') { + right++; } } // Then we throw an exception if the error criteria are met. - if (!right.isEmpty() && left.isEmpty()) { + if (!(right == 0) && (left == 0)) { throw new IllegalArgumentException("'}' character ends string prematurely."); } - if (!right.isEmpty() && (right.get(0) < left.get(0))) { + if (!(right == 0) && (right < left)) { throw new IllegalArgumentException("'}' character ends string prematurely."); } - if (left.size() != right.size()) { + if (left != right) { throw new IllegalArgumentException("Braces don't match."); } diff --git a/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java b/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java index 541604a68ca..918296d0998 100644 --- a/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java +++ b/src/test/java/org/jabref/logic/bibtex/LatexFieldFormatterTests.java @@ -95,4 +95,18 @@ public void reportUnbalancedBracingWithEscapedBraces() { formatter.format(unbalanced, "anyfield"); } + + @Test + public void tolerateBalancedBrace() { + String text = "Incorporating evolutionary {Measures into Conservation Prioritization}"; + + assertEquals("{" + text + "}", formatter.format(text, "anyfield")); + } + + @Test + public void tolerateEscapeCharacters() { + String text = "Incorporating {\\O}evolutionary {Measures into Conservation Prioritization}"; + + assertEquals("{" + text + "}", formatter.format(text, "anyfield")); + } } From 48f5293dca7bf19a027e64ea6ea64cf0f2d3734a Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Thu, 2 Mar 2017 11:45:03 +0100 Subject: [PATCH 21/21] Implement #1904: filter groups (#2588) --- CHANGELOG.md | 1 + .../jabref/gui/groups/GroupNodeViewModel.java | 21 +- .../java/org/jabref/gui/groups/GroupTree.css | 2 +- .../java/org/jabref/gui/groups/GroupTree.fxml | 31 +- .../gui/groups/GroupTreeController.java | 44 +- .../jabref/gui/groups/GroupTreeViewModel.java | 23 +- .../jabref/gui/util/RecursiveTreeItem.java | 55 ++- .../gui/groups/GroupNodeViewModelTest.java | 16 + .../gui/util/RecursiveTreeItemTest.java | 58 +++ .../java/org/jabref/model/TreeNodeTest.java | 402 +++++++----------- .../org/jabref/model/TreeNodeTestData.java | 121 ++++++ 11 files changed, 470 insertions(+), 304 deletions(-) create mode 100644 src/test/java/org/jabref/gui/util/RecursiveTreeItemTest.java create mode 100644 src/test/java/org/jabref/model/TreeNodeTestData.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ba31c920e6a..0a1dedb8aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Redesigned group panel. - Number of matched entries is always shown. - The background color of the hit counter signals whether the group contains all/any of the entries selected in the main table. + - Added a possibility to filter the groups panel [#1904](https://github.com/JabRef/jabref/issues/1904) - Removed edit mode. - Redesigned about dialog. - Redesigned key bindings dialog. diff --git a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index 61d8f72742f..578465c411c 100644 --- a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -27,6 +27,7 @@ import org.jabref.model.groups.AllEntriesGroup; import org.jabref.model.groups.AutomaticGroup; import org.jabref.model.groups.GroupTreeNode; +import org.jabref.model.strings.StringUtil; import com.google.common.eventbus.Subscribe; import org.fxmisc.easybind.EasyBind; @@ -136,13 +137,8 @@ public boolean equals(Object o) { GroupNodeViewModel that = (GroupNodeViewModel) o; - if (isRoot != that.isRoot) return false; - if (!displayName.equals(that.displayName)) return false; - if (!iconCode.equals(that.iconCode)) return false; - if (!children.equals(that.children)) return false; - if (!databaseContext.equals(that.databaseContext)) return false; if (!groupNode.equals(that.groupNode)) return false; - return hits.getValue().equals(that.hits.getValue()); + return true; } @Override @@ -160,14 +156,7 @@ public String toString() { @Override public int hashCode() { - int result = displayName.hashCode(); - result = 31 * result + (isRoot ? 1 : 0); - result = 31 * result + iconCode.hashCode(); - result = 31 * result + children.hashCode(); - result = 31 * result + databaseContext.hashCode(); - result = 31 * result + groupNode.hashCode(); - result = 31 * result + hits.hashCode(); - return result; + return groupNode.hashCode(); } public String getIconCode() { @@ -207,4 +196,8 @@ public GroupTreeNode addSubgroup(AbstractGroup subgroup) { void toggleExpansion() { expandedProperty().set(!expandedProperty().get()); } + + boolean isMatchedBy(String searchString) { + return StringUtil.isBlank(searchString) || getDisplayName().contains(searchString); + } } diff --git a/src/main/java/org/jabref/gui/groups/GroupTree.css b/src/main/java/org/jabref/gui/groups/GroupTree.css index 0c36d609d15..284c08f7f68 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTree.css +++ b/src/main/java/org/jabref/gui/groups/GroupTree.css @@ -107,7 +107,7 @@ -fx-translate-x: -5px; } -#buttonBarBottom { +#barBottom { -fx-background-color: #dadad8; -fx-border-color: dimgray; -fx-border-width: 1 0 0 0; diff --git a/src/main/java/org/jabref/gui/groups/GroupTree.fxml b/src/main/java/org/jabref/gui/groups/GroupTree.fxml index 571694c45af..f0ce56026e6 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTree.fxml +++ b/src/main/java/org/jabref/gui/groups/GroupTree.fxml @@ -6,6 +6,8 @@ + + @@ -24,18 +26,21 @@ - - - - - + + + + + + + + diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeController.java b/src/main/java/org/jabref/gui/groups/GroupTreeController.java index 590a8b76820..1d175aaf7ef 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeController.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeController.java @@ -1,7 +1,11 @@ package org.jabref.gui.groups; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + import javax.inject.Inject; +import javafx.beans.property.ObjectProperty; import javafx.css.PseudoClass; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -9,6 +13,7 @@ import javafx.scene.control.Control; import javafx.scene.control.MenuItem; import javafx.scene.control.SelectionModel; +import javafx.scene.control.TextField; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableColumn; import javafx.scene.control.TreeTableRow; @@ -24,14 +29,21 @@ import org.jabref.gui.util.ViewModelTreeTableCellFactory; import org.jabref.logic.l10n.Localization; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.controlsfx.control.textfield.CustomTextField; +import org.controlsfx.control.textfield.TextFields; import org.fxmisc.easybind.EasyBind; public class GroupTreeController extends AbstractController { + private static final Log LOGGER = LogFactory.getLog(GroupTreeController.class); + @FXML private TreeTableView groupTree; @FXML private TreeTableColumn mainColumn; @FXML private TreeTableColumn numberColumn; @FXML private TreeTableColumn disclosureNodeColumn; + @FXML private CustomTextField searchField; @Inject private StateManager stateManager; @Inject private DialogService dialogService; @@ -41,15 +53,23 @@ public void initialize() { viewModel = new GroupTreeViewModel(stateManager, dialogService); // Set-up bindings - groupTree.rootProperty().bind( - EasyBind.map(viewModel.rootGroupProperty(), - group -> new RecursiveTreeItem<>(group, GroupNodeViewModel::getChildren, GroupNodeViewModel::expandedProperty)) - ); viewModel.selectedGroupProperty().bind( EasyBind.monadic(groupTree.selectionModelProperty()) .flatMap(SelectionModel::selectedItemProperty) .selectProperty(TreeItem::valueProperty) ); + viewModel.filterTextProperty().bind(searchField.textProperty()); + searchField.textProperty().addListener((observable, oldValue, newValue) -> { + }); + + groupTree.rootProperty().bind( + EasyBind.map(viewModel.rootGroupProperty(), + group -> new RecursiveTreeItem<>( + group, + GroupNodeViewModel::getChildren, + GroupNodeViewModel::expandedProperty, + viewModel.filterPredicateProperty())) + ); // Icon and group name mainColumn.setCellValueFactory(cellData -> cellData.getValue().valueProperty()); @@ -122,6 +142,9 @@ public void initialize() { return row; }); + + // Filter text field + setupClearButtonField(searchField); } private ContextMenu createContextMenuForGroup(GroupNodeViewModel group) { @@ -137,4 +160,17 @@ private ContextMenu createContextMenuForGroup(GroupNodeViewModel group) { public void addNewGroup(ActionEvent actionEvent) { viewModel.addNewGroupToRoot(); } + + /** + * Workaround taken from https://bitbucket.org/controlsfx/controlsfx/issues/330/making-textfieldssetupclearbuttonfield + */ + private void setupClearButtonField(CustomTextField customTextField) { + try { + Method m = TextFields.class.getDeclaredMethod("setupClearButtonField", TextField.class, ObjectProperty.class); + m.setAccessible(true); + m.invoke(null, customTextField, customTextField.rightProperty()); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + LOGGER.error("Failed to decorate text field with clear button", ex); + } + } } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index 6e7ed7e35b1..ea057cc0678 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -2,9 +2,13 @@ import java.util.Objects; import java.util.Optional; +import java.util.function.Predicate; +import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; @@ -21,18 +25,23 @@ public class GroupTreeViewModel extends AbstractViewModel { private final ObjectProperty selectedGroup = new SimpleObjectProperty<>(); private final StateManager stateManager; private final DialogService dialogService; + private final ObjectProperty> filterPredicate = new SimpleObjectProperty<>(); + private final StringProperty filterText = new SimpleStringProperty(); private Optional currentDatabase; public GroupTreeViewModel(StateManager stateManager, DialogService dialogService) { this.stateManager = Objects.requireNonNull(stateManager); this.dialogService = Objects.requireNonNull(dialogService); - // Init - onActiveDatabaseChanged(stateManager.activeDatabaseProperty().getValue()); - // Register listener stateManager.activeDatabaseProperty().addListener((observable, oldValue, newValue) -> onActiveDatabaseChanged(newValue)); selectedGroup.addListener((observable, oldValue, newValue) -> onSelectedGroupChanged(newValue)); + + // Set-up bindings + filterPredicate.bind(Bindings.createObjectBinding(() -> group -> group.isMatchedBy(filterText.get()), filterText)); + + // Init + onActiveDatabaseChanged(stateManager.activeDatabaseProperty().getValue()); } public ObjectProperty rootGroupProperty() { @@ -43,6 +52,14 @@ public ObjectProperty selectedGroupProperty() { return selectedGroup; } + public ObjectProperty> filterPredicateProperty() { + return filterPredicate; + } + + public StringProperty filterTextProperty() { + return filterText; + } + /** * Gets invoked if the user selects a different group. * We need to notify the {@link StateManager} about this change so that the main table gets updated. diff --git a/src/main/java/org/jabref/gui/util/RecursiveTreeItem.java b/src/main/java/org/jabref/gui/util/RecursiveTreeItem.java index 2714455e99f..059e5ccb883 100644 --- a/src/main/java/org/jabref/gui/util/RecursiveTreeItem.java +++ b/src/main/java/org/jabref/gui/util/RecursiveTreeItem.java @@ -1,11 +1,17 @@ package org.jabref.gui.util; import java.util.List; +import java.util.function.Predicate; import java.util.stream.Collectors; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.scene.Node; import javafx.scene.control.TreeItem; import javafx.util.Callback; @@ -17,20 +23,29 @@ public class RecursiveTreeItem extends TreeItem { private final Callback expandedProperty; private Callback> childrenFactory; + private ObjectProperty> filter = new SimpleObjectProperty<>(); + private FilteredList children; public RecursiveTreeItem(final T value, Callback> func) { - this(value, func, null); + this(value, func, null, null); } - public RecursiveTreeItem(final T value, Callback> func, Callback expandedProperty) { - this(value, (Node) null, func, expandedProperty); + public RecursiveTreeItem(final T value, Callback> func, Callback expandedProperty, ObservableValue> filter) { + this(value, null, func, expandedProperty, filter); } - public RecursiveTreeItem(final T value, Node graphic, Callback> func, Callback expandedProperty) { + public RecursiveTreeItem(final T value, Callback> func, ObservableValue> filter) { + this(value, null, func, null, filter); + } + + private RecursiveTreeItem(final T value, Node graphic, Callback> func, Callback expandedProperty, ObservableValue> filter) { super(value, graphic); this.childrenFactory = func; this.expandedProperty = expandedProperty; + if (filter != null) { + this.filter.bind(filter); + } if(value != null) { addChildrenListener(value); @@ -40,7 +55,7 @@ public RecursiveTreeItem(final T value, Node graphic, Callback{ if(newValue != null){ addChildrenListener(newValue); - bindExpandedProperty(value, expandedProperty); + bindExpandedProperty(newValue, expandedProperty); } }); } @@ -52,17 +67,14 @@ private void bindExpandedProperty(T value, Callback expanded } private void addChildrenListener(T value){ - final ObservableList children = childrenFactory.call(value); + children = new FilteredList<>(childrenFactory.call(value)); + children.predicateProperty().bind(Bindings.createObjectBinding(() -> this::showNode, filter)); - children.forEach(child -> RecursiveTreeItem.this.getChildren().add(new RecursiveTreeItem<>(child, getGraphic(), childrenFactory, expandedProperty))); + children.forEach(this::addAsChild); children.addListener((ListChangeListener) change -> { while(change.next()){ - if(change.wasAdded()){ - change.getAddedSubList().forEach(t -> RecursiveTreeItem.this.getChildren().add(new RecursiveTreeItem<>(t, getGraphic(), childrenFactory, expandedProperty))); - } - if(change.wasRemoved()){ change.getRemoved().forEach(t->{ final List> itemsToRemove = RecursiveTreeItem.this.getChildren().stream().filter(treeItem -> treeItem.getValue().equals(t)).collect(Collectors.toList()); @@ -71,7 +83,28 @@ private void addChildrenListener(T value){ }); } + if (change.wasAdded()) { + change.getAddedSubList().forEach(this::addAsChild); + } } }); } + + private boolean addAsChild(T child) { + return RecursiveTreeItem.this.getChildren().add(new RecursiveTreeItem<>(child, getGraphic(), childrenFactory, expandedProperty, filter)); + } + + private boolean showNode(T t) { + if (filter.get() == null) { + return true; + } + + if (filter.get().test(t)) { + // Node is directly matched -> so show it + return true; + } + + // Are there children (or children of children...) that are matched? If yes we also need to show this node + return childrenFactory.call(t).stream().anyMatch(this::showNode); + } } diff --git a/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java index 07e626f5144..2b71b6bc3f1 100644 --- a/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java +++ b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java @@ -12,6 +12,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -19,12 +20,17 @@ public class GroupNodeViewModelTest { private StateManager stateManager; private BibDatabaseContext databaseContext; + private GroupNodeViewModel viewModel; @Before public void setUp() throws Exception { stateManager = mock(StateManager.class); when(stateManager.getSelectedEntries()).thenReturn(FXCollections.emptyObservableList()); databaseContext = new BibDatabaseContext(); + + viewModel = getViewModelForGroup( + new WordKeywordGroup("Test group", GroupHierarchyType.INDEPENDENT, "test", "search", true, ',', false)); + } @Test @@ -34,6 +40,16 @@ public void getDisplayNameConvertsLatexToUnicode() throws Exception { assertEquals("β", viewModel.getDisplayName()); } + @Test + public void alwaysMatchedByEmptySearchString() throws Exception { + assertTrue(viewModel.isMatchedBy("")); + } + + @Test + public void isMatchedIfContainsPartOfSearchString() throws Exception { + assertTrue(viewModel.isMatchedBy("est")); + } + private GroupNodeViewModel getViewModelForGroup(AbstractGroup group) { return new GroupNodeViewModel(databaseContext, stateManager, group); } diff --git a/src/test/java/org/jabref/gui/util/RecursiveTreeItemTest.java b/src/test/java/org/jabref/gui/util/RecursiveTreeItemTest.java new file mode 100644 index 00000000000..6f06b4012db --- /dev/null +++ b/src/test/java/org/jabref/gui/util/RecursiveTreeItemTest.java @@ -0,0 +1,58 @@ +package org.jabref.gui.util; + +import java.util.Collections; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.TreeItem; + +import org.jabref.model.TreeNode; +import org.jabref.model.TreeNodeTestData; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class RecursiveTreeItemTest { + + private RecursiveTreeItem rootTreeItem; + private TreeNodeTestData.TreeNodeMock root; + private ObjectProperty> filterPredicate; + private TreeNodeTestData.TreeNodeMock node; + + @Before + public void setUp() throws Exception { + root = new TreeNodeTestData.TreeNodeMock(); + node = TreeNodeTestData.getNodeInSimpleTree(root); + node.setName("test node"); + + filterPredicate = new SimpleObjectProperty<>(); + + rootTreeItem = new RecursiveTreeItem<>(root, TreeNode::getChildren, filterPredicate); + } + + @Test + public void addsAllChildrenNodes() throws Exception { + assertEquals(root.getChildren(), rootTreeItem.getChildren().stream().map(TreeItem::getValue).collect(Collectors.toList())); + } + + @Test + public void addsAllChildrenOfChildNode() throws Exception { + assertEquals( + root.getChildAt(1).get().getChildren(), + rootTreeItem.getChildren().get(1).getChildren().stream().map(TreeItem::getValue).collect(Collectors.toList())); + } + + @Test + public void respectsFilter() throws Exception { + filterPredicate.setValue(item -> item.getName().contains("test")); + + assertEquals(Collections.singletonList(node.getParent().get()), rootTreeItem.getChildren().stream().map(TreeItem::getValue).collect(Collectors.toList())); + assertEquals( + Collections.singletonList(node), + rootTreeItem.getChildren().get(0).getChildren().stream().map(TreeItem::getValue).collect(Collectors.toList())); + } +} diff --git a/src/test/java/org/jabref/model/TreeNodeTest.java b/src/test/java/org/jabref/model/TreeNodeTest.java index fb7a336a18f..841dfaeab29 100644 --- a/src/test/java/org/jabref/model/TreeNodeTest.java +++ b/src/test/java/org/jabref/model/TreeNodeTest.java @@ -21,88 +21,7 @@ public class TreeNodeTest { @Mock - Consumer subscriber; - - /** - * Gets the marked node in the following tree: - * Root - * A - * A (= parent) - * B (<-- this) - */ - private TreeNodeMock getNodeInSimpleTree(TreeNodeMock root) { - root.addChild(new TreeNodeMock()); - TreeNodeMock parent = new TreeNodeMock(); - root.addChild(parent); - TreeNodeMock node = new TreeNodeMock(); - parent.addChild(node); - return node; - } - - private TreeNodeMock getNodeInSimpleTree() { - return getNodeInSimpleTree(new TreeNodeMock()); - } - - /** - * Gets the marked node in the following tree: - * Root - * A - * A - * A (= grand parent) - * B - * B (= parent) - * C (<-- this) - * D (= child) - * C - * C - * C - * B - * B - * A - */ - private TreeNodeMock getNodeInComplexTree(TreeNodeMock root) { - root.addChild(new TreeNodeMock()); - root.addChild(new TreeNodeMock()); - TreeNodeMock grandParent = new TreeNodeMock(); - root.addChild(grandParent); - root.addChild(new TreeNodeMock()); - - grandParent.addChild(new TreeNodeMock()); - TreeNodeMock parent = new TreeNodeMock(); - grandParent.addChild(parent); - grandParent.addChild(new TreeNodeMock()); - grandParent.addChild(new TreeNodeMock()); - - TreeNodeMock node = new TreeNodeMock(); - parent.addChild(node); - parent.addChild(new TreeNodeMock()); - parent.addChild(new TreeNodeMock()); - parent.addChild(new TreeNodeMock()); - - node.addChild(new TreeNodeMock()); - return node; - } - - private TreeNodeMock getNodeInComplexTree() { - return getNodeInComplexTree(new TreeNodeMock()); - } - - /** - * Gets the marked in the following tree: - * Root - * A - * A - * A (<- this) - * A - */ - private TreeNodeMock getNodeAsChild(TreeNodeMock root) { - root.addChild(new TreeNodeMock()); - root.addChild(new TreeNodeMock()); - TreeNodeMock node = new TreeNodeMock(); - root.addChild(node); - root.addChild(new TreeNodeMock()); - return node; - } + Consumer subscriber; @Test(expected = UnsupportedOperationException.class) public void constructorChecksThatClassImplementsCorrectInterface() { @@ -111,13 +30,13 @@ public void constructorChecksThatClassImplementsCorrectInterface() { @Test public void constructorExceptsCorrectImplementation() { - TreeNodeMock treeNode = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock treeNode = new TreeNodeTestData.TreeNodeMock(); assertNotNull(treeNode); } @Test public void newTreeNodeHasNoParentOrChildren() { - TreeNodeMock treeNode = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock treeNode = new TreeNodeTestData.TreeNodeMock(); assertEquals(Optional.empty(), treeNode.getParent()); assertEquals(Collections.emptyList(), treeNode.getChildren()); assertNotNull(treeNode); @@ -125,126 +44,126 @@ public void newTreeNodeHasNoParentOrChildren() { @Test public void getIndexedPathFromRootReturnsEmptyListForRoot() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(Collections.emptyList(), root.getIndexedPathFromRoot()); } @Test public void getIndexedPathFromRootSimplePath() { - assertEquals(Arrays.asList(1, 0), getNodeInSimpleTree().getIndexedPathFromRoot()); + assertEquals(Arrays.asList(1, 0), TreeNodeTestData.getNodeInSimpleTree().getIndexedPathFromRoot()); } @Test public void getIndexedPathFromRootComplexPath() { - assertEquals(Arrays.asList(2, 1, 0), getNodeInComplexTree().getIndexedPathFromRoot()); + assertEquals(Arrays.asList(2, 1, 0), TreeNodeTestData.getNodeInComplexTree().getIndexedPathFromRoot()); } @Test public void getDescendantSimplePath() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); assertEquals(node, root.getDescendant(Arrays.asList(1, 0)).get()); } @Test public void getDescendantComplexPath() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); assertEquals(node, root.getDescendant(Arrays.asList(2, 1, 0)).get()); } @Test public void getDescendantNonExistentReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.getNodeInComplexTree(root); assertEquals(Optional.empty(), root.getDescendant(Arrays.asList(1, 100, 0))); } @Test(expected = UnsupportedOperationException.class) public void getPositionInParentForRootThrowsException() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); root.getPositionInParent(); } @Test public void getPositionInParentSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); assertEquals(2, node.getPositionInParent()); } @Test public void getIndexOfNonExistentChildReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - assertEquals(Optional.empty(), root.getIndexOfChild(new TreeNodeMock())); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + assertEquals(Optional.empty(), root.getIndexOfChild(new TreeNodeTestData.TreeNodeMock())); } @Test public void getIndexOfChild() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); assertEquals((Integer)2, root.getIndexOfChild(node).get()); } @Test public void getLevelOfRoot() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(0, root.getLevel()); } @Test public void getLevelInSimpleTree() { - assertEquals(2, getNodeInSimpleTree().getLevel()); + assertEquals(2, TreeNodeTestData.getNodeInSimpleTree().getLevel()); } @Test public void getLevelInComplexTree() { - assertEquals(3, getNodeInComplexTree().getLevel()); + assertEquals(3, TreeNodeTestData.getNodeInComplexTree().getLevel()); } @Test public void getChildCountInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.getNodeInSimpleTree(root); assertEquals(2, root.getNumberOfChildren()); } @Test public void getChildCountInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.getNodeInComplexTree(root); assertEquals(4, root.getNumberOfChildren()); } @Test public void moveToAddsAsLastChildInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); node.moveTo(root); assertEquals((Integer)2, root.getIndexOfChild(node).get()); } @Test public void moveToAddsAsLastChildInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); node.moveTo(root); assertEquals((Integer)4, root.getIndexOfChild(node).get()); } @Test public void moveToChangesParent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); node.moveTo(root); assertEquals(root, node.getParent().get()); } @Test public void moveToInSameLevelAddsAtEnd() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock child1 = new TreeNodeMock(); - TreeNodeMock child2 = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock(); root.addChild(child1); root.addChild(child2); @@ -255,10 +174,10 @@ public void moveToInSameLevelAddsAtEnd() { @Test public void getPathFromRootInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); - List path = node.getPathFromRoot(); + List path = node.getPathFromRoot(); assertEquals(3, path.size()); assertEquals(root, path.get(0)); assertEquals(node, path.get(2)); @@ -266,10 +185,10 @@ public void getPathFromRootInSimpleTree() { @Test public void getPathFromRootInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); - List path = node.getPathFromRoot(); + List path = node.getPathFromRoot(); assertEquals(4, path.size()); assertEquals(root, path.get(0)); assertEquals(node, path.get(3)); @@ -277,149 +196,149 @@ public void getPathFromRootInComplexTree() { @Test public void getPreviousSiblingReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - root.addChild(new TreeNodeMock()); - TreeNodeMock previous = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + root.addChild(new TreeNodeTestData.TreeNodeMock()); + TreeNodeTestData.TreeNodeMock previous = new TreeNodeTestData.TreeNodeMock(); root.addChild(previous); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); - root.addChild(new TreeNodeMock()); + root.addChild(new TreeNodeTestData.TreeNodeMock()); assertEquals(previous, node.getPreviousSibling().get()); } @Test public void getPreviousSiblingForRootReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(Optional.empty(), root.getPreviousSibling()); } @Test public void getPreviousSiblingForNonexistentReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); assertEquals(Optional.empty(), node.getPreviousSibling()); } @Test public void getNextSiblingReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - root.addChild(new TreeNodeMock()); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + root.addChild(new TreeNodeTestData.TreeNodeMock()); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); - TreeNodeMock next = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock next = new TreeNodeTestData.TreeNodeMock(); root.addChild(next); - root.addChild(new TreeNodeMock()); + root.addChild(new TreeNodeTestData.TreeNodeMock()); assertEquals(next, node.getNextSibling().get()); } @Test public void getNextSiblingForRootReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(Optional.empty(), root.getNextSibling()); } @Test public void getNextSiblingForNonexistentReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); assertEquals(Optional.empty(), node.getPreviousSibling()); } @Test public void getParentReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); assertEquals(root, node.getParent().get()); } @Test public void getParentForRootReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(Optional.empty(), root.getParent()); } @Test public void getChildAtReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); assertEquals(node, root.getChildAt(2).get()); } @Test public void getChildAtInvalidIndexReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - root.addChild(new TreeNodeMock()); - root.addChild(new TreeNodeMock()); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + root.addChild(new TreeNodeTestData.TreeNodeMock()); + root.addChild(new TreeNodeTestData.TreeNodeMock()); assertEquals(Optional.empty(), root.getChildAt(10)); } @Test public void getRootReturnsTrueForRoot() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertTrue(root.isRoot()); } @Test public void getRootReturnsFalseForChild() { - assertFalse(getNodeInSimpleTree().isRoot()); + assertFalse(TreeNodeTestData.getNodeInSimpleTree().isRoot()); } @Test public void nodeIsAncestorOfItself() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertTrue(root.isAncestorOf(root)); } @Test public void isAncestorOfInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); assertTrue(root.isAncestorOf(node)); } @Test public void isAncestorOfInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); assertTrue(root.isAncestorOf(node)); } @Test public void getRootOfSingleNode() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertEquals(root, root.getRoot()); } @Test public void getRootInSimpleTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); assertEquals(root, node.getRoot()); } @Test public void getRootInComplexTree() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); assertEquals(root, node.getRoot()); } @Test public void isLeafIsCorrectForRootWithoutChildren() { - TreeNodeMock root = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); assertTrue(root.isLeaf()); } @Test public void removeFromParentSetsParentToEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); node.removeFromParent(); assertEquals(Optional.empty(), node.getParent()); @@ -427,8 +346,8 @@ public void removeFromParentSetsParentToEmpty() { @Test public void removeFromParentRemovesNodeFromChildrenCollection() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); node.removeFromParent(); assertFalse(root.getChildren().contains(node)); @@ -436,8 +355,8 @@ public void removeFromParentRemovesNodeFromChildrenCollection() { @Test public void removeAllChildrenSetsParentOfChildToEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeAllChildren(); assertEquals(Optional.empty(), node.getParent()); @@ -445,8 +364,8 @@ public void removeAllChildrenSetsParentOfChildToEmpty() { @Test public void removeAllChildrenRemovesAllNodesFromChildrenCollection() { - TreeNodeMock root = new TreeNodeMock(); - getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.getNodeAsChild(root); root.removeAllChildren(); assertEquals(Collections.emptyList(), root.getChildren()); @@ -454,8 +373,8 @@ public void removeAllChildrenRemovesAllNodesFromChildrenCollection() { @Test public void getFirstChildAtReturnsCorrect() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); assertEquals(node, root.getFirstChild().get()); @@ -463,30 +382,30 @@ public void getFirstChildAtReturnsCorrect() { @Test public void getFirstChildAtLeafReturnsEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock leaf = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock leaf = TreeNodeTestData.getNodeAsChild(root); assertEquals(Optional.empty(), leaf.getFirstChild()); } @Test public void isNodeDescendantInFirstLevel() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock child = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child = TreeNodeTestData.getNodeAsChild(root); assertTrue(root.isNodeDescendant(child)); } @Test public void isNodeDescendantInComplex() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock descendant = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock descendant = TreeNodeTestData.getNodeInComplexTree(root); assertTrue(root.isNodeDescendant(descendant)); } @Test public void getChildrenReturnsAllChildren() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock child1 = new TreeNodeMock(); - TreeNodeMock child2 = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock(); root.addChild(child1); root.addChild(child2); @@ -495,8 +414,8 @@ public void getChildrenReturnsAllChildren() { @Test public void removeChildSetsParentToEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeChild(node); assertEquals(Optional.empty(), node.getParent()); @@ -504,8 +423,8 @@ public void removeChildSetsParentToEmpty() { @Test public void removeChildRemovesNodeFromChildrenCollection() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeChild(node); assertFalse(root.getChildren().contains(node)); @@ -513,8 +432,8 @@ public void removeChildRemovesNodeFromChildrenCollection() { @Test public void removeChildIndexSetsParentToEmpty() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeChild(2); assertEquals(Optional.empty(), node.getParent()); @@ -522,8 +441,8 @@ public void removeChildIndexSetsParentToEmpty() { @Test public void removeChildIndexRemovesNodeFromChildrenCollection() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.removeChild(2); assertFalse(root.getChildren().contains(node)); @@ -531,18 +450,18 @@ public void removeChildIndexRemovesNodeFromChildrenCollection() { @Test(expected = UnsupportedOperationException.class) public void addThrowsExceptionIfNodeHasParent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.addChild(node); } @Test public void moveAllChildrenToAddsAtSpecifiedPosition() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); - TreeNodeMock child1 = new TreeNodeMock(); - TreeNodeMock child2 = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock(); node.addChild(child1); node.addChild(child2); @@ -552,11 +471,11 @@ public void moveAllChildrenToAddsAtSpecifiedPosition() { @Test public void moveAllChildrenToChangesParent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = new TreeNodeTestData.TreeNodeMock(); root.addChild(node); - TreeNodeMock child1 = new TreeNodeMock(); - TreeNodeMock child2 = new TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock(); node.addChild(child1); node.addChild(child2); @@ -567,18 +486,18 @@ public void moveAllChildrenToChangesParent() { @Test(expected = UnsupportedOperationException.class) public void moveAllChildrenToDescendantThrowsException() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); root.moveAllChildrenTo(node, 0); } @Test public void sortChildrenSortsInFirstLevel() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock child1 = new TreeNodeMock("a"); - TreeNodeMock child2 = new TreeNodeMock("b"); - TreeNodeMock child3 = new TreeNodeMock("c"); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock("a"); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock("b"); + TreeNodeTestData.TreeNodeMock child3 = new TreeNodeTestData.TreeNodeMock("c"); root.addChild(child2); root.addChild(child3); root.addChild(child1); @@ -589,11 +508,11 @@ public void sortChildrenSortsInFirstLevel() { @Test public void sortChildrenRecursiveSortsInDeeperLevel() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInSimpleTree(root); - TreeNodeMock child1 = new TreeNodeMock("a"); - TreeNodeMock child2 = new TreeNodeMock("b"); - TreeNodeMock child3 = new TreeNodeMock("c"); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInSimpleTree(root); + TreeNodeTestData.TreeNodeMock child1 = new TreeNodeTestData.TreeNodeMock("a"); + TreeNodeTestData.TreeNodeMock child2 = new TreeNodeTestData.TreeNodeMock("b"); + TreeNodeTestData.TreeNodeMock child3 = new TreeNodeTestData.TreeNodeMock("c"); node.addChild(child2); node.addChild(child3); node.addChild(child1); @@ -604,10 +523,10 @@ public void sortChildrenRecursiveSortsInDeeperLevel() { @Test public void copySubtreeCopiesChildren() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeAsChild(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeAsChild(root); - TreeNodeMock copiedRoot = root.copySubtree(); + TreeNodeTestData.TreeNodeMock copiedRoot = root.copySubtree(); assertEquals(Optional.empty(), copiedRoot.getParent()); assertFalse(copiedRoot.getChildren().contains(node)); assertEquals(root.getNumberOfChildren(), copiedRoot.getNumberOfChildren()); @@ -615,20 +534,20 @@ public void copySubtreeCopiesChildren() { @Test public void addChildSomewhereInTreeInvokesChangeEvent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); root.subscribeToDescendantChanged(subscriber); - node.addChild(new TreeNodeMock()); + node.addChild(new TreeNodeTestData.TreeNodeMock()); verify(subscriber).accept(node); } @Test public void moveNodeSomewhereInTreeInvokesChangeEvent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); - TreeNodeMock oldParent = node.getParent().get(); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock oldParent = node.getParent().get(); root.subscribeToDescendantChanged(subscriber); @@ -639,9 +558,9 @@ public void moveNodeSomewhereInTreeInvokesChangeEvent() { @Test public void removeChildSomewhereInTreeInvokesChangeEvent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); - TreeNodeMock child = node.addChild(new TreeNodeMock()); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); + TreeNodeTestData.TreeNodeMock child = node.addChild(new TreeNodeTestData.TreeNodeMock()); root.subscribeToDescendantChanged(subscriber); @@ -651,9 +570,9 @@ public void removeChildSomewhereInTreeInvokesChangeEvent() { @Test public void removeChildIndexSomewhereInTreeInvokesChangeEvent() { - TreeNodeMock root = new TreeNodeMock(); - TreeNodeMock node = getNodeInComplexTree(root); - node.addChild(new TreeNodeMock()); + TreeNodeTestData.TreeNodeMock root = new TreeNodeTestData.TreeNodeMock(); + TreeNodeTestData.TreeNodeMock node = TreeNodeTestData.getNodeInComplexTree(root); + node.addChild(new TreeNodeTestData.TreeNodeMock()); root.subscribeToDescendantChanged(subscriber); @@ -661,49 +580,16 @@ public void removeChildIndexSomewhereInTreeInvokesChangeEvent() { verify(subscriber).accept(node); } - /** - * This is just a dummy class deriving from TreeNode so that we can test the generic class - */ - private static class TreeNodeMock extends TreeNode { - - private final String name; - - public TreeNodeMock() { - this(""); - } - - public TreeNodeMock(String name) { - super(TreeNodeMock.class); - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return "TreeNodeMock{" + - "name='" + name + '\'' + - '}'; - } - - @Override - public TreeNodeMock copyNode() { - return new TreeNodeMock(name); - } - } - - private static class WrongTreeNodeImplementation extends TreeNode { + private static class WrongTreeNodeImplementation extends TreeNode { // This class is a wrong derived class of TreeNode // since it does not extends TreeNode // See test constructorChecksThatClassImplementsCorrectInterface public WrongTreeNodeImplementation() { - super(TreeNodeMock.class); + super(TreeNodeTestData.TreeNodeMock.class); } @Override - public TreeNodeMock copyNode() { + public TreeNodeTestData.TreeNodeMock copyNode() { return null; } } diff --git a/src/test/java/org/jabref/model/TreeNodeTestData.java b/src/test/java/org/jabref/model/TreeNodeTestData.java new file mode 100644 index 00000000000..939e9629292 --- /dev/null +++ b/src/test/java/org/jabref/model/TreeNodeTestData.java @@ -0,0 +1,121 @@ +package org.jabref.model; + +public class TreeNodeTestData { + /** + * Gets the marked node in the following tree: + * Root + * A + * A (= parent) + * B (<-- this) + */ + public static TreeNodeMock getNodeInSimpleTree(TreeNodeMock root) { + root.addChild(new TreeNodeMock()); + TreeNodeMock parent = new TreeNodeMock(); + root.addChild(parent); + TreeNodeMock node = new TreeNodeMock(); + parent.addChild(node); + return node; + } + + public static TreeNodeMock getNodeInSimpleTree() { + return getNodeInSimpleTree(new TreeNodeMock()); + } + + /** + * Gets the marked node in the following tree: + * Root + * A + * A + * A (= grand parent) + * B + * B (= parent) + * C (<-- this) + * D (= child) + * C + * C + * C + * B + * B + * A + */ + public static TreeNodeMock getNodeInComplexTree(TreeNodeMock root) { + root.addChild(new TreeNodeMock()); + root.addChild(new TreeNodeMock()); + TreeNodeMock grandParent = new TreeNodeMock(); + root.addChild(grandParent); + root.addChild(new TreeNodeMock()); + + grandParent.addChild(new TreeNodeMock()); + TreeNodeMock parent = new TreeNodeMock(); + grandParent.addChild(parent); + grandParent.addChild(new TreeNodeMock()); + grandParent.addChild(new TreeNodeMock()); + + TreeNodeMock node = new TreeNodeMock(); + parent.addChild(node); + parent.addChild(new TreeNodeMock()); + parent.addChild(new TreeNodeMock()); + parent.addChild(new TreeNodeMock()); + + node.addChild(new TreeNodeMock()); + return node; + } + + public static TreeNodeMock getNodeInComplexTree() { + return getNodeInComplexTree(new TreeNodeMock()); + } + + /** + * Gets the marked in the following tree: + * Root + * A + * A + * A (<- this) + * A + */ + public static TreeNodeMock getNodeAsChild(TreeNodeMock root) { + root.addChild(new TreeNodeMock()); + root.addChild(new TreeNodeMock()); + TreeNodeMock node = new TreeNodeMock(); + root.addChild(node); + root.addChild(new TreeNodeMock()); + return node; + } + + /** + * This is just a dummy class deriving from TreeNode so that we can test the generic class + */ + public static class TreeNodeMock extends TreeNode { + + private String name; + + public TreeNodeMock() { + this(""); + } + + public TreeNodeMock(String name) { + super(TreeNodeMock.class); + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "TreeNodeMock{" + + "name='" + name + '\'' + + '}'; + } + + @Override + public TreeNodeMock copyNode() { + return new TreeNodeMock(name); + } + } +}