diff --git a/.github/workflows/cleanup_pr.yml b/.github/workflows/cleanup_pr.yml index 54cc1310f25..63090aba74a 100644 --- a/.github/workflows/cleanup_pr.yml +++ b/.github/workflows/cleanup_pr.yml @@ -27,7 +27,7 @@ jobs: echo "##[set-output name=branch;]$(echo ${{ github.event.pull_request.head.ref }})" - name: Delete folder on builds.jabref.org if: ${{ steps.checksecrets.outputs.secretspresent }} - uses: appleboy/ssh-action@v0.1.3 + uses: appleboy/ssh-action@v0.1.4 with: script: rm -rf /var/www/builds.jabref.org/www/${{ steps.extract_branch.outputs.branch }} || true host: build-upload.jabref.org diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 9712508c1ee..62065768782 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -49,12 +49,12 @@ jobs: with: fetch-depth: 0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.6 + uses: gittools/actions/gitversion/setup@v0.9.7 with: versionSpec: "5.x" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.6 + uses: gittools/actions/gitversion/execute@v0.9.7 - name: Set up JDK 15 for linux and mac uses: actions/setup-java@v1 with: @@ -161,12 +161,12 @@ jobs: - name: Fetch all history for all tags and branches run: git fetch --prune --unshallow - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.9.6 + uses: gittools/actions/gitversion/setup@v0.9.7 with: versionSpec: '5.x' - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.9.6 + uses: gittools/actions/gitversion/execute@v0.9.7 - name: Get linux binaries uses: actions/download-artifact@master with: diff --git a/AUTHORS b/AUTHORS index 18a72d4d5de..0802288e203 100644 --- a/AUTHORS +++ b/AUTHORS @@ -237,6 +237,7 @@ Luis Romero Mairieli Wessel Malik Atalla Malte Deiseroth +Manas Singh Manuel Siebeneicher Manuel Wtfjoke Marcel Luethi diff --git a/CHANGELOG.md b/CHANGELOG.md index 355e11dfa0c..df9506fbcd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ to the page field for cases where the page numbers are missing. [#7019](https:// - We added a new formatter to output shorthand month format. [#6579](https://github.com/JabRef/jabref/issues/6579) - We added support for the new Microsoft Edge browser in all platforms. [#7056](https://github.com/JabRef/jabref/pull/7056) - We reintroduced emacs/bash-like keybindings. [#6017](https://github.com/JabRef/jabref/issues/6017) +- We added a feature to provide automated cross library search using a cross library query language. This provides support for the search step of systematic literature reviews (SLRs). [koppor#369](https://github.com/koppor/jabref/issues/369) ### Changed @@ -73,6 +74,9 @@ inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://git - We fixed an issue where errors from imports were not shown to the user [#7084](https://github.com/JabRef/jabref/pull/7084) - We fixed an issue where the EndNote XML Import would fail on empty keywords tags [forum#2387](https://discourse.jabref.org/t/importing-in-unknown-format-fails-to-import-xml-library-from-bookends-export/2387) - We fixed an issue where the color of groups of type "free search expression" not persisting after restarting the application [#6999](https://github.com/JabRef/jabref/issues/6999) +- We fixed an issue where modifications in the source tab where not saved without switching to another field before saving the library [#6622](https://github.com/JabRef/jabref/issues/6622) +- We fixed an issue where the "Document Viewer" did not show the first page of the opened pdf document and did not show the correct total number of pages [#7108](https://github.com/JabRef/jabref/issues/7108) +- We fixed an issue where the password for a shared SQL database was not remembered [#6869](https://github.com/JabRef/jabref/issues/6869) ### Removed diff --git a/DEVELOPERS b/DEVELOPERS index 8f6e0bf2623..d8cb8727de1 100644 --- a/DEVELOPERS +++ b/DEVELOPERS @@ -5,3 +5,4 @@ Tobias Diez (since 2015) Christoph Schwentker (since 2016) Linus Dietz (since 2017) Carl Christian Snethlage (since 2020) +Dominik Voigt (since 2020) diff --git a/README.md b/README.md index d81ab2b909e..388b25ccdd4 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ We will discuss improvements with you and agree to merge them once the [develope If you want a step-by-step walk-through on how to set-up your workspace, please check [this guideline](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace). -To compile JabRef from source, you need a Java Development Kit 14 and `JAVA_HOME` pointing to this JDK. +To compile JabRef from source, you need a Java Development Kit 15 and `JAVA_HOME` pointing to this JDK. To run it, just execute `gradlew run`. When you want to develop, it is necessary to generate additional sources using `gradlew generateSource` and then generate the Eclipse `gradlew eclipse`. diff --git a/build.gradle b/build.gradle index 6e04f5b0b22..d9fc99d0188 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,6 @@ repositories { } configurations { - libreoffice antlr3 antlr4 // TODO: Remove the following workaround for split error messages such as @@ -117,14 +116,8 @@ dependencies { implementation 'commons-cli:commons-cli:1.4' - // For Java 9+ compatibility, we include a bundled version of the libreoffice libraries - // See https://bugs.documentfoundation.org/show_bug.cgi?id=117331#c8 for background information - // Use the task bundleLibreOffice to update the bundled jar - // DO NOT CHANGE THE libreoffice PREFIX - libreoffice 'org.libreoffice:juh:6.4.3' - libreoffice 'org.libreoffice:jurt:6.4.3' - libreoffice 'org.libreoffice:ridl:6.4.3' - libreoffice 'org.libreoffice:unoil:6.4.3' + implementation 'org.libreoffice:libreoffice:7.0.3' + implementation 'org.libreoffice:unoloader:7.0.3' implementation 'io.github.java-diff-utils:java-diff-utils:4.9' implementation 'info.debatty:java-string-similarity:2.0.0' @@ -132,14 +125,16 @@ dependencies { antlr3 'org.antlr:antlr:3.5.2' implementation 'org.antlr:antlr-runtime:3.5.2' - antlr4 'org.antlr:antlr4:4.8-1' - implementation 'org.antlr:antlr4-runtime:4.8-1' + antlr4 'org.antlr:antlr4:4.9' + implementation 'org.antlr:antlr4-runtime:4.9' implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.7.0') { exclude group: 'org.apache.lucene', module: 'lucene-sandbox' } - implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.0' + implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '5.9.0.202009080501-r' + + implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.1' implementation 'org.postgresql:postgresql:42.2.18' @@ -197,7 +192,7 @@ dependencies { implementation 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.62.2' implementation 'com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.62.2' - testImplementation 'io.github.classgraph:classgraph:4.8.90' + testImplementation 'io.github.classgraph:classgraph:4.8.92' testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.7.0' testImplementation 'org.junit.platform:junit-platform-launcher:1.7.0' @@ -205,7 +200,7 @@ dependencies { testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.18' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' - testImplementation 'org.mockito:mockito-core:3.6.0' + testImplementation 'org.mockito:mockito-core:3.6.28' testImplementation 'org.xmlunit:xmlunit-core:2.8.1' testImplementation 'org.xmlunit:xmlunit-matchers:2.8.1' testRuntime 'com.tngtech.archunit:archunit-junit5-engine:0.14.1' @@ -214,7 +209,7 @@ dependencies { testImplementation "org.testfx:testfx-junit5:4.0.17-alpha-SNAPSHOT" testImplementation "org.hamcrest:hamcrest-library:2.2" - checkstyle 'com.puppycrawl.tools:checkstyle:8.37' + checkstyle 'com.puppycrawl.tools:checkstyle:8.38' xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '2.3.3' jython 'org.python:jython-standalone:2.7.2' } @@ -765,13 +760,4 @@ task downloadDependencies { } } -task bundleLibreOffice(type: Jar) { - from configurations.libreoffice.collect { zipTree it } - - manifest { - attributes 'Automatic-Module-Name': 'org.jabref.thirdparty.libreoffice' - } - destinationDir = file('lib') - archiveName = 'libreoffice.jar' -} diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md index 4d90e5b9c0c..38f335aff02 100644 --- a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace.md @@ -55,7 +55,7 @@ It is strongly recommend that you have git installed. ### IDE -We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/). +We suggest [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=jabref). For advanced users, [Eclipse](https://eclipse.org/) \(`2020-09` or newer\) is also possible. #### IntelliJ diff --git a/lib/libreoffice.jar b/lib/libreoffice.jar deleted file mode 100644 index 072f96ec432..00000000000 Binary files a/lib/libreoffice.jar and /dev/null differ diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index eb7e0102e92..64504a259fe 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -47,7 +47,7 @@ requires applicationinsights.core; // Libre Office - requires org.jabref.thirdparty.libreoffice; + requires org.libreoffice.uno; // Other modules requires commons.logging; @@ -91,4 +91,5 @@ requires com.h2database.mvstore; requires lucene.queryparser; requires lucene.core; + requires org.eclipse.jgit; } diff --git a/src/main/java/org/jabref/gui/Dark.css b/src/main/java/org/jabref/gui/Dark.css index e0b19e2adc5..0fd1c188c7f 100644 --- a/src/main/java/org/jabref/gui/Dark.css +++ b/src/main/java/org/jabref/gui/Dark.css @@ -90,3 +90,13 @@ #preferencesContainer .tab-pane > .tab-header-area > .tab-header-background { -fx-background-color: -jr-background; } + +.mainToolbar .search-field .toggle-button .glyph-icon { + -fx-fill: -jr-search-text; + -fx-text-fill: -jr-search-text; +} + +.mainToolbar .search-field .toggle-button:selected .glyph-icon { + -fx-fill: derive(-jr-search-text, 80%); + -fx-text-fill: derive(-jr-search-text, 80%); +} diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index d7aac3f6a4c..7579a5e793a 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -814,7 +814,9 @@ private MenuBar createMenu() { new SeparatorMenuItem(), factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsEMailAction(dialogService, stateManager)), - pushToApplicationMenuItem + pushToApplicationMenuItem, + new SeparatorMenuItem(), + factory.createMenuItem(StandardActions.START_SYSTEMATIC_LITERATURE_REVIEW, new StartLiteratureReviewAction(this, Globals.getFileUpdateMonitor(), Globals.prefs.getWorkingDir(), Globals.TASK_EXECUTOR)) ); SidePaneComponent webSearch = sidePaneManager.getComponent(SidePaneType.WEB_SEARCH); @@ -991,7 +993,7 @@ public void addParserResult(ParserResult parserResult, boolean focusPanel) { * This method causes all open LibraryTabs to set up their tables anew. When called from PreferencesDialogViewModel, * this updates to the new settings. * We need to notify all tabs about the changes to avoid problems when changing the column set. - * */ + */ public void setupAllTables() { tabbedPane.getTabs().forEach(tab -> { LibraryTab libraryTab = (LibraryTab) tab; @@ -1012,7 +1014,7 @@ private ContextMenu createTabContextMenu(KeyBindingRepository keyBindingReposito new SeparatorMenuItem(), factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, new OpenDatabaseFolder()), factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(stateManager)) - ); + ); return contextMenu; } diff --git a/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java b/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java new file mode 100644 index 00000000000..c67328a4e19 --- /dev/null +++ b/src/main/java/org/jabref/gui/StartLiteratureReviewAction.java @@ -0,0 +1,82 @@ +package org.jabref.gui; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.FileDialogConfiguration; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.crawler.Crawler; +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.JabRefPreferences; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StartLiteratureReviewAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(StartLiteratureReviewAction.class); + private final JabRefFrame frame; + private final DialogService dialogService; + private final FileUpdateMonitor fileUpdateMonitor; + private final Path workingDirectory; + private final TaskExecutor taskExecutor; + + public StartLiteratureReviewAction(JabRefFrame frame, FileUpdateMonitor fileUpdateMonitor, Path standardWorkingDirectory, TaskExecutor taskExecutor) { + this.frame = frame; + this.dialogService = frame.getDialogService(); + this.fileUpdateMonitor = fileUpdateMonitor; + this.workingDirectory = getInitialDirectory(standardWorkingDirectory); + this.taskExecutor = taskExecutor; + } + + @Override + public void execute() { + FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() + .withInitialDirectory(workingDirectory) + .build(); + + Optional studyDefinitionFile = dialogService.showFileOpenDialog(fileDialogConfiguration); + if (studyDefinitionFile.isEmpty()) { + // Do nothing if selection was canceled + return; + } + final Crawler crawler; + try { + crawler = new Crawler(studyDefinitionFile.get(), new GitHandler(studyDefinitionFile.get().getParent()), fileUpdateMonitor, JabRefPreferences.getInstance().getImportFormatPreferences(), JabRefPreferences.getInstance().getSavePreferences(), new BibEntryTypesManager()); + } catch (IOException | ParseException | GitAPIException e) { + LOGGER.error("Error during reading of study definition file.", e); + dialogService.showErrorDialogAndWait(Localization.lang("Error during reading of study definition file."), e); + return; + } + BackgroundTask.wrap(() -> { + crawler.performCrawl(); + return 0; // Return any value to make this a callable instead of a runnable. This allows throwing exceptions. + }) + .onFailure(e -> { + LOGGER.error("Error during persistence of crawling results."); + dialogService.showErrorDialogAndWait(Localization.lang("Error during persistence of crawling results."), e); + }) + .onSuccess(unused -> new OpenDatabaseAction(frame).openFile(Path.of(studyDefinitionFile.get().getParent().toString(), "studyResult.bib"), true)) + .executeWith(taskExecutor); + } + + /** + * @return Path of current panel database directory or the standard working directory + */ + private Path getInitialDirectory(Path standardWorkingDirectory) { + if (frame.getBasePanelCount() == 0) { + return standardWorkingDirectory; + } else { + Optional databasePath = frame.getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath(); + return databasePath.map(Path::getParent).orElse(standardWorkingDirectory); + } + } +} diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 2b316dca80a..0767d4021bb 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -88,6 +88,7 @@ public enum StandardActions implements Action { PARSE_LATEX(Localization.lang("Search for citations in LaTeX files..."), IconTheme.JabRefIcons.LATEX_CITATIONS), NEW_SUB_LIBRARY_FROM_AUX(Localization.lang("New sublibrary based on AUX file") + "...", Localization.lang("New BibTeX sublibrary") + Localization.lang("This feature generates a new library based on which entries are needed in an existing LaTeX document."), IconTheme.JabRefIcons.NEW), WRITE_XMP(Localization.lang("Write XMP metadata to PDFs"), Localization.lang("Will write XMP metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_XMP), + START_SYSTEMATIC_LITERATURE_REVIEW(Localization.lang("Start systematic literature review")), OPEN_DATABASE_FOLDER(Localization.lang("Reveal in file explorer")), OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), KeyBinding.OPEN_FOLDER), OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), diff --git a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java index dc32be00a35..59eac25ae99 100644 --- a/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/DocumentViewerViewModel.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Objects; +import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ListProperty; @@ -55,9 +56,11 @@ public DocumentViewerViewModel(StateManager stateManager) { } }); - maxPages.bindBidirectional( + // we need to wrap this in run later so that the max pages number is correctly shown + Platform.runLater(() -> { + maxPages.bindBidirectional( EasyBind.wrapNullable(currentDocument).selectProperty(DocumentViewModel::maxPagesProperty)); - + }); setCurrentEntries(this.stateManager.getSelectedEntries()); } diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java index 5ad9a3b528c..9010ec3379c 100644 --- a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentPageViewModel.java @@ -60,7 +60,7 @@ public Image render(int width, int height) { @Override public int getPageNumber() { - return pageNumber; + return pageNumber + 1; } @Override diff --git a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java index 9b73c87102a..7e5ae9a5969 100644 --- a/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java +++ b/src/main/java/org/jabref/gui/documentviewer/PdfDocumentViewModel.java @@ -26,7 +26,7 @@ public ObservableList getPages() { List pdfPages = new ArrayList<>(); // There is apparently no neat way to get the page number from a PDPage...thus this old-style for loop for (int i = 0; i < pages.getCount(); i++) { - pdfPages.add(new PdfDocumentPageViewModel(pages.get(i), i + 1, document)); + pdfPages.add(new PdfDocumentPageViewModel(pages.get(i), i, document)); } return FXCollections.observableArrayList(pdfPages); } diff --git a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java index 9161758a58d..4f8e4d7fcc9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/SourceTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/SourceTab.java @@ -116,7 +116,7 @@ public SourceTab(BibDatabaseContext bibDatabaseContext, CountingUndoManager undo } private void highlightSearchPattern() { - if (searchHighlightPattern.isPresent() && codeArea != null) { + if (searchHighlightPattern.isPresent() && (codeArea != null)) { codeArea.setStyleClass(0, codeArea.getLength(), "text"); Matcher matcher = searchHighlightPattern.get().matcher(codeArea.getText()); while (matcher.find()) { @@ -172,7 +172,8 @@ private void setupSourceEditor() { } }); codeArea.setId("bibtexSourceCodeArea"); - codeArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> CodeAreaKeyBindings.call(codeArea, event)); + codeArea.addEventFilter(KeyEvent.KEY_PRESSED, event -> CodeAreaKeyBindings.call(codeArea, event, keyBindingRepository)); + codeArea.addEventFilter(KeyEvent.KEY_PRESSED, this::listenForSaveKeybinding); ActionFactory factory = new ActionFactory(keyBindingRepository); ContextMenu contextMenu = new ContextMenu(); @@ -197,7 +198,7 @@ private void setupSourceEditor() { }); codeArea.focusedProperty().addListener((obs, oldValue, onFocus) -> { - if (!onFocus && currentEntry != null) { + if (!onFocus && (currentEntry != null)) { storeSource(currentEntry, codeArea.textProperty().getValue()); } }); @@ -232,7 +233,7 @@ private void updateCodeArea() { @Override protected void bindToEntry(BibEntry entry) { - if (previousEntry != null && codeArea != null) { + if ((previousEntry != null) && (codeArea != null)) { storeSource(previousEntry, codeArea.textProperty().getValue()); } this.previousEntry = entry; @@ -321,4 +322,16 @@ private void storeSource(BibEntry outOfFocusEntry, String text) { LOGGER.debug("Incorrect source", ex); } } + + private void listenForSaveKeybinding(KeyEvent event) { + keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { + + switch (binding) { + case SAVE_DATABASE, SAVE_ALL, SAVE_DATABASE_AS -> { + storeSource(currentEntry, codeArea.textProperty().getValue()); + } + } + }); + } + } diff --git a/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java b/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java index d055eaed84c..8c199b02c97 100644 --- a/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java +++ b/src/main/java/org/jabref/gui/keyboard/CodeAreaKeyBindings.java @@ -2,7 +2,6 @@ import javafx.scene.input.KeyEvent; -import org.jabref.gui.Globals; import org.jabref.logic.util.strings.StringManipulator; import org.jabref.model.util.ResultingStringState; @@ -11,8 +10,7 @@ public class CodeAreaKeyBindings { - public static void call(CodeArea codeArea, KeyEvent event) { - KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs(); + public static void call(CodeArea codeArea, KeyEvent event, KeyBindingRepository keyBindingRepository) { keyBindingRepository.mapToKeyBinding(event).ifPresent(binding -> { switch (binding) { case EDITOR_DELETE -> { diff --git a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java index 4a97139f0d0..145b8fe0d84 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java +++ b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java @@ -1267,7 +1267,7 @@ public BibDatabase generateDatabase(List databases) throws NoSuchElementException, WrappedTargetException { BibDatabase resultDatabase = new BibDatabase(); List cited = findCitedKeys(); - List entriesToInsert = new ArrayList(); + List entriesToInsert = new ArrayList<>(); // For each cited key for (String key : cited) { diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 5d3e526985d..4cf95de899d 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -68,6 +68,14 @@ public class PreviewViewer extends ScrollPane implements InvalidationListener { "function(){function n(e){t(this,n),this.ctx=e,this.ie=!1;var r=window.navigator.userAgent;(r.indexOf(\"MSIE\")>-1||r.indexOf(\"Trident\")>-1)&&(this.ie=!0)}return r(n,[{key:\"log\",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:\"debug\",r=this.opt.log;this.opt.debug&&\"object\"===e(r)&&\"function\"==typeof r[n]&&r[n](\"mark.js: \".concat(t))}},{key:\"getSeparatedKeywords\",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(\" \").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:\"isNumeric\",value:function(e){return Number(parseFloat(e))==e}},{key:\"checkRanges\",value:function(e){var t=this;if(!Array.isArray(e)||\"[object Object]\"!==Object.prototype.toString.call(e[0]))return this.log(\"markRanges() will only accept an array of objects\"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var o=t.callNoMatchOnInvalidRanges(e,r),i=o.start,a=o.end;o.valid&&(e.start=i,e.length=a-i,n.push(e),r=a)}),n}},{key:\"callNoMatchOnInvalidRanges\",value:function(e,t){var n,r,o=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?o=!0:(this.log(\"Ignoring invalid or overlapping range: \"+\"\".concat(JSON.stringify(e))),this.opt.noMatch(e))):(this.log(\"Ignoring invalid range: \".concat(JSON.stringify(e))),this.opt.noMatch(e)),{start:n,end:r,valid:o}}},{key:\"checkWhitespaceRanges\",value:function(e,t,n){var r,o=!0,i=n.length,a=t-i,s=parseInt(e.start,10)-a;return(r=(s=s>i?i:s)+parseInt(e.length,10))>i&&(r=i,this.log(\"End range automatically set to the max value of \".concat(i))),s<0||r-s<0||s>i||r>i?(o=!1,this.log(\"Invalid range: \".concat(JSON.stringify(e))),this.opt.noMatch(e)):\"\"===n.substring(s,r).replace(/\\s+/g,\"\")&&(o=!1,this.log(\"Skipping whitespace only range: \"+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:o}}},{key:\"getTextNodes\",value:function(e){var t=this,n=\"\",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:\"matchesExclude\",value:function(e){return i.matches(e,this.opt.exclude.concat([\"script\",\"style\",\"title\",\"head\",\"html\"]))}},{key:\"wrapRangeInTextNode\",value:function(e,t,n){var r=this.opt.element?this.opt.element:\"mark\",o=e.splitText(t),i=o.splitText(n-t),a=document.createElement(r);return a.setAttribute(\"data-markjs\",\"true\"),this.opt.className&&a.setAttribute(\"class\",this.opt.className),a.textContent=o.textContent,o.parentNode.replaceChild(a,o),i}},{key:\"wrapRangeInMappedTextNode\",value:function(e,t,n,r,o){var i=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=i.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,o(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:\"wrapGroups\",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:\"separateGroups\",value:function(e,t,n,r,o){for(var i=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,o))}return e}},{key:\"wrapMatches\",value:function(e,t,n,r,o){var i=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){var o;for(t=t.node;null!==(o=e.exec(t.textContent))&&\"\"!==o[a];){if(i.opt.separateGroups)t=i.separateGroups(t,o,a,n,r);else{if(!n(o[a],t))continue;var s=o.index;if(0!==a)for(var c=1;c\n" + ""; + private static final String JS_MARK_REG_EXP_CALLBACK = "" + + "{done: function(){" + + " markInstance.markRegExp(%s);}" + + "}"; + private static final String JS_UNMARK_WITH_CALLBACK = "" + + "var markInstance = new Mark(document.getElementById(\"content\"));" + + "markInstance.unmark(%s);"; + private static final Pattern UNESCAPED_FORWARD_SLASH = Pattern.compile("\"(? { diff --git a/src/main/java/org/jabref/logic/crawler/Crawler.java b/src/main/java/org/jabref/logic/crawler/Crawler.java new file mode 100644 index 00000000000..b17745e05c5 --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/Crawler.java @@ -0,0 +1,52 @@ +package org.jabref.logic.crawler; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.ParseException; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.study.QueryResult; +import org.jabref.model.study.Study; +import org.jabref.model.util.FileUpdateMonitor; + +import org.eclipse.jgit.api.errors.GitAPIException; + +/** + * This class provides a service for SLR support by conducting an automated search and persistance + * of studies using the queries and E-Libraries specified in the provided study definition file. + * + * It composes a StudyRepository for repository management, + * and a StudyFetcher that manages the crawling over the selected E-Libraries. + */ +public class Crawler { + private final StudyRepository studyRepository; + private final StudyFetcher studyFetcher; + + /** + * Creates a crawler for retrieving studies from E-Libraries + * + * @param studyDefinitionFile The path to the study definition file that contains the list of targeted E-Libraries and used cross-library queries + */ + public Crawler(Path studyDefinitionFile, GitHandler gitHandler, FileUpdateMonitor fileUpdateMonitor, ImportFormatPreferences importFormatPreferences, SavePreferences savePreferences, BibEntryTypesManager bibEntryTypesManager) throws IllegalArgumentException, IOException, ParseException, GitAPIException { + Path studyRepositoryRoot = studyDefinitionFile.getParent(); + studyRepository = new StudyRepository(studyRepositoryRoot, gitHandler, importFormatPreferences, fileUpdateMonitor, savePreferences, bibEntryTypesManager); + Study study = studyRepository.getStudy(); + LibraryEntryToFetcherConverter libraryEntryToFetcherConverter = new LibraryEntryToFetcherConverter(study.getActiveLibraryEntries(), importFormatPreferences); + this.studyFetcher = new StudyFetcher(libraryEntryToFetcherConverter.getActiveFetchers(), study.getSearchQueryStrings()); + } + + /** + * This methods performs the crawling of the active libraries defined in the study definition file. + * This method also persists the results in the same folder the study definition file is stored in. + * + * @throws IOException Thrown if a problem occurred during the persistence of the result. + */ + public void performCrawl() throws IOException, GitAPIException { + List results = studyFetcher.crawl(); + studyRepository.persist(results); + } +} diff --git a/src/main/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverter.java b/src/main/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverter.java new file mode 100644 index 00000000000..cadf5b2978e --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverter.java @@ -0,0 +1,67 @@ +package org.jabref.logic.crawler; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.WebFetchers; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.UnknownField; + +import static org.jabref.model.entry.types.SystematicLiteratureReviewStudyEntryType.LIBRARY_ENTRY; + +/** + * Converts library entries from the given study into their corresponding fetchers. + */ +class LibraryEntryToFetcherConverter { + private final List libraryEntries; + private final ImportFormatPreferences importFormatPreferences; + + public LibraryEntryToFetcherConverter(List libraryEntries, ImportFormatPreferences importFormatPreferences) { + this.libraryEntries = libraryEntries; + this.importFormatPreferences = importFormatPreferences; + } + + /** + * Returns a list of instances of all active library fetchers. + * + * A fetcher is considered active if there exists an library entry of the library the fetcher is associated with that is enabled. + * + * @return Instances of all active fetchers defined in the study definition. + */ + public List getActiveFetchers() { + return getFetchersFromLibraryEntries(this.libraryEntries); + } + + /** + * Transforms a list of libraryEntries into a list of SearchBasedFetcher instances. + * + * @param libraryEntries List of entries + * @return List of fetcher instances + */ + private List getFetchersFromLibraryEntries(List libraryEntries) { + return libraryEntries.parallelStream() + .filter(bibEntry -> bibEntry.getType().getName().equals(LIBRARY_ENTRY.getName())) + .map(this::createFetcherFromLibraryEntry) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * Transforms a library entry into a SearchBasedFetcher instance. This only works if the library entry specifies a supported fetcher. + * + * @param libraryEntry the entry that will be converted + * @return An instance of the fetcher defined by the library entry. + */ + private SearchBasedFetcher createFetcherFromLibraryEntry(BibEntry libraryEntry) { + Set searchBasedFetchers = WebFetchers.getSearchBasedFetchers(importFormatPreferences); + String libraryNameFromFetcher = libraryEntry.getField(new UnknownField("name")).orElse(""); + return searchBasedFetchers.stream() + .filter(searchBasedFetcher -> searchBasedFetcher.getName().toLowerCase().equals(libraryNameFromFetcher.toLowerCase())) + .findAny() + .orElse(null); + } +} diff --git a/src/main/java/org/jabref/logic/crawler/StudyFetcher.java b/src/main/java/org/jabref/logic/crawler/StudyFetcher.java new file mode 100644 index 00000000000..c39ba7efe52 --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/StudyFetcher.java @@ -0,0 +1,80 @@ +package org.jabref.logic.crawler; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.PagedSearchBasedFetcher; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.study.FetchResult; +import org.jabref.model.study.QueryResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Delegates the search of the provided set of targeted E-Libraries with the provided queries to the E-Library specific fetchers, + * and aggregates the results returned by the fetchers by query and E-Library. + */ +class StudyFetcher { + private static final Logger LOGGER = LoggerFactory.getLogger(StudyFetcher.class); + private static final int MAX_AMOUNT_OF_RESULTS_PER_FETCHER = 100; + + private final List activeFetchers; + private final List searchQueries; + + StudyFetcher(List activeFetchers, List searchQueries) throws IllegalArgumentException { + this.searchQueries = searchQueries; + this.activeFetchers = activeFetchers; + } + + /** + * Each Map Entry contains the results for one search term for all libraries. + * Each entry of the internal map contains the results for a given library. + * If any library API is not available, its corresponding entry is missing from the internal map. + */ + public List crawl() { + return searchQueries.parallelStream() + .map(this::getQueryResult) + .collect(Collectors.toList()); + } + + private QueryResult getQueryResult(String searchQuery) { + return new QueryResult(searchQuery, performSearchOnQuery(searchQuery)); + } + + /** + * Queries all Databases on the given searchQuery. + * + * @param searchQuery The query the search is performed for. + * @return Mapping of each fetcher by name and all their retrieved publications as a BibDatabase + */ + private List performSearchOnQuery(String searchQuery) { + return activeFetchers.parallelStream() + .map(fetcher -> performSearchOnQueryForFetcher(searchQuery, fetcher)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private FetchResult performSearchOnQueryForFetcher(String searchQuery, SearchBasedFetcher fetcher) { + try { + List fetchResult = new ArrayList<>(); + if (fetcher instanceof PagedSearchBasedFetcher) { + int pages = ((int) Math.ceil(((double) MAX_AMOUNT_OF_RESULTS_PER_FETCHER) / ((PagedSearchBasedFetcher) fetcher).getPageSize())); + for (int page = 0; page < pages; page++) { + fetchResult.addAll(((PagedSearchBasedFetcher) fetcher).performSearchPaged(searchQuery, page).getContent()); + } + } else { + fetchResult = fetcher.performSearch(searchQuery); + } + return new FetchResult(fetcher.getName(), new BibDatabase(fetchResult)); + } catch (FetcherException e) { + LOGGER.warn(String.format("%s API request failed", fetcher.getName()), e); + return null; + } + } +} diff --git a/src/main/java/org/jabref/logic/crawler/StudyRepository.java b/src/main/java/org/jabref/logic/crawler/StudyRepository.java new file mode 100644 index 00000000000..b302e065946 --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/StudyRepository.java @@ -0,0 +1,344 @@ +package org.jabref.logic.crawler; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.database.DatabaseMerger; +import org.jabref.logic.exporter.BibtexDatabaseWriter; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.OpenDatabase; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.entry.types.SystematicLiteratureReviewStudyEntryType; +import org.jabref.model.study.FetchResult; +import org.jabref.model.study.QueryResult; +import org.jabref.model.study.Study; +import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.JabRefPreferences; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class manages all aspects of the study process related to the repository. + * + * It includes the parsing of the study definition file (study.bib) into a Study instance, + * the structured persistence of the crawling results for the study within the file based repository, + * as well as the sharing, and versioning of results using git. + */ +class StudyRepository { + // Tests work with study.bib + private static final String STUDY_DEFINITION_FILE_NAME = "study.bib"; + private static final Logger LOGGER = LoggerFactory.getLogger(StudyRepository.class); + private static final Pattern MATCHCOLON = Pattern.compile(":"); + private static final Pattern MATCHILLEGALCHARACTERS = Pattern.compile("[^A-Za-z0-9_.\\s=-]"); + + private final Path repositoryPath; + private final Path studyDefinitionBib; + private final GitHandler gitHandler; + private final Study study; + private final ImportFormatPreferences importFormatPreferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final SavePreferences savePreferences; + private final BibEntryTypesManager bibEntryTypesManager; + + /** + * Creates a study repository. + * + * @param pathToRepository Where the repository root is located. + * @param gitHandler The git handler that managages any interaction with the remote repository + * @throws IllegalArgumentException If the repository root directory does not exist, or the root directory does not contain the study definition file. + * @throws IOException Thrown if the given repository does not exists, or the study definition file does not exist + * @throws ParseException Problem parsing the study definition file. + */ + public StudyRepository(Path pathToRepository, GitHandler gitHandler, ImportFormatPreferences importFormatPreferences, FileUpdateMonitor fileUpdateMonitor, SavePreferences savePreferences, BibEntryTypesManager bibEntryTypesManager) throws IOException, ParseException, GitAPIException { + this.repositoryPath = pathToRepository; + this.gitHandler = gitHandler; + try { + gitHandler.updateLocalRepository(); + } catch (GitAPIException e) { + LOGGER.error("Updating repository from remote failed"); + } + this.importFormatPreferences = importFormatPreferences; + this.fileUpdateMonitor = fileUpdateMonitor; + this.studyDefinitionBib = Path.of(repositoryPath.toString(), STUDY_DEFINITION_FILE_NAME); + this.savePreferences = savePreferences; + this.bibEntryTypesManager = bibEntryTypesManager; + + if (Files.notExists(repositoryPath)) { + throw new IOException("The given repository does not exists."); + } else if (Files.notExists(studyDefinitionBib)) { + throw new IOException("The study definition file does not exist in the given repository."); + } + study = parseStudyFile(); + this.setUpRepositoryStructure(); + } + + /** + * Returns entries stored in the repository for a certain query and fetcher + */ + public BibDatabaseContext getFetcherResultEntries(String query, String fetcherName) throws IOException { + return OpenDatabase.loadDatabase(getPathToFetcherResultFile(query, fetcherName), importFormatPreferences, fileUpdateMonitor).getDatabaseContext(); + } + + /** + * Returns the merged entries stored in the repository for a certain query + */ + public BibDatabaseContext getQueryResultEntries(String query) throws IOException { + return OpenDatabase.loadDatabase(getPathToQueryResultFile(query), importFormatPreferences, fileUpdateMonitor).getDatabaseContext(); + } + + /** + * Returns the merged entries stored in the repository for all queries + */ + public BibDatabaseContext getStudyResultEntries() throws IOException { + return OpenDatabase.loadDatabase(getPathToStudyResultFile(), importFormatPreferences, fileUpdateMonitor).getDatabaseContext(); + } + + /** + * The study definition file contains all the definitions of a study. This method extracts the BibEntries from the study BiB file. + * + * @return Returns the BibEntries parsed from the study definition file. + * @throws IOException Problem opening the input stream. + * @throws ParseException Problem parsing the study definition file. + */ + private Study parseStudyFile() throws IOException, ParseException { + BibtexParser parser = new BibtexParser(importFormatPreferences, fileUpdateMonitor); + List parsedEntries = new ArrayList<>(); + try (InputStream inputStream = Files.newInputStream(studyDefinitionBib)) { + parsedEntries.addAll(parser.parseEntries(inputStream)); + } + + BibEntry studyEntry = parsedEntries.parallelStream() + .filter(bibEntry -> bibEntry.getType().equals(SystematicLiteratureReviewStudyEntryType.STUDY_ENTRY)).findAny() + .orElseThrow(() -> new ParseException("Study definition file does not contain a study entry")); + List queryEntries = parsedEntries.parallelStream() + .filter(bibEntry -> bibEntry.getType().equals(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY)) + .collect(Collectors.toList()); + List libraryEntries = parsedEntries.parallelStream() + .filter(bibEntry -> bibEntry.getType().equals(SystematicLiteratureReviewStudyEntryType.LIBRARY_ENTRY)) + .collect(Collectors.toList()); + + return new Study(studyEntry, queryEntries, libraryEntries); + } + + public Study getStudy() { + return study; + } + + public void persist(List crawlResults) throws IOException, GitAPIException { + try { + gitHandler.updateLocalRepository(); + } catch (GitAPIException e) { + LOGGER.error("Updating repository from remote failed"); + } + persistResults(crawlResults); + study.setLastSearchDate(LocalDate.now()); + persistStudy(); + try { + gitHandler.updateRemoteRepository("Conducted search " + LocalDate.now()); + } catch (GitAPIException e) { + LOGGER.error("Updating remote repository failed"); + } + } + + private void persistStudy() throws IOException { + writeResultToFile(studyDefinitionBib, new BibDatabase(study.getAllEntries())); + } + + /** + * Create for each query a folder, and for each fetcher a bib file in the query folder to store its results. + */ + private void setUpRepositoryStructure() throws IOException { + // Cannot use stream here since IOException has to be thrown + LibraryEntryToFetcherConverter converter = new LibraryEntryToFetcherConverter(study.getActiveLibraryEntries(), importFormatPreferences); + for (String query : study.getSearchQueryStrings()) { + createQueryResultFolder(query); + converter.getActiveFetchers() + .forEach(searchBasedFetcher -> createFetcherResultFile(query, searchBasedFetcher)); + createQueryResultFile(query); + } + createStudyResultFile(); + } + + /** + * Creates a folder using the query and its corresponding query id. + * This folder name is unique for each query, as long as the query id in the study definition is unique for each query. + * + * @param query The query the folder is created for + */ + private void createQueryResultFolder(String query) throws IOException { + Path queryResultFolder = getPathToQueryDirectory(query); + createFolder(queryResultFolder); + } + + private void createFolder(Path folder) throws IOException { + if (Files.notExists(folder)) { + Files.createDirectory(folder); + } + } + + private void createFetcherResultFile(String query, SearchBasedFetcher searchBasedFetcher) { + String fetcherName = searchBasedFetcher.getName(); + Path fetcherResultFile = getPathToFetcherResultFile(query, fetcherName); + createBibFile(fetcherResultFile); + } + + private void createQueryResultFile(String query) { + Path queryResultFile = getPathToFetcherResultFile(query, "result"); + createBibFile(queryResultFile); + } + + private void createStudyResultFile() { + createBibFile(getPathToStudyResultFile()); + } + + private void createBibFile(Path file) { + if (Files.notExists(file)) { + try { + Files.createFile(file); + } catch (IOException e) { + throw new IllegalStateException("Error during creation of repository structure.", e); + } + } + } + + /** + * Returns a string that can be used as a folder name. + * This removes all characters from the query that are illegal for directory names. + * Structure: ID-trimmed query + * + * Examples: + * Input: '(title: test-title AND abstract: Test)' as a query entry with id 1 + * Output: '1 - title= test-title AND abstract= Test' + * + * Input: 'abstract: Test*' as a query entry with id 1 + * Output: '1 - abstract= Test' + * + * Input: '"test driven"' as a query entry with id 1 + * Output: '1 - test driven' + * + * @param query that is trimmed and combined with its query id + * @return a unique folder name for any query. + */ + private String trimNameAndAddID(String query) { + // Replace all field: with field= for folder name + String trimmedNamed = MATCHCOLON.matcher(query).replaceAll("="); + trimmedNamed = MATCHILLEGALCHARACTERS.matcher(trimmedNamed).replaceAll(""); + if (query.length() > 240) { + trimmedNamed = query.substring(0, 240); + } + String id = findQueryIDByQueryString(query); + return id + " - " + trimmedNamed; + } + + /** + * Helper to find the query id for folder name creation. + * Returns the id of the first SearchQuery BibEntry with a query field that matches the given query. + * + * @param query The query whose ID is searched + * @return ID of the query defined in the study definition. + */ + private String findQueryIDByQueryString(String query) { + String queryField = "query"; + return study.getSearchQueryEntries() + .parallelStream() + .filter(bibEntry -> bibEntry.getField(new UnknownField(queryField)).orElse("").equals(query)) + .map(BibEntry::getCitationKey) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst() + .orElseThrow() + .replaceFirst(queryField, ""); + } + + /** + * Persists the crawling results in the local file based repository. + * + * @param crawlResults The results that shall be persisted. + */ + private void persistResults(List crawlResults) throws IOException { + DatabaseMerger merger = new DatabaseMerger(); + BibDatabase newStudyResultEntries = new BibDatabase(); + + for (QueryResult result : crawlResults) { + BibDatabase queryResultEntries = new BibDatabase(); + for (FetchResult fetcherResult : result.getResultsPerFetcher()) { + BibDatabase fetcherEntries = fetcherResult.getFetchResult(); + BibDatabaseContext existingFetcherResult = getFetcherResultEntries(result.getQuery(), fetcherResult.getFetcherName()); + + // Create citation keys for all entries that do not have one + generateCiteKeys(existingFetcherResult, fetcherEntries); + + // Merge new entries into fetcher result file + merger.merge(existingFetcherResult.getDatabase(), fetcherEntries); + // Aggregate each fetcher result into the query result + merger.merge(queryResultEntries, fetcherEntries); + + writeResultToFile(getPathToFetcherResultFile(result.getQuery(), fetcherResult.getFetcherName()), existingFetcherResult.getDatabase()); + } + BibDatabase existingQueryEntries = getQueryResultEntries(result.getQuery()).getDatabase(); + + // Merge new entries into query result file + merger.merge(existingQueryEntries, queryResultEntries); + // Aggregate all new entries for every query into the study result + merger.merge(newStudyResultEntries, queryResultEntries); + + writeResultToFile(getPathToQueryResultFile(result.getQuery()), existingQueryEntries); + } + BibDatabase existingStudyResultEntries = getStudyResultEntries().getDatabase(); + + // Merge new entries into study result file + merger.merge(existingStudyResultEntries, newStudyResultEntries); + + writeResultToFile(getPathToStudyResultFile(), existingStudyResultEntries); + } + + private void generateCiteKeys(BibDatabaseContext existingEntries, BibDatabase targetEntries) { + CitationKeyGenerator citationKeyGenerator = new CitationKeyGenerator(existingEntries, JabRefPreferences.getInstance().getCitationKeyPatternPreferences()); + targetEntries.getEntries().stream().filter(bibEntry -> !bibEntry.hasCitationKey()).forEach(citationKeyGenerator::generateAndSetKey); + } + + private void writeResultToFile(Path pathToFile, BibDatabase entries) throws IOException { + try (Writer fileWriter = new FileWriter(pathToFile.toFile())) { + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, savePreferences, bibEntryTypesManager); + databaseWriter.saveDatabase(new BibDatabaseContext(entries)); + } + } + + private Path getPathToFetcherResultFile(String query, String fetcherName) { + return Path.of(repositoryPath.toString(), trimNameAndAddID(query), fetcherName + ".bib"); + } + + private Path getPathToQueryResultFile(String query) { + return Path.of(repositoryPath.toString(), trimNameAndAddID(query), "result.bib"); + } + + private Path getPathToStudyResultFile() { + return Path.of(repositoryPath.toString(), "studyResult.bib"); + } + + private Path getPathToQueryDirectory(String query) { + return Path.of(repositoryPath.toString(), trimNameAndAddID(query)); + } +} diff --git a/src/main/java/org/jabref/logic/crawler/git/GitHandler.java b/src/main/java/org/jabref/logic/crawler/git/GitHandler.java new file mode 100644 index 00000000000..439f08dfccd --- /dev/null +++ b/src/main/java/org/jabref/logic/crawler/git/GitHandler.java @@ -0,0 +1,83 @@ +package org.jabref.logic.crawler.git; + +import java.io.IOException; +import java.nio.file.Path; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.RmCommand; +import org.eclipse.jgit.api.Status; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class handles the updating of the local and remote git repository that is located at the repository path + */ +public class GitHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); + private final Path repositoryPath; + private final CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(System.getenv("GIT_EMAIL"), System.getenv("GIT_PW")); + + /** + * Initialize the handler for the given repository + * + * @param repositoryPath The root of the intialized git repository + */ + public GitHandler(Path repositoryPath) { + this.repositoryPath = repositoryPath; + } + + /** + * Updates the local repository based on the main branch of the original remote repository + */ + public void updateLocalRepository() throws IOException, GitAPIException { + try (Git git = Git.open(this.repositoryPath.toFile())) { + git.pull() + .setRemote("origin") + .setRemoteBranchName("main") + .setCredentialsProvider(credentialsProvider) + .call(); + } + } + + /** + * Adds all the added, changed, and removed files to the index and updates the remote origin repository + * If pushiong to remote fails it fails silently + * + * @param commitMessage The commit message used for the commit to the remote repository + */ + public void updateRemoteRepository(String commitMessage) throws IOException, GitAPIException { + // First get up to date + this.updateLocalRepository(); + try (Git git = Git.open(this.repositoryPath.toFile())) { + Status status = git.status().call(); + if (!status.isClean()) { + // Add new and changed files to index + git.add() + .addFilepattern(".") + .call(); + // Add all removed files to index + if (!status.getMissing().isEmpty()) { + RmCommand removeCommand = git.rm() + .setCached(true); + status.getMissing().forEach(removeCommand::addFilepattern); + removeCommand.call(); + } + git.commit() + .setAllowEmpty(false) + .setMessage(commitMessage) + .call(); + try { + + git.push() + .setCredentialsProvider(credentialsProvider) + .call(); + } catch (GitAPIException e) { + LOGGER.info("Failed to push"); + } + } + } + } +} diff --git a/src/main/java/org/jabref/logic/importer/WebFetchers.java b/src/main/java/org/jabref/logic/importer/WebFetchers.java index e2791d3c13d..b73dbf8191b 100644 --- a/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -127,6 +127,7 @@ public static SortedSet getIdBasedFetchers(ImportFormatPreferenc set.add(new IacrEprintFetcher(importFormatPreferences)); set.add(new RfcFetcher(importFormatPreferences)); set.add(new Medra()); + set.add(new JstorFetcher(importFormatPreferences)); return set; } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java index eb50f3fef1d..6315e708c52 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java @@ -1,15 +1,20 @@ package org.jabref.logic.importer.fetcher; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.FulltextFetcher; +import org.jabref.logic.importer.IdBasedParserFetcher; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.Parser; @@ -28,11 +33,12 @@ /** * Fetcher for jstor.org **/ -public class JstorFetcher implements SearchBasedParserFetcher, FulltextFetcher { +public class JstorFetcher implements SearchBasedParserFetcher, FulltextFetcher, IdBasedParserFetcher { private static final String HOST = "https://www.jstor.org"; private static final String SEARCH_HOST = HOST + "/open/search"; - private static final String CITE_HOST = HOST + "/citation/text"; + private static final String CITE_HOST = HOST + "/citation/text/"; + private static final String URL_QUERY_REGEX = "(?<=\\?).*"; private final ImportFormatPreferences importFormatPreferences; @@ -82,21 +88,51 @@ public URL getURLForQuery(ComplexSearchQuery query) throws URISyntaxException, M return uriBuilder.build().toURL(); } + @Override + public URL getUrlForIdentifier(String identifier) throws FetcherException { + String start = "https://www.jstor.org/citation/text/"; + if (identifier.startsWith("http")) { + identifier = identifier.replace("https://www.jstor.org/stable", ""); + identifier = identifier.replace("http://www.jstor.org/stable", ""); + } + identifier = identifier.replaceAll(URL_QUERY_REGEX, ""); + + try { + if (identifier.contains("/")) { + // if identifier links to a entry with a valid doi + return new URL(start + identifier); + } + // else use default doi start. + return new URL(start + "10.2307/" + identifier); + } catch (IOException e) { + throw new FetcherException("could not construct url for jstor", e); + } + } + @Override public Parser getParser() { return inputStream -> { + BibtexParser parser = new BibtexParser(importFormatPreferences, new DummyFileUpdateMonitor()); + String text = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining()); + + // does the input stream contain bibtex ? + if (text.startsWith("@")) { + return parser.parseEntries(text); + } + // input stream contains html List entries; try { Document doc = Jsoup.parse(inputStream, null, HOST); - List elements = doc.body().getElementsByClass("cite-this-item"); + StringBuilder stringBuilder = new StringBuilder(); + List elements = doc.body().getElementsByClass("cite-this-item"); for (Element element : elements) { String id = element.attr("href").replace("citation/info/", ""); String data = new URLDownload(CITE_HOST + id).asString(); stringBuilder.append(data); } - BibtexParser parser = new BibtexParser(importFormatPreferences, new DummyFileUpdateMonitor()); entries = new ArrayList<>(parser.parseEntries(stringBuilder.toString())); } catch (IOException e) { throw new ParseException("Could not download data from jstor.org", e); @@ -111,7 +147,7 @@ public String getName() { } @Override - public Optional findFullText(BibEntry entry) throws IOException, FetcherException { + public Optional findFullText(BibEntry entry) throws IOException { if (entry.getField(StandardField.URL).isEmpty()) { return Optional.empty(); } @@ -133,4 +169,9 @@ public Optional findFullText(BibEntry entry) throws IOException, FetcherExc public TrustLevel getTrustLevel() { return TrustLevel.META_SEARCH; } + + @Override + public void doPostCleanup(BibEntry entry) { + // do nothing + } } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java index a547dbe2175..1064a7f272e 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java @@ -178,7 +178,7 @@ private String constructComplexQueryString(ComplexSearchQuery complexSearchQuery complexSearchQuery.getTitlePhrases().forEach(title -> searchTerms.add("title:" + title)); complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("journal:" + journal)); // Since Springer API does not support year range search, we ignore formYear and toYear and use "singleYear" only - complexSearchQuery.getSingleYear().ifPresent(year -> searchTerms.add("year:" + year.toString())); + complexSearchQuery.getSingleYear().ifPresent(year -> searchTerms.add("date:" + year.toString() + "*")); searchTerms.addAll(complexSearchQuery.getDefaultFieldPhrases()); return String.join(" AND ", searchTerms); } diff --git a/src/main/java/org/jabref/logic/search/SearchQuery.java b/src/main/java/org/jabref/logic/search/SearchQuery.java index 67f79cb5289..5138edcf178 100644 --- a/src/main/java/org/jabref/logic/search/SearchQuery.java +++ b/src/main/java/org/jabref/logic/search/SearchQuery.java @@ -4,8 +4,9 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.StringJoiner; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; @@ -17,12 +18,6 @@ import org.jabref.model.search.rules.SentenceAnalyzer; public class SearchQuery implements SearchMatcher { - - /** - * Regex pattern for escaping special characters in javascript regular expressions - */ - public static final Pattern JAVASCRIPT_ESCAPED_CHARS_PATTERN = Pattern.compile("[\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\/]"); - /** * The mode of escaping special characters in regular expressions */ @@ -30,11 +25,34 @@ private enum EscapeMode { /** * using \Q and \E marks */ - JAVA, + JAVA { + @Override + String format(String regex) { + return Pattern.quote(regex); + } + }, /** * escaping all javascript regex special characters separately */ - JAVASCRIPT + JAVASCRIPT { + @Override + String format(String regex) { + return JAVASCRIPT_ESCAPED_CHARS_PATTERN.matcher(regex).replaceAll("\\\\$0"); + } + }; + + /** + * Regex pattern for escaping special characters in javascript regular expressions + */ + private static final Pattern JAVASCRIPT_ESCAPED_CHARS_PATTERN = Pattern.compile("[.*+?^${}()|\\[\\]\\\\/]"); + + /** + * Attempt to escape all regex special characters. + * + * @param regex a string containing a regex expression + * @return a regex with all special characters escaped + */ + abstract String format(String regex); } private final String query; @@ -128,8 +146,7 @@ public boolean isRegularExpression() { } /** - * Returns a list of words this query searches for. - * The returned strings can be a regular expression. + * Returns a list of words this query searches for. The returned strings can be a regular expression. */ public List getSearchWords() { if (isRegularExpression()) { @@ -151,7 +168,9 @@ public Optional getJavaScriptPatternForWords() { return joinWordsToPattern(EscapeMode.JAVASCRIPT); } - /** Returns a regular expression pattern in the form (w1)|(w2)| ... wi are escaped if no regular expression search is enabled + /** + * Returns a regular expression pattern in the form (w1)|(w2)| ... wi are escaped if no regular expression search is enabled + * * @param escapeMode the mode of escaping special characters in wi */ private Optional joinWordsToPattern(EscapeMode escapeMode) { @@ -162,24 +181,12 @@ private Optional joinWordsToPattern(EscapeMode escapeMode) { } // compile the words to a regular expression in the form (w1)|(w2)|(w3) - StringJoiner joiner = new StringJoiner(")|(", "(", ")"); - for (String word : words) { - if (regularExpression) { - joiner.add(word); - } else { - switch (escapeMode) { - case JAVA: - joiner.add(Pattern.quote(word)); - break; - case JAVASCRIPT: - joiner.add(JAVASCRIPT_ESCAPED_CHARS_PATTERN.matcher(word).replaceAll("\\\\$0")); - break; - default: - throw new IllegalArgumentException("Unknown special characters escape mode: " + escapeMode); - } - } + Stream joiner = words.stream(); + if (!regularExpression) { + // Reformat string when we are looking for a literal match + joiner = joiner.map(escapeMode::format); } - String searchPattern = joiner.toString(); + String searchPattern = joiner.collect(Collectors.joining(")|(", "(", ")")); if (caseSensitive) { return Optional.of(Pattern.compile(searchPattern)); diff --git a/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java b/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java index 1ecf238382f..29422891f4e 100644 --- a/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java +++ b/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java @@ -50,6 +50,7 @@ public static EntryType parse(String typeName) { List types = new ArrayList<>(Arrays.asList(StandardEntryType.values())); types.addAll(Arrays.asList(IEEETranEntryType.values())); + types.addAll(Arrays.asList(SystematicLiteratureReviewStudyEntryType.values())); return types.stream().filter(type -> type.getName().equals(typeName.toLowerCase(Locale.ENGLISH))).findFirst().orElse(new UnknownEntryType(typeName)); } diff --git a/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryType.java b/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryType.java new file mode 100644 index 00000000000..1d9bd4be112 --- /dev/null +++ b/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryType.java @@ -0,0 +1,33 @@ +package org.jabref.model.entry.types; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Optional; + +public enum SystematicLiteratureReviewStudyEntryType implements EntryType { + STUDY_ENTRY("Study"), + SEARCH_QUERY_ENTRY("SearchQuery"), + LIBRARY_ENTRY("Library"); + + private final String displayName; + + SystematicLiteratureReviewStudyEntryType(String displayName) { + this.displayName = displayName; + } + + public static Optional fromName(String name) { + return Arrays.stream(SystematicLiteratureReviewStudyEntryType.values()) + .filter(field -> field.getName().equalsIgnoreCase(name)) + .findAny(); + } + + @Override + public String getName() { + return displayName.toLowerCase(Locale.ENGLISH); + } + + @Override + public String getDisplayName() { + return displayName; + } +} diff --git a/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitions.java new file mode 100644 index 00000000000..5d1bf665bfe --- /dev/null +++ b/src/main/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitions.java @@ -0,0 +1,60 @@ +package org.jabref.model.entry.types; + +import java.util.Arrays; +import java.util.List; + +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.BibEntryTypeBuilder; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; + +/** + * This class represents all supported entry types used in a study definition file + */ +public class SystematicLiteratureReviewStudyEntryTypeDefinitions { + + /** + * Entry type used for study meta data within a study definition file + * + *
    + *
  • Required fields: author, lastsearchdate, name, enabled
  • + *
  • Optional fields:
  • + *
+ */ + private static final BibEntryType STUDY_ENTRY = new BibEntryTypeBuilder() + .withType(SystematicLiteratureReviewStudyEntryType.STUDY_ENTRY) + .withRequiredFields(StandardField.AUTHOR, new UnknownField("lastsearchdate"), new UnknownField("name"), new UnknownField("researchquestions")) + .build(); + + /** + * Entry type for the queries within the study definition file + * + *
    + *
  • Required fields: query
  • + *
  • Optional fields:
  • + *
+ */ + private static final BibEntryType SEARCH_QUERY_ENTRY = new BibEntryTypeBuilder() + .withType(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY) + .withRequiredFields(new UnknownField("query")) + .build(); + + /** + * Entry type for the targeted libraries within a study definition file + * + *
    + *
  • Required fields: name, enabled
  • + *
  • Optional fields: comment
  • + *
+ */ + private static final BibEntryType LIBRARY_ENTRY = new BibEntryTypeBuilder() + .withType(SystematicLiteratureReviewStudyEntryType.STUDY_ENTRY) + .withRequiredFields(new UnknownField("name"), new UnknownField("enabled")) + .withImportantFields(StandardField.COMMENT) + .build(); + + public static final List ALL = Arrays.asList(STUDY_ENTRY, SEARCH_QUERY_ENTRY, LIBRARY_ENTRY); + + private SystematicLiteratureReviewStudyEntryTypeDefinitions() { + } +} diff --git a/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java index f70a05b1576..98e12978963 100644 --- a/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java +++ b/src/main/java/org/jabref/model/search/rules/GrammarBasedSearchRule.java @@ -232,7 +232,7 @@ public Boolean visitComparison(SearchParser.ComparisonContext context) { if (fieldDescriptor.isPresent()) { return comparison(fieldDescriptor.get().getText(), ComparisonOperator.build(context.operator.getText()), right); } else { - return new ContainBasedSearchRule(caseSensitive).applyRule(right, entry); + return SearchRules.getSearchRule(caseSensitive, regex).applyRule(right, entry); } } diff --git a/src/main/java/org/jabref/model/search/rules/SearchRules.java b/src/main/java/org/jabref/model/search/rules/SearchRules.java index 88268f6d2b7..dcde2b66227 100644 --- a/src/main/java/org/jabref/model/search/rules/SearchRules.java +++ b/src/main/java/org/jabref/model/search/rules/SearchRules.java @@ -31,7 +31,7 @@ private static boolean isSimpleQuery(String query) { return SIMPLE_EXPRESSION.matcher(query).matches(); } - private static SearchRule getSearchRule(boolean caseSensitive, boolean regex) { + static SearchRule getSearchRule(boolean caseSensitive, boolean regex) { if (regex) { return new RegexBasedSearchRule(caseSensitive); } else { diff --git a/src/main/java/org/jabref/model/study/FetchResult.java b/src/main/java/org/jabref/model/study/FetchResult.java new file mode 100644 index 00000000000..80637feb4ab --- /dev/null +++ b/src/main/java/org/jabref/model/study/FetchResult.java @@ -0,0 +1,24 @@ +package org.jabref.model.study; + +import org.jabref.model.database.BibDatabase; + +/** + * Represents the result of fetching the results for a query for a specific library + */ +public class FetchResult { + private final String fetcherName; + private final BibDatabase fetchResult; + + public FetchResult(String fetcherName, BibDatabase fetcherResult) { + this.fetcherName = fetcherName; + this.fetchResult = fetcherResult; + } + + public String getFetcherName() { + return fetcherName; + } + + public BibDatabase getFetchResult() { + return fetchResult; + } +} diff --git a/src/main/java/org/jabref/model/study/QueryResult.java b/src/main/java/org/jabref/model/study/QueryResult.java new file mode 100644 index 00000000000..2976b5224fe --- /dev/null +++ b/src/main/java/org/jabref/model/study/QueryResult.java @@ -0,0 +1,24 @@ +package org.jabref.model.study; + +import java.util.List; + +/** + * Represents the result of fetching the results from all active fetchers for a specific query. + */ +public class QueryResult { + private final String query; + private final List resultsPerLibrary; + + public QueryResult(String query, List resultsPerLibrary) { + this.query = query; + this.resultsPerLibrary = resultsPerLibrary; + } + + public String getQuery() { + return query; + } + + public List getResultsPerFetcher() { + return resultsPerLibrary; + } +} diff --git a/src/main/java/org/jabref/model/study/Study.java b/src/main/java/org/jabref/model/study/Study.java new file mode 100644 index 00000000000..37ed6e2328a --- /dev/null +++ b/src/main/java/org/jabref/model/study/Study.java @@ -0,0 +1,98 @@ +package org.jabref.model.study; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.UnknownField; + +/** + * This class represents a scientific study. + * + * This class defines all aspects of a scientific study relevant to the application. It is a proxy for the file based study definition. + */ +public class Study { + private static final String SEARCH_QUERY_FIELD_NAME = "query"; + + private final BibEntry studyEntry; + private final List queryEntries; + private final List libraryEntries; + + public Study(BibEntry studyEntry, List queryEntries, List libraryEntries) { + this.studyEntry = studyEntry; + this.queryEntries = queryEntries; + this.libraryEntries = libraryEntries; + } + + public List getAllEntries() { + List allEntries = new ArrayList<>(); + allEntries.add(studyEntry); + allEntries.addAll(queryEntries); + allEntries.addAll(libraryEntries); + return allEntries; + } + + /** + * Returns all query strings + * + * @return List of all queries as Strings. + */ + public List getSearchQueryStrings() { + return queryEntries.parallelStream() + .map(bibEntry -> bibEntry.getField(new UnknownField(SEARCH_QUERY_FIELD_NAME))) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + /** + * This method returns the SearchQuery entries. + * This is required when the BibKey of the search term entry is required in combination with the search query (e.g. + * for the creation of the study repository structure). + */ + public List getSearchQueryEntries() { + return queryEntries; + } + + /** + * Returns a meta data entry of the first study entry found in the study definition file of the provided type. + * + * @param metaDataField The type of requested meta-data + * @return returns the requested meta data type of the first found study entry + * @throws IllegalArgumentException If the study file does not contain a study entry. + */ + public Optional getStudyMetaDataField(StudyMetaDataField metaDataField) throws IllegalArgumentException { + return studyEntry.getField(metaDataField.toField()); + } + + /** + * Sets the lastSearchDate field of the study entry + * + * @param date date the last time a search was conducted + */ + public void setLastSearchDate(LocalDate date) { + studyEntry.setField(StudyMetaDataField.STUDY_LAST_SEARCH.toField(), date.toString()); + } + + /** + * Extracts all active LibraryEntries from the BibEntries. + * + * @return List of BibEntries of type Library + * @throws IllegalArgumentException If a transformation from Library entry to LibraryDefinition fails + */ + public List getActiveLibraryEntries() throws IllegalArgumentException { + return libraryEntries + .parallelStream() + .filter(bibEntry -> { + // If enabled is not defined, the fetcher is active. + return bibEntry.getField(new UnknownField("enabled")) + .map(enabled -> enabled.equals("true")) + .orElse(true); + }) + .collect(Collectors.toList()); + } +} + diff --git a/src/main/java/org/jabref/model/study/StudyMetaDataField.java b/src/main/java/org/jabref/model/study/StudyMetaDataField.java new file mode 100644 index 00000000000..6dbea2a2dc8 --- /dev/null +++ b/src/main/java/org/jabref/model/study/StudyMetaDataField.java @@ -0,0 +1,24 @@ +package org.jabref.model.study; + +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; + +/** + * This enum represents the different fields in the study entry + */ +public enum StudyMetaDataField { + STUDY_NAME(new UnknownField("name")), STUDY_RESEARCH_QUESTIONS(new UnknownField("researchQuestions")), + STUDY_AUTHORS(StandardField.AUTHOR), STUDY_GIT_REPOSITORY(new UnknownField("gitRepositoryURL")), + STUDY_LAST_SEARCH(new UnknownField("lastSearchDate")); + + private final Field field; + + StudyMetaDataField(Field field) { + this.field = field; + } + + public Field toField() { + return this.field; + } +} diff --git a/src/main/resources/csl-styles/.github/workflows/merge.yaml b/src/main/resources/csl-styles/.github/workflows/merge.yaml new file mode 100644 index 00000000000..285fd219cee --- /dev/null +++ b/src/main/resources/csl-styles/.github/workflows/merge.yaml @@ -0,0 +1,104 @@ +name: Merge to release + +on: + push: + branches: + - master + workflow_dispatch: + inputs: + commit_message: + description: Commit message + required: true + +jobs: + release: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + DISTRIBUTION_UPDATER_TOKEN: "${{ secrets.DISTRIBUTION_UPDATER_TOKEN }}" + steps: + - uses: actions/checkout@v2 + if: github.event_name == 'push' + - uses: actions/checkout@v2 + if: github.event_name == 'workflow_dispatch' + with: + fetch-depth: 0 + + - name: Release branch version + id: release + run: echo ::set-output name=branch::v1.0.1 + - name: Checkout release branch + uses: actions/checkout@v2 + with: + ref: ${{ steps.release.outputs.branch }} + path: './release' + + - name: Check for relevant changes + if: github.event_name == 'push' + uses: dorny/paths-filter@v2 + id: update + with: + list-files: shell + filters: | + updated: + - added|modified: [ '*.csl', '*.xml' ] + deleted: + - deleted: [ '*.csl', '*.xml' ] + + - name: Changed files + if: github.event_name == 'push' + run: | + echo updated: ${{ steps.update.outputs.updated_files }} + echo deleted: ${{ steps.update.outputs.deleted_files }} + + - name: Set up Ruby + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true')) + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.1 + - name: but use cache to speed that up + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true')) + uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Bundle install + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true')) + run: | + bundle config path vendor/bundle + bundle update sheldon --jobs 4 --retry 3 + + - name: Populate new branch + run: bundle exec sheldon --token=$GITHUB_TOKEN --verbose --populate release + if: github.event_name == 'workflow_dispatch' + + - name: update the timestamps and add the changes + run: bundle exec sheldon --token=$GITHUB_TOKEN --verbose --release release ${{ steps.update.outputs.updated_files }} + if: github.event_name == 'push' && steps.update.outputs.updated == 'true' + + - name: delete deleted files + run: cd release && git rm ${{ steps.update.outputs.deleted_files }} + if: github.event_name == 'push' && steps.update.outputs.deleted == 'true' + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + repository: 'release' + commit_message: Releasing ${{ steps.update.outputs.updated_files }} ${{ steps.update.outputs.deleted_files }} + if: github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true') + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + repository: 'release' + commit_message: ${{ github.event.inputs.commit_message }} + if: github.event_name == 'workflow_dispatch' + + # https://styles-update.zotero.org:8826/ is for Zotero (styles page, API's citation server, client style updates, etc.) + # https://styles-update.zotero.org:8827/ is for the Zotero-run instance of https://github.com/citation-style-language/distribution-updater + # that performs the updating of the (to-be-deprecated) https://github.com/citation-style-language/styles-distribution/ repo + - name: ping Zotero servers + if: github.repository == 'citation-style-language/styles' + run: | + curl -H 'Content-Length:' -H "Authorization: $ZOTERO_UPDATE_TOKEN" -F 'payload={"type":"push","branch":"${{ steps.release.outputs.branch }}","status":0,"commit":"'$GITHUB_SHA'"}' https://styles-update.zotero.org:8826/ + curl -H 'Content-Length:' -H "Authorization: $ZOTERO_UPDATE_TOKEN" -F 'payload={"type":"push","branch":"${{ steps.release.outputs.branch }}","status":0,"commit":"'$GITHUB_SHA'"}' https://styles-update.zotero.org:8827/ diff --git a/src/main/resources/csl-styles/.github/workflows/sheldon.yaml b/src/main/resources/csl-styles/.github/workflows/sheldon.yaml new file mode 100644 index 00000000000..28385ac0672 --- /dev/null +++ b/src/main/resources/csl-styles/.github/workflows/sheldon.yaml @@ -0,0 +1,63 @@ +name: Pull request feedback + +on: + pull_request_target: + types: [ opened, synchronize ] + +jobs: + test: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + steps: + - uses: actions/checkout@v2 + + - name: Check for relevant changes + uses: dorny/paths-filter@v2 + id: changed + with: + list-files: shell + filters: | + style: + - '*.csl' + locale: + - '*.xml' + + - name: Changed files + run: | + echo changed: ${{ steps.changed.outputs.style_files }} ${{ steps.changed.outputs.locale_files }} + + - name: Set up Ruby + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7.1 + - name: but use cache to speed that up + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + uses: actions/cache@v2 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Bundle install + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + run: | + bundle config path vendor/bundle + bundle update sheldon --jobs 4 --retry 3 + + - name: Apply the PR + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + run: bundle exec sheldon --token=$GITHUB_TOKEN --apply + + - name: Welcome to a new PR + if: github.event.action == 'opened' && steps.changed.outputs.style == 'true' + run: bundle exec sheldon --token=$GITHUB_TOKEN --welcome + + - name: See if the styles/locales work + if: steps.changed.outputs.style == 'true' || steps.changed.outputs.locale == 'true' + run: bundle exec rake + + - name: report + if: (failure() || success()) && steps.changed.outputs.style == 'true' + run: bundle exec sheldon --token=$GITHUB_TOKEN --report --verbose diff --git a/src/main/resources/csl-styles/.travis.yml b/src/main/resources/csl-styles/.travis.yml deleted file mode 100644 index 724298fd1ef..00000000000 --- a/src/main/resources/csl-styles/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -dist: focal -language: ruby -cache: bundler -rvm: -- 2.7.1 -install: -- bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle} -- bundle update sheldon -notifications: - email: - on_success: change - on_failure: always - recipients: - - secure: Ko9SzcdByE/HOMP7s9ddTpRdqahnO5LJ1MGJWX/cmymQc8fDRMgP9jgI6D/G5Ntgo6uGUO+xkcV5mBbOAoI5v8U2GQ5nICDXHdQrX8kJzve9JDiQJOy0c17TYi3d7bBeS1bOhy7E0TxHRax2wWWxDhqz80GwSo9JAhQbcusR/1U= - - secure: Ov0xcwOVBSbc7uuCk0Qu+ILmh3HuFNa9PnfI363at9V3aG05oJHiTRseFXvfiHgK5663Wcytcl3DU+A2vYZSz7Y3ZGzcUzhlRBLMUfebncB2nAsX3NJsieJ6FoYlkRBZdzA2lzt3FVv99hebuZnU4OiANdnHFiLmFzeWRaPZfIQ= - webhooks: - urls: - - https://shelbot.herokuapp.com/build - - https://styles-update.zotero.org:8826/ - - https://styles-update.zotero.org:8827/ - on_success: always - on_failure: always - on_start: never diff --git a/src/main/resources/csl-styles/Gemfile.lock b/src/main/resources/csl-styles/Gemfile.lock index 69f6fbab0b5..b220ca304d2 100644 --- a/src/main/resources/csl-styles/Gemfile.lock +++ b/src/main/resources/csl-styles/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/citation-style-language/Sheldon.git - revision: 7822dbda11e229cbd933f84a3a1cfdbe8e75dfaf + revision: 1962fad73610a3e0b1610e0c24c16669d584196b specs: sheldon (1.0.2) citeproc-ruby diff --git a/src/main/resources/csl-styles/Rakefile b/src/main/resources/csl-styles/Rakefile index f284116cdca..1f29f3380df 100644 --- a/src/main/resources/csl-styles/Rakefile +++ b/src/main/resources/csl-styles/Rakefile @@ -7,20 +7,10 @@ rescue Bundler::BundlerError => e exit e.status_code end -if ENV['TRAVIS'] - at_exit do - system('bundle exec sheldon') - end -end - require 'rspec/core' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| - if ENV['TRAVIS'] - spec.rspec_opts = %w{ --require spec_helper.rb --format Fuubar --color --format json --out spec/sheldon/travis.json } - else - spec.rspec_opts = %w{ --require spec_helper.rb --format Fuubar --color --format json --out spec/sheldon/travis.json } - end + spec.rspec_opts = %w{ --require spec_helper.rb --format Fuubar --color --format json --out spec/sheldon/ci.json } end task :default => [:spec] diff --git a/src/main/resources/csl-styles/american-chemical-society.csl b/src/main/resources/csl-styles/american-chemical-society.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/austral-entomology.csl b/src/main/resources/csl-styles/austral-entomology.csl new file mode 100644 index 00000000000..35b2171f554 --- /dev/null +++ b/src/main/resources/csl-styles/austral-entomology.csl @@ -0,0 +1,238 @@ + + diff --git a/src/main/resources/csl-styles/beltz-padagogik.csl b/src/main/resources/csl-styles/beltz-padagogik.csl index 72438b5a92b..c36e6311d95 100644 --- a/src/main/resources/csl-styles/beltz-padagogik.csl +++ b/src/main/resources/csl-styles/beltz-padagogik.csl @@ -128,7 +128,7 @@ - + diff --git a/src/main/resources/csl-styles/berlin-school-of-economics-and-law-international-marketing-management.csl b/src/main/resources/csl-styles/berlin-school-of-economics-and-law-international-marketing-management.csl index ba1dcaaa7fa..9d9ce715834 100644 --- a/src/main/resources/csl-styles/berlin-school-of-economics-and-law-international-marketing-management.csl +++ b/src/main/resources/csl-styles/berlin-school-of-economics-and-law-international-marketing-management.csl @@ -146,7 +146,7 @@ - + diff --git a/src/main/resources/csl-styles/biuletyn-polskiego-towarzystwa-jezykoznawczego.csl b/src/main/resources/csl-styles/biuletyn-polskiego-towarzystwa-jezykoznawczego.csl index 5eea3b12b41..934b94b2f7f 100644 --- a/src/main/resources/csl-styles/biuletyn-polskiego-towarzystwa-jezykoznawczego.csl +++ b/src/main/resources/csl-styles/biuletyn-polskiego-towarzystwa-jezykoznawczego.csl @@ -78,6 +78,9 @@ + + + @@ -132,7 +135,7 @@ - + diff --git a/src/main/resources/csl-styles/bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl b/src/main/resources/csl-styles/bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl index 191e0304c63..04227951907 100644 --- a/src/main/resources/csl-styles/bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl +++ b/src/main/resources/csl-styles/bursa-uludag-universitesi-saglik-bilimleri-enstitusu.csl @@ -1,664 +1,1465 @@ - diff --git a/src/main/resources/csl-styles/chungara-revista-de-antropologia-chilena.csl b/src/main/resources/csl-styles/chungara-revista-de-antropologia-chilena.csl new file mode 100644 index 00000000000..975f8ff3e94 --- /dev/null +++ b/src/main/resources/csl-styles/chungara-revista-de-antropologia-chilena.csl @@ -0,0 +1,362 @@ + + diff --git a/src/main/resources/csl-styles/clinical-orthopaedics-and-related-research.csl b/src/main/resources/csl-styles/clinical-orthopaedics-and-related-research.csl index f0507271a37..4f045d06e47 100644 --- a/src/main/resources/csl-styles/clinical-orthopaedics-and-related-research.csl +++ b/src/main/resources/csl-styles/clinical-orthopaedics-and-related-research.csl @@ -17,6 +17,11 @@ 2012-09-27T22:06:38+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License + + + - + + diff --git a/src/main/resources/csl-styles/dependent/current-biology.csl b/src/main/resources/csl-styles/dependent/current-biology.csl index ebed5648014..224648bdd5e 100644 --- a/src/main/resources/csl-styles/dependent/current-biology.csl +++ b/src/main/resources/csl-styles/dependent/current-biology.csl @@ -5,14 +5,14 @@ Current Biology http://www.zotero.org/styles/current-biology - + 0960-9822 1879-0445 - 2016-12-13T12:00:00+00:00 + 2017-10-20T12:00:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License diff --git a/src/main/resources/csl-styles/deutsche-sprache.csl b/src/main/resources/csl-styles/deutsche-sprache.csl index e9d357b783a..915c5dc7dfd 100644 --- a/src/main/resources/csl-styles/deutsche-sprache.csl +++ b/src/main/resources/csl-styles/deutsche-sprache.csl @@ -60,6 +60,9 @@ + + + @@ -135,7 +138,7 @@ - + diff --git a/src/main/resources/csl-styles/guide-des-citations-references-et-abreviations-juridiques.csl b/src/main/resources/csl-styles/guide-des-citations-references-et-abreviations-juridiques.csl new file mode 100644 index 00000000000..eaaf050adca --- /dev/null +++ b/src/main/resources/csl-styles/guide-des-citations-references-et-abreviations-juridiques.csl @@ -0,0 +1,400 @@ + + diff --git a/src/main/resources/csl-styles/harper-adams-university-harvard.csl b/src/main/resources/csl-styles/harper-adams-university-harvard.csl new file mode 100644 index 00000000000..ad36e6c6880 --- /dev/null +++ b/src/main/resources/csl-styles/harper-adams-university-harvard.csl @@ -0,0 +1,423 @@ + + diff --git a/src/main/resources/csl-styles/harvard-institut-fur-praxisforschung-de.csl b/src/main/resources/csl-styles/harvard-institut-fur-praxisforschung-de.csl index 4addff2d2d3..100c3970c43 100644 --- a/src/main/resources/csl-styles/harvard-institut-fur-praxisforschung-de.csl +++ b/src/main/resources/csl-styles/harvard-institut-fur-praxisforschung-de.csl @@ -46,7 +46,8 @@ - + diff --git a/src/main/resources/csl-styles/iso690-author-date-fr-no-abstract.csl b/src/main/resources/csl-styles/iso690-author-date-fr-no-abstract.csl index 5cbfabb8e89..f8bf57bcb40 100644 --- a/src/main/resources/csl-styles/iso690-author-date-fr-no-abstract.csl +++ b/src/main/resources/csl-styles/iso690-author-date-fr-no-abstract.csl @@ -9,10 +9,14 @@ Pierre-Amiel Giraud pierre-amiel.giraud@u-bordeaux3.fr + + Raphael Grolimund + grolimur@protonmail.ch + Style based on ISO 690:2010(F), V1, derived from Mellifluo, Grolimund, Hardegger and Giraud version. - 2013-03-31T23:22:38+00:00 + 2020-11-19T21:30:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -235,7 +239,7 @@ - + @@ -285,7 +289,7 @@ - + @@ -330,7 +334,7 @@ - + @@ -427,6 +431,7 @@ + diff --git a/src/main/resources/csl-styles/iso690-author-date-fr.csl b/src/main/resources/csl-styles/iso690-author-date-fr.csl index 32658c37692..4b8a2825d14 100644 --- a/src/main/resources/csl-styles/iso690-author-date-fr.csl +++ b/src/main/resources/csl-styles/iso690-author-date-fr.csl @@ -11,7 +11,7 @@ Raphael Grolimund - raphael.grolimund@epfl.ch + grolimur@protonmail.ch Michel Hardegger @@ -24,7 +24,7 @@ Style based on ISO 690:2010(F), V1.1 - 2013-05-31T20:00:00+01:00 + 2020-11-19T21:30:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -107,15 +107,19 @@ - - + + + + + + + + + + + + - - - - - - @@ -147,7 +151,7 @@ - + @@ -168,11 +172,11 @@ - + - + diff --git a/src/main/resources/csl-styles/iso690-note-fr.csl b/src/main/resources/csl-styles/iso690-note-fr.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/iso690-numeric-fr.csl b/src/main/resources/csl-styles/iso690-numeric-fr.csl index e031530a3b8..405705bc549 100644 --- a/src/main/resources/csl-styles/iso690-numeric-fr.csl +++ b/src/main/resources/csl-styles/iso690-numeric-fr.csl @@ -11,7 +11,7 @@ Raphael Grolimund - raphael.grolimund@epfl.ch + grolimur@protonmail.ch Michel Hardegger @@ -20,7 +20,7 @@ Style based on ISO 690:2010(F), V1.1 - 2013-05-31T20:00:00+01:00 + 2020-11-19T21:30:00+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -145,7 +145,7 @@ - + diff --git a/src/main/resources/csl-styles/journal-of-the-indian-law-institute.csl b/src/main/resources/csl-styles/journal-of-the-indian-law-institute.csl new file mode 100644 index 00000000000..ecd4d008f2e --- /dev/null +++ b/src/main/resources/csl-styles/journal-of-the-indian-law-institute.csl @@ -0,0 +1,267 @@ + + diff --git a/src/main/resources/csl-styles/keel-ja-kirjandus.csl b/src/main/resources/csl-styles/keel-ja-kirjandus.csl index fad7be4b874..7997b1031d1 100644 --- a/src/main/resources/csl-styles/keel-ja-kirjandus.csl +++ b/src/main/resources/csl-styles/keel-ja-kirjandus.csl @@ -63,6 +63,9 @@ + + + @@ -138,7 +141,7 @@ - + diff --git a/src/main/resources/csl-styles/la-trobe-university-harvard.csl b/src/main/resources/csl-styles/la-trobe-university-harvard.csl index c3077e69ce8..cf3c090240a 100644 --- a/src/main/resources/csl-styles/la-trobe-university-harvard.csl +++ b/src/main/resources/csl-styles/la-trobe-university-harvard.csl @@ -1,5 +1,6 @@ diff --git a/src/main/resources/csl-styles/modern-language-association.csl b/src/main/resources/csl-styles/modern-language-association.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/molecular-biology.csl b/src/main/resources/csl-styles/molecular-biology.csl new file mode 100644 index 00000000000..5ee2cfdc861 --- /dev/null +++ b/src/main/resources/csl-styles/molecular-biology.csl @@ -0,0 +1,147 @@ + + diff --git a/src/main/resources/csl-styles/molecular-plant-pathology.csl b/src/main/resources/csl-styles/molecular-plant-pathology.csl index 118cf530c92..d04ecc5994d 100644 --- a/src/main/resources/csl-styles/molecular-plant-pathology.csl +++ b/src/main/resources/csl-styles/molecular-plant-pathology.csl @@ -103,7 +103,7 @@ - + diff --git a/src/main/resources/csl-styles/nouvelles-perspectives-en-sciences-sociales.csl b/src/main/resources/csl-styles/nouvelles-perspectives-en-sciences-sociales.csl new file mode 100644 index 00000000000..d76bf130877 --- /dev/null +++ b/src/main/resources/csl-styles/nouvelles-perspectives-en-sciences-sociales.csl @@ -0,0 +1,596 @@ + + diff --git a/src/main/resources/csl-styles/opto-electronic-advances.csl b/src/main/resources/csl-styles/opto-electronic-advances.csl new file mode 100644 index 00000000000..6710073db28 --- /dev/null +++ b/src/main/resources/csl-styles/opto-electronic-advances.csl @@ -0,0 +1,152 @@ + + diff --git a/src/main/resources/csl-styles/radiology.csl b/src/main/resources/csl-styles/radiology.csl index 604041f7c0e..aa9df2e75fa 100644 --- a/src/main/resources/csl-styles/radiology.csl +++ b/src/main/resources/csl-styles/radiology.csl @@ -5,7 +5,7 @@ http://www.zotero.org/styles/radiology - + Adam Tunis atunis@gmail.com @@ -60,12 +60,15 @@ - + + + + - + @@ -206,18 +209,20 @@ - + + + + + + - - - - + + + - - diff --git a/src/main/resources/csl-styles/saint-paul-university-faculty-of-canon-law.csl b/src/main/resources/csl-styles/saint-paul-university-faculty-of-canon-law.csl new file mode 100644 index 00000000000..a8d9b5902b7 --- /dev/null +++ b/src/main/resources/csl-styles/saint-paul-university-faculty-of-canon-law.csl @@ -0,0 +1,267 @@ + + diff --git a/src/main/resources/csl-styles/sekolah-tinggi-meteorologi-klimatologi-dan-geofisika.csl b/src/main/resources/csl-styles/sekolah-tinggi-meteorologi-klimatologi-dan-geofisika.csl new file mode 100644 index 00000000000..00c3908b788 --- /dev/null +++ b/src/main/resources/csl-styles/sekolah-tinggi-meteorologi-klimatologi-dan-geofisika.csl @@ -0,0 +1,219 @@ + + diff --git a/src/main/resources/csl-styles/society-for-american-archaeology.csl b/src/main/resources/csl-styles/society-for-american-archaeology.csl index 376552ee8b6..c8471736d51 100644 --- a/src/main/resources/csl-styles/society-for-american-archaeology.csl +++ b/src/main/resources/csl-styles/society-for-american-archaeology.csl @@ -19,9 +19,13 @@ Allison Grunwald agrunwa1@uwyo.edu + + Erik Marsh + erik.marsh@gmail.com + - 2020-04-17T09:23:48+00:00 + 2020-11-23T22:11:55+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -238,7 +242,7 @@ - @@ -338,6 +342,10 @@ + + + + diff --git a/src/main/resources/csl-styles/technische-universitat-dortmund-ag-virtual-machining.csl b/src/main/resources/csl-styles/technische-universitat-dortmund-ag-virtual-machining.csl new file mode 100644 index 00000000000..48c27640249 --- /dev/null +++ b/src/main/resources/csl-styles/technische-universitat-dortmund-ag-virtual-machining.csl @@ -0,0 +1,141 @@ + + diff --git a/src/main/resources/csl-styles/university-of-york-chicago.csl b/src/main/resources/csl-styles/university-of-york-chicago.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/university-of-york-harvard-archaeology.csl b/src/main/resources/csl-styles/university-of-york-harvard-archaeology.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/university-of-york-harvard-environment.csl b/src/main/resources/csl-styles/university-of-york-harvard-environment.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/university-of-york-harvard.csl b/src/main/resources/csl-styles/university-of-york-harvard.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/university-of-york-ieee.csl b/src/main/resources/csl-styles/university-of-york-ieee.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/university-of-york-mhra.csl b/src/main/resources/csl-styles/university-of-york-mhra.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/university-of-york-oscola.csl b/src/main/resources/csl-styles/university-of-york-oscola.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/university-of-york-vancouver.csl b/src/main/resources/csl-styles/university-of-york-vancouver.csl old mode 100755 new mode 100644 diff --git a/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-abteilung-fur-bildungswissenschaft.csl b/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-abteilung-fur-bildungswissenschaft.csl index a40460f98c7..d4288def807 100644 --- a/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-abteilung-fur-bildungswissenschaft.csl +++ b/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-abteilung-fur-bildungswissenschaft.csl @@ -232,6 +232,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -412,7 +442,9 @@ - + + + diff --git a/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-health-care-management.csl b/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-health-care-management.csl new file mode 100644 index 00000000000..dd8502a0dfd --- /dev/null +++ b/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-health-care-management.csl @@ -0,0 +1,622 @@ + + diff --git a/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-unternehmensrechnung-und-controlling.csl b/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-unternehmensrechnung-und-controlling.csl index 15d80f84dc9..9a5396d2b30 100644 --- a/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-unternehmensrechnung-und-controlling.csl +++ b/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-unternehmensrechnung-und-controlling.csl @@ -220,6 +220,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -389,7 +419,9 @@ - + + + diff --git a/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-wirtschaftspadagogik.csl b/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-wirtschaftspadagogik.csl index 9adb6c0aa80..f93c0275881 100644 --- a/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-wirtschaftspadagogik.csl +++ b/src/main/resources/csl-styles/wirtschaftsuniversitat-wien-wirtschaftspadagogik.csl @@ -277,6 +277,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -459,7 +489,9 @@ - + + + diff --git a/src/main/resources/csl-styles/zeitschrift-fur-qualitative-forschung.csl b/src/main/resources/csl-styles/zeitschrift-fur-qualitative-forschung.csl index 5227c1a4d9e..d415e56e5d3 100644 --- a/src/main/resources/csl-styles/zeitschrift-fur-qualitative-forschung.csl +++ b/src/main/resources/csl-styles/zeitschrift-fur-qualitative-forschung.csl @@ -138,7 +138,7 @@ - + diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index b58510e6d5c..d66d978e315 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -295,32 +295,21 @@ Entry\ owner=Entry owner Entry\ preview=Entry preview Entry\ table=Entry table - Entry\ table\ columns=Entry table columns Entry\ Title\ (Required\ to\ deliver\ recommendations.)=Entry Title (Required to deliver recommendations.) - Entry\ type=Entry type - Error=Error - Error\ occurred\ when\ parsing\ entry=Error occurred when parsing entry - Error\ opening\ file=Error opening file - Error\ while\ writing=Error while writing - +Error\ during\ persistence\ of\ crawling\ results.=Error during persistence of crawling results. +Error\ during\ reading\ of\ study\ definition\ file.=Error during reading of study definition file. '%0'\ exists.\ Overwrite\ file?='%0' exists. Overwrite file? - Export=Export - Export\ preferences=Export preferences - Export\ preferences\ to\ file=Export preferences to file - Export\ to\ clipboard=Export to clipboard - Export\ to\ text\ file.=Export to text file. - Exporting=Exporting Extension=Extension @@ -644,11 +633,9 @@ Previous\ preview\ layout=Previous preview layout Available=Available Selected=Selected Selected\ Layouts\ can\ not\ be\ empty=Selected Layouts can not be empty - +Start\ systematic\ literature\ review=Start systematic literature review Reset\ default\ preview\ style=Reset default preview style - Previous\ entry=Previous entry - Primary\ sort\ criterion=Primary sort criterion Problem\ with\ parsing\ entry=Problem with parsing entry Processing\ %0=Processing %0 diff --git a/src/test/java/org/jabref/logic/crawler/CrawlerTest.java b/src/test/java/org/jabref/logic/crawler/CrawlerTest.java new file mode 100644 index 00000000000..c93ab4c628d --- /dev/null +++ b/src/test/java/org/jabref/logic/crawler/CrawlerTest.java @@ -0,0 +1,108 @@ +package org.jabref.logic.crawler; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.eclipse.jgit.api.Git; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Integration test of the components used for SLR support + */ +class CrawlerTest { + @TempDir + Path tempRepositoryDirectory; + ImportFormatPreferences importFormatPreferences; + SavePreferences savePreferences; + BibEntryTypesManager entryTypesManager; + GitHandler gitHandler = mock(GitHandler.class, Answers.RETURNS_DEFAULTS); + + @Test + public void testWhetherAllFilesAreCreated() throws Exception { + setUp(); + Crawler testCrawler = new Crawler(getPathToStudyDefinitionFile(), + gitHandler, + new DummyFileUpdateMonitor(), + importFormatPreferences, + savePreferences, + entryTypesManager + ); + + testCrawler.performCrawl(); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3"))); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "ArXiv.bib"))); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "Springer.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "Springer.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "Springer.bib"))); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "result.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "result.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "result.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "studyResult.bib"))); + } + + private Path getPathToStudyDefinitionFile() { + return tempRepositoryDirectory.resolve("study.bib"); + } + + /** + * Set up mocks and copies the study definition file into the test repository + */ + private void setUp() throws Exception { + setUpRepository(); + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); + when(savePreferences.getSaveOrder()).thenReturn(new SaveOrderConfig()); + when(savePreferences.getEncoding()).thenReturn(null); + when(savePreferences.takeMetadataSaveOrderInAccount()).thenReturn(true); + when(importFormatPreferences.getKeywordSeparator()).thenReturn(','); + when(importFormatPreferences.getFieldContentFormatterPreferences()).thenReturn(new FieldContentFormatterPreferences()); + when(importFormatPreferences.isKeywordSyncEnabled()).thenReturn(false); + when(importFormatPreferences.getEncoding()).thenReturn(StandardCharsets.UTF_8); + entryTypesManager = new BibEntryTypesManager(); + } + + private void setUpRepository() throws Exception { + Git git = Git.init() + .setDirectory(tempRepositoryDirectory.toFile()) + .call(); + setUpTestStudyDefinitionFile(); + git.add() + .addFilepattern(".") + .call(); + git.commit() + .setMessage("Initialize") + .call(); + git.close(); + } + + private void setUpTestStudyDefinitionFile() throws Exception { + Path destination = tempRepositoryDirectory.resolve("study.bib"); + URL studyDefinition = this.getClass().getResource("study.bib"); + FileUtil.copyFile(Path.of(studyDefinition.toURI()), destination, false); + } +} diff --git a/src/test/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverterTest.java b/src/test/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverterTest.java new file mode 100644 index 00000000000..629fad93ec4 --- /dev/null +++ b/src/test/java/org/jabref/logic/crawler/LibraryEntryToFetcherConverterTest.java @@ -0,0 +1,69 @@ +package org.jabref.logic.crawler; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.study.Study; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class LibraryEntryToFetcherConverterTest { + ImportFormatPreferences importFormatPreferences; + SavePreferences savePreferences; + BibEntryTypesManager entryTypesManager; + GitHandler gitHandler; + @TempDir + Path tempRepositoryDirectory; + + @BeforeEach + void setUpMocks() { + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); + when(savePreferences.getSaveOrder()).thenReturn(new SaveOrderConfig()); + when(savePreferences.getEncoding()).thenReturn(null); + when(savePreferences.takeMetadataSaveOrderInAccount()).thenReturn(true); + when(importFormatPreferences.getKeywordSeparator()).thenReturn(','); + when(importFormatPreferences.getFieldContentFormatterPreferences()).thenReturn(new FieldContentFormatterPreferences()); + when(importFormatPreferences.isKeywordSyncEnabled()).thenReturn(false); + when(importFormatPreferences.getEncoding()).thenReturn(StandardCharsets.UTF_8); + entryTypesManager = new BibEntryTypesManager(); + gitHandler = mock(GitHandler.class, Answers.RETURNS_DEFAULTS); + } + + @Test + public void getActiveFetcherInstances() throws Exception { + Path studyDefinition = tempRepositoryDirectory.resolve("study.bib"); + copyTestStudyDefinitionFileIntoDirectory(studyDefinition); + + Study study = new StudyRepository(tempRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager).getStudy(); + LibraryEntryToFetcherConverter converter = new LibraryEntryToFetcherConverter(study.getActiveLibraryEntries(), importFormatPreferences); + List result = converter.getActiveFetchers(); + + Assertions.assertEquals(2, result.size()); + Assertions.assertEquals(result.get(0).getName(), "Springer"); + Assertions.assertEquals(result.get(1).getName(), "ArXiv"); + } + + private void copyTestStudyDefinitionFileIntoDirectory(Path destination) throws Exception { + URL studyDefinition = this.getClass().getResource("study.bib"); + FileUtil.copyFile(Path.of(studyDefinition.toURI()), destination, false); + } +} diff --git a/src/test/java/org/jabref/logic/crawler/StudyRepositoryTest.java b/src/test/java/org/jabref/logic/crawler/StudyRepositoryTest.java new file mode 100644 index 00000000000..8a69c6d7a01 --- /dev/null +++ b/src/test/java/org/jabref/logic/crawler/StudyRepositoryTest.java @@ -0,0 +1,312 @@ +package org.jabref.logic.crawler; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; +import org.jabref.logic.citationkeypattern.GlobalCitationKeyPattern; +import org.jabref.logic.crawler.git.GitHandler; +import org.jabref.logic.database.DatabaseMerger; +import org.jabref.logic.exporter.SavePreferences; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.metadata.SaveOrderConfig; +import org.jabref.model.study.FetchResult; +import org.jabref.model.study.QueryResult; +import org.jabref.model.study.Study; +import org.jabref.model.study.StudyMetaDataField; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import static org.jabref.logic.citationkeypattern.CitationKeyGenerator.DEFAULT_UNWANTED_CHARACTERS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class StudyRepositoryTest { + private static final String NON_EXISTING_DIRECTORY = "nonExistingTestRepositoryDirectory"; + CitationKeyPatternPreferences citationKeyPatternPreferences; + ImportFormatPreferences importFormatPreferences; + SavePreferences savePreferences; + BibEntryTypesManager entryTypesManager; + @TempDir + Path tempRepositoryDirectory; + StudyRepository studyRepository; + GitHandler gitHandler = mock(GitHandler.class, Answers.RETURNS_DEFAULTS); + + /** + * Set up mocks + */ + @BeforeEach + public void setUpMocks() { + savePreferences = mock(SavePreferences.class, Answers.RETURNS_DEEP_STUBS); + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + citationKeyPatternPreferences = new CitationKeyPatternPreferences( + false, + false, + false, + CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A, + "", + "", + DEFAULT_UNWANTED_CHARACTERS, + GlobalCitationKeyPattern.fromPattern("[auth][year]"), + ','); + when(savePreferences.getSaveOrder()).thenReturn(new SaveOrderConfig()); + when(savePreferences.getEncoding()).thenReturn(null); + when(savePreferences.takeMetadataSaveOrderInAccount()).thenReturn(true); + when(importFormatPreferences.getKeywordSeparator()).thenReturn(','); + when(importFormatPreferences.getFieldContentFormatterPreferences()).thenReturn(new FieldContentFormatterPreferences()); + when(importFormatPreferences.isKeywordSyncEnabled()).thenReturn(false); + when(importFormatPreferences.getEncoding()).thenReturn(StandardCharsets.UTF_8); + entryTypesManager = new BibEntryTypesManager(); + } + + @Test + void providePathToNonExistentRepositoryThrowsException() { + Path nonExistingRepositoryDirectory = tempRepositoryDirectory.resolve(NON_EXISTING_DIRECTORY); + + assertThrows(IOException.class, () -> new StudyRepository(nonExistingRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager)); + } + + @Test + void providePathToExistentRepositoryWithOutStudyDefinitionFileThrowsException() { + assertThrows(IOException.class, () -> new StudyRepository(tempRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager)); + } + + /** + * Tests whether the StudyRepository correctly imports the study file. + */ + @Test + void studyFileCorrectlyImported() throws Exception { + setUpTestStudyDefinitionFile(); + List expectedSearchterms = List.of("Quantum", "Cloud Computing", "TestSearchQuery3"); + List expectedActiveFetchersByName = List.of("Springer", "ArXiv"); + + Study study = new StudyRepository(tempRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager).getStudy(); + + assertEquals(expectedSearchterms, study.getSearchQueryStrings()); + assertEquals("TestStudyName", study.getStudyMetaDataField(StudyMetaDataField.STUDY_NAME).get()); + assertEquals("Jab Ref", study.getStudyMetaDataField(StudyMetaDataField.STUDY_AUTHORS).get()); + assertEquals("Question1; Question2", study.getStudyMetaDataField(StudyMetaDataField.STUDY_RESEARCH_QUESTIONS).get()); + assertEquals(expectedActiveFetchersByName, study.getActiveLibraryEntries() + .stream() + .filter(bibEntry -> bibEntry.getType().getName().equals("library")) + .map(bibEntry -> bibEntry.getField(new UnknownField("name")).orElse("")) + .collect(Collectors.toList()) + ); + } + + /** + * Tests whether the file structure of the repository is created correctly from the study definitions file. + */ + @Test + void repositoryStructureCorrectlyCreated() throws Exception { + // When repository is instantiated the directory structure is created + getTestStudyRepository(); + + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "ArXiv.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "Springer.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "Springer.bib"))); + assertTrue(Files.exists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "Springer.bib"))); + assertTrue(Files.notExists(Path.of(tempRepositoryDirectory.toString(), "1 - Quantum", "IEEEXplore.bib"))); + assertTrue(Files.notExists(Path.of(tempRepositoryDirectory.toString(), "2 - Cloud Computing", "IEEEXplore.bib"))); + assertTrue(Files.notExists(Path.of(tempRepositoryDirectory.toString(), "3 - TestSearchQuery3", "IEEEXplore.bib"))); + } + + /** + * This tests whether the repository returns the stored bib entries correctly. + */ + @Test + void bibEntriesCorrectlyStored() throws Exception { + StudyRepository repository = getTestStudyRepository(); + setUpTestResultFile(); + List result = repository.getFetcherResultEntries("Quantum", "ArXiv").getEntries(); + assertEquals(getArXivQuantumMockResults(), result); + } + + @Test + void fetcherResultsPersistedCorrectly() throws Exception { + List mockResults = getMockResults(); + + getTestStudyRepository().persist(mockResults); + + assertEquals(getArXivQuantumMockResults(), getTestStudyRepository().getFetcherResultEntries("Quantum", "ArXiv").getEntries()); + assertEquals(getSpringerQuantumMockResults(), getTestStudyRepository().getFetcherResultEntries("Quantum", "Springer").getEntries()); + assertEquals(getSpringerCloudComputingMockResults(), getTestStudyRepository().getFetcherResultEntries("Cloud Computing", "Springer").getEntries()); + } + + @Test + void mergedResultsPersistedCorrectly() throws Exception { + List mockResults = getMockResults(); + List expected = new ArrayList<>(); + expected.addAll(getArXivQuantumMockResults()); + expected.add(getSpringerQuantumMockResults().get(1)); + expected.add(getSpringerQuantumMockResults().get(2)); + + getTestStudyRepository().persist(mockResults); + + // All Springer results are duplicates for "Quantum" + assertEquals(expected, getTestStudyRepository().getQueryResultEntries("Quantum").getEntries()); + assertEquals(getSpringerCloudComputingMockResults(), getTestStudyRepository().getQueryResultEntries("Cloud Computing").getEntries()); + } + + @Test + void setsLastSearchDatePersistedCorrectly() throws Exception { + List mockResults = getMockResults(); + + getTestStudyRepository().persist(mockResults); + + assertEquals(LocalDate.now().toString(), getTestStudyRepository().getStudy().getStudyMetaDataField(StudyMetaDataField.STUDY_LAST_SEARCH).get()); + } + + @Test + void studyResultsPersistedCorrectly() throws Exception { + List mockResults = getMockResults(); + + getTestStudyRepository().persist(mockResults); + + assertEquals(new HashSet<>(getNonDuplicateBibEntryResult().getEntries()), new HashSet<>(getTestStudyRepository().getStudyResultEntries().getEntries())); + } + + private StudyRepository getTestStudyRepository() throws Exception { + if (Objects.isNull(studyRepository)) { + setUpTestStudyDefinitionFile(); + studyRepository = new StudyRepository(tempRepositoryDirectory, gitHandler, importFormatPreferences, new DummyFileUpdateMonitor(), savePreferences, entryTypesManager); + } + return studyRepository; + } + + /** + * Copies the study definition file into the test repository + */ + private void setUpTestStudyDefinitionFile() throws Exception { + Path destination = tempRepositoryDirectory.resolve("study.bib"); + URL studyDefinition = this.getClass().getResource("study.bib"); + FileUtil.copyFile(Path.of(studyDefinition.toURI()), destination, false); + } + + /** + * This overwrites the existing result file in the repository with a result file containing multiple BibEntries. + * The repository has to exist before this method is called. + */ + private void setUpTestResultFile() throws Exception { + Path queryDirectory = Path.of(tempRepositoryDirectory.toString(), "1 - Quantum"); + Path resultFileLocation = Path.of(queryDirectory.toString(), "ArXiv" + ".bib"); + URL resultFile = this.getClass().getResource("ArXivQuantumMock.bib"); + FileUtil.copyFile(Path.of(resultFile.toURI()), resultFileLocation, true); + resultFileLocation = Path.of(queryDirectory.toString(), "Springer" + ".bib"); + resultFile = this.getClass().getResource("SpringerQuantumMock.bib"); + FileUtil.copyFile(Path.of(resultFile.toURI()), resultFileLocation, true); + } + + private BibDatabase getNonDuplicateBibEntryResult() { + BibDatabase mockResults = new BibDatabase(getSpringerCloudComputingMockResults()); + DatabaseMerger merger = new DatabaseMerger(); + merger.merge(mockResults, new BibDatabase(getSpringerQuantumMockResults())); + merger.merge(mockResults, new BibDatabase(getArXivQuantumMockResults())); + return mockResults; + } + + private List getMockResults() { + QueryResult resultQuantum = + new QueryResult("Quantum", List.of( + new FetchResult("ArXiv", new BibDatabase(stripCitationKeys(getArXivQuantumMockResults()))), + new FetchResult("Springer", new BibDatabase(stripCitationKeys(getSpringerQuantumMockResults()))))); + QueryResult resultCloudComputing = new QueryResult("Cloud Computing", List.of(new FetchResult("Springer", new BibDatabase(getSpringerCloudComputingMockResults())))); + return List.of(resultQuantum, resultCloudComputing); + } + + /** + * Strips the citation key from fetched entries as these normally do not have a citation key + */ + private List stripCitationKeys(List entries) { + entries.forEach(bibEntry -> bibEntry.setCitationKey("")); + return entries; + } + + private List getArXivQuantumMockResults() { + BibEntry entry1 = new BibEntry() + .withCitationKey("Blaha") + .withField(StandardField.AUTHOR, "Stephen Blaha") + .withField(StandardField.TITLE, "Quantum Computers and Quantum Computer Languages: Quantum Assembly Language and Quantum C Language"); + entry1.setType(StandardEntryType.Article); + BibEntry entry2 = new BibEntry() + .withCitationKey("Kaye") + .withField(StandardField.AUTHOR, "Phillip Kaye and Michele Mosca") + .withField(StandardField.TITLE, "Quantum Networks for Generating Arbitrary Quantum States"); + entry2.setType(StandardEntryType.Article); + BibEntry entry3 = new BibEntry() + .withCitationKey("Watrous") + .withField(StandardField.AUTHOR, "John Watrous") + .withField(StandardField.TITLE, "Quantum Computational Complexity"); + entry3.setType(StandardEntryType.Article); + + return List.of(entry1, entry2, entry3); + } + + private List getSpringerQuantumMockResults() { + // This is a duplicate of entry 1 of ArXiv + BibEntry entry1 = new BibEntry() + .withCitationKey("Blaha") + .withField(StandardField.AUTHOR, "Stephen Blaha") + .withField(StandardField.TITLE, "Quantum Computers and Quantum Computer Languages: Quantum Assembly Language and Quantum C Language"); + entry1.setType(StandardEntryType.Article); + BibEntry entry2 = new BibEntry() + .withCitationKey("Kroeger") + .withField(StandardField.AUTHOR, "H. Kröger") + .withField(StandardField.TITLE, "Nonlinear Dynamics In Quantum Physics -- Quantum Chaos and Quantum Instantons"); + entry2.setType(StandardEntryType.Article); + BibEntry entry3 = new BibEntry() + .withField(StandardField.AUTHOR, "Zieliński, Cezary") + .withField(StandardField.TITLE, "Automatic Control, Robotics, and Information Processing"); + entry3.setType(StandardEntryType.Article); + + CitationKeyGenerator citationKeyGenerator = new CitationKeyGenerator(new BibDatabaseContext(), citationKeyPatternPreferences); + citationKeyGenerator.generateAndSetKey(entry3); + + return List.of(entry1, entry2, entry3); + } + + private List getSpringerCloudComputingMockResults() { + BibEntry entry1 = new BibEntry() + .withCitationKey("Gritzalis") + .withField(StandardField.AUTHOR, "Gritzalis, Dimitris and Stergiopoulos, George and Vasilellis, Efstratios and Anagnostopoulou, Argiro") + .withField(StandardField.TITLE, "Readiness Exercises: Are Risk Assessment Methodologies Ready for the Cloud?"); + entry1.setType(StandardEntryType.Article); + BibEntry entry2 = new BibEntry() + .withCitationKey("Rangras") + .withField(StandardField.AUTHOR, "Rangras, Jimit and Bhavsar, Sejal") + .withField(StandardField.TITLE, "Design of Framework for Disaster Recovery in Cloud Computing"); + entry2.setType(StandardEntryType.Article); + return List.of(entry1, entry2); + } +} diff --git a/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java index c8d51bd1fee..2124b8f73cc 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java @@ -40,12 +40,32 @@ public class JstorFetcherTest implements SearchBasedFetcherCapabilityTest { .withField(StandardField.URL, "http://www.jstor.org/stable/90002164") .withField(StandardField.YEAR, "2017"); + private final BibEntry doiEntry = new BibEntry(StandardEntryType.Article) + .withCitationKey("10.1086/501484") + .withField(StandardField.AUTHOR, "Johnmarshall Reeve") + .withField(StandardField.TITLE, "Teachers as Facilitators: What Autonomy‐Supportive Teachers Do and Why Their Students Benefit") + .withField(StandardField.ISSN, "{00135984, 15548279") + .withField(StandardField.JOURNAL, "The Elementary School Journal") + .withField(StandardField.ABSTRACT, "Abstract Students are sometimes proactive and engaged in classroom learning activities, but they are also sometimes only reactive and passive. Recognizing this, in this article I argue that students’ classroom engagement depends, in part, on the supportive quality of the classroom climate in which they learn. According to the dialectical framework within self‐determination theory, students possess inner motivational resources that classroom conditions can support or frustrate. When teachers find ways to nurture these inner resources, they adopt an autonomy‐supportive motivating style. After articulating what autonomy‐supportive teachers say and do during instruction, I discuss 3 points: teachers can learn how to be more autonomy supportive toward students; teachers most engage students when they offer high levels of both autonomy support and structure; and an autonomy‐supportive motivating style is an important element to a high‐quality teacher‐student relationship.") + .withField(StandardField.PUBLISHER, "The University of Chicago Press") + .withField(StandardField.NUMBER, "3") + .withField(StandardField.PAGES, "225--236") + .withField(StandardField.VOLUME, "106") + .withField(StandardField.URL, "http://www.jstor.org/stable/10.1086/501484") + .withField(StandardField.YEAR, "2006"); + @Test void searchByTitle() throws Exception { List entries = fetcher.performSearch("title: \"Test Anxiety Analysis of Chinese College Students in Computer-based Spoken English Test\""); assertEquals(Collections.singletonList(bibEntry), entries); } + @Test + void searchById() throws FetcherException { + assertEquals(Optional.of(bibEntry), fetcher.performSearchById("90002164")); + assertEquals(Optional.of(doiEntry), fetcher.performSearchById("https://www.jstor.org/stable/10.1086/501484?seq=1")); + } + @Test void fetchPDF() throws IOException, FetcherException { Optional url = fetcher.findFullText(bibEntry); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java b/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java index 73afa6d63c9..c31bd348b0c 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/SearchBasedFetcherCapabilityTest.java @@ -21,7 +21,7 @@ /** * Defines the set of capability tests that each tests a given search capability, e.g. author based search. * The idea is to code the capabilities of a fetcher into Java code. - * This way, a) the capbilities of a fetcher are checked automatically (because they can change from time-to-time by the provider) + * This way, a) the capabilities of a fetcher are checked automatically (because they can change from time-to-time by the provider) * and b) the queries sent to the fetchers can be debugged directly without a route through to some fetcher code. */ interface SearchBasedFetcherCapabilityTest { diff --git a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java index 3d9f5c94314..ad7b0f645ad 100644 --- a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java +++ b/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java @@ -6,7 +6,8 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test case for ContainBasedSearchRule. @@ -23,32 +24,31 @@ public void testBasicSearchParsing() { String query = "marine 2001 shields"; - assertEquals(false, bsCaseSensitive.applyRule(query, be)); - assertEquals(true, bsCaseInsensitive.applyRule(query, be)); - assertEquals(false, bsCaseSensitiveRegexp.applyRule(query, be)); - assertEquals(false, bsCaseInsensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertTrue(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be)); query = "\"marine larviculture\""; - assertEquals(false, bsCaseSensitive.applyRule(query, be)); - assertEquals(false, bsCaseInsensitive.applyRule(query, be)); - assertEquals(false, bsCaseSensitiveRegexp.applyRule(query, be)); - assertEquals(false, bsCaseInsensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertFalse(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be)); query = "marine [A-Za-z]* larviculture"; - assertEquals(false, bsCaseSensitive.applyRule(query, be)); - assertEquals(false, bsCaseInsensitive.applyRule(query, be)); - assertEquals(false, bsCaseSensitiveRegexp.applyRule(query, be)); - assertEquals(true, bsCaseInsensitiveRegexp.applyRule(query, be)); + assertFalse(bsCaseSensitive.applyRule(query, be)); + assertFalse(bsCaseInsensitive.applyRule(query, be)); + assertFalse(bsCaseSensitiveRegexp.applyRule(query, be)); + assertTrue(bsCaseInsensitiveRegexp.applyRule(query, be)); } public BibEntry makeBibtexEntry() { - BibEntry e = new BibEntry(StandardEntryType.InCollection); - e.setField(StandardField.TITLE, "Marine finfish larviculture in Europe"); - e.setCitationKey("shields01"); - e.setField(StandardField.YEAR, "2001"); - e.setField(StandardField.AUTHOR, "Kevin Shields"); - return e; + return new BibEntry(StandardEntryType.InCollection) + .withCitationKey("shields01") + .withField(StandardField.TITLE, "Marine finfish larviculture in Europe") + .withField(StandardField.YEAR, "2001") + .withField(StandardField.AUTHOR, "Kevin Shields"); } } diff --git a/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java new file mode 100644 index 00000000000..28f045b07fb --- /dev/null +++ b/src/test/java/org/jabref/model/search/rules/GrammarBasedSearchRuleTest.java @@ -0,0 +1,42 @@ +package org.jabref.model.search.rules; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test case for GrammarBasedSearchRuleTest. + */ +public class GrammarBasedSearchRuleTest { + + @Test + void applyRuleMatchesSingleTermWithRegex() { + GrammarBasedSearchRule searchRule = new GrammarBasedSearchRule(true, true); + + String query = "M[a-z]+e"; + assertTrue(searchRule.validateSearchStrings(query)); + assertTrue(searchRule.applyRule(query, makeBibtexEntry())); + } + + @Test + void applyRuleDoesNotMatchSingleTermWithRegex() { + GrammarBasedSearchRule searchRule = new GrammarBasedSearchRule(true, true); + + String query = "M[0-9]+e"; + assertTrue(searchRule.validateSearchStrings(query)); + assertFalse(searchRule.applyRule(query, makeBibtexEntry())); + } + + public BibEntry makeBibtexEntry() { + return new BibEntry(StandardEntryType.InCollection) + .withCitationKey("shields01") + .withField(StandardField.TITLE, "Marine finfish larviculture in Europe") + .withField(StandardField.YEAR, "2001") + .withField(StandardField.AUTHOR, "Kevin Shields"); + } +} diff --git a/src/test/java/org/jabref/model/study/StudyTest.java b/src/test/java/org/jabref/model/study/StudyTest.java new file mode 100644 index 00000000000..9ab34fcd55e --- /dev/null +++ b/src/test/java/org/jabref/model/study/StudyTest.java @@ -0,0 +1,94 @@ +package org.jabref.model.study; + +import java.time.LocalDate; +import java.util.List; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.entry.types.SystematicLiteratureReviewStudyEntryType; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StudyTest { + Study testStudy; + + @BeforeEach + public void setUpTestStudy() { + BibEntry studyEntry = new BibEntry() + .withField(new UnknownField("name"), "TestStudyName") + .withField(StandardField.AUTHOR, "Jab Ref") + .withField(new UnknownField("researchQuestions"), "Question1; Question2") + .withField(new UnknownField("gitRepositoryURL"), "https://github.com/eclipse/jgit.git"); + studyEntry.setType(SystematicLiteratureReviewStudyEntryType.STUDY_ENTRY); + + // Create three SearchTerm entries. + BibEntry searchQuery1 = new BibEntry() + .withField(new UnknownField("query"), "TestSearchQuery1"); + searchQuery1.setType(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY); + searchQuery1.setCitationKey("query1"); + + BibEntry searchQuery2 = new BibEntry() + .withField(new UnknownField("query"), "TestSearchQuery2"); + searchQuery2.setType(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY); + searchQuery2.setCitationKey("query2"); + + BibEntry searchQuery3 = new BibEntry() + .withField(new UnknownField("query"), "TestSearchQuery3"); + searchQuery3.setType(SystematicLiteratureReviewStudyEntryType.SEARCH_QUERY_ENTRY); + searchQuery3.setCitationKey("query3"); + + // Create two Library entries + BibEntry library1 = new BibEntry() + .withField(new UnknownField("name"), "acm") + .withField(new UnknownField("enabled"), "false") + .withField(new UnknownField("comment"), "disabled, because no good results"); + library1.setType(SystematicLiteratureReviewStudyEntryType.LIBRARY_ENTRY); + library1.setCitationKey("library1"); + + BibEntry library2 = new BibEntry() + .withField(new UnknownField("name"), "arxiv") + .withField(new UnknownField("enabled"), "true") + .withField(new UnknownField("Comment"), ""); + library2.setType(SystematicLiteratureReviewStudyEntryType.LIBRARY_ENTRY); + library2.setCitationKey("library2"); + + testStudy = new Study(studyEntry, List.of(searchQuery1, searchQuery2, searchQuery3), List.of(library1, library2)); + } + + @Test + void getSearchTermsAsStrings() { + List expectedSearchTerms = List.of("TestSearchQuery1", "TestSearchQuery2", "TestSearchQuery3"); + assertEquals(expectedSearchTerms, testStudy.getSearchQueryStrings()); + } + + @Test + void setLastSearchTime() { + LocalDate date = LocalDate.now(); + testStudy.setLastSearchDate(date); + assertEquals(date.toString(), testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_LAST_SEARCH).get()); + } + + @Test + void getStudyName() { + assertEquals("TestStudyName", testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_NAME).get()); + } + + @Test + void getStudyAuthor() { + assertEquals("Jab Ref", testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_AUTHORS).get()); + } + + @Test + void getResearchQuestions() { + assertEquals("Question1; Question2", testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_RESEARCH_QUESTIONS).get()); + } + + @Test + void getGitRepositoryURL() { + assertEquals("https://github.com/eclipse/jgit.git", testStudy.getStudyMetaDataField(StudyMetaDataField.STUDY_GIT_REPOSITORY).get()); + } +} diff --git a/src/test/resources/org/jabref/logic/crawler/ArXivQuantumMock.bib b/src/test/resources/org/jabref/logic/crawler/ArXivQuantumMock.bib new file mode 100644 index 00000000000..85df0f1060b --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/ArXivQuantumMock.bib @@ -0,0 +1,15 @@ + +@Article{Blaha, + author = {Stephen Blaha}, + title = {Quantum Computers and Quantum Computer Languages: Quantum Assembly Language and Quantum C Language}, +} + +@Article{Kaye, + author = {Phillip Kaye and Michele Mosca}, + title = {Quantum Networks for Generating Arbitrary Quantum States}, +} + +@Article{Watrous, + author = {John Watrous}, + title = {Quantum Computational Complexity}, +} diff --git a/src/test/resources/org/jabref/logic/crawler/SpringerCloud ComputingMock.bib b/src/test/resources/org/jabref/logic/crawler/SpringerCloud ComputingMock.bib new file mode 100644 index 00000000000..627166213fa --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/SpringerCloud ComputingMock.bib @@ -0,0 +1,9 @@ +@InCollection{Gritzalis, + author = {Gritzalis, Dimitris and Stergiopoulos, George and Vasilellis, Efstratios and Anagnostopoulou, Argiro}, + title = {Readiness Exercises: Are Risk Assessment Methodologies Ready for the Cloud?}, +} + +@InCollection{Rangras, + author = {Rangras, Jimit and Bhavsar, Sejal}, + title = {Design of Framework for Disaster Recovery in Cloud Computing}, +} diff --git a/src/test/resources/org/jabref/logic/crawler/SpringerQuantumMock.bib b/src/test/resources/org/jabref/logic/crawler/SpringerQuantumMock.bib new file mode 100644 index 00000000000..3cfa2f88487 --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/SpringerQuantumMock.bib @@ -0,0 +1,9 @@ +@Article{Zielinski, + author = {Zieliński, Cezary}, + title = {Quantum Computers and Quantum Computer Languages: Quantum Assembly Language and Quantum C Language}, +} + +@Article{Kaye, + author = {H. Kröger}, + title = {Quantum Networks for Generating Arbitrary Quantum States}, +} diff --git a/src/test/resources/org/jabref/logic/crawler/study.bib b/src/test/resources/org/jabref/logic/crawler/study.bib new file mode 100644 index 00000000000..3f9809a82e5 --- /dev/null +++ b/src/test/resources/org/jabref/logic/crawler/study.bib @@ -0,0 +1,37 @@ +% Encoding: UTF-8 + +@Study{v10, + name={TestStudyName}, + author={Jab Ref}, + researchQuestions={Question1; Question2}, +} + +@SearchQuery{query1, + query={Quantum}, +} + +@SearchQuery{query2, + query={Cloud Computing}, +} + +@SearchQuery{query3, + query={TestSearchQuery3}, +} + +@Library{library1, + name = {Springer}, + enabled = {true}, + comment = {}, +} + +@Library{library2, + name = {ArXiv}, + enabled = {true}, + comment = {}, +} + +@Library{library3, + name = {IEEEXplore}, + enabled = {false}, + comment = {}, +}