diff --git a/.gitignore b/.gitignore index 496ee2ca..e207db94 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.DS_Store \ No newline at end of file +.DS_Store +/classes diff --git a/.idea/sq_2024.iml b/.idea/sq_2024.iml index d6ebd480..84a7d115 100644 --- a/.idea/sq_2024.iml +++ b/.idea/sq_2024.iml @@ -2,7 +2,9 @@ - + + + diff --git a/README.md b/README.md deleted file mode 100644 index 8c9afb3d..00000000 --- a/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# Software Quality - - - -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin https://gitlab.uni-marburg.de/tadjikys/software-quality.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://gitlab.uni-marburg.de/tadjikys/software-quality/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README - -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. diff --git a/jabref/.devcontainer/devcontainer.json b/jabref/.devcontainer/devcontainer.json new file mode 100644 index 00000000..8c9b29c2 --- /dev/null +++ b/jabref/.devcontainer/devcontainer.json @@ -0,0 +1,39 @@ +{ + "name": "Java", + "image": "mcr.microsoft.com/devcontainers/base:debian", + "customizations": { + "vscode": { + "extensions": [ + "vscjava.vscode-java-pack", + "vscjava.vscode-gradle", + "shengchen.vscode-checkstyle", + "DavidAnson.vscode-markdownlint" + ] + } + }, + + // Source code generation needs to be done before hand-over to VS Code. + // Otherwise, the Java extension will go mad. + "onCreateCommand": "./gradlew testClasses --console=plain --no-daemon", + + // Forward the vncPort and noVNC port. + // They are provided by desktop-lite: + // https://github.com/devcontainers/features/tree/main/src/desktop-lite#options + "forwardPorts": [5901, 6080], + + // Need to connect as root otherwise we run into issues with gradle. + // default option is "vscode". More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "root", + "features": { + // Adds a lightweight desktop that can be accessed using a VNC viewer or the web + "ghcr.io/devcontainers/features/desktop-lite:1": {}, + + // Install java. + // See https://github.com/devcontainers/features/tree/main/src/java#options for details. + "ghcr.io/devcontainers/features/java:1": { + "version": "23.0.1-tem", + "installGradle": false, + "jdkDistro": "Temurin" + } + } +} diff --git a/jabref/.editorconfig b/jabref/.editorconfig new file mode 100644 index 00000000..1f812bb3 --- /dev/null +++ b/jabref/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{fxml,java,properties,sh}] +end_of_line = lf + +[*.md] +trim_trailing_whitespace = false diff --git a/jabref/.git-blame-ignore-revs b/jabref/.git-blame-ignore-revs new file mode 100644 index 00000000..efa199bd --- /dev/null +++ b/jabref/.git-blame-ignore-revs @@ -0,0 +1,28 @@ +# Automatic code reformatting +c5bcad738fe6e8dfcb62442a426c2778241515a1 + +# net.sf.jabref -> org.jabref +b2ad6eb279f5def38fa21be12cb5dd4545c1ba1a +dd385f4ad6dc9251bfdb1c0b75ded402f680b32a +a6e80311c6376c973b5860658f6e4ace7a3fd1f4 +648a4703e80ac0dbe1ff044f354bc0e332e4064d +ac08c6a2c55e1600bf5072c0ea2e6aac920b915d +ec863166cf4f38a1a6bcc80a607fd33299b815aa +8c61577e4377746fc064db2a136bb68968cd4055 +0ef3228f0c1334ff203d4ea5e698e3ad88cb7089 +ce939b4ef19ca856e7ecac08275cf1ca764207b6 +7f1a8069d03737c202bcb7ce352b3755c6a36f5f +dbd0cfbc8177ada414e89da038cd014657d48ed3 +bc7ea00c8cc3ec9ec8b1a799b0dd96513bc51404 +9a5cff44aa1e0d4b737296a30b97f7f384c8b885 +bf81b595a77f0f7f254872be6f05a063c44528d8 +277b40c9e79e0158d272de33e24fa7fc06af91bf +662dd326d212ecfd336a00214e969145ec501c5a +33f040cfbb16111ada117f858e98d606a6bee4fd +29fe730f64eeb62ff9de10fcb460a63297e24be6 + +# This commit should not exist +185d7345946c29a2a4e2726c912be0c4db4810b9 +# Resulted in this problematic merge commits +7e1645978b3028df5e65af19f0f819ddfd0f24aa +a31f396765492ac12eaab228e33eb9d22487403b diff --git a/jabref/.gitattributes b/jabref/.gitattributes new file mode 100644 index 00000000..23707952 --- /dev/null +++ b/jabref/.gitattributes @@ -0,0 +1,14 @@ +# unix line endings at unix files +gradlew text eol=lf +*.sh text eol=lf + +# windows line endings at windows files +*.bat text eol=crlf + +# ensure that line endings of *.bib, *.fxml, *.java, *.properties are normalized to LF +*.bib text eol=lf +*.fxml text eol=lf +*.java text eol=lf +*.properties text eol=lf + +CHANGELOG.md merge=union diff --git a/jabref/.github/FUNDING.yml b/jabref/.github/FUNDING.yml new file mode 100644 index 00000000..44f9af71 --- /dev/null +++ b/jabref/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: JabRef +patreon: # Patreon user account +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: JabRef +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: https://github.com/JabRef/jabref/wiki/Donations # Replace with a single custom sponsorship URL diff --git a/jabref/.github/ISSUE_TEMPLATE/bug_report.yml b/jabref/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..a28be65b --- /dev/null +++ b/jabref/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,81 @@ +name: "Bug report" +description: Create a report to help us improve +body: + - type: markdown + attributes: + value: + "Please use the GitHub issue tracker only for bug reports. + Feature requests, questions and general feedback is now handled at http://discourse.jabref.org. + Please make sure you looked into our [list of existing issues](https://github.com/jabref/JabRef/issues) before creating a new issue to avoid duplicates!" + + - type: dropdown + attributes: + label: JabRef version + options: + - "5.15 (latest release)" + - Latest development branch build (please note build date below) + - Other (please describe below) + description: The version as shown in the about dialog. + validations: + required: true + + - type: dropdown + attributes: + label: Operating system + multiple: false + options: + - Windows + - GNU / Linux + - macOS + - Other (please describe below) + validations: + required: true + + - type: input + attributes: + label: Details on version and operating system + description: OS Version, distribution, desktop environment, older JabRef version etc. + placeholder: Ubuntu 21.04 with Plasma 5.22 / Windows 10 21H1 / macOS 10.14 + validations: + required: false + + - type: checkboxes + attributes: + label: Checked with the latest development build (copy version output from About dialog) + description: | + Please always test if the bug is still reproducible in the latest development version. We are constantly improving JabRef and some bugs may already be fixed. If you already use a development version, ensure that you use the latest one. + You can download the latest development build at: https://builds.jabref.org/main/ . **Please make a backup of your library before you try out this version.** + options: + - label: I made a backup of my libraries before testing the latest development version. + required: true + - label: I have tested the latest development version and the problem persists + required: true + + - type: textarea + attributes: + label: Steps to reproduce the behaviour + description: A clear and concise description of what the bug is and how to make it occur. + value: | + 1. ... + 2. ... + 3. ... + validations: + required: true + + - type: textarea + attributes: + label: Appendix + description: "If applicable: An excerpt of the bibliography file, a screenshot, an excerpt of log (available in the error console) etc." + value: | + ... +
+ + Log File + + ``` + Paste an excerpt of your log file here + ``` + +
+ validations: + required: false diff --git a/jabref/.github/ISSUE_TEMPLATE/config.yml b/jabref/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..9850f404 --- /dev/null +++ b/jabref/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Help + url: https://discourse.jabref.org/c/help/ + about: Questions and requests for help are handled at https://discourse.jabref.org + - name: Feature requests + url: https://discourse.jabref.org/c/features/ + about: If you are missing an important feature? Let us know! diff --git a/jabref/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md b/jabref/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md new file mode 100644 index 00000000..1bcbdb55 --- /dev/null +++ b/jabref/.github/ISSUE_TEMPLATE/suggestion-for-improvement.md @@ -0,0 +1,23 @@ +--- +name: Suggestion for improvement +about: Suggest an enhancement +title: '' +labels: '' +assignees: '' + +--- + + + +**Is your suggestion for improvement related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/jabref/.github/PULL_REQUEST_TEMPLATE.md b/jabref/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..a01fad86 --- /dev/null +++ b/jabref/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +Describe the changes you have made here: what, why, ... +Link the issue that will be closed, e.g., "Closes #333". If your PR closes a koppor issue, link it using its URL, e.g., "Closes https://github.com/koppor/jabref/issues/47". + +### Mandatory checks + + + +- [x] I own the copyright of the code submitted and I licence it under the [MIT license](https://github.com/JabRef/jabref/blob/main/LICENSE) +- [ ] Change in `CHANGELOG.md` described in a way that is understandable for the average user (if change is visible to the user) +- [ ] Tests created for changes (if applicable) +- [ ] Manually tested changed features in running JabRef (always required) +- [ ] Screenshots added in PR description (for UI changes) +- [ ] [Checked developer's documentation](https://devdocs.jabref.org/): Is the information available and up to date? If not, I outlined it in this pull request. +- [ ] [Checked documentation](https://docs.jabref.org/): Is the information available and up to date? If not, I created an issue at or, even better, I submitted a pull request to the documentation repository. diff --git a/jabref/.github/dependabot.yml b/jabref/.github/dependabot.yml new file mode 100644 index 00000000..1d4ef9ee --- /dev/null +++ b/jabref/.github/dependabot.yml @@ -0,0 +1,31 @@ +version: 2 +updates: + - package-ecosystem: gradle + directory: "/" + schedule: + interval: weekly + labels: + - "dependencies" + ignore: + - dependency-name: com.microsoft.azure:applicationinsights-core + versions: + - ">= 2.5.a" # Blocked by https://github.com/microsoft/ApplicationInsights-Java/issues/1155 + - dependency-name: com.microsoft.azure:applicationinsights-logging-log4j2 + versions: + - ">= 2.5.a" # Blocked by https://github.com/microsoft/ApplicationInsights-Java/issues/1155 + - package-ecosystem: gradle + directory: "buildSrc/" + schedule: + interval: weekly + labels: + - "dependencies" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + labels: + - "dependencies" + - package-ecosystem: "gitsubmodule" + directory: "/" + schedule: + interval: weekly diff --git a/jabref/.github/ghprcomment.yml b/jabref/.github/ghprcomment.yml new file mode 100644 index 00000000..96ed0801 --- /dev/null +++ b/jabref/.github/ghprcomment.yml @@ -0,0 +1,37 @@ +- jobName: Checkstyle + message: | + Your code currently does not meet [JabRef's code guidelines](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.html). + We use [Checkstyle](https://checkstyle.sourceforge.io/) to identify issues. + Please carefully follow [the setup guide for the codestyle](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.html). + Afterwards, please [run checkstyle locally](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.html#run-checkstyle) and fix the issues. +- jobName: OpenRewrite + message: | + Your code currently does not meet JabRef's code guidelines. + We use [OpenRewrite](https://docs.openrewrite.org/) to ensure "modern" Java coding practices. + The issues found can be **automatically fixed**. + Please execute the gradle task *`rewriteRun`*, check the results, commit, and push. + + You can check the detailed error output by navigating to your pull request, selecting the tab "Checks", section "Tests" (on the left), subsection "OpenRewrite". +- jobName: Modernizer + message: | + Your code currently does not meet JabRef's code guidelines. + We use [Gradle Modernizer Plugin](https://github.com/andygoossens/gradle-modernizer-plugin#gradle-modernizer-plugin) to ensure "modern" Java coding practices. + Please fix the detected errors, commit, and push. + + You can check the detailed error output by navigating to your pull request, selecting the tab "Checks", section "Tests" (on the left), subsection "Modernizer". +- jobName: Markdown + message: | + You modified Markdown (`*.md`) files and did not meet JabRef's rules for consistently formatted Markdown files. + To ensure consistent styling, we have [markdown-lint](https://github.com/DavidAnson/markdownlint) in place. + [Markdown lint's rules](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#rules) help to keep our Markdown files consistent within this repository and consistent with the Markdown files outside here. + + You can check the detailed error output by navigating to your pull request, selecting the tab "Checks", section "Tests" (on the left), subsection "Markdown". +- jobName: CHANGELOG.md + message: | + While the PR was in progress, a new version of JabRef has been released. + You have to merge `upstream/main` and move your entry in `CHANGELOG.md` up to the section `## [Unreleased]`. +- jobName: 'Unit tests' + message: | + JUnit tests are failing. In the area "Some checks were not successful", locate "Tests / Unit tests (pull_request)" and click on "Details". This brings you to the test output. + + You can then run these tests in IntelliJ to reproduce the failing tests locally. We offer a quick test running howto in the section [Final build system checks](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.html#final-build-system-checks) in our setup guide. diff --git a/jabref/.github/workflows/assign-issue.yml b/jabref/.github/workflows/assign-issue.yml new file mode 100644 index 00000000..07450aec --- /dev/null +++ b/jabref/.github/workflows/assign-issue.yml @@ -0,0 +1,57 @@ +name: Assign Issue + +on: + schedule: + - cron: 4 12 * * * + issue_comment: + types: [created] + workflow_dispatch: + +jobs: + assign: + if: github.repository_owner == 'JabRef' + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Assign the user or unassign stale assignments + id: assign + uses: takanome-dev/assign-issue-action@beta + with: + github_token: '${{ secrets.GITHUB_TOKEN }}' + days_until_unassign: 30 + maintainers: koppor, Siedlerchr, ThiloteE, calixtus, HoussemNasri + assigned_comment: | + 👋 Hey @{{ handle }}, thank you for your interest in this issue! 🎉 + + We're excited to have you on board. Start by exploring our [Contributing](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md) guidelines, and don't forget to check out our [workspace setup guidelines](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace) to get started smoothly. + + In case you encounter failing tests during development, please check our [developer FAQs](https://devdocs.jabref.org/code-howtos/faq.html)! + + Having any questions or issues? Feel free to ask here on GitHub. Need help setting up your local workspace? Join the conversation on [JabRef's Gitter chat](https://gitter.im/JabRef/jabref). And don't hesitate to open a (draft) pull request early on to show the direction it is heading towards. This way, you will receive valuable feedback. + + Happy coding! 🚀 + + ⏳ Please note, you will be automatically unassigned if the issue isn't closed within **{{ total_days }} days** (by **{{ unassigned_date }}**). A maintainer can also add the "**{{ pin_label }}**"" label to prevent automatic unassignment. + - name: Move Issue to "Assigned" Column in "Candidates for University Projects" + if: steps.assign.outputs.assigned == 'yes' + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/3" + target-labels: "📍 Assigned" + target-column: "Assigned" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true + - name: Move Issue to "Assigned" Column in "Good First Issues" + if: steps.assign.outputs.assigned == 'yes' + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/5" + target-labels: "📍 Assigned" + target-column: "Assigned" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true diff --git a/jabref/.github/workflows/automerge.yml b/jabref/.github/workflows/automerge.yml new file mode 100644 index 00000000..856b3f66 --- /dev/null +++ b/jabref/.github/workflows/automerge.yml @@ -0,0 +1,34 @@ +name: Auto Merge +on: [pull_request_target, workflow_dispatch] + +permissions: + contents: write + pull-requests: write + +jobs: + automerge: + runs-on: ubuntu-latest + # Run only if PR is inside JabRef's main repository and created by dependabot or by an update workflow + if: > + (github.repository == 'JabRef/jabref') && + (github.event.pull_request.head.repo.full_name == 'JabRef/jabref') && + ( + (github.actor == 'dependabot[bot]') || + ( + startsWith(github.event.pull_request.title, '[Bot] ') || + startsWith(github.event.pull_request.title, 'Bump ') || + startsWith(github.event.pull_request.title, 'New Crowdin updates') || + startsWith(github.event.pull_request.title, 'Update Gradle Wrapper from') + ) + ) + steps: + - name: Approve PR + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GH_TOKEN_JABREF_MACHINE_PR_APPROVE}} + - name: Merge PR + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER}} diff --git a/jabref/.github/workflows/check-links.yml b/jabref/.github/workflows/check-links.yml new file mode 100644 index 00000000..3006d476 --- /dev/null +++ b/jabref/.github/workflows/check-links.yml @@ -0,0 +1,38 @@ +name: Check external href links in the documentation + +on: + push: + paths: + - '.github/workflows/check-links.yml' + - '.lycheeignore' + - 'lychee.toml' + - '**/*.md' + schedule: + # Run on the first of each month at 9:00 AM (See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07) + - cron: "0 9 1 * *" + workflow_dispatch: + +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + lychee: + if: github.repository_owner == 'JabRef' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + show-progress: 'false' + - name: Restore lychee cache + uses: actions/cache@v4 + with: + path: .lycheecache + key: cache-lychee-${{ github.sha }} + restore-keys: cache-lychee- + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@v2.1.0 + with: + fail: true + args: --accept '200,201,202,203,204,403,429,500' --max-concurrency 1 --cache --no-progress --exclude-all-private './**/*.md' diff --git a/jabref/.github/workflows/cleanup-pr.yml b/jabref/.github/workflows/cleanup-pr.yml new file mode 100644 index 00000000..a4caf50b --- /dev/null +++ b/jabref/.github/workflows/cleanup-pr.yml @@ -0,0 +1,45 @@ +name: Cleanup after PR + +on: + pull_request: + types: [closed] + +jobs: + cleanup: + if: github.repository_owner == 'JabRef' + runs-on: ubuntu-latest + steps: + - name: Cancel deployment run + uses: styfle/cancel-workflow-action@0.12.1 + with: + ignore_sha: true + workflow_id: 9813 # workflow "Deployment" + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Delete folder on builds.jabref.org + if: steps.checksecrets.outputs.secretspresent == 'YES' + uses: appleboy/ssh-action@v1.1.0 + with: + script: rm -rf /var/www/builds.jabref.org/www/pull/${{ github.event.pull_request.number }} || true + host: build-upload.jabref.org + port: 9922 + username: jrrsync + key: ${{ secrets.buildJabRefPrivateKey }} + - name: Update PR comment + if: steps.checksecrets.outputs.secretspresent == 'YES' + uses: thollander/actions-comment-pull-request@v3 + with: + comment-tag: download-link + message: The build for this PR is no longer available. Please visit for the latest build. + mode: upsert diff --git a/jabref/.github/workflows/deployment-arm64.yml b/jabref/.github/workflows/deployment-arm64.yml new file mode 100644 index 00000000..b2d795d3 --- /dev/null +++ b/jabref/.github/workflows/deployment-arm64.yml @@ -0,0 +1,272 @@ +name: Deployment (macOS/ARM64) +on: + push: + branches: + - main + - main-release + paths-ignore: + - 'docs/**' + - 'src/test/**' + - 'README.md' + tags: + - '*' + pull_request: + merge_group: + workflow_dispatch: + inputs: + notarization: + type: boolean + required: false + default: false + +env: + SpringerNatureAPIKey: ${{ secrets.SpringerNatureAPIKey }} + AstrophysicsDataSystemAPIKey: ${{ secrets.AstrophysicsDataSystemAPIKey }} + IEEEAPIKey: ${{ secrets.IEEEAPIKey }} + BiodiversityHeritageApiKey: ${{ secrets.BiodiversityHeritageApiKey}} + OSXCERT: ${{ secrets.OSX_SIGNING_CERT }} + GRADLE_OPTS: -Xmx4g -Dorg.gradle.daemon=false -Dorg.gradle.vfs.watch=false + JAVA_OPTS: -Xmx4g + +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + build: + if: github.repository_owner == 'JabRef' + strategy: + fail-fast: false + matrix: + include: + - os: macos-14 + displayName: macOS (ARM64) + suffix: '_arm64' + runs-on: ${{ matrix.os }} + outputs: + major: ${{ steps.gitversion.outputs.Major }} + minor: ${{ steps.gitversion.outputs.Minor }} + branchname: ${{ steps.gitversion.outputs.branchName }} + name: Create installer and portable version for ${{ matrix.displayName }} + steps: + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Fetch all history for all tags and branches + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: 'true' + show-progress: 'false' + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v3.0.0 + with: + versionSpec: "5.x" + - name: Run GitVersion + id: gitversion + uses: gittools/actions/gitversion/execute@v3.0.0 + - name: Setup JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Clean up keychain + run: | + security delete-keychain signing_temp.keychain ${{runner.temp}}/keychain/notarization.keychain || true + - name: Setup OSX key chain on macOS-arm + if: (steps.checksecrets.outputs.secretspresent == 'YES') + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d + with: + p12-file-base64: ${{ secrets.OSX_SIGNING_CERT }} + p12-password: ${{ secrets.OSX_CERT_PWD }} + keychain-password: jabref + - name: Setup OSX key chain on OSX for app id cert + if: (steps.checksecrets.outputs.secretspresent == 'YES') + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d + with: + p12-file-base64: ${{ secrets.OSX_SIGNING_CERT_APPLICATION }} + p12-password: ${{ secrets.OSX_CERT_PWD }} + create-keychain: false + keychain-password: jabref + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Prepare merged jars and modules dir (macOS) + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" prepareModulesDir + - name: Build dmg (macOS) + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + jpackage \ + --module org.jabref/org.jabref.Launcher \ + --module-path ${{env.JAVA_HOME}}/jmods/:build/jlinkbase/jlinkjars \ + --add-modules org.jabref,org.jabref.merged.module \ + --add-modules jdk.incubator.vector \ + --dest build/distribution \ + --app-content buildres/mac/jabrefHost.py \ + --app-content buildres/mac/native-messaging-host \ + --name JabRef \ + --app-version ${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }} \ + --verbose \ + --mac-sign \ + --vendor "JabRef e.V." \ + --mac-package-identifier JabRef \ + --mac-package-name JabRef \ + --type dmg --mac-signing-key-user-name "JabRef e.V. (6792V39SK3)" \ + --mac-package-signing-prefix org.jabref \ + --mac-entitlements buildres/mac/jabref.entitlements \ + --icon src/main/resources/icons/jabref.icns \ + --resource-dir buildres/mac \ + --file-associations buildres/mac/bibtexAssociations.properties \ + --jlink-options --bind-services \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref.merged.module \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control.skin=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.base/javafx.collections=org.jabref \ + --java-options --add-opens=javafx.base/javafx.collections.transformation=org.jabref \ + --java-options --add-modules=jdk.incubator.vector + - name: Build pkg (macOS) + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + jpackage \ + --module org.jabref/org.jabref.Launcher \ + --module-path ${{env.JAVA_HOME}}/jmods/:build/jlinkbase/jlinkjars \ + --add-modules org.jabref,org.jabref.merged.module \ + --add-modules jdk.incubator.vector \ + --dest build/distribution \ + --app-content buildres/mac/jabrefHost.py \ + --app-content buildres/mac/native-messaging-host \ + --name JabRef \ + --app-version ${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }} \ + --verbose \ + --mac-sign \ + --vendor "JabRef e.V." \ + --mac-package-identifier JabRef \ + --mac-package-name JabRef \ + --type pkg --mac-signing-key-user-name "JabRef e.V. (6792V39SK3)" \ + --mac-package-signing-prefix org.jabref \ + --mac-entitlements buildres/mac/jabref.entitlements \ + --icon src/main/resources/icons/jabref.icns \ + --resource-dir buildres/mac \ + --file-associations buildres/mac/bibtexAssociations.properties \ + --jlink-options --bind-services \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref.merged.module \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.base/javafx.collections=org.jabref \ + --java-options --add-opens=javafx.base/javafx.collections.transformation=org.jabref \ + --java-options --add-modules=jdk.incubator.vector + - name: Rename files with arm64 suffix as well + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + mv build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.dmg build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-arm64.dmg + mv build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}.pkg build/distribution/JabRef-${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-arm64.pkg + - name: Setup rsync (macOS) + if: ${{ (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) && (steps.checksecrets.outputs.secretspresent == 'YES') && ((matrix.os == 'macos-14') && !((startsWith(github.ref, 'refs/tags/') || inputs.notarization == true))) }} + run: brew install rsync + - name: Setup SSH key + if: ${{ (steps.checksecrets.outputs.secretspresent == 'YES') && (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) && ((matrix.os != 'macos-14') || !((startsWith(github.ref, 'refs/tags/') || (inputs.notarization == true)))) }} + run: | + echo "${{ secrets.buildJabRefPrivateKey }}" > sshkey + chmod 600 sshkey + - name: Upload to builds.jabref.org (linux, macOS) + # macOS: Negated condition of "Upload to GitHub workflow artifacts store (macOS)" + # Reason: We either upload the non-notarized files - or notarize the files later (and upload these later) + # needs to be on one line; multi line does not work + if: ${{ (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) && (steps.checksecrets.outputs.secretspresent == 'YES') && ((matrix.os == 'ubuntu-latest') || ((matrix.os == 'macos-14') && !((startsWith(github.ref, 'refs/tags/') || inputs.notarization == true)))) }} + shell: bash + run: | + rsync -rt --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r --itemize-changes --stats --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }} && rsync" -e 'ssh -p 9922 -i sshkey -o StrictHostKeyChecking=no' build/distribution/ jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }}/ || true + - name: Upload to GitHub workflow artifacts store (macOS) + if: (matrix.os == 'macos-14') && (steps.checksecrets.outputs.secretspresent == 'YES') && (startsWith(github.ref, 'refs/tags/') || inputs.notarization == true) + uses: actions/upload-artifact@v4 + with: + # tbn = to-be-notarized + name: JabRef-macOS-arm-tbn + path: build/distribution + compression-level: 0 # no compression + - name: Upload to GitHub workflow artifacts store + if: (steps.checksecrets.outputs.secretspresent != 'YES') + uses: actions/upload-artifact@v4 + with: + # tbn = to-be-notarized + name: JabRef-${{ matrix.os }} + path: build/distribution + compression-level: 0 # no compression + + notarize: # outsourced in a separate job to be able to rerun if this fails for timeouts + name: macOS notarization-arm + runs-on: macos-14 + needs: [build] + if: ${{ startsWith(github.ref, 'refs/tags/') || inputs.notarization == true }} + steps: + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Download from GitHub workflow artifacts store (macOS) + if: (steps.checksecrets.outputs.secretspresent == 'YES') + uses: actions/download-artifact@master + with: + name: JabRef-macOS-arm-tbn + path: build/distribution/ + - name: Notarize dmg + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + xcrun notarytool store-credentials "notarytool-profile" --apple-id "vorstand@jabref.org" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" + xcrun notarytool submit build/distribution/JabRef-${{ needs.build.outputs.major }}.${{ needs.build.outputs.minor }}-arm64.dmg --keychain-profile "notarytool-profile" --wait + xcrun stapler staple build/distribution/JabRef-${{ needs.build.outputs.major }}.${{ needs.build.outputs.minor }}-arm64.dmg + - name: Notarize pkg + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + xcrun notarytool store-credentials "notarytool-profile" --apple-id "vorstand@jabref.org" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" + xcrun notarytool submit build/distribution/JabRef-${{ needs.build.outputs.major }}.${{ needs.build.outputs.minor }}-arm64.pkg --keychain-profile "notarytool-profile" --wait + xcrun stapler staple build/distribution/JabRef-${{ needs.build.outputs.major }}.${{ needs.build.outputs.minor }}-arm64.pkg + - name: Upload to builds.jabref.org + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + echo "${{ secrets.buildJabRefPrivateKey }}" > sshkey + chmod 600 sshkey + rsync -rt --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r --itemize-changes --stats --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ needs.build.outputs.branchname }} && rsync" -e 'ssh -p 9922 -i sshkey -o StrictHostKeyChecking=no' build/distribution/ jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ needs.build.outputs.branchname }}/ diff --git a/jabref/.github/workflows/deployment-jdk-ea.yml b/jabref/.github/workflows/deployment-jdk-ea.yml new file mode 100644 index 00000000..8cdf1e63 --- /dev/null +++ b/jabref/.github/workflows/deployment-jdk-ea.yml @@ -0,0 +1,282 @@ +# This workflow is a clone of "deployment.yml" +# The difference is that this workflow uses JDK early access builds (jdk-ea) to check the build of JabRef +# We separated this from the main workflow as we do not want to check on each PR if the JDK build, but only on main +name: Deployment (JDK early access builds) + +on: + schedule: + - cron: "0 18 * * 1" + pull_request: + paths: + - .github/workflows/deployment-jdk-ea.yml + - build.gradle + workflow_dispatch: + inputs: + notarization: + type: boolean + required: false + default: false + +env: + SpringerNatureAPIKey: ${{ secrets.SpringerNatureAPIKey }} + AstrophysicsDataSystemAPIKey: ${{ secrets.AstrophysicsDataSystemAPIKey }} + IEEEAPIKey: ${{ secrets.IEEEAPIKey }} + BiodiversityHeritageApiKey: ${{ secrets.BiodiversityHeritageApiKey}} + OSXCERT: ${{ secrets.OSX_SIGNING_CERT }} + GRADLE_OPTS: -Xmx4g -Dorg.gradle.vfs.watch=false + JAVA_OPTS: -Xmx4g + +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + build: + if: github.repository_owner == 'JabRef' + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest, buildjet-8vcpu-ubuntu-2204-arm] + jdk: [23] + javafx: [24] + include: + - os: ubuntu-latest + displayName: linux + archivePortable: tar -c -C build/distribution JabRef | pigz --rsyncable > build/distribution/JabRef-portable_linux.tar.gz && rm -R build/distribution/JabRef + - os: windows-latest + displayName: windows + archivePortable: 7z a -r build/distribution/JabRef-portable_windows.zip ./build/distribution/JabRef && rm -R build/distribution/JabRef + - os: buildjet-8vcpu-ubuntu-2204-arm + displayName: "linux-arm" + archivePortable: "tar -c -C build/distribution JabRef | pigz --rsyncable > build/distribution/JabRef-portable_linux-arm64.tar.gz && rm -R build/distribution/JabRef" + - os: macos-latest + displayName: macOS + archivePortable: "brew install pigz && tar -c -C build/distribution JabRef.app | pigz --rsyncable > build/distribution/JabRef-portable_macos.tar.gz && rm -R build/distribution/JabRef.app" + runs-on: ${{ matrix.os }} + outputs: + major: ${{ steps.gitversion.outputs.Major }} + minor: ${{ steps.gitversion.outputs.Minor }} + branchname: ${{ steps.gitversion.outputs.branchName }} + name: "JDK ${{ matrix.jdk }}: ${{ matrix.displayName }} JavaFX ${{ matrix.javafx }}" + steps: + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Fetch all history for all tags and branches + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: 'true' + show-progress: 'false' + - name: Install pigz and cache (linux) + if: (matrix.os == 'ubuntu-latest') || (matrix.os == 'buildjet-8vcpu-ubuntu-2204-arm') + uses: awalsh128/cache-apt-pkgs-action@master + with: + packages: pigz + version: 1.0 + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v3.0.0 + with: + versionSpec: "5.x" + - name: Run GitVersion + id: gitversion + uses: gittools/actions/gitversion/execute@v3.0.0 + + # JDK + - name: 'Set up JDK ${{ matrix.jdk }}' + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.jdk }} + distribution: 'temurin' + - name: 'Set JDK${{ matrix.jdk }} env var' + shell: bash + run: echo "JDK${{ matrix.jdk }}=$JAVA_HOME" >> $GITHUB_ENV + - name: 'Set JDK${{ matrix.jdk }} in toolchain (linux, Windows)' + if: (matrix.os != 'macos-latest') + shell: bash + run: | + sed -i 's/JavaLanguageVersion.of(.*)/JavaLanguageVersion.of(${{ matrix.jdk }})/' build.gradle + sed -i 's/JavaVersion.VERSION_(.*)/JavaVersion.VERSION_(${{ matrix.jdk }})/' build.gradle + - name: 'Set JDK${{ matrix.jdk }} in toolchain (macOS)' + if: (matrix.os == 'macos-latest') + shell: bash + run: | + sed -i'.bak' 's/JavaLanguageVersion.of(.*)/JavaLanguageVersion.of(${{ matrix.jdk }})/' build.gradle + sed -i'.bak' 's/JavaVersion.VERSION_(.*)/JavaVersion.VERSION_(${{ matrix.jdk }})/' build.gradle + + # JavaFX + - name: Download and extract JavaFX ${{ matrix.javafx }} + if: (matrix.os != 'buildjet-8vcpu-ubuntu-2204-arm') + shell: bash + run: | + cd javafx + curl --no-progress-meter https://jdk.java.net/javafx${{ matrix.javafx }}/ > javafx.html + + case "${{ matrix.os }}" in + "ubuntu-latest") + OS="linux" + EXTRACT="tar xzf *.tar.gz" + EXT="tar.gz" + ;; + "buildjet-8vcpu-ubuntu-2204-arm") + OS="linux" + EXTRACT="tar xzf *.tar.gz" + EXT="tar.gz" + echo "There are no ARM EA builds" + exit 0 + ;; + "windows-latest") + OS="windows" + EXTRACT="unzip -qq *.zip" + EXT="zip" + ;; + "macos-latest") + OS="macos" + EXTRACT="tar xzf *.tar.gz" + EXT="tar.gz" + ;; + *) + echo "Unsupported OS" + exit 1 + ;; + esac + echo "OS set to $OS" + + URL_SDK=$(grep -o "https://download.java.net/java/.*/javafx.*${OS}-x64_bin-sdk.${EXT}" javafx.html | head -n 1) + echo "Downloading $URL_SDK..." + curl -OJ --no-progress-meter $URL_SDK + $EXTRACT + rm *.$EXT + + URL_JMODS=$(grep -o "https://download.java.net/java/.*/javafx.*${OS}-x64_bin-jmods.${EXT}" javafx.html | head -n 1) + echo "Downloading $URL_JMODS..." + curl -OJ --no-progress-meter $URL_JMODS + $EXTRACT + rm *.$EXT + - name: 'Set JavaFX ${{ matrix.javafx }} (linux, Windows)' + if: (matrix.os != 'macos-latest') && (matrix.os != 'buildjet-8vcpu-ubuntu-2204-arm') + run: | + sed -i '/javafx {/{n;s#version = ".*"#sdk = "javafx/javafx-sdk-${{ matrix.javafx }}"#}' build.gradle + sed -i "s#jlink {#jlink { addExtraModulePath 'javafx/javafx-jmods-${{ matrix.javafx }}'#" build.gradle + cat build.gradle + - name: 'Set JavaFX ${{ matrix.javafx }} (macOS)' + if: (matrix.os == 'macos-latest') && (matrix.os != 'buildjet-8vcpu-ubuntu-2204-arm') + run: | + sed -i '.bak' -e '/javafx {/{n' -e 's#version = ".*"#sdk = "javafx/javafx-sdk-${{ matrix.javafx }}"#;}' build.gradle + sed -i '.bak' -e "s#jlink {#jlink { addExtraModulePath 'javafx/javafx-jmods-${{ matrix.javafx }}'#" build.gradle + cat build.gradle + - name: 'Set JavaFX ${{ matrix.javafx }} (linux-arm)' + if: (matrix.os == 'buildjet-8vcpu-ubuntu-2204-arm') + # No JavaFX EA build for ARM at https://jdk.java.net/javafx23/, therefore using Maven Central artifact + run: | + curl -s "https://search.maven.org/solrsearch/select?q=g:org.openjfx+AND+a:javafx&rows=10&core=gav" > /tmp/versions.json + jq '[.response.docs[] | select(.v | test(".*-ea\\+.*")) | select(.v | test("^17|^18|^19|^20|^21|^22") | not) | {version: .v}] | group_by(.version | capture("^(?\\d+).*").major) | map(max_by(.version))' < /tmp/versions.json > /tmp/versions-latest.json + JAVAFX=$(jq -r '.[-1].version' /tmp/versions-latest.json) + echo "Using JavaFX ${JAVAFX}" + sed -i "/javafx {/{n;s#version = \".*\"#version = \"${JAVAFX}\"#}" build.gradle && cat build.gradle + + # Gradle + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: 'temurin' + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Prepare merged jars and modules dir + # prepareModulesDir is executing a build, which should run through even if no upload to builds.jabref.org is made + if: (steps.checksecrets.outputs.secretspresent == 'NO') + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" prepareModulesDir + - name: Set up macOS key chain + if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d + with: + p12-file-base64: ${{ secrets.OSX_SIGNING_CERT }} + p12-password: ${{ secrets.OSX_CERT_PWD }} + keychain-password: jabref + - name: Set up macOS key chain for app id cert + if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d + with: + p12-file-base64: ${{ secrets.OSX_SIGNING_CERT_APPLICATION }} + p12-password: ${{ secrets.OSX_CERT_PWD }} + create-keychain: false + keychain-password: jabref + - name: Build runtime image and installer + shell: bash + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage jlinkZip + - name: Package application image + shell: bash + run: ${{ matrix.archivePortable }} + - name: Rename files + if: (matrix.os != 'macos-latest') + shell: pwsh + run: | + get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "${{ steps.gitversion.outputs.AssemblySemVer }}","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}"} + get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "portable","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-portable"} + - name: Repack deb file for Debian + if: (matrix.os == 'ubuntu-latest') + shell: bash + run: | + cd build/distribution + ar x jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64.deb + zstd -d < control.tar.zst | xz > control.tar.xz + zstd -d < data.tar.zst | xz > data.tar.xz + ar -m -c -a sdsd jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64_repackaged.deb debian-binary control.tar.xz data.tar.xz + rm debian-binary control.tar.* data.tar.* + mv -f jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64_repackaged.deb jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64.deb + - name: Rename files with JDK version + shell: bash + run: | + for file in build/distribution/*.*; do + base=${file%.*} + ext=${file##*.} + mv "$file" "${base}-jdk${{ matrix.jdk }}-javafx${{ matrix.javafx }}.${ext}" + done + + # Upload + - name: Set up rsync (macOS) + if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') && (github.ref == 'refs/heads/main') + run: brew install rsync + - name: Set up rsync (Windows) + if: (matrix.os == 'windows-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') && (github.ref == 'refs/heads/main') + # We want to have rsync available at this place to avoid uploading and downloading from GitHub artifact store (taking > 5 minutes in total) + # We cannot use "action-rsyncer", because that requires Docker which is unavailable on Windows + # We cannot use "setup-rsync", because that does not work on Windows + # We do not use egor-tensin/setup-cygwin@v4, because it replaces the default shell + run: choco install --no-progress rsync + - name: Set up SSH key + if: (steps.checksecrets.outputs.secretspresent == 'YES') && (github.ref == 'refs/heads/main') + run: | + echo "${{ secrets.buildJabRefPrivateKey }}" > sshkey + chmod 600 sshkey + - name: Upload to builds.jabref.org (Windows) + if: (matrix.os == 'windows-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') && (github.ref == 'refs/heads/main') + shell: cmd + # for rsync installed by chocolatey, we need the ssh.exe delivered with that installation + run: | + rsync -rt --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r --itemize-changes --stats --rsync-path="mkdir -p /var/www/builds.jabref.org/www/jdk-ea && rsync" -e 'C:\ProgramData\chocolatey\lib\rsync\tools\bin\ssh.exe -p 9922 -i sshkey -o StrictHostKeyChecking=no' build/distribution/ jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/jdk-ea/ || true + - name: Upload to builds.jabref.org (linux, macOS) + if: (matrix.os != 'windows-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') && (github.ref == 'refs/heads/main') + shell: bash + run: | + rsync -rt --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r --itemize-changes --stats --rsync-path="mkdir -p /var/www/builds.jabref.org/www/jdk-ea && rsync" -e 'ssh -p 9922 -i sshkey -o StrictHostKeyChecking=no' build/distribution/ jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/jdk-ea/ + - name: Upload to GitHub workflow artifacts store + if: (steps.checksecrets.outputs.secretspresent != 'YES') || (github.ref != 'refs/heads/main') + uses: actions/upload-artifact@v4 + with: + name: JabRef-${{ matrix.os }} + path: build/distribution + compression-level: 0 # no compression diff --git a/jabref/.github/workflows/deployment.yml b/jabref/.github/workflows/deployment.yml new file mode 100644 index 00000000..543ce705 --- /dev/null +++ b/jabref/.github/workflows/deployment.yml @@ -0,0 +1,341 @@ +name: Deployment + +on: + push: + branches: + - main + - main-release + paths-ignore: + - 'docs/**' + - 'src/test/**' + - 'README.md' + tags: + - '*' + pull_request: + merge_group: + workflow_dispatch: + inputs: + notarization: + type: boolean + required: false + default: false + +env: + SpringerNatureAPIKey: ${{ secrets.SpringerNatureAPIKey }} + AstrophysicsDataSystemAPIKey: ${{ secrets.AstrophysicsDataSystemAPIKey }} + IEEEAPIKey: ${{ secrets.IEEEAPIKey }} + BiodiversityHeritageApiKey: ${{ secrets.BiodiversityHeritageApiKey}} + OSXCERT: ${{ secrets.OSX_SIGNING_CERT }} + GRADLE_OPTS: -Xmx4g -Dorg.gradle.vfs.watch=false + JAVA_OPTS: -Xmx4g + +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-13] + include: + - os: ubuntu-latest + displayName: linux + archivePortable: tar -c -C build/distribution JabRef | pigz --rsyncable > build/distribution/JabRef-portable_linux.tar.gz && rm -R build/distribution/JabRef + - os: windows-latest + displayName: windows + archivePortable: 7z a -r build/distribution/JabRef-portable_windows.zip ./build/distribution/JabRef && rm -R build/distribution/JabRef + - os: macos-13 # intel image + displayName: macOS + runs-on: ${{ matrix.os }} + outputs: + major: ${{ steps.gitversion.outputs.Major }} + minor: ${{ steps.gitversion.outputs.Minor }} + branchname: ${{ steps.gitversion.outputs.branchName }} + name: ${{ matrix.displayName }} installer and portable version + steps: + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Fetch all history for all tags and branches + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: 'true' + show-progress: 'false' + - name: Install pigz and cache (linux) + if: (matrix.os == 'ubuntu-latest') + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: pigz + version: 1.0 + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v3.0.0 + with: + versionSpec: "5.x" + - name: Run GitVersion + id: gitversion + uses: gittools/actions/gitversion/execute@v3.0.0 + - name: Setup JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Prepare merged jars and modules dir (macOS) + # prepareModulesDir is executing a build, which should run through even if no upload to builds.jabref.org is made + if: (matrix.os == 'macos-13') || (steps.checksecrets.outputs.secretspresent == 'NO') + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" prepareModulesDir + - name: Setup macOS key chain + if: (matrix.os == 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES') + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d + with: + p12-file-base64: ${{ secrets.OSX_SIGNING_CERT }} + p12-password: ${{ secrets.OSX_CERT_PWD }} + keychain-password: jabref + - name: Setup macOS key chain for app id cert + if: (matrix.os == 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES') + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d + with: + p12-file-base64: ${{ secrets.OSX_SIGNING_CERT_APPLICATION }} + p12-password: ${{ secrets.OSX_CERT_PWD }} + create-keychain: false + keychain-password: jabref + - name: Build dmg (macOS) + if: (matrix.os == 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + jpackage \ + --module org.jabref/org.jabref.Launcher \ + --module-path ${{env.JAVA_HOME}}/jmods/:build/jlinkbase/jlinkjars \ + --add-modules org.jabref,org.jabref.merged.module \ + --add-modules jdk.incubator.vector \ + --dest build/distribution \ + --app-content buildres/mac/jabrefHost.py \ + --app-content buildres/mac/native-messaging-host \ + --name JabRef \ + --app-version ${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }} \ + --verbose \ + --mac-sign \ + --vendor "JabRef e.V." \ + --mac-package-identifier JabRef \ + --mac-package-name JabRef \ + --type dmg --mac-signing-key-user-name "JabRef e.V. (6792V39SK3)" \ + --mac-package-signing-prefix org.jabref \ + --mac-entitlements buildres/mac/jabref.entitlements \ + --icon src/main/resources/icons/jabref.icns \ + --resource-dir buildres/mac \ + --file-associations buildres/mac/bibtexAssociations.properties \ + --jlink-options --bind-services \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref.merged.module \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control.skin=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.base/javafx.collections=org.jabref \ + --java-options --add-opens=javafx.base/javafx.collections.transformation=org.jabref \ + --java-options --add-modules=jdk.incubator.vector + - name: Build pkg (macOS) + if: (matrix.os == 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + jpackage \ + --module org.jabref/org.jabref.Launcher \ + --module-path ${{env.JAVA_HOME}}/jmods/:build/jlinkbase/jlinkjars \ + --add-modules org.jabref,org.jabref.merged.module \ + --add-modules jdk.incubator.vector \ + --dest build/distribution \ + --app-content buildres/mac/jabrefHost.py \ + --app-content buildres/mac/native-messaging-host \ + --name JabRef \ + --app-version ${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }} \ + --verbose \ + --mac-sign \ + --vendor "JabRef e.V." \ + --mac-package-identifier JabRef \ + --mac-package-name JabRef \ + --type pkg --mac-signing-key-user-name "JabRef e.V. (6792V39SK3)" \ + --mac-package-signing-prefix org.jabref \ + --mac-entitlements buildres/mac/jabref.entitlements \ + --icon src/main/resources/icons/jabref.icns \ + --resource-dir buildres/mac \ + --file-associations buildres/mac/bibtexAssociations.properties \ + --jlink-options --bind-services \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref.merged.module \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control.skin=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.base/javafx.collections=org.jabref \ + --java-options --add-opens=javafx.base/javafx.collections.transformation=org.jabref \ + --java-options --add-modules=jdk.incubator.vector + - name: Build runtime image and installer (linux, Windows) + if: (matrix.os != 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage jlinkZip + - name: Package application image (linux, Windows) + if: (matrix.os != 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: ${{ matrix.archivePortable }} + - name: Rename files + if: (matrix.os != 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES') + shell: pwsh + run: | + get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "${{ steps.gitversion.outputs.AssemblySemVer }}","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}"} + get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "portable","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-portable"} + - name: Repack deb file for Debian + if: (matrix.os == 'ubuntu-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + cd build/distribution + ar x jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64.deb + zstd -d < control.tar.zst | xz > control.tar.xz + zstd -d < data.tar.zst | xz > data.tar.xz + ar -m -c -a sdsd jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64_repackaged.deb debian-binary control.tar.xz data.tar.xz + rm debian-binary control.tar.* data.tar.* + mv -f jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64_repackaged.deb jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64.deb + - name: Setup rsync (macOS) + if: ${{ (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) && (steps.checksecrets.outputs.secretspresent == 'YES') && ((matrix.os == 'macos-13') && !((startsWith(github.ref, 'refs/tags/') || inputs.notarization == true))) }} + run: brew install rsync + - name: Setup rsync (Windows) + if: (matrix.os == 'windows-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') && (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) + # We want to have rsync available at this place to avoid uploading and downloading from GitHub artifact store (taking > 5 minutes in total) + # We cannot use "action-rsyncer", because that requires Docker which is unavailable on Windows + # We cannot use "setup-rsync", because that does not work on Windows + # We do not use egor-tensin/setup-cygwin@v4, because it replaces the default shell + run: choco install --no-progress rsync + - name: Setup SSH key + if: ${{ (steps.checksecrets.outputs.secretspresent == 'YES') && (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) && ((matrix.os != 'macos-13') || !((startsWith(github.ref, 'refs/tags/') || (inputs.notarization == true)))) }} + run: | + echo "${{ secrets.buildJabRefPrivateKey }}" > sshkey + chmod 600 sshkey + - name: Upload to builds.jabref.org (Windows) + if: (matrix.os == 'windows-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') && (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) + shell: cmd + # for rsync installed by chocolatey, we need the ssh.exe delivered with that installation + run: | + rsync -rt --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r --itemize-changes --stats --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }} && rsync" -e 'C:\ProgramData\chocolatey\lib\rsync\tools\bin\ssh.exe -p 9922 -i sshkey -o StrictHostKeyChecking=no' build/distribution/ jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }}/ || true + - name: Upload to builds.jabref.org (linux, macOS) + # macOS: Negated condition of "Upload to GitHub workflow artifacts store (macOS)" + # Reason: We either upload the non-notarized files - or notarize the files later (and upload these later) + # needs to be on one line; multi line does not work + if: ${{ (!startsWith(github.ref, 'refs/heads/gh-readonly-queue')) && (steps.checksecrets.outputs.secretspresent == 'YES') && ((matrix.os == 'ubuntu-latest') || ((matrix.os == 'macos-13') && !((startsWith(github.ref, 'refs/tags/') || inputs.notarization == true)))) }} + shell: bash + run: | + rsync -rt --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r --itemize-changes --stats --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }} && rsync" -e 'ssh -p 9922 -i sshkey -o StrictHostKeyChecking=no' build/distribution/ jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ steps.gitversion.outputs.branchName }}/ || true + - name: Upload to GitHub workflow artifacts store (macOS) + if: (matrix.os == 'macos-13') && (steps.checksecrets.outputs.secretspresent == 'YES') && (startsWith(github.ref, 'refs/tags/') || inputs.notarization == true) + uses: actions/upload-artifact@v4 + with: + # tbn = to-be-notarized + name: JabRef-macOS-tbn + path: build/distribution + compression-level: 0 # no compression + - name: Upload to GitHub workflow artifacts store + if: (steps.checksecrets.outputs.secretspresent != 'YES') + uses: actions/upload-artifact@v4 + with: + name: JabRef-${{ matrix.os }} + path: build/distribution + compression-level: 0 # no compression + announce: + name: Comment on pull request + runs-on: ubuntu-latest + needs: [build] + if: ${{ github.event_name == 'pull_request' }} + steps: + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Comment PR + if: (steps.checksecrets.outputs.secretspresent == 'YES') + uses: thollander/actions-comment-pull-request@v3 + with: + message: | + The build of this PR is available at . + comment-tag: download-link + mode: recreate + notarize: # outsourced in a separate job to be able to rerun if this fails for timeouts + name: macOS notarization + runs-on: macos-13 + needs: [build] + if: ${{ startsWith(github.ref, 'refs/tags/') || inputs.notarization == true }} + steps: + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Download from GitHub workflow artifacts store (macOS) + if: (steps.checksecrets.outputs.secretspresent == 'YES') + uses: actions/download-artifact@master + with: + name: JabRef-macOS-tbn + path: build/distribution/ + - name: Notarize dmg + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + xcrun notarytool store-credentials "notarytool-profile" --apple-id "vorstand@jabref.org" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" + xcrun notarytool submit build/distribution/JabRef-${{ needs.build.outputs.major }}.${{ needs.build.outputs.minor }}.dmg --keychain-profile "notarytool-profile" --wait + xcrun stapler staple build/distribution/JabRef-${{ needs.build.outputs.major }}.${{ needs.build.outputs.minor }}.dmg + - name: Notarize pkg + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + xcrun notarytool store-credentials "notarytool-profile" --apple-id "vorstand@jabref.org" --team-id "6792V39SK3" --password "${{ secrets.OSX_NOTARIZATION_APP_PWD }}" + xcrun notarytool submit build/distribution/JabRef-${{ needs.build.outputs.major }}.${{ needs.build.outputs.minor }}.pkg --keychain-profile "notarytool-profile" --wait + xcrun stapler staple build/distribution/JabRef-${{ needs.build.outputs.major }}.${{ needs.build.outputs.minor }}.pkg + - name: Upload to builds.jabref.org + if: (steps.checksecrets.outputs.secretspresent == 'YES') + shell: bash + run: | + echo "${{ secrets.buildJabRefPrivateKey }}" > sshkey + chmod 600 sshkey + rsync -rt --chmod=Du=rwx,Dg=rx,Do=rx,Fu=rw,Fg=r,Fo=r --itemize-changes --stats --rsync-path="mkdir -p /var/www/builds.jabref.org/www/${{ needs.build.outputs.branchname }} && rsync" -e 'ssh -p 9922 -i sshkey -o StrictHostKeyChecking=no' build/distribution/ jrrsync@build-upload.jabref.org:/var/www/builds.jabref.org/www/${{ needs.build.outputs.branchname }}/ diff --git a/jabref/.github/workflows/gource.yml b/jabref/.github/workflows/gource.yml new file mode 100644 index 00000000..3bccc1f3 --- /dev/null +++ b/jabref/.github/workflows/gource.yml @@ -0,0 +1,96 @@ +name: Gource + +on: + push: + branches: + - gource + - fix-gource + schedule: + - cron: '15 3 5 * *' + workflow_dispatch: + +concurrency: + group: gource + cancel-in-progress: true + +jobs: + action: + if: github.repository_owner == 'JabRef' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + show-progress: 'false' + - name: 'Development history of current build' + uses: BoundfoxStudios/action-gource@v2 + with: + gource_title: 'JabRef v5.16 (in development) | more information at contribute.jabref.org' + logo_url: 'https://www.jabref.org/img/JabRef-icon-256.png' + avatars_auto_fetch: true + # 5s * 365 / 4 = 7.5min + gource_seconds_per_day: 1 + gource_start_date: '2024-07-11' + gource_file_filter: 'buildres/csl|\.csl' + - name: 'Store video' + run: | + mkdir gource-videos + mv ./gource/gource.mp4 ./gource-videos/jabref-v5.16-dev.mp4 + - name: 'Development history of last release' + uses: BoundfoxStudios/action-gource@v2 + with: + gource_title: 'JabRef v5.15 | more information at contribute.jabref.org' + logo_url: 'https://www.jabref.org/img/JabRef-icon-256.png' + avatars_auto_fetch: true + # 5s * 365 / 4 = 7.5min + gource_seconds_per_day: 2 + gource_start_date: '2024-07-09' + gource_stop_date: '2024-07-10' + gource_file_filter: 'buildres/csl|\.csl' + - name: 'Store video' + run: | + mv ./gource/gource.mp4 ./gource-videos/jabref-v5.15.mp4 + - name: 'Complete development history' + uses: BoundfoxStudios/action-gource@v2 + with: + gource_title: 'JabRef | more information at contribute.jabref.org' + logo_url: 'https://www.jabref.org/img/JabRef-icon-256.png' + avatars_auto_fetch: true + # 0.01 leads to a 45 second video for the complete history until end of 2020 + # 0.1 leads to a 8 minute video + gource_seconds_per_day: 0.1 + gource_file_filter: 'buildres/csl|\.csl' + - name: 'Store video' + run: | + mv gource/gource.mp4 gource-videos/jabref-complete.mp4 + - name: 'Upload gource video' + uses: actions/upload-artifact@v4 + with: + name: Gource + path: gource-videos/ + retention-days: 80 + - name: Check secrets presence + id: checksecrets + shell: bash + run: | + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret BUILDJABREFPRIVATEKEY not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret BUILDJABREFPRIVATEKEY present" + fi + env: + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + - name: Upload to files.jabref.org + if: steps.checksecrets.outputs.secretspresent == 'YES' + uses: Pendect/action-rsyncer@v2.0.0 + env: + DEPLOY_KEY: ${{ secrets.buildJabRefPrivateKey }} + with: + flags: -vaz --itemize-changes --stats --partial-dir=/tmp/partial + options: '' + ssh_options: '-p 9922' + src: 'gource-videos/' + dest: jrrsync@build-upload.jabref.org:/var/www/files.jabref.org/www/gource/ diff --git a/jabref/.github/workflows/on-labeled-issue.yml b/jabref/.github/workflows/on-labeled-issue.yml new file mode 100644 index 00000000..93e65e1c --- /dev/null +++ b/jabref/.github/workflows/on-labeled-issue.yml @@ -0,0 +1,124 @@ +name: On labeled issue + +on: + issues: + types: + - labeled + +jobs: + Assigned: + # Triggered when manually assigned the label "📍 Assigned" to trigger the automatic unassignment after 30 days + name: "📍 Assigned" + if: ${{ github.event.label.name == '📍 Assigned' && github.repository_owner == 'JabRef' }} + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Move Issue to "Free to take" Column in "Candidates for University Projects" + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/3" + target-labels: "📍 Assigned" + target-column: "Free to take" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true + - name: Move Issue to "Free to take" Column in "Good First Issues" + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/5" + target-labels: "📍 Assigned" + target-column: "Free to take" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true + FirstTimeCodeContribution: + if: ${{ github.event.label.name == 'FirstTimeCodeContribution' && github.repository_owner == 'JabRef' }} + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: GreetingFirstTimeCodeContribution + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.issue.number || github.event.pull_request.number }} + body: | + Welcome to the vibrant world of open-source development with JabRef! + + Newcomers, we're excited to have you on board. Start by exploring our [Contributing](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md) guidelines, and don't forget to check out our [workspace setup guidelines](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace) to get started smoothly. + + In case you encounter failing tests during development, please check our [developer FAQs](https://devdocs.jabref.org/code-howtos/faq.html)! + + Having any questions or issues? Feel free to ask here on GitHub. Need help setting up your local workspace? Join the conversation on [JabRef's Gitter chat](https://gitter.im/JabRef/jabref). And don't hesitate to open a (draft) pull request early on to show the direction it is heading towards. This way, you will receive valuable feedback. + + ⚠ Note that this issue will become unassigned if it isn't closed within **30 days**. + + 🔧 A maintainer can also add the **`Pinned`** label to prevent it from being unassigned automatically. + + Happy coding! 🚀 + - name: Move Issue to "Assigned" Column in "Candidates for University Projects" + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/3" + target-labels: "FirstTimeCodeContribution" + target-column: "Assigned" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true + - name: Move Issue to "Assigned" Column in "Good First Issues" + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/5" + target-labels: "FirstTimeCodeContribution" + target-column: "Assigned" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true + good-first-issue: + name: "good first issue" + if: "${{ github.event.label.name == 'good first issue' && github.repository_owner == 'JabRef' }}" + runs-on: ubuntu-latest + steps: + - name: "good first issue" + env: + GH_TOKEN: ${{ secrets.GH_TOKEN_PROJECT_ITEM_ADD }} + run: | + ISSUE_URL=$(jq --raw-output .issue.html_url "$GITHUB_EVENT_PATH") + gh project item-add 5 --owner JabRef --url $ISSUE_URL + needs-refinement: + if: github.event.label.name == 'needs-refinement' && github.repository_owner == 'JabRef' + runs-on: ubuntu-latest + steps: + - name: needs-refinement + env: + GH_TOKEN: ${{ secrets.GH_TOKEN_PROJECT_ITEM_ADD }} + run: | + ISSUE_URL=$(jq --raw-output .issue.html_url "$GITHUB_EVENT_PATH") + gh project item-add 15 --owner JabRef --url $ISSUE_URL + status-freeze: + name: "status: freeze" + if: "${{ github.event.label.name == 'status: freeze' && github.repository_owner == 'JabRef' }}" + runs-on: ubuntu-latest + steps: + - name: "status: freeze" + env: + GH_TOKEN: ${{ secrets.GH_TOKEN_PROJECT_ITEM_ADD }} + run: | + ISSUE_URL=$(jq --raw-output .issue.html_url "$GITHUB_EVENT_PATH") + gh project item-add 9 --owner JabRef --url $ISSUE_URL + ui: + if: "${{ github.event.label.name == 'ui' && github.repository_owner == 'JabRef' }}" + runs-on: ubuntu-latest + steps: + - name: ui + env: + GH_DEBUG: api + GH_TOKEN: ${{ secrets.GH_TOKEN_PROJECT_ITEM_ADD }} + run: | + ISSUE_URL=$(jq --raw-output .issue.html_url "$GITHUB_EVENT_PATH") + echo $ISSUE_URL + gh project item-add 8 --owner JabRef --url $ISSUE_URL diff --git a/jabref/.github/workflows/on-labeled-pr.yml b/jabref/.github/workflows/on-labeled-pr.yml new file mode 100644 index 00000000..2e64d79f --- /dev/null +++ b/jabref/.github/workflows/on-labeled-pr.yml @@ -0,0 +1,23 @@ +name: On labeled PR + +on: + pull_request: + types: + - labeled + +jobs: + automerge: + name: Auto Merge + if: "${{ github.event.label.name == 'automerge' && github.repository_owner == 'JabRef' }}" + runs-on: ubuntu-latest + steps: + - name: Approve PR + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GH_TOKEN_JABREF_MACHINE_PR_APPROVE}} + - name: Merge PR + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER}} diff --git a/jabref/.github/workflows/on-unlabeled-issue.yml b/jabref/.github/workflows/on-unlabeled-issue.yml new file mode 100644 index 00000000..64872289 --- /dev/null +++ b/jabref/.github/workflows/on-unlabeled-issue.yml @@ -0,0 +1,34 @@ +name: On unlabeled issue + +on: + issues: + types: + - unlabeled + +jobs: + FirstTimeCodeContribution_or_Assigned: + if: ${{ ((github.event.label.name == 'FirstTimeCodeContribution') || (github.event.label.name == '📍 Assigned')) && github.repository_owner == 'JabRef' }} + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Move Issue to "Free to take" Column in "Candidates for University Projects" + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/3" + target-labels: "📍 Assigned" + target-column: "Assigned" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true + - name: Move Issue to "Free to take" Column in "Good First Issues" + uses: m7kvqbe1/github-action-move-issues@feat/skip-if-not-in-project-flag + with: + github-token: ${{ secrets.GH_TOKEN_ACTION_MOVE_ISSUE }} + project-url: "https://github.com/orgs/JabRef/projects/5" + target-labels: "📍 Assigned" + target-column: "Assigned" + ignored-columns: "" + default-column: "Free to take" + skip-if-not-in-project: true diff --git a/jabref/.github/workflows/pages.yml b/jabref/.github/workflows/pages.yml new file mode 100644 index 00000000..2cf13e04 --- /dev/null +++ b/jabref/.github/workflows/pages.yml @@ -0,0 +1,63 @@ +name: Deploy Jekyll site to Pages + +on: + pull_request: + paths: + - 'docs/**' + - '.github/workflows/pages.yml' + push: + branches: + main + paths: + - 'docs/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + show-progress: 'false' + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' # Not needed with a .ruby-version file + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + cache-version: 0 # Increment this number if you need to re-download cached gems + working-directory: docs/ + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + - name: Build with Jekyll + run: | + cd docs + bundle exec jekyll build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_site/ + + deploy: + if: github.ref == 'refs/heads/main' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/jabref/.github/workflows/pr-comment.yml b/jabref/.github/workflows/pr-comment.yml new file mode 100644 index 00000000..090adf78 --- /dev/null +++ b/jabref/.github/workflows/pr-comment.yml @@ -0,0 +1,63 @@ +# Description: This workflow is triggered when the "Check" workflow completes. +# Since this pull request has write permissions on the target repo, we should **NOT** execute any untrusted code. +# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ +# Based on https://github.com/spring-projects/spring-security/pull/15477/files +--- +name: Comment on PR + +on: + workflow_run: + workflows: ["Tests"] + types: + - completed + +jobs: + comment: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-a-workflow-based-on-the-conclusion-of-another-workflow + if: ${{ github.event.workflow_run.conclusion == 'failure' && (github.repository_owner == 'JabRef') }} + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + pull-requests: write + timeout-minutes: 10 + steps: + - name: Download PR number + uses: actions/download-artifact@v4 + with: + name: pr_number + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + - name: Read pr_number.txt + id: read-pr_number + run: | + PR_NUMBER=$(cat pr_number.txt) + echo "Read PR number $PR_NUMBER" + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + - uses: actions/checkout@v4 + - name: Is PR from forked? + if: ${{ steps.read-pr_number.outputs.pr_number != '' }} + id: isCrossRepository + run: | + isCrossRepository=$(gh pr view $pr_number --json isCrossRepository --jq '.isCrossRepository') + echo "Got isCrossRepository $isCrossRepository" + echo isCrossRepository=$isCrossRepository >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + pr_number: ${{ steps.read-pr_number.outputs.pr_number }} + - name: Checkout + if: ${{ steps.read-pr_number.outputs.pr_number != '' && steps.isCrossRepository.outputs.isCrossRepository == 'true' }} + uses: actions/checkout@v4 + with: + fetch-depth: '0' + show-progress: 'false' + token: ${{ secrets.GITHUB_TOKEN }} + - name: jbang + if: ${{ steps.read-pr_number.outputs.pr_number != '' && steps.isCrossRepository.outputs.isCrossRepository == 'true' }} + uses: jbangdev/jbang-action@v0.119.0 + with: + script: ghprcomment@koppor/ghprcomment + scriptargs: "-r JabRef/jabref -p ${{ steps.read-pr_number.outputs.pr_number }} -w ${{ github.event.workflow_run.id }}" + trust: https://github.com/koppor/ghprcomment/ + env: + GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} diff --git a/jabref/.github/workflows/tests-fetchers.yml b/jabref/.github/workflows/tests-fetchers.yml new file mode 100644 index 00000000..e02bfca4 --- /dev/null +++ b/jabref/.github/workflows/tests-fetchers.yml @@ -0,0 +1,60 @@ +name: Fetcher Tests + +on: + push: + branches: + - main + paths: + - 'src/main/java/org/jabref/logic/importer/fetcher/**' + - 'src/test/java/org/jabref/logic/importer/fetcher/**' + - 'src/main/java/org/jabref/logic/crawler/**' + - 'src/test/java/org/jabref/logic/crawler/**' + - '.github/workflows/tests-fetchers.yml' + - 'build.gradle' + pull_request: + paths: + - 'src/main/java/org/jabref/logic/importer/fetcher/**' + - 'src/test/java/org/jabref/logic/importer/fetcher/**' + - '.github/workflows/tests-fetchers.yml' + - 'build.gradle' + schedule: + # run on each Wednesday + - cron: '2 3 * * 3' + workflow_dispatch: + +env: + SpringerNatureAPIKey: ${{ secrets.SPRINGERNATUREAPIKEY_FOR_TESTS }} + AstrophysicsDataSystemAPIKey: ${{ secrets.AstrophysicsDataSystemAPIKey_FOR_TESTS }} + IEEEAPIKey: ${{ secrets.IEEEAPIKey_FOR_TESTS }} + BiodiversityHeritageApiKey: ${{ secrets.BiodiversityHeritageApiKey_FOR_TESTS}} + +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +permissions: + contents: read + +jobs: + fetchertests: + name: Fetcher tests + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'true' + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Run fetcher tests + run: ./gradlew fetcherTest + env: + CI: "true" diff --git a/jabref/.github/workflows/tests.yml b/jabref/.github/workflows/tests.yml new file mode 100644 index 00000000..5907a2ae --- /dev/null +++ b/jabref/.github/workflows/tests.yml @@ -0,0 +1,393 @@ +name: Tests + +on: + push: + branches: + - main + - main-release + pull_request: + merge_group: + workflow_dispatch: + +env: + SpringerNatureAPIKey: ${{ secrets.SpringerNatureAPIKey }} + AstrophysicsDataSystemAPIKey: ${{ secrets.AstrophysicsDataSystemAPIKey }} + IEEEAPIKey: ${{ secrets.IEEEAPIKey }} + BiodiversityHeritageApiKey: ${{ secrets.BiodiversityHeritageApiKey}} + GRADLE_OPTS: -Xmx4g + JAVA_OPTS: -Xmx4g + +concurrency: + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +permissions: + pull-requests: write + +jobs: + checkstyle: + name: Checkstyle + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'true' + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Run checkstyle reporter + uses: dbelyaev/action-checkstyle@master + with: + reporter: github-pr-review + github_token: ${{ secrets.GITHUB_TOKEN }} + checkstyle_config: 'config/checkstyle/checkstyle_reviewdog.xml' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Run checkstyle using gradle + run: ./gradlew checkstyleMain checkstyleTest checkstyleJmh + + + openrewrite: + name: OpenRewrite + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'true' + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Run OpenRewrite + run: | + ./gradlew rewriteDryRun + + modernizer: + name: Modernizer + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'true' + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Run modernizer + run: | + # enable failing of this task if modernizer complains + sed -i "s/failOnViolations = false/failOnViolations = true/" build.gradle + ./gradlew modernizer + + markdown: + name: Markdown + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'false' + show-progress: 'false' + - name: markdownlint-cli2-action + uses: DavidAnson/markdownlint-cli2-action@v17 + with: + globs: | + *.md + docs/**/*.md + + changelog: + name: CHANGELOG.md + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'false' + show-progress: 'false' + - name: Lint CHANGELOG.md + run: | + # Install jbang + curl -Ls https://sh.jbang.dev | bash -s - app setup + export PATH=$PATH:$HOME/.jbang/bin + + # run heylogs verification + jbang com.github.nbbrd.heylogs:heylogs-cli:0.9.2:bin check CHANGELOG.md > heylogs.txt || true + + # improve output + sed -i 's/all-h2-contain-a-version/all-h2-contain-a-version (ignored)/' heylogs.txt + + cat heylogs.txt + + # exit 1 in case of error + # We have 1 "valid" issue in CHANGELOG.md + grep -q "1 problem" heylogs.txt || exit 1 + + changelog-unreleased-only: + name: CHANGELOG.md - only unreleased touched + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'false' + show-progress: 'false' + fetch-depth: 0 + - name: Install clparse + run: | + curl -LO https://github.com/marcaddeo/clparse/releases/download/0.9.1/clparse-0.9.1-x86_64-unknown-linux-musl.tar.gz + tar xzvf clparse-0.9.1-x86_64-unknown-linux-musl.tar.gz + sudo mv clparse /usr/local/bin/clparse + - name: Check CHANGELOG.md diff + run: | + diff \ + <(git show origin/main:CHANGELOG.md | clparse --format=json --separator=– - | jq '.releases[] | select(.version != null)') \ + <(git show HEAD:CHANGELOG.md | clparse --format=json --separator=– - | jq '.releases[] | select(.version != null)') + + tests: + name: Unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'true' + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Run tests + run: xvfb-run --auto-servernum ./gradlew check -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x modernizer + env: + CI: "true" + - name: Prepare format failed test results + if: failure() + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: xml-twig-tools xsltproc + version: 1.0 + - name: Format failed test results + if: failure() + run: scripts/after-failure.sh + + databasetests: + name: Database tests + runs-on: ubuntu-latest + services: + postgres: + image: postgres:13-alpine + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'true' + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Run tests on PostgreSQL + run: ./gradlew databaseTest --rerun-tasks + env: + CI: "true" + DBMS: "postgresql" + - name: Shutdown Ubuntu MySQL + run: sudo service mysql stop # Shutdown the Default MySQL, "sudo" is necessary, please not remove it + - name: Start custom MySQL + uses: mirromutth/mysql-action@v1.1 + with: + host port: 3800 + container port: 3307 + character set server: 'utf8' + collation server: 'utf8_general_ci' + mysql version: '8.0' + mysql database: 'jabref' + mysql root password: 'root' + - name: Run tests on MySQL + run: ./gradlew databaseTest --rerun-tasks + env: + CI: "true" + DBMS: "mysql" + + guitests: + name: GUI tests + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + submodules: 'true' + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Run GUI tests + run: xvfb-run --auto-servernum ./gradlew guiTest + env: + CI: "true" + + codecoverage: + name: Code coverage + runs-on: ubuntu-latest + services: + postgres: + image: postgres:10.8 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - name: Check secrets presence + id: checksecrets + if: github.ref == 'refs/heads/main' + shell: bash + run: | + if [ "$CODECOV_TOKEN" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + echo "❌ Secret CODECOV_TOKEN not present" + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + echo "✔️ Secret CODECOV_TOKEN present" + fi + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - name: Checkout source + if: github.ref == 'refs/heads/main' + uses: actions/checkout@v4 + with: + submodules: 'true' + show-progress: 'false' + - name: Set up JDK + if: github.ref == 'refs/heads/main' + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + - name: Update test coverage metrics + if: (github.ref == 'refs/heads/main') && (steps.checksecrets.outputs.secretspresent == 'YES') + run: xvfb-run --auto-servernum ./gradlew jacocoTestReport + env: + CI: "true" + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + DBMS: "postgresql" + - uses: codecov/codecov-action@v4 + if: (github.ref == 'refs/heads/main') && (steps.checksecrets.outputs.secretspresent == 'YES') + with: + token: ${{ secrets.CODECOV_TOKEN }} + - name: Upload Codacy report + if: (github.ref == 'refs/heads/main') && (steps.checksecrets.outputs.secretspresent == 'YES') + run: bash <(curl -Ls https://coverage.codacy.com/get.sh) + env: + CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + + requirements_coverage: + name: "Validate requirement coverage" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + show-progress: 'false' + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - run: ./gradlew traceRequirements + - if: failure() + run: cat build/reports/tracing.txt + + # This is https://github.com/marketplace/actions/gradle-wrapper-validation + # It ensures that the jar file is from gradle and not by a strange third party. + gradlevalidation: + name: "Validate Gradle Wrapper" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + show-progress: 'false' + - uses: gradle/actions/wrapper-validation@v4 + + # This ensures that no git merge conflict markers (<<<, ...) are contained + merge_conflict_job: + name: Find merge conflicts + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + show-progress: 'false' + - name: Merge Conflict finder + uses: olivernybroe/action-conflict-finder@v4.0 + + other_than_main: + name: Source branch is other than "main" + runs-on: ubuntu-latest + steps: + - if: github.head_ref == 'main' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Pull requests should come from a branch other than "main"\n\n👉 Please read [the CONTRIBUTING guide](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md#contributing) carefully again. 👈') + + upload-pr-number: + runs-on: ubuntu-latest + steps: + - name: Create pr_number.txt + run: echo "${{ github.event.number }}" > pr_number.txt + - uses: actions/upload-artifact@v4 + with: + name: pr_number + path: pr_number.txt diff --git a/jabref/.github/workflows/update-gradle-wrapper.yml b/jabref/.github/workflows/update-gradle-wrapper.yml new file mode 100644 index 00000000..d0081709 --- /dev/null +++ b/jabref/.github/workflows/update-gradle-wrapper.yml @@ -0,0 +1,25 @@ +name: Update Gradle Wrapper + +on: + schedule: + - cron: "0 5 * * 1" + workflow_dispatch: + +jobs: + update-gradle-wrapper: + if: github.repository_owner == 'JabRef' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup JDK + uses: actions/setup-java@v4 + with: + java-version: 23.0.1 + distribution: 'temurin' + + - name: Update Gradle Wrapper + uses: gradle-update/update-gradle-wrapper-action@v2 + with: + labels: dependencies + repo-token: ${{ secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER }} diff --git a/jabref/.github/workflows/upload-release.yml b/jabref/.github/workflows/upload-release.yml new file mode 100644 index 00000000..d7020264 --- /dev/null +++ b/jabref/.github/workflows/upload-release.yml @@ -0,0 +1,24 @@ +name: Release binaries on GitHub + +on: + # due to synchronization with a complete build, we need a manual trigger + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Get binaries from builds.jabref.org + run: | + mkdir build + cd build + wget -q -m -r -nH --cut-dirs 2 --no-parent --accept=tar.gz,dmg,pkg,deb,rpm,zip,msi https://builds.jabref.org/tags/ + - name: Release + uses: softprops/action-gh-release@v2 + with: + draft: true + files: build/** diff --git a/jabref/.gitignore b/jabref/.gitignore new file mode 100644 index 00000000..2e33739e --- /dev/null +++ b/jabref/.gitignore @@ -0,0 +1,569 @@ +# no generated files in version control +src/main/gen/ +src/main/generated/ +src-gen/ + +.lycheecache + +javafx/javafx-sdk-* +javafx/javafx-jmods-* +javafx/javafx.html +javafx/*.tar.gz +javafx/*.zip + +# generated by https://plugins.jetbrains.com/plugin/15991-plantuml-diagram-generator +*.puml + +structure101-settings.java.hsw + +# private data +/buildres/jabref-cert-2016.p12 + +# GitBook assets +docs/book.pdf +docs/_book/ + +# Jekyll for docs/ +docs/Gemfile.lock + +# ignore the generated markdown file if the user forgets to delete it +status.md + +# Install4J +install4j6/ + +# JDK14 (see .github/deployment.yml for details) +jdk-14/ + +# Python +__pycache__/ + +# Snap +parts/ +stage/ +prime/ +*.snap +jabref_source.tar.bz2 +snap/.snapcraft/ + +# flatpak +flatpak/.buildconfig +flatpak/JabRef-portable_linux.tar.gz + +# Gradle +# generated when `gradlew --gui` is called +ui/ +# for workspace specific gradle properties +gradle.properties + + +# IntelliJ IDEA +.idea/* +!.idea/runConfigurations/ +*.ipr +*.iml + +# UNKNWON +jabref.xml +*.sonargraph + +## !! IN CASE YOU UPDATE, PLEASE KEEP THE LINES AT THE END OF THE FILE !! + + +# Created by https://www.toptal.com/developers/gitignore/api/gradle,java,jabref,jetbrains,eclipse,netbeans,windows,linux,macos,node,snapcraft +# Edit at https://www.toptal.com/developers/gitignore?templates=gradle,java,jabref,jetbrains,eclipse,netbeans,windows,linux,macos,node,snapcraft + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + +### Eclipse Patch ### +# Spring Boot Tooling +.sts4-cache/ + +### JabRef ### +# JabRef - https://www.jabref.org/ +*.sav + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### JetBrains ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### JetBrains Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### NetBeans ### +**/nbproject/private/ +**/nbproject/Makefile-*.mk +**/nbproject/Package-*.bash +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +### Node ### +# Logs +logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### Snapcraft ### +/parts/ +/stage/ +/prime/ +/*.snap + +# Snapcraft global state tracking data(automatically generated) +# https://forum.snapcraft.io/t/location-to-save-global-state/768 +/snap/.snapcraft/ + +# Source archive packed by `snapcraft cleanbuild` before pushing to the LXD container +/*_source.tar.bz2 + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof + +# End of https://www.toptal.com/developers/gitignore/api/gradle,java,jabref,jetbrains,eclipse,netbeans,windows,linux,macos,node,snapcraft + + +## !! KEEP THESE LINES !! + +# we really version .jar files - needs to be go after the www.gitignore.io-generated ones, because they ignore *.jar files +!/lib/*.jar + +# do not distribute Oracle's JDBC driver +lib/ojdbc.jar + +# do not ignore the source of the build +!/buildSrc/src/ +!/buildSrc/src/main/groovy/org/jabref/build + +# do not ignore JabRef icons (they are ignored by the macos setting above) +!src/main/java/org/jabref/gui/icon + +!**/autosaveandbackup/*.bak + +# generated during release process +CHANGELOG.html diff --git a/jabref/.gitmodules b/jabref/.gitmodules new file mode 100644 index 00000000..10944a3d --- /dev/null +++ b/jabref/.gitmodules @@ -0,0 +1,15 @@ +[submodule "abbrv.jabref.org"] + path = buildres/abbrv.jabref.org + url = https://github.com/JabRef/abbrv.jabref.org.git + ignore = all + shallow = true +[submodule "csl-styles"] + path = src/main/resources/csl-styles + url = https://github.com/citation-style-language/styles.git + ignore = all + shallow = true +[submodule "csl-locales"] + path = src/main/resources/csl-locales + url = https://github.com/citation-style-language/locales.git + ignore = all + shallow = true diff --git a/jabref/.gitpod.Dockerfile b/jabref/.gitpod.Dockerfile new file mode 100644 index 00000000..61c8da1a --- /dev/null +++ b/jabref/.gitpod.Dockerfile @@ -0,0 +1,8 @@ +# See https://www.gitpod.io/docs/java-in-gitpod/ for a full documentation of Java in GitPod + +FROM gitpod/workspace-full + +# All available versions can be listed using sdk ls java +# More information about SDKMAN available at https://github.com/sdkman/sdkman-cli#sdkman-cli +RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh \ + && sdk install java 23-open" diff --git a/jabref/.gitpod.yml b/jabref/.gitpod.yml new file mode 100644 index 00000000..a0aa262e --- /dev/null +++ b/jabref/.gitpod.yml @@ -0,0 +1,16 @@ +image: + file: .gitpod.Dockerfile +tasks: + - init: ./gradlew assemble +jetbrains: + intellij: + plugins: + - CheckStyle-IDEA + - zielu.gittoolbox +vscode: + extensions: + - redhat.java + - richardwillis.vscode-gradle + - vscjava.vscode-java-debug + - kiteco.kite + - DavidAnson.vscode-markdownlint diff --git a/jabref/.lycheeignore b/jabref/.lycheeignore new file mode 100644 index 00000000..1e0316e2 --- /dev/null +++ b/jabref/.lycheeignore @@ -0,0 +1,16 @@ +https://arxiv.org/ +https://chrome.google.com/ +https://contribute.jabref.org/ +https://donations.jabref.org/ +https://pubs.acs.org/ +https://scholar.archive.org/ +https://web.archive.org/ +https://www.researchgate.net/privacy-policy +https://www.sciencedirect.com/ + +https://github.com/koppor/jabref/issue +https://github.com/koppor/jabref/pull +https://github.com/JabRef/jabref/issue +https://github.com/JabRef/jabref/pull + +0005-example.md diff --git a/jabref/.markdownlint.yml b/jabref/.markdownlint.yml new file mode 100644 index 00000000..fc1907c9 --- /dev/null +++ b/jabref/.markdownlint.yml @@ -0,0 +1,8 @@ +default: true + +# allow arbitrary line length +MD013: false + +MD033: + # we have tags with ids and superscript + allowed_elements: ['a', 'kbd', 'sup'] diff --git a/jabref/.vscode/extensions.json b/jabref/.vscode/extensions.json new file mode 100644 index 00000000..d9b2c4ab --- /dev/null +++ b/jabref/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "davidanson.vscode-markdownlint", + "ltex-plus.vscode-ltex-plus" + ] +} diff --git a/jabref/.vscode/ltex.dictionary.en-US.txt b/jabref/.vscode/ltex.dictionary.en-US.txt new file mode 100644 index 00000000..49996eb0 --- /dev/null +++ b/jabref/.vscode/ltex.dictionary.en-US.txt @@ -0,0 +1,7 @@ +Checkstyle +CouchDB +JabDrive +JabRef +OpenFastTrace +OpenRewrite +Temurin diff --git a/jabref/.vscode/settings.json b/jabref/.vscode/settings.json new file mode 100644 index 00000000..2094775d --- /dev/null +++ b/jabref/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive", + "java.format.settings.url": "/config/VSCode Code Style.xml", + "java.checkstyle.configuration": "${workspaceFolder}/config/checkstyle/checkstyle_reviewdog.xml", + "java.checkstyle.version": "10.3.4" +} diff --git a/jabref/CHANGELOG.md b/jabref/CHANGELOG.md new file mode 100644 index 00000000..2921d7cc --- /dev/null +++ b/jabref/CHANGELOG.md @@ -0,0 +1,1498 @@ +# Changelog + +All notable changes to this project will be documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `#NUM`. +In case, there is no issue present, the pull request implementing the feature is linked. + +Note that this project **does not** adhere to [Semantic Versioning](https://semver.org/). + +## [Unreleased] + +### Added + +- We added a "view as BibTeX" option before importing an entry from the citation relation tab. [#11826](https://github.com/JabRef/jabref/issues/11826) +- We added support finding LaTeX-encoded special characters based on plain Unicode and vice versa. [#11542](https://github.com/JabRef/jabref/pull/11542) +- When a search hits a file, the file icon of that entry is changed accordingly. [#11542](https://github.com/JabRef/jabref/pull/11542) +- We added an AI-based chat for entries with linked PDF files. [#11430](https://github.com/JabRef/jabref/pull/11430) +- We added an AI-based summarization possibility for entries with linked PDF files. [#11430](https://github.com/JabRef/jabref/pull/11430) +- We added an AI section in JabRef's [preferences](https://docs.jabref.org/ai/preferences). [#11430](https://github.com/JabRef/jabref/pull/11430) +- We added AI providers: OpenAI, Mistral AI, Hugging Face and Google. [#11430](https://github.com/JabRef/jabref/pull/11430), [#11736](https://github.com/JabRef/jabref/pull/11736) +- We added AI providers: [Ollama](https://docs.jabref.org/ai/local-llm#step-by-step-guide-for-ollama) and GPT4All, which add the possibility to use local LLMs privately on your own device. [#11430](https://github.com/JabRef/jabref/pull/11430), [#11870](https://github.com/JabRef/jabref/issues/11870) +- We added support for selecting and using CSL Styles in JabRef's OpenOffice/LibreOffice integration for inserting bibliographic and in-text citations into a document. [#2146](https://github.com/JabRef/jabref/issues/2146), [#8893](https://github.com/JabRef/jabref/issues/8893) +- We added "Tools > New library based on references in PDF file" ... to create a new library based on the references section in a PDF file. [#11522](https://github.com/JabRef/jabref/pull/11522) +- When converting the references section of a paper (PDF file), more than the last page is treated. [#11522](https://github.com/JabRef/jabref/pull/11522) +- Added the functionality to invoke offline reference parsing explicitly. [#11565](https://github.com/JabRef/jabref/pull/11565) +- The dialog for [adding an entry using reference text](https://docs.jabref.org/collect/newentryfromplaintext) is now filled with the clipboard contents as default. [#11565](https://github.com/JabRef/jabref/pull/11565) +- Added minimal support for [biblatex data annotation](https://mirrors.ctan.org/macros/latex/contrib/biblatex/doc/biblatex.pdf#subsection.3.7) fields in `.layout` files. [#11505](https://github.com/JabRef/jabref/issues/11505) +- Added saving of selected options in the [Lookup -> Search for unlinked local files dialog](https://docs.jabref.org/collect/findunlinkedfiles#link-the-pdfs-to-your-bib-library). [#11439](https://github.com/JabRef/jabref/issues/11439) +- We enabled creating a new file link manually. [#11017](https://github.com/JabRef/jabref/issues/11017) +- We added a toggle button to invert the selected groups. [#9073](https://github.com/JabRef/jabref/issues/9073) +- We reintroduced the floating search in the main table. [#4237](https://github.com/JabRef/jabref/issues/4237) +- We improved [cleanup](https://docs.jabref.org/finding-sorting-and-cleaning-entries/cleanupentries) of `arXiv` IDs in distributed in the fields `note`, `version`, `institution`, and `eid` fields. [#11306](https://github.com/JabRef/jabref/issues/11306) +- We added a switch not to store the linked file URL, because it caused troubles at other apps. [#11735](https://github.com/JabRef/jabref/pull/11735) +- When starting a new SLR, the selected catalogs now persist within and across JabRef sessions. [koppor#614](https://github.com/koppor/jabref/issues/614) +- We added support for drag'n'drop on an entry in the maintable to an external application to get the entry preview dropped. [#11846](https://github.com/JabRef/jabref/pull/11846) +- We added the functionality to double click on a [LaTeX citation](https://docs.jabref.org/advanced/entryeditor/latex-citations) to jump to the respective line in the LaTeX editor. [#11996](https://github.com/JabRef/jabref/issues/11996) +- We added a different background color to the search bar to indicate when the search syntax is wrong. [#11658](https://github.com/JabRef/jabref/pull/11658) +- We added a setting which always adds the literal "Cited on pages" text before each JStyle citation. [#11691](https://github.com/JabRef/jabref/pull/11732) +- We added a new plain citation parser that uses LLMs. [#11825](https://github.com/JabRef/jabref/issues/11825) +- We added support for modifier keys when dropping a file on an entry in the main table. [#12001](https://github.com/JabRef/jabref/pull/12001) +- We added an importer for SSRN URLs. [#12021](https://github.com/JabRef/jabref/pull/12021) +- We added a compare button to the duplicates in the citation relations tab to open the "Possible duplicate entries" window. [#11192](https://github.com/JabRef/jabref/issues/11192) +- We added automatic browser extension install on Windows for Chrome and Edge. [#6076](https://github.com/JabRef/jabref/issues/6076) +- We added support to automatically open a `.bib` file in the current/parent folder if no other library is opened. [koppor#377](https://github.com/koppor/jabref/issues/377) +- We added a search bar for filtering keyboard shortcuts. [#11686](https://github.com/JabRef/jabref/issues/11686) +- By double clicking on a local citation in the Citation Relations Tab you can now jump the linked entry. [#11955](https://github.com/JabRef/jabref/pull/11955) +- We use the menu icon for background tasks as a progress indicator to visualise an import's progress when dragging and dropping several PDF files into the main table. [#12072](https://github.com/JabRef/jabref/pull/12072) + +### Changed + +- A search in "any" fields ignores the [groups](https://docs.jabref.org/finding-sorting-and-cleaning-entries/groups). [#7996](https://github.com/JabRef/jabref/issues/7996) +- When a communication error with an [online service](https://docs.jabref.org/collect/import-using-online-bibliographic-database) occurs, JabRef displays the HTTP error. [#11223](https://github.com/JabRef/jabref/issues/11223) +- The Pubmed/Medline Plain importer now imports the PMID field as well [#11488](https://github.com/JabRef/jabref/issues/11488) +- The 'Check for updates' menu bar button is now always enabled. [#11485](https://github.com/JabRef/jabref/pull/11485) +- JabRef respects the [configuration for storing files relative to the .bib file](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#directories-for-files) in more cases. [#11492](https://github.com/JabRef/jabref/pull/11492) +- JabRef does not show finished background tasks in the status bar popup. [#11821](https://github.com/JabRef/jabref/pull/11821) +- We enhanced the indexing speed. [#11502](https://github.com/JabRef/jabref/pull/11502) +- When dropping a file into the main table, after copy or move, the file is now put in the [configured directory and renamed according to the configured patterns](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#filename-format-and-file-directory-pattern). [#12001](https://github.com/JabRef/jabref/pull/12001) +- ⚠️ Renamed command line parameters `embeddBibfileInPdf` to `embedBibFileInPdf`, `writeMetadatatoPdf` to `writeMetadataToPdf`, and `writeXMPtoPdf` to `writeXmpToPdf`. [#11575](https://github.com/JabRef/jabref/pull/11575) +- The browse button for a Custom theme now opens in the directory of the current used CSS file. [#11597](https://github.com/JabRef/jabref/pull/11597) +- The browse button for a Custom exporter now opens in the directory of the current used exporter file. [#11717](https://github.com/JabRef/jabref/pull/11717) +- ⚠️ We relaxed the escaping requirements for [bracketed patterns](https://docs.jabref.org/setup/citationkeypatterns), which are used for the [citaton key generator](https://docs.jabref.org/advanced/entryeditor#autogenerate-citation-key) and [filename and directory patterns](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#auto-linking-files). One only needs to write `\"` if a quote sign should be escaped. All other escapings are not necessary (and working) any more. [#11967](https://github.com/JabRef/jabref/pull/11967) +- When importing BibTeX data starging from on a PDF, the XMP metadata takes precedence over Grobid data. [#11992](https://github.com/JabRef/jabref/pull/11992) +- JabRef now uses TLS 1.2 for all HTTPS connections. [#11852](https://github.com/JabRef/jabref/pull/11852) +- We improved the functionality of getting BibTeX data out of PDF files. [#11999](https://github.com/JabRef/jabref/issues/11999) +- We improved the display of long messages in the integrity check dialog. [#11619](https://github.com/JabRef/jabref/pull/11619) +- We improved the undo/redo buttons in the main toolbar and main menu to be disabled when there is nothing to undo/redo. [#8807](https://github.com/JabRef/jabref/issues/8807) +- We improved the DOI detection in PDF imports. [#11782](https://github.com/JabRef/jabref/pull/11782) +- We improved the performance when pasting and importing entries in an existing library. [#11843](https://github.com/JabRef/jabref/pull/11843) +- When fulltext search is selected but indexing is deactivated, a dialog is now shown asking if the user wants to enable indexing now [#9491](https://github.com/JabRef/jabref/issues/9491) +- We changed instances of 'Search Selected' to 'Search Pre-configured' in Web Search Preferences UI. [#11871](https://github.com/JabRef/jabref/pull/11871) +- We added a new CSS style class `main-table` for the main table. [#11881](https://github.com/JabRef/jabref/pull/11881) +- When renaming a file, the old extension is now used if there is none provided in the new name. [#11903](https://github.com/JabRef/jabref/issues/11903) +- When importing a file using "Find Unlinked Files", when one or more file directories are available, the file path will be relativized where possible [koppor#549](https://github.com/koppor/jabref/issues/549) +- We added minimum window sizing for windows dedicated to creating new entries [#11944](https://github.com/JabRef/jabref/issues/11944) +- We changed the name of the library-based file directory from 'General File Directory' to 'Library-specific File Directory' per issue. [#571](https://github.com/koppor/jabref/issues/571) +- We changed the defualt [unwanted charachters](https://docs.jabref.org/setup/citationkeypatterns#removing-unwanted-characters) in the citation key generator and allow a dash (`-`) and colon (`:`) being part of a citation key. [#12144](https://github.com/JabRef/jabref/pull/12144) +- The CitationKey column is now a default shown column for the entry table. [#10510](https://github.com/JabRef/jabref/issues/10510) + +### Fixed + +- We fixed an issue where certain actions were not disabled when no libraries were open. [#11923](https://github.com/JabRef/jabref/issues/11923) +- We fixed an issue where the "Check for updates" preference was not saved. [#11485](https://github.com/JabRef/jabref/pull/11485) +- We fixed an issue where an exception was thrown after changing "show preview as a tab" in the preferences. [#11515](https://github.com/JabRef/jabref/pull/11515) +- We fixed an issue where JabRef put file paths as absolute path when an entry was created using drag and drop of a PDF file. [#11173](https://github.com/JabRef/jabref/issues/11173) +- We fixed an issue that online and offline mode for new library creation were handled incorrectly. [#11565](https://github.com/JabRef/jabref/pull/11565) +- We fixed an issue with colors in the search bar when dark theme is enabled. [#11569](https://github.com/JabRef/jabref/issues/11569) +- We fixed an issue with query transformers (JStor and others). [#11643](https://github.com/JabRef/jabref/pull/11643) +- We fixed an issue where a new unsaved library was not marked with an asterisk. [#11519](https://github.com/JabRef/jabref/pull/11519) +- We fixed an issue where JabRef starts without window decorations. [#11440](https://github.com/JabRef/jabref/pull/11440) +- We fixed an issue where the entry preview highlight was not working when searching before opening the entry editor. [#11659](https://github.com/JabRef/jabref/pull/11659) +- We fixed an issue where text in Dark mode inside "Citation information" was not readable. [#11512](https://github.com/JabRef/jabref/issues/11512) +- We fixed an issue where the selection of an entry in the table lost after searching for a group. [#3176](https://github.com/JabRef/jabref/issues/3176) +- We fixed the non-functionality of the option "Automatically sync bibliography when inserting citations" in the OpenOffice panel, when enabled in case of JStyles. [#11684](https://github.com/JabRef/jabref/issues/11684) +- We fixed an issue where the library was not marked changed after a migration. [#11542](https://github.com/JabRef/jabref/pull/11542) +- We fixed an issue where rebuilding the full-text search index was not working. [#11374](https://github.com/JabRef/jabref/issues/11374) +- We fixed an issue where the progress of indexing linked files showed an incorrect number of files. [#11378](https://github.com/JabRef/jabref/issues/11378) +- We fixed an issue where the full-text search results were incomplete. [#8626](https://github.com/JabRef/jabref/issues/8626) +- We fixed an issue where search result highlighting was incorrectly highlighting the boolean operators. [#11595](https://github.com/JabRef/jabref/issues/11595) +- We fixed an issue where search result highlighting was broken at complex searches. [#8067](https://github.com/JabRef/jabref/issues/8067) +- We fixed an exception when searching for unlinked files. [#11731](https://github.com/JabRef/jabref/issues/11731) +- We fixed an issue with the link to the full text at the BVB fetcher. [#11852](https://github.com/JabRef/jabref/pull/11852) +- We fixed an issue where two contradicting notifications were shown when cutting an entry in the main table. [#11724](https://github.com/JabRef/jabref/pull/11724) +- We fixed an issue where unescaped braces in the arXiv fetcher were not treated. [#11704](https://github.com/JabRef/jabref/issues/11704) +- We fixed an issue where HTML instead of the fulltext pdf was downloaded when importing arXiv entries. [#4913](https://github.com/JabRef/jabref/issues/4913) +- We fixed an issue where the keywords and crossref fields were not properly focused. [#11177](https://github.com/JabRef/jabref/issues/11177) +- We fixed handling of `\"` in [bracketed patterns](https://docs.jabref.org/setup/citationkeypatterns) containing a RegEx. [#11967](https://github.com/JabRef/jabref/pull/11967) +- We fixed an issue where the Undo/Redo buttons were active even when all libraries are closed. [#11837](https://github.com/JabRef/jabref/issues/11837) +- We fixed an issue where recently opened files were not displayed in the main menu properly. [#9042](https://github.com/JabRef/jabref/issues/9042) +- We fixed an issue where the DOI lookup would show an error when a DOI was found for an entry. [#11850](https://github.com/JabRef/jabref/issues/11850) +- We fixed an issue where Tab cannot be used to jump to next field in some single-line fields. [#11785](https://github.com/JabRef/jabref/issues/11785) +- We fixed an issue where the "Do not ask again" checkbox was not working, when asking for permission to use Grobid [koppor#556](https://github.com/koppor/jabref/issues/566). +- We fixed an issue where we display warning message for moving attached open files. [#10121](https://github.com/JabRef/jabref/issues/10121) +- We fixed an issue where it was not possible to select selecting content of other user's comments.[#11106](https://github.com/JabRef/jabref/issues/11106) +- We fixed an issue where web search preferences "Custom API key" table modifications not discarded. [#11925](https://github.com/JabRef/jabref/issues/11925) +- We fixed an issue when opening attached files in [extra file columns](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#adding-additional-columns-to-entry-table-for-file-types). [#12005](https://github.com/JabRef/jabref/issues/12005) +- We fixed an issue where trying to open a library from a failed mounted directory on Mac would cause an error. [#10548](https://github.com/JabRef/jabref/issues/10548) +- We fixed an issue when the preview was out of sync. [#9172](https://github.com/JabRef/jabref/issues/9172) +- We fixed an issue where identifier paste couldn't work with Unicode REPLACEMENT CHARACTER. [#11986](https://github.com/JabRef/jabref/issues/11986) + +### Removed + +- We removed the description of search strings. [#11542](https://github.com/JabRef/jabref/pull/11542) +- We removed support for importing using the SilverPlatterImporter (`Record INSPEC`). [#11576](https://github.com/JabRef/jabref/pull/11576) +- We removed support for automatically generating file links using the CLI (`--automaticallySetFileLinks`). + + + + + + + +## [5.15] – 2024-07-10 + +### Added + +- We made new groups automatically to focus upon creation. [#11449](https://github.com/JabRef/jabref/issues/11449) + +### Fixed + +- We fixed an issue where JabRef was no longer built for Intel based macs (x86) [#11468](https://github.com/JabRef/jabref/issues/11468) +- We fixed usage when using running on Snapcraft. [#11465](https://github.com/JabRef/jabref/issues/11465) +- We fixed detection for `soffice.exe` on Windows. [#11478](https://github.com/JabRef/jabref/pull/11478) +- We fixed an issue where saving preferences when importing preferences on first run in a snap did not work [forum#4399](https://discourse.jabref.org/t/how-to-report-problems-in-the-distributed-version-5-14-ensuring-that-one-can-no-longer-work-with-jabref/4399/5) + +## [5.14] – 2024-07-08 + +### Added + +- We added support for offline extracting references from PDFs following the IEEE format. [#11156](https://github.com/JabRef/jabref/pull/11156) +- We added a new keyboard shortcut ctrl + , to open the preferences. [#11154](https://github.com/JabRef/jabref/pull/11154) +- We added value selection (such as for month) for content selectors in custom entry types. [#11109](https://github.com/JabRef/jabref/issues/11109) +- We added a duplicate checker for the Citation Relations tab. [#10414](https://github.com/JabRef/jabref/issues/10414) +- We added tooltip on main table cells that shows cell content or cell content and entry preview if set in preferences. [10925](https://github.com/JabRef/jabref/issues/10925) +- Added a formatter to remove word enclosing braces. [#11222](https://github.com/JabRef/jabref/issues/11222) +- We added the ability to add a keyword/crossref when typing the separator character (e.g., comma) in the keywords/crossref fields. [#11178](https://github.com/JabRef/jabref/issues/11178) +- We added an exporter and improved the importer for Endnote XML format. [#11137](https://github.com/JabRef/jabref/issues/11137) +- We added support for using BibTeX Style files (BST) in the Preview. [#11102](https://github.com/JabRef/jabref/issues/11102) +- We added support for automatically update LaTeX citations when a LaTeX file is created, removed, or modified. [#10585](https://github.com/JabRef/jabref/issues/10585) + +### Changed + +- We replaced the word "Key bindings" with "Keyboard shortcuts" in the Preferences tab. [#11153](https://github.com/JabRef/jabref/pull/11153) +- We slightly improved the duplicate check if ISBNs are present. [#8885](https://github.com/JabRef/jabref/issues/8885) +- JabRef no longer downloads HTML files of websites when a PDF was not found. [#10149](https://github.com/JabRef/jabref/issues/10149) +- We added the HTTP message (in addition to the response code) if an error is encountered. [#11341](https://github.com/JabRef/jabref/pull/11341) +- We made label wrap text to fit view size when reviewing external group changes. [#11220](https://github.com/JabRef/jabref/issues/11220) + +### Fixed + +- We fixed an issue where entry type with duplicate fields prevented opening existing libraries with custom entry types. [#11127](https://github.com/JabRef/jabref/issues/11127) +- We fixed an issue where Markdown rendering removed braces from the text. [#10928](https://github.com/JabRef/jabref/issues/10928) +- We fixed an issue when the file was flagged as changed on disk in the case of content selectors or groups. [#9064](https://github.com/JabRef/jabref/issues/9064) +- We fixed crash on opening the entry editor when auto-completion is enabled. [#11188](https://github.com/JabRef/jabref/issues/11188) +- We fixed the usage of the key binding for "Clear search" (default: Escape). [#10764](https://github.com/JabRef/jabref/issues/10764) +- We fixed an issue where library shown as unsaved and marked (*) after accepting changes made externally to the file. [#11027](https://github.com/JabRef/jabref/issues/11027) +- We fixed an issue where drag and dropping entries from one library to another was not always working. [#11254](https://github.com/JabRef/jabref/issues/11254) +- We fixed an issue where drag and dropping entries created a shallow copy. [#11160](https://github.com/JabRef/jabref/issues/11160) +- We fixed an issue where imports to a custom group would only work for the first entry [#11085](https://github.com/JabRef/jabref/issues/11085), [#11269](https://github.com/JabRef/jabref/issues/11269) +- We fixed an issue when cursor jumped to the beginning of the line. [#5904](https://github.com/JabRef/jabref/issues/5904) +- We fixed an issue where a new entry was not added to the selected group [#8933](https://github.com/JabRef/jabref/issues/8933) +- We fixed an issue where the horizontal position of the Entry Preview inside the entry editor was not remembered across restarts [#11281](https://github.com/JabRef/jabref/issues/11281) +- We fixed an issue where the search index was not updated after linking PDF files. [#11317](https://github.com/JabRef/jabref/pull/11317) +- We fixed rendering of (first) author with a single letter surname. [forum#4330](https://discourse.jabref.org/t/correct-rendering-of-first-author-with-a-single-letter-surname/4330) +- We fixed that the import of the related articles tab sometimes used the wrong library mode. [#11282](https://github.com/JabRef/jabref/pull/11282) +- We fixed an issue where the entry editor context menu was not shown correctly when JabRef is opened on a second, extended screen [#11323](https://github.com/JabRef/jabref/issues/11323), [#11174](https://github.com/JabRef/jabref/issues/11174) +- We fixed an issue where the value of "Override default font settings" was not applied on startup [#11344](https://github.com/JabRef/jabref/issues/11344) +- We fixed an issue when "Library changed on disk" appeared after a save by JabRef. [#4877](https://github.com/JabRef/jabref/issues/4877) +- We fixed an issue where the Pubmed/Medline Plain importer would not respect the user defined keyword separator [#11413](https://github.com/JabRef/jabref/issues/11413) +- We fixed an issue where the value of "Override default font settings" was not applied on startup [#11344](https://github.com/JabRef/jabref/issues/11344) +- We fixed an issue where DatabaseChangeDetailsView was not scrollable when reviewing external metadata changes [#11220](https://github.com/JabRef/jabref/issues/11220) +- We fixed undo/redo for text fields. [#11420](https://github.com/JabRef/jabref/issues/11420) +- We fixed an issue where clicking on a page number in the search results tab opens a wrong file in the document viewer. [#11432](https://github.com/JabRef/jabref/pull/11432) + +### Removed + +- We removed the misleading message "Doing a cleanup for X entries" when opening the Cleanup entries dialog [#11463](https://github.com/JabRef/jabref/pull/11463) + +## [5.13] – 2024-04-01 + +### Added + +- We converted the "Custom API key" list to a table to be more accessible. [#10926](https://github.com/JabRef/jabref/issues/10926) +- We added a "refresh" button for the LaTeX citations tab in the entry editor. [#10584](https://github.com/JabRef/jabref/issues/10584) +- We added the possibility to show the BibTeX source in the [web search](https://docs.jabref.org/collect/import-using-online-bibliographic-database) import screen. [#560](https://github.com/koppor/jabref/issues/560) +- We added a fetcher for [ISIDORE](https://isidore.science/), simply paste in the link into the text field or the last 6 digits in the link that identify that paper. [#10423](https://github.com/JabRef/jabref/issues/10423) +- When importing entries form the "Citation relations" tab, the field [cites](https://docs.jabref.org/advanced/entryeditor/entrylinks) is now filled according to the relationship between the entries. [#10572](https://github.com/JabRef/jabref/pull/10752) +- We added a new integrity check and clean up option for strings having Unicode characters not encoded in [Unicode "Normalization Form Canonical Composition" (NFC)](https://en.wikipedia.org/wiki/Unicode_equivalence#Normal_forms"). [#10506](https://github.com/JabRef/jabref/issues/10506) +- We added a new group icon column to the main table showing the icons of the entry's groups. [#10801](https://github.com/JabRef/jabref/pull/10801) +- When deleting an entry, the files linked to the entry are now optionally deleted as well. [#10509](https://github.com/JabRef/jabref/issues/10509) +- We added support to move the file to the system trash (instead of deleting it). [#10591](https://github.com/JabRef/jabref/pull/10591) +- We added ability to jump to an entry in the command line using `-j CITATIONKEY`. [koppor#540](https://github.com/koppor/jabref/issues/540) +- We added a new boolean to the style files for Openoffice/Libreoffice integration to switch between ZERO_WIDTH_SPACE (default) and no space. [#10843](https://github.com/JabRef/jabref/pull/10843) +- When pasting HTML into the abstract or a comment field, the hypertext is automatically converted to Markdown. [#10558](https://github.com/JabRef/jabref/issues/10558) +- We added the possibility to redownload files that had been present but are no longer in the specified location. [#10848](https://github.com/JabRef/jabref/issues/10848) +- We added the citation key pattern `[camelN]`. Equivalent to the first N words of the `[camel]` pattern. +- We added importing of static groups and linked files from BibDesk .bib files. [#10381](https://github.com/JabRef/jabref/issues/10381) +- We added ability to export in CFF (Citation File Format) [#10661](https://github.com/JabRef/jabref/issues/10661). +- We added ability to push entries to TeXworks. [#3197](https://github.com/JabRef/jabref/issues/3197) +- We added the ability to zoom in and out in the document viewer using Ctrl + Scroll. [#10964](https://github.com/JabRef/jabref/pull/10964) +- We added a Cleanup for removing non-existent files and grouped the related options [#10929](https://github.com/JabRef/jabref/issues/10929) +- We added the functionality to parse the bibliography of PDFs using the GROBID online service. [#10200](https://github.com/JabRef/jabref/issues/10200) +- We added a seperated search bar for the global search window. [#11032](https://github.com/JabRef/jabref/pull/11032) +- We added ability to double-click on an entry in the global search window to select the corresponding entry in the main table. [#11010](https://github.com/JabRef/jabref/pull/11010) +- We added support for BibTeX String constants during copy & paste between libraries. [#10872](https://github.com/JabRef/jabref/issues/10872) +- We added the field `langid` which is important for hyphenation and casing in LaTeX. [#10868](https://github.com/JabRef/jabref/issues/10868) +- Event log entries can now be copied via a context menu. [#11100](https://github.com/JabRef/jabref/issues/11100) + +### Changed + +- The "Automatically open folders of attached files" preference default status has been changed to enabled on Windows. [koppor#56](https://github.com/koppor/jabref/issues/56) +- The Custom export format now uses the custom DOI base URI in the preferences for the `DOICheck`, if activated [forum#4084](https://discourse.jabref.org/t/export-html-disregards-custom-doi-base-uri/4084) +- The index directories for full text search have now more readable names to increase debugging possibilities using Apache Lucense's Lurk. [#10193](https://github.com/JabRef/jabref/issues/10193) +- The fulltext search also indexes files ending with .pdf (but do not having an explicit file type set). [#10193](https://github.com/JabRef/jabref/issues/10193) +- We changed the arrangement of the lists in the "Citation relations" tab. `Cites` are now on the left and `Cited by` on the right [#10572](https://github.com/JabRef/jabref/pull/10752) +- Sub libraries based on `aux` file can now also be generated if some citations are not found library. [#10775](https://github.com/JabRef/jabref/pull/10775) +- We rearranged the tab order in the entry editor and renamed the "Scite Tab" to "Citation information". [#10821](https://github.com/JabRef/jabref/issues/10821) +- We changed the duplicate handling in the Import entries dialog. Potential duplicate entries are marked with an icon and importing will now trigger the merge dialog [#10914](https://github.com/JabRef/jabref/pull/10914) +- We made the command "Push to TexShop" more robust to allow cite commands with a character before the first slash. [forum#2699](https://discourse.jabref.org/t/push-to-texshop-mac/2699/17?u=siedlerchr) +- We only show the notification "Saving library..." if the library contains more than 2000 entries. [#9803](https://github.com/JabRef/jabref/issues/9803) +- JabRef now keeps previous log files upon start. [#11023](https://github.com/JabRef/jabref/pull/11023) +- When normalizing author names, complete enclosing braces are kept. [#10031](https://github.com/JabRef/jabref/issues/10031) +- We enhanced the dialog for adding new fields in the content selector with a selection box containing a list of standard fields. [#10912](https://github.com/JabRef/jabref/pull/10912) +- We store the citation relations in an LRU cache to avoid bloating the memory and out-of-memory exceptions. [#10958](https://github.com/JabRef/jabref/issues/10958) +- Keywords field are now displayed as tags. [#10910](https://github.com/JabRef/jabref/pull/10910) +- Citation relations now get more information, and have quick access to view the articles in a browser without adding them to the library [#10869](https://github.com/JabRef/jabref/issues/10869) +- Importer/Exporter for CFF format now supports JabRef `cites` and `related` relationships, as well as all fields from the CFF specification. [#10993](https://github.com/JabRef/jabref/issues/10993) +- The XMP-Exporter no longer writes the content of the `file`-field. [#11083](https://github.com/JabRef/jabref/pull/11083) +- We added notes, checks and warnings for the case of selection of non-empty directories while starting a new Systematic Literature Review. [#600](https://github.com/koppor/jabref/issues/600) +- Text in the import dialog (web search results) will now be wrapped to prevent horizontal scrolling. [#10931](https://github.com/JabRef/jabref/issues/10931) +- We improved the error handling when invalid bibdesk-files are encountered [#11117](https://github.com/JabRef/jabref/issues/11117) + +### Fixed + +- We fixed an issue where the fulltext search button in entry editor used to disappear on click till the search is completed. [#10425](https://github.com/JabRef/jabref/issues/10425) +- We fixed an issue where attempting to cancel the importing/generation of an entry from id is ignored. [#10508](https://github.com/JabRef/jabref/issues/10508) +- We fixed an issue where the preview panel showing the wrong entry (an entry that is not selected in the entry table). [#9172](https://github.com/JabRef/jabref/issues/9172) +- We fixed an issue where HTML-reserved characters like '&' and '<', in addition to HTML entities like '&' were not rendered correctly in entry preview. [#10677](https://github.com/JabRef/jabref/issues/10677) +- The last page of a PDF is now indexed by the full text search. [#10193](https://github.com/JabRef/jabref/issues/10193) +- The entry editor respects the configured custom tabs when showing "Other fields". [#11012](https://github.com/JabRef/jabref/pull/11012) +- The default owner of an entry can be changed again. [#10924](https://github.com/JabRef/jabref/issues/10924) +- We fixed an issue where the duplicate check did not take umlauts or other LaTeX-encoded characters into account. [#10744](https://github.com/JabRef/jabref/pull/10744) +- We fixed the colors of the icon on hover for unset special fields. [#10431](https://github.com/JabRef/jabref/issues/10431) +- We fixed an issue where the CrossRef field did not work if autocompletion was disabled [#8145](https://github.com/JabRef/jabref/issues/8145) +- In biblatex mode, JabRef distinguishes between "Optional fields" and "Optional fields 2" again. [#11022](https://github.com/JabRef/jabref/pull/11022) +- We fixed an issue where exporting`@electronic` and `@online` entry types to the Office XMl would duplicate the field `title` [#10807](https://github.com/JabRef/jabref/issues/10807) +- We fixed an issue where the `CommentsTab` was not properly formatted when the `defaultOwner` contained capital or special letters. [#10870](https://github.com/JabRef/jabref/issues/10870) +- We fixed an issue where the `File -> Close library` menu item was not disabled when no library was open. [#10948](https://github.com/JabRef/jabref/issues/10948) +- We fixed an issue where the Document Viewer would show the PDF in only half the window when maximized. [#10934](https://github.com/JabRef/jabref/issues/10934) +- Clicking on the crossref and related tags in the entry editor jumps to the linked entry. [#5484](https://github.com/JabRef/jabref/issues/5484) [#9369](https://github.com/JabRef/jabref/issues/9369) +- We fixed an issue where JabRef could not parse absolute file paths from Zotero exports. [#10959](https://github.com/JabRef/jabref/issues/10959) +- We fixed an issue where an exception occured when toggling between "Live" or "Locked" in the internal Document Viewer. [#10935](https://github.com/JabRef/jabref/issues/10935) +- When fetching article information fom IEEE Xplore, the em dash is now converted correctly. [koppor#286](https://github.com/koppor/jabref/issues/286) +- Fixed an issue on Windows where the browser extension reported failure to send an entry to JabRef even though it was sent properly. [JabRef-Browser-Extension#493](https://github.com/JabRef/JabRef-Browser-Extension/issues/493) +- Fixed an issue on Windows where TeXworks path was not resolved if it was installed with MiKTeX. [#10977](https://github.com/JabRef/jabref/issues/10977) +- We fixed an issue with where JabRef would throw an error when using MathSciNet search, as it was unable to parse the fetched JSON coreectly. [10996](https://github.com/JabRef/jabref/issues/10996) +- We fixed an issue where the "Import by ID" function would throw an error when a DOI that contains URL-encoded characters was entered. [#10648](https://github.com/JabRef/jabref/issues/10648) +- We fixed an issue with handling of an "overflow" of authors at `[authIniN]`. [#11087](https://github.com/JabRef/jabref/issues/11087) +- We fixed an issue where an exception occurred when selecting entries in the web search results. [#11081](https://github.com/JabRef/jabref/issues/11081) +- When a new library is unsaved, there is now no warning when fetching entries with PDFs. [#11075](https://github.com/JabRef/jabref/issues/11075) +- We fixed an issue where the message "The libary has been modified by another program" occurred when editing library metadata and saving the library. [#4877](https://github.com/JabRef/jabref/issues/4877) + +### Removed + +- We removed the predatory journal checks due to a high rate of false positives. [#11066](https://github.com/JabRef/jabref/pull/11066) + +## [5.12] – 2023-12-24 + +### Added + +- We added a scite.ai tab in the entry editor that retrieves 'Smart Citation' tallies for citations that have a DOI. [koppor#375](https://github.com/koppor/jabref/issues/375) +- We added a dropdown menu to let users change the reference library during AUX file import. [#10472](https://github.com/JabRef/jabref/issues/10472) +- We added a button to let users reset the cite command to the default value. [#10569](https://github.com/JabRef/jabref/issues/10569) +- We added the option to use System Preference for Light/Dark Theme [#8729](https://github.com/JabRef/jabref/issues/8729). +- We added [scholar.archive.org](https://scholar.archive.org/) as a new fetcher. [#10498](https://github.com/JabRef/jabref/issues/10498) +- We integrated predatory journal checking as part of the Integrity Checker based on the [check-bib-for-predatory](https://github.com/CfKu/check-bib-for-predatory). [koppor#348](https://github.com/koppor/jabref/issues/348) +- We added a 'More options' section in the main table right click menu opening the preferences dialog. [#9432](https://github.com/JabRef/jabref/issues/9432) +- When creating a new group, it inherits the icon of the parent group. [#10521](https://github.com/JabRef/jabref/pull/10521) + +### Changed + +- We moved the location of the 'Open only one instance of JabRef' preference option from "Network" to "General". [#9306](https://github.com/JabRef/jabref/issues/9306) +- The two previews in the change resolver dialog now have their scrollbars synchronized. [#9576](https://github.com/JabRef/jabref/issues/9576). +- We changed the setting of the keyword separator to accept a single character only. [#177](https://github.com/koppor/jabref/issues/177) +- We replaced "SearchAll" in Web Search by "Search Selected". [#10556](https://github.com/JabRef/jabref/issues/10556) +- Short DOI formatter now checks, if the value is already formatted. If so, it returns the value instead of calling the ShortDOIService again. [#10589](https://github.com/JabRef/jabref/issues/10589) +- We upgraded to JavaFX 21.0.1. As a consequence JabRef requires now macOS 11 or later and GTK 3.8 or later on Linux [10627](https://github.com/JabRef/jabref/pull/10627). +- A user-specific comment fields is not enabled by default, but can be enabled using the "Add" button. [#10424](https://github.com/JabRef/jabref/issues/10424) +- We upgraded to Lucene 9.9 for the fulltext search. The search index will be rebuild. [#10686](https://github.com/JabRef/jabref/pull/10686) +- When using "Copy..." -> "Copy citation key", the delimiter configured at "Push applications" is respected. [#10707](https://github.com/JabRef/jabref/pull/10707) + +### Fixed + +- We fixed an issue where the added protected term has unwanted leading and trailing whitespaces, where the formatted text has unwanted empty brackets and where the word at the cursor in the textbox can be added to the list. [#10415](https://github.com/JabRef/jabref/issues/10415) +- We fixed an issue where in the merge dialog the file field of entries was not correctly merged when the first and second entry both contained values inside the file field. [#10572](https://github.com/JabRef/jabref/issues/10572) +- We fixed some small inconsistencies in the user interface. [#10507](https://github.com/JabRef/jabref/issues/10507) [#10458](https://github.com/JabRef/jabref/issues/10458) [#10660](https://github.com/JabRef/jabref/issues/10660) +- We fixed the issue where the Hayagriva YAML exporter would not include a parent field for the publisher/series. [#10596](https://github.com/JabRef/jabref/issues/10596) +- We fixed issues in the external file type dialog w.r.t. duplicate entries in the case of a language switch. [#10271](https://github.com/JabRef/jabref/issues/10271) +- We fixed an issue where the right-click action "Copy cite..." did not respect the configured citation command under "External Programs" -> "[Push Applications](https://docs.jabref.org/cite/pushtoapplications)" [#10615](https://github.com/JabRef/jabref/issues/10615) + +### Removed + +- We removed duplicate filtering and sorting operations in the MainTable when editing BibEntries. [#10619](https://github.com/JabRef/jabref/pull/10619) + +## [5.11] – 2023-10-22 + +### Added + +- We added the ability to sort subgroups in Z-A order, as well as by ascending and descending number of subgroups. [#10249](https://github.com/JabRef/jabref/issues/10249) +- We added the possibility to find (and add) papers that cite or are cited by a given paper. [#6187](https://github.com/JabRef/jabref/issues/6187) +- We added an error-specific message for when a download from a URL fails. [#9826](https://github.com/JabRef/jabref/issues/9826) +- We added support for customizing the citation command (e.g., `[@key1,@key2]`) when [pushing to external applications](https://docs.jabref.org/cite/pushtoapplications). [#10133](https://github.com/JabRef/jabref/issues/10133) +- We added an integrity check for more special characters. [#8712](https://github.com/JabRef/jabref/issues/8712) +- We added protected terms described as "Computer science". [#10222](https://github.com/JabRef/jabref/pull/10222) +- We added a link "Get more themes..." in the preferences to that points to [themes.jabref.org](https://themes.jabref.org) allowing the user to download new themes. [#10243](https://github.com/JabRef/jabref/issues/10243) +- We added a fetcher for [LOBID](https://lobid.org/resources/api) resources. [koppor#386](https://github.com/koppor/jabref/issues/386) +- When in `biblatex` mode, the [integrity check](https://docs.jabref.org/finding-sorting-and-cleaning-entries/checkintegrity) for journal titles now also checks the field `journal`. +- We added support for exporting to Hayagriva YAML format. [#10382](https://github.com/JabRef/jabref/issues/10382) +- We added support for pushing citations to [TeXShop](https://pages.uoregon.edu/koch/texshop/) on macOS [forum#2699](https://discourse.jabref.org/t/push-to-texshop-mac/2699). +- We added the 'Bachelor's thesis' type for Biblatex's 'Thesis' EntryType [#10029](https://github.com/JabRef/jabref/issues/10029). + +### Changed + +- The export formats `listrefs`, `tablerefs`, `tablerefsabsbib`, now use the ISO date format in the footer [#10383](https://github.com/JabRef/jabref/pull/10383). +- When searching for an identifier in the "Web search", the title of the search window is now "Identifier-based Web Search". [#10391](https://github.com/JabRef/jabref/pull/10391) +- The ampersand checker now skips verbatim fields (`file`, `url`, ...). [#10419](https://github.com/JabRef/jabref/pull/10419) +- If no existing document is selected for exporting "XMP annotated pdf" JabRef will now create a new PDF file with a sample text and the metadata. [#10102](https://github.com/JabRef/jabref/issues/10102) +- We modified the DOI cleanup to infer the DOI from an ArXiV ID if it's present. [#10426](https://github.com/JabRef/jabref/issues/10426) +- The ISI importer uses the field `comment` for notes (instead of `review). [#10478](https://github.com/JabRef/jabref/pull/10478) +- If no existing document is selected for exporting "Embedded BibTeX pdf" JabRef will now create a new PDF file with a sample text and the metadata. [#10101](https://github.com/JabRef/jabref/issues/10101) +- Translated titles format no longer raise a warning. [#10459](https://github.com/JabRef/jabref/issues/10459) +- We re-added the empty grey containers in the groups panel to keep an indicator for the current selected group, if displaying of group item count is turned off [#9972](https://github.com/JabRef/jabref/issues/9972) + +### Fixed + +- We fixed an issue where "Move URL in note field to url field" in the cleanup dialog caused an exception if no note field was present [forum#3999](https://discourse.jabref.org/t/cleanup-entries-cant-get-it-to-work/3999) +- It is possible again to use "current table sort order" for the order of entries when saving. [#9869](https://github.com/JabRef/jabref/issues/9869) +- Passwords can be stored in GNOME key ring. [#10274](https://github.com/JabRef/jabref/issues/10274) +- We fixed an issue where groups based on an aux file could not be created due to an exception [#10350](https://github.com/JabRef/jabref/issues/10350) +- We fixed an issue where the JabRef browser extension could not communicate with JabRef under macOS due to missing files. You should use the `.pkg` for the first installation as it updates all necessary files for the extension [#10308](https://github.com/JabRef/jabref/issues/10308) +- We fixed an issue where the ISBN fetcher returned the entrytype `misc` for certain ISBN numbers [#10348](https://github.com/JabRef/jabref/issues/10348) +- We fixed a bug where an exception was raised when saving less than three export save orders in the preference. [#10157](https://github.com/JabRef/jabref/issues/10157) +- We fixed an issue where it was possible to create a group with no name or with a group separator inside the name [#9776](https://github.com/JabRef/jabref/issues/9776) +- Biblatex's `journaltitle` is now also respected for showing the journal information. [#10397](https://github.com/JabRef/jabref/issues/10397) +- JabRef does not hang anymore when exporting via CLI. [#10380](https://github.com/JabRef/jabref/issues/10380) +- We fixed an issue where it was not possible to save a library on a network share under macOS due to an exception when acquiring a file lock [#10452](https://github.com/JabRef/jabref/issues/10452) +- We fixed an issue where exporting "XMP annotated pdf" without selecting an existing document would produce an exception. [#10102](https://github.com/JabRef/jabref/issues/10102) +- We fixed an issue where the "Enabled" column in the "Protected terms files" tab in the preferences could not be resized [#10285](https://github.com/JabRef/jabref/issues/10285) +- We fixed an issue where after creation of a new library, the new library was not focused. [koppor#592](https://github.com/koppor/jabref/issues/592) +- We fixed an issue where double clicking on an url in the file field would trigger an exception instead of opening the browser [#10480](https://github.com/JabRef/jabref/pull/10480) +- We fixed an issue where scrolling was impossible on dragging a citation on the groups panel. [#9754](https://github.com/JabRef/jabref/issues/9754) +- We fixed an issue where exporting "Embedded BibTeX pdf" without selecting an existing document would produce an exception. [#10101](https://github.com/JabRef/jabref/issues/10101) +- We fixed an issue where there was a failure to access the url link for "eprint" for the ArXiv entry.[#10474](https://github.com/JabRef/jabref/issues/10474) +- We fixed an issue where it was not possible to connect to a shared database once a group with entries was added or other metadata modified [#10336](https://github.com/JabRef/jabref/issues/10336) +- We fixed an issue where middle-button paste in X not always worked [#7905](https://github.com/JabRef/jabref/issues/7905) + +## [5.10] – 2023-09-02 + +### Added + +- We added a field showing the BibTeX/biblatex source for added and deleted entries in the "External Changes Resolver" dialog. [#9509](https://github.com/JabRef/jabref/issues/9509) +- We added user-specific comment field so that multiple users can make separate comments. [#543](https://github.com/koppor/jabref/issues/543) +- We added a search history list in the search field's right click menu. [#7906](https://github.com/JabRef/jabref/issues/7906) +- We added a full text fetcher for IACR eprints. [#9651](https://github.com/JabRef/jabref/pull/9651) +- We added "Attach file from URL" to right-click context menu to download and store a file with the reference library. [#9646](https://github.com/JabRef/jabref/issues/9646) +- We enabled updating an existing entry with data from InspireHEP. [#9351](https://github.com/JabRef/jabref/issues/9351) +- We added a fetcher for the Bibliotheksverbund Bayern (experimental). [#9641](https://github.com/JabRef/jabref/pull/9641) +- We added support for more biblatex date formats for parsing dates. [#2753](https://github.com/JabRef/jabref/issues/2753) +- We added support for multiple languages for exporting to and importing references from MS Office. [#9699](https://github.com/JabRef/jabref/issues/9699) +- We enabled scrolling in the groups list when dragging a group on another group. [#2869](https://github.com/JabRef/jabref/pull/2869) +- We added the option to automatically download online files when a new entry is created from an existing ID (e.g., DOI). The option can be disabled in the preferences under "Import and Export". [#9756](https://github.com/JabRef/jabref/issues/9756) +- We added a new Integrity check for unescaped ampersands. [koppor#585](https://github.com/koppor/jabref/issues/585) +- We added support for parsing `$\backslash$` in file paths (as exported by Mendeley). [forum#3470](https://discourse.jabref.org/t/mendeley-bib-import-with-linked-files/3470) +- We added the possibility to automatically fetch entries when an ISBN is pasted on the main table. [#9864](https://github.com/JabRef/jabref/issues/9864) +- We added the option to disable the automatic linking of files in the entry editor [#5105](https://github.com/JabRef/jabref/issues/5105) +- We added the link icon for ISBNs in linked identifiers column. [#9819](https://github.com/JabRef/jabref/issues/9819) +- We added key binding to focus on groups alt + s [#9863](https://github.com/JabRef/jabref/issues/9863) +- We added the option to unprotect a text selection, which strips all pairs of curly braces away. [#9950](https://github.com/JabRef/jabref/issues/9950) +- We added drag and drop events for field 'Groups' in entry editor panel. [#569](https://github.com/koppor/jabref/issues/569) +- We added support for parsing MathML in the Medline importer. [#4273](https://github.com/JabRef/jabref/issues/4273) +- We added the ability to search for an identifier (DOI, ISBN, ArXiv ID) directly from 'Web Search'. [#7575](https://github.com/JabRef/jabref/issues/7575) [#9674](https://github.com/JabRef/jabref/issues/9674) +- We added a cleanup activity that identifies a URL or a last-visited-date in the `note` field and moves it to the `url` and `urldate` field respectively. [koppor#216](https://github.com/koppor/jabref/issues/216) +- We enabled the user to change the name of a field in a custom entry type by double-clicking on it. [#9840](https://github.com/JabRef/jabref/issues/9840) +- We added some preferences options to disable online activity. [#10064](https://github.com/JabRef/jabref/issues/10064) +- We integrated two mail actions ("As Email" and "To Kindle") under a new "Send" option in the right-click & Tools menus. The Kindle option creates an email targeted to the user's Kindle email, which can be set in preferences under "External programs" [#6186](https://github.com/JabRef/jabref/issues/6186) +- We added an option to clear recent libraries' history. [#10003](https://github.com/JabRef/jabref/issues/10003) +- We added an option to encrypt and remember the proxy password. [#8055](https://github.com/JabRef/jabref/issues/8055)[#10044](https://github.com/JabRef/jabref/issues/10044) +- We added support for showing journal information, via info buttons next to the `Journal` and `ISSN` fields in the entry editor. [#6189](https://github.com/JabRef/jabref/issues/6189) +- We added support for pushing citations to Sublime Text 3 [#10098](https://github.com/JabRef/jabref/issues/10098) +- We added support for the Finnish language. [#10183](https://github.com/JabRef/jabref/pull/10183) +- We added the option to automatically replaces illegal characters in the filename when adding a file to JabRef. [#10182](https://github.com/JabRef/jabref/issues/10182) +- We added a privacy policy. [#10064](https://github.com/JabRef/jabref/issues/10064) +- We added a tooltip to show the number of entries in a group [#10208](https://github.com/JabRef/jabref/issues/10208) +- We fixed an issue where it was no longer possible to add or remove selected entries to groups via context menu [#10404](https://github.com/JabRef/jabref/issues/10404), [#10317](https://github.com/JabRef/jabref/issues/10317) [#10374](https://github.com/JabRef/jabref/issues/10374) + +### Changed + +- We replaced "Close" by "Close library" and placed it after "Save all" in the File menu. [#10043](https://github.com/JabRef/jabref/pull/10043) +- We upgraded to Lucene 9.7 for the fulltext search. The search index will be rebuild. [#9584](https://github.com/JabRef/jabref/pull/10036) +- 'Get full text' now also checks the file url. [#568](https://github.com/koppor/jabref/issues/568) +- JabRef writes a new backup file only if there is a change. Before, JabRef created a backup upon start. [#9679](https://github.com/JabRef/jabref/pull/9679) +- We modified the `Add Group` dialog to use the most recently selected group hierarchical context. [#9141](https://github.com/JabRef/jabref/issues/9141) +- We refined the 'main directory not found' error message. [#9625](https://github.com/JabRef/jabref/pull/9625) +- JabRef writes a new backup file only if there is a change. Before, JabRef created a backup upon start. [#9679](https://github.com/JabRef/jabref/pull/9679) +- Backups of libraries are not stored per JabRef version, but collected together. [#9676](https://github.com/JabRef/jabref/pull/9676) +- We streamlined the paths for logs and backups: The parent path fragment is always `logs` or `backups`. +- `log.txt` now contains an entry if a BibTeX entry could not be parsed. +- `log.txt` now contains debug messages. Debugging needs to be enabled explicitly. [#9678](https://github.com/JabRef/jabref/pull/9678) +- `log.txt` does not contain entries for non-found files during PDF indexing. [#9678](https://github.com/JabRef/jabref/pull/9678) +- The hostname is now determined using environment variables (`COMPUTERNAME`/`HOSTNAME`) first. [#9910](https://github.com/JabRef/jabref/pull/9910) +- We improved the Medline importer to correctly import ISO dates for `revised`. [#9536](https://github.com/JabRef/jabref/issues/9536) +- To avoid cluttering of the directory, We always delete the `.sav` file upon successful write. [#9675](https://github.com/JabRef/jabref/pull/9675) +- We improved the unlinking/deletion of multiple linked files of an entry using the Delete key. [#9473](https://github.com/JabRef/jabref/issues/9473) +- The field names of customized entry types are now exchanged preserving the case. [#9993](https://github.com/JabRef/jabref/pull/9993) +- We moved the custom entry types dialog into the preferences dialog. [#9760](https://github.com/JabRef/jabref/pull/9760) +- We moved the manage content selectors dialog to the library properties. [#9768](https://github.com/JabRef/jabref/pull/9768) +- We moved the preferences menu command from the options menu to the file menu. [#9768](https://github.com/JabRef/jabref/pull/9768) +- We reworked the cross ref labels in the entry editor and added a right click menu. [#10046](https://github.com/JabRef/jabref/pull/10046) +- We reorganized the order of tabs and settings in the library properties. [#9836](https://github.com/JabRef/jabref/pull/9836) +- We changed the handling of an "overflow" of authors at `[authIniN]`: JabRef uses `+` to indicate an overflow. Example: `[authIni2]` produces `A+` (instead of `AB`) for `Aachen and Berlin and Chemnitz`. [#9703](https://github.com/JabRef/jabref/pull/9703) +- We moved the preferences option to open the last edited files on startup to the 'General' tab. [#9808](https://github.com/JabRef/jabref/pull/9808) +- We improved the recognition of DOIs when pasting a link containing a DOI on the maintable. [#9864](https://github.com/JabRef/jabref/issues/9864s) +- We reordered the preferences dialog. [#9839](https://github.com/JabRef/jabref/pull/9839) +- We split the 'Import and Export' tab into 'Web Search' and 'Export'. [#9839](https://github.com/JabRef/jabref/pull/9839) +- We moved the option to run JabRef in memory stick mode into the preferences dialog toolbar. [#9866](https://github.com/JabRef/jabref/pull/9866) +- In case the library contains empty entries, they are not written to disk. [#8645](https://github.com/JabRef/jabref/issues/8645) +- The formatter `remove_unicode_ligatures` is now called `replace_unicode_ligatures`. [#9890](https://github.com/JabRef/jabref/pull/9890) +- We improved the error message when no terminal was found. [#9607](https://github.com/JabRef/jabref/issues/9607) +- In the context of the "systematic literature functionality", we changed the name "database" to "catalog" to use a separate term for online catalogs in comparison to SQL databases. [#9951](https://github.com/JabRef/jabref/pull/9951) +- We now show more fields (including Special Fields) in the dropdown selection for "Save sort order" in the library properties and for "Export sort order" in the preferences. [#10010](https://github.com/JabRef/jabref/issues/10010) +- We now encrypt and store the custom API keys in the OS native credential store. [#10044](https://github.com/JabRef/jabref/issues/10044) +- We changed the behavior of group addition/edit, so that sorting by alphabetical order is not performed by default after the modification. [#10017](https://github.com/JabRef/jabref/issues/10017) +- We fixed an issue with spacing in the cleanup dialogue. [#10081](https://github.com/JabRef/jabref/issues/10081) +- The GVK fetcher now uses the new [K10plus](https://www.bszgbv.de/services/k10plus/) database. [#10189](https://github.com/JabRef/jabref/pull/10189) + +### Fixed + +- We fixed an issue where clicking the group expansion pane/arrow caused the node to be selected, when it should just expand/detract the node. [#10111](https://github.com/JabRef/jabref/pull/10111) +- We fixed an issue where the browser import would add ' characters before the BibTeX entry on Linux. [#9588](https://github.com/JabRef/jabref/issues/9588) +- We fixed an issue where searching for a specific term with the DOAB fetcher lead to an exception. [#9571](https://github.com/JabRef/jabref/issues/9571) +- We fixed an issue where the "Import" -> "Library to import to" did not show the correct library name if two opened libraries had the same suffix. [#9567](https://github.com/JabRef/jabref/issues/9567) +- We fixed an issue where the rpm-Version of JabRef could not be properly uninstalled and reinstalled. [#9558](https://github.com/JabRef/jabref/issues/9558), [#9603](https://github.com/JabRef/jabref/issues/9603) +- We fixed an issue where the command line export using `--exportMatches` flag does not create an output bib file. [#9581](https://github.com/JabRef/jabref/issues/9581) +- We fixed an issue where custom field in the custom entry types could not be set to mulitline. [#9609](https://github.com/JabRef/jabref/issues/9609) +- We fixed an issue where the Office XML exporter did not resolve BibTeX-Strings when exporting entries. [forum#3741](https://discourse.jabref.org/t/exporting-bibtex-constant-strings-to-ms-office-2007-xml/3741) +- We fixed an issue where the Merge Entries Toolbar configuration was not saved after hitting 'Merge Entries' button. [#9091](https://github.com/JabRef/jabref/issues/9091) +- We fixed an issue where the password is stored in clear text if the user wants to use a proxy with authentication. [#8055](https://github.com/JabRef/jabref/issues/8055) +- JabRef is now more relaxed when parsing field content: In case a field content ended with `\`, the combination `\}` was treated as plain `}`. [#9668](https://github.com/JabRef/jabref/issues/9668) +- We resolved an issue that cut off the number of group entries when it exceeded four digits. [#8797](https://github.com/JabRef/jabref/issues/8797) +- We fixed the issue where the size of the global search window was not retained after closing. [#9362](https://github.com/JabRef/jabref/issues/9362) +- We fixed an issue where the Global Search UI preview is still white in dark theme. [#9362](https://github.com/JabRef/jabref/issues/9362) +- We fixed the double paste issue when Cmd + v is pressed on 'New entry from plaintext' dialog. [#9367](https://github.com/JabRef/jabref/issues/9367) +- We fixed an issue where the pin button on the Global Search dialog was located at the bottom and not at the top. [#9362](https://github.com/JabRef/jabref/issues/9362) +- We fixed the log text color in the event log console when using dark mode. [#9732](https://github.com/JabRef/jabref/issues/9732) +- We fixed an issue where searching for unlinked files would include the current library's .bib file. [#9735](https://github.com/JabRef/jabref/issues/9735) +- We fixed an issue where it was no longer possible to connect to a shared mysql database due to an exception. [#9761](https://github.com/JabRef/jabref/issues/9761) +- We fixed an issue where an exception was thrown for the user after Ctrl+Z command. [#9737](https://github.com/JabRef/jabref/issues/9737) +- We fixed the citation key generation for [`[authors]`, `[authshort]`, `[authorsAlpha]`, `[authIniN]`, `[authEtAl]`, `[auth.etal]`](https://docs.jabref.org/setup/citationkeypatterns#special-field-markers) to handle `and others` properly. [koppor#626](https://github.com/koppor/jabref/issues/626) +- We fixed the Save/save as file type shows BIBTEX_DB instead of "Bibtex library". [#9372](https://github.com/JabRef/jabref/issues/9372) +- We fixed the default main file directory for non-English Linux users. [#8010](https://github.com/JabRef/jabref/issues/8010) +- We fixed an issue when overwriting the owner was disabled. [#9896](https://github.com/JabRef/jabref/pull/9896) +- We fixed an issue regarding recording redundant prefixes in search history. [#9685](https://github.com/JabRef/jabref/issues/9685) +- We fixed an issue where passing a URL containing a DOI led to a "No entry found" notification. [#9821](https://github.com/JabRef/jabref/issues/9821) +- We fixed some minor visual inconsistencies and issues in the preferences dialog. [#9866](https://github.com/JabRef/jabref/pull/9866) +- The order of save actions is now retained. [#9890](https://github.com/JabRef/jabref/pull/9890) +- We fixed an issue where the order of save actions was not retained in the bib file. [#9890](https://github.com/JabRef/jabref/pull/9890) +- We fixed an issue in the preferences 'External file types' tab ignoring a custom application path in the edit dialog. [#9895](https://github.com/JabRef/jabref/issues/9895) +- We fixed an issue in the preferences where custom columns could be added to the entry table with no qualifier. [#9913](https://github.com/JabRef/jabref/issues/9913) +- We fixed an issue where the encoding header in a bib file was not respected when the file contained a BOM (Byte Order Mark). [#9926](https://github.com/JabRef/jabref/issues/9926) +- We fixed an issue where cli help output for import and export format was inconsistent. [koppor#429](https://github.com/koppor/jabref/issues/429) +- We fixed an issue where the user could select multiple conflicting options for autocompletion at once. [#10181](https://github.com/JabRef/jabref/issues/10181) +- We fixed an issue where no preview could be generated for some entry types and led to an exception. [#9947](https://github.com/JabRef/jabref/issues/9947) +- We fixed an issue where the Linux terminal working directory argument was malformed and therefore ignored upon opening a terminal [#9953](https://github.com/JabRef/jabref/issues/9953) +- We fixed an issue under Linux where under some systems the file instead of the folder was opened. [#9607](https://github.com/JabRef/jabref/issues/9607) +- We fixed an issue where an Automatic Keyword Group could not be deleted in the UI. [#9778](https://github.com/JabRef/jabref/issues/9778) +- We fixed an issue where the citation key pattern `[edtrN_M]` returned the wrong editor. [#9946](https://github.com/JabRef/jabref/pull/9946) +- We fixed an issue where empty grey containers would remain in the groups panel, if displaying of group item count is turned off. [#9972](https://github.com/JabRef/jabref/issues/9972) +- We fixed an issue where fetching an ISBN could lead to application freezing when the fetcher did not return any results. [#9979](https://github.com/JabRef/jabref/issues/9979) +- We fixed an issue where closing a library containing groups and entries caused an exception [#9997](https://github.com/JabRef/jabref/issues/9997) +- We fixed a bug where the editor for strings in a bibliography file did not sort the entries by their keys [#10083](https://github.com/JabRef/jabref/pull/10083) +- We fixed an issues where clicking on the empty space of specific context menu entries would not trigger the associated action. [#8388](https://github.com/JabRef/jabref/issues/8388) +- We fixed an issue where JabRef would not remember whether the window was in fullscreen. [#4939](https://github.com/JabRef/jabref/issues/4939) +- We fixed an issue where the ACM Portal search sometimes would not return entries for some search queries when the article author had no given name. [#10107](https://github.com/JabRef/jabref/issues/10107) +- We fixed an issue that caused high CPU usage and a zombie process after quitting JabRef because of author names autocompletion. [#10159](https://github.com/JabRef/jabref/pull/10159) +- We fixed an issue where files with illegal characters in the filename could be added to JabRef. [#10182](https://github.com/JabRef/jabref/issues/10182) +- We fixed that checked-out radio buttons under "specified keywords" were not displayed as checked after closing and reopening the "edit group" window. [#10248](https://github.com/JabRef/jabref/issues/10248) +- We fixed that when editing groups, checked-out properties such as case sensitive and regular expression (under "Free search expression") were not displayed checked. [#10108](https://github.com/JabRef/jabref/issues/10108) + +### Removed + +- We removed the support of BibTeXML. [#9540](https://github.com/JabRef/jabref/issues/9540) +- We removed support for Markdown syntax for strikethrough and task lists in comment fields. [#9726](https://github.com/JabRef/jabref/pull/9726) +- We removed the options menu, because the two contents were moved to the File menu or the properties of the library. [#9768](https://github.com/JabRef/jabref/pull/9768) +- We removed the 'File' tab in the preferences and moved its contents to the 'Export' tab. [#9839](https://github.com/JabRef/jabref/pull/9839) +- We removed the "[Collection of Computer Science Bibliographies](https://en.wikipedia.org/wiki/Collection_of_Computer_Science_Bibliographies)" fetcher the websits is no longer available. [#6638](https://github.com/JabRef/jabref/issues/6638) + +## [5.9] – 2023-01-06 + +### Added + +- We added a dropdown menu to let users change the library they want to import into during import. [#6177](https://github.com/JabRef/jabref/issues/6177) +- We added the possibility to add/remove a preview style from the selected list using a double click. [#9490](https://github.com/JabRef/jabref/issues/9490) +- We added the option to define fields as "multine" directly in the custom entry types dialog. [#6448](https://github.com/JabRef/jabref/issues/6448) +- We changed the minWidth and the minHeight of the main window, so it won't have a width and/or a height with the value 0. [#9606](https://github.com/JabRef/jabref/issues/9606) + +### Changed + +- We changed database structure: in MySQL/MariaDB we renamed tables by adding a `JABREF_` prefix, and in PGSQL we moved tables in `jabref` schema. We added `VersionDBStructure` variable in `METADATA` table to indicate current version of structure, this variable is needed for automatic migration. [#9312](https://github.com/JabRef/jabref/issues/9312) +- We moved some preferences options to a new tab in the preferences dialog. [#9442](https://github.com/JabRef/jabref/pull/9308) +- We renamed "Medline abbreviation" to "dotless abbreviation". [#9504](https://github.com/JabRef/jabref/pull/9504) +- We now have more "dots" in the offered journal abbreviations. [#9504](https://github.com/JabRef/jabref/pull/9504) +- We now disable the button "Full text search" in the Searchbar by default [#9527](https://github.com/JabRef/jabref/pull/9527) + + +### Fixed + +- The tab "deprecated fields" is shown in biblatex-mode only. [#7757](https://github.com/JabRef/jabref/issues/7757) +- In case a journal name of an IEEE journal is abbreviated, the "normal" abbreviation is used - and not the one of the IEEE BibTeX strings. [abbrv#91](https://github.com/JabRef/abbrv.jabref.org/issues/91) +- We fixed a performance issue when loading large lists of custom journal abbreviations. [#8928](https://github.com/JabRef/jabref/issues/8928) +- We fixed an issue where the last opened libraries were not remembered when a new unsaved library was open as well. [#9190](https://github.com/JabRef/jabref/issues/9190) +- We fixed an issue where no context menu for the group "All entries" was present. [forum#3682](https://discourse.jabref.org/t/how-sort-groups-a-z-not-subgroups/3682) +- We fixed an issue where extra curly braces in some fields would trigger an exception when selecting the entry or doing an integrity check. [#9475](https://github.com/JabRef/jabref/issues/9475), [#9503](https://github.com/JabRef/jabref/issues/9503) +- We fixed an issue where entering a date in the format "YYYY/MM" in the entry editor date field caused an exception. [#9492](https://github.com/JabRef/jabref/issues/9492) +- For portable versions, the `.deb` file now works on plain debian again. [#9472](https://github.com/JabRef/jabref/issues/9472) +- We fixed an issue where the download of linked online files failed after an import of entries for certain urls. [#9518](https://github.com/JabRef/jabref/issues/9518) +- We fixed an issue where an exception occurred when manually downloading a file from an URL in the entry editor. [#9521](https://github.com/JabRef/jabref/issues/9521) +- We fixed an issue with open office csv file formatting where commas in the abstract field where not escaped. [#9087](https://github.com/JabRef/jabref/issues/9087) +- We fixed an issue with deleting groups where subgroups different from the selected group were deleted. [#9281](https://github.com/JabRef/jabref/issues/9281) + +## [5.8] – 2022-12-18 + +### Added + +- We integrated a new three-way merge UI for merging entries in the Entries Merger Dialog, the Duplicate Resolver Dialog, the Entry Importer Dialog, and the External Changes Resolver Dialog. [#8945](https://github.com/JabRef/jabref/pull/8945) +- We added the ability to merge groups, keywords, comments and files when merging entries. [#9022](https://github.com/JabRef/jabref/pull/9022) +- We added a warning message next to the authors field in the merge dialog to warn users when the authors are the same but formatted differently. [#8745](https://github.com/JabRef/jabref/issues/8745) +- The default file directory of a library is used as default directory for [unlinked file lookup](https://docs.jabref.org/collect/findunlinkedfiles#link-the-pdfs-to-your-bib-library). [koppor#546](https://github.com/koppor/jabref/issues/546) +- The properties of an existing systematic literature review (SLR) can be edited. [koppor#604](https://github.com/koppor/jabref/issues/604) +- An systematic literature review (SLR) can now be started from the SLR itself. [#9131](https://github.com/JabRef/jabref/pull/9131), [koppor#601](https://github.com/koppor/jabref/issues/601) +- On startup, JabRef notifies the user if there were parsing errors during opening. +- We added support for the field `fjournal` (in `@article`) for abbreviation and unabbreviation functionalities. [#321](https://github.com/JabRef/jabref/pull/321) +- In case a backup is found, the filename of the backup is shown and one can navigate to the file. [#9311](https://github.com/JabRef/jabref/pull/9311) +- We added support for the Ukrainian and Arabic languages. [#9236](https://github.com/JabRef/jabref/pull/9236), [#9243](https://github.com/JabRef/jabref/pull/9243) + +### Changed + +- We improved the Citavi Importer to also import so called Knowledge-items into the field `comment` of the corresponding entry [#9025](https://github.com/JabRef/jabref/issues/9025) +- We modified the change case sub-menus and their corresponding tips (displayed when you stay long over the menu) to properly reflect exemplified cases. [#9339](https://github.com/Jabref/jabref/issues/9339) +- We call backup files `.bak` and temporary writing files now `.sav`. +- JabRef keeps 10 older versions of a `.bib` file in the [user data dir](https://github.com/harawata/appdirs#supported-directories) (instead of a single `.sav` (now: `.bak`) file in the directory of the `.bib` file) +- We improved the External Changes Resolver dialog to be more usaable. [#9021](https://github.com/JabRef/jabref/pull/9021) +- We simplified the actions to fast-resolve duplicates to 'Keep Left', 'Keep Right', 'Keep Both' and 'Keep Merged'. [#9056](https://github.com/JabRef/jabref/issues/9056) +- The fallback directory of the file folder now is the general file directory. In case there was a directory configured for a library and this directory was not found, JabRef placed the PDF next to the .bib file and not into the general file directory. +- The global default directory for storing PDFs is now the documents folder in the user's home. +- When adding or editing a subgroup it is placed w.r.t. to alphabetical ordering rather than at the end. [koppor#577](https://github.com/koppor/jabref/issues/577) +- Groups context menu now shows appropriate options depending on number of subgroups. [koppor#579](https://github.com/koppor/jabref/issues/579) +- We modified the "Delete file" dialog and added the full file path to the dialog text. The file path in the title was changed to file name only. [koppor#534](https://github.com/koppor/jabref/issues/534) +- Download from URL now automatically fills with URL from clipboard. [koppor#535](https://github.com/koppor/jabref/issues/535) +- We added HTML and Markdown files to Find Unlinked Files and removed BibTeX. [koppor#547](https://github.com/koppor/jabref/issues/547) +- ArXiv fetcher now retrieves additional data from related DOIs (both ArXiv and user-assigned). [#9170](https://github.com/JabRef/jabref/pull/9170) +- We modified the Directory of Open Access Books (DOAB) fetcher so that it will now also fetch the ISBN when possible. [#8708](https://github.com/JabRef/jabref/issues/8708) +- Genres are now mapped correctly to entry types when importing MODS files. [#9185](https://github.com/JabRef/jabref/issues/9185) +- We changed the button label from "Return to JabRef" to "Return to library" to better indicate the purpose of the action. +- We changed the color of found text from red to high-contrast colors (background: yellow; font color: purple). [koppor#552](https://github.com/koppor/jabref/issues/552) +- We fixed an issue where the wrong icon for a successful import of a bib entry was shown. [#9308](https://github.com/JabRef/jabref/pull/9308) +- We changed the messages after importing unlinked local files to past tense. [koppor#548](https://github.com/koppor/jabref/issues/548) +- We fixed an issue where the wrong icon for a successful import of a bib entry was shown [#9308](https://github.com/JabRef/jabref/pull/9308) +- In the context of the [Cleanup dialog](https://docs.jabref.org/finding-sorting-and-cleaning-entries/cleanupentries) we changed the text of the conversion of BibTeX to biblatex (and vice versa) to make it more clear. [koppor#545](https://github.com/koppor/jabref/issues/545) +- We removed wrapping of string constants when writing to a `.bib` file. +- In the context of a systematic literature review (SLR), a user can now add arbitrary data into `study.yml`. JabRef just ignores this data. [#9124](https://github.com/JabRef/jabref/pull/9124) +- In the context of a systematic literature review (SLR), we reworked the "Define study" parameters dialog. [#9123](https://github.com/JabRef/jabref/pull/9123) +- We upgraded to Lucene 9.4 for the fulltext search. The search index will be rebuild. [#9213](https://github.com/JabRef/jabref/pull/9213) +- We disabled the "change case" menu for empty fields. [#9214](https://github.com/JabRef/jabref/issues/9214) +- We disabled the conversion menu for empty fields. [#9200](https://github.com/JabRef/jabref/issues/9200) + +### Fixed + +- We fixed an issue where applied save actions on saving the library file would lead to the dialog "The library has been modified by another program" popping up. [#4877](https://github.com/JabRef/jabref/issues/4877) +- We fixed issues with save actions not correctly loaded when opening the library. [#9122](https://github.com/JabRef/jabref/pull/9122) +- We fixed the behavior of "Discard changes" when reopening a modified library. [#9361](https://github.com/JabRef/jabref/issues/9361) +- We fixed several bugs regarding the manual and the autosave of library files that could lead to exceptions. [#9067](https://github.com/JabRef/jabref/pull/9067), [#8484](https://github.com/JabRef/jabref/issues/8484), [#8746](https://github.com/JabRef/jabref/issues/8746), [#6684](https://github.com/JabRef/jabref/issues/6684), [#6644](https://github.com/JabRef/jabref/issues/6644), [#6102](https://github.com/JabRef/jabref/issues/6102), [#6000](https://github.com/JabRef/jabref/issues/6000) +- We fixed an issue where pdfs were re-indexed on each startup. [#9166](https://github.com/JabRef/jabref/pull/9166) +- We fixed an issue when using an unsafe character in the citation key, the auto-linking feature fails to link files. [#9267](https://github.com/JabRef/jabref/issues/9267) +- We fixed an issue where a message about changed metadata would occur on saving although nothing changed. [#9159](https://github.com/JabRef/jabref/issues/9159) +- We fixed an issue where the possibility to generate a subdatabase from an aux file was writing empty files when called from the commandline. [#9115](https://github.com/JabRef/jabref/issues/9115), [forum#3516](https://discourse.jabref.org/t/export-subdatabase-from-aux-file-on-macos-command-line/3516) +- We fixed an issue where author names with tilde accents (for example ñ) were marked as "Names are not in the standard BibTeX format". [#8071](https://github.com/JabRef/jabref/issues/8071) +- We fixed an issue where capitalize didn't capitalize words after hyphen characters. [#9157](https://github.com/JabRef/jabref/issues/9157) +- We fixed an issue where title case didn't capitalize words after en-dash characters and skip capitalization of conjunctions that comes after en-dash characters. [#9068](https://github.com/JabRef/jabref/pull/9068),[#9142](https://github.com/JabRef/jabref/pull/9142) +- We fixed an issue with the message that is displayed when fetcher returns an empty list of entries for given query. [#9195](https://github.com/JabRef/jabref/issues/9195) +- We fixed an issue where editing entry's "date" field in library mode "biblatex" causes an uncaught exception. [#8747](https://github.com/JabRef/jabref/issues/8747) +- We fixed an issue where importing from XMP would fail for certain PDFs. [#9383](https://github.com/JabRef/jabref/issues/9383) +- We fixed an issue that JabRef displayed the wrong group tree after loading. [koppor#637](https://github.com/koppor/jabref/issues/637) +- We fixed that sorting of entries in the maintable by special fields is updated immediately. [#9334](https://github.com/JabRef/jabref/issues/9334) +- We fixed the display of issue, number, eid and pages fields in the entry preview. [#8607](https://github.com/JabRef/jabref/pull/8607), [#8372](https://github.com/JabRef/jabref/issues/8372), [Koppor#514](https://github.com/koppor/jabref/issues/514), [forum#2390](https://discourse.jabref.org/t/unable-to-edit-my-bibtex-file-that-i-used-before-vers-5-1/2390), [forum#3462](https://discourse.jabref.org/t/jabref-5-6-need-help-with-export-from-jabref-to-microsoft-word-entry-preview-of-apa-7-not-rendering-correctly/3462) +- We fixed the page ranges checker to detect article numbers in the pages field (used at [Check Integrity](https://docs.jabref.org/finding-sorting-and-cleaning-entries/checkintegrity)). [#8607](https://github.com/JabRef/jabref/pull/8607) +- The [HtmlToLaTeXFormatter](https://docs.jabref.org/finding-sorting-and-cleaning-entries/saveactions#html-to-latex) keeps single `<` characters. +- We fixed a performance regression when opening large libraries. [#9041](https://github.com/JabRef/jabref/issues/9041) +- We fixed a bug where spaces are trimmed when highlighting differences in the Entries merge dialog. [koppor#371](https://github.com/koppor/jabref/issues/371) +- We fixed some visual glitches with the linked files editor field in the entry editor and increased its height. [#8823](https://github.com/JabRef/jabref/issues/8823) +- We fixed some visual inconsistencies (round corners of highlighted buttons). [#8806](https://github.com/JabRef/jabref/issues/8806) +- We fixed an issue where JabRef would not exit when a connection to a LibreOffice document was established previously and the document is still open. [#9075](https://github.com/JabRef/jabref/issues/9075) +- We fixed an issue about selecting the save order in the preferences. [#9147](https://github.com/JabRef/jabref/issues/9147) +- We fixed an issue where an exception when fetching a DOI was not logged correctly. [koppor#627](https://github.com/koppor/jabref/issues/627) +- We fixed an issue where a user could not open an attached file in a new unsaved library. [#9386](https://github.com/JabRef/jabref/issues/9386) +- We fixed a typo within a connection error message. [koppor#625](https://github.com/koppor/jabref/issues/625) +- We fixed an issue where journal abbreviations would not abbreviate journal titles with escaped ampersands (\\&). [#8948](https://github.com/JabRef/jabref/issues/8948) +- We fixed the readability of the file field in the dark theme. [#9340](https://github.com/JabRef/jabref/issues/9340) +- We fixed an issue where the 'close dialog' key binding was not closing the Preferences dialog. [#8888](https://github.com/jabref/jabref/issues/8888) +- We fixed an issue where a known journal's medline/dot-less abbreviation does not switch to the full name. [#9370](https://github.com/JabRef/jabref/issues/9370) +- We fixed an issue where hitting enter on the search field within the preferences dialog closed the dialog. [koppor#630](https://github.com/koppor/jabref/issues/630) +- We fixed the "Cleanup entries" dialog is partially visible. [#9223](https://github.com/JabRef/jabref/issues/9223) +- We fixed an issue where font size preferences did not apply correctly to preference dialog window and the menu bar. [#8386](https://github.com/JabRef/jabref/issues/8386) and [#9279](https://github.com/JabRef/jabref/issues/9279) +- We fixed the display of the "Customize Entry Types" dialog title. [#9198](https://github.com/JabRef/jabref/issues/9198) +- We fixed an issue where the CSS styles are missing in some dialogs. [#9150](https://github.com/JabRef/jabref/pull/9150) +- We fixed an issue where controls in the preferences dialog could outgrow the window. [#9017](https://github.com/JabRef/jabref/issues/9017) +- We fixed an issue where highlighted text color for entry merge dialogue was not clearly visible. [#9192](https://github.com/JabRef/jabref/issues/9192) + +### Removed + +- We removed "last-search-date" from the systematic literature review feature, because the last-search-date can be deducted from the git logs. [#9116](https://github.com/JabRef/jabref/pull/9116) +- We removed the [CiteseerX](https://docs.jabref.org/collect/import-using-online-bibliographic-database#citeseerx) fetcher, because the API used by JabRef is sundowned. [#9466](https://github.com/JabRef/jabref/pull/9466) + +## [5.7] – 2022-08-05 + +### Added + +- We added a fetcher for [Biodiversity Heritage Library](https://www.biodiversitylibrary.org/). [8539](https://github.com/JabRef/jabref/issues/8539) +- We added support for multiple messages in the snackbar. [#7340](https://github.com/JabRef/jabref/issues/7340) +- We added an extra option in the 'Find Unlinked Files' dialog view to ignore unnecessary files like Thumbs.db, DS_Store, etc. [koppor#373](https://github.com/koppor/jabref/issues/373) +- JabRef now writes log files. Linux: `$home/.cache/jabref/logs/version`, Windows: `%APPDATA%\..\Local\harawata\jabref\version\logs`, Mac: `Users/.../Library/Logs/jabref/version` +- We added an importer for Citavi backup files, support ".ctv5bak" and ".ctv6bak" file formats. [#8322](https://github.com/JabRef/jabref/issues/8322) +- We added a feature to drag selected entries and drop them to other opened inactive library tabs [koppor521](https://github.com/koppor/jabref/issues/521). +- We added support for the [biblatex-apa](https://github.com/plk/biblatex-apa) legal entry types `Legislation`, `Legadminmaterial`, `Jurisdiction`, `Constitution` and `Legal` [#8931](https://github.com/JabRef/jabref/issues/8931) + +### Changed + +- The file column in the main table now shows the corresponding defined icon for the linked file [8930](https://github.com/JabRef/jabref/issues/8930). +- We improved the color of the selected entries and the color of the summary in the Import Entries Dialog in the dark theme. [#7927](https://github.com/JabRef/jabref/issues/7927) +- We upgraded to Lucene 9.2 for the fulltext search. + Thus, the now created search index cannot be read from older versions of JabRef anylonger. + ⚠️ JabRef will recreate the index in a new folder for new files and this will take a long time for a huge library. + Moreover, switching back and forth JabRef versions and meanwhile adding PDFs also requires rebuilding the index now and then. + [#8868](https://github.com/JabRef/jabref/pull/8868) +- We improved the Latex2Unicode conversion [#8639](https://github.com/JabRef/jabref/pull/8639) +- Writing BibTeX data into a PDF (XMP) removes braces. [#8452](https://github.com/JabRef/jabref/issues/8452) +- Writing BibTeX data into a PDF (XMP) does not write the `file` field. +- Writing BibTeX data into a PDF (XMP) considers the configured keyword separator (and does not use "," as default any more) +- The Medline/Pubmed search now also supports the [default fields and operators for searching](https://docs.jabref.org/collect/import-using-online-bibliographic-database#search-syntax). [forum#3554](https://discourse.jabref.org/t/native-pubmed-search/3354) +- We improved group expansion arrow that prevent it from activating group when expanding or collapsing. [#7982](https://github.com/JabRef/jabref/issues/7982), [#3176](https://github.com/JabRef/jabref/issues/3176) +- When configured SSL certificates changed, JabRef warns the user to restart to apply the configuration. +- We improved the appearances and logic of the "Manage field names & content" dialog, and renamed it to "Automatic field editor". [#6536](https://github.com/JabRef/jabref/issues/6536) +- We improved the message explaining the options when modifying an automatic keyword group [#8911](https://github.com/JabRef/jabref/issues/8911) +- We moved the preferences option "Warn about duplicates on import" option from the tab "File" to the tab "Import and Export". [koppor#570](https://github.com/koppor/jabref/issues/570) +- When JabRef encounters `% Encoding: UTF-8` header, it is kept during writing (and not removed). [#8964](https://github.com/JabRef/jabref/pull/8964) +- We replace characters which cannot be decoded using the specified encoding by a (probably another) valid character. This happens if JabRef detects the wrong charset (e.g., UTF-8 instead of Windows 1252). One can use the [Integrity Check](https://docs.jabref.org/finding-sorting-and-cleaning-entries/checkintegrity) to find those characters. + +### Fixed + +- We fixed an issue where linked fails containing parts of the main file directory could not be opened. [#8991](https://github.com/JabRef/jabref/issues/8991) +- Linked files with an absolute path can be opened again. [#8991](https://github.com/JabRef/jabref/issues/8991) +- We fixed an issue where the user could not rate an entry in the main table when an entry was not yet ranked. [#5842](https://github.com/JabRef/jabref/issues/5842) +- We fixed an issue that caused JabRef to sometimes open multiple instances when "Remote Operation" is enabled. [#8653](https://github.com/JabRef/jabref/issues/8653) +- We fixed an issue where linked files with the filetype "application/pdf" in an entry were not shown with the correct PDF-Icon in the main table [8930](https://github.com/JabRef/jabref/issues/8930) +- We fixed an issue where "open folder" for linked files did not open the folder and did not select the file unter certain Linux desktop environments [#8679](https://github.com/JabRef/jabref/issues/8679), [#8849](https://github.com/JabRef/jabref/issues/8849) +- We fixed an issue where the content of a big shared database library is not shown [#8788](https://github.com/JabRef/jabref/issues/8788) +- We fixed the unnecessary horizontal scroll bar in group panel [#8467](https://github.com/JabRef/jabref/issues/8467) +- We fixed an issue where the notification bar message, icon and actions appeared to be invisible. [#8761](https://github.com/JabRef/jabref/issues/8761) +- We fixed an issue where deprecated fields tab is shown when the fields don't contain any values. [#8396](https://github.com/JabRef/jabref/issues/8396) +- We fixed an issue where an exception for DOI search occurred when the DOI contained urlencoded characters. [#8787](https://github.com/JabRef/jabref/issues/8787) +- We fixed an issue which allow us to select and open identifiers from a popup list in the maintable [#8758](https://github.com/JabRef/jabref/issues/8758), [8802](https://github.com/JabRef/jabref/issues/8802) +- We fixed an issue where the escape button had no functionality within the "Filter groups" textfield. [koppor#562](https://github.com/koppor/jabref/issues/562) +- We fixed an issue where the exception that there are invalid characters in filename. [#8786](https://github.com/JabRef/jabref/issues/8786) +- When the proxy configuration removed the proxy user/password, this change is applied immediately. +- We fixed an issue where removing several groups deletes only one of them. [#8390](https://github.com/JabRef/jabref/issues/8390) +- We fixed an issue where the Sidepane (groups, web search and open office) width is not remembered after restarting JabRef. [#8907](https://github.com/JabRef/jabref/issues/8907) +- We fixed a bug where switching between themes will cause an error/exception. [#8939](https://github.com/JabRef/jabref/pull/8939) +- We fixed a bug where files that were deleted in the source bibtex file were kept in the index. [#8962](https://github.com/JabRef/jabref/pull/8962) +- We fixed "Error while sending to JabRef" when the browser extension interacts with JabRef. [JabRef-Browser-Extension#479](https://github.com/JabRef/JabRef-Browser-Extension/issues/479) +- We fixed a bug where updating group view mode (intersection or union) requires re-selecting groups to take effect. [#6998](https://github.com/JabRef/jabref/issues/6998) +- We fixed a bug that prevented external group metadata changes from being merged. [#8873](https://github.com/JabRef/jabref/issues/8873) +- We fixed the shared database opening dialog to remember autosave folder and tick. [#7516](https://github.com/JabRef/jabref/issues/7516) +- We fixed an issue where name formatter could not be saved. [#9120](https://github.com/JabRef/jabref/issues/9120) +- We fixed a bug where after the export of Preferences, custom exports were duplicated. [#10176](https://github.com/JabRef/jabref/issues/10176) + +### Removed + +- We removed the social media buttons for our Twitter and Facebook pages. [#8774](https://github.com/JabRef/jabref/issues/8774) + +## [5.6] – 2022-04-25 + +### Added + +- We enabled the user to customize the API Key for some fetchers. [#6877](https://github.com/JabRef/jabref/issues/6877) +- We added an extra option when right-clicking an entry in the Entry List to copy either the DOI or the DOI url. +- We added a fetcher for [Directory of Open Access Books (DOAB)](https://doabooks.org/) [8576](https://github.com/JabRef/jabref/issues/8576) +- We added an extra option to ask the user whether they want to open to reveal the folder holding the saved file with the file selected. [#8195](https://github.com/JabRef/jabref/issues/8195) +- We added a new section to network preferences to allow using custom SSL certificates. [#8126](https://github.com/JabRef/jabref/issues/8126) +- We improved the version check to take also beta version into account and now redirect to the right changelog for the version. +- We added two new web and fulltext fetchers: SemanticScholar and ResearchGate. +- We added notifications on success and failure when writing metadata to a PDF-file. [#8276](https://github.com/JabRef/jabref/issues/8276) +- We added a cleanup action that escapes `$` (by adding a backslash in front). [#8673](https://github.com/JabRef/jabref/issues/8673) + +### Changed + +- We upgraded to Lucene 9.1 for the fulltext search. + Thus, the now created search index cannot be read from older versions of JabRef any longer. + ⚠️ JabRef will recreate the index in a new folder for new files and this will take a long time for a huge library. + Moreover, switching back and forth JabRef versions and meanwhile adding PDFs also requires rebuilding the index now and then. + [#8362](https://github.com/JabRef/jabref/pull/8362) +- We changed the list of CSL styles to those that support formatting bibliographies. [#8421](https://github.com/JabRef/jabref/issues/8421) [citeproc-java#116](https://github.com/michel-kraemer/citeproc-java/issues/116) +- The CSL preview styles now also support displaying data from cross references entries that are linked via the `crossref` field. [#7378](https://github.com/JabRef/jabref/issues/7378) +- We made the Search button in Web Search wider. We also skewed the panel titles to the left. [#8397](https://github.com/JabRef/jabref/issues/8397) +- We introduced a preference to disable fulltext indexing. [#8468](https://github.com/JabRef/jabref/issues/8468) +- When exporting entries, the encoding is always UTF-8. +- When embedding BibTeX data into a PDF, the encoding is always UTF-8. +- We replaced the [OttoBib](https://en.wikipedia.org/wiki/OttoBib) fetcher by a fetcher by [OpenLibrary](https://openlibrary.org/dev/docs/api/books). [#8652](https://github.com/JabRef/jabref/issues/8652) +- We first fetch ISBN data from OpenLibrary, if nothing found, ebook.de is tried. +- We now only show a warning when exiting for tasks that will not be recovered automatically upon relaunch of JabRef. [#8468](https://github.com/JabRef/jabref/issues/8468) + +### Fixed + +- We fixed an issue where right clicking multiple entries and pressing "Change entry type" would only change one entry. [#8654](https://github.com/JabRef/jabref/issues/8654) +- We fixed an issue where it was no longer possible to add or delete multiple files in the `file` field in the entry editor. [#8659](https://github.com/JabRef/jabref/issues/8659) +- We fixed an issue where the author's lastname was not used for the citation key generation if it started with a lowercase letter. [#8601](https://github.com/JabRef/jabref/issues/8601) +- We fixed an issue where custom "Protected terms" files were missing after a restart of JabRef. [#8608](https://github.com/JabRef/jabref/issues/8608) +- We fixed an issue where JabRef could not start due to a missing directory for the fulltex index. [#8579](https://github.com/JabRef/jabref/issues/8579) +- We fixed an issue where long article numbers in the `pages` field would cause an exception and preventing the citation style to display. [#8381](https://github.com/JabRef/jabref/issues/8381), [citeproc-java](https://github.com/michel-kraemer/citeproc-java/issues/114) +- We fixed an issue where online links in the file field were not detected correctly and could produce an exception. [#8510](https://github.com/JabRef/jabref/issues/8510) +- We fixed an issue where an exception could occur when saving the preferences [#7614](https://github.com/JabRef/jabref/issues/7614) +- We fixed an issue where "Copy DOI url" in the right-click menu of the Entry List would just copy the DOI and not the DOI url. [#8389](https://github.com/JabRef/jabref/issues/8389) +- We fixed an issue where opening the console from the drop-down menu would cause an exception. [#8466](https://github.com/JabRef/jabref/issues/8466) +- We fixed an issue when reading non-UTF-8 encoded. When no encoding header is present, the encoding is now detected from the file content (and the preference option is disregarded). [#8417](https://github.com/JabRef/jabref/issues/8417) +- We fixed an issue where pasting a URL was replacing `+` signs by spaces making the URL unreachable. [#8448](https://github.com/JabRef/jabref/issues/8448) +- We fixed an issue where creating subsidiary files from aux files created with some versions of biblatex would produce incorrect results. [#8513](https://github.com/JabRef/jabref/issues/8513) +- We fixed an issue where opening the changelog from withing JabRef led to a 404 error. [#8563](https://github.com/JabRef/jabref/issues/8563) +- We fixed an issue where not all found unlinked local files were imported correctly due to some race condition. [#8444](https://github.com/JabRef/jabref/issues/8444) +- We fixed an issue where Merge entries dialog exceeds screen boundaries. +- We fixed an issue where the app lags when selecting an entry after a fresh start. [#8446](https://github.com/JabRef/jabref/issues/8446) +- We fixed an issue where no citationkey was generated on import, pasting a doi or an entry on the main table. [8406](https://github.com/JabRef/jabref/issues/8406), [koppor#553](https://github.com/koppor/jabref/issues/553) +- We fixed an issue where accent search does not perform consistently. [#6815](https://github.com/JabRef/jabref/issues/6815) +- We fixed an issue where the incorrect entry was selected when "New Article" is pressed while search filters are active. [#8674](https://github.com/JabRef/jabref/issues/8674) +- We fixed an issue where "Write BibTeXEntry metadata to PDF" button remains enabled while writing to PDF is in-progress. [#8691](https://github.com/JabRef/jabref/issues/8691) + +### Removed + +- We removed the option to copy CSL Citation styles data as `XSL_FO`, `ASCIIDOC`, and `RTF` as these have not been working since a long time and are no longer supported in the external library used for processing the styles. [#7378](https://github.com/JabRef/jabref/issues/7378) +- We removed the option to configure the default encoding. The default encoding is now hard-coded to the modern UTF-8 encoding. + +## [5.5] – 2022-01-17 + +### Changed + +- We integrated the external file types dialog directly inside the preferences. [#8341](https://github.com/JabRef/jabref/pull/8341) +- We disabled the add group button color change after adding 10 new groups. [#8051](https://github.com/JabRef/jabref/issues/8051) +- We inverted the logic for resolving [BibTeX strings](https://docs.jabref.org/advanced/strings). This helps to keep `#` chars. By default String resolving is only activated for a couple of standard fields. The list of fields can be modified in the preferences. [#7010](https://github.com/JabRef/jabref/issues/7010), [#7012](https://github.com/JabRef/jabref/issues/7012), [#8303](https://github.com/JabRef/jabref/issues/8303) +- We moved the search box in preview preferences closer to the available citation styles list. [#8370](https://github.com/JabRef/jabref/pull/8370) +- Changing the preference to show the preview panel as a separate tab now has effect without restarting JabRef. [#8370](https://github.com/JabRef/jabref/pull/8370) +- We enabled switching themes in JabRef without the need to restart JabRef. [#7335](https://github.com/JabRef/jabref/pull/7335) +- We added support for the field `day`, `rights`, `coverage` and `language` when reading XMP data in Dublin Core format. [#8491](https://github.com/JabRef/jabref/issues/8491) + +### Fixed + +- We fixed an issue where the preferences for "Search and store files relative to library file location" where ignored when the "Main file directory" field was not empty [#8385](https://github.com/JabRef/jabref/issues/8385) +- We fixed an issue where `#`chars in certain fields would be interpreted as BibTeX strings [#7010](https://github.com/JabRef/jabref/issues/7010), [#7012](https://github.com/JabRef/jabref/issues/7012), [#8303](https://github.com/JabRef/jabref/issues/8303) +- We fixed an issue where the fulltext search on an empty library with no documents would lead to an exception [koppor#522](https://github.com/koppor/jabref/issues/522) +- We fixed an issue where clicking on "Accept changes" in the merge dialog would lead to an exception [forum#2418](https://discourse.jabref.org/t/the-library-has-been-modified-by-another-program/2418/8) +- We fixed an issue where clicking on headings in the entry preview could lead to an exception. [#8292](https://github.com/JabRef/jabref/issues/8292) +- We fixed an issue where IntegrityCheck used the system's character encoding instead of the one set by the library or in preferences [#8022](https://github.com/JabRef/jabref/issues/8022) +- We fixed an issue about empty metadata in library properties when called from the right click menu. [#8358](https://github.com/JabRef/jabref/issues/8358) +- We fixed an issue where someone could add a duplicate field in the customize entry type dialog. [#8194](https://github.com/JabRef/jabref/issues/8194) +- We fixed a typo in the library properties tab: "String constants". There, one can configure [BibTeX string constants](https://docs.jabref.org/advanced/strings). +- We fixed an issue when writing a non-UTF-8 encoded file: The header is written again. [#8417](https://github.com/JabRef/jabref/issues/8417) +- We fixed an issue where folder creation during systemic literature review failed due to an illegal fetcher name. [#8552](https://github.com/JabRef/jabref/pull/8552) + +## [5.4] – 2021-12-20 + +### Added + +- We added confirmation dialog when user wants to close a library where any empty entries are detected. [#8096](https://github.com/JabRef/jabref/issues/8096) +- We added import support for CFF files. [#7945](https://github.com/JabRef/jabref/issues/7945) +- We added the option to copy the DOI of an entry directly from the context menu copy submenu. [#7826](https://github.com/JabRef/jabref/issues/7826) +- We added a fulltext search feature. [#2838](https://github.com/JabRef/jabref/pull/2838) +- We improved the deduction of bib-entries from imported fulltext pdfs. [#7947](https://github.com/JabRef/jabref/pull/7947) +- We added unprotect_terms to the list of bracketed pattern modifiers [#7826](https://github.com/JabRef/jabref/pull/7960) +- We added a dialog that allows to parse metadata from linked pdfs. [#7929](https://github.com/JabRef/jabref/pull/7929) +- We added an icon picker in group edit dialog. [#6142](https://github.com/JabRef/jabref/issues/6142) +- We added a preference to Opt-In to JabRef's online metadata extraction service (Grobid) usage. [#8002](https://github.com/JabRef/jabref/pull/8002) +- We readded the possibility to display the search results of all databases ("Global Search"). It is shown in a separate window. [#4096](https://github.com/JabRef/jabref/issues/4096) +- We readded the possibility to keep the search string when switching tabs. It is implemented by a toggle button. [#4096](https://github.com/JabRef/jabref/issues/4096#issuecomment-575986882) +- We allowed the user to also preview the available citation styles in the preferences besides the selected ones [#8108](https://github.com/JabRef/jabref/issues/8108) +- We added an option to search the available citation styles by name in the preferences [#8108](https://github.com/JabRef/jabref/issues/8108) +- We added an option to generate bib-entries from ID through a popover in the toolbar. [#4183](https://github.com/JabRef/jabref/issues/4183) +- We added a menu option in the right click menu of the main table tabs to display the library properties. [#6527](https://github.com/JabRef/jabref/issues/6527) +- When a `.bib` file ("library") was saved successfully, a notification is shown + +### Changed + +- Local library settings may overwrite the setting "Search and store files relative to library file location" [#8179](https://github.com/JabRef/jabref/issues/8179) +- The option "Fit table horizontally on screen" in the "Entry table" preferences is now disabled by default [#8148](https://github.com/JabRef/jabref/pull/8148) +- We improved the preferences and descriptions in the "Linked files" preferences tab [#8148](https://github.com/JabRef/jabref/pull/8148) +- We slightly changed the layout of the Journal tab in the preferences for ui consistency. [#7937](https://github.com/JabRef/jabref/pull/7937) +- The JabRefHost on Windows now writes a temporary file and calls `-importToOpen` instead of passing the bibtex via `-importBibtex`. [#7374](https://github.com/JabRef/jabref/issues/7374), [JabRef Browser Ext #274](https://github.com/JabRef/JabRef-Browser-Extension/issues/274) +- We reordered some entries in the right-click menu of the main table. [#6099](https://github.com/JabRef/jabref/issues/6099) +- We merged the barely used ImportSettingsTab and the CustomizationTab in the preferences into one single tab and moved the option to allow Integers in Edition Fields in Bibtex-Mode to the EntryEditor tab. [#7849](https://github.com/JabRef/jabref/pull/7849) +- We moved the export order in the preferences from `File` to `Import and Export`. [#7935](https://github.com/JabRef/jabref/pull/7935) +- We reworked the export order in the preferences and the save order in the library preferences. You can now set more than three sort criteria in your library preferences. [#7935](https://github.com/JabRef/jabref/pull/7935) +- The metadata-to-pdf actions now also embeds the bibfile to the PDF. [#8037](https://github.com/JabRef/jabref/pull/8037) +- The snap was updated to use the core20 base and to use lzo compression for better startup performance [#8109](https://github.com/JabRef/jabref/pull/8109) +- We moved the union/intersection view button in the group sidepane to the left of the other controls. [#8202](https://github.com/JabRef/jabref/pull/8202) +- We improved the Drag and Drop behavior in the "Customize Entry Types" Dialog [#6338](https://github.com/JabRef/jabref/issues/6338) +- When determining the URL of an ArXiV eprint, the URL now points to the version [#8149](https://github.com/JabRef/jabref/pull/8149) +- We Included all standard fields with citation key when exporting to Old OpenOffice/LibreOffice Calc Format [#8176](https://github.com/JabRef/jabref/pull/8176) +- In case the database is encoded with `UTF8`, the `% Encoding` marker is not written anymore +- The written `.bib` file has the same line endings [#390](https://github.com/koppor/jabref/issues/390) +- The written `.bib` file always has a final line break +- The written `.bib` file keeps the newline separator of the loaded `.bib` file +- We present options to manually enter an article or return to the New Entry menu when the fetcher DOI fails to find an entry for an ID [#7870](https://github.com/JabRef/jabref/issues/7870) +- We trim white space and non-ASCII characters from DOI [#8127](https://github.com/JabRef/jabref/issues/8127) +- The duplicate checker now inspects other fields in case no difference in the required and optional fields are found. +- We reworked the library properties dialog and integrated the `Library > Preamble`, `Library > Citation key pattern` and `Library > String constants dialogs` [#8264](https://github.com/JabRef/jabref/pulls/8264) +- We improved the startup time of JabRef by switching from the logging library `log4j2` to `tinylog` [#8007](https://github.com/JabRef/jabref/issues/8007) + +### Fixed + +- We fixed an issue where an exception occurred when pasting an entry with a publication date-range of the form 1910/1917 [#7864](https://github.com/JabRef/jabref/issues/7864) +- We fixed an issue where an exception occurred when a preview style was edited and afterwards another preview style selected. [#8280](https://github.com/JabRef/jabref/issues/8280) +- We fixed an issue where the actions to move a file to a directory were incorrectly disabled. [#7908](https://github.com/JabRef/jabref/issues/7908) +- We fixed an issue where an exception occurred when a linked online file was edited in the entry editor [#8008](https://github.com/JabRef/jabref/issues/8008) +- We fixed an issue when checking for a new version when JabRef is used behind a corporate proxy. [#7884](https://github.com/JabRef/jabref/issues/7884) +- We fixed some icons that were drawn in the wrong color when JabRef used a custom theme. [#7853](https://github.com/JabRef/jabref/issues/7853) +- We fixed an issue where the `Aux file` on `Edit group` doesn't support relative sub-directories path to import. [#7719](https://github.com/JabRef/jabref/issues/7719). +- We fixed an issue where it was impossible to add or modify groups. [#7912](https://github.com/JabRef/jabref/pull/793://github.com/JabRef/jabref/pull/7921) +- We fixed an issue about the visible side pane components being out of sync with the view menu. [#8115](https://github.com/JabRef/jabref/issues/8115) +- We fixed an issue where the side pane would not close when all its components were closed. [#8082](https://github.com/JabRef/jabref/issues/8082) +- We fixed an issue where exported entries from a Citavi bib containing URLs could not be imported [#7882](https://github.com/JabRef/jabref/issues/7882) +- We fixed an issue where the icons in the search bar had the same color, toggled as well as untoggled. [#8014](https://github.com/JabRef/jabref/pull/8014) +- We fixed an issue where typing an invalid UNC path into the "Main file directory" text field caused an error. [#8107](https://github.com/JabRef/jabref/issues/8107) +- We fixed an issue where "Open Folder" didn't select the file on macOS in Finder [#8130](https://github.com/JabRef/jabref/issues/8130) +- We fixed an issue where importing PDFs resulted in an uncaught exception [#8143](https://github.com/JabRef/jabref/issues/8143) +- We fixed "The library has been modified by another program" showing up when line breaks change [#4877](https://github.com/JabRef/jabref/issues/4877) +- The default directory of the "LaTeX Citations" tab is now the directory of the currently opened database (and not the directory chosen at the last open file dialog or the last database save) [koppor#538](https://github.com/koppor/jabref/issues/538) +- When writing a bib file, the `NegativeArraySizeException` should not occur [#8231](https://github.com/JabRef/jabref/issues/8231) [#8265](https://github.com/JabRef/jabref/issues/8265) +- We fixed an issue where some menu entries were available without entries selected. [#4795](https://github.com/JabRef/jabref/issues/4795) +- We fixed an issue where right-clicking on a tab and selecting close will close the focused tab even if it is not the tab we right-clicked [#8193](https://github.com/JabRef/jabref/pull/8193) +- We fixed an issue where selecting a citation style in the preferences would sometimes produce an exception [#7860](https://github.com/JabRef/jabref/issues/7860) +- We fixed an issue where an exception would occur when clicking on a DOI link in the preview pane [#7706](https://github.com/JabRef/jabref/issues/7706) +- We fixed an issue where XMP and embedded BibTeX export would not work [#8278](https://github.com/JabRef/jabref/issues/8278) +- We fixed an issue where the XMP and embedded BibTeX import of a file containing multiple schemas failed [#8278](https://github.com/JabRef/jabref/issues/8278) +- We fixed an issue where writing embedded BibTeX import fails due to write protection or bibtex already being present [#8332](https://github.com/JabRef/jabref/pull/8332) +- We fixed an issue where pdf-paths and the pdf-indexer could get out of sync [#8182](https://github.com/JabRef/jabref/issues/8182) +- We fixed an issue where Status-Logger error messages appeared during the startup of JabRef [#5475](https://github.com/JabRef/jabref/issues/5475) + +### Removed + +- We removed two orphaned preferences options [#8164](https://github.com/JabRef/jabref/pull/8164) +- We removed the functionality of the `--debug` commandline options. Use the java command line switch `-Dtinylog.level=debug` for debug output instead. [#8226](https://github.com/JabRef/jabref/pull/8226) + +## [5.3] – 2021-07-05 + +### Added + +- We added a progress counter to the title bar in Possible Duplicates dialog window. [#7366](https://github.com/JabRef/jabref/issues/7366) +- We added new "Customization" tab to the preferences which includes option to choose a custom address for DOI access. [#7337](https://github.com/JabRef/jabref/issues/7337) +- We added zbmath to the public databases from which the bibliographic information of an existing entry can be updated. [#7437](https://github.com/JabRef/jabref/issues/7437) +- We showed to the find Unlinked Files Dialog the date of the files' most recent modification. [#4652](https://github.com/JabRef/jabref/issues/4652) +- We added to the find Unlinked Files function a filter to show only files based on date of last modification (Last Year, Last Month, Last Week, Last Day). [#4652](https://github.com/JabRef/jabref/issues/4652) +- We added to the find Unlinked Files function a filter that sorts the files based on the date of last modification(Sort by Newest, Sort by Oldest First). [#4652](https://github.com/JabRef/jabref/issues/4652) +- We added the possibility to add a new entry via its zbMath ID (zbMATH can be chosen as ID type in the "Select entry type" window). [#7202](https://github.com/JabRef/jabref/issues/7202) +- We added the extension support and the external application support (For Texshow, Texmaker and LyX) to the flatpak [#7248](https://github.com/JabRef/jabref/pull/7248) +- We added some symbols and keybindings to the context menu in the entry editor. [#7268](https://github.com/JabRef/jabref/pull/7268) +- We added keybindings for setting and clearing the read status. [#7264](https://github.com/JabRef/jabref/issues/7264) +- We added two new fields to track the creation and most recent modification date and time for each entry. [koppor#130](https://github.com/koppor/jabref/issues/130) +- We added a feature that allows the user to copy highlighted text in the preview window. [#6962](https://github.com/JabRef/jabref/issues/6962) +- We added a feature that allows you to create new BibEntry via paste arxivId [#2292](https://github.com/JabRef/jabref/issues/2292) +- We added support for conducting automated and systematic literature search across libraries and git support for persistence [#369](https://github.com/koppor/jabref/issues/369) +- We added a add group functionality at the bottom of the side pane. [#4682](https://github.com/JabRef/jabref/issues/4682) +- We added a feature that allows the user to choose whether to trust the target site when unable to find a valid certification path from the file download site. [#7616](https://github.com/JabRef/jabref/issues/7616) +- We added a feature that allows the user to open all linked files of multiple selected entries by "Open file" option. [#6966](https://github.com/JabRef/jabref/issues/6966) +- We added a keybinding preset for new entries. [#7705](https://github.com/JabRef/jabref/issues/7705) +- We added a select all button for the library import function. [#7786](https://github.com/JabRef/jabref/issues/7786) +- We added a search feature for journal abbreviations. [#7804](https://github.com/JabRef/jabref/pull/7804) +- We added auto-key-generation progress to the background task list. [#7267](https://github.com/JabRef/jabref/issues/7267) +- We added the option to write XMP metadata to pdfs from the CLI. [7814](https://github.com/JabRef/jabref/pull/7814) + +### Changed + +- The export to MS Office XML now exports the author field as `Inventor` if the bibtex entry type is `patent` [#7830](https://github.com/JabRef/jabref/issues/7830) +- We changed the EndNote importer to import the field `label` to the corresponding bibtex field `endnote-label` [forum#2734](https://discourse.jabref.org/t/importing-endnote-label-field-to-jabref-from-xml-file/2734) +- The keywords added via "Manage content selectors" are now displayed in alphabetical order. [#3791](https://github.com/JabRef/jabref/issues/3791) +- We improved the "Find unlinked files" dialog to show import results for each file. [#7209](https://github.com/JabRef/jabref/pull/7209) +- The content of the field `timestamp` is migrated to `creationdate`. In case one configured "udpate timestampe", it is migrated to `modificationdate`. [koppor#130](https://github.com/koppor/jabref/issues/130) +- The JabRef specific meta-data content in the main field such as priorities (prio1, prio2, ...) are migrated to their respective fields. They are removed from the keywords. [#6840](https://github.com/jabref/jabref/issues/6840) +- We fixed an issue where groups generated from authors' last names did not include all entries of the authors' [#5833](https://github.com/JabRef/jabref/issues/5833) +- The export to MS Office XML now uses the month name for the field `MonthAcessed` instead of the two digit number [#7354](https://github.com/JabRef/jabref/issues/7354) +- We included some standalone dialogs from the options menu in the main preference dialog and fixed some visual issues in the preferences dialog. [#7384](https://github.com/JabRef/jabref/pull/7384) +- We improved the linking of the `python3` interpreter via the shebang to dynamically use the systems default Python. Related to [JabRef-Browser-Extension #177](https://github.com/JabRef/JabRef-Browser-Extension/issues/177) +- Automatically found pdf files now have the linking button to the far left and uses a link icon with a plus instead of a briefcase. The file name also has lowered opacity(70%) until added. [#3607](https://github.com/JabRef/jabref/issues/3607) +- We simplified the select entry type form by splitting it into two parts ("Recommended" and "Others") based on internal usage data. [#6730](https://github.com/JabRef/jabref/issues/6730) +- We improved the submenu list by merging the'Remove group' having two options, with or without subgroups. [#4682](https://github.com/JabRef/jabref/issues/4682) +- The export to MS Office XML now uses the month name for the field `Month` instead of the two digit number [forum#2685](https://discourse.jabref.org/t/export-month-as-text-not-number/2685) +- We reintroduced missing default keybindings for new entries. [#7346](https://github.com/JabRef/jabref/issues/7346) [#7439](https://github.com/JabRef/jabref/issues/7439) +- Lists of available fields are now sorted alphabetically. [#7716](https://github.com/JabRef/jabref/issues/7716) +- The tooltip of the search field explaining the search is always shown. [#7279](https://github.com/JabRef/jabref/pull/7279) +- We rewrote the ACM fetcher to adapt to the new interface. [#5804](https://github.com/JabRef/jabref/issues/5804) +- We moved the select/collapse buttons in the unlinked files dialog into a context menu. [#7383](https://github.com/JabRef/jabref/issues/7383) +- We fixed an issue where journal abbreviations containing curly braces were not recognized [#7773](https://github.com/JabRef/jabref/issues/7773) + +### Fixed + +- We fixed an issue where some texts (e.g. descriptions) in dialogs could not be translated [#7854](https://github.com/JabRef/jabref/issues/7854) +- We fixed an issue where import hangs for ris files with "ER - " [#7737](https://github.com/JabRef/jabref/issues/7737) +- We fixed an issue where getting bibliograhpic data from DOI or another identifer did not respect the library mode (BibTeX/biblatex)[#6267](https://github.com/JabRef/jabref/issues/6267) +- We fixed an issue where importing entries would not respect the library mode (BibTeX/biblatex)[#1018](https://github.com/JabRef/jabref/issues/1018) +- We fixed an issue where an exception occurred when importing entries from a web search [#7606](https://github.com/JabRef/jabref/issues/7606) +- We fixed an issue where the table column sort order was not properly stored and resulted in unsorted eports [#7524](https://github.com/JabRef/jabref/issues/7524) +- We fixed an issue where the value of the field `school` or `institution` would be printed twice in the HTML Export [forum#2634](https://discourse.jabref.org/t/problem-with-exporting-techreport-phdthesis-mastersthesis-to-html/2634) +- We fixed an issue preventing to connect to a shared database. [#7570](https://github.com/JabRef/jabref/pull/7570) +- We fixed an issue preventing files from being dragged & dropped into an empty library. [#6851](https://github.com/JabRef/jabref/issues/6851) +- We fixed an issue where double-click onto PDF in file list under the 'General' tab section should just open the file. [#7465](https://github.com/JabRef/jabref/issues/7465) +- We fixed an issue where the dark theme did not extend to a group's custom color picker. [#7481](https://github.com/JabRef/jabref/issues/7481) +- We fixed an issue where choosing the fields on which autocompletion should not work in "Entry editor" preferences had no effect. [#7320](https://github.com/JabRef/jabref/issues/7320) +- We fixed an issue where the "Normalize page numbers" formatter did not replace en-dashes or em-dashes with a hyphen-minus sign. [#7239](https://github.com/JabRef/jabref/issues/7239) +- We fixed an issue with the style of highlighted check boxes while searching in preferences. [#7226](https://github.com/JabRef/jabref/issues/7226) +- We fixed an issue where the option "Move file to file directory" was disabled in the entry editor for all files [#7194](https://github.com/JabRef/jabref/issues/7194) +- We fixed an issue where application dialogs were opening in the wrong display when using multiple screens [#7273](https://github.com/JabRef/jabref/pull/7273) +- We fixed an issue where the "Find unlinked files" dialog would freeze JabRef on importing. [#7205](https://github.com/JabRef/jabref/issues/7205) +- We fixed an issue where the "Find unlinked files" would stop importing when importing a single file failed. [#7206](https://github.com/JabRef/jabref/issues/7206) +- We fixed an issue where JabRef froze for a few seconds in MacOS when DNS resolution timed out. [#7441](https://github.com/JabRef/jabref/issues/7441) +- We fixed an issue where an exception would be displayed for previewing and preferences when a custom theme has been configured but is missing [#7177](https://github.com/JabRef/jabref/issues/7177) +- We fixed an issue where URLs in `file` fields could not be handled on Windows. [#7359](https://github.com/JabRef/jabref/issues/7359) +- We fixed an issue where the regex based file search miss-interpreted specific symbols. [#4342](https://github.com/JabRef/jabref/issues/4342) +- We fixed an issue where the Harvard RTF exporter used the wrong default file extension. [4508](https://github.com/JabRef/jabref/issues/4508) +- We fixed an issue where the Harvard RTF exporter did not use the new authors formatter and therefore did not export "organization" authors correctly. [4508](https://github.com/JabRef/jabref/issues/4508) +- We fixed an issue where the field `urldate` was not exported to the corresponding fields `YearAccessed`, `MonthAccessed`, `DayAccessed` in MS Office XML [#7354](https://github.com/JabRef/jabref/issues/7354) +- We fixed an issue where the password for a shared SQL database was only remembered if it was the same as the username [#6869](https://github.com/JabRef/jabref/issues/6869) +- We fixed an issue where some custom exports did not use the new authors formatter and therefore did not export authors correctly [#7356](https://github.com/JabRef/jabref/issues/7356) +- We fixed an issue where alt+keyboard shortcuts do not work [#6994](https://github.com/JabRef/jabref/issues/6994) +- We fixed an issue about the file link editor did not allow to change the file name according to the default pattern after changing an entry. [#7525](https://github.com/JabRef/jabref/issues/7525) +- We fixed an issue where the file path is invisible in dark theme. [#7382](https://github.com/JabRef/jabref/issues/7382) +- We fixed an issue where the secondary sorting is not working for some special fields. [#7015](https://github.com/JabRef/jabref/issues/7015) +- We fixed an issue where changing the font size makes the font size field too small. [#7085](https://github.com/JabRef/jabref/issues/7085) +- We fixed an issue with TexGroups on Linux systems, where the modification of an aux-file did not trigger an auto-update for TexGroups. Furthermore, the detection of file modifications is now more reliable. [#7412](https://github.com/JabRef/jabref/pull/7412) +- We fixed an issue where the Unicode to Latex formatter produced wrong results for characters with a codepoint higher than Character.MAX_VALUE. [#7387](https://github.com/JabRef/jabref/issues/7387) +- We fixed an issue where a non valid value as font size results in an uncaught exception. [#7415](https://github.com/JabRef/jabref/issues/7415) +- We fixed an issue where "Merge citations" in the Openoffice/Libreoffice integration panel did not have a corresponding opposite. [#7454](https://github.com/JabRef/jabref/issues/7454) +- We fixed an issue where drag and drop of bib files for opening resulted in uncaught exceptions [#7464](https://github.com/JabRef/jabref/issues/7464) +- We fixed an issue where columns shrink in width when we try to enlarge JabRef window. [#6818](https://github.com/JabRef/jabref/issues/6818) +- We fixed an issue where Content selector does not seem to work for custom fields. [#6819](https://github.com/JabRef/jabref/issues/6819) +- We fixed an issue where font size of the preferences dialog does not update with the rest of the GUI. [#7416](https://github.com/JabRef/jabref/issues/7416) +- We fixed an issue in which a linked online file consisting of a web page was saved as an invalid pdf file upon being downloaded. The user is now notified when downloading a linked file results in an HTML file. [#7452](https://github.com/JabRef/jabref/issues/7452) +- We fixed an issue where opening BibTex file (doubleclick) from Folder with spaces not working. [#6487](https://github.com/JabRef/jabref/issues/6487) +- We fixed the header title in the Add Group/Subgroup Dialog box. [#4682](https://github.com/JabRef/jabref/issues/4682) +- We fixed an issue with saving large `.bib` files [#7265](https://github.com/JabRef/jabref/issues/7265) +- We fixed an issue with very large page numbers [#7590](https://github.com/JabRef/jabref/issues/7590) +- We fixed an issue where the file extension is missing on saving the library file on linux [#7451](https://github.com/JabRef/jabref/issues/7451) +- We fixed an issue with opacity of disabled icon-buttons [#7195](https://github.com/JabRef/jabref/issues/7195) +- We fixed an issue where journal abbreviations in UTF-8 were not recognized [#5850](https://github.com/JabRef/jabref/issues/5850) +- We fixed an issue where the article title with curly brackets fails to download the arXiv link (pdf file). [#7633](https://github.com/JabRef/jabref/issues/7633) +- We fixed an issue with toggle of special fields does not work for sorted entries [#7016](https://github.com/JabRef/jabref/issues/7016) +- We fixed an issue with the default path of external application. [#7641](https://github.com/JabRef/jabref/issues/7641) +- We fixed an issue where urls must be embedded in a style tag when importing EndNote style Xml files. Now it can parse url with or without a style tag. [#6199](https://github.com/JabRef/jabref/issues/6199) +- We fixed an issue where the article title with colon fails to download the arXiv link (pdf file). [#7660](https://github.com/JabRef/jabref/issues/7660) +- We fixed an issue where the keybinding for delete entry did not work on the main table [7580](https://github.com/JabRef/jabref/pull/7580) +- We fixed an issue where the RFC fetcher is not compatible with the draft [7305](https://github.com/JabRef/jabref/issues/7305) +- We fixed an issue where duplicate files (both file names and contents are the same) is downloaded and add to linked files [#6197](https://github.com/JabRef/jabref/issues/6197) +- We fixed an issue where changing the appearance of the preview tab did not trigger a restart warning. [#5464](https://github.com/JabRef/jabref/issues/5464) +- We fixed an issue where editing "Custom preview style" triggers exception. [#7526](https://github.com/JabRef/jabref/issues/7526) +- We fixed the [SAO/NASA Astrophysics Data System](https://docs.jabref.org/collect/import-using-online-bibliographic-database#sao-nasa-astrophysics-data-system) fetcher. [#7867](https://github.com/JabRef/jabref/pull/7867) +- We fixed an issue where a title with multiple applied formattings in EndNote was not imported correctly [forum#2734](https://discourse.jabref.org/t/importing-endnote-label-field-to-jabref-from-xml-file/2734) +- We fixed an issue where a `report` in EndNote was imported as `article` [forum#2734](https://discourse.jabref.org/t/importing-endnote-label-field-to-jabref-from-xml-file/2734) +- We fixed an issue where the field `publisher` in EndNote was not imported in JabRef [forum#2734](https://discourse.jabref.org/t/importing-endnote-label-field-to-jabref-from-xml-file/2734) + +### Removed + +- We removed add group button beside the filter group tab. [#4682](https://github.com/JabRef/jabref/issues/4682) + +## [5.2] – 2020-12-24 + +### Added + +- We added a validation to check if the current database location is shared, preventing an exception when Pulling Changes From Shared Database. [#6959](https://github.com/JabRef/jabref/issues/6959) +- We added a query parser and mapping layer to enable conversion of queries formulated in simplified lucene syntax by the user into api queries. [#6799](https://github.com/JabRef/jabref/pull/6799) +- We added some basic functionality to customise the look of JabRef by importing a css theme file. [#5790](https://github.com/JabRef/jabref/issues/5790) +- We added connection check function in network preference setting [#6560](https://github.com/JabRef/jabref/issues/6560) +- We added support for exporting to YAML. [#6974](https://github.com/JabRef/jabref/issues/6974) +- We added a DOI format and organization check to detect [American Physical Society](https://journals.aps.org/) journals to copy the article ID to the page field for cases where the page numbers are missing. [#7019](https://github.com/JabRef/jabref/issues/7019) +- We added an error message in the New Entry dialog that is shown in case the fetcher did not find anything . [#7000](https://github.com/JabRef/jabref/issues/7000) +- 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 + +- We changed the default preferences for OpenOffice/LibreOffice integration to automatically sync the bibliography when inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://github.com/JabRef/jabref/issues/6957) +- We restructured the 'File' tab and extracted some parts into the 'Linked files' tab [#6779](https://github.com/JabRef/jabref/pull/6779) +- JabRef now offers journal lists from . JabRef the lists which use a dot inside the abbreviations. [#5749](https://github.com/JabRef/jabref/pull/5749) +- We removed two useless preferences in the groups preferences dialog. [#6836](https://github.com/JabRef/jabref/pull/6836) +- Synchronization of SpecialFields to keywords is now disabled by default. [#6621](https://github.com/JabRef/jabref/issues/6621) +- JabRef no longer opens the entry editor with the first entry on startup [#6855](https://github.com/JabRef/jabref/issues/6855) +- We completed the rebranding of `bibtexkey` as `citationkey` which was started in JabRef 5.1. +- JabRef no longer opens the entry editor with the first entry on startup [#6855](https://github.com/JabRef/jabref/issues/6855) +- Fetch by ID: (long) "SAO/NASA Astrophysics Data System" replaced by (short) "SAO/NASA ADS" [#6876](https://github.com/JabRef/jabref/pull/6876) +- We changed the title of the window "Manage field names and content" to have the same title as the corresponding menu item [#6895](https://github.com/JabRef/jabref/pull/6895) +- We renamed the menus "View -> Previous citation style" and "View -> Next citation style" into "View -> Previous preview style" and "View -> Next preview style" and renamed the "Preview" style to "Customized preview style". [#6899](https://github.com/JabRef/jabref/pull/6899) +- We changed the default preference option "Search and store files relative to library file location" to on, as this seems to be a more intuitive behaviour. [#6863](https://github.com/JabRef/jabref/issues/6863) +- We changed the title of the window "Manage field names and content": to have the same title as the corresponding menu item [#6895](https://github.com/JabRef/jabref/pull/6895) +- We improved the detection of "short" DOIs [6880](https://github.com/JabRef/jabref/issues/6880) +- We improved the duplicate detection when identifiers like DOI or arxiv are semantiaclly the same, but just syntactically differ (e.g. with or without http(s):// prefix). [#6707](https://github.com/JabRef/jabref/issues/6707) +- We improved JabRef start up time [6057](https://github.com/JabRef/jabref/issues/6057) +- We changed in the group interface "Generate groups from keywords in a BibTeX field" by "Generate groups from keywords in the following field". [#6983](https://github.com/JabRef/jabref/issues/6983) +- We changed the name of a group type from "Searching for keywords" to "Searching for a keyword". [6995](https://github.com/JabRef/jabref/pull/6995) +- We changed the way JabRef displays the title of a tab and of the window. [4161](https://github.com/JabRef/jabref/issues/4161) +- We changed connect timeouts for server requests to 30 seconds in general and 5 seconds for GROBID server (special) and improved user notifications on connection issues. [7026](https://github.com/JabRef/jabref/pull/7026) +- We changed the order of the library tab context menu items. [#7171](https://github.com/JabRef/jabref/issues/7171) +- We changed the way linked files are opened on Linux to use the native openFile method, compatible with confined packages. [7037](https://github.com/JabRef/jabref/pull/7037) +- We refined the entry preview to show the full names of authors and editors, to list the editor only if no author is present, have the year earlier. [#7083](https://github.com/JabRef/jabref/issues/7083) + +### Fixed + +- We fixed an issue changing the icon link_variation_off that is not meaningful. [#6834](https://github.com/JabRef/jabref/issues/6834) +- We fixed an issue where the `.sav` file was not deleted upon exiting JabRef. [#6109](https://github.com/JabRef/jabref/issues/6109) +- We fixed a linked identifier icon inconsistency. [#6705](https://github.com/JabRef/jabref/issues/6705) +- We fixed the wrong behavior that font size changes are not reflected in dialogs. [#6039](https://github.com/JabRef/jabref/issues/6039) +- We fixed the failure to Copy citation key and link. [#5835](https://github.com/JabRef/jabref/issues/5835) +- We fixed an issue where the sort order of the entry table was reset after a restart of JabRef. [#6898](https://github.com/JabRef/jabref/pull/6898) +- We fixed an issue where no longer a warning was displayed when inserting references into LibreOffice with an invalid "ReferenceParagraphFormat". [#6907](https://github.com/JabRef/jabref/pull/60907). +- We fixed an issue where a selected field was not removed after the first click in the custom entry types dialog. [#6934](https://github.com/JabRef/jabref/issues/6934) +- We fixed an issue where a remove icon was shown for standard entry types in the custom entry types dialog. [#6906](https://github.com/JabRef/jabref/issues/6906) +- We fixed an issue where it was impossible to connect to OpenOffice/LibreOffice on Mac OSX. [#6970](https://github.com/JabRef/jabref/pull/6970) +- We fixed an issue with the python script used by browser plugins that failed to locate JabRef if not installed in its default location. [#6963](https://github.com/JabRef/jabref/pull/6963/files) +- We fixed an issue where spaces and newlines in an isbn would generate an exception. [#6456](https://github.com/JabRef/jabref/issues/6456) +- We fixed an issue where identity column header had incorrect foreground color in the Dark theme. [#6796](https://github.com/JabRef/jabref/issues/6796) +- We fixed an issue where the RIS exporter added extra blank lines.[#7007](https://github.com/JabRef/jabref/pull/7007/files) +- We fixed an issue where clicking on Collapse All button in the Search for Unlinked Local Files expanded the directory structure erroneously [#6848](https://github.com/JabRef/jabref/issues/6848) +- We fixed an issue, when pulling changes from shared database via shortcut caused creation of a new tech report [6867](https://github.com/JabRef/jabref/issues/6867) +- We fixed an issue where the JabRef GUI does not highlight the "All entries" group on start-up [#6691](https://github.com/JabRef/jabref/issues/6691) +- We fixed an issue where a custom dark theme was not applied to the entry preview tab [7068](https://github.com/JabRef/jabref/issues/7068) +- We fixed an issue where modifications to the Custom preview layout in the preferences were not saved [#6447](https://github.com/JabRef/jabref/issues/6447) +- 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 context menu was not updated after a file link was changed. [#5777](https://github.com/JabRef/jabref/issues/5777) +- We fixed an issue where the password for a shared SQL database was not remembered [#6869](https://github.com/JabRef/jabref/issues/6869) +- We fixed an issue where newly added entires were not synced to a shared SQL database [#7176](https://github.com/JabRef/jabref/issues/7176) +- We fixed an issue where the PDF-Content importer threw an exception when no DOI number is present at the first page of the PDF document [#7203](https://github.com/JabRef/jabref/issues/7203) +- We fixed an issue where groups created from aux files did not update on file changes [#6394](https://github.com/JabRef/jabref/issues/6394) +- We fixed an issue where authors that only have last names were incorrectly identified as institutes when generating citation keys [#7199](https://github.com/JabRef/jabref/issues/7199) +- We fixed an issue where institutes were incorrectly identified as universities when generating citation keys [#6942](https://github.com/JabRef/jabref/issues/6942) + +### Removed + +- We removed the Google Scholar fetcher and the ACM fetcher do not work due to traffic limitations [#6369](https://github.com/JabRef/jabref/issues/6369) +- We removed the menu entry "Manage external file types" because it's already in 'Preferences' dialog [#6991](https://github.com/JabRef/jabref/issues/6991) +- We removed the integrity check "Abbreviation detected" for the field journal/journaltitle in the entry editor [#3925](https://github.com/JabRef/jabref/issues/3925) + +## [5.1] – 2020-08-30 + +### Added + +- We added a new fetcher to enable users to search mEDRA DOIs [#6602](https://github.com/JabRef/jabref/issues/6602) +- We added a new fetcher to enable users to search "[Collection of Computer Science Bibliographies](https://en.wikipedia.org/wiki/Collection_of_Computer_Science_Bibliographies)". [#6638](https://github.com/JabRef/jabref/issues/6638) +- We added default values for delimiters in Add Subgroup window [#6624](https://github.com/JabRef/jabref/issues/6624) +- We improved responsiveness of general fields specification dialog window. [#6604](https://github.com/JabRef/jabref/issues/6604) +- We added support for importing ris file and load DOI [#6530](https://github.com/JabRef/jabref/issues/6530) +- We added the Library properties to a context menu on the library tabs [#6485](https://github.com/JabRef/jabref/issues/6485) +- We added a new field in the preferences in 'BibTeX key generator' for unwanted characters that can be user-specified. [#6295](https://github.com/JabRef/jabref/issues/6295) +- We added support for searching ShortScience for an entry through the user's browser. [#6018](https://github.com/JabRef/jabref/pull/6018) +- We updated EditionChecker to permit edition to start with a number. [#6144](https://github.com/JabRef/jabref/issues/6144) +- We added tooltips for most fields in the entry editor containing a short description. [#5847](https://github.com/JabRef/jabref/issues/5847) +- We added support for basic markdown in custom formatted previews [#6194](https://github.com/JabRef/jabref/issues/6194) +- We now show the number of items found and selected to import in the online search dialog. [#6248](https://github.com/JabRef/jabref/pull/6248) +- We created a new install screen for macOS. [#5759](https://github.com/JabRef/jabref/issues/5759) +- We added a new integrity check for duplicate DOIs. [koppor#339](https://github.com/koppor/jabref/issues/339) +- We implemented an option to download fulltext files while importing. [#6381](https://github.com/JabRef/jabref/pull/6381) +- We added a progress-indicator showing the average progress of background tasks to the toolbar. Clicking it reveals a pop-over with a list of running background tasks. [6443](https://github.com/JabRef/jabref/pull/6443) +- We fixed the bug when strike the delete key in the text field. [#6421](https://github.com/JabRef/jabref/issues/6421) +- We added a BibTex key modifier for truncating strings. [#3915](https://github.com/JabRef/jabref/issues/3915) +- We added support for jumping to target entry when typing letter/digit after sorting a column in maintable [#6146](https://github.com/JabRef/jabref/issues/6146) +- We added a new fetcher to enable users to search all available E-Libraries simultaneously. [koppor#369](https://github.com/koppor/jabref/issues/369) +- We added the field "entrytype" to the export sort criteria [#6531](https://github.com/JabRef/jabref/pull/6531) +- We added the possibility to change the display order of the fields in the entry editor. The order can now be configured using drag and drop in the "Customize entry types" dialog [#6152](https://github.com/JabRef/jabref/pull/6152) +- We added native support for biblatex-software [#6574](https://github.com/JabRef/jabref/issues/6574) +- We added a missing restart warning for AutoComplete in the preferences dialog. [#6351](https://github.com/JabRef/jabref/issues/6351) +- We added a note to the citation key pattern preferences dialog as a temporary workaround for a JavaFX bug, about committing changes in a table cell, if the focus is lost. [#5825](https://github.com/JabRef/jabref/issues/5825) +- We added support for customized fallback fields in bracketed patterns. [#7111](https://github.com/JabRef/jabref/issues/7111) + +### Changed + +- We improved the arXiv fetcher. Now it should find entries even more reliably and does no longer include the version (e.g `v1`) in the `eprint` field. [forum#1941](https://discourse.jabref.org/t/remove-version-in-arxiv-import/1941) +- We moved the group search bar and the button "New group" from bottom to top position to make it more prominent. [#6112](https://github.com/JabRef/jabref/pull/6112) +- When JabRef finds a `.sav` file without changes, there is no dialog asking for acceptance of changes anymore. +- We changed the buttons for import/export/show all/reset of preferences to smaller icon buttons in the preferences dialog. [#6130](https://github.com/JabRef/jabref/pull/6130) +- We moved the functionality "Manage field names & content" from the "Library" menu to the "Edit" menu, because it affects the selected entries and not the whole library +- We merged the functionality "Append contents from a BibTeX library into the currently viewed library" into the "Import into database" functionality. Fixes [#6049](https://github.com/JabRef/jabref/issues/6049). +- We changed the directory where fulltext downloads are stored to the directory set in the import-tab in preferences. [#6381](https://github.com/JabRef/jabref/pull/6381) +- We improved the error message for invalid jstyles. [#6303](https://github.com/JabRef/jabref/issues/6303) +- We changed the section name of 'Advanced' to 'Network' in the preferences and removed some obsolete options.[#6489](https://github.com/JabRef/jabref/pull/6489) +- We improved the context menu of the column "Linked identifiers" of the main table, by truncating their texts, if they are too long. [#6499](https://github.com/JabRef/jabref/issues/6499) +- We merged the main table tabs in the preferences dialog. [#6518](https://github.com/JabRef/jabref/pull/6518) +- We changed the command line option 'generateBibtexKeys' to the more generic term 'generateCitationKeys' while the short option remains 'g'.[#6545](https://github.com/JabRef/jabref/pull/6545) +- We improved the "Possible duplicate entries" window to remember its size and position throughout a session. [#6582](https://github.com/JabRef/jabref/issues/6582) +- We divided the toolbar into small parts, so if the application window is to small, only a part of the toolbar is moved into the chevron popup. [#6682](https://github.com/JabRef/jabref/pull/6682) +- We changed the layout for of the buttons in the Open Office side panel to ensure that the button text is always visible, specially when resizing. [#6639](https://github.com/JabRef/jabref/issues/6639) +- We merged the two new library commands in the file menu to one which always creates a new library in the default library mode. [#6359](https://github.com/JabRef/jabref/pull/6539#issuecomment-641056536) + +### Fixed + +- We fixed an issue where entry preview tab has no name in drop down list. [#6591](https://github.com/JabRef/jabref/issues/6591) +- We fixed to only search file links in the BIB file location directory when preferences has corresponding checkbox checked. [#5891](https://github.com/JabRef/jabref/issues/5891) +- We fixed wrong button order (Apply and Cancel) in ManageProtectedTermsDialog. +- We fixed an issue with incompatible characters at BibTeX key [#6257](https://github.com/JabRef/jabref/issues/6257) +- We fixed an issue where dash (`-`) was reported as illegal BibTeX key [#6295](https://github.com/JabRef/jabref/issues/6295) +- We greatly improved the performance of the overall application and many operations. [#5071](https://github.com/JabRef/jabref/issues/5071) +- We fixed an issue where sort by priority was broken. [#6222](https://github.com/JabRef/jabref/issues/6222) +- We fixed an issue where opening a library from the recent libraries menu was not possible. [#5939](https://github.com/JabRef/jabref/issues/5939) +- We fixed an issue with inconsistent capitalization of file extensions when downloading files. [#6115](https://github.com/JabRef/jabref/issues/6115) +- We fixed the display of language and encoding in the preferences dialog. [#6130](https://github.com/JabRef/jabref/pull/6130) +- Now the link and/or the link description in the column "linked files" of the main table gets truncated or wrapped, if too long, otherwise display issues arise. [#6178](https://github.com/JabRef/jabref/issues/6178) +- We fixed the issue that groups panel does not keep size when resizing window. [#6180](https://github.com/JabRef/jabref/issues/6180) +- We fixed an error that sometimes occurred when using the context menu. [#6085](https://github.com/JabRef/jabref/issues/6085) +- We fixed an issue where search full-text documents downloaded files with same name, overwriting existing files. [#6174](https://github.com/JabRef/jabref/pull/6174) +- We fixed an issue when importing into current library an erroneous message "import cancelled" is displayed even though import is successful. [#6266](https://github.com/JabRef/jabref/issues/6266) +- We fixed an issue where custom jstyles for Open/LibreOffice where not saved correctly. [#6170](https://github.com/JabRef/jabref/issues/6170) +- We fixed an issue where the INSPIRE fetcher was no longer working [#6229](https://github.com/JabRef/jabref/issues/6229) +- We fixed an issue where custom exports with an uppercase file extension could not be selected for "Copy...-> Export to Clipboard" [#6285](https://github.com/JabRef/jabref/issues/6285) +- We fixed the display of icon both in the main table and linked file editor. [#6169](https://github.com/JabRef/jabref/issues/6169) +- We fixed an issue where the windows installer did not create an entry in the start menu [bug report in the forum](https://discourse.jabref.org/t/error-while-fetching-from-doi/2018/3) +- We fixed an issue where only the field `abstract` and `comment` were declared as multiline fields. Other fields can now be configured in the preferences using "Do not wrap the following fields when saving" [4373](https://github.com/JabRef/jabref/issues/4373) +- We fixed an issue where JabRef switched to discrete graphics under macOS [#5935](https://github.com/JabRef/jabref/issues/5935) +- We fixed an issue where the Preferences entry preview will be unexpected modified leads to Value too long exception [#6198](https://github.com/JabRef/jabref/issues/6198) +- We fixed an issue where custom jstyles for Open/LibreOffice would only be valid if a layout line for the entry type `default` was at the end of the layout section [#6303](https://github.com/JabRef/jabref/issues/6303) +- We fixed an issue where a new entry is not shown in the library if a search is active [#6297](https://github.com/JabRef/jabref/issues/6297) +- We fixed an issue where long directory names created from patterns could create an exception. [#3915](https://github.com/JabRef/jabref/issues/3915) +- We fixed an issue where sort on numeric cases was broken. [#6349](https://github.com/JabRef/jabref/issues/6349) +- We fixed an issue where year and month fields were not cleared when converting to biblatex [#6224](https://github.com/JabRef/jabref/issues/6224) +- We fixed an issue where an "Not on FX thread" exception occurred when saving on linux [#6453](https://github.com/JabRef/jabref/issues/6453) +- We fixed an issue where the library sort order was lost. [#6091](https://github.com/JabRef/jabref/issues/6091) +- We fixed an issue where brackets in regular expressions were not working. [6469](https://github.com/JabRef/jabref/pull/6469) +- We fixed an issue where multiple background task popups stacked over each other.. [#6472](https://github.com/JabRef/jabref/issues/6472) +- We fixed an issue where LaTeX citations for specific commands (`\autocite`s) of biblatex-mla were not recognized. [#6476](https://github.com/JabRef/jabref/issues/6476) +- We fixed an issue where drag and drop was not working on empty database. [#6487](https://github.com/JabRef/jabref/issues/6487) +- We fixed an issue where the name fields were not updated after the preferences changed. [#6515](https://github.com/JabRef/jabref/issues/6515) +- We fixed an issue where "null" appeared in generated BibTeX keys. [#6459](https://github.com/JabRef/jabref/issues/6459) +- We fixed an issue where the authors' names were incorrectly displayed in the authors' column when they were bracketed. [#6465](https://github.com/JabRef/jabref/issues/6465) [#6459](https://github.com/JabRef/jabref/issues/6459) +- We fixed an issue where importing certain unlinked files would result in an exception [#5815](https://github.com/JabRef/jabref/issues/5815) +- We fixed an issue where downloaded files would be moved to a directory named after the citationkey when no file directory pattern is specified [#6589](https://github.com/JabRef/jabref/issues/6589) +- We fixed an issue with the creation of a group of cited entries which incorrectly showed the message that the library had been modified externally whenever saving the library. [#6420](https://github.com/JabRef/jabref/issues/6420) +- We fixed an issue with the creation of a group of cited entries. Now the file path to an aux file gets validated. [#6585](https://github.com/JabRef/jabref/issues/6585) +- We fixed an issue on Linux systems where the application would crash upon inotify failure. Now, the user is prompted with a warning, and given the choice to continue the session. [#6073](https://github.com/JabRef/jabref/issues/6073) +- We moved the search modifier buttons into the search bar, as they were not accessible, if autocompletion was disabled. [#6625](https://github.com/JabRef/jabref/issues/6625) +- We fixed an issue about duplicated group color indicators [#6175](https://github.com/JabRef/jabref/issues/6175) +- We fixed an issue where entries with the entry type Misc from an imported aux file would not be saved correctly to the bib file on disk [#6405](https://github.com/JabRef/jabref/issues/6405) +- We fixed an issue where percent sign ('%') was not formatted properly by the HTML formatter [#6753](https://github.com/JabRef/jabref/issues/6753) +- We fixed an issue with the [SAO/NASA Astrophysics Data System](https://docs.jabref.org/collect/add-entry-using-an-id#sao-nasa-a-ds) fetcher where `\textbackslash` appeared at the end of the abstract. +- We fixed an issue with the Science Direct fetcher where PDFs could not be downloaded. Fixes [#5860](https://github.com/JabRef/jabref/issues/5860) +- We fixed an issue with the Library of Congress importer. +- We fixed the [link to the external libraries listing](https://github.com/JabRef/jabref/blob/master/external-libraries.md) in the about dialog +- We fixed an issue regarding pasting on Linux. [#6293](https://github.com/JabRef/jabref/issues/6293) + +### Removed + +- We removed the option of the "enforce legal key". [#6295](https://github.com/JabRef/jabref/issues/6295) +- We removed the obsolete `External programs / Open PDF` section in the preferences, as the default application to open PDFs is now set in the `Manage external file types` dialog. [#6130](https://github.com/JabRef/jabref/pull/6130) +- We removed the option to configure whether a `.bib.bak` file should be generated upon save. It is now always enabled. Documentation at . [#6092](https://github.com/JabRef/jabref/issues/6092) +- We removed the built-in list of IEEE journal abbreviations using BibTeX strings. If you still want to use them, you have to download them separately from . + +## [5.0] – 2020-03-06 + +### Changed + +- Added browser integration to the snap package for firefox/chromium browsers. [#6062](https://github.com/JabRef/jabref/pull/6062) +- We reintroduced the possibility to extract references from plain text (using [GROBID](https://grobid.readthedocs.io/en/latest/)). [#5614](https://github.com/JabRef/jabref/pull/5614) +- We changed the open office panel to show buttons in rows of three instead of going straight down to save space as the button expanded out to take up unnecessary horizontal space. [#5479](https://github.com/JabRef/jabref/issues/5479) +- We cleaned up the group add/edit dialog. [#5826](https://github.com/JabRef/jabref/pull/5826) +- We reintroduced the index column. [#5844](https://github.com/JabRef/jabref/pull/5844) +- Filenames of external files can no longer contain curly braces. [#5926](https://github.com/JabRef/jabref/pull/5926) +- We made the filters more easily accessible in the integrity check dialog. [#5955](https://github.com/JabRef/jabref/pull/5955) +- We reimplemented and improved the dialog "Customize entry types". [#4719](https://github.com/JabRef/jabref/issues/4719) +- We added an [American Physical Society](https://journals.aps.org/) fetcher. [#818](https://github.com/JabRef/jabref/issues/818) +- We added possibility to enable/disable items quantity in groups. [#6042](https://github.com/JabRef/jabref/issues/6042) + +### Fixed + +- We fixed an issue where the command line console was always opened in the background. [#5474](https://github.com/JabRef/jabref/issues/5474) +- We fixed and issue where pdf files will not open under some KDE linux distributions when using okular. [#5253](https://github.com/JabRef/jabref/issues/5253) +- We fixed an issue where the Medline fetcher was only working when JabRef was running from source. [#5645](https://github.com/JabRef/jabref/issues/5645) +- We fixed some visual issues in the dark theme. [#5764](https://github.com/JabRef/jabref/pull/5764) [#5753](https://github.com/JabRef/jabref/issues/5753) +- We fixed an issue where non-default previews didn't handle unicode characters. [#5779](https://github.com/JabRef/jabref/issues/5779) +- We improved the performance, especially changing field values in the entry should feel smoother now. [#5843](https://github.com/JabRef/jabref/issues/5843) +- We fixed an issue where the ampersand character wasn't rendering correctly on previews. [#3840](https://github.com/JabRef/jabref/issues/3840) +- We fixed an issue where an erroneous "The library has been modified by another program" message was shown when saving. [#4877](https://github.com/JabRef/jabref/issues/4877) +- We fixed an issue where the file extension was missing after downloading a file (we now fall-back to pdf). [#5816](https://github.com/JabRef/jabref/issues/5816) +- We fixed an issue where cleaning up entries broke web URLs, if "Make paths of linked files relative (if possible)" was enabled, which resulted in various other issues subsequently. [#5861](https://github.com/JabRef/jabref/issues/5861) +- We fixed an issue where the tab "Required fields" of the entry editor did not show all required fields, if at least two of the defined required fields are linked with a logical or. [#5859](https://github.com/JabRef/jabref/issues/5859) +- We fixed several issues concerning managing external file types: Now everything is usable and fully functional. Previously, there were problems with the radio buttons, with saving the settings and with loading an input field value. Furthermore, different behavior for Windows and other operating systems was given, which was unified as well. [#5846](https://github.com/JabRef/jabref/issues/5846) +- We fixed an issue where entries containing Unicode charaters were not parsed correctly [#5899](https://github.com/JabRef/jabref/issues/5899) +- We fixed an issue where an entry containing an external filename with curly braces could not be saved. Curly braces are now longer allowed in filenames. [#5899](https://github.com/JabRef/jabref/issues/5899) +- We fixed an issue where changing the type of an entry did not update the main table [#5906](https://github.com/JabRef/jabref/issues/5906) +- We fixed an issue in the optics of the library properties, that cropped the dialog on scaled displays. [#5969](https://github.com/JabRef/jabref/issues/5969) +- We fixed an issue where changing the type of an entry did not update the main table. [#5906](https://github.com/JabRef/jabref/issues/5906) +- We fixed an issue where opening a library from the recent libraries menu was not possible. [#5939](https://github.com/JabRef/jabref/issues/5939) +- We fixed an issue where the most bottom group in the list got lost, if it was dragged on itself. [#5983](https://github.com/JabRef/jabref/issues/5983) +- We fixed an issue where changing entry type doesn't always work when biblatex source is shown. [#5905](https://github.com/JabRef/jabref/issues/5905) +- We fixed an issue where the group and the link column were not updated after changing the entry in the main table. [#5985](https://github.com/JabRef/jabref/issues/5985) +- We fixed an issue where reordering the groups was not possible after inserting an article. [#6008](https://github.com/JabRef/jabref/issues/6008) +- We fixed an issue where citation styles except the default "Preview" could not be used. [#5622](https://github.com/JabRef/jabref/issues/5622) +- We fixed an issue where a warning was displayed when the title content is made up of two sentences. [#5832](https://github.com/JabRef/jabref/issues/5832) +- We fixed an issue where an exception was thrown when adding a save action without a selected formatter in the library properties [#6069](https://github.com/JabRef/jabref/issues/6069) +- We fixed an issue where JabRef's icon was missing in the Export to clipboard Dialog. [#6286](https://github.com/JabRef/jabref/issues/6286) +- We fixed an issue when an "Abstract field" was duplicating text, when importing from RIS file (Neurons) [#6065](https://github.com/JabRef/jabref/issues/6065) +- We fixed an issue where adding the addition of a new entry was not completely validated [#6370](https://github.com/JabRef/jabref/issues/6370) +- We fixed an issue where the blue and red text colors in the Merge entries dialog were not quite visible [#6334](https://github.com/JabRef/jabref/issues/6334) +- We fixed an issue where underscore character was removed from the file name in the Recent Libraries list in File menu [#6383](https://github.com/JabRef/jabref/issues/6383) +- We fixed an issue where few keyboard shortcuts regarding new entries were missing [#6403](https://github.com/JabRef/jabref/issues/6403) + +### Removed + +- Ampersands are no longer escaped by default in the `bib` file. If you want to keep the current behaviour, you can use the new "Escape Ampersands" formatter as a save action. [#5869](https://github.com/JabRef/jabref/issues/5869) +- The "Merge Entries" entry was removed from the Quality Menu. Users should use the right-click menu instead. [#6021](https://github.com/JabRef/jabref/pull/6021) + +## [5.0-beta] – 2019-12-15 + +### Changed + +- We added a short DOI field formatter which shortens DOI to more human-readable form. [koppor#343](https://github.com/koppor/jabref/issues/343) +- We improved the display of group memberships by adding multiple colored bars if the entry belongs to more than one group. [#4574](https://github.com/JabRef/jabref/issues/4574) +- We added an option to show the preview as an extra tab in the entry editor (instead of in a split view). [#5244](https://github.com/JabRef/jabref/issues/5244) +- A custom Open/LibreOffice jstyle file now requires a layout line for the entry type `default` [#5452](https://github.com/JabRef/jabref/issues/5452) +- The entry editor is now open by default when JabRef starts up. [#5460](https://github.com/JabRef/jabref/issues/5460) +- Customized entry types are now serialized in alphabetical order in the bib file. +- We added a new ADS fetcher to use the new ADS API. [#4949](https://github.com/JabRef/jabref/issues/4949) +- We added support of the [X11 primary selection](https://unix.stackexchange.com/a/139193/18033) [#2389](https://github.com/JabRef/jabref/issues/2389) +- We added support to switch between biblatex and bibtex library types. [#5550](https://github.com/JabRef/jabref/issues/5550) +- We changed the save action buttons to be easier to understand. [#5565](https://github.com/JabRef/jabref/issues/5565) +- We made the columns for groups, files and uri in the main table reorderable and merged the clickable icon columns for uri, url, doi and eprint. [#5544](https://github.com/JabRef/jabref/pull/5544) +- We reduced the number of write actions performed when autosave is enabled [#5679](https://github.com/JabRef/jabref/issues/5679) +- We made the column sort order in the main table persistent [#5730](https://github.com/JabRef/jabref/pull/5730) +- When an entry is modified on disk, the change dialog now shows the merge dialog to highlight the changes [#5688](https://github.com/JabRef/jabref/pull/5688) + +### Fixed + +- Inherit fields from cross-referenced entries as specified by biblatex. [#5045](https://github.com/JabRef/jabref/issues/5045) +- We fixed an issue where it was no longer possible to connect to LibreOffice. [#5261](https://github.com/JabRef/jabref/issues/5261) +- The "All entries group" is no longer shown when no library is open. +- We fixed an exception which occurred when closing JabRef. [#5348](https://github.com/JabRef/jabref/issues/5348) +- We fixed an issue where JabRef reports incorrectly about customized entry types. [#5332](https://github.com/JabRef/jabref/issues/5332) +- We fixed a few problems that prevented JabFox to communicate with JabRef. [#4737](https://github.com/JabRef/jabref/issues/4737) [#4303](https://github.com/JabRef/jabref/issues/4303) +- We fixed an error where the groups containing an entry loose their highlight color when scrolling. [#5022](https://github.com/JabRef/jabref/issues/5022) +- We fixed an error where scrollbars were not shown. [#5374](https://github.com/JabRef/jabref/issues/5374) +- We fixed an error where an exception was thrown when merging entries. [#5169](https://github.com/JabRef/jabref/issues/5169) +- We fixed an error where certain metadata items were not serialized alphabetically. +- After assigning an entry to a group, the item count is now properly colored to reflect the new membership of the entry. [#3112](https://github.com/JabRef/jabref/issues/3112) +- The group panel is now properly updated when switching between libraries (or when closing/opening one). [#3142](https://github.com/JabRef/jabref/issues/3142) +- We fixed an error where the number of matched entries shown in the group pane was not updated correctly. [#4441](https://github.com/JabRef/jabref/issues/4441) +- We fixed an error where the wrong file is renamed and linked when using the "Copy, rename and link" action. [#5653](https://github.com/JabRef/jabref/issues/5653) +- We fixed a "null" error when writing XMP metadata. [#5449](https://github.com/JabRef/jabref/issues/5449) +- We fixed an issue where empty keywords lead to a strange display of automatic keyword groups. [#5333](https://github.com/JabRef/jabref/issues/5333) +- We fixed an error where the default color of a new group was white instead of dark gray. [#4868](https://github.com/JabRef/jabref/issues/4868) +- We fixed an issue where the first field in the entry editor got the focus while performing a different action (like searching). [#5084](https://github.com/JabRef/jabref/issues/5084) +- We fixed an issue where multiple entries were highlighted in the web search result after scrolling. [#5035](https://github.com/JabRef/jabref/issues/5035) +- We fixed an issue where the hover indication in the web search pane was not working. [#5277](https://github.com/JabRef/jabref/issues/5277) +- We fixed an error mentioning "javafx.controls/com.sun.javafx.scene.control" that was thrown when interacting with the toolbar. +- We fixed an error where a cleared search was restored after switching libraries. [#4846](https://github.com/JabRef/jabref/issues/4846) +- We fixed an exception which occurred when trying to open a non-existing file from the "Recent files"-menu [#5334](https://github.com/JabRef/jabref/issues/5334) +- We fixed an issues where the search highlight in the entry preview did not worked. [#5069](https://github.com/JabRef/jabref/issues/5069) +- The context menu for fields in the entry editor is back. [#5254](https://github.com/JabRef/jabref/issues/5254) +- We fixed an exception which occurred when trying to open a non-existing file from the "Recent files"-menu [#5334](https://github.com/JabRef/jabref/issues/5334) +- We fixed a problem where the "editor" information has been duplicated during saving a .bib-Database. [#5359](https://github.com/JabRef/jabref/issues/5359) +- We re-introduced the feature to switch between different preview styles. [#5221](https://github.com/JabRef/jabref/issues/5221) +- We fixed various issues (including [#5263](https://github.com/JabRef/jabref/issues/5263)) related to copying entries to the clipboard +- We fixed some display errors in the preferences dialog and replaced some of the controls [#5033](https://github.com/JabRef/jabref/pull/5033) [#5047](https://github.com/JabRef/jabref/pull/5047) [#5062](https://github.com/JabRef/jabref/pull/5062) [#5141](https://github.com/JabRef/jabref/pull/5141) [#5185](https://github.com/JabRef/jabref/pull/5185) [#5265](https://github.com/JabRef/jabref/pull/5265) [#5315](https://github.com/JabRef/jabref/pull/5315) [#5360](https://github.com/JabRef/jabref/pull/5360) +- We fixed an exception which occurred when trying to import entries without an open library. [#5447](https://github.com/JabRef/jabref/issues/5447) +- The "Automatically set file links" feature now follows symbolic links. [#5664](https://github.com/JabRef/jabref/issues/5664) +- After successful import of one or multiple bib entries the main table scrolls to the first imported entry [#5383](https://github.com/JabRef/jabref/issues/5383) +- We fixed an exception which occurred when an invalid jstyle was loaded. [#5452](https://github.com/JabRef/jabref/issues/5452) +- We fixed an issue where the command line arguments `importBibtex` and `importToOpen` did not import into the currently open library, but opened a new one. [#5537](https://github.com/JabRef/jabref/issues/5537) +- We fixed an error where the preview theme did not adapt to the "Dark" mode [#5463](https://github.com/JabRef/jabref/issues/5463) +- We fixed an issue where multiple entries were allowed in the "crossref" field [#5284](https://github.com/JabRef/jabref/issues/5284) +- We fixed an issue where the merge dialog showed the wrong text colour in "Dark" mode [#5516](https://github.com/JabRef/jabref/issues/5516) +- We fixed visibility issues with the scrollbar and group selection highlight in "Dark" mode, and enabled "Dark" mode for the OpenOffice preview in the style selection window. [#5522](https://github.com/JabRef/jabref/issues/5522) +- We fixed an issue where the author field was not correctly parsed during bibtex key-generation. [#5551](https://github.com/JabRef/jabref/issues/5551) +- We fixed an issue where notifications where shown during autosave. [#5555](https://github.com/JabRef/jabref/issues/5555) +- We fixed an issue where the side pane was not remembering its position. [#5615](https://github.com/JabRef/jabref/issues/5615) +- We fixed an issue where JabRef could not interact with [Oracle XE](https://www.oracle.com/de/database/technologies/appdev/xe.html) in the [shared SQL database setup](https://docs.jabref.org/collaborative-work/sqldatabase). +- We fixed an issue where the toolbar icons were hidden on smaller screens. +- We fixed an issue where renaming referenced files for bib entries with long titles was not possible. [#5603](https://github.com/JabRef/jabref/issues/5603) +- We fixed an issue where a window which is on an external screen gets unreachable when external screen is removed. [#5037](https://github.com/JabRef/jabref/issues/5037) +- We fixed a bug where the selection of groups was lost after drag and drop. [#2868](https://github.com/JabRef/jabref/issues/2868) +- We fixed an issue where the custom entry types didn't show the correct display name [#5651](https://github.com/JabRef/jabref/issues/5651) + +### Removed + +- We removed some obsolete notifications. [#5555](https://github.com/JabRef/jabref/issues/5555) +- We removed an internal step in the [ISBN-to-BibTeX fetcher](https://docs.jabref.org/collect/add-entry-using-an-id#isbn): The [ISBN to BibTeX Converter](https://manas.tungare.name/software/isbn-to-bibtex) by [@manastungare](https://github.com/manastungare) is not used anymore, because it is offline: "people using this tool have not been generating enough sales for Amazon." +- We removed the option to control the default drag and drop behaviour. You can use the modifier keys (like CtrL or Alt) instead. + +## [5.0-alpha] – 2019-08-25 + +### Changed + +- We added eventitle, eventdate and venue fields to `@unpublished` entry type. +- We added `@software` and `@dataSet` entry type to biblatex. +- All fields are now properly sorted alphabetically (in the subgroups of required/optional fields) when the entry is written to the bib file. +- We fixed an issue where some importers used the field `pubstatus` instead of the standard BibTeX field `pubstate`. +- We changed the latex command removal for docbook exporter. [#3838](https://github.com/JabRef/jabref/issues/3838) +- We changed the location of some fields in the entry editor (you might need to reset your preferences for these changes to come into effect) + - Journal/Year/Month in biblatex mode -> Deprecated (if filled) + - DOI/URL: General -> Optional + - Internal fields like ranking, read status and priority: Other -> General + - Moreover, empty deprecated fields are no longer shown +- Added server timezone parameter when connecting to a shared database. +- We updated the dialog for setting up general fields. +- URL field formatting is updated. All whitespace chars, located at the beginning/ending of the URL, are trimmed automatically +- We changed the behavior of the field formatting dialog such that the `bibtexkey` is not changed when formatting all fields or all text fields. +- We added a "Move file to file directory and rename file" option for simultaneously moving and renaming of document file. [#4166](https://github.com/JabRef/jabref/issues/4166) +- Use integrated graphics card instead of discrete on macOS [#4070](https://github.com/JabRef/jabref/issues/4070) +- We added a cleanup operation that detects an arXiv identifier in the note, journal or URL field and moves it to the `eprint` field. + Because of this change, the last-used cleanup operations were reset. +- We changed the minimum required version of Java to 1.8.0_171, as this is the latest release for which the automatic Java update works. [#4093](https://github.com/JabRef/jabref/issues/4093) +- The special fields like `Printed` and `Read status` now show gray icons when the row is hovered. +- We added a button in the tab header which allows you to close the database with one click. [#494](https://github.com/JabRef/jabref/issues/494) +- Sorting in the main table now takes information from cross-referenced entries into account. [#2808](https://github.com/JabRef/jabref/issues/2808) +- If a group has a color specified, then entries matched by this group have a small colored bar in front of them in the main table. +- Change default icon for groups to a circle because a colored version of the old icon was hard to distinguish from its black counterpart. +- In the main table, the context menu appears now when you press the "context menu" button on the keyboard. [feature request in the forum](https://discourse.jabref.org/t/how-to-enable-keyboard-context-key-windows) +- We added icons to the group side panel to quickly switch between `union` and `intersection` group view mode. [#3269](https://github.com/JabRef/jabref/issues/3269). +- We use `https` for [fetching from most online bibliographic database](https://docs.jabref.org/collect/import-using-online-bibliographic-database). +- We changed the default keyboard shortcuts for moving between entries when the entry editor is active to ̀alt + up/down. +- Opening a new file now prompts the directory of the currently selected file, instead of the directory of the last opened file. +- Window state is saved on close and restored on start. +- We made the MathSciNet fetcher more reliable. +- We added the ISBN fetcher to the list of fetcher available under "Update with bibliographic information from the web" in the entry editor toolbar. +- Files without a defined external file type are now directly opened with the default application of the operating system +- We streamlined the process to rename and move files by removing the confirmation dialogs. +- We removed the redundant new lines of markings and wrapped the summary in the File annotation tab. [#3823](https://github.com/JabRef/jabref/issues/3823) +- We add auto URL formatting when user paste link to URL field in entry editor. [koppor#254](https://github.com/koppor/jabref/issues/254) +- We added a minimum height for the entry editor so that it can no longer be hidden by accident. [#4279](https://github.com/JabRef/jabref/issues/4279) +- We added a new keyboard shortcut so that the entry editor could be closed by Ctrl + E. [#4222](https://github.com/JabRef/jabref/issues/4222) +- We added an option in the preference dialog box, that allows user to pick the dark or light theme option. [#4130](https://github.com/JabRef/jabref/issues/4130) +- We updated the Related Articles tab to accept JSON from the new version of the Mr. DLib service +- We added an option in the preference dialog box that allows user to choose behavior after dragging and dropping files in Entry Editor. [#4356](https://github.com/JabRef/jabref/issues/4356) +- We added the ability to have an export preference where previously "File"-->"Export"/"Export selected entries" would not save the user's preference[#4495](https://github.com/JabRef/jabref/issues/4495) +- We optimized the code responsible for connecting to an external database, which should lead to huge improvements in performance. +- For automatically created groups, added ability to filter groups by entry type. [#4539](https://github.com/JabRef/jabref/issues/4539) +- We added the ability to add field names from the Preferences Dialog [#4546](https://github.com/JabRef/jabref/issues/4546) +- We added the ability to change the column widths directly in the main table. [#4546](https://github.com/JabRef/jabref/issues/4546) +- We added a description of how recommendations were chosen and better error handling to Related Articles tab +- We added the ability to execute default action in dialog by using with Ctrl + Enter combination [#4496](https://github.com/JabRef/jabref/issues/4496) +- We grouped and reordered the Main Menu (File, Edit, Library, Quality, Tools, and View tabs & icons). [#4666](https://github.com/JabRef/jabref/issues/4666) [#4667](https://github.com/JabRef/jabref/issues/4667) [#4668](https://github.com/JabRef/jabref/issues/4668) [#4669](https://github.com/JabRef/jabref/issues/4669) [#4670](https://github.com/JabRef/jabref/issues/4670) [#4671](https://github.com/JabRef/jabref/issues/4671) [#4672](https://github.com/JabRef/jabref/issues/4672) [#4673](https://github.com/JabRef/jabref/issues/4673) +- We added additional modifiers (capitalize, titlecase and sentencecase) to the Bibtex key generator. [#1506](https://github.com/JabRef/jabref/issues/1506) +- We have migrated from the mysql jdbc connector to the mariadb one for better authentication scheme support. [#4745](https://github.com/JabRef/jabref/issues/4745) +- We grouped the toolbar icons and changed the Open Library and Copy icons. [#4584](https://github.com/JabRef/jabref/issues/4584) +- We added a browse button next to the path text field for aux-based groups. [#4586](https://github.com/JabRef/jabref/issues/4586) +- We changed the title of Group Dialog to "Add subgroup" from "Edit group" when we select Add subgroup option. +- We enable import button only if entries are selected. [#4755](https://github.com/JabRef/jabref/issues/4755) +- We made modifications to improve the contrast of UI elements. [#4583](https://github.com/JabRef/jabref/issues/4583) +- We added a warning for empty BibTeX keys in the entry editor. [#4440](https://github.com/JabRef/jabref/issues/4440) +- We added an option in the settings to set the default action in JabRef when right clicking on any entry in any database and selecting "Open folder". [#4763](https://github.com/JabRef/jabref/issues/4763) +- The Medline fetcher now normalizes the author names according to the BibTeX-Standard [#4345](https://github.com/JabRef/jabref/issues/4345) +- We added an option on the Linked File Viewer to rename the attached file of an entry directly on the JabRef. [#4844](https://github.com/JabRef/jabref/issues/4844) +- We added an option in the preference dialog box that allows user to enable helpful tooltips.[#3599](https://github.com/JabRef/jabref/issues/3599) +- We reworked the functionality for extracting BibTeX entries from plain text, because our used service [freecite shut down](https://library.brown.edu/libweb/freecite_notice.php). [#5206](https://github.com/JabRef/jabref/pull/5206) +- We moved the dropdown menu for selecting the push-application from the toolbar into the external application preferences. [#674](https://github.com/JabRef/jabref/issues/674) +- We removed the alphabetical ordering of the custom tabs and updated the error message when trying to create a general field with a name containing an illegal character. [#5019](https://github.com/JabRef/jabref/issues/5019) +- We added a context menu to the bib(la)tex-source-editor to copy'n'paste. [#5007](https://github.com/JabRef/jabref/pull/5007) +- We added a tool that allows searching for citations in LaTeX files. It scans directories and shows which entries are used, how many times and where. +- We added a 'LaTeX citations' tab to the entry editor, to search for citations to the active entry in the LaTeX file directory. It can be disabled in the preferences dialog. +- We added an option in preferences to allow for integers in field "edition" when running database in bibtex mode. [#4680](https://github.com/JabRef/jabref/issues/4680) +- We added the ability to use negation in export filter layouts. [#5138](https://github.com/JabRef/jabref/pull/5138) +- Focus on Name Area instead of 'OK' button whenever user presses 'Add subgroup'. [#6307](https://github.com/JabRef/jabref/issues/6307) +- We changed the behavior of merging that the entry which has "smaller" bibkey will be selected. [#7395](https://github.com/JabRef/jabref/issues/7395) + +### Fixed + +- We fixed an issue where JabRef died silently for the user without enough inotify instances [#4874](https://github.com/JabRef/jabref/issues/4874) +- We fixed an issue where corresponding groups are sometimes not highlighted when clicking on entries [#3112](https://github.com/JabRef/jabref/issues/3112) +- We fixed an issue where custom exports could not be selected in the 'Export (selected) entries' dialog [#4013](https://github.com/JabRef/jabref/issues/4013) +- Italic text is now rendered correctly. [#3356](https://github.com/JabRef/jabref/issues/3356) +- The entry editor no longer gets corrupted after using the source tab. [#3532](https://github.com/JabRef/jabref/issues/3532) [#3608](https://github.com/JabRef/jabref/issues/3608) [#3616](https://github.com/JabRef/jabref/issues/3616) +- We fixed multiple issues where entries did not show up after import if a search was active. [#1513](https://github.com/JabRef/jabref/issues/1513) [#3219](https://github.com/JabRef/jabref/issues/3219)) +- We fixed an issue where the group tree was not updated correctly after an entry was changed. [#3618](https://github.com/JabRef/jabref/issues/3618) +- We fixed an issue where a right-click in the main table selected a wrong entry. [#3267](https://github.com/JabRef/jabref/issues/3267) +- We fixed an issue where in rare cases entries where overlayed in the main table. [#3281](https://github.com/JabRef/jabref/issues/3281) +- We fixed an issue where selecting a group messed up the focus of the main table and the entry editor. [#3367](https://github.com/JabRef/jabref/issues/3367) +- We fixed an issue where composite author names were sorted incorrectly. [#2828](https://github.com/JabRef/jabref/issues/2828) +- We fixed an issue where commands followed by `-` didn't work. [#3805](https://github.com/JabRef/jabref/issues/3805) +- We fixed an issue where a non-existing aux file in a group made it impossible to open the library. [#4735](https://github.com/JabRef/jabref/issues/4735) +- We fixed an issue where some journal names were wrongly marked as abbreviated. [#4115](https://github.com/JabRef/jabref/issues/4115) +- We fixed an issue where the custom file column were sorted incorrectly. [#3119](https://github.com/JabRef/jabref/issues/3119) +- We improved the parsing of author names whose infix is abbreviated without a dot. [#4864](https://github.com/JabRef/jabref/issues/4864) +- We fixed an issues where the entry losses focus when a field is edited and at the same time used for sorting. [#3373](https://github.com/JabRef/jabref/issues/3373) +- We fixed an issue where the menu on Mac OS was not displayed in the usual Mac-specific way. [#3146](https://github.com/JabRef/jabref/issues/3146) +- We improved the integrity check for page numbers. [#4113](https://github.com/JabRef/jabref/issues/4113) and [feature request in the forum](https://discourse.jabref.org/t/pages-field-allow-use-of-en-dash/1199) +- We fixed an issue where the order of fields in customized entry types was not saved correctly. [#4033](https://github.com/JabRef/jabref/issues/4033) +- We fixed an issue where renaming a group did not change the group name in the interface. [#3189](https://github.com/JabRef/jabref/issues/3189) +- We fixed an issue where the groups tree of the last database was still shown even after the database was already closed. +- We fixed an issue where the "Open file dialog" may disappear behind other windows. [#3410](https://github.com/JabRef/jabref/issues/3410) +- We fixed an issue where the number of entries matched was not updated correctly upon adding or removing an entry. [#3537](https://github.com/JabRef/jabref/issues/3537) +- We fixed an issue where the default icon of a group was not colored correctly. +- We fixed an issue where the first field in entry editor was not focused when adding a new entry. [#4024](https://github.com/JabRef/jabref/issues/4024) +- We reworked the "Edit file" dialog to make it resizeable and improved the workflow for adding and editing files [#2970](https://github.com/JabRef/jabref/issues/2970) +- We fixed an issue where custom name formatters were no longer found correctly. [#3531](https://github.com/JabRef/jabref/issues/3531) +- We fixed an issue where the month was not shown in the preview. [#3239](https://github.com/JabRef/jabref/issues/3239) +- Rewritten logic to detect a second jabref instance. [#4023](https://github.com/JabRef/jabref/issues/4023) +- We fixed an issue where the "Convert to BibTeX-Cleanup" moved the content of the `file` field to the `pdf` field [#4120](https://github.com/JabRef/jabref/issues/4120) +- We fixed an issue where the preview pane in entry preview in preferences wasn't showing the citation style selected [#3849](https://github.com/JabRef/jabref/issues/3849) +- We fixed an issue where the default entry preview style still contained the field `review`. The field `review` in the style is now replaced with comment to be consistent with the entry editor [#4098](https://github.com/JabRef/jabref/issues/4098) +- We fixed an issue where users were vulnerable to XXE attacks during parsing [#4229](https://github.com/JabRef/jabref/issues/4229) +- We fixed an issue where files added via the "Attach file" contextmenu of an entry were not made relative. [#4201](https://github.com/JabRef/jabref/issues/4201) and [#4241](https://github.com/JabRef/jabref/issues/4241) +- We fixed an issue where author list parser can't generate bibtex for Chinese author. [#4169](https://github.com/JabRef/jabref/issues/4169) +- We fixed an issue where the list of XMP Exclusion fields in the preferences was not be saved [#4072](https://github.com/JabRef/jabref/issues/4072) +- We fixed an issue where the ArXiv Fetcher did not support HTTP URLs [koppor#328](https://github.com/koppor/jabref/issues/328) +- We fixed an issue where only one PDF file could be imported [#4422](https://github.com/JabRef/jabref/issues/4422) +- We fixed an issue where "Move to group" would always move the first entry in the library and not the selected [#4414](https://github.com/JabRef/jabref/issues/4414) +- We fixed an issue where an older dialog appears when downloading full texts from the quality menu. [#4489](https://github.com/JabRef/jabref/issues/4489) +- We fixed an issue where right clicking on any entry in any database and selecting "Open folder" results in the NullPointer exception. [#4763](https://github.com/JabRef/jabref/issues/4763) +- We fixed an issue where option 'open terminal here' with custom command was passing the wrong argument. [#4802](https://github.com/JabRef/jabref/issues/4802) +- We fixed an issue where ranking an entry would generate an IllegalArgumentException. [#4754](https://github.com/JabRef/jabref/issues/4754) +- We fixed an issue where special characters where removed from non-label key generation pattern parts [#4767](https://github.com/JabRef/jabref/issues/4767) +- We fixed an issue where the RIS import would overwite the article date with the value of the acessed date [#4816](https://github.com/JabRef/jabref/issues/4816) +- We fixed an issue where an NullPointer exception was thrown when a referenced entry in an Open/Libre Office document was no longer present in the library. Now an error message with the reference marker of the missing entry is shown. [#4932](https://github.com/JabRef/jabref/issues/4932) +- We fixed an issue where a database exception related to a missing timezone was too big. [#4827](https://github.com/JabRef/jabref/issues/4827) +- We fixed an issue where the IEEE fetcher returned an error if no keywords were present in the result from the IEEE website [#4997](https://github.com/JabRef/jabref/issues/4997) +- We fixed an issue where the command line help text had several errors, and arguments and descriptions have been rewritten to simplify and detail them better. [#2016](https://github.com/JabRef/jabref/issues/2016) +- We fixed an issue where the same menu for changing entry type had two different sizes and weights. [#4977](https://github.com/JabRef/jabref/issues/4977) +- We fixed an issue where the "Attach file" dialog, in the right-click menu for an entry, started on the working directory instead of the user's main directory. [#4995](https://github.com/JabRef/jabref/issues/4995) +- We fixed an issue where the JabRef Icon in the macOS launchpad was not displayed correctly [#5003](https://github.com/JabRef/jabref/issues/5003) +- We fixed an issue where the "Search for unlinked local files" would throw an exception when parsing the content of a PDF-file with missing "series" information [#5128](https://github.com/JabRef/jabref/issues/5128) +- We fixed an issue where the XMP Importer would incorrectly return an empty default entry when importing pdfs [#6577](https://github.com/JabRef/jabref/issues/6577) +- We fixed an issue where opening the menu 'Library properties' marked the library as modified [#6451](https://github.com/JabRef/jabref/issues/6451) +- We fixed an issue when importing resulted in an exception [#7343](https://github.com/JabRef/jabref/issues/7343) +- We fixed an issue where the field in the Field formatter dropdown selection were sorted in random order. [#7710](https://github.com/JabRef/jabref/issues/7710) + +### Removed + +- The feature to "mark entries" was removed and merged with the groups functionality. For migration, a group is created for every value of the `__markedentry` field and the entry is added to this group. +- The number column was removed. +- We removed the global search feature. +- We removed the coloring of cells in the main table according to whether the field is optional/required. +- We removed the feature to find and resolve duplicate BibTeX keys (as this use case is already covered by the integrity check). +- We removed a few commands from the right-click menu that are not needed often and thus don't need to be placed that prominently: + - Print entry preview: available through entry preview + - All commands related to marking: marking is not yet reimplemented + - Set/clear/append/rename fields: available through Edit menu + - Manage keywords: available through the Edit menu + - Copy linked files to folder: available through File menu + - Add/move/remove from group: removed completely (functionality still available through group interface) +- We removed the option to change the column widths in the preferences dialog. [#4546](https://github.com/JabRef/jabref/issues/4546) + +## Older versions + +The changelog of JabRef 4.x is available at the [v4.3.1 tag](https://github.com/JabRef/jabref/blob/v4.3.1/CHANGELOG.md). +The changelog of JabRef 3.x is available at the [v3.8.2 tag](https://github.com/JabRef/jabref/blob/v3.8.2/CHANGELOG.md). +The changelog of JabRef 2.11 and all previous versions is available as [text file in the v2.11.1 tag](https://github.com/JabRef/jabref/blob/v2.11.1/CHANGELOG). + +[Unreleased]: https://github.com/JabRef/jabref/compare/v5.15...HEAD +[5.15]: https://github.com/JabRef/jabref/compare/v5.14...v5.15 +[5.14]: https://github.com/JabRef/jabref/compare/v5.13...v5.14 +[5.13]: https://github.com/JabRef/jabref/compare/v5.12...v5.13 +[5.12]: https://github.com/JabRef/jabref/compare/v5.11...v5.12 +[5.11]: https://github.com/JabRef/jabref/compare/v5.10...v5.11 +[5.10]: https://github.com/JabRef/jabref/compare/v5.9...v5.10 +[5.9]: https://github.com/JabRef/jabref/compare/v5.8...v5.9 +[5.8]: https://github.com/JabRef/jabref/compare/v5.7...v5.8 +[5.7]: https://github.com/JabRef/jabref/compare/v5.6...v5.7 +[5.6]: https://github.com/JabRef/jabref/compare/v5.5...v5.6 +[5.5]: https://github.com/JabRef/jabref/compare/v5.4...v5.5 +[5.4]: https://github.com/JabRef/jabref/compare/v5.3...v5.4 +[5.3]: https://github.com/JabRef/jabref/compare/v5.2...v5.3 +[5.2]: https://github.com/JabRef/jabref/compare/v5.1...v5.2 +[5.1]: https://github.com/JabRef/jabref/compare/v5.0...v5.1 +[5.0]: https://github.com/JabRef/jabref/compare/v5.0-beta...v5.0 +[5.0-beta]: https://github.com/JabRef/jabref/compare/v5.0-alpha...v5.0-beta +[5.0-alpha]: https://github.com/JabRef/jabref/compare/v4.3...v5.0-alpha + diff --git a/jabref/CITATION.cff b/jabref/CITATION.cff new file mode 100644 index 00000000..570a4e57 --- /dev/null +++ b/jabref/CITATION.cff @@ -0,0 +1,58 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: JabRef +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - given-names: Oliver + family-names: Kopp + orcid: 'https://orcid.org/0000-0001-6962-4290' + - given-names: Tobias + family-names: Diez + orcid: 'https://orcid.org/0000-0002-1407-7696' + - given-names: Christoph + family-names: Schwentker + - given-names: Carl Christian + family-names: Snethlage + - given-names: Jonatan + family-names: Asketorp + - given-names: Benedikt + family-names: Tutzer + - given-names: Thilo + family-names: Ertel + - given-names: Houssem + family-names: Nasri +repository-code: 'https://github.com/jabref/jabref/' +url: 'https://www.jabref.org' +abstract: >- + JabRef is an open-source, cross-platform citation and + reference management tool. +keywords: + - reference manager + - bibtex + - biblatex +license: MIT +preferred-citation: + type: article + authors: + - family-names: "Kopp" + given-names: "Oliver" + orcid: "https://orcid.org/0000-0001-6962-4290" + - family-names: "Snethlage" + given-names: "Carl Christian" + - family-names: "Schwentker" + given-names: "Christoph" + doi: "10.47397/tb/44-3/tb138kopp-jabref" + journal: "TUGboat" + month: 11 + start: 441 + end: 447 + title: "JabRef: BibTeX-based literature management software" + issue: 138 + volume: 44 + number: 3 + year: 2023 diff --git a/jabref/CONTRIBUTING.md b/jabref/CONTRIBUTING.md new file mode 100644 index 00000000..e0efbdd9 --- /dev/null +++ b/jabref/CONTRIBUTING.md @@ -0,0 +1,198 @@ +# Contributing + +General overview about contributing for non-programmers is available at . + +We welcome contributions to JabRef and encourage you to follow the GitHub workflow specified below. +If you are not familiar with this type of workflow, take a look at GitHub's excellent overview on the [GitHub flow](https://docs.github.com/en/get-started/using-github/github-flow) and the explanation of [Feature Branch Workflow](https://atlassian.com/git/tutorials/comparing-workflows#feature-branch-workflow) for the idea behind this kind of development. + +Before you start, get the JabRef code on your local machine. +Detailed instructions about this step can be found in our [guidelines for setting up a local workspace](getting-into-the-code/guidelines-for-setting-up-a-local-workspace/). + +## Table of Contents + +* [Choosing a task](#choosing-a-task-) +* [Getting a task assigned](#getting-a-task-assigned) +* [Pull Request Process](#pull-request-process) + * [Requirements on the pull request and code](#requirements-on-the-pull-request-and-code) + * [After submission of a pull request](#after-submission-of-a-pull-request) + * [Development hints](#development-hints) + +## Choosing a task [![Join the chat at https://gitter.im/JabRef/jabref](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/JabRef/jabref) + +In general, we offer small issues perfect for aspiring developers. +These tasks provide an opportunity to learn how to set up your local workspace, create your first pull request on GitHub, and contribute towards solving minor problems or making small enhancements in JabRef. + +It is essential to note that JabRef's issues vary in difficulty. +Some are simpler, while others are more complex. Our primary aim is to guide you through the code, ensuring that the understanding scope remains manageable. Sometimes, grasping the code might demand more effort than actually writing lines of code. + +### I am a student and I want to start with something easy + +We collect good issues to start with at our [list of good first issues](https://github.com/orgs/JabRef/projects/5/views/1). + +### I am a student and I want to choose from a curated list of university projects + +Take a look at [JabRef's candidates for university projects](https://github.com/orgs/JabRef/projects/3). There, a list of possible projects to work on during a teaching period is offered. + +### I am a lecturer + +If you ask yourself how to integrate JabRef into your class, please read the [documentation about how to integrate JabRef into a class of software engineering training](https://devdocs.jabref.org/teaching.html#jabref-and-software-engineering-training). +As student, you may notify your lecturer about this possibility. + +### I want something with huge impact + +Look at the discussions in our forum about [new features](https://discourse.jabref.org/c/features/6). +Find an interesting topic, discuss it and start contributing. +Alternatively, you can check out [JabRef's projects page at GitHub](https://github.com/JabRef/jabref/projects?query=is%3Aopen). +Although, of course, you can choose to work on ANY issue, choosing from the projects page has the advantage that these issues have already been categorized, sorted and screened by JabRef maintainers. +A typical subclassifications scheme is "priority" (high, normal and low). Fixing high priority issues is preferred. + +### I want to know how to contribute code and set up my workspace + +Check out the [documentation for developers](https://devdocs.jabref.org/contributing.html#contribute-code) + +### I want to improve the developer's documentation + +For improving developer's documentation, go on at the [docs/ subdirectory of JabRef's code](https://github.com/JabRef/jabref/tree/main/docs) and edit the file. +GitHub offers a good guide at [Editing files in another user's repository](https://help.github.com/en/github/managing-files-in-a-repository/editing-files-in-another-users-repository). +One can also add [callouts](https://just-the-docs.github.io/just-the-docs-tests/components/callouts/). + +## Getting a task assigned + +Comment on the issue you want to work at with `/assign-me`. +GitHub will then automatically assign you. + +## Pull Request Process + +1. Follow the steps at [Pre Condition 3: Code on the local machine](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-03-code.html) to a) create a fork and b) have the fork checked out on your local machine +2. Ensure that you followed the [steps to set up a local workspace](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/) to have the code running properly in IntelliJ. +3. **Create a new branch** (such as `fix-for-issue-121`). Be sure to create a **separate branch** for each improvement you implement. +4. Work on the **new branch — not the `main` branch.** Refer to our [code how-tos](https://devdocs.jabref.org/code-howtos) if you have questions about your implementation. +5. Create a [pull request to JabRef main repository](https://github.com/JabRef/jabref/pulls). + For an overview on the concept of pull requests, take a look at GitHub's [pull request help documentation](https://help.github.com/articles/about-pull-requests/). + 1. Ensure that you followed the requirements listed below. They are not too hard, they merely support the maintainers to focus on supportive feedback than just stating the obvious. + 2. For text inspirations, consider [How to write the perfect pull request](https://github.com/blog/1943-how-to-write-the-perfect-pull-request). + 3. In case your pull request is not yet complete or not yet ready for review, create a [draft pull request](https://github.blog/2019-02-14-introducing-draft-pull-requests/) instead. +6. Wait for feedback of the developers +7. Address the feedback of the developers +8. After two developers gave their green flag, the pull request will be merged. + +In case you have any questions, please + +1. comment on the issue, +2. show up in our [Gitter chat](https://gitter.im/JabRef/jabref), or +3. show your current code using a draft pull request and ask questions. + +We favor looking into your code using a draft pull request, because we can then also load the code into our IDE. +As counterexample, if you provide us with a screenshot of your changes, we cannot run it in our IDE. + +### Requirements on the pull request and code + +#### Test your code + +We know that writing test cases takes a lot of time. +Nevertheless, we rely on our test cases to ensure that a bug fix or a feature implementation does not break anything. + +For UI changes, we know that test cases are hard to write. +Therefore, you can omit them. +However, please at least add a screenshot showing your changes to the request. + + + +#### Write a good commit message + +See [good commit message](https://github.com/joelparkerhenderson/git-commit-message) or [commit guidelines section of Pro Git](http://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines). For the curious: [Why good commit messages matter!](https://cbea.ms/git-commit/). The first line of your commit message is automatically taken as the title for the pull-request. All other lines make up the body of the pull request. Add the words `fixes #xxx` to your PR to auto-close the corresponding issue. + +#### Add your change to `CHANGELOG.md` + +You should edit the [`CHANGELOG.md`](https://github.com/JabRef/jabref/blob/main/CHANGELOG.md#changelog) file located in the root directory of the JabRef source. Add a line with your changes in the appropriate section. + +If you did internal refactorings or improvements not visible to the user (e.g., UI, .bib file), then you don't need to put an entry there. + +#### Author credits + +Please, **do not add yourself at JavaDoc's `@authors`**. +The contribution information is tracked via the version control system and shown at [https://github.com/JabRef/jabref/graphs/contributors](https://github.com/JabRef/jabref/graphs/contributors). +We also show all contributors in our blog posts. See [Release 5.15 blog post](https://blog.jabref.org/2024/07/16/JabRef5-15/) for an example. + +Your contribution is considered being made under [MIT license](https://tldrlegal.com/license/mit-license). + +#### Notes on AI usage + +Please keep these two principles in mind when you contribute: + +1. Never let an LLM speak for you. +2. Never let an LLM think for you. + +More reading on that is available at . + +We reserve the right to reject pull requests that contain little or no genuine and original contribution from the contributor. + +### After submission of a pull request + +After you submitted a pull request, automated checks will run. +You may see "Some checks were not successful". +You can click on failing checks to see more information about why they failed. +Then, please look into them and handle accordingly. + +Afterwards, you will receive comments on your pull request. +The pull request may be approved immediatly, or a reviewer may request changes. +In that case, please implement the requested changes. + +After implementing changes, commit to the branch your pull request is *from* and push. +The pull request will automatically be updated with your changes. +Your commits will also be automatically squashed upon the pull request being accepted. + +Please – **Never ever close a pull request and open a new one** - +This causes unessesary work on our side, and is not in the the style of the GitHub open source community. +You can push any changes you need to make to the branch your pull request is *from*. +These changes will be automatically added to your pull request. + +### Development hints + +#### When adding an external dependency + +Please try to use a version available at JCenter and add it to `build.gradle`. +In any case, describe the library at [`external-libraries.md`](https://github.com/JabRef/jabref/blob/main/external-libraries.md#external-libraries). +We need that information for our package maintainers (e.g., those of the [debian package](https://tracker.debian.org/pkg/jabref)). + +#### When making an architectural decision + +In case you add a library or do major code rewrites, we ask you to document your decision. Recommended reading: [https://adr.github.io/](https://adr.github.io). + +We simply ask to create a new markdown file in `docs/adr` following the template presented at [https://adr.github.io/madr/](https://adr.github.io/madr/). +You can link that ADR using `@ADR({num})` as annotation. + +#### When adding a new `Localization.lang` entry + +Add new `Localization.lang("KEY")` to a Java file. The tests will fail. In the test output a snippet is generated, which must be added to the English translation file. + +Example: + +```text +java.lang.AssertionError: DETECTED LANGUAGE KEYS WHICH ARE NOT IN THE ENGLISH LANGUAGE FILE +PASTE THESE INTO THE ENGLISH LANGUAGE FILE +[ +Opens\ JabRef's\ Twitter\ page=Opens JabRef's Twitter page +] +Expected :[] +Actual :[Opens\ JabRef's\ Twitter\ page (src\main\java\org\jabref\gui\JabRefFrame.java LANG)] +``` + +Add the above snippet to the English translation file located at `src/main/resources/l10n/JabRef_en.properties`. +[Crowdin](https://crowdin.com/project/jabref) will automatically pick up the new string and add it to the other translations. + +You can also directly run the specific test in your IDE. +The test "`LocalizationConsistencyTest`" is placed under `src/test/java/org.jabref.logic.l10n/LocalizationConsistencyTest.java`. +Find more information in the [JabRef developer docs](https://devdocs.jabref.org/code-howtos/localization.html). + +#### **Format of keyboard shortcuts** + +In Markdown files (e.g., `CHANGELOG.md`), sometimes keyboard shortcuts need to be added. +Example: `Ctrl + Enter` + +In case you add keys to the changelog, please follow these rules: + +* `` tag for each key +* First letter of key capitalized +* Combined keys separated by `+` +* Spaces before and after separator `+` diff --git a/jabref/GitVersion.yml b/jabref/GitVersion.yml new file mode 100644 index 00000000..11c5cf1d --- /dev/null +++ b/jabref/GitVersion.yml @@ -0,0 +1,9 @@ +assembly-versioning-format: '{major}.{minor}.{WeightedPreReleaseNumber}' +assembly-informational-format: '{major}.{minor}{PreReleaseTagWithDash}--{CommitDate}--{ShortSha}' +mode: ContinuousDeployment + +branches: + main: + regex: ^main + tag: '' + pre-release-weight: 0 # 0 after stable release, 15000 before alpha release, 30000 before beta release, 50000 before stable release diff --git a/jabref/LICENSE b/jabref/LICENSE new file mode 100644 index 00000000..f47e5227 --- /dev/null +++ b/jabref/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright © 2003-2024 JabRef Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/jabref/MAINTAINERS b/jabref/MAINTAINERS new file mode 100644 index 00000000..84466c84 --- /dev/null +++ b/jabref/MAINTAINERS @@ -0,0 +1,7 @@ +Oliver Kopp (since 2011) +Tobias Diez (since 2015) +Christoph Schwentker (since 2016) +Carl Christian Snethlage (since 2020) +Jonatan Asketorp (since 2021) +Thilo Ertel (since 2021) +Houssem Nasri (since 2023) diff --git a/jabref/PRIVACY.md b/jabref/PRIVACY.md new file mode 100644 index 00000000..d21c0fbb --- /dev/null +++ b/jabref/PRIVACY.md @@ -0,0 +1,128 @@ +# Privacy Policy + +Last updated: 2024-08-06 + +Your privacy is a fundamental right JabRef e.V. respects and supports. +By using JabRef and its related online services, you choose to share some of your personal information. +In this Privacy Policy we explain how we collect, use, and share information about you, along with the choices you have. + +The term 'Personal information' in this policy means any information that either directly identifies you or can be somehow linked to you. 'JabRef' refers to the desktop application that is provided by JabRef e.V. + +Please remember that no method of transmission over the Internet, or method of electronic storage is absolute secure. +While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security. +Also, whenever you communicate through the internet, your IP-Address will always be transmitted and retained by third parties for technical and in some cases for legal reasons. + +## JabRef Desktop Application + +### Collecting information + +JabRef does not collect any personal information directly linked to you. +However, on certain occasions JabRef will send some information to the online services of JabRef e.V.: + +- On application start, JabRef will check for the latest version online (by default *enabled*). +- Information about a journal you are citing when looking for more information about this journal, using our journal database (by default *enabled*). +- A pdf document you automatically want to extract citation information from, using our GROBID service (by default *disabled*). +- Anonymized statistical data on the use of the graphical user interface for internal analysis purposes (by default *disabled*). + +### Storing information + +JabRef only stores the following personal information locally on your computer: + +- Your proxy username and password, if you decide to store them (by default *disabled*). +- Any personal API key you use to access third party online services (by default *disabled*). + +### Sharing information + +Certain operations you perform in JabRef may trigger requests to public third-party services such as Zotero, Crossref or the Library of Congress for metadata retrieval. +These third parties may log additional information besides your IP address and the search terms (e.g., DOI, ISBN or the current URL) according to their privacy policies. + +These third-party services are the following: + +| Service | Privacy Policy | +|------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| +| [ACM](https://www.acm.org/) | | +| [ACS Publications](https://pubs.acs.org/) | | +| [APS Advancing Physics](https://harvest.aps.org/) | | +| [arXiv.org](https://arxiv.org/) | | +| [Bibliotheksverbund Bayern](https://www.bib-bvb.de/) | | +| [Biodiversity Heritage Library](https://www.biodiversitylibrary.org/) | | +| [Collection of Computer Science Bibliographies](https://en.wikipedia.org/wiki/Collection_of_Computer_Science_Bibliographies) | **currently unavailable**, offline | +| [CrossRef](https://www.crossref.org/) | | +| [dblp](https://dblp.uni-trier.de/) | | +| [DJL (Deep Java Library)](https://djl.ai/) | | +| [Directory of Open Access Books](https://www.doabooks.org/) | | +| [Digitala Vetenskapliga Arkivet](https://www.diva-portal.org/) | | +| [DOI Foundation](https://www.doi.org/) | | +| [Elsevier](https://www.elsevier.com/) | | +| [Google Gemini](https://ai.google.dev/gemini-api) | | +| [Google Scholar](https://scholar.google.com/) | | +| [Gemeinsamer Verbundkatalog](https://www.gbv.de/) | | +| [Hugging Face](https://huggingface.co/) | | +| [IACR](https://www.iacr.org/) | | +| [IEEEXplore](https://ieeexplore.ieee.org/Xplore/home.jsp) | | +| [INSPIRE](https://inspirehep.net/) | | +| [ISIDORE](https://isidore.science/) | | +| [JSTOR](https://www.jstor.org/) | | +| [Library of Congress](https://lccn.loc.gov/) | | +| [Mistral AI](https://mistral.ai/) | | +| [National Library of Medicine](https://www.ncbi.nlm.nih.gov/) | | +| [MathSciNet](http://www.ams.org/mathscinet) | | +| [mEDRA](https://www.medra.org/) | | +| [Mr. DLib](https://mr-dlib.org/) [1] | | +| [OpenAI](https://openai.com/) | | +| [Openlibrary](https://openlibrary.org) | | +| [ResearchGate](https://www.researchgate.net/) | | +| [IETF Datatracker](https://datatracker.ietf.org/) | | +| [Semantic Scholar](https://www.semanticscholar.org/), powered by [Allen Institute for AI](https://allenai.org/) | | +| [Springer Nature](https://dev.springernature.com/) | | +| [The SAO/NASA Astrophysics Data System](https://ui.adsabs.harvard.edu/) | | +| [Unpaywall](https://unpaywall.org/) | | +| [zbMATH Open](https://www.zbmath.org) | | + +[1]: *Note: The Mr. DLib service is used for the related articles tab in the entry editor and collects also your language, your browser and operating system (*disabled* by default).* + +## JabRef Browser Extension + +No personal data (like name, email address, billing address or credit card) is collected by the Browser Extension itself. +But be aware that the browser Extension uses Zotero services, where [Zotero's Privacy Policy](https://www.zotero.org/support/privacy) applies. + +### Collecting information + +When actively used, the extension has access to the current website and its content to process citation information. In particular, the following information is used: + +- the url of the current website you are visiting, +- the content of the current website. + +### Storing information + +No data will be stored by the Browser Extension. + +### Sharing information + +The Browser Extension does not share any data except with the local instance of the JabRef software application, which stores the citation data as a new entry in its library. + +## Links to other Websites + +Our Service may contain links to other websites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit. + +We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services. + +## Changes to this Privacy Policy + +This privacy policy may be changed eventually. +We encourage you to check this Privacy Policy periodically for any changes. +Any material change will be mentioned in the changelog of the desktop application and in our [blog](https://blog.jabref.org/). + +This privacy policy is in effect as of the day mentioned as "last updated" above and will remain in effect except with respect to any changes in its provisions in the future, which will be in effect immediately after being posted on this page. + +## Contact + +If you get in touch with us, we may ask you to provide us with certain personal information (e.g. name and email address) to stay in contact with you. +For any questions or concerns regarding the privacy policy, please send us an email to or write to + +JabRef e.V. +Josef-Lanner-Str. 9 +71069 Sindelfingen +Germany + + diff --git a/jabref/README.md b/jabref/README.md new file mode 100644 index 00000000..3ca468f4 --- /dev/null +++ b/jabref/README.md @@ -0,0 +1,101 @@ +# JabRef Bibliography Management + +JabRef is an open-source, cross-platform citation and reference management tool. + +Stay on top of your literature: JabRef helps you to collect and organize sources, find the paper you need and discover the latest research. + +![main table](docs/images/jabref-mainscreen.png) + +## Features + +JabRef is available free of charge and is actively developed. +It supports you in every step of your research work. + +### Collect + +- Search across many online scientific catalogues like CiteSeer, CrossRef, Google Scholar, IEEEXplore, INSPIRE-HEP, Medline PubMed, MathSciNet, Springer, arXiv, and zbMATH +- Import options for over 15 reference formats +- Easily retrieve and link full-text articles +- Fetch complete bibliographic information based on ISBN, DOI, PubMed-ID and arXiv-ID +- Extract metadata from PDFs +- Import new references directly from the browser with one click using the [official browser extension](https://github.com/JabRef/JabRef-Browser-Extension) for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/jabref/?src=external-github), [Chrome](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh), [Edge](https://microsoftedge.microsoft.com/addons/detail/pgkajmkfgbehiomipedjhoddkejohfna), and [Vivaldi](https://chrome.google.com/webstore/detail/jabref-browser-extension/bifehkofibaamoeaopjglfkddgkijdlh) + +### Organize + +- Group your research into hierarchical collections and organize research items based on keywords/tags, search terms, or your manual assignments +- Advanced search and filter features +- Complete and fix bibliographic data by comparing with curated online catalogs such as Google Scholar, Springer, or MathSciNet +- Customizable citation key generator +- Customize and add new metadata fields or reference types +- Find and merge duplicates +- Attach related documents: 20 different kinds of documents supported out of the box, completely customizable and extendable +- Automatically rename and move associated documents according to customizable rules +- Keep track of what you read: ranking, priority, printed, quality-assured + +### Cite + +- Native BibTeX and Biblatex support +- Cite-as-you-write functionality for external applications such as Emacs, Kile, LyX, Texmaker, TeXstudio, Vim and WinEdt. +- Format references using one of thousands of built-in citation styles or create your own style +- Support for Word and LibreOffice/OpenOffice for inserting and formatting citations + +### Share + +- Many built-in export options or create your export format +- Library is saved as a simple text file, and thus it is easy to share with others via Dropbox and is version-control friendly +- Work in a team: sync the contents of your library via a SQL database + +## Installation + +Fresh development builds are available at [builds.jabref.org](https://builds.jabref.org/main/). +The [latest stable release is available at FossHub](https://downloads.jabref.org/). + +Please see our [Installation Guide](https://docs.jabref.org/installation). + +## Bug Reports, Suggestions, Other Feedback + +[![Donation](https://img.shields.io/badge/donate%20to-jabref-orange.svg)](https://donations.jabref.org) +[![PayPal Donate](https://img.shields.io/badge/donate-paypal-00457c.svg?logo=paypal&style=flat-square)](https://paypal.me/JabRef) + +We are thankful for any bug reports or other feedback. +If you have ideas for new features you want to be included in JabRef, tell us in [the feature section](http://discourse.jabref.org/c/features) of our forum! +If you need support in using JabRef, please read [the documentation](https://docs.jabref.org/) first, the [frequently asked questions (FAQ)](https://docs.jabref.org/faq) and also have a look at our [community forum](https://discourse.jabref.org/c/help/7). +You can use our [GitHub issue tracker](https://github.com/JabRef/jabref/issues) to file bug reports. + +An explanation of donation possibilities and usage of donations is available at our [donations page](https://donations.jabref.org). + +## Contributing + +[![dev-docs](https://img.shields.io/badge/dev-docs-blue)](https://devdocs.jabref.org/) +[![Help Contribute to Open Source](https://www.codetriage.com/jabref/jabref/badges/users.svg)](https://www.codetriage.com/jabref/jabref) +[![Join the chat at https://gitter.im/JabRef/jabref](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/JabRef/jabref?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![OpenHub](https://www.openhub.net/p/jabref/widgets/project_thin_badge.gif)](https://www.openhub.net/p/jabref) +[![Deployment Status](https://github.com/JabRef/jabref/workflows/Deployment/badge.svg)](https://github.com/JabRef/jabref/actions?query=workflow%3ADeployment) +[![Test Status](https://github.com/JabRef/jabref/workflows/Tests/badge.svg)](https://github.com/JabRef/jabref/actions?query=workflow%3ATests) +[![codecov.io](https://codecov.io/github/JabRef/jabref/coverage.svg?branch=master)](https://codecov.io/github/JabRef/jabref?branch=main) + +Want to be part of a free and open-source project that tens of thousands of scientists use every day? +Check out the ways you can contribute, below: + +- Not a programmer? Help translating JabRef at [Crowdin](https://crowdin.com/project/jabref) or learn how to help at [contribute.jabref.org](https://contribute.jabref.org) +- Quick overview on the architecture needed? Look at our [high-level documentation](https://devdocs.jabref.org/getting-into-the-code/high-level-documentation) +- For details on how to contribute, have a look at our [guidelines for contributing](CONTRIBUTING.md). +- You are welcome to contribute new features. To get your code included into JabRef, just [fork](https://help.github.com/en/articles/fork-a-repo) the JabRef repository, make your changes, and create a [pull request](https://help.github.com/en/articles/about-pull-requests). +- To work on existing JabRef issues, check out our [issue tracker](https://github.com/JabRef/jabref/issues). New to open source contributing? Look for issues with the ["good first issue"](https://github.com/JabRef/jabref/labels/good%20first%20issue) label to get started. + +Please follow our [step-by-step guide on how to set-up your workspace](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace). + +We use [GitHub Actions](https://github.com/JabRef/jabref/actions) for executing the tests after each commit. +For developing, it is sufficient to locally only run the associated test for the classes you changed. +GitHub will report any other failure. + +We view pull requests as a collaborative process. +Submit a pull request early to get feedback from the team on work in progress. +We will discuss improvements with you and agree to merge them once the [developers](https://github.com/JabRef/jabref/blob/main/MAINTAINERS) approve. +Please also remember to discuss bigger changes early with the core developers to ensure properly spend time and work. +Some fundamental design decisions can be found within our list of [Architectural Decision Records](https://devdocs.jabref.org/decisions/). + +## Sponsoring + +JabRef development is powered by YourKit Java Profiler +[![YourKit Java Profiler](https://www.yourkit.com/images/yk_logo.svg)](https://www.yourkit.com/java/profiler/) diff --git a/jabref/build.gradle b/jabref/build.gradle new file mode 100644 index 00000000..1a08fd86 --- /dev/null +++ b/jabref/build.gradle @@ -0,0 +1,955 @@ +import org.gradle.internal.os.OperatingSystem +import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform +import org.jabref.build.xjc.XjcPlugin +import org.jabref.build.xjc.XjcTask + +plugins { + id 'application' + + id 'com.github.andygoossens.modernizer' version '1.10.0' + + id 'me.champeau.jmh' version '0.7.2' + + // This is https://github.com/java9-modularity/gradle-modules-plugin/pull/282 + id 'com.github.koppor.gradle-modules-plugin' version 'v1.8.15-cmd-1' + + id 'org.openjfx.javafxplugin' version '0.1.0' + + id 'org.beryx.jlink' version '3.1.0-rc-1' + + // nicer test outputs during running and completion + // Homepage: https://github.com/radarsh/gradle-test-logger-plugin + id 'com.adarshr.test-logger' version '4.0.0' + + id 'jacoco' + + id 'checkstyle' + + id 'project-report' + + id 'idea' + + id 'org.openrewrite.rewrite' version '6.27.0' + + id "org.itsallcode.openfasttrace" version "3.0.1" +} + +// Enable following for debugging +// gradle.startParameter.showStacktrace = org.gradle.api.logging.configuration.ShowStacktrace. + +apply plugin: XjcPlugin + +apply from: 'eclipse.gradle' + +group = "org.jabref" +version = project.findProperty('projVersion') ?: '100.0.0' + +java { + sourceCompatibility = JavaVersion.VERSION_23 + targetCompatibility = JavaVersion.VERSION_23 + + // Workaround needed for Eclipse, probably because of https://github.com/gradle/gradle/issues/16922 + // Should be removed as soon as Gradle 7.0.1 is released ( https://github.com/gradle/gradle/issues/16922#issuecomment-828217060 ) + modularity.inferModulePath.set(false) + + toolchain { + // If this is updated, also update + // - .gitpod.Dockerfile + // - .devcontainer/devcontainer.json#L34 and + // - .github/workflows/deployment*.yml + // - .github/workflows/tests*.yml + // - .github/workflows/update-gradle-wrapper.yml + // - docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md + // - build.gradle -> jacoco -> toolVersion (because JaCoCo does not support newest JDK out of the box. Check versions at https://www.jacoco.org/jacoco/trunk/doc/changes.html) + languageVersion = JavaLanguageVersion.of(23) + // See https://docs.gradle.org/current/javadoc/org/gradle/jvm/toolchain/JvmVendorSpec.html for a full list + // vendor = JvmVendorSpec.AMAZON + } +} + +application { + mainClass.set('org.jabref.Launcher') + mainModule.set('org.jabref') + + applicationDefaultJvmArgs = [ + // On a change here, also adapt + // 1. "run > moduleOptions" + // 2. "deployment.yml" (macOS part) + // 3. "deployment-arm64.yml" + + // Note that the arguments are cleared for the "run" task to avoid messages like "WARNING: Unknown module: org.jabref.merged.module specified to --add-exports" + + // Fix for https://github.com/JabRef/jabref/issues/11188 + '--add-exports=javafx.base/com.sun.javafx.event=org.jabref.merged.module', + '--add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module', + + // Fix for https://github.com/JabRef/jabref/issues/11198 + '--add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module', + '--add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module', + '--add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module', + // fix for https://github.com/JabRef/jabref/issues/11426 + '--add-opens=javafx.controls/javafx.scene.control.skin=org.jabref.merged.module', + + // Fix for https://github.com/JabRef/jabref/issues/11225 on linux + '--add-opens=javafx.controls/javafx.scene.control=org.jabref', + '--add-exports=javafx.base/com.sun.javafx.event=org.jabref', + '--add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref', + '--add-opens=javafx.graphics/javafx.scene=org.jabref', + '--add-opens=javafx.controls/javafx.scene.control=org.jabref', + '--add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref', + + '--add-opens=javafx.base/javafx.collections=org.jabref', + '--add-opens=javafx.base/javafx.collections.transformation=org.jabref' + ] +} + +// Workaround for https://github.com/openjfx/javafx-gradle-plugin/issues/89 +// See also https://github.com/java9-modularity/gradle-modules-plugin/issues/165 +modularity.disableEffectiveArgumentsAdjustment() + +sourceSets { + main { + java { + // src-gen reasoning: https://stackoverflow.com/a/64612308/873282 + srcDirs = ["src/main/java", "src-gen/main/java"] + } + resources { + srcDirs = ["src/main/java", "src/main/resources"] + } + } + test { + java { + srcDirs = ["src/test/java"] + } + resources { + srcDirs = ["src/test/resources"] + } + } +} + +repositories { + mavenCentral() + maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + maven { url 'https://jitpack.io' } + maven { url 'https://oss.sonatype.org/content/groups/public' } + + // Required for one.jpro.jproutils:tree-showing + maven { url 'https://sandec.jfrog.io/artifactory/repo' } +} + +configurations { + antlr4 +} + +dependencyLocking { + lockAllConfigurations() +} + +javafx { + version = "23.0.1" + modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.web', 'javafx.swing' ] +} + +jacoco { + toolVersion = "0.8.13-SNAPSHOT" +} + +dependencies { + // Include all jar-files in the 'lib' folder as dependencies + implementation fileTree(dir: 'lib', includes: ['*.jar']) + + def pdfbox = "3.0.3" + implementation ("org.apache.pdfbox:pdfbox:$pdfbox") { + exclude group: 'commons-logging' + } + implementation ("org.apache.pdfbox:fontbox:$pdfbox") { + exclude group: 'commons-logging' + } + implementation ("org.apache.pdfbox:xmpbox:$pdfbox") { + exclude group: 'org.junit.jupiter' + exclude group: 'commons-logging' + } + + def luceneVersion = "10.0.0" + implementation "org.apache.lucene:lucene-core:$luceneVersion" + implementation "org.apache.lucene:lucene-queryparser:$luceneVersion" + implementation "org.apache.lucene:lucene-queries:$luceneVersion" + implementation "org.apache.lucene:lucene-analysis-common:$luceneVersion" + implementation "org.apache.lucene:lucene-highlighter:$luceneVersion" + + implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.12.0' + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.17.0' + implementation group: 'org.apache.commons', name: 'commons-text', version: '1.12.0' + implementation 'commons-logging:commons-logging:1.3.4' + implementation 'com.h2database:h2-mvstore:2.3.232' + + // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 + implementation 'org.bouncycastle:bcprov-jdk18on:1.79' + + implementation 'commons-cli:commons-cli:1.9.0' + + // region: LibreOffice + implementation 'org.libreoffice:unoloader:24.2.3' + implementation 'org.libreoffice:libreoffice:24.2.3' + // Required for ID generation + implementation 'io.github.thibaultmeyer:cuid:2.0.3' + // endregion + + implementation 'io.github.java-diff-utils:java-diff-utils:4.12' + implementation 'info.debatty:java-string-similarity:2.0.0' + implementation 'com.github.javakeyring:java-keyring:1.0.4' + + antlr4 'org.antlr:antlr4:4.13.2' + implementation 'org.antlr:antlr4-runtime:4.13.2' + + implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '7.0.0.202409031743-r' + + implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.18.1' + implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.18.1' + + implementation 'com.fasterxml:aalto-xml:1.3.3' + + implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.9' + + implementation 'org.postgresql:postgresql:42.7.4' + + // Support unix socket connection types + implementation 'com.kohlschutter.junixsocket:junixsocket-core:2.10.1' + implementation 'com.kohlschutter.junixsocket:junixsocket-mysql:2.10.1' + + implementation ('com.oracle.ojdbc:ojdbc10:19.3.0.0') { + // causing module issues + exclude module: 'oraclepki' + } + + implementation ('com.google.guava:guava:33.1.0-jre') { + // TODO: Remove this as soon as https://github.com/google/guava/issues/2960 is fixed + exclude module: "jsr305" + } + + implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1' + implementation 'jakarta.inject:jakarta.inject-api:2.0.1' + + implementation('org.jabref:afterburner.fx:2.0.0') { + exclude group: 'org.openjfx' + } + implementation 'org.kordamp.ikonli:ikonli-javafx:12.3.1' + implementation 'org.kordamp.ikonli:ikonli-materialdesign2-pack:12.3.1' + implementation 'com.github.sialcasa.mvvmFX:mvvmfx-validation:f195849ca9' //jitpack + implementation 'de.saxsys:mvvmfx:1.8.0' + implementation('org.jabref:easybind:2.2.1-SNAPSHOT') { + exclude group: 'org.openjfx' + } + implementation 'org.fxmisc.flowless:flowless:0.7.3' + implementation 'org.fxmisc.richtext:richtextfx:0.11.3' + implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '2.64.0') { + exclude module: 'javax.inject' // Split package, use only jakarta.inject + exclude module: 'commons-lang3' + exclude group: 'org.apache.commons.validator' + exclude group: 'org.apache.commons.commons-logging' + exclude module: 'kotlin-stdlib-jdk8' + exclude group: 'com.squareup.retrofit2' + exclude group: 'org.openjfx' + exclude group: 'org.apache.logging.log4j' + exclude group: 'tech.units' + } + // Required by gemsfx + implementation 'tech.units:indriya:2.2' + // Required by gemsfx and langchain4j + implementation ('com.squareup.retrofit2:retrofit:2.11.0') { + exclude group: 'com.squareup.okhttp3' + } + + implementation 'org.controlsfx:controlsfx:11.2.1' + + // region HTTP clients + implementation 'org.jsoup:jsoup:1.18.1' + implementation 'com.konghq:unirest-java-core:4.4.4' + implementation 'com.konghq:unirest-modules-gson:4.4.5' + implementation 'org.apache.httpcomponents.client5:httpclient5:5.4' + // endregion + + implementation 'org.slf4j:slf4j-api:2.0.16' + implementation 'org.tinylog:tinylog-api:2.7.0' + implementation 'org.tinylog:slf4j-tinylog:2.7.0' + implementation 'org.tinylog:tinylog-impl:2.7.0' + + // route all requests to java.util.logging to SLF4J (which in turn routes to tinylog) + implementation 'org.slf4j:jul-to-slf4j:2.0.16' + // route all requests to log4j to SLF4J + implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.24.1' + + implementation('de.undercouch:citeproc-java:3.1.0') { + exclude group: 'org.antlr' + } + + // jakarta.activation is already dependency of glassfish + implementation group: 'jakarta.xml.bind', name: 'jakarta.xml.bind-api', version: '4.0.2' + implementation group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '4.0.3' + + implementation ('com.github.tomtung:latex2unicode_2.13:0.3.2') { + exclude module: 'fastparse_2.13' + } + + implementation "de.rototor.snuggletex:snuggletex:1.3.0" + implementation ("de.rototor.snuggletex:snuggletex-jeuclid:1.3.0") { + exclude group: "org.apache.xmlgraphics" + } + + implementation 'com.vladsch.flexmark:flexmark:0.64.8' + implementation 'com.vladsch.flexmark:flexmark-html2md-converter:0.64.8' + + implementation group: 'net.harawata', name: 'appdirs', version: '1.2.2' + + implementation group: 'org.jooq', name: 'jool', version: '0.9.15' + // JAX-RS implemented by Jersey + // API + implementation 'jakarta.ws.rs:jakarta.ws.rs-api:4.0.0' + // Implementation of the API + implementation 'org.glassfish.jersey.core:jersey-server:3.1.9' + // injection framework + implementation 'org.glassfish.jersey.inject:jersey-hk2:3.1.9' + implementation 'org.glassfish.hk2:hk2-api:3.1.1' + // testImplementation 'org.glassfish.hk2:hk2-testing:3.0.4' + // implementation 'org.glassfish.hk2:hk2-testing-jersey:3.0.4' + // testImplementation 'org.glassfish.hk2:hk2-junitrunner:3.0.4' + // HTTP server + // implementation 'org.glassfish.jersey.containers:jersey-container-netty-http:3.1.1' + implementation 'org.glassfish.jersey.containers:jersey-container-grizzly2-http:3.1.9' + testImplementation 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:3.1.9' + // Allow objects "magically" to be mapped to JSON using GSON + // implementation 'org.glassfish.jersey.media:jersey-media-json-gson:3.1.1' + + // Because of GraalVM quirks, we need to ship that. See https://github.com/jspecify/jspecify/issues/389#issuecomment-1661130973 for details + implementation 'org.jspecify:jspecify:1.0.0' + + // parse plist files + implementation 'com.googlecode.plist:dd-plist:1.28' + + // Parse lnk files + implementation 'com.github.vatbub:mslinks:1.0.6.2' + + // YAML formatting + implementation 'org.yaml:snakeyaml:2.3' + + // region AI + implementation 'dev.langchain4j:langchain4j:0.35.0' + // Even though we use jvm-openai for LLM connection, we still need this package for tokenization. + implementation('dev.langchain4j:langchain4j-open-ai:0.35.0') { + exclude group: 'com.squareup.okhttp3' + exclude group: 'com.squareup.retrofit2', module: 'retrofit' + exclude group: 'org.jetbrains.kotlin' + } + implementation('dev.langchain4j:langchain4j-mistral-ai:0.35.0') { + exclude group: 'com.squareup.okhttp3' + exclude group: 'com.squareup.retrofit2', module: 'retrofit' + exclude group: 'org.jetbrains.kotlin' + } + implementation('dev.langchain4j:langchain4j-google-ai-gemini:0.35.0') { + exclude group: 'com.squareup.okhttp3' + exclude group: 'com.squareup.retrofit2', module: 'retrofit' + } + implementation('dev.langchain4j:langchain4j-hugging-face:0.35.0') { + exclude group: 'com.squareup.okhttp3' + exclude group: 'com.squareup.retrofit2', module: 'retrofit' + exclude group: 'org.jetbrains.kotlin' + } + + implementation 'org.apache.velocity:velocity-engine-core:2.4.1' + implementation platform('ai.djl:bom:0.30.0') + implementation 'ai.djl:api' + implementation 'ai.djl.huggingface:tokenizers' + implementation 'ai.djl.pytorch:pytorch-model-zoo' + implementation 'io.github.stefanbratanov:jvm-openai:0.11.0' + // openai depends on okhttp, which needs kotlin - see https://github.com/square/okhttp/issues/5299 for details + implementation ('com.squareup.okhttp3:okhttp:4.12.0') { + exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' + } + // GemxFX also (transitively) depends on kotlin + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21' + // endregion + + implementation 'commons-io:commons-io:2.17.0' + + // Even if "compileOnly" is used, IntelliJ always adds to module-info.java. To avoid issues during committing, we use "implementation" instead of "compileOnly" + implementation 'io.github.adr:e-adr:2.0.0-SNAPSHOT' + + implementation 'io.zonky.test:embedded-postgres:2.0.7' + implementation enforcedPlatform('io.zonky.test.postgres:embedded-postgres-binaries-bom:17.0.0') + + testImplementation 'io.github.classgraph:classgraph:4.8.177' + testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0' + testImplementation 'org.junit.platform:junit-platform-launcher:1.10.3' + + testImplementation 'org.mockito:mockito-core:5.14.2' + testImplementation 'org.xmlunit:xmlunit-core:2.10.0' + testImplementation 'org.xmlunit:xmlunit-matchers:2.10.0' + testRuntimeOnly 'com.tngtech.archunit:archunit-junit5-engine:1.3.0' + testImplementation 'com.tngtech.archunit:archunit-junit5-api:1.3.0' + testImplementation "org.testfx:testfx-core:4.0.16-alpha" + testImplementation "org.testfx:testfx-junit5:4.0.16-alpha" + testImplementation "org.hamcrest:hamcrest-library:3.0" + testImplementation "com.github.javaparser:javaparser-symbol-solver-core:3.26.2" + + // recommended by https://github.com/wiremock/wiremock/issues/2149#issuecomment-1835775954 + testImplementation 'org.wiremock:wiremock-standalone:3.3.1' + + checkstyle 'com.puppycrawl.tools:checkstyle:10.20.1' + // xjc needs the runtime as well for the ant task, otherwise it fails + xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '3.0.2' + xjc group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '3.0.2' + + rewrite(platform("org.openrewrite.recipe:rewrite-recipe-bom:2.19.0")) + rewrite("org.openrewrite.recipe:rewrite-static-analysis") + rewrite("org.openrewrite.recipe:rewrite-logging-frameworks") + rewrite("org.openrewrite.recipe:rewrite-testing-frameworks") + rewrite("org.openrewrite.recipe:rewrite-migrate-java") + + configurations.checkstyle { + resolutionStrategy.capabilitiesResolution.withCapability("com.google.collections:google-collections") { + select("com.google.guava:guava:0") + } + } + + configurations + .matching(c -> c.name.contains("downloadSources")) + .configureEach { + attributes { + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(OperatingSystemFamily, DefaultNativePlatform.getCurrentOperatingSystem().getName())) + attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(MachineArchitecture, DefaultNativePlatform.getCurrentArchitecture().getName())) + } + } +} + +clean { + delete "src/main/generated" + delete "src-gen" +} + +processResources { + filteringCharset = 'UTF-8' + + filesMatching("build.properties") { + expand(version: project.findProperty('projVersionInfo') ?: '100.0.0', + "year": String.valueOf(Calendar.getInstance().get(Calendar.YEAR)), + "maintainers": new File('MAINTAINERS').readLines().findAll { !it.startsWith("#") }.join(", "), + "azureInstrumentationKey": System.getenv('AzureInstrumentationKey') ? System.getenv('AzureInstrumentationKey') : '', + "springerNatureAPIKey": System.getenv('SpringerNatureAPIKey') ? System.getenv('SpringerNatureAPIKey') : '', + "astrophysicsDataSystemAPIKey": System.getenv('AstrophysicsDataSystemAPIKey') ? System.getenv('AstrophysicsDataSystemAPIKey') : '', + "ieeeAPIKey": System.getenv('IEEEAPIKey') ? System.getenv('IEEEAPIKey') : '', + "scienceDirectApiKey": System.getenv('SCIENCEDIRECTAPIKEY') ? System.getenv('SCIENCEDIRECTAPIKEY') : '', + "biodiversityHeritageApiKey": System.getenv('BiodiversityHeritageApiKey') ? System.getenv('BiodiversityHeritageApiKey') : '', + "semanticScholarApiKey": System.getenv('SemanticScholarApiKey') ? System.getenv("SemanticScholarApiKey") : '' + ) + filteringCharset = 'UTF-8' + } + + filesMatching(["resources/resource/ods/meta.xml", "resources/resource/openoffice/meta.xml"]) { + expand version: project.version + } +} + +tasks.register('generateSource') { + dependsOn("generateBstGrammarSource", + "generateSearchGrammarSource", + "generateCitaviSource") + group = 'JabRef' + description 'Generates all necessary (Java) source files.' +} + +tasks.register("generateBstGrammarSource", JavaExec) { + group = "JabRef" + description = 'Generates BstLexer.java and BstParser.java from the Bst.g grammar file using antlr4.' + classpath = configurations.antlr4 + mainClass = "org.antlr.v4.Tool" + javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) + + inputs.dir('src/main/antlr4/org/jabref/bst/') + outputs.dir("src-gen/main/java/org/jabref/logic/bst/") + args = ["-o", "src-gen/main/java/org/jabref/logic/bst/", "-visitor", "-no-listener", "-package", "org.jabref.logic.bst", "$projectDir/src/main/antlr4/org/jabref/bst/Bst.g4"] +} + +tasks.register("generateSearchGrammarSource", JavaExec) { + group = 'JabRef' + description = "Generates java files for Search.g antlr4." + classpath = configurations.antlr4 + mainClass = "org.antlr.v4.Tool" + javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) + + inputs.dir("src/main/antlr4/org/jabref/search/") + outputs.dir("src-gen/main/java/org/jabref/search/") + args = ["-o","src-gen/main/java/org/jabref/search" , "-visitor", "-no-listener", "-package", "org.jabref.search", "$projectDir/src/main/antlr4/org/jabref/search/Search.g4"] +} + +tasks.register("generateJournalListMV", JavaExec) { + group = "JabRef" + description = "Converts the comma-separated journal abbreviation file to a H2 MVStore" + classpath = sourceSets.main.runtimeClasspath + mainClass = "org.jabref.cli.JournalListMvGenerator" + javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) + onlyIf { + !file("build/resources/main/journals/journal-list.mv").exists() + } +} + +jar.dependsOn("generateJournalListMV") +compileTestJava.dependsOn("generateJournalListMV") + +tasks.register('generateCitaviSource', XjcTask) { + group = 'JabRef' + description = "Generates java files for the citavi importer." + + schemaFile = "src/main/resources/xjc/citavi/citavi.xsd" + outputDirectory = "src-gen/main/java/" + javaPackage = "org.jabref.logic.importer.fileformat.citavi" +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + + // hint by https://docs.gradle.org/current/userguide/performance.html#run_the_compiler_as_a_separate_process + options.fork = true +} + +compileJava { + options.compilerArgs << "-Xlint:none" + dependsOn "generateSource" + + options.generatedSourceOutputDirectory.set(file("src-gen/main/java")) + + moduleOptions { + addExports = [ + // TODO: Remove access to internal api + 'javafx.controls/com.sun.javafx.scene.control' : 'org.jabref', + 'org.controlsfx.controls/impl.org.controlsfx.skin' : 'org.jabref' + ] + } +} + +// Configures "application > run" task +run { + doFirst { + // Clear the default JVM arguments, to avoid messages like "WARNING: Unknown module: org.jabref.merged.module specified to --add-exports" + application.applicationDefaultJvmArgs = [] + } + + moduleOptions { + // On a change here, also adapt "application > applicationDefaultJvmArgs" + addExports = [ + // TODO: Remove access to internal api + 'javafx.base/com.sun.javafx.event' : 'org.jabref.merged.module', + 'javafx.controls/com.sun.javafx.scene.control' : 'org.jabref', + + // We need to restate the ControlsFX exports, because we get following error otherwise: + // java.lang.IllegalAccessError: + // class org.controlsfx.control.textfield.AutoCompletionBinding (in module org.controlsfx.controls) + // cannot access class com.sun.javafx.event.EventHandlerManager (in module javafx.base) because + // module javafx.base does not export com.sun.javafx.event to module org.controlsfx.controls + // Taken from here: https://github.com/controlsfx/controlsfx/blob/9.0.0/build.gradle#L1 + 'javafx.graphics/com.sun.javafx.scene' : 'org.controlsfx.controls', + 'javafx.graphics/com.sun.javafx.scene.traversal' : 'org.controlsfx.controls', + 'javafx.graphics/com.sun.javafx.css' : 'org.controlsfx.controls', + 'javafx.controls/com.sun.javafx.scene.control' : 'org.controlsfx.controls', + 'javafx.controls/com.sun.javafx.scene.control.behavior' : 'org.controlsfx.controls', + 'javafx.controls/com.sun.javafx.scene.control.inputmap' : 'org.controlsfx.controls', + 'javafx.base/com.sun.javafx.event' : 'org.controlsfx.controls', + 'javafx.base/com.sun.javafx.collections' : 'org.controlsfx.controls', + 'javafx.base/com.sun.javafx.runtime': 'org.controlsfx.controls', + 'javafx.web/com.sun.webkit' : 'org.controlsfx.controls', + ] + + addOpens = [ + 'javafx.controls/javafx.scene.control' : 'org.jabref', + 'javafx.controls/com.sun.javafx.scene.control' : 'org.jabref', + 'org.controlsfx.controls/impl.org.controlsfx.skin' : 'org.jabref', + 'org.controlsfx.controls/org.controlsfx.control.textfield' : 'org.jabref', + + 'javafx.controls/javafx.scene.control.skin' : 'org.controlsfx.controls', + 'javafx.graphics/javafx.scene' : 'org.controlsfx.controls', + + 'javafx.base/javafx.collections' : 'org.jabref', + 'javafx.base/javafx.collections.transformation' : 'org.jabref' + ] + + addModules = [ + 'jdk.incubator.vector' + ] + + createCommandLineArgumentFile = true + } + + if (project.hasProperty('component')){ + if (component == 'httpserver'){ + main = 'org.jabref.http.server.Server' + } + } +} + +javadoc { + options { + encoding = 'UTF-8' + version = false + author = false + addMultilineStringsOption("-add-exports").setValue([ + 'javafx.controls/com.sun.javafx.scene.control=org.jabref' + ]) + } +} + +test { + useJUnitPlatform { + excludeTags 'DatabaseTest', 'FetcherTest', 'GUITest' + } + + moduleOptions { + // TODO: Remove this as soon as ArchUnit is modularized + runOnClasspath = true + } +} + +testlogger { + // See https://github.com/radarsh/gradle-test-logger-plugin#configuration for configuration options + + theme 'standard' + + showPassed false + showSkipped false + + showCauses false + showStackTraces false +} + +tasks.withType(Test).configureEach { + reports.html.outputLocation.set(file("${reporting.baseDir}/${name}")) + // Enable parallel tests (on desktop). + // See https://docs.gradle.org/8.1/userguide/performance.html#execute_tests_in_parallel for details. + if (!providers.environmentVariable("CI").isPresent()) { + maxParallelForks = Math.max(Runtime.runtime.availableProcessors() - 1, 1) + } +} + +tasks.register('databaseTest', Test) { + useJUnitPlatform { + includeTags 'DatabaseTest' + } + + testLogging { + // set options for log level LIFECYCLE + events = ["FAILED"] + exceptionFormat "full" + } + + maxParallelForks = 1 +} + +tasks.register('fetcherTest', Test) { + useJUnitPlatform { + includeTags 'FetcherTest' + } + + maxParallelForks = 1 +} + +tasks.register('guiTest', Test) { + useJUnitPlatform { + includeTags 'GUITest' + } + + testLogging { + // set options for log level LIFECYCLE + events = ["FAILED"] + exceptionFormat "full" + } + + maxParallelForks = 1 +} + +// Test result tasks +tasks.register('copyTestResources', Copy) { + from "${projectDir}/src/test/resources" + into "${buildDir}/classes/test" +} +processTestResources.dependsOn copyTestResources + +tasks.register('jacocoPrepare') { + doFirst { + // Ignore failures of tests + tasks.withType(Test).tap { + configureEach { + ignoreFailures = true + } + } + } +} +test.mustRunAfter jacocoPrepare +databaseTest.mustRunAfter jacocoPrepare +fetcherTest.mustRunAfter jacocoPrepare + +jacocoTestReport { + dependsOn jacocoPrepare, test, fetcherTest, databaseTest + + executionData files( + layout.buildDirectory.file('jacoco/test.exec').get().asFile, + layout.buildDirectory.file('jacoco/fetcherTest.exec').get().asFile, + layout.buildDirectory.file('jacoco/databaseTest.exec').get().asFile) + + reports { + csv.required = true + html.required = true + // coveralls plugin depends on xml format report + xml.required = true + } +} + +// Code quality tasks +checkstyle { + // will only run when called explicitly from the command line + sourceSets = [] +} + +rewrite { + activeRecipe( + 'org.jabref.config.rewrite.cleanup' + ) + exclusion ( + "build.gradle", + "buildSrc/build.gradle", + "eclipse.gradle", + "settings.gradle", + "src-gen/**", + "src/main/resources/**", + "src/test/resources/**", + "**/module-info.java", + "**/*.py", + "**/*.xml", + "**/*.yml" + ) + plainTextMask("**/*.md") + failOnDryRunResults = true +} + +modernizer { + failOnViolations = false + includeTestClasses = true + exclusions = [ + 'java/util/Optional.get:()Ljava/lang/Object;' + ] +} + +// Release tasks +tasks.register('deleteInstallerTemp', Delete) { + delete "$buildDir/installer" +} + +jpackage.dependsOn deleteInstallerTemp +jlinkZip.dependsOn jpackage +jlink { + // https://github.com/beryx/badass-jlink-plugin/issues/61#issuecomment-504640018 + addExtraDependencies("javafx") + + addOptions('--strip-debug', '--compress', 'zip-6', '--no-header-files', '--no-man-pages') + launcher { + name = 'JabRef' + } + + addOptions("--bind-services") + + // TODO: Remove the following as soon as the dependencies are fixed (upstream) + // forceMerge is usually needed when some non-modular artifacts in the dependency graph use code that was previously part of the JDK + // but it was removed in the newer releases. + // The pom.xml associated with such a non-modular artifact does not mention that the artifact depends on the removed code + // (because the artifact was published when this code was still available in the JDK). + forceMerge "controlsfx", "bcprov", "jaxb", "istack", "stax" + + // TODO: Remove the following correction to the merged module + // The module descriptor automatically generated by the plugin for the merged module contained some invalid entries. + // This is based on ./gradlew suggestMergedModuleInfo, sort, strip ";"", remove non-used modules, and include the suggested directives here. + mergedModule { + requires 'com.google.gson' + requires 'com.fasterxml.jackson.annotation' + requires 'com.fasterxml.jackson.databind' + requires 'com.fasterxml.jackson.core' + requires 'com.fasterxml.jackson.datatype.jdk8' + requires 'jakarta.xml.bind' + requires 'java.compiler' + requires 'java.datatransfer' + requires 'java.desktop' + requires 'java.logging' + requires 'java.management' + requires 'java.naming' + requires 'java.net.http' + requires 'java.rmi' + requires 'java.scripting' + requires 'java.security.jgss' + requires 'java.security.sasl' + requires 'java.sql' + requires 'java.sql.rowset' + requires 'java.transaction.xa' + requires 'java.xml' + requires 'javafx.base' + requires 'javafx.controls' + requires 'javafx.fxml' + requires 'javafx.graphics' + requires 'javafx.media' + requires 'javafx.swing' + requires 'jdk.jsobject' + requires 'jdk.security.jgss' + requires 'jdk.unsupported' + requires 'jdk.unsupported.desktop' + requires 'jdk.xml.dom' + requires 'org.apache.commons.lang3' + requires 'org.apache.commons.logging' + requires 'org.apache.commons.text' + requires 'org.apache.commons.codec' + requires 'org.apache.commons.io' + requires 'org.apache.commons.compress' + requires 'org.freedesktop.dbus' + requires 'org.jsoup' + requires 'org.slf4j' + requires 'org.tukaani.xz'; + uses 'ai.djl.engine.EngineProvider' + uses 'ai.djl.repository.RepositoryFactory' + uses 'ai.djl.repository.zoo.ZooProvider' + uses 'dev.langchain4j.spi.prompt.PromptTemplateFactory' + uses 'kong.unirest.core.json.JsonEngine' + uses 'org.eclipse.jgit.lib.Signer' + uses 'org.eclipse.jgit.transport.SshSessionFactory' + uses 'org.mariadb.jdbc.LocalInfileInterceptor' + uses 'org.mariadb.jdbc.authentication.AuthenticationPlugin' + uses 'org.mariadb.jdbc.credential.CredentialPlugin' + uses 'org.mariadb.jdbc.tls.TlsSocketPlugin' + uses 'org.postgresql.shaded.com.ongres.stringprep.Profile' + + provides 'org.mariadb.jdbc.tls.TlsSocketPlugin' with 'org.mariadb.jdbc.internal.protocol.tls.DefaultTlsSocketPlugin' + provides 'java.sql.Driver' with 'org.postgresql.Driver' + provides 'org.mariadb.jdbc.authentication.AuthenticationPlugin' with 'org.mariadb.jdbc.internal.com.send.authentication.CachingSha2PasswordPlugin', + 'org.mariadb.jdbc.internal.com.send.authentication.ClearPasswordPlugin', + 'org.mariadb.jdbc.internal.com.send.authentication.Ed25519PasswordPlugin', + 'org.mariadb.jdbc.internal.com.send.authentication.NativePasswordPlugin', + 'org.mariadb.jdbc.internal.com.send.authentication.OldPasswordPlugin', + 'org.mariadb.jdbc.internal.com.send.authentication.SendGssApiAuthPacket', + 'org.mariadb.jdbc.internal.com.send.authentication.SendPamAuthPacket', + 'org.mariadb.jdbc.internal.com.send.authentication.Sha256PasswordPlugin' + provides 'org.mariadb.jdbc.credential.CredentialPlugin' with 'org.mariadb.jdbc.credential.aws.AwsIamCredentialPlugin', + 'org.mariadb.jdbc.credential.env.EnvCredentialPlugin', + 'org.mariadb.jdbc.credential.system.PropertiesCredentialPlugin' + provides 'java.security.Provider' with 'org.bouncycastle.jce.provider.BouncyCastleProvider', + 'org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider' + provides 'kong.unirest.core.json.JsonEngine' with 'kong.unirest.modules.gson.GsonEngine'; + provides 'ai.djl.repository.zoo.ZooProvider' with 'ai.djl.engine.rust.zoo.RsZooProvider', + 'ai.djl.huggingface.zoo.HfZooProvider', + 'ai.djl.pytorch.zoo.PtZooProvider', + 'ai.djl.repository.zoo.DefaultZooProvider'; + provides 'ai.djl.engine.EngineProvider' with 'ai.djl.engine.rust.RsEngineProvider', + 'ai.djl.pytorch.engine.PtEngineProvider'; + } + + jpackage { + outputDir = "distribution" + + if (OperatingSystem.current().isWindows()) { + // This requires WiX to be installed: https://github.com/wixtoolset/wix3/releases + installerType = "msi" + imageOptions = [ + '--icon', "${projectDir}/src/main/resources/icons/jabref.ico", + ] + installerOptions = [ + '--vendor', 'JabRef', + '--app-version', "${project.version}", + '--verbose', + '--win-upgrade-uuid', 'd636b4ee-6f10-451e-bf57-c89656780e36', + '--win-dir-chooser', + '--win-shortcut', + '--win-menu', + '--win-menu-group', "JabRef", + '--temp', "$buildDir/installer", + '--resource-dir', "${projectDir}/buildres/windows", + '--license-file', "${projectDir}/buildres/LICENSE_with_Privacy.md", + '--file-associations', "${projectDir}/buildres/windows/bibtexAssociations.properties" + ] + } + + if (OperatingSystem.current().isLinux()) { + imageOptions = [ + '--icon', "${projectDir}/src/main/resources/icons/JabRef-icon-64.png", + '--app-version', "${project.version}", + ] + installerOptions = [ + '--verbose', + '--vendor', 'JabRef', + '--app-version', "${project.version}", + // '--temp', "$buildDir/installer", + '--resource-dir', "${projectDir}/buildres/linux", + '--linux-menu-group', 'Office;', + '--linux-rpm-license-type', 'MIT', + // '--license-file', "${projectDir}/LICENSE.md", + '--description', 'JabRef is an open source bibliography reference manager. The native file format used by JabRef is BibTeX, the standard LaTeX bibliography format.', + '--linux-shortcut', + '--file-associations', "${projectDir}/buildres/linux/bibtexAssociations.properties" + ] + } + + if (OperatingSystem.current().isMacOsX()) { + imageOptions = [ + '--icon', "${projectDir}/src/main/resources/icons/jabref.icns", + '--resource-dir', "${projectDir}/buildres/mac" + ] + // Notarized mac images and packages are built on the pipeline only + skipInstaller = true + installerOptions = [ + '--verbose', + '--vendor', 'JabRef', + '--mac-package-identifier', "JabRef", + '--mac-package-name', "JabRef", + '--app-version', "${project.version}", + '--file-associations', "${projectDir}/buildres/mac/bibtexAssociations.properties", + '--resource-dir', "${projectDir}/buildres/mac" + ] + } + } +} + +if (OperatingSystem.current().isWindows()) { + tasks.jpackageImage.doLast { + copy { + from("${projectDir}/buildres/windows") { + include "jabref-firefox.json", "jabref-chrome.json", "JabRefHost.bat", "JabRefHost.ps1" + } + into "$buildDir/distribution/JabRef" + } + } +} + +if (OperatingSystem.current().isLinux()) { + tasks.jpackageImage.doLast { + copy { + from("${projectDir}/buildres/linux") { + include "native-messaging-host/**", "jabrefHost.py" + } + into "$buildDir/distribution/JabRef/lib" + } + } +} + +if (OperatingSystem.current().isMacOsX()) { + tasks.jpackageImage.doLast { + copy { + from("${projectDir}/buildres/mac") { + include "native-messaging-host/**", "jabrefHost.py" + } + into "$buildDir/distribution/JabRef.app/Contents/Resources" + } + } +} + +jmh { + warmupIterations = 5 + iterations = 10 + fork = 2 +} + +requirementTracing { + inputDirectories = files('docs', 'src/main/java', 'src/test/java') +} diff --git a/jabref/buildSrc/src/main/groovy/org/jabref/build/xjc/XjcPlugin.groovy b/jabref/buildSrc/src/main/groovy/org/jabref/build/xjc/XjcPlugin.groovy new file mode 100644 index 00000000..72e2c364 --- /dev/null +++ b/jabref/buildSrc/src/main/groovy/org/jabref/build/xjc/XjcPlugin.groovy @@ -0,0 +1,20 @@ +package org.jabref.build.xjc + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class XjcPlugin implements Plugin { + + static final def CONFIGURATION_NAME = "xjc" + + @Override + void apply(Project target) { + def configuration = target.configurations.create(CONFIGURATION_NAME) + configuration.description = "Dependencies needed to run the XJC tool." + + target.afterEvaluate { evaluated -> + evaluated.logger.info(evaluated.configurations.xjc.asPath) + evaluated.ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask', classpath: evaluated.configurations.getByName(CONFIGURATION_NAME).asPath) + } + } +} diff --git a/jabref/buildSrc/src/main/groovy/org/jabref/build/xjc/XjcTask.groovy b/jabref/buildSrc/src/main/groovy/org/jabref/build/xjc/XjcTask.groovy new file mode 100644 index 00000000..fb7bbf7c --- /dev/null +++ b/jabref/buildSrc/src/main/groovy/org/jabref/build/xjc/XjcTask.groovy @@ -0,0 +1,114 @@ +package org.jabref.build.xjc + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction + +class XjcTask extends DefaultTask { + + private def schemaFile + @Optional + private def bindingFile + private def outputDirectory + private String javaPackage + private String encoding + @Optional + private String arguments + + @TaskAction + def generateClasses() { + project.mkdir(outputDirectory) + project.ant.xjc(destdir: outputDirectory, package: javaPackage, encoding: getEncoding()) { + schema(dir: schemaFile.getParent(), includes: schemaFile.getName()) + if (bindingFile != null) { + binding(dir: bindingFile.getParent(), includes: bindingFile.getName()) + } + if (arguments != null) { + arg(value: arguments) + } + } + } + + @InputFile + File getSchemaFile() { + if (schemaFile == null) { + return null + } + return project.file(schemaFile) + } + + void setSchemaFile(Object schemaFile) { + this.schemaFile = schemaFile + } + + @OutputDirectory + File getOutputDirectory() { + if (outputDirectory == null) { + return null + } + return project.file(outputDirectory) + } + + void setOutputDirectory(Object outputDirectory) { + this.outputDirectory = outputDirectory + updateOutput() + } + + @Input + String getJavaPackage() { + return javaPackage + } + + void setJavaPackage(String javaPackage) { + this.javaPackage = javaPackage + updateOutput() + } + + @Input + String getEncoding() { + if(encoding == null ) { + // use UTF-8 as default encoding + return "UTF-8" + } else { + return encoding + } + } + + void setEncoding(String encoding) { + this.encoding = encoding + } + + @Input + String getArguments() { + return arguments + } + + void setArguments(String args) { + this.arguments = args + } + + @InputFile + File getBindingFile() { + if (bindingFile == null) { + return null + } + return project.file(bindingFile) + } + + void setBindingFile(Object binding) { + this.bindingFile = binding + } + + private void updateOutput() { + if (outputDirectory != null && javaPackage != null) { + outputs.dir(new File(getOutputDirectory(), packageAsPath(javaPackage))) + } + } + + private static String packageAsPath(String pkg) { + return pkg.replace((char) '.', File.separatorChar) + } +} diff --git a/jabref/buildres/JabRef.VisualElementsManifest.xml b/jabref/buildres/JabRef.VisualElementsManifest.xml new file mode 100644 index 00000000..6d28051b --- /dev/null +++ b/jabref/buildres/JabRef.VisualElementsManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/jabref/buildres/LICENSE_with_Privacy.md b/jabref/buildres/LICENSE_with_Privacy.md new file mode 100644 index 00000000..4a498007 --- /dev/null +++ b/jabref/buildres/LICENSE_with_Privacy.md @@ -0,0 +1,179 @@ +MIT License + +Copyright © 2003-2024 JabRef Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- Privacy Policy ---- + +# Privacy Policy + +Last updated: 2023-08-24 + +Your privacy is a fundamental right JabRef e.V. respects and supports. +By using JabRef and its related online services, you choose to share +some of your personal information. In this Privacy Policy we explain how +we collect, use, and share information about you, along with the choices +you have. + +The term \'Personal information\' in this policy means any information +that either directly identifies you or can be somehow linked to you. +\'JabRef\' refers to the desktop application that is provided by JabRef +e.V. + +Please remember that no method of transmission over the Internet, or +method of electronic storage is absolute secure. While we strive to use +commercially acceptable means to protect your Personal Data, we cannot +guarantee its absolute security. Also, whenever you communicate through +the internet, your IP-Address will always be transmitted and retained by +third parties for technical and in some cases for legal reasons. + +## JabRef Desktop Application + +### Collecting information + +JabRef does not collect any personal information directly linked to you. +However, on certain occasions JabRef will send some information to the +online services of JabRef e.V.: + +- On application start, JabRef will check for the latest version + online (by default *enabled*). +- Information about a journal you are citing when looking for more + information about this journal, using our journal database (by + default *enabled*). +- A pdf document you automatically want to extract citation + information from, using our GROBID service (by default *disabled*). +- Anonymized statistical data on the use of the graphical user + interface for internal analysis purposes (by default *disabled*). + +### Storing information + +JabRef only stores the following personal information locally on your +computer: + +- Your proxy username and password, if you decide to store them (by + default *disabled*). +- Any personal API key you use to access third party online services + (by default *disabled*). + +### Sharing information + +Certain operations you perform in JabRef may trigger requests to public +third-party services such as Zotero, Crossref or the Library of Congress +for metadata retrieval. These third parties may log additional +information besides your IP address and the search terms (e.g., DOI, +ISBN or the current URL) according to their privacy policies. + +These third-party services are the following: + +Service;Privacy Policy +[ACM](https://www.acm.org/); +[ACS Publications](https://pubs.acs.org/); +[APS Advancing Physics](https://harvest.aps.org/); +[arXiv.org](https://arxiv.org/); +[Bibliotheksverbund Bayern](https://www.bib-bvb.de/); +[Biodiversity Heritage Library](https://www.biodiversitylibrary.org/); +[Collection of Computer Science Bibliographies](https://en.wikipedia.org/wiki/Collection_of_Computer_Science_Bibliographies);**currently unavailable**, offline +[CrossRef](https://www.crossref.org/); +[dblp](https://dblp.uni-trier.de/); +[Directory of Open Access Books](https://www.doabooks.org/); +[Digitala Vetenskapliga Arkivet](https://www.diva-portal.org/); +[DOI Foundation](https://www.doi.org/); +[Elsevier](https://www.elsevier.com/); +[Google Scholar](https://scholar.google.com/); +[Gemeinsamer Verbundkatalog](https://www.gbv.de/); +[IACR](https://www.iacr.org/); +[IEEEXplore](https://ieeexplore.ieee.org/Xplore/home.jsp); +[INSPIRE](https://inspirehep.net/); +[JSTOR](https://www.jstor.org/); +[Library of Congress](https://lccn.loc.gov/); +[National Library of Medicine](https://www.ncbi.nlm.nih.gov/); +[MathSciNet](http://www.ams.org/mathscinet); +[mEDRA](https://www.medra.org/); +[Mr. DLib](https://mr-dlib.org/) [1]; +[Openlibrary](https://openlibrary.org); +[ResearchGate](https://www.researchgate.net/); +[IETF Datatracker](https://datatracker.ietf.org/); +[Semantic Scholar](https://www.semanticscholar.org/), powered by [Allen Institute for AI](https://allenai.org/); +[Springer Nature](https://dev.springernature.com/); +[The SAO/NASA Astrophysics Data System](https://ui.adsabs.harvard.edu/); +[Unpaywall](https://unpaywall.org/); +[zbMATH Open](https://www.zbmath.org); + +[1]: *Note: The Mr. DLib service is used for the related articles tab in the entry editor and collects also your language, your browser and operating system (by default*disabled*).*; + +## JabRef Browser Extension + +No personal data (like name, email address, billing address or credit +card) is collected by the Browser Extension itself. But be aware that +the browser Extension uses Zotero services, where [Zotero\'s Privacy +Policy](https://www.zotero.org/support/privacy) applies. + +### Collecting information + +When actively used, the extension has access to the current website and +its content to process citation information. In particular, the +following information is used: + +- the url of the current website you are visiting, +- the content of the current website. + +### Storing information + +No data will be stored by the Browser Extension. + +### Sharing information + +The Browser Extension does not share any data except with the local +instance of the JabRef software application, which stores the citation +data as a new entry in its library. + +## Links to other Websites + +Our Service may contain links to other websites that are not operated by +us. If you click on a third party link, you will be directed to that +third party\'s site. We strongly advise you to review the Privacy Policy +of every site you visit. + +We have no control over and assume no responsibility for the content, +privacy policies or practices of any third party sites or services. + +## Changes to this Privacy Policy + +This privacy policy may be changed eventually. We encourage you to check +this Privacy Policy periodically for any changes. Any material change +will be mentioned in the changelog of the desktop application and in our +[blog](https://blog.jabref.org/). + +This privacy policy is in effect as of the day mentioned as \"last +updated\" above and will remain in effect except with respect to any +changes in its provisions in the future, which will be in effect +immediately after being posted on this page. + +## Contact + +If you get in touch with us, we may aks you to provide us with certain +personal information (e.g. name and email address) to stay in contact +with you. For any questions or concerns regarding the privacy policy, +please send us an email to or write to + +JabRef e.V.\ +Josef-Lanner-Str. 9\ +71069 Sindelfingen\ +Germany diff --git a/jabref/buildres/linux/JabRef.desktop b/jabref/buildres/linux/JabRef.desktop new file mode 100644 index 00000000..ccfefafb --- /dev/null +++ b/jabref/buildres/linux/JabRef.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=APPLICATION_NAME +GenericName=BibTeX Editor +Comment=APPLICATION_DESCRIPTION +Exec=APPLICATION_LAUNCHER +Icon=APPLICATION_ICON +Terminal=false +Type=Application +DESKTOP_MIMES +Categories=DEPLOY_BUNDLE_CATEGORY +Keywords=bibtex;biblatex;latex;bibliography +StartupWMClass=org.jabref.gui.JabRefGUI diff --git a/jabref/buildres/linux/JabRef.png b/jabref/buildres/linux/JabRef.png new file mode 100644 index 00000000..e807e044 Binary files /dev/null and b/jabref/buildres/linux/JabRef.png differ diff --git a/jabref/buildres/linux/bibtexAssociations.properties b/jabref/buildres/linux/bibtexAssociations.properties new file mode 100644 index 00000000..e1f13f7c --- /dev/null +++ b/jabref/buildres/linux/bibtexAssociations.properties @@ -0,0 +1,4 @@ +extension=bib +mime-type=text/x-bibtex +description=BibTeX File +icon=JabRef.png diff --git a/jabref/buildres/linux/copyright b/jabref/buildres/linux/copyright new file mode 100644 index 00000000..19375cdd --- /dev/null +++ b/jabref/buildres/linux/copyright @@ -0,0 +1,5 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Files: * +Copyright: Copyright (C) 2003-2019 JabRef Authors +License: MIT diff --git a/jabref/buildres/linux/jabref.spec b/jabref/buildres/linux/jabref.spec new file mode 100644 index 00000000..9ff20288 --- /dev/null +++ b/jabref/buildres/linux/jabref.spec @@ -0,0 +1,81 @@ +Summary: APPLICATION_SUMMARY +Name: APPLICATION_PACKAGE +Version: APPLICATION_VERSION +Release: APPLICATION_RELEASE +License: APPLICATION_LICENSE_TYPE +Vendor: APPLICATION_VENDOR +Prefix: %{dirname:APPLICATION_DIRECTORY} +Provides: APPLICATION_PACKAGE +%if "xAPPLICATION_GROUP" != "x" +Group: APPLICATION_GROUP +%endif + +Autoprov: 0 +Autoreq: 0 +%if "xPACKAGE_DEFAULT_DEPENDENCIES" != "x" || "xPACKAGE_CUSTOM_DEPENDENCIES" != "x" +Requires: PACKAGE_DEFAULT_DEPENDENCIES PACKAGE_CUSTOM_DEPENDENCIES +%endif + +#avoid ARCH subfolder +%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm + +#comment line below to enable effective jar compression +#it could easily get your package size from 40 to 15Mb but +#build time will substantially increase and it may require unpack200/system java to install +%define __jar_repack %{nil} + +%description +APPLICATION_DESCRIPTION + +%global __os_install_post %{nil} + +%prep + +%build + +%install +rm -rf %{buildroot} +install -d -m 755 %{buildroot}APPLICATION_DIRECTORY +cp -r %{_sourcedir}APPLICATION_DIRECTORY/* %{buildroot}APPLICATION_DIRECTORY +%if "xAPPLICATION_LICENSE_FILE" != "x" + %define license_install_file %{_defaultlicensedir}/%{name}-%{version}/%{basename:APPLICATION_LICENSE_FILE} + install -d -m 755 %{buildroot}%{dirname:%{license_install_file}} + install -m 644 APPLICATION_LICENSE_FILE %{buildroot}%{license_install_file} +%endif + +%files +%if "xAPPLICATION_LICENSE_FILE" != "x" + %license %{license_install_file} + %{dirname:%{license_install_file}} +%endif +# If installation directory for the application is /a/b/c, we want only root +# component of the path (/a) in the spec file to make sure all subdirectories +# are owned by the package. +%(echo APPLICATION_DIRECTORY | sed -e "s|\(^/[^/]\{1,\}\).*$|\1|") + +%post +# Install the native-messaging host script for firefox/chrome/chromium +install -D -m0755 /opt/jabref/lib/native-messaging-host/firefox/org.jabref.jabref.json /usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json +install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/chromium/native-messaging-hosts/org.jabref.jabref.json +install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json +# Trigger an auto-install of the browser addon for chrome/chromium browsers +install -D -m0644 /opt/jabref/lib/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /opt/google/chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json +install -D -m0644 /opt/jabref/lib/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /usr/share/google-chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json +DESKTOP_COMMANDS_INSTALL + +%preun +# Remove the native-messaging hosts script only if relative to the deb package +for NATIVE_MESSAGING_JSON in "/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json"\ + "/etc/chromium/native-messaging-hosts/org.jabref.jabref.json"\ + "/etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json"; do + if [ -e $NATIVE_MESSAGING_JSON ] && grep --quiet '"path": "/opt' $NATIVE_MESSAGING_JSON; then + rm $NATIVE_MESSAGING_JSON + fi +done +# Remove the auto-install triggers of the browser addon for chrom/chromium +rm -f /opt/google/chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json || true +rm -f /usr/share/google-chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json || true +DESKTOP_SCRIPTS +DESKTOP_COMMANDS_UNINSTALL + +%clean diff --git a/jabref/buildres/linux/jabrefHost.py b/jabref/buildres/linux/jabrefHost.py new file mode 100644 index 00000000..4c3b42df --- /dev/null +++ b/jabref/buildres/linux/jabrefHost.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 + +import json +import logging +import os +import platform +import shlex +import shutil +import struct +import subprocess +import sys +from pathlib import Path + +def which(command): + if os.getenv("FLATPAK_ID"): + try: + return subprocess.check_output(["flatpak-spawn", "--host", "which", command]).decode().strip() + except subprocess.CalledProcessError: + pass + path = shutil.which(command) + if path != "": + return path + else: + return None + + +JABREF_PATH = "" +if os.getenv("FLATPAK_ID"): + try: + subprocess.check_output(["flatpak-spawn", "--host", "true"]) + except subprocess.CalledProcessError: + logging.error("Failed to call JabRef: Flatpak browser missing permissions") + send_message({"message": "flatpakPermissionsError", "output": "Flatpak browser missing permissions"}) + exit(-1) + JABREF_PATH = "flatpak-spawn --host " + +# Try a set of possible launchers to execute JabRef +script_dir = Path(__file__).resolve().parent.parent +relpath_path = str(script_dir / "bin/JabRef") +lowercase_path = which("jabref") +uppercase_path = which("JabRef") + +# Relative path used in the portable install +if which(relpath_path) is not None: + JABREF_PATH += relpath_path +# Lowercase launcher used in deb/rpm/snap packages +elif lowercase_path is not None: + JABREF_PATH += lowercase_path +# Uppercase launcher used in Arch AUR package +elif uppercase_path is not None: + JABREF_PATH += uppercase_path +# FLatpak support +elif which("/var/lib/flatpak/exports/bin/org.jabref.jabref") is not None: + JABREF_PATH += "/var/lib/flatpak/exports/bin/org.jabref.jabref" +else: + logging.error("Could not determine JABREF_PATH") + sys.exit(-1) + +logging_dir = Path.home() / ".mozilla/native-messaging-hosts/" +if not logging_dir.exists(): + logging_dir.mkdir(parents=True) +logging.basicConfig(filename=str(logging_dir / "jabref_browser_extension.log")) + +# Read a message from stdin and decode it. +def get_message(): + raw_length = sys.stdin.buffer.read(4) + if not raw_length: + logging.error("Raw_length null") + sys.exit(0) + message_length = struct.unpack("=I", raw_length)[0] + logging.info("Got length: {} bytes to be read".format(message_length)) + message = sys.stdin.buffer.read(message_length).decode("utf-8") + logging.info("Got message of {} chars".format(len(message))) + data = json.loads(message) + logging.info("Successfully retrieved JSON") + return data + + +# Encode a message for transmission, given its content. +def encode_message(message_content): + encoded_content = json.dumps(message_content).encode("utf-8") + encoded_length = struct.pack("=I", len(encoded_content)) + return { + "length": encoded_length, + "content": struct.pack(str(len(encoded_content)) + "s", encoded_content), + } + + +# Send an encoded message to stdout. +def send_message(message): + encoded_message = encode_message(message) + sys.stdout.buffer.write(encoded_message["length"]) + sys.stdout.buffer.write(encoded_message["content"]) + sys.stdout.buffer.flush() + + +def add_jabref_entry(data): + """Send string via cli as literal to preserve special characters""" + cmd = str(JABREF_PATH).split() + ["--importBibtex", r"{}".format(data)] + logging.info("Try to execute command {}".format(cmd)) + response = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + logging.info("Called JabRef and got: {}".format(response)) + return response + + +logging.info("Starting JabRef backend") + +try: + message = get_message() +except Exception as e: + message = str(e) +logging.info(str(message)) + +if "status" in message and message["status"] == "validate": + cmd = str(JABREF_PATH).split() + ["--version"] + try: + response = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + logging.error("Failed to call JabRef: {} {}".format(exc.returncode, exc.output)) + send_message({"message": "jarNotFound", "path": JABREF_PATH}) + else: + logging.info("Response: {}".format(response)) + send_message({"message": "jarFound"}) +else: + entry = message["text"] + try: + output = add_jabref_entry(entry) + send_message({"message": "ok", "output": str(output)}) + except subprocess.CalledProcessError as exc: + logging.error("Failed to call JabRef: {} {}".format(exc.returncode, exc.output)) + send_message({"message": "error", "output": str(exc.output)}) diff --git a/jabref/buildres/linux/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json b/jabref/buildres/linux/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json new file mode 100644 index 00000000..17f681d0 --- /dev/null +++ b/jabref/buildres/linux/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json @@ -0,0 +1,3 @@ +{ + "external_update_url": "https://clients2.google.com/service/update2/crx" +} diff --git a/jabref/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json b/jabref/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json new file mode 100644 index 00000000..902c8d50 --- /dev/null +++ b/jabref/buildres/linux/native-messaging-host/chromium/org.jabref.jabref.json @@ -0,0 +1,10 @@ +{ + "name": "org.jabref.jabref", + "description": "JabRef", + "path": "/opt/jabref/lib/jabrefHost.py", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" + ] +} diff --git a/jabref/buildres/linux/native-messaging-host/firefox/org.jabref.jabref.json b/jabref/buildres/linux/native-messaging-host/firefox/org.jabref.jabref.json new file mode 100644 index 00000000..1edebc9c --- /dev/null +++ b/jabref/buildres/linux/native-messaging-host/firefox/org.jabref.jabref.json @@ -0,0 +1,10 @@ +{ + "name": "org.jabref.jabref", + "description": "JabRef", + "path": "/opt/jabref/lib/jabrefHost.py", + "type": "stdio", + "allowed_extensions": [ + "browserextension@jabref.org", + "@jabfox" + ] +} diff --git a/jabref/buildres/linux/postinst b/jabref/buildres/linux/postinst new file mode 100644 index 00000000..b24ded8e --- /dev/null +++ b/jabref/buildres/linux/postinst @@ -0,0 +1,42 @@ +#!/bin/sh +# postinst script for my jabref +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + configure) + # Install the native-messaging host script for firefox/chrome/chromium + install -D -m0755 /opt/jabref/lib/native-messaging-host/firefox/org.jabref.jabref.json /usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json + install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/chromium/native-messaging-hosts/org.jabref.jabref.json + install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json + install -D -m0755 /opt/jabref/lib/native-messaging-host/chromium/org.jabref.jabref.json /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json + # Trigger an auto-install of the browser addon for chrome/chromium browsers + install -D -m0644 /opt/jabref/lib/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /opt/google/chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json + install -D -m0644 /opt/jabref/lib/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /usr/share/google-chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json + DESKTOP_COMMANDS_INSTALL + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/jabref/buildres/linux/postrm b/jabref/buildres/linux/postrm new file mode 100644 index 00000000..eb36765e --- /dev/null +++ b/jabref/buildres/linux/postrm @@ -0,0 +1,42 @@ +#!/bin/sh +# postrm script for APPLICATION_PACKAGE +# +# see: dh_installdeb(1) +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + # Remove the native-messaging hosts script only if relative to the deb package + for NATIVE_MESSAGING_JSON in "/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json"\ + "/etc/chromium/native-messaging-hosts/org.jabref.jabref.json"\ + "/etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json"\ + "/etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json"; do + if [ -e $NATIVE_MESSAGING_JSON ] && grep --quiet '"path": "/opt' $NATIVE_MESSAGING_JSON; then + rm -f $NATIVE_MESSAGING_JSON + fi + done + # Remove the auto-install triggers of the browser addon for chrom/chromium + rm -f /opt/google/chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json + rm -f /usr/share/google-chrome/extensions/bifehkofibaamoeaopjglfkddgkijdlh.json + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/jabref/buildres/mac/Info-lite.plist.template b/jabref/buildres/mac/Info-lite.plist.template new file mode 100644 index 00000000..509eb841 --- /dev/null +++ b/jabref/buildres/mac/Info-lite.plist.template @@ -0,0 +1,39 @@ + + + + + LSMinimumSystemVersion + 10.9 + CFBundleDevelopmentRegion + English + CFBundleAllowMixedLocalizations + + CFBundleExecutable + DEPLOY_LAUNCHER_NAME + CFBundleIconFile + DEPLOY_ICON_FILE + CFBundleIdentifier + DEPLOY_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DEPLOY_BUNDLE_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + DEPLOY_BUNDLE_SHORT_VERSION + CFBundleSignature + ???? + + LSApplicationCategoryType + Unknown + CFBundleVersion + DEPLOY_BUNDLE_CFBUNDLE_VERSION + NSHumanReadableCopyright + DEPLOY_BUNDLE_COPYRIGHTDEPLOY_FILE_ASSOCIATIONS + NSHighResolutionCapable + true + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/jabref/buildres/mac/Info.plist b/jabref/buildres/mac/Info.plist new file mode 100644 index 00000000..509eb841 --- /dev/null +++ b/jabref/buildres/mac/Info.plist @@ -0,0 +1,39 @@ + + + + + LSMinimumSystemVersion + 10.9 + CFBundleDevelopmentRegion + English + CFBundleAllowMixedLocalizations + + CFBundleExecutable + DEPLOY_LAUNCHER_NAME + CFBundleIconFile + DEPLOY_ICON_FILE + CFBundleIdentifier + DEPLOY_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DEPLOY_BUNDLE_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + DEPLOY_BUNDLE_SHORT_VERSION + CFBundleSignature + ???? + + LSApplicationCategoryType + Unknown + CFBundleVersion + DEPLOY_BUNDLE_CFBUNDLE_VERSION + NSHumanReadableCopyright + DEPLOY_BUNDLE_COPYRIGHTDEPLOY_FILE_ASSOCIATIONS + NSHighResolutionCapable + true + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/jabref/buildres/mac/Info.plist.template b/jabref/buildres/mac/Info.plist.template new file mode 100644 index 00000000..509eb841 --- /dev/null +++ b/jabref/buildres/mac/Info.plist.template @@ -0,0 +1,39 @@ + + + + + LSMinimumSystemVersion + 10.9 + CFBundleDevelopmentRegion + English + CFBundleAllowMixedLocalizations + + CFBundleExecutable + DEPLOY_LAUNCHER_NAME + CFBundleIconFile + DEPLOY_ICON_FILE + CFBundleIdentifier + DEPLOY_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DEPLOY_BUNDLE_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + DEPLOY_BUNDLE_SHORT_VERSION + CFBundleSignature + ???? + + LSApplicationCategoryType + Unknown + CFBundleVersion + DEPLOY_BUNDLE_CFBUNDLE_VERSION + NSHumanReadableCopyright + DEPLOY_BUNDLE_COPYRIGHTDEPLOY_FILE_ASSOCIATIONS + NSHighResolutionCapable + true + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/jabref/buildres/mac/JabRef-background-darkAqua.png b/jabref/buildres/mac/JabRef-background-darkAqua.png new file mode 100644 index 00000000..0612c825 Binary files /dev/null and b/jabref/buildres/mac/JabRef-background-darkAqua.png differ diff --git a/jabref/buildres/mac/JabRef-background.png b/jabref/buildres/mac/JabRef-background.png new file mode 100644 index 00000000..0612c825 Binary files /dev/null and b/jabref/buildres/mac/JabRef-background.png differ diff --git a/jabref/buildres/mac/JabRef-background.tiff b/jabref/buildres/mac/JabRef-background.tiff new file mode 100644 index 00000000..62782b1a Binary files /dev/null and b/jabref/buildres/mac/JabRef-background.tiff differ diff --git a/jabref/buildres/mac/JabRef-dmg-setup.scpt b/jabref/buildres/mac/JabRef-dmg-setup.scpt new file mode 100644 index 00000000..fcb2c174 --- /dev/null +++ b/jabref/buildres/mac/JabRef-dmg-setup.scpt @@ -0,0 +1,44 @@ +tell application "Finder" + set theDisk to a reference to (disks whose URL = "DEPLOY_VOLUME_URL") + open theDisk + + set theWindow to a reference to (container window of disks whose URL = "DEPLOY_VOLUME_URL") + + set current view of theWindow to icon view + set toolbar visible of theWindow to false + set statusbar visible of theWindow to false + + -- size of window should fit the size of background + set the bounds of theWindow to {346, 100, 920, 500} + + set theViewOptions to a reference to the icon view options of theWindow + set arrangement of theViewOptions to not arranged + set icon size of theViewOptions to 128 + + set background picture of theViewOptions to POSIX file "DEPLOY_BG_FILE" + + -- Create alias for install location + make new alias file at POSIX file "DEPLOY_VOLUME_PATH" to POSIX file "DEPLOY_INSTALL_LOCATION" with properties {name:"DEPLOY_INSTALL_LOCATION"} + + set allTheFiles to the name of every item of theWindow + set xpos to 120 + repeat with theFile in allTheFiles + set theFilePath to POSIX path of theFile + set appFilePath to POSIX path of "/DEPLOY_TARGET" + if theFilePath is "DEPLOY_INSTALL_LOCATION" then + -- Position install location + set position of item theFile of theWindow to {430, 170} + else if theFilePath ends with appFilePath then + -- Position application or runtime + set position of item theFile of theWindow to {140, 170} + else + -- Position all other items in a second row. + set position of item theFile of theWindow to {xpos, 290} + set xpos to xpos + 150 + end if + end repeat + + update theDisk without registering applications + delay 5 + close (get window of theDisk) +end tell diff --git a/jabref/buildres/mac/Jabref-volume.icns b/jabref/buildres/mac/Jabref-volume.icns new file mode 100644 index 00000000..cf7a8d59 Binary files /dev/null and b/jabref/buildres/mac/Jabref-volume.icns differ diff --git a/jabref/buildres/mac/README.md b/jabref/buildres/mac/README.md new file mode 100644 index 00000000..495b5742 --- /dev/null +++ b/jabref/buildres/mac/README.md @@ -0,0 +1,12 @@ +# Mac Resources + +## Modifying DMG Setup scpt + +Rename `JabRef-dmg-setup.scpt script` to `JabRef-dmg-setup.applescript`. +Only modify the `JabRef-dmg-setup.applescript` in the macOS Script Editor. Afterwards copy over the file and rename it to `JabRef-dmg-setup.scpt`. +Normally the `scpt` file is a binary compiled variant and the `.applescript` the uncompiled format but jpackage expects the sctp in uncompiled format + +## Generate iconsets + +To generate icns files use the script under `src/main/resources/icons` +Install [svg2png](https://formulae.brew.sh/formula/svg2png) and call the script with the svg filename as first argument. diff --git a/jabref/buildres/mac/Runtime-Info.plist b/jabref/buildres/mac/Runtime-Info.plist new file mode 100644 index 00000000..76fb7eeb --- /dev/null +++ b/jabref/buildres/mac/Runtime-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + libjli.dylib + CFBundleIdentifier + CF_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 7.0 + CFBundleName + CF_BUNDLE_NAME + CFBundlePackageType + BNDL + CFBundleShortVersionString + CF_BUNDLE_SHORT_VERSION_STRING + CFBundleSignature + ???? + CFBundleVersion + CF_BUNDLE_VERSION + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/jabref/buildres/mac/Runtime-Info.plist.template b/jabref/buildres/mac/Runtime-Info.plist.template new file mode 100644 index 00000000..76fb7eeb --- /dev/null +++ b/jabref/buildres/mac/Runtime-Info.plist.template @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + libjli.dylib + CFBundleIdentifier + CF_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 7.0 + CFBundleName + CF_BUNDLE_NAME + CFBundlePackageType + BNDL + CFBundleShortVersionString + CF_BUNDLE_SHORT_VERSION_STRING + CFBundleSignature + ???? + CFBundleVersion + CF_BUNDLE_VERSION + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/jabref/buildres/mac/bibtexAssociations.properties b/jabref/buildres/mac/bibtexAssociations.properties new file mode 100644 index 00000000..9db815db --- /dev/null +++ b/jabref/buildres/mac/bibtexAssociations.properties @@ -0,0 +1,4 @@ +extension=bib +mime-type=text/x-bibtex +description=BibTeX File +icon=jabref.icns diff --git a/jabref/buildres/mac/info-lite.plist b/jabref/buildres/mac/info-lite.plist new file mode 100644 index 00000000..509eb841 --- /dev/null +++ b/jabref/buildres/mac/info-lite.plist @@ -0,0 +1,39 @@ + + + + + LSMinimumSystemVersion + 10.9 + CFBundleDevelopmentRegion + English + CFBundleAllowMixedLocalizations + + CFBundleExecutable + DEPLOY_LAUNCHER_NAME + CFBundleIconFile + DEPLOY_ICON_FILE + CFBundleIdentifier + DEPLOY_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DEPLOY_BUNDLE_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + DEPLOY_BUNDLE_SHORT_VERSION + CFBundleSignature + ???? + + LSApplicationCategoryType + Unknown + CFBundleVersion + DEPLOY_BUNDLE_CFBUNDLE_VERSION + NSHumanReadableCopyright + DEPLOY_BUNDLE_COPYRIGHTDEPLOY_FILE_ASSOCIATIONS + NSHighResolutionCapable + true + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/jabref/buildres/mac/jabref.entitlements b/jabref/buildres/mac/jabref.entitlements new file mode 100644 index 00000000..56f6d632 --- /dev/null +++ b/jabref/buildres/mac/jabref.entitlements @@ -0,0 +1,24 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.server + + com.apple.security.network.client + + com.apple.security.files.user-selected.read-write + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.allow-dyld-environment-variables + + + diff --git a/jabref/buildres/mac/jabrefHost.py b/jabref/buildres/mac/jabrefHost.py new file mode 100644 index 00000000..b9d53de1 --- /dev/null +++ b/jabref/buildres/mac/jabrefHost.py @@ -0,0 +1,101 @@ +#!/usr/bin/python3 + +import json +import logging +import platform +import shlex +import shutil +import struct +import subprocess +import sys +from pathlib import Path + +# We assume that this python script is located in "jabref/lib" while the executable is "jabref/bin/JabRef" +# Note that the package structure is different when installed as a .app bundle on MacOs, so the path must be altered. +script_dir = Path(__file__).resolve().parent.parent +JABREF_PATH = script_dir / "bin/JabRef" + +# on mac we must only go one folder upwards +if sys.platform.startswith('darwin'): + script_dir = Path(__file__).resolve().parent +if not JABREF_PATH.exists(): + JABREF_PATH = script_dir / "MacOS/JabRef" + +if not JABREF_PATH.exists(): + logging.error("Could not determine JABREF_PATH") + send_message({"message": "error", "output": "Could not find JabRef. Please check that it is installed correctly."}) + sys.exit(-1) + +logging_dir = Path.home() / ".mozilla/native-messaging-hosts/" +if not logging_dir.exists(): + logging_dir.mkdir(parents=True) +logging.basicConfig(filename=str(logging_dir / "jabref_browser_extension.log")) + +# Read a message from stdin and decode it. +def get_message(): + raw_length = sys.stdin.buffer.read(4) + if not raw_length: + logging.error("Raw_length null") + sys.exit(0) + message_length = struct.unpack("=I", raw_length)[0] + logging.info("Got length: {} bytes to be read".format(message_length)) + message = sys.stdin.buffer.read(message_length).decode("utf-8") + logging.info("Got message of {} chars".format(len(message))) + data = json.loads(message) + logging.info("Successfully retrieved JSON") + return data + + +# Encode a message for transmission, given its content. +def encode_message(message_content): + encoded_content = json.dumps(message_content).encode("utf-8") + encoded_length = struct.pack("=I", len(encoded_content)) + return { + "length": encoded_length, + "content": struct.pack(str(len(encoded_content)) + "s", encoded_content), + } + + +# Send an encoded message to stdout. +def send_message(message): + encoded_message = encode_message(message) + sys.stdout.buffer.write(encoded_message["length"]) + sys.stdout.buffer.write(encoded_message["content"]) + sys.stdout.buffer.flush() + + +def add_jabref_entry(data): + """Send string via cli as literal to preserve special characters""" + cmd = str(JABREF_PATH).split() + ["--importBibtex", r"{}".format(data)] + logging.info("Try to execute command {}".format(cmd)) + response = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + logging.info("Called JabRef and got: {}".format(response)) + return response + + +logging.info("Starting JabRef backend") + +try: + message = get_message() +except Exception as e: + message = str(e) +logging.info(str(message)) + +if "status" in message and message["status"] == "validate": + cmd = str(JABREF_PATH).split() + ["--version"] + try: + response = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + logging.error("Failed to call JabRef: {} {}".format(exc.returncode, exc.output)) + send_message({"message": "jarNotFound", "path": JABREF_PATH}) + else: + logging.info("Response: {}".format(response)) + send_message({"message": "jarFound"}) +else: + entry = message["text"] + try: + output = add_jabref_entry(entry) + send_message({"message": "ok", "output": str(output)}) + except subprocess.CalledProcessError as exc: + logging.error("Failed to call JabRef: {} {}".format(exc.returncode, exc.output)) + send_message({"message": "error", "output": str(exc.output)}) diff --git a/jabref/buildres/mac/launcher.icns b/jabref/buildres/mac/launcher.icns new file mode 100644 index 00000000..cf7a8d59 Binary files /dev/null and b/jabref/buildres/mac/launcher.icns differ diff --git a/jabref/buildres/mac/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json b/jabref/buildres/mac/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json new file mode 100644 index 00000000..17f681d0 --- /dev/null +++ b/jabref/buildres/mac/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json @@ -0,0 +1,3 @@ +{ + "external_update_url": "https://clients2.google.com/service/update2/crx" +} diff --git a/jabref/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json b/jabref/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json new file mode 100644 index 00000000..a075a368 --- /dev/null +++ b/jabref/buildres/mac/native-messaging-host/chromium/org.jabref.jabref.json @@ -0,0 +1,10 @@ +{ + "name": "org.jabref.jabref", + "description": "JabRef", + "path": "/Applications/JabRef.app/Contents/jabrefHost.py", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" + ] +} diff --git a/jabref/buildres/mac/native-messaging-host/firefox/org.jabref.jabref.json b/jabref/buildres/mac/native-messaging-host/firefox/org.jabref.jabref.json new file mode 100644 index 00000000..d6c6541f --- /dev/null +++ b/jabref/buildres/mac/native-messaging-host/firefox/org.jabref.jabref.json @@ -0,0 +1,7 @@ +{ + "name": "org.jabref.jabref", + "description": "JabRef", + "path": "/Applications/JabRef.app/Contents/jabrefHost.py", + "type": "stdio", + "allowed_extensions": ["browserextension@jabref.org", "@jabfox"] +} diff --git a/jabref/buildres/mac/postinstall b/jabref/buildres/mac/postinstall new file mode 100644 index 00000000..9c646441 --- /dev/null +++ b/jabref/buildres/mac/postinstall @@ -0,0 +1,20 @@ +#!/bin/sh + +chown root:wheel "INSTALL_LOCATION" +chmod a+rX "INSTALL_LOCATION" +chmod +r "APP_LOCATION/"*.jar +# Trigger an auto-install of the browser addon for chrome/chromium browsers +# First create the necessary path, then copy the autoinstall file. +install -d /Library/Application\ Support/Google/Chrome/External\ Extensions/ +install -m0644 /Applications/JabRef.app/Contents/native-messaging-host/chromium/bifehkofibaamoeaopjglfkddgkijdlh.json /Library/Application\ Support/Google/Chrome/External\ Extensions/bifehkofibaamoeaopjglfkddgkijdlh.json +# Install the native-messaging host script for firefox/chrome/chromium +install -d /Library/Application\ Support/Mozilla/NativeMessagingHosts/ +install -m0755 /Applications/JabRef.app/Contents/native-messaging-host/firefox/org.jabref.jabref.json /Library/Application\ Support/Mozilla/NativeMessagingHosts/org.jabref.jabref.json +install -d /Library/Application\ Support/Chromium/NativeMessagingHosts/ +install -m0755 /Applications/JabRef.app/Contents/native-messaging-host/chromium/org.jabref.jabref.json /Library/Application\ Support/Chromium/NativeMessagingHosts/org.jabref.jabref.json +install -d /Library/Google/Chrome/NativeMessagingHosts/ +install -m0755 /Applications/JabRef.app/Contents/native-messaging-host/chromium/org.jabref.jabref.json /Library/Google/Chrome/NativeMessagingHosts/org.jabref.jabref.json +install -d /Library/Microsoft/Edge/NativeMessagingHosts/ +install -m0755 /Applications/JabRef.app/Contents/native-messaging-host/chromium/org.jabref.jabref.json /Library/Microsoft/Edge/NativeMessagingHosts/org.jabref.jabref.json + +exit 0 diff --git a/jabref/buildres/setup-test-oracle.sql b/jabref/buildres/setup-test-oracle.sql new file mode 100644 index 00000000..b980a5b0 --- /dev/null +++ b/jabref/buildres/setup-test-oracle.sql @@ -0,0 +1,6 @@ +create pluggable database jabref admin user jabref identified by jabref + file_name_convert=('/opt/oracle/oradata/XE/pdbseed','/opt/oracle/oradata/XE/JABREF'); +alter pluggable database jabref open read write; +alter pluggable database all save state; +ALTER SESSION SET CONTAINER = jabref; +grant all privileges to jabref container=current; diff --git a/jabref/buildres/windows/JabRef-post-image.wsf b/jabref/buildres/windows/JabRef-post-image.wsf new file mode 100644 index 00000000..96717d1d --- /dev/null +++ b/jabref/buildres/windows/JabRef-post-image.wsf @@ -0,0 +1,36 @@ + + + + + + diff --git a/jabref/buildres/windows/JabRefHost.bat b/jabref/buildres/windows/JabRefHost.bat new file mode 100644 index 00000000..1da03167 --- /dev/null +++ b/jabref/buildres/windows/JabRefHost.bat @@ -0,0 +1,20 @@ +@echo off +pushd %~dp0 + +:: Test if pwsh exists +setlocal enabledelayedexpansion +where /q pwsh.exe +if !ERRORLEVEL!==0 ( + @pwsh.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File ".\JabRefHost.ps1" +) else ( + :: If not, test if powershell exists + where /q powershell.exe + if !ERRORLEVEL!==0 ( + @powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -WindowStyle Hidden -File ".\JabRefHost.ps1" + ) else ( + echo "Could not find pwsh.exe or powershell.exe" 1>&2 + echo "Please install PowerShell and try again." 1>&2 + exit /b 1 + ) +) +endlocal diff --git a/jabref/buildres/windows/JabRefHost.ps1 b/jabref/buildres/windows/JabRefHost.ps1 new file mode 100644 index 00000000..d5d4e43e --- /dev/null +++ b/jabref/buildres/windows/JabRefHost.ps1 @@ -0,0 +1,54 @@ +function Respond($response) { + $jsonResponse = $response | ConvertTo-Json + + try { + $writer = New-Object System.IO.BinaryWriter([System.Console]::OpenStandardOutput()) + $writer.Write([int]$jsonResponse.Length) + $writer.Write([System.Text.Encoding]::UTF8.GetBytes($jsonResponse)) + $writer.Close() + } finally { + $writer.Dispose() + } +} + +$jabRefExe = [System.IO.Path]::Combine($PSScriptRoot, "runtime\\bin\\JabRef.bat") + +try { + $reader = New-Object System.IO.BinaryReader([System.Console]::OpenStandardInput()) + $length = $reader.ReadInt32() + $messageRaw = [System.Text.Encoding]::UTF8.GetString($reader.ReadBytes($length)) + $message = $messageRaw | ConvertFrom-Json + + if ($message.Status -eq "validate") { + if (-not (Test-Path $jabRefExe)) { + return Respond @{message="jarNotFound";path=$jabRefExe} + } else { + return Respond @{message="jarFound"} + } + } + + if (-not (Test-Path $jabRefExe)) { + return Respond @{message="jarNotFound"; output="Unable to locate '$jabRefExe'."} + } + + $messageText = $message.Text.replace("`n"," ").replace("`r"," ") + $tempfile = New-TemporaryFile + # WriteAllLines should write the file as UTF-8 without BOM + # unlike Out-File which writes UTF-16 with BOM in ps5.1 + [IO.File]::WriteAllLines($tempfile, $messageText) + $output = & $jabRefExe -importToOpen $tempfile *>&1 + Remove-Item $tempfile *>$null + # For debugging: uncomment the following lines to get the output of JabRef be displayed as a popup + #$wshell = New-Object -ComObject Wscript.Shell + #$wshell.Popup("Input: $messageText; Output: $output", 0, "JabRef", 0x0 + 0x30) + return Respond @{message="ok"; output="$output"} +} +catch { + $err = $PSItem + Respond @{message="error"; output="$($err.Exception.Message)"; stacktrace="$($err.ScriptStackTrace)"} + # Still print the error to the console so that it is picked up by the browser in addition + throw $err +} +finally { + $reader.Dispose() +} diff --git a/jabref/buildres/windows/JabRefTopBanner.bmp b/jabref/buildres/windows/JabRefTopBanner.bmp new file mode 100644 index 00000000..7e95c3b8 Binary files /dev/null and b/jabref/buildres/windows/JabRefTopBanner.bmp differ diff --git a/jabref/buildres/windows/bibtexAssociations.properties b/jabref/buildres/windows/bibtexAssociations.properties new file mode 100644 index 00000000..4a269642 --- /dev/null +++ b/jabref/buildres/windows/bibtexAssociations.properties @@ -0,0 +1,4 @@ +extension=bib +mime-type=text/x-bibtex +description=BibTeX File +icon=JabRef.ico diff --git a/jabref/buildres/windows/jabref-chrome.json b/jabref/buildres/windows/jabref-chrome.json new file mode 100644 index 00000000..a8d3020c --- /dev/null +++ b/jabref/buildres/windows/jabref-chrome.json @@ -0,0 +1,10 @@ +{ + "name": "org.jabref.jabref", + "description": "JabRef", + "path": "JabRefHost.bat", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://bifehkofibaamoeaopjglfkddgkijdlh/", + "chrome-extension://pgkajmkfgbehiomipedjhoddkejohfna/" + ] +} diff --git a/jabref/buildres/windows/jabref-firefox.json b/jabref/buildres/windows/jabref-firefox.json new file mode 100644 index 00000000..30dfc790 --- /dev/null +++ b/jabref/buildres/windows/jabref-firefox.json @@ -0,0 +1,10 @@ +{ + "name": "org.jabref.jabref", + "description": "JabRef", + "path": "JabRefHost.bat", + "type": "stdio", + "allowed_extensions": [ + "browserextension@jabref.org", + "@jabfox" + ] +} diff --git a/jabref/codecov.yml b/jabref/codecov.yml new file mode 100644 index 00000000..4db424d3 --- /dev/null +++ b/jabref/codecov.yml @@ -0,0 +1,9 @@ +coverage: + ignore: + - src/generated/.* + status: + patch: false + project: + default: + threshold: 0.01 +comment: false diff --git a/jabref/config/Eclipse Code Style.epf b/jabref/config/Eclipse Code Style.epf new file mode 100644 index 00000000..f0a04dd9 --- /dev/null +++ b/jabref/config/Eclipse Code Style.epf @@ -0,0 +1,328 @@ +#Fri Mar 09 22:05:27 CET 2018 +\!/= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.argumentSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.fieldSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.localSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes= +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=88 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_block_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_header=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_html=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_line_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.format_source_code=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.line_length=120 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.compact_else_if=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.continuation_indentation=2 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_empty_lines=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.indentation.size=4 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.join_lines_in_comments=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.join_wrapped_lines=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.lineSplit=9999 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.tabulation.char=space +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.tabulation.size=4 +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.use_on_off_tags=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=false +/instance/org.eclipse.jdt.core/org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true +/instance/org.eclipse.jdt.ui/formatter_profile=_eclipse-cs JabRef +/instance/org.eclipse.jdt.ui/formatter_settings_version=13 +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.cleanupprofiles=\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.exception.name=e +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles=\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.formatterprofiles.version=13 +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.gettersetter.use.is=true +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.overrideannotation=true +/instance/org.eclipse.jdt.ui/org.eclipse.jdt.ui.text.custom_code_templates= +@org.eclipse.jdt.core=3.13.100.v20171123-1049 +@org.eclipse.jdt.ui=3.13.51.v20171122-0652 +file_export_version=3.0 diff --git a/jabref/config/IntelliJ Code Style.xml b/jabref/config/IntelliJ Code Style.xml new file mode 100644 index 00000000..6a03e104 --- /dev/null +++ b/jabref/config/IntelliJ Code Style.xml @@ -0,0 +1,296 @@ + + \ No newline at end of file diff --git a/jabref/config/README.md b/jabref/config/README.md new file mode 100644 index 00000000..0bea959d --- /dev/null +++ b/jabref/config/README.md @@ -0,0 +1,30 @@ +# IntelliJ IDEA Code Style Configuration + +IntelliJ IDEA comes with a powerful code formatter that helps you to keep the formatting consistent with the style JabRef uses. +Style-checks are done for each pull request and installing this code style configuration helps you to ensure that this test passes. To install it, you need to do the following steps: + +1. Go to *Preferences* or press Ctrl + Alt + S (Cmd + , on macOS) +2. Go to "Editor > Code Style" +3. Click the gear (right of "Scheme: ...") +4. Click "Import Scheme >" +5. Choose `IntelliJ IDEA code style XML` +6. Select the file `config\IntelliJ Code Style.xml` +7. Press "OK" +8. Press "OK" +9. Press "Close" +10. Press "OK" + +* Please let `.editorconfig` override the settings of IntelliJ + + +# Eclipse: + +The Eclipse code formatter style is stored in the `eclipse.gradle` file and gets imported automatically. +In case the formatter style needs to be adapted, configure it and export in eclipse. + +1. Right click on the eclipse project "JabRef" +2. Select "Export > General > Preferences" +3. Select "Java Code Style preferences" +4. Choose output file +5. Compare the formatter settings in the epf file with the ones in the eclipse.gradle file (`org.eclipse.jdt.core.formatter.`) +6. Replace the Eclipse Code Style.epf with the exported epf file diff --git a/jabref/config/VSCode Code Style.xml b/jabref/config/VSCode Code Style.xml new file mode 100644 index 00000000..a408440e --- /dev/null +++ b/jabref/config/VSCode Code Style.xml @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jabref/config/checkstyle/checkstyle.xml b/jabref/config/checkstyle/checkstyle.xml new file mode 100644 index 00000000..b40e6b25 --- /dev/null +++ b/jabref/config/checkstyle/checkstyle.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jabref/config/checkstyle/checkstyle_reviewdog.xml b/jabref/config/checkstyle/checkstyle_reviewdog.xml new file mode 100644 index 00000000..510baff9 --- /dev/null +++ b/jabref/config/checkstyle/checkstyle_reviewdog.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jabref/config/checkstyle/suppressions.xml b/jabref/config/checkstyle/suppressions.xml new file mode 100644 index 00000000..471a9f57 --- /dev/null +++ b/jabref/config/checkstyle/suppressions.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/jabref/crowdin.yml b/jabref/crowdin.yml new file mode 100644 index 00000000..90c30e89 --- /dev/null +++ b/jabref/crowdin.yml @@ -0,0 +1,8 @@ +files: + - source: /src/main/resources/l10n/JabRef_en.properties + translation: /src/main/resources/l10n/JabRef_%two_letters_code%.properties + languages_mapping: + two_letters_code: + pt-BR: pt_BR + zh-CN: zh_CN + zh-TW: zh_TW diff --git a/jabref/docs/.dockerignore b/jabref/docs/.dockerignore new file mode 100644 index 00000000..30e5c368 --- /dev/null +++ b/jabref/docs/.dockerignore @@ -0,0 +1,3 @@ +.jekyll-cache +.jekyll-metadata +_site/ diff --git a/jabref/docs/.gitignore b/jabref/docs/.gitignore new file mode 100644 index 00000000..b29706bc --- /dev/null +++ b/jabref/docs/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock +.jekyll-cache +.jekyll-metadata +_site/ diff --git a/jabref/docs/CNAME b/jabref/docs/CNAME new file mode 100644 index 00000000..e8ffe057 --- /dev/null +++ b/jabref/docs/CNAME @@ -0,0 +1 @@ +devdocs.jabref.org diff --git a/jabref/docs/Dockerfile b/jabref/docs/Dockerfile new file mode 100644 index 00000000..4a0ae5ed --- /dev/null +++ b/jabref/docs/Dockerfile @@ -0,0 +1,14 @@ +FROM ruby:2.7 + +ENV LC_ALL=C.UTF-8 +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US.UTF-8 + +EXPOSE 4000 + +WORKDIR /srv/jekyll +COPY . /srv/jekyll + +RUN gem install bundler && bundle install + +CMD bundle exec jekyll serve -H 0.0.0.0 -t diff --git a/jabref/docs/Gemfile b/jabref/docs/Gemfile new file mode 100644 index 00000000..c4cfb6ca --- /dev/null +++ b/jabref/docs/Gemfile @@ -0,0 +1,22 @@ +source 'https://rubygems.org' + +gem "jekyll", "~> 4.3.4" # installed by `gem jekyll` + +# Homepage: https://github.com/paulrobertlloyd/jekyll-figure#jekyll-figure +gem 'jekyll-figure' + +gem "just-the-docs", "0.10.0" + +gem "jekyll-remote-theme" + +# Added, because defualt layout with path "**/*.md" does not work at Jekyll +# Source: https://jekyllrb.com/docs/configuration/front-matter-defaults/#glob-patterns-in-front-matter-defaults +# Homepage: https://github.com/benbalter/jekyll-default-layout#jekyll-default-layout +gem 'jekyll-default-layout' + +# Somehow, "title" is set "magically" when on GitHub, but not when running locally +# Homepage: https://github.com/benbalter/jekyll-titles-from-headings#jekyll-titles-from-headings +gem 'jekyll-titles-from-headings' + +# Homepage: https://github.com/benbalter/jekyll-relative-links +gem 'jekyll-relative-links' diff --git a/jabref/docs/Groups.uml b/jabref/docs/Groups.uml new file mode 100644 index 00000000..6fdf75c8 --- /dev/null +++ b/jabref/docs/Groups.uml @@ -0,0 +1,91 @@ + + + JAVA + net.sf.jabref.model.groups.AbstractGroup + + net.sf.jabref.model.groups.RegexKeywordGroup + net.sf.jabref.model.groups.AllEntriesGroup + net.sf.jabref.model.groups.AbstractGroup + net.sf.jabref.model.groups.ExplicitGroup + net.sf.jabref.model.groups.SearchGroup + net.sf.jabref.model.groups.GroupHierarchyType + net.sf.jabref.model.groups.GroupTreeNode + net.sf.jabref.model.search.SearchMatcher + java.lang.FunctionalInterface + net.sf.jabref.model.groups.GroupEntryChanger + net.sf.jabref.model.groups.KeywordGroup + net.sf.jabref.model.groups.SimpleKeywordGroup + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fields + Inner Classes + Properties + Methods + + All + private + + diff --git a/jabref/docs/README.md b/jabref/docs/README.md new file mode 100644 index 00000000..18c6f411 --- /dev/null +++ b/jabref/docs/README.md @@ -0,0 +1,39 @@ +# JabRef Developer Documentation + +The developer documentation is created using the Jekyll Theme [Just the Docs](https://just-the-docs.github.io/just-the-docs/). + +## Local Development + +For local development, follow the [Jekyll installation instructions](https://jekyllrb.com/docs/installation/). +Installing the latest version of ruby followed by `gem install bundler` should be enough. + +Afterwards, run + +```shell +bundle install +jekyll serve --livereload +``` + +and go to in your browser. + +On **Windows**, using a dockerized environment is recommended: + +```shell +docker build . -t jrjekyll +docker run -p 4000:4000 -it --rm --volume="C:\git-repositories\jabref\docs":/srv/jekyll jrjekyll jekyll serve -H 0.0.0.0 -t +``` + +* With Ctrl+C you can stop the server (this is enabled by the `-it` switch). +* In case you get errors regarding `Gemfile.lock`, just delete `Gemfile.lock` and rerun. +* The current `Dockerfile` is based on . + The [Jekyll Docker image](https://github.com/envygeeks/jekyll-docker#jekyll-docker) did not work end of 2022 (because Ruby was too new). + +## Execute linting action + +You can execute the linting action as if it was executed by GitHub by executing following command. + +```shell +act --rm --platform ubuntu-latest=fwilhe2/act-runner:latest -W .github/workflows/tests.yml -j "markdown" +``` + +That command uses [act](https://github.com/nektos/act), which brings GitHub actions execution to the developer machine. diff --git a/jabref/docs/_config.yml b/jabref/docs/_config.yml new file mode 100644 index 00000000..93c568ed --- /dev/null +++ b/jabref/docs/_config.yml @@ -0,0 +1,73 @@ +repository: JabRef/jabref +title: "Developer Documentation" +remote_theme: just-the-docs/just-the-docs@v0.5.0 +color_scheme: light + +exclude: [_config.yml, .dockerignore, .gitignore, CNAME, Dockerfile, Gemfile, Gemfile.lock, README.md, mkdocs-custom.css] + +# Hint by https://github.com/just-the-docs/just-the-docs/issues/374#issuecomment-680273258 +# Theme read from https://github.com/StylishThemes/Syntax-Themes/blob/master/pygments/css-github/ +# Browse alternative themes at https://stylishthemes.github.io/Syntax-Themes/pygments/ +highlight_theme: jellybeans + +favicon_ico: "/images/favicon.ico" + +search_enabled: true +button: true +aux_links: + "Gitter Chat": + - "https://gitter.im/JabRef/jabref" + "Discussion Forum": + - "https://discourse.jabref.org/" + "Code Repository": + - "https://github.com/jabref/jabref/" +aux_links_new_tab: false +last_edit_timestamp: false # show or hide edit time - page must have `last_modified_date` defined in the frontmatter +last_edit_time_format: "%b %e %Y at %I:%M %p" # uses ruby's time format: https://ruby-doc.org/stdlib-2.7.0/libdoc/time/rdoc/Time.html +gh_edit_link: true +gh_edit_link_text: "Edit this page on GitHub." +gh_edit_repository: "https://github.com/jabref/jabref" +gh_edit_branch: "main" +gh_edit_source: "docs" +gh_edit_view_mode: "edit" + +# ga_tracking: UA-5555555-55 +# ga_tracking_anonymize_ip: true + +baseurl: "" +url: "" + +plugins: + - jekyll-default-layout + - jekyll-figure + - jekyll-remote-theme + - jekyll-titles-from-headings + - jekyll-relative-links + +jekyll-figure: + paragraphs: false + +callouts_level: quiet # or loud +callouts: + highlight: + title: Summary + color: blue + important: + title: Important + color: yellow + new: + title: New + color: green + note: + title: Note + color: purple + warning: + title: Warning + color: red + +mermaid: + # Version of mermaid library + # Pick an available version from https://cdn.jsdelivr.net/npm/mermaid/ + version: "9.4.3" + +enable_copy_code_button: true diff --git a/jabref/docs/_sass/custom/custom.scss b/jabref/docs/_sass/custom/custom.scss new file mode 100644 index 00000000..d5e70410 --- /dev/null +++ b/jabref/docs/_sass/custom/custom.scss @@ -0,0 +1,8 @@ +figcaption { + font-size: .75em; + font-weight: 550; +} + +figcaption:before { + content: "Figure: " +} diff --git a/jabref/docs/code-howtos/IntelliJ.md b/jabref/docs/code-howtos/IntelliJ.md new file mode 100644 index 00000000..56e9acf4 --- /dev/null +++ b/jabref/docs/code-howtos/IntelliJ.md @@ -0,0 +1,29 @@ +--- +parent: Code Howtos +--- + +# IntelliJ Hints + +{: .highlight } +Did you know that [IntelliJ allows for reformatting selected code](https://www.jetbrains.com/help/idea/reformat-and-rearrange-code.html#reformat_code) if you press Ctrl + Alt + L? + +## Key hints for IntelliJ + +* Shift+Shift (AKA double-shift): Open the search dialog. +* Ctrl+N: Open the search dialog and select search for a class. +* Ctrl+Shift+F: Search everywhere in the code base. +* Alt+F1 and then Enter: Locate the file in the search bar on the left side. +* Ctrl+Shift+T: Navigate from a class to the test class. + +## Show variable values in IntelliJ + +1. Go to a test case (example: [`org.jabref.model.entry.BibEntryTest#settingTypeToNullThrowsException`](https://github.com/JabRef/jabref/blob/main/src/test/java/org/jabref/model/entry/BibEntryTest.java#L52-L52) +2. Set the breakpoint to the first line +3. Execute the test +4. Go to the settings of the debugger and activate "Show Variable Values in Editor" and "Show Method Return Values" +
+ Debugger Configuration +
Debugger Configuration
+
+ + diff --git a/jabref/docs/code-howtos/bibtex.md b/jabref/docs/code-howtos/bibtex.md new file mode 100644 index 00000000..1ab3f3e7 --- /dev/null +++ b/jabref/docs/code-howtos/bibtex.md @@ -0,0 +1,19 @@ +--- +parent: Code Howtos +--- +# JabRef's handling of BibTeX + +The main class to handle a single BibTeX entry is `org.jabref.model.entry.BibEntry`. +The content of a `.bib` file is handled in `org.jabref.model.database.BibDatabase`. +Things not written in the `.bib` file, but required for handling are stored in `org.jabref.model.database.BibDatabaseContext`. +For instance, this stores the mode of the library, which can be BibTeX or `biblatex`. + +Standard BibTeX fields known to JabRef are modeled in `org.jabref.model.entry.field.StandardField`. +A user-defined field not known to JabRef's code is modelled in `org.jabref.model.entry.field.UnknownField`. +Typically, to get from a String to a `Field`, one needs to use `org.jabref.model.entry.field.FieldFactory#parseField(java.lang.String)`. + +## Cross-references + +BibTeX allows for referencing other entries by the field `crossref` (`org.jabref.model.entry.field.StandardField#CROSSREF`). +Note that BibTeX and `biblatex` handle this differently. +The method `org.jabref.model.entry.BibEntry#getResolvedFieldOrAlias(org.jabref.model.entry.field.Field, org.jabref.model.database.BibDatabase)` handles this difference. diff --git a/jabref/docs/code-howtos/code-quality.md b/jabref/docs/code-howtos/code-quality.md new file mode 100644 index 00000000..e707031b --- /dev/null +++ b/jabref/docs/code-howtos/code-quality.md @@ -0,0 +1,43 @@ +--- +parent: Code Howtos +--- +# Code Quality + +## Code style checkers + +JabRef has three code style checkers in place: + +* [Checkstyle](https://checkstyle.sourceforge.io/) for basic checks, such as wrong import order. +* [Gradle Modernizer Plugin](https://github.com/andygoossens/gradle-modernizer-plugin#gradle-modernizer-plugin) for Java library usage checks. + It ensures that "modern" Java concepts are used (e.g., [one should use `Deque` instead of `Stack`](https://stackoverflow.com/a/73021741/873282)). +* [OpenRewrite](https://docs.openrewrite.org/) for advanced rules. OpenRewrite can also automatically fix issues. + JabRef's CI toolchain does NOT automatically rewrite the source code, but checks whether OpenRewrite would rewrite something. + As developer, one can execute `./gradlew rewriteRun` to fix the issues. + Note that [JabRef is available on the Moderne platform](https://app.moderne.io/organizations/JabRef/jabref?branch=main&origin=github.com), too. + +In case a check fails, [the CI](https://github.com/JabRef/jabref/blob/main/.github/workflows/tests.yml#L24C6-L24C6) automatically adds a comment on the pull request. + +## Monitoring + +We monitor the general source code quality at three places: + +* [codacy](https://www.codacy.com) is a hosted service to monitor code quality. It thereby combines the results of available open source code quality checkers such as [Checkstyle](https://checkstyle.sourceforge.io) or [PMD](https://pmd.github.io). The code quality analysis for JabRef is available at [https://app.codacy.com/gh/JabRef/jabref/dashboard](https://app.codacy.com/gh/JabRef/jabref/dashboard), especially the [list of open issues](https://app.codacy.com/gh/JabRef/jabref/issues/index). In case a rule feels wrong, it is most likely a PMD rule. The JabRef team can change the configuration at [https://app.codacy.com/p/306789/patterns/list?engine=9ed24812-b6ee-4a58-9004-0ed183c45b8f](https://app.codacy.com/p/306789/patterns/list?engine=9ed24812-b6ee-4a58-9004-0ed183c45b8f). +* [codecov](https://codecov.io) is a solution to check code coverage of test cases. The code coverage metrics for JabRef are available at [https://codecov.io/github/JabRef/jabref](https://codecov.io/github/JabRef/jabref). +* [Teamscale](https://www.cqse.eu/en/teamscale/overview/) is a popular German product analyzing code quality. The analysis results are available at . + +## Up to date dependencies + +We believe that updated dependencies are a sign of maintained code and thus an indidcator of good quality. +We use [GitHub's dependabot](https://docs.github.com/en/code-security/getting-started/dependabot-quickstart-guide) to keep our versions up to date. + +Moreover, we try to test JabRef with the latest Java Development Kit (JDK) builds. +Our results can be seen at the [Quality Outreach page](https://wiki.openjdk.org/display/quality/Quality+Outreach). + +## Background literature + +We strongly recommend reading following two books on code quality: + +* [Java by Comparison](http://java.by-comparison.com) is a book by three JabRef developers which focuses on code improvements close to single statements. It is fast to read and one gains much information from each recommendation discussed in the book. +* [Effective Java](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/) is the standard book for advanced Java programming. Did you know that `enum` is the [recommended way to enforce a singleton instance of a class](https://learning.oreilly.com/library/view/effective-java-3rd/9780134686097/ch2.xhtml#lev3)? Did you know that one should [refer to objects by their interfaces](https://learning.oreilly.com/library/view/effective-java-3rd/9780134686097/ch9.xhtml#lev64)? + +The principles we follow to ensure high code quality in JabRef is stated at our [Development Strategy](../getting-into-the-code/development-strategy.md). diff --git a/jabref/docs/code-howtos/custom-svg-icons.md b/jabref/docs/code-howtos/custom-svg-icons.md new file mode 100644 index 00000000..4a099d03 --- /dev/null +++ b/jabref/docs/code-howtos/custom-svg-icons.md @@ -0,0 +1,61 @@ +--- +parent: Code Howtos +--- +# Custom SVG icons + +JabRef uses [Material Design Icons](https://materialdesignicons.com) for most buttons on toolbars and panels. While most required icons are available, some specific ones cannot be found in the standard set, like Vim, Emacs, etc. Although custom icons might not fit the existing icons perfectly in style and level of detail, they will fit much better into JabRef than having a color pixel icon between all Material Design Icons. + +![toolbar](http://i.imgur.com/KlyYrNn.png) + +This tutorial aims to describe the process of adding missing icons created in a vector drawing tool like Adobe Illustrator and packing them into a _true type font_ (TTF) to fit seamlessly into the JabRef framework. + +The process consists of 5 steps: + +1. Download the template vector graphics from the Material Design Icons webpage. This gives you a set of existing underlying shapes that are typically used and the correct bounding boxes. You can design the missing icon based on this template and export it as an SVG file. +2. Pack the set of icons into a TTF with the help of the free IcoMoon tool. +3. Replace the existing `JabRefMaterialDesign.ttf` in the `src/main/resources/fonts` folder. +4. Adapt the class `org.jabref.gui.JabRefMaterialDesignIcon` to include all icons. +5. Adapt the class `org.jabref.gui.IconTheme` to make the new icons available in JabRef + +## Step 1. Designing the icon + +Good icon design requires years of experience and cannot be covered here. Adapting color icons with a high degree of detail to look good in the flat, one-colored setting is an even harder task. Therefore, only 3 tips: 1. Look up some tutorials on icon design, 2. reuse the provided basic shapes in the template, and 3. export your icon in the SVG format. + +## Step 2. Packing the icons into a font + +Use the [IcoMoon](https://icomoon.io) tool for packing the icons. + +1. Create a new set by importing the json file [JabRefMaterialDesign.json.zip](https://github.com/user-attachments/files/16617468/JabRefMaterialDesign.json.zip) + +2. Next to the icons, click on the hamburger menu, chose "Import to Set" to add a new icon (it will be added to the front) +Rearrange them so that they have the same order as in `org.jabref.gui.JabRefMaterialDesignIcon`. This will avoid that you have to change the code points for the existing glyphs. In the settings for your icon set, set the _Grid_ to 24. This is important to get the correct spacing. The name of the font is `JabRefMaterialDesign`. +3. Next to the icons, click on the hamburger menu and click "Select all". +4. Proceed with the font creating, set the font property name to `JabRefMaterialDesign` +When your icon-set is ready, select all of them and download the font-package. + +## Step 3. Replace the existing `JabRefMaterialDesign.ttf` + +Unpack the downloaded font-package and copy the `.ttf` file under `fonts` to `src/main/resources/fonts/JabRefMaterialDesign.ttf`. + +## Step 4. Adapt the class `org.jabref.gui.JabRefMaterialDesignIcon` + +Inside the font-package will be a CSS file that specifies which icon (glyph) is at which code point. If you have ordered them correctly, you newly designed icon(s) will be at the end and you can simply append them to `org.jabref.gui.JabRefMaterialDesignIcon`: + +```java + TEX_STUDIO("\ue900"), + TEX_MAKER("\ue901"), + EMACS("\ue902"), + OPEN_OFFICE("\ue903"), + VIM("\ue904"), + LYX("\ue905"), + WINEDT("\ue906"), + ARXIV("\ue907"); +``` + +## Step 5. Adapt the class `org.jabref.gui.IconTheme` + +If you added an icon that already existed (but not as flat Material Design Icon), then you need to change the appropriate line in `org.jabref.gui.IconTheme`, where the icon is assigned. If you created a new one, then you need to add a line. You can specify the icon like this: + +```java +APPLICATION_EMACS(JabRefMaterialDesignIcon.EMACS) +``` diff --git a/jabref/docs/code-howtos/error-handling.md b/jabref/docs/code-howtos/error-handling.md new file mode 100644 index 00000000..562a74e8 --- /dev/null +++ b/jabref/docs/code-howtos/error-handling.md @@ -0,0 +1,40 @@ +--- +parent: Code Howtos +--- +# Error Handling in JabRef + +## Throwing and Catching Exceptions + +Principles: + +* All exceptions we throw should be or extend `JabRefException`; This is especially important if the message stored in the Exception should be shown to the user. `JabRefException` has already implemented the `getLocalizedMessage()` method which should be used for such cases (see details below!). +* Catch and wrap all API exceptions (such as `IOExceptions`) and rethrow them + * Example: + + ```java + try { + // ... + } catch (IOException ioe) { + throw new JabRefException("Something went wrong...", + Localization.lang("Something went wrong...", ioe); + } + ``` + +* Never, ever throw and catch `Exception` or `Throwable` +* Errors should only be logged when they are finally caught (i.e., logged only once). See **Logging** for details. +* If the Exception message is intended to be shown to the User in the UI (see below) provide also a localizedMessage (see `JabRefException`). + +_(Rationale and further reading:_ [https://www.baeldung.com/java-exceptions](https://www.baeldung.com/java-exceptions)_)_ + +## Outputting Errors in the UI + +Principle: Error messages shown to the User should not contain technical details (e.g., underlying exceptions, or even stack traces). Instead, the message should be concise, understandable for non-programmers and localized. The technical reasons (and stack traces) for a failure should only be logged. + +To show error message two different ways are usually used in JabRef: + +* showing an error dialog +* updating the status bar at the bottom of the main window + +```text +TODO: Usage of status bar and `DialogService` +``` diff --git a/jabref/docs/code-howtos/eventbus.md b/jabref/docs/code-howtos/eventbus.md new file mode 100644 index 00000000..061b8eb7 --- /dev/null +++ b/jabref/docs/code-howtos/eventbus.md @@ -0,0 +1,70 @@ +--- +parent: Code Howtos +--- +# Event Bus and Event System + +## What the EventSystem is used for + +Many times there is a need to provide an object on many locations simultaneously. This design pattern is quite similar to Java's Observer, but it is much simpler and readable while having the same functional sense. + +## Main principle + +`EventBus` represents a communication line between multiple components. Objects can be passed through the bus and reach the listening method of another object which is registered on that `EventBus` instance. Hence, the passed object is available as a parameter in the listening method. + +## Register to the `EventBus` + +Any listening method has to be annotated with `@Subscribe` keyword and must have only one accepting parameter. Furthermore, the object which contains such listening method(s) has to be registered using the `register(Object)` method provided by `EventBus`. The listening methods can be overloaded by using different parameter types. + +## Posting an object + +`post(object)` posts an object through the `EventBus` which has been used to register the listening/subscribing methods. + +## Short example + +```java +/* Listener.java */ + +import com.google.common.eventbus.Subscribe; + +public class Listener { + + private int value = 0; + + @Subscribe + public void listen(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } +} +``` + +```java +/* Main.java */ + +import com.google.common.eventbus.EventBus; + +public class Main { + private static EventBus eventBus = new EventBus(); + + public static void main(String[] args) { + Main main = new Main(); + Listener listener = new Listener(); + eventBus.register(listener); + eventBus.post(1); // 1 represents the passed event + + // Output should be 1 + System.out.println(listener.getValue()); + } +} +``` + +## Event handling in JabRef + +The `event` package contains some specific events which occur in JabRef. + +For example: Every time an entry was added to the database a new `EntryAddedEvent` is sent through the `eventBus` which is located in `BibDatabase`. + +If you want to catch the event you'll have to register your listener class with the `registerListener(Object listener)` method in `BibDatabase`. `EntryAddedEvent` provides also methods to get the inserted `BibEntry`. diff --git a/jabref/docs/code-howtos/faq.md b/jabref/docs/code-howtos/faq.md new file mode 100644 index 00000000..e92bb62d --- /dev/null +++ b/jabref/docs/code-howtos/faq.md @@ -0,0 +1,152 @@ +--- +parent: Code Howtos +--- +# Frequently Asked Questions (FAQ) + +Following is a list of common errors encountered by developers which lead to failing tests, with their common solutions: + +## Failing tests + +### Failing Checkstyle tests + +JabRef follows a pre-defined style of code for uniformity and maintainability that must be adhered to during development. To set up warnings and auto-fixes conforming to these style rules in your IDE, follow [Step 3](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.html) of the process to set up a local workspace in the documentation. Ideally, follow all the [set up rules](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/) in the documentation end-to-end to avoid typical set-up errors.
Note: The steps provided in the documentation are for IntelliJ, which is the preferred IDE for Java development. The `checkstyle.xml` is also available for VSCode, in the same directory as mentioned in the steps. + +### Failing OpenRewrite tests + +Execute the Gradle task `rewriteRun` from the `rewrite` group of the Gradle Tool window in IntelliJ to apply the automated refactoring and pass the test:
+![Executing Gradle task rewriteRun](../images/rewriteRun.png) + +Background: [OpenRewrite](https://docs.openrewrite.org/) is an automated refactoring ecosystem for source code. + +### `org.jabref.logic.l10n.LocalizationConsistencyTest findMissingLocalizationKeys` FAILED + +You have probably used Strings that are visible on the UI (to the user) but not wrapped them using `Localization.lang(...)` and added them to the [localization properties file](https://github.com/JabRef/jabref/blob/main/src/main/resources/l10n/JabRef_en.properties). + +Read more about the background and format of localization in JabRef [here](https://devdocs.jabref.org/code-howtos/localization.html). + +### `org.jabref.logic.l10n.LocalizationConsistencyTest findObsoleteLocalizationKeys` FAILED + +Navigate to the unused key-value pairs in the file and remove them. +You can always click on the details of the failing test to pinpoint which keys are unused. + +Background: There are localization keys in the [localization properties file](https://github.com/JabRef/jabref/blob/main/src/main/resources/l10n/JabRef_en.properties) that are not used in the code, probably due to the removal of existing code. +Read more about the background and format of localization in JabRef [here](https://devdocs.jabref.org/code-howtos/localization.html). + +### `org.jabref.logic.citationstyle.CitationStyle discoverCitationStyles` ERROR: Could not find any citation style. Tried with /ieee.csl. + +Check the directory `src/main/resources/csl-styles`. +If it is missing or empty, run `git submodule update`. +Now, check inside if `ieee.csl` exists. +If it does not, run `git reset --hard` **inside that directory**. + +### `java.lang.IllegalArgumentException`: Unable to load locale en-US ERROR: Could not generate BibEntry citation. The CSL engine could not create a preview for your item. + +Check the directory `src/main/resources/csl-locales`. +If it is missing or empty, run `git submodule update`. +If still not fixed, run `git reset --hard` **inside that directory**. + +### `org.jabref.architecture.MainArchitectureTest restrictStandardStreams` FAILED + +Check if you've used `System.out.println(...)` (the standard output stream) to log anything into the console. +This is an architectural violation, as you should use the Logger instead for logging. +More details on how to log can be found [here](https://devdocs.jabref.org/code-howtos/logging.html). + +### `org.jabref.architecture.MainArchitectureTest doNotUseLogicInModel` FAILED + +One common case when this test fails is when you put any class purely containing business logic inside the `model` package (i.e., inside the directory `org/jabref/model/`). +To fix this, shift the class to a sub-package within the `logic` package (i.e., the directory`org/jabref/logic/`). +An efficient way to do this is to use IntelliJ's built-in refactoring capabilities - right-click on the file, go to "Refactor" and use "Move Class". +The import statement for all the classes using this class will be automatically adjusted according to the new location.
+![Moving a file using refactor](../images/refactor-moving.png)
+ +More information on the architecture can be found at [../getting-into-the-code/high-level-documentation.md](High-level documentation). + +### `Check external href links in the documentation / lychee (push)` FAILED + +This test is triggered when any kind of documentation is touched (be it the JabRef docs, or JavaDoc in code). If you changed something in the documentation, and particularly added/changed any links (to external files or websites), check if the links are correct and working. If you didn't change/add any link, or added correct links, the test is most probably failing due to any of the existing links being broken, and thus can be ignored (in the context of your contribution). + +### Failing Fetcher tests + +Fetcher tests are run when any file in the `.../fetcher` directory has been touched. If you have changed any fetcher logic, check if the changes are correct. You can look for more details on how to locally run fetcher tests [here](https://devdocs.jabref.org/code-howtos/testing.html#fetchers-in-tests). +Otherwise, since these tests depend on remote services, their failure can also be caused by the network or an external server, and thus can be ignored in the context of your contribution. For more information, you can look [here](https://devdocs.jabref.org/code-howtos/fetchers.html#committing-and-pushing-changes-to-fetcher-files). + +## Gradle outputs + +### `ANTLR Tool version 4.12.0 used for code generation does not match the current runtime version 4.13.1` + +Execute the Gradle task `clean` from the `build` group of the Gradle Tool Window in IntelliJ:
+![Executing Gradle task clean](../images/clean.png)
+ +### `BstVMVisitor.java:157: error: package BstParser does not exist` + +Execute gradle task `clean` from the `build` group of the Gradle Tool Window in IntelliJ. + +### `No test candidates found` + +You probably chose the wrong gradle task: + +![Gradle tests](../images/gradle-tests.png)
+ +## Submodules + +### The problem + +Sometimes, when contributing to JabRef, you may see `abbrv.jabref.org` or `csl-styles` or `csl-locales` among the changed files in your pull request. This means that you have accidentally committed your local submodules into the branch. + +![Changed submodules](../images/submodules.png) + +### Context + +JabRef needs external submodules (such as CSL style files) for some of its respective features. These are cloned once when you set up a local development environment, using `--recurse-submodules` (you may have noticed). These submodules, in the main branch, are automatically periodically updated but not fetched into local again when you pull, as they are set to be ignored in `.gitmodules` (this is to avoid merge conflicts). So when remote has updated submodules, and your local has the old ones, when you stage all files, these changes are noticed. +What's strange (mostly an IntelliJ bug): Regardless of CLI or GUI, These changes should ideally not be noticed on staging, as per the `.gitmodules` configuration. However, that is somehow overruled when using IntelliJ's CLI. + +### Fix + +For `csl-styles`: + +```bash +git merge origin/main +git checkout main -- src/main/resources/csl-styles +... git commit ... +git push +``` + +And similarly for `csl-locales` or `abbrv.jabref.org`. + +#### Alternative method (if the above doesn't work) + +1. Edit `.gitmodules`: comment out `ignore = all` (for the respective submodules you are trying to reset) + + ```gitignore + # ignore = all + ``` + +2. `cd` into the changed submodules directory (lets say `csl-styles` was changed): + + ```bash + cd src/main/resources/csl-styles + ``` + +3. Find the latest submodule commit id from remote (github): + + ![Submodule commits](../images/submodule-commit.png) + + Here, in the case of `csl-styles`, it is `4e0902d`. + +4. Checkout the commit: + + ```bash + git checkout 4e0902d + ``` + +5. Now, IntelliJ's commit tab will notice that the submodules have been modified. This means we are on the right track. + +6. Use IntelliJ's git manager (commit tab) or `git gui` to commit submodule changes only. Repeat steps 2-5 for other submodules that are shown as modified in the PR. Then, push these changes. + +7. Revert the changes in `.gitmodules` (that you made in step 1). + +### Prevention + +To avoid this, avoid staging using `git add .` from CLI. Preferably use a GUI-based git manager, such as the one built in IntelliJ or open git gui from the command line. Even if you accidentally stage them, don't commit all files, selectively commit the files you touched using the GUI based tool, and push. + + diff --git a/jabref/docs/code-howtos/fetchers.md b/jabref/docs/code-howtos/fetchers.md new file mode 100644 index 00000000..e10c800b --- /dev/null +++ b/jabref/docs/code-howtos/fetchers.md @@ -0,0 +1,98 @@ +--- +parent: Code Howtos +--- +# Fetchers + +Fetchers are the implementation of the [search using online services](https://docs.jabref.org/collect/import-using-online-bibliographic-database). Some fetchers require API keys to get them working. To get the fetchers running in a JabRef development setup, the keys need to be placed in the respective environment variable. The following table lists the respective fetchers, where to get the key from and the environment variable where the key has to be placed. + +| Service | Key Source | Environment Variable | Rate Limit | +|:--------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------------------------|----------------------------------| +| [IEEEXplore](https://docs.jabref.org/collect/import-using-online-bibliographic-database#ieeexplore) | [IEEE Xplore API portal](https://developer.ieee.org) | `IEEEAPIKey` | 200 calls/day | +| [MathSciNet](http://www.ams.org/mathscinet) | (none) | (none) | Depending on the current network | +| [SAO/NASA Astrophysics Data System](https://docs.jabref.org/collect/import-using-online-bibliographic-database#sao-nasa-astrophysics-data-system) | [ADS UI](https://ui.adsabs.harvard.edu/user/settings/token) | `AstrophysicsDataSystemAPIKey` | 5000 calls/day | +| [ScienceDirect](https://www.sciencedirect.com) | | `ScienceDirectApiKey` | | +| [SemanticScholar](https://www.semanticscholar.org/) | | `SemanticScholarApiKey` | | +| [Springer Nature](https://docs.jabref.org/collect/import-using-online-bibliographic-database#springer) | [Springer Nature API Portal](https://dev.springernature.com) | `SpringerNatureAPIKey` | 5000 calls/day | +| [Zentralblatt Math](https://www.zbmath.org) | (none) | (none) | Depending on the current network | +| [Biodiversity Heritage Library](https://www.biodiversitylibrary.org/) | [Biodiversitylibrary](https://about.biodiversitylibrary.org/tools-and-services/developer-and-data-tools/#APIs) | `BiodiversityHeritageApiKey` | - | + +"Depending on the current network" means that it depends on whether your request is routed through a network having paid access. For instance, some universities have subscriptions to MathSciNet. + +On Windows, you have to log off and log on to let IntelliJ know about the environment variable change. Execute the gradle task `processResources` in the group "others" within IntelliJ to ensure the values have been correctly written. Now, the fetcher tests should run without issues. + +JabRef supports different kinds of fetchers: + +* `EntryBasedFetcher`: Completes an existing bibliographic entry with information retrieved by the fetcher +* `FulltextFetcher`: Searches for a PDF for an exiting bibliography entry +* `SearchBasedFetcher`: Searches providers using a given query and returns a set of (new) bibliography entry. The user-facing side is implemented in the UI described at [https://docs.jabref.org/collect/import-using-online-bibliographic-database](https://docs.jabref.org/collect/import-using-online-bibliographic-database). + +There are more fetchers supported by JabRef. Investigate the package `org.jabref.logic.importer`. Another possibility is to investigate the inheritance relation of `WebFetcher` (Ctrl+H in IntelliJ). + +## Fulltext Fetchers + +* all fulltext fetchers run in parallel +* the result with the highest priority wins +* `InterruptedException` | `ExecutionException` | `CancellationException` are ignored + +### Trust Levels + +* `SOURCE` (highest): definitive URL for a particular paper +* `PUBLISHER`: any publisher library +* `PREPRINT`: any preprint library that might include non final publications of a paper +* `META_SEARCH`: meta search engines +* `UNKNOWN` (lowest): anything else not fitting the above categories + +### Current trust levels + +All fetchers are contained in the package `org.jabref.logic.importer.fetcher`. Here we list the trust levels of some of them: + +* DOI: SOURCE, as the DOI is always forwarded to the correct publisher page for the paper +* ScienceDirect: Publisher +* Springer: Publisher +* ACS: Publisher +* IEEE: Publisher +* Google Scholar: META\_SEARCH, because it is a search engine +* Arxiv: PREPRINT, because preprints are published there +* OpenAccessDOI: META\_SEARCH + +Reasoning: + +* A DOI uniquely identifies a paper. Per definition, a DOI leads to the right paper. Everything else is good guessing. +* We assume the DOI resolution surely points to the correct paper and that publisher fetches may have errors: For instance, a title of a paper may lead to different publications of it. One the conference version, the other the journal version. --> the PDF could be chosen randomly + +Code was first introduced at [PR#3882](https://github.com/JabRef/jabref/pull/3882). + +## Background on embedding the keys in JabRef + +The keys are placed into the `build.properties` file. + +```groovy +springerNatureAPIKey=${springerNatureAPIKey} +``` + +In `build.gradle`, these variables are filled: + +```groovy +"springerNatureAPIKey" : System.getenv('SpringerNatureAPIKey') +``` + +The `BuildInfo` class reads from that file and the key needs to be put into the map of default API keys in `JabRefCliPreferences::getDefaultFetcherKeys`. + +```java +keys.put(SpringerFetcher.FETCHER_NAME, buildInfo.springerNatureAPIKey); +``` + +The fetcher api key can then be obtained by calling the preferences. + +```java +importerPreferences.getApiKey(SpringerFetcher.FETCHER_NAME); +``` + +When executing `./gradlew run`, gradle executes `processResources` and populates `build/build.properties` accordingly. However, when working directly in the IDE, Eclipse keeps reading `build.properties` from `src/main/resources`. In IntelliJ, the task `JabRef Main` is executing `./gradlew processResources` before running JabRef from the IDE to ensure the `build.properties` is properly populated. + +## Committing and pushing changes to fetcher files + +Fetcher tests are run when a PR contains changes touching any file in the `src/main/java/org/jabref/logic/importer/fetcher/` directory. +Since these tests rely on remote services, some of them may fail due to the network or the external server. + +To learn more about doing fetcher tests locally, see Fetchers in tests in [Testing](https://devdocs.jabref.org/code-howtos/testing.html). diff --git a/jabref/docs/code-howtos/http-server.md b/jabref/docs/code-howtos/http-server.md new file mode 100644 index 00000000..295c02aa --- /dev/null +++ b/jabref/docs/code-howtos/http-server.md @@ -0,0 +1,77 @@ +--- +parent: Code Howtos +--- +# HTTP Server + +JabRef has a built-in http server. +For example, the resource for a library is implemented at [`org.jabref.http.server.LibraryResource`](https://github.com/JabRef/jabref/blob/main/src/main/java/org/jabref/http/server/LibraryResource.java). + +## Start http server + +The class starting the server is `org.jabref.http.server.Server`. + +Test files to server can be passed as arguments. +If no files are passed, the last opened files are served. +If that list is also empty, the file `src/main/resources/org/jabref/http/server/http-server-demo.bib` is served. + +### Starting with gradle + +Does not work. + +Current try: + +```shell +./gradlew run -Pcomment=httpserver +``` + +However, there are with `ForkJoin` (discussion at ) + +Gradle output: + +```shell +> Task :run +2023-04-22 11:30:59 [main] org.jabref.http.server.Server.main() +DEBUG: Libraries served: [C:\git-repositories\jabref-all\jabref\src\main\resources\org\jabref\http\server\http-server-demo.bib] +2023-04-22 11:30:59 [main] org.jabref.http.server.Server.startServer() +DEBUG: Starting server... +<============-> 92% EXECUTING [2m 27s] +> :run +``` + +IntelliJ output, if `org.jabref.http.server.Server#main` is executed: + +```shell +DEBUG: Starting server... +2023-04-22 11:44:59 [ForkJoinPool.commonPool-worker-1] org.glassfish.grizzly.http.server.NetworkListener.start() +INFO: Started listener bound to [localhost:6051] +2023-04-22 11:44:59 [ForkJoinPool.commonPool-worker-1] org.glassfish.grizzly.http.server.HttpServer.start() +INFO: [HttpServer] Started. +2023-04-22 11:44:59 [ForkJoinPool.commonPool-worker-1] org.jabref.http.server.Server.lambda$startServer$4() +DEBUG: Server started. +``` + +## Developing with IntelliJ + +IntelliJ Ultimate offers a Markdown-based http-client. One has to open the file `src/test/java/org/jabref/testutils/interactive/http/rest-api.http`. +Then, there are play buttons appearing for interacting with the server. + +## Get SSL Working + +When interacting with the [Microsoft Word AddIn](https://github.com/JabRef/JabRef-Word-Addin), a SSL-based connection is required. +[The Word-AddIn is currentely under development](https://github.com/JabRef/JabRef-Word-Addin/pull/568). + +(Based on ) + +Howto for Windows - other operating systems work similar: + +1. As admin `choco install mkcert` +2. As admin: `mkcert -install` +3. `cd %APPDATA%\..\local\org.jabref\jabref\ssl` +4. `mkcert -pkcs12 jabref.desktop jabref localhost 127.0.0.1 ::1` +5. Rename the file to `server.p12` + +Note: If you do not do this, you get following error message: + +```text +Could not find server key store C:\Users\USERNAME\AppData\Local\org.jabref\jabref\ssl\server.p12. +``` diff --git a/jabref/docs/code-howtos/index.md b/jabref/docs/code-howtos/index.md new file mode 100644 index 00000000..932dcb04 --- /dev/null +++ b/jabref/docs/code-howtos/index.md @@ -0,0 +1,127 @@ +--- +nav_order: 6 +has_children: true +--- +# Code Howtos + +This page provides some development support in the form of howtos. +See also [High Level Documentation](../getting-into-the-code/high-level-documentation.md). + +## Generic code how tos + +We really recommend reading the book [Java by Comparison](http://java.by-comparison.com). + +Please read . + +* try not to abbreviate names of variables, classes or methods +* use lowerCamelCase instead of snake\_case +* name enums in singular, e.g. `Weekday` instead of `Weekdays` (except if they represent flags) + +## Dependency injection + +JabRef uses a [fork](https://github.com/JabRef/afterburner.fx) of the [afterburner.fx framework](https://github.com/AdamBien/afterburner.fx) by [Adam Bien](https://adam-bien.com/). + +The main idea is to get instances by using `Injector.instantiateModelOrService(X.class)`, where `X` is the instance one needs. +The method `instantiateModelOrService` checks if there is already an instance of the given class. If yes, it returns it. If not, it creates a new one. +A singleton can be added by `com.airhacks.afterburner.injection.Injector#setModelOrService(X.class, y)`, where X is the class and y the instance you want to inject. + +## Cleanup and Formatters + +We try to build a cleanup mechanism based on formatters. The idea is that we can register these actions in arbitrary places, e.g., onSave, onImport, onExport, cleanup, etc. and apply them to different fields. The formatters themselves are independent of any logic and therefore easy to test. + +Example: [NormalizePagesFormatter](https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/formatter/bibtexfields/NormalizePagesFormatter.java) + +## Drag and Drop + +Drag and Drop makes usage of the Dragboard. For JavaFX the following [tutorial](https://docs.oracle.com/javafx/2/drag_drop/jfxpub-drag_drop.htm) is helpful. Note that the data has to be serializable which is put on the dragboard. For drag and drop of Bib-entries between the maintable and the groups panel, a custom Dragboard is used, `CustomLocalDragboard` which is a generic alternative to the system one. + +For accessing or putting data into the Clipboard use the `ClipboardManager`. + +## Get the JabRef frame panel + +`JabRefFrame` and `BasePanel` are the two main classes. You should never directly call them, instead pass them as parameters to the class. + +## Get Absolute Filename or Path for file in File directory + +JabRef stores files relative to one of [multiple possible directories](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#directories-for-files). +The convert the relative path to an absolute one, there is the `find` method in `FileUtil`: + +```java +org.jabref.logic.util.io.FileUtil.find(org.jabref.model.database.BibDatabaseContext, java.lang.String, org.jabref.logic.FilePreferences) +``` + +`String path` Can be the files name or a relative path to it. The Preferences should only be directly accessed in the GUI. For the usage in logic pass them as parameter + +## Get a relative filename (or path) for a file + +[JabRef offers multiple directories per library to store a file.](https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#directories-for-files). +When adding a file to a library, the path should be stored relative to "the best matching" directory of these. +This is implemented in `FileUtil`: + +```java +org.jabref.logic.util.io.FileUtil.relativize(java.nio.file.Path, org.jabref.model.database.BibDatabaseContext, org.jabref.logic.FilePreferences) +``` + +## Setting a Directory for a .bib File + +* `@comment{jabref-meta: fileDirectory:` +* “fileDirectory” is determined by Globals.pref.get(“userFileDir”) (which defaults to “fileDirectory” +* There is also “fileDirectory-\”, which is determined by Globals.prefs.get(“userFileDirIndividual”) +* Used at DatabasePropertiesDialog + +## How to work with Preferences + +`model` and `logic` must not know `JabRefPreferences`. See `ProxyPreferences` for encapsulated preferences and [https://github.com/JabRef/jabref/pull/658](https://github.com/JabRef/jabref/pull/658) for a detailed discussion. + +See [https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/preferences/TimestampPreferences.java](https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/preferences/TimestampPreferences.java) (via [https://github.com/JabRef/jabref/pull/3092](https://github.com/JabRef/jabref/pull/3092)) for the current way how to deal with preferences. + +Defaults should go into the model package. See [Comments in this Commit](https://github.com/JabRef/jabref/commit/2f553e6557bddf7753b618b0f4edcaa6e873f719#commitcomment-15779484) + +## UI + +Global variables should be avoided. Try to pass them as dependency. + +## "Special Fields" + +### keywords sync + +`Database.addDatabaseChangeListener` does not work as the `DatabaseChangedEvent` does not provide the field information. +Therefore, we have to use `BibtexEntry.addPropertyChangeListener(VetoableChangeListener listener)`. + +## Working with BibTeX data + +### Working with authors + +You can normalize the authors using `org.jabref.model.entry.AuthorList.fixAuthor_firstNameFirst(String)`. Then the authors always look nice. The only alternative containing all data of the names is `org.jabref.model.entry.AuthorList.fixAuthor_lastNameFirst(String)`. The other `fix...` methods omit data (like the "von" parts or the junior information). + +## Benchmarks + +* Benchmarks can be executed by running the `jmh` gradle task (this functionality uses the [JMH Gradle plugin](https://github.com/melix/jmh-gradle-plugin)) +* Best practices: + * Read test input from `@State` objects + * Return result of calculations (either explicitly or via a `BlackHole` object) +* [List of examples](https://github.com/melix/jmh-gradle-example/tree/master/src/jmh/java/org/openjdk/jmh/samples) + +## Measure performance + +Try out the [YourKit Java Profiler](https://www.yourkit.com). + +## equals + +When creating an `equals` method follow: + +1. Use the `==` operator to check if the argument is a reference to this object. If so, return `true`. +2. Use the `instanceof` operator to check if the argument has the correct type. If not, return `false`. +3. Cast the argument to the correct type. +4. For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object. If all these tests succeed, return `true` otherwise, return `false`. +5. When you are finished writing your `equals` method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent? + +Also, note: + +* Always override `hashCode` when you override equals (`hashCode` also has very strict rules) (Item 9 of[Effective Java](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/)) +* Don’t try to be too clever +* Don’t substitute another type for `Object` in the equals declaration + +## Files and Paths + +Always try to use the methods from the nio-package. For interoperability, they provide methods to convert between file and path. [https://docs.oracle.com/javase/tutorial/essential/io/path.html](https://docs.oracle.com/javase/tutorial/essential/io/path.html) Mapping between old methods and new methods [https://docs.oracle.com/javase/tutorial/essential/io/legacy.html#mapping](https://docs.oracle.com/javase/tutorial/essential/io/legacy.html#mapping) diff --git a/jabref/docs/code-howtos/intellij-debugger-configuration.png b/jabref/docs/code-howtos/intellij-debugger-configuration.png new file mode 100644 index 00000000..b0b2a4ed Binary files /dev/null and b/jabref/docs/code-howtos/intellij-debugger-configuration.png differ diff --git a/jabref/docs/code-howtos/javafx.md b/jabref/docs/code-howtos/javafx.md new file mode 100644 index 00000000..f2aab28e --- /dev/null +++ b/jabref/docs/code-howtos/javafx.md @@ -0,0 +1,219 @@ +--- +parent: Code Howtos +--- +# JavaFX + +> [JavaFX](https://github.com/openjdk/jfx?tab=readme-ov-file#openjfx) is an open source, next generation client application platform for desktop, mobile and embedded systems based on JavaSE. +> It is a collaborative effort by many individuals and companies with the goal of producing a modern, efficient, and fully featured toolkit for developing rich client applications. + +JavaFX is used on JabRef for the user interface. + +## Resources + +* [JavaFX Documentation project](https://fxdocs.github.io/docs/html5/index.html): Collected information on JavaFX in a central place +* [curated list of awesome JavaFX frameworks, libraries, books and etc...](https://github.com/mhrimaz/AwesomeJavaFX?tab=readme-ov-file#awesome-javafx-) +* [FXTutorials](https://github.com/AlmasB/FXTutorials?tab=readme-ov-file#fxtutorials) A wide range of practical tutorials focusing on Java, JavaFX and FXGL +* [ControlsFX](http://fxexperience.com/controlsfx/features/) amazing collection of controls +* [CSS Reference](http://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html) +* [mvvm framework](https://github.com/sialcasa/mvvmFX/wiki) +* [Validation framework](https://github.com/sialcasa/mvvmFX/wiki/Validation) +* [additional bindings](https://github.com/lestard/advanced-bindings) or [EasyBind](https://github.com/TomasMikula/EasyBind) +* [Undo manager](https://github.com/FXMisc/UndoFX) +* [Docking manager](https://github.com/alexbodogit/AnchorFX) or [DockFX](https://github.com/RobertBColton/DockFX) +* [Kubed](https://github.com/hudsonb/kubed): data visualization (inspired by d3) +* [Foojay](https://foojay.io) Java and JavaFX tutorials + +### Resources of historical interest + +* [FXExperience](http://fxexperience.com) JavaFX Links of the week + +## Architecture: Model - View - (Controller) - ViewModel (MV(C)VM) + +The goal of the MVVM architecture is to separate the state/behavior from the appearance of the ui. This is archived by dividing JabRef into different layers, each having a clear responsibility. + +* The _Model_ contains the business logic and data structures. These aspects are again encapsulated in the _logic_ and _model_ package, respectively. +* The _View_ controls the appearance and structure of the UI. It is usually defined in a _FXML_ file. +* _View model_ converts the data from logic and model in a form that is easily usable in the gui. Thus it controls the state of the View. Moreover, the ViewModel contains all the logic needed to change the current state of the UI or perform an action. These actions are usually passed down to the _logic_ package, after some data validation. The important aspect is that the ViewModel contains all the ui-related logic but does _not_ have direct access to the controls defined in the View. Hence, the ViewModel can easily be tested by unit tests. +* The _Controller_ initializes the view model and binds it to the view. In an ideal world all the binding would already be done directly in the FXML. But JavaFX's binding expressions are not yet powerful enough to accomplish this. It is important to keep in mind that the Controller should be as minimalistic as possible. Especially one should resist the temptation to validate inputs in the controller. The ViewModel should handle data validation! It is often convenient to load the FXML file directly from the controller. + +The only class which access model and logic classes is the ViewModel. Controller and View have only access the ViewModel and never the backend. The ViewModel does not know the Controller or View. + +More details about the MVVM pattern can be found in [an article by Microsoft](https://msdn.microsoft.com/en-us/magazine/dd419663.aspx) and in [an article focusing on the implementation with JavaFX](https://web.archive.org/web/20140825151304/http://blog.buildpath.de/javafx-decouple-the-view-and-its-behavior-to-create-a-testable-ui/). + +### Example + +#### ViewModel + +* The ViewModel should derive from `AbstractViewModel` + +```java +public class MyDialogViewModel extends AbstractViewModel { +} +``` + +* Add a (readonly) property as a private field and generate the getters according to the [JavaFX bean conventions](https://docs.oracle.com/javafx/2/binding/jfxpub-binding.htm): + +```java +private final ReadOnlyStringWrapper heading = new ReadOnlyStringWrapper(); + +public ReadOnlyStringProperty headingProperty() { + return heading.getReadOnlyProperty(); +} + +public String getHeading() { + return heading.get(); +} +``` + +* Create constructor which initializes the fields to their default values. Write tests to ensure that everything works as expected! + +```java +public MyDialogViewModel(Dependency dependency) { + this.dependency = Objects.requireNonNull(dependency); + heading.set("Hello " + dependency.getUserName()); +} +``` + +* Add methods which allow interaction. Again, don't forget to write tests! + +```java +public void shutdown() { + heading.set("Goodbye!"); +} +``` + +#### View - Controller + +* The "code-behind" part of the view, which binds the `View` to the `ViewModel`. +* The usual convention is that the controller ends on the suffix `*View`. Dialogs should derive from `BaseDialog`. + +```java +public class AboutDialogView extends BaseDialog +``` + +* You get access to nodes in the FXML file by declaring them with the `@FXML` annotation. + +```java +@FXML protected Button helloButton; +@FXML protected ImageView iconImage; +``` + +* Dependencies can easily be injected into the controller using the `@Inject` annotation. + +```java +@Inject private DialogService dialogService; +``` + +* It is convenient to load the FXML-view directly from the controller class. + + The FXML file is loaded using `ViewLoader` based on the name of the class passed to `view`. To make this convention-over-configuration approach work, both the FXML file and the View class should have the same name and should be located in the same package. + + Note that fields annotated with `@FXML` or `@Inject` only become accessible after `ViewLoader.load()` is called. + + a `View` class that loads the FXML file. + +```java +private Dependency dependency; + +public AboutDialogView(Dependency dependency) { + this.dependency = dependency; + + this.setTitle(Localization.lang("About JabRef")); + + ViewLoader.view(this) + .load() + .setAsDialogPane(this); +} +``` + +* Dialogs should use [setResultConverter](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Dialog.html#setResultConverter-javafx.util.Callback-) to convert the data entered in the dialog to the desired result. This conversion should be done by the view model and not the controller. + +```java +setResultConverter(button -> { + if (button == ButtonType.OK) { + return viewModel.getData(); + } + return null; +}); +``` + +* The initialize method may use data-binding to connect the ui-controls and the `ViewModel`. However, it is recommended to do as much binding as possible directly in the FXML-file. + +```java +@FXML +private void initialize() { + viewModel = new AboutDialogViewModel(dialogService, dependency, ...); + + helloLabel.textProperty().bind(viewModel.helloMessageProperty()); +} +``` + +* calling the view model: + +```java +@FXML +private void openJabrefWebsite() { + viewModel.openJabrefWebsite(); +} +``` + +#### View - FXML + +The view consists a FXML file `MyDialog.fxml` which defines the structure and the layout of the UI. Moreover, the FXML file may be accompanied by a style file that should have the same name as the FXML file but with a `css` ending, e.g., `MyDialog.css`. It is recommended to use a graphical design tools like [SceneBuilder](http://gluonhq.com/labs/scene-builder/) to edit the FXML file. The tool [Scenic View](https://github.com/JonathanGiles/scenic-view) is very helpful in debugging styling issues. + +## FXML + +The following expressions can be used in FXML attributes, according to the [official documentation](https://docs.oracle.com/javase/8/javafx/api/javafx/fxml/doc-files/introduction_to_fxml.html#attributes) + +| Type | Expression | Value point to | Remark | +| -------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| Location | `@image.png` | path relative to the current FXML file | | +| Resource | `%textToBeTranslated` | key in ResourceBundle | | +| Attribute variable | `$idOfControl` or `$variable` | named control or variable in controller (may be path in the namespace) | resolved only once at load time | +| Expression binding | `${expression}` | expression, for example `textField.text` | changes to source are propagated | +| Bidirectional expression binding | `#{expression}` | expression | changes are propagated in both directions (not yet implemented in JavaFX, see [feature request](https://bugs.openjdk.java.net/browse/JDK-8090665)) | +| Event handler | `#nameOfEventHandler` | name of the event handler method in the controller | | +| Constant | `` | constant (here `MYSTRING` in the `Strings` class) | | + +## JavaFX Radio Buttons Example + +All radio buttons that should be grouped together need to have a ToggleGroup defined in the FXML code Example: + +```markup + + + + + + + + + +``` + +## JavaFX Dialogs + +All dialogs should be displayed to the user via `DialogService` interface methods. `DialogService` provides methods to display various dialogs (including custom ones) to the user. It also ensures the displayed dialog opens on the correct window via `initOwner()` (for cases where the user has multiple screens). The following code snippet demonstrates how a custom dialog is displayed to the user: + +```java +dialogService.showCustomDialog(new DocumentViewerView()); +``` + +If an instance of `DialogService` is unavailable within current class/scope in which the dialog needs to be displayed, `DialogService` can be instantiated via the code snippet shown as follows: + +```java +DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); +``` + +## Properties and Bindings + +JabRef makes heavy use of Properties and Bindings. These are wrappers around Observables. A good explanation on the concept can be found here: +[JavaFX Bindings and Properties](https://edencoding.com/javafx-properties-and-binding-a-complete-guide/) + +## Features missing in JavaFX + +* bidirectional binding in FXML, see [official feature request](https://bugs.openjdk.java.net/browse/JDK-8090665) diff --git a/jabref/docs/code-howtos/jpackage.md b/jabref/docs/code-howtos/jpackage.md new file mode 100644 index 00000000..574b500e --- /dev/null +++ b/jabref/docs/code-howtos/jpackage.md @@ -0,0 +1,39 @@ +--- +parent: Code Howtos +--- +# JPackage: Creating a binary and debug it + +JabRef uses [jpackage](https://docs.oracle.com/en/java/javase/14/jpackage/) to build binary application bundles and installers for Windows, Linux, and macOS. For Gradle, we use the [Badass JLink Plugin](https://badass-jlink-plugin.beryx.org/releases/latest/). + +## Build Windows binaries locally + +Preparation: Install [WiX Toolset](https://wixtoolset.org) + +1. Open administrative shell +2. Use [Chocolatey](https://chocolatey.org) to install it: `choco install wixtoolset` + +Create the application image: + +`./gradlew -PprojVersion="5.0.50013" -PprojVersionInfo="5.0-ci.13--2020-03-05--c8e5924" jpackageImage` + +Create the installer: + +`./gradlew -PprojVersion="5.0.50013" -PprojVersionInfo="5.0-ci.13--2020-03-05--c8e5924" jpackage` + +## Debugging jpackage installations + +Sometimes issues with modularity only arise in the installed version and do not occur if you run from source. Using remote debugging, it's still possible to hook your IDE into the running JabRef application to enable debugging. + +### Debugging on Windows + +1. Open `build.gradle`, under jlink options remove `--strip-debug` +2. Build using `jpackageImage` (or let the CI build a new version) +3. Modify the `build\image\JabRef\runtime\bin\Jabref.bat` file, replace the last line with + + ```shell + pushd %DIR% & %JAVA_EXEC% -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n -p "%~dp0/../app" -m org.jabref/org.jabref.Launcher %* & popd + ``` + +4. Open your IDE and add a "Remote Debugging Configuration" for `localhost:8000` +5. Start JabRef by running the above bat file +6. Connect with your IDE using remote debugging diff --git a/jabref/docs/code-howtos/localization.md b/jabref/docs/code-howtos/localization.md new file mode 100644 index 00000000..7ef71486 --- /dev/null +++ b/jabref/docs/code-howtos/localization.md @@ -0,0 +1,73 @@ +--- +parent: Code Howtos +--- +# Localization + +More information about this topic from the translator side is provided at [Translating JabRef Interface](https://docs.jabref.org/faqcontributing/how-to-translate-the-ui). + +All labeled UI elements, descriptions and messages shown to the user should be localized, i.e., should be displayed in the chosen language. + +[JabRef uses ResourceBundles](https://github.com/JabRef/jabref/blob/4b41108107fb92cc0a8acfcb834ccbb0b6e79ae5/src/main/resources/l10n/JabRef_en.properties) ([see Oracle Tutorial](https://docs.oracle.com/javase/tutorial/i18n/resbundle/concept.html)) to store `key=value` pairs for each String to be localized. + +## Localization in Java code + +To show a localized String the following `org.jabref.logic.l10n.Localization` has to be used. The Class currently provides three methods to obtain translated strings: + +```java + public static String lang(String key); + + public static String lang(String key, String... params); + + public static String menuTitle(String key, String... params); +``` + +The actual usage might look like: + +```java + Localization.lang("Get me a translated String"); + Localization.lang("Using %0 or more %1 is also possible", "one", "parameter"); + Localization.menuTitle("Used for Menus only"); +``` + +## Localization in FXML + +To write a localized string in FXML file, prepend it with `%`, like in this code: + +```xml + + +``` + +## General hints + +* Use the String you want to localize directly, do not use members or local variables: `Localization.lang("Translate me");` instead of `Localization.lang(someVariable)` (possibly in the form `someVariable = Localization.lang("Translate me")` +* Use `%x`-variables where appropriate: `Localization.lang("Exported %0 entry(s).", number)` instead of `Localization.lang("Exported ") + number + Localization.lang(" entry(s).");` +* Use a full stop/period (".") to end full sentences +* For pluralization, use a combined form. E.g., `Localization.lang("checked %0 entry(s)")`. + +## Checking for correctness + +The tests in `org.jabref.logic.l10n.LocalizationConsistencyTest` check whether translation strings appear correctly in the resource bundles. + +## Adding a new key + +1. Add new `Localization.lang("KEY")` to Java file. Run the `org.jabref.logic.LocalizationConsistencyTest`. +2. Tests fail. In the test output a snippet is generated which must be added to the English translation file. +3. Add snippet to English translation file located at `src/main/resources/l10n/JabRef_en.properties` +4. Please do not add translations for other languages directly in the properties. They will be overwritten by [Crowdin](https://crowdin.com/project/jabref) + +## Adding a new Language + +1. Add the new Language to the Language enum in [https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/l10n/Language.java](https://github.com/JabRef/jabref/blob/master/src/main/java/org/jabref/logic/l10n/Language.java) +2. Create an empty \.properties file +3. Configure the new language in [Crowdin](https://crowdin.com/project/jabref) + +If the language is a variant of a language `zh_CN` or `pt_BR` it is necessary to add a language mapping for Crowdin to the crowdin.yml file in the root. Of course the properties file also has to be named according to the language code and locale. + +## Background information + +The localization is tested via the class [LocalizationConsistencyTest](https://github.com/JabRef/jabref/blob/main/src/test/java/org/jabref/logic/l10n/LocalizationConsistencyTest.java). diff --git a/jabref/docs/code-howtos/logging.md b/jabref/docs/code-howtos/logging.md new file mode 100644 index 00000000..4503d073 --- /dev/null +++ b/jabref/docs/code-howtos/logging.md @@ -0,0 +1,47 @@ +--- +parent: Code Howtos +--- +# Logging + +JabRef uses the logging facade [SLF4j](https://www.slf4j.org). All log messages are passed internally to [tinylog](https://tinylog.org/v2/) which handles any filtering, formatting and writing of log messages. + +Obtaining a logger for a class: + +```java +private static final Logger LOGGER = LoggerFactory.getLogger(.class); +``` + +Please always use `LOGGER.debug` for debugging. + +Example: + +```java +String example = "example"; +LOGGER.debug("Some state {}", example); +``` + +Enable logging in `tinylog.properties`: + +```properties +level@org.jabref.example.ExampleClass = debug +``` + +If the logging event is caused by an exception, please add the exception to the log message as: + +```java + catch (SomeException e) { + LOGGER.warn("Warning text.", e); + ... + } +``` + +When running tests, `tinylog-test.properties` is used. +It is located under `src/test/resources`. As default, only `info` is logged. +When developing, it makes sense to use `debug` as log level. +One can change the log level per class using the pattern `level@class=debug` is set to `debug`. +In the `.properties` file, this is done for `org.jabref.model.entry.BibEntry`. + +## Further reading + +SLF4J also support parameterized logging, e.g. if you want to print out multiple arguments in a log statement use a pair of curly braces (`{}`). +Head to for examples. diff --git a/jabref/docs/code-howtos/openoffice/code-reorganization.md b/jabref/docs/code-howtos/openoffice/code-reorganization.md new file mode 100644 index 00000000..d7bd01a1 --- /dev/null +++ b/jabref/docs/code-howtos/openoffice/code-reorganization.md @@ -0,0 +1,144 @@ +--- +nav_order: 4 +parent: The LibreOffice Panel +grand_parent: Code Howtos +--- +# Code reorganization + +Why + +* Separate backend +* Separate GUI code (dialogs) and logic +* Data is now organized around `Citation`, `CitationGroup` instead of arrays for citation group fields, and arrays of arrays for citation fields.\ + Also take `citationKey` as the central data unit, this is what we start with: unresolved `citationKeys` do not stop processing. Although we cannot sort them by author and year, we can still emit a marker that acts as a placeholder and shows the user the problematic key. + +## Result + +### Layers + +![Layers](layers-v1.svg) + +### By directories + +* `model` + * `util` : general utilities + * (`OOPair`, `OOTuple3`) collect two or three objects without creating a new class + * `OOResult` : while an Optional.empty can comunicate failure, it cannot provide details.\ + `OOResult` allows an arbitrary error object to be provided in case of failure. + * `OOVoidResult` : for functions returning no result on success, only diagnostics on failure. + * `OOListUtil`: some utilities working on List + * `uno` : helpers for various tasks via UNO.\ + These are conceptually independent of JabRef code and logic. + * `ootext` : to separate decisions on the format of references and citation marks from the actual insertion into the document, the earlier method `OOUtil.insertOOFormattedTextAtCurrentLocation` was extended to handle new tags that describe actions earlier done in code. + * This became `OOTextIntoOO.write` + * `(change)` Now all output to the document goes through this, not only those from Layout. This allows the citation markers and `jstyle:Title` to use these tags. + * This allows some backward-compatible extensions to jstyle.\ + `(change)` Added some extra keywords, in `{prefix}_MARKUP_BEFORE`, `{prefix}_MARKUP_AFTER` pairs to allow bracketing some parts of citation marks with text and/or open/close tag pairs. + * `OOFormat` contains helpers to create the appropriate tags + * `OOText` formalizes the distinction from `String`. I did not change `String` to `OOText` in old code, (in particular in OOStyle). + * `rangesort` : ordering objects that have an `XTextRange`, optionally with an extra integer to break ties. + * `RangeSort.partitionAndSortRanges` : since `XTextRangeCompare` can only compare `XTextRange` values in the same `XText`, we partition them accordingly and only sort within each partition. + * `RangeSortable` (interface), `RangeSortEntry` (implements) :\ + When we replace `XTextRange` of citation marks in footnotes with the range of the footnote mark, multiple citation marks may be mapped to the same location. To preserve the order between these, `RangeSortable` allows this order to be indicated by returning appropriate indices from `getIndexInPosition` + * `RangeSortVisual` : sort in top-to-bottom left-to-right order.\ + Needs a functional `XTextViewCursor`.\ + Works on `RangeSortable` values. + * `FunctionalTextViewCursor` : helper to get a functional `XTextViewCursor` (cannot always) + * `RangeOverlapWithin` : check for overlaps within a set of `XTextRange` values. Probably O(n\*log(n)). Used for all-to-all check of protected ranges. + * `RangeOverlapBetween` : check for overlaps between two sets of `XTextRange` values. Assumes one set is small. O(n\*k). Used for checking if the cursor is in a protected range. + * `backend` : interfaces to be provided by backends.\ + May change as new backends may need different APIs. + * `style` : data structures and interfaces used while going from ordered list of citation groups to formatted citation markers and bibliography. Does not communicate with the document. Too long to fit here, starting a new section. + +## model/style + +At the core, + +* we have `Citation` values + * represented in the document by their `citationKey` + * each may have a `pageInfo` +* A citation group (`CitationGroup`) has + * a list of citations (`citationsInStorageOrder`) + * an identifier `CitationGroupId cgid` + * this allows to refer to the group + * also used to associate the group to its citation markers location (outside the style part, in `Backend52`) + * `OODataModel dataModel` is here, in order to handle old (Jabref5.2) structure where pageInfo belonged to CitationGroup not Citation + * `referenceMarkNameForLinking` is optional: can be used to crosslink to the citation marker from the bibliography. +* `CitationGroups` represents the collection of citation groups.\ + Processing starts with creating a `CitationGroups` instance from the data stored in the document. +* `CitedKey` represents a cited source, with ordered back references (using `CitationPath`) to the corresponding citations. +* `CitedKeys` is just an order-preserving collection of `CitedKeys` that also supports lookup by `citationKey`. While producing citation markers, we also create a corresponding `CitedKeys` instance, and store it in `CitationGroups.bibliography`. This is already sorted, its entries have `uniqueLetter` or `number` assigned, but not converted to markup yet. + +Common processing steps: + +* We need `globalOrder` for the citation groups (provided externally) `CitationGroups.setGlobalOrder()` +* We need to look up each citationKey in the bibliography databases: + * `CitationGroups.lookupCitations` collects the cited keys, looks up each, then distributes the results to the citations. Uses a temporary `CitedKeys` instance, based on unsorted citations and citation groups. +* `CitationGroups.imposeLocalOrder` fills `localOrder` in each `CitationGroup` +* Now we have order of appearance for the citations (`globalOrder` and `localOrder`).\ + We can create a `CitedKeys` instance (`bibliography`) according to this order. +* For citations numbered in order of first appearance we number the sources and distribute the numbers to the corresponding citations. +* For citations numbered in order of bibliography, we sort the bibliography, number, distribute. +* For author-year citations we have to decide on the letters `uniqueLetter` used to distinguish sources. This needs order of first appearance of the sources and recognizing clashing citation markers. This is done in logic, in `OOProcessAuthorYearMarkers.createUniqueLetters()` +* We also mark first appearance of each source (`setIsFirstAppearanceOfSourceInCitations`) + +The entry point for this processing is: `OOProcess.produceCitationMarkers`. + +It fills + +* each `CitationGroup.citationMarker` +* `CitationGroups.bibliography` + * From bibliography `OOFormatBibliography.formatBibliography()` creates an `OOText` ready to be written to the document. + +## logic/style + +* `StyleLoader` : not changed (knows about default styles) Used by GUI +* `OOPreFormatter` : LaTeX code to unicode and OOText tags. (not changed) +* `OOBibStyle` : is mostly concerned by loading/parsing jstyle files and presenting its pieces to the rest. Originally it also contains code to format numeric and author-year citation markers. + * Details of their new implementations are in `OOBibStyleGetNumCitationMarker` and `OOBibStyleGetCitationMarker` + * The new implementations + * support pageInfo for each citation + * support unresolved citations + * instead of `List` and (`List` plus arrays and database) they expect more self-contained entries `List`, `List`. + * We have distinct methods for `getNormalizedCitationMarker(CitationMarkerNormEntry)` and `getNumCitationMarkerForBibliography(CitationMarkerNumericBibEntry)`. + * The corresponding interfaces in model/style: + * `CitationMarkerNumericEntry` + * `CitationMarkerEntry` + * `CitationMarkerNumericBibEntry` + * `CitationMarkerNormEntry`\ + describe their expected input entries. +* `OOProcess.produceCitationMarkers` is the main entry point for style application. Calls to specific implementations in `OOProcessCitationKeyMarkers`, `OOProcessNumericMarkers` and `OOProcessAuthorYearMarkers` according to jstyle flags. + +## logic/backend + +Details of encoding and retrieving data stored in a document as well as the citation maker locations. Also contains dataModel-dependent code (which could probably be moved out once the datamodel is settled). + +Creating and finding the bibliography (providing a cursor to write at) should be here too.\ +These are currently in `UpdateBibliography` + +## logic/frontend + +* `OOFrontend` : has a `Backend` and `CitationGroups` + * Its constructor creates a backend, reads data from the document and creates a CitationGroups instance. + * provides functionality that requires both access to the document and the CitationGroups instance +* `RangeForOverlapCheck` used in `OOFrontend` +* `UpdateBibliography` : Create, find and update the bibliography in the document using output from `produceCitationMarkers()` +* `UpdateCitationMarkers` create `CitationGroup`, update citation markers using output from `produceCitationMarkers()` + +## logic/action + +GUI-independent part of implementations of GUI actions. + +## gui + +* `OOError` : common error messages and dialog titles + * adds `title` to `Jabrefexception` + * converts from some common exception types using type-specific message + * contains some dialog messages that do not correspond to exceptions +* `OOBibBase2` : most activity was moved out from here to parts discussed above. + * connecting / selecting a document moved to `OOBibBaseConnect` + * the rest connects higher parts of the GUI to actions in logic + * does argument and precondition checking + * catches all exceptions + * shows error and warning dialogs + * adds `enterUndoContext`, `leaveUndoContext` around action code diff --git a/jabref/docs/code-howtos/openoffice/index.md b/jabref/docs/code-howtos/openoffice/index.md new file mode 100644 index 00000000..a36a86fd --- /dev/null +++ b/jabref/docs/code-howtos/openoffice/index.md @@ -0,0 +1,6 @@ +--- +parent: Code Howtos +nav_order: 12 +has_children: true +--- +# The LibreOffice Panel diff --git a/jabref/docs/code-howtos/openoffice/layers-v1.svg b/jabref/docs/code-howtos/openoffice/layers-v1.svg new file mode 100644 index 00000000..f9b8239d --- /dev/null +++ b/jabref/docs/code-howtos/openoffice/layers-v1.svg @@ -0,0 +1,915 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + document content (UNO) + + frontend + + actions + + OOBibBase2 + + backend + + style + + OOTextIntoOO + + rangesort + data in doc, ranges + order ranges + fill ranges + markup text + XTextDocument + Backend, CitationGroups + + + + GUI: + BibEntry, BibDatabase, OOBibStyle + provides input in terms of these types + + provides connection to doc + Cite, Update, Merge, Separate, Manage, Export + Connect + Load Style + Create OOFrontend instance + Catch exceptions, Undo + Forward requests to actions + Check preconditions + + locations + citation keys + pageInfo + citation type + + + lookup, localOrder, number, + uniqueLetter, sort bibliography, + format citationMarkers, + format bibliography + + + or visually + within XText + + checkRangeOverlaps, checkRangeOverlapsWithCursor + connects the parts below + getVisuallySortedCitationGroups, imposeGlobalOrder + UpdateCitationMarkers, UpdateBibliography + lock screen refresh + GUI-independent part of actions + + + diff --git a/jabref/docs/code-howtos/openoffice/ooresult-ooerror/index.md b/jabref/docs/code-howtos/openoffice/ooresult-ooerror/index.md new file mode 100644 index 00000000..7d39a6c4 --- /dev/null +++ b/jabref/docs/code-howtos/openoffice/ooresult-ooerror/index.md @@ -0,0 +1,168 @@ +--- +nav_order: 5 +parent: The LibreOffice Panel +grand_parent: Code Howtos +--- +# About OOError, OOResult, and OOVoidResult + +## Context + +### Relieve GUI panel code + +On the question of where should we catch exceptions in relation to GUI code it was suggested (Jonatan Asketorp [here](https://github.com/koppor/jabref/pull/496#discussion_r629695493), "most of them (all?) should be handled latest in the ViewModel.") that catching them early could help simplifying the higher levels. + +### Same messages in different contexts + +Some types of exceptions are caught in _different GUI actions_, often resulting in basically the same error dialog, possibly only differing in the indicated context (which GUI action). + +Problems found during _precondition checking_ (for example: do we have a connection to a document) and error conditions (for example: lost connection to a document during an action) can overlap. + +### OOBibBase as a precondition and exception handling layer + +Since most of the code originally in `OOBibBase` was moved to `logic` and almost all GUI actions go through `OOBibBase`, it seemed a good location to collect precondition checking and exception handling code. + +Note: some of the precondition checking still needs to stay in `OpenOfficePanel`: for example to provide a list of selected `BibEntry` instances, it needs to go through some steps from `frame.getCurrentLibraryTab()` to `(!entries.isEmpty() && checkThatEntriesHaveKeys(entries))` + +To avoid `OOBibBase` depending on the higher level `OpenOfficePanel` message texts needed in `OOBibBase` were moved from `OpenOfficePanel` to `OOError`. (Others stayed, but could be moved if that seems worthwile) + +## OOError + +* `OOError` is a collection of data used in error dialogs. + * It is a `JabRefException` with an added field: `localizedTitle` + * It can store: a dialog title, a localized message (optionally a non-localized message as well) and a `Throwable` + * I used it in `OOBibBase` as a unified format for errors to be shown in an error dialog. + * Static constructors in `OOError` provide uniform translation from some exception types to `OOError` with the corresponding localized messages:\ + `public static OOError from(SomeException ex)`\ + There is also `public static OOError fromMisc(Exception ex)` for exception types not handled individually. (It has a different name, to avoid ambiguity) + * Another set of constructors provide messages for some preconditions.\ + For example `public static OOError noDataBaseIsOpenForCiting()` + +Some questions: + +* Should we use static data instead of static methods for the precondition-related messages? + * pro: why create a new instance for each error? + * con: `OOError.setTitle()` currently just sets `this.localizedTitle` and returns `this`. For static instances this would modify a shared resource unless we create a new copy in `setTitle`. However `setTitle` can be called repeatedly on the same object: as we bubble up, we can be more specific about the context. +* Should we remove title from `OOError`? + * pro: we almost always override its original value + * con: may need to duplicate the title in different files (preconditions for an action in OpenOfficePanel and in OOBibBase) +* Should we include `OOError.showErrorDialog` ? + * pro: since it was intended _for_ error dialogs, it is nice to provide this. + * con: the reference to `DialogService` forces it to `gui`, thus it cannot be used in `logic` or `model` +* Should we use `JabRefException` as base? + * pro: `JabRefException` is mentioned as the standard form of errors in the developers guide.\ + [All Exceptions we throw should be or extend JabRefException](https://jabref.readthedocs.io/en/latest/getting-into-the-code/code-howtos/#throwing-and-catching-exceptions) + * against: `JabRefException` is in `logic` cannot be used in model.\ + (Could this be resolved by moving `JabRefException` to `model`?) + +## OOResult + +During precondition checking + +1. some tests return no data, only report problems +2. we may need to get some resources that might not be available (for example: connection to a document, a functional textview cursor) +3. some test depend on these resources + +While concentrating on these and on "do not throw exceptions here" ... using a [Result type](https://en.wikipedia.org/wiki/Result_type) as a return value from precondition checking code seemed a good fit: + +* Instead of throwing an exception, we can return some data describing the problem. +* Conceptually it is a data structure that either holds the result (of a computation) or and error value. +* It can be considered as an extended `Optional`, that can provide details on "why empty"? +* It can be considered as an alternative to throwing an exception: we return an `error` instead. +* Methods throwing checked exceptions cannot be used with for example `List.map`.\ + Methods returning a Result could. +* `Result` shares the problem (with any other solutions) that in a function several types of errors may occur, but we can only return a single error type. Java solves this using checked exceptions being all descendants of Exception. (Also adds try/catch/catch to select cases based on the exceptions type, and some checking against forgotten cases of checked exception types) + +In `OOBibBase` I used `OOError` as the unified error type: it can store error messages and wrap exceptions. It contains everything we need for an error dialog. On the other hand it does not support programmatic dissection. + +### Implementation + +Unlike `Optional` and `List`, `Result` (in the sense used here) did not get into java standard libraries. There are some implementations of this idea for java on the net: + +* [bgerstle/result-java](https://github.com/bgerstle/result-java/) +* [MrKloan/result-type](https://github.com/MrKloan/result-type) +* [david-bakin](https://gist.github.com/david-bakin/35d55daeeaee1eb71cea) +* [vavr-try](https://www.baeldung.com/vavr-try) + +Generics allow an implementation built around + +```java +class OOResult { + private final Optional result; + private final Optional error; +} +``` + +with an assumption that at any time exactly one of `result` and `error` is present. + +> `class X { boolean isOK; Object data; }` expresses this assumption more directly, (but omits the relation between the type parameters `` and the type in `data`) + +* Since `OOResult` encodes the state `isOK` in `result.isPresent()` (and equivalently in `errror.isEmpty()`), we cannot allow construction of instances where both values are `isEmpty`.\ + In particular, `OOResult.ok(null)` and `OOResult.error(null)` are not allowed: it would make the state `isOK` ambiguous.\ + It would also break the similarity to `Optional` to allow both `isEmpty` and `isOK` to be true. +* Not allowing null, has a consequence on `OOResult`\ + According to [baeldung.com/java-void-type](https://www.baeldung.com/java-void-type), the only possible value for `Void` is `null` which we excluded. + + `OOResult.ok(null)` would look strange: in this case we need `ok()` without arguments. + +To solve this problem, I introduced + +```java +class OOVoidResult { + private final Optional error; + ... +} +``` + +with methods on the error side similar to those in `OOError`, and `OOVoidResult.ok()` to construct the success case with no data. + +### The relation between `Optional` and `OOVoidResult` + +* Both `Optional` and `OOVoidResult` can store 0 or 1 values, in this respect they are equivalent + * Actually, `OOVoidResult` is just a wrapper around an `Optional` +* In terms of communication to human readers when used, their connotation in respect to success and failure is the opposite: + * `Optional.empty()` normally suggests failure, `OOVoidResult.ok()` mean success. + * `Optional.of(something)` probably means success, `OOVoidResult.error(something)` indicates failure. + * `OOVoidResult` is "the other half" (the failure branch) of `OOResult` + * its content is accessed through `getError`, `mapError`, `ifError`, not `get`, `map`, `ifPresent` + +`OOVoidResult` allows + +* a clear distinction between success and failure when calls to "get" something that might not be available (`Optional`) and calls to precondition checking where we can only get reasons for failure (`OOVoidResult`) appear together.\ + Using `Optional` for both is possible, but is more error-prone. +* it also allows using uniform verbs (`isError`, `getError`, `ifError`, return `OO{Void}Result.error`) for "we have a problem" when + * checking preconditions (`OOVoidResult`) is mixed with + * "I need an X" orelse "we have a problem" (`OOResult`) +* at a functions head: + * `OOVoidResult function()` says: no result, but may get an error message + * `Optional function()` says: a `String` result or nothing. + +**Summary**: technically could use `Optional` for both situation, but it would be less precise, leaving more room for confusion and bugs. `OOVoidResult` forces use of `getError` instead of `get`, and `isError` or `isOk` instead of `isPresent`or `isEmpty`. + +## What does OOResult buy us? + +The promise of `Result` is that we can avoid throwing exceptions and return errors instead. This allows the caller to handle these latter as data, for example may summarize / collect them for example into a single message dialog. + +Handling the result needs some code in the caller. If we only needed checks that return only errors (not results), the code could look like this (with possibly more tests listed): + +```java +OOResult odoc = getXTextDocument(); +if (testDialog(title, + odoc, + styleIsRequired(style), + selectedBibEntryIsRequired(entries, OOError::noEntriesSelectedForCitation))) { + return; +} +``` + +with a reasonably small footstep. + +Dependencies of tests on earlier results complicates this: now we repeat the + +```java +if (testDialog(title, + ...)) { + return; +} +``` + +part several times. diff --git a/jabref/docs/code-howtos/openoffice/ooresult-ooerror/ooresult-alternatives.md b/jabref/docs/code-howtos/openoffice/ooresult-ooerror/ooresult-alternatives.md new file mode 100644 index 00000000..8fc70b09 --- /dev/null +++ b/jabref/docs/code-howtos/openoffice/ooresult-ooerror/ooresult-alternatives.md @@ -0,0 +1,468 @@ +--- +parent: The LibreOffice Panel +grand_parent: Code Howtos +--- +# Alternatives to using OOResult and OOVoidResult in OOBibBase + +(Talk about ADRs prompted me to think about alternatives to what I used.) + +Situation: + +* some tests return no data, only report problems +* we may need to get some resources that might not be available (for example: connection to a document, a functional textview cursor) +* some test depend on these resources + +One strategy could be to use a single try-catch around the whole body, then showing a message based on the type of exceptions thrown. + +## \[base case] + +```java +try { + A a = f(); + B b = g(a); + realAction(a,b); +} catch (FirstExceptionType ex) { + showDialog( title, messageForFirstExceptionType(ex) ); +} catch (SecondExceptionType ex) { + showDialog( title, messageForSecondExceptionType(ex) ); +} catch (Exception ex) { + showDialog( title, messageForOtherExceptions(ex) ); +} +``` + +This our base case. + +It is not clear from the code, nor within the catch branches (unless we start looking into stack traces) which call (`f()`, `g(a)` or `realAction(a,b)`) resulted in the exception. This limits the specificity of the message and makes it hard to think about the "why" can we get this exception here? + +## Catch around each call? + +A more detailed strategy would be to try-catch around each call.\ +In case we need a result from the call, this means either increasingly indented code (try-in-try). + +```java +try { + A a = f(); + try { + B b = g(a); + try { + realAction(ab); + } catch (...){ + showDialog(); + } + } catch (G ex) { + showDialog(title, ex); // title describes which GUI action we are in + } +} catch (F ex) { + // could an F be thrown in g? + showDialog( title, ex ); +} +``` + +or (declare and fill later) + +```java +A a = null; +try { + a = f(); +} catch (F ex) { + showDialog(title, ex); + return; +} +B b = null; +try { + b = g(a); +} catch (G ex) { + showDialog(title, ex); + return; +} +try { + realAction(ab); +} catch (...){ + showDialog(); +} +``` + +In either case, the code becomes littered with exception handling code. + +## Catch in wrappers? + +We might push the try-catch into its own function.\ +If the wrapper is called multiple times, this may reduce duplication of the catch-and-assign-message part. + +We can show an error dialog here: `title` carries some information from the caller, the exeption caught brings some from below. + +We still need to notify the action handler (the caller) about failure. Since we have shown the dialog, we do not need to provide a message. + +### Notify caller with `Optional` result + +With `Optional` we get something like this: + +#### \[dialog in wrap, return Optional] + +```java +Optional
wrap_f(String title) { + try { + return Optional.of(f()); + } catch (F ex) { + showDialog(title, ex); + return Optional.empty(); + } +} + +Optional wrap_g(String title, A a) { + try { + return Optional.of(g(a)); + } catch (G ex) { + showDialog(title, ex); + return Optional.empty(); + } +} +``` + +and use it like this: + +```java +Optional a = wrap_f(title); +if (a.isEmpty()) { return; } + +Optional b = wrap_g(title, a.get()); +if (b.isEmpty()) { return; } + +try { + realAction(a.get(), b.get()); +} catch (...) { +} +``` + +This looks fairly regular. + +If `g` did not need `a`, we could simplify to + +```java +Optional a = wrap_f(title); +Optional b = wrap_g(title); +if (a.isEmpty() || b.isEmpty()) { return; } + +try { + realAction(a.get(), b.get()); +} catch (...) { +} +``` + +### Notify caller with `Result` result + +With `Result` we get something like this: + +#### \[dialog in wrap, return OOResult] + +```java +OOResult wrap_f() { + try { + return OOResult.ok(f()); + } catch (F ex) { + return OOResult.error(OOError.from(ex)); + } catch (F2 ex) { + String message = "..."; + return OOResult.error(new OOError(message, ex)); // [1] + } +} +// [1] : this OOError constructor (explicit message but no title) is missing + +Optional wrap_g(A a) { + try { + return OOResult.ok(g(a)); + } catch (G ex) { + return OOResult.error(OOError.from(ex)); + } +} +``` + +and use it like this: + +```java +OOResult a = wrap_f(); +if (testDialog(title, a)) { // [1] + return; +} + +// [1] needs boolean testDialog(String title, OOResultLike... a); +// where OOResultLike is an interface with `OOVoidResult asVoidResult()` +// and is implemented by OOResult and OOVoidResult + +OOResult b = wrap_g(a.get()); +if (testDialog(title, b)) { return; } // (checkstyle makes this 3 lines) + +try { + realAction(a.get(), b.get()); +} catch (...) { +} +``` + +If `g` did not need `a`, we could simplify to + +```java +Optional a = wrap_f(); +Optional b = wrap_g(); +if (testDialog(title, a, b)) { // a single dialog can show both messages + return; +} + +try { + realAction(a.get(), b.get()); +} catch (...) { +} +``` + +### Notify caller by throwing an exception + +Or we can throw an exception to notify the caller. + +To simplify code in the caller, I assume we are using an exception type not used elsewhere, but shared by all precondition checks. + +#### \[dialog in wrap, PreconditionException] + +```java +A wrap_f(String title) throws PreconditionException { + try { + return f(); + } catch (F ex) { + showDialog(title, ex) + throw new PreconditionException(); + } +} + +B wrap_g(String title, A a) throws PreconditionException { + try { + return g(a); + } catch (G ex) { + showDialog(title, ex); + throw new PreconditionException(); + } +} +``` + +use + +```java +try { + A a = wrap_f(title); + B b = wrap_g(title, a); + try { + realAction(a, b); + } catch (...) { + showDialog(...) + } +} catch( PreconditionException ) { + // Only precondition checks get us here. + return; +} +``` + +or (since PreconditionException is not thrown from realAction) + +```java +try { + A a = wrap_f(title); + B b = wrap_g(title, a); + realAction(a, b); +} catch (...) { + // Only realAction gets us here + showDialog(...) +} catch( PreconditionException ) { + // Only precondition checks get us here. + return; +} +``` + +or (separate try-catch for preconditions and realAction) + +```java +A a = null; +B b = null; +try { + a = wrap_f(title); + b = wrap_g(title, a); +} catch( PreconditionException ) { + return; +} +try { + realAction(a, b); +} catch (...) { +} +``` + +or to reduce passing around the title part: + +#### \[PreconditionException, dialog in catch] + +```java +A wrap_f() throws PreconditionException { + try { + return f(); + } catch (F ex) { + throw new PreconditionException(message, ex); + } +} + +B wrap_g(A a) throws PreconditionException { + try { + return g(a); + } catch (G ex) { + throw new PreconditionException(message, ex); + } +} +``` + +use + +```java +try { + A a = wrap_f(); + B b = wrap_g(a); + try { + realAction(a, b); + } catch (...) { + showDialog(...); + } +} catch(PreconditionException ex) { + showDialog(title, ex.message ); + return; +} +``` + +or + +```java +try { + A a = wrap_f(); + B b = wrap_g(a); + realAction(a, b); +} catch (...) { + showDialog(...); +} catch(PreconditionException ex) { + showDialog(title, ex.message ); + return; +} +``` + +## Push associating the message further down + +As [the developers guide](https://jabref.readthedocs.io/en/latest/getting-into-the-code/code-howtos/#throwing-and-catching-exceptions) suggest, we could "Catch and wrap all API exceptions" and rethrow them as a `JabRefException` or some exception derived from it. In this case the try-catch part goes even further down, and in principle we could just + +```java +try { + A a = f(); + B b = g(a); + realAction(a, b); +} catch(JabRefException ex) { + showDialog(title, ex.message ); + return; +} +``` + +Constraints: + +* conversion to `JabRefException` cannot be done in `model` (since JabRefException is in `logic`) +* `JabRefException` expects a localized message. Or we need to remember which `JabRefException` instances are localized and which need to be caught for localizing the message. +* At the bottom we usually have very little information on higher level contexts: at a failure like `NoSuchProperty` we cannot tell which set of properties did we look in and why.\ + For messages originating too deeply, we might want to override or extend the message anyway. +* for each exeption we might want to handle programmatically, we need a variant based on `JabRefException` + +So we might end up: + +```java +try { + A a = f(); + B b = g(a); + realAction(a, b); +} catch(FDerivedFromJabRefException ex) { + showDialog(title, messageForF ); +} catch(GDerivedFromJabRefException ex) { + showDialog(title, messageForG ); +} catch(JabRefException ex) { + showDialog(title, ex.message ); +} catch(Exception ex) { // [1] + showDialog(title, ex.message, ex ); + // [1] does "never catch Exception or Throwable" apply at this point? + // Probably should not: we are promising not to throw. +} +``` + +which looks very similar to the original version. + +This again loses the information: can `GDerivedFromJabRefException` come from `realAction` or `f` or not? This is because we have pushed down the last catch/throw indefinitely (eliminating `wrap_f`) into a depth, where we cannot necessarily assign an appropriate message. + +To a lesser extent this also happens in `wrap_f`: it only knows about the action that called it what we provide (`title` or nothing). It knows the precondition it checks: probably an optimal location to assign a message. + +**Summary**: going from top to bottom, we move to increasingly more local context, our knowledge shifts towards the "in which part of the code did we have a problem" and away from the high level ("which action"). + +One natural point to meet information from these to levels is the top level of action handlers. For precondition checking code a wrapper around code elsewhere may be considered. Using such wrappers may reduce duplication if called in multiple actions. + +We still have to signal failure to the action handler: the options considered above were using an `Optional` and throwing an exception with the appropriate message. + +The more promising variants were + +* **\[dialog in wrap, return Optional]**\ + `Optional wrap_f(String title)` (showDialog inside) + * pro: explicit return in caller + * con: explicit return in caller (boilerplate) + * con: passing in the title is repeated + * would be 'pro' if we wanted title to vary within an action +* **\[PreconditionException, dialog in catch]**\ + `A wrap_f() throws PreconditionException`\ + (with `showDialog` under `catch(PreconditionException ex)`) + * con: hidden control flow + * pro: no repeated `if(){return}` boilerplate + * pro: title used only once + +### \[using OOResult] + +```java +final String title = "Could not insert citation"; + +OOResult odoc = getXTextDocument(); +if (testDialog(title, + odoc, + styleIsRequired(style), + selectedBibEntryIsRequired(entries, OOError::noEntriesSelectedForCitation))) { + return; +} +XTextDocument doc = odoc.get(); + +OOResult ofr = getFrontend(doc); +if (testDialog(title, ofr)) { + return; +} +OOFrontend fr = ofr.get(); + +OOResult cursor = getUserCursorForTextInsertion(doc); +if (testDialog(title, cursor)) { + return; +} +... +``` + +### \[using PreconditionException, dialog in catch] + +```java +final String title = "Could not insert citation"; + +try { + XTextDocument doc = getXTextDocument(); + styleIsRequired(style); + selectedBibEntryIsRequired(entries, OOError::noEntriesSelectedForCitation); + OOFrontend fr = getFrontend(doc); + XTextCursor cursor = getUserCursorForTextInsertion(doc); + ... +} catch (PreconditionException ex) { + showDialog(title, ex); +} catch (...) { +} +``` + +I would suggest using the latter, + +* probably using `OOError` for `PreconditionException` + * In this case `OOError` being in `gui` becomes an asset: we can be sure code in `logic` cannot throw it. +* We lose the capability to collect mmessages in a single dialog (we stop processing at the first problem). +* The division between precondition checking (only throws PreconditionException) and `realAction`becomes invisible in the action code. diff --git a/jabref/docs/code-howtos/openoffice/order-of-appearance.md b/jabref/docs/code-howtos/openoffice/order-of-appearance.md new file mode 100644 index 00000000..ddb2d6ba --- /dev/null +++ b/jabref/docs/code-howtos/openoffice/order-of-appearance.md @@ -0,0 +1,78 @@ +--- +nav_order: 2 +parent: The LibreOffice Panel +grand_parent: Code Howtos +--- +# Order of appearance of citation groups + +The order of appearance of citations is decided on two levels: + +1. their order within each citation group (`localOrder`), and +2. the order of the citation groups that appear as citation markers in the text (`globalOrder`). + +This page is about the latter: how to decide the order of appearance (numbering sequence) of a set of citation markers? + +## Conceptually + +In a continuous text it is easy: take the textual order of citation markers. + +In the presence of figures, tables, footnotes/endnotes possibly far from the location they are referred to in the text or wrapped around with text it becomes less obvious what is the correct order. + +Examples: + +* References in footnotes: are they _after_ the page content, or number them as if they appeared at the footnote mark? (JabRef does the latter) +* A figure with references in its caption. Text may flow on either or both sides.\ + Where should we insert these in the sequence? +* In a two-column layout, a text frame or figure mostly, but not fully in the second column: shall we consider it part of the second column? + +## Technically + +In LibreOffice, a document has a main text that supports the [XText](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1text_1_1XText.html) interface.\ +This allows several types of [XTextContent](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1text_1_1XTextContent.html) to be inserted. + +* Some of these allow text inside with further insertions. + +### Anchors + +* Many, but not all XTextContent types support getting a "technical" insertion point or text range through [getAnchor](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1text_1_1XTextContent.html#ae82a8b42f6b2578549b68b4483a877d3). +* In Libreoffice positioning both a frame and its anchor seems hard: moving the frame tends to also move the anchor. +* Consequence: producing an order of appearance for the citation groups based solely on `getAnchor` calls may be impossible. + * Allowing or requiring the user to insert "logical anchors" for frames and other "floating" parts might help to alleviate these problems. + +### Sorting within a `Text` + +The text ranges occupied by the citation markers support the [XTextRange](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1text_1_1XTextRange.html) interface. + +* These provide access to the XText they are contained in. +* The [Text](https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1text_1_1Text.html) service may support (optional) the [XTextRangeCompare](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1text_1_1XTextRangeCompare.html) interface, that allows two XTextRange values to be compared if both belong to this `Text` + +### Visual ordering + +* The cursor used by the user is available as an [XTextViewCursor](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1text_1_1XTextViewCursor.html) +* If we can get it and can set its position in the document to each XTextRange to be sorted, and ask its [getPosition](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1text_1_1XTextViewCursor.html#a9b2bafd342ef75b5d504a9313dbb1389) to provide coordinates "relative to the top left position of the first page of the document.", then we can sort by these coordinates in top-to-bottom left-to-right order. +* Note: in some cases, for example when the cursor is in a comment (as in `Libreoffice:[menu:Insert]/[Comment]`), the XTextViewCursor is not available (I know of no way to get it). +* In some other cases, for example when an image is selected, the XTextViewCursor we normally receive is not 'functional': we cannot position it for getting coordinates for the citation marks. The [FunctionalTextViewCursor](https://github.com/antalk2/jabref/blob/improve-reversibility-rebased-03/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java) class can solve this case by accessing and manipulating the cursor through [XSelectionSupplier](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1view_1_1XSelectionSupplier.html) + +Consequences of getting these visual coordinates and using them to order the citation markers + +* allows uniform handling of the markers. Works in footnotes, tables, frames (apparently anywhere) +* requires moving the user visible cursor to each position and with [screen refresh](https://github.com/antalk2/jabref/blob/improve-reversibility-rebased-03/src/main/java/org/jabref/model/openoffice/uno/UnoScreenRefresh.java) enabled.\ + `(problem)` This results in some user-visible flashing and scrolling around in the document view. +* The expression "relative to the top left position of the first page of the document" is understood literally, "as on the screen".\ + `(problem)` Showing pages side by side or using a two-column layout will result in markers in the top half of the second column or page to be sorted before those on the bottom of the first column of the first page. + +## JabRef + +Jabref uses the following steps for sorting citation markers (providing `globalOrder`): + +1. the textranges of citation marks in footnotes are replaced by the textranges of the footnote marks. +2. get the positions (coordinates) of these marks +3. sort in top-to-bottom left-to-right order + +`(problem)` In JabRef5.2 the positions of citation marks within the same footnote become indistinguishable, thus their order after sorting may differ from their order in the footnote text.\ +This caused problems for + +1. numbering order\ + `(solved)` by keeping track of the order-in-footnote of citation markers during sorting using [getIndexInPosition](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java#L21)) +2. `click:Merge`: It examines _consecutive_ pairs of citation groups if they can be merged. Wrong order may result in not discovering some mergeable pairs or attempting to merge in wrong order.\ + `(solved)` by not using visual order, only XTextRangeCompare-based order within each XText [here](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java#L325)) diff --git a/jabref/docs/code-howtos/openoffice/overview.md b/jabref/docs/code-howtos/openoffice/overview.md new file mode 100644 index 00000000..a76ece7b --- /dev/null +++ b/jabref/docs/code-howtos/openoffice/overview.md @@ -0,0 +1,225 @@ +--- +nav_order: 1 +parent: The LibreOffice Panel +grand_parent: Code Howtos +--- +# Overview + +This is a partial overview of the OpenOffice/LibreOffice panel and the code behind. + +* To access the panel: `JabRef:/[menu:View]/[OpenOffice/LibreOffice]` +* The user documentation is at [https://docs.jabref.org/cite/openofficeintegration](https://docs.jabref.org/cite/openofficeintegration) + +I am going to refer to OpenOffice Writer and LibreOffice Writer as LibreOffice or LO: their UNO APIs are still mostly identical, but I only tested with LibreOffice and differences do exist. + +## Subject + +* What is stored in a document, how. +* Generating citation markers and bibliography + * (excluding the bibliography entries, which is delegated to the layout module) + +## The purpose of the panel + +* Allow the user to insert **citations** in a LibreOffice writer document. +* Automatically format these according to some prescribed style as **citation markers**. +* Generate a **bibliography**, also formatted according to the style. + * The bibliography consists of a title (e.g. "References") and a sorted list of formatted bibliography entries, possibly prefixed with a marker (e.g. "\[1]") +* It also allows some related activities: connect to a document, select a style, group ("Merge") the citations for nicer output, ungroup ("Separate") them to move or delete them individually, edit ("Manage") their page-info parts, and collect the database entries of cited sources to a new database. + +## Citation types + +Citations (actually citation groups, see below) have three types depending on how the citation marker is intended to appear in the text: + +* **Parenthesized**: "(Smith, 2000)" +* **In-text**: "Smith (2000)" +* **Invisible**: no visible citation mark. + * An invisible citation mark lets the user to use any form for the citation by taking control (and responsibility) back from the style. + * Like the other two citation types, they have a location in the document. + * In the bibliography these behave as the other two citation types. + * In LibreOffice (`LibreOffice:[Ctrl-F8]` or`LibreOffice:[menu:View]/[Field Shadings]`) shows reference marks with gray background. Invisible citation marks appear as a thin gray rectangle. +* These citation types correspond to `\citep{Smith2000}`, `\citet{Smith2000}` in [natbib](http://tug.ctan.org/macros/latex/contrib/natbib/natnotes.pdf) and `\nocite{Smith2000}`. I will use `\citep`, `\citet` and `\citen` in "LaTeX pseudocode" below. + +## PageInfo + +The citations can be augmented with a string detailing which part of a document is cited, for example "page 11" or "chapter 2". + +Sample citation markers (with LaTeX pseudocode): + +* `\citep[page 11]{Smith2000}` "(Smith, 2000; page 11)" +* `\citet[page 11]{Smith2000}` "Smith (2000; page 11)" +* `\citen[page 11]{Smith2000}` "" +* This string is referred to as **`pageInfo`** in the code. +* In the GUI the labels "Cite special", "Extra information (e.g. page number)" are used. + +## Citation groups + +Citations can be grouped. + +A group of parenthesized citations share the parentheses around, like this:\ +"(Smith, 2000; Jones 2001)". + +* Examples with pseudocode: + * `\citep{Smith2000,Jones2001}` "(Smith, 2000; Jones 2001)" + * `\citet{Smith2000,Jones2001}` "Smith (2000); Jones (2001)" + * `\citen{Smith2000,Jones2001}` "" + +From the user's point of view, citation groups can be created by + +1. Selecting multiple entries in a bibliography database, then + + * `[click:Cite]` or + * `[click:Cite in-text]` or + * `[click:Cite special]` or + * `[click:Insert empty citation]` in the panel. + + This method allows any of the citation types to be used. +2. `[click:Merge citations]` finds all sets of consecutive citations in the text and replaces each with a group. + * `(change)` The new code only merges consecutive [parenthesized](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java#L183) citations. + * This is inconsistent with the solution used in `[click:Cite]` + * My impression is that + * groups of in-text or invisible citations are probably not useful + * mixed groups are even less. However, with a numbered style there is no visual difference between parenthesized and in-text citations, the user may be left wondering why did merge not work. + * One way out could be to merge as a "parenthesized" group. But then users switching between styles get a surprise, we have unexpectedly overridden their choice. + * I would prefer a visible log-like warning that does not require a click to close and lets me see multiple warnings. Could the main window have such an area at the bottom? + * Starting with JabRef 5.3 there is also `[click:Separate citations]` that breaks all groups to single citations. + * This allows + * deleting individual citations + * moving individual citations around (between citation groups) + * (copy does not work) + * (Moving a citation within a group has no effect on the final output due to sorting of citations within groups. See [Sorting within a citation group](overview.md#localOrder)) + +In order to manage single citations and groups uniformly, we consider each citation in the document to belong to a citation group, even if it means a group containing a single citation. + +Citation groups correspond to citation markers in the document. The latter is empty for invisible citation groups. When creating the citation markers, the citations in the group are processed together. + +## Citation styles + +The details of how to format the bibliography and the citation markers are described in a text file. + +* These normally use `.jstyle` extension, and I will refer to them as jstyle files. +* See the [User documentation](https://docs.jabref.org/cite/openofficeintegration#the-style-file) for details. +* I will refer to keywords in jstyle files as `jstyle:keyword` below. + +Four major types citation of styles can be described by a jstyle. + +* (1) `jstyle:BibTeXKeyCitations` + * The citation markers show the citationKey. + * It is not fully implemented + * does not produce markers before the bibliography entries + * does not show pageInfo + * It is not advertised in the [User documentation](https://docs.jabref.org/cite/openofficeintegration#the-style-file). + * Its intended purpose may be + * (likely) a proper style, with "\[Smith2000]" style citation markers + * (possibly) a style for "draft mode" that + * can avoid lookup of citation markers in the database when only the citation markers are updated + * can produce unique citation markers trivially (only needs local information) + * makes the citation keys visible to the user + * can work without knowing the order of appearance of citation groups + * In case we expect to handle larger documents, a "draft mode" minimizing work during `[click:Cite]` may be useful. +* There are two types of numbered (`jstyle:IsNumberEntries`) citation styles: + * (2) Citations numbered in order of first appearance (`jstyle:IsSortByPosition`) + * (3) Citations numbered according to their order in the sorted bibliography +* (4) Author-year styles + +## Sorting + +### Sorting te bibliography + +The bibliography is sorted in (author, year, title) order + +* except for `jstyle:IsSortByPosition`, that uses the order of first appearance of the cited sources. + +### Ordering the citations + +The order of appearance of citations (as considered during numbering and adding letters after the year to ensure that citation markers uniquely identify sources in the bibliography) is decided on two levels. + +1. Their order within each citation group (`localOrder`), and +2. the order of the citation groups (citation markers) in the text (`globalOrder`). + +#### Sorting within a citation group (`localOrder`) + +The order of citations within a citation group is controlled by `jstyle:MultiCiteChronological`. + +* `true` asks for (year, author, title) ordering, +* `false` for (author, year, title). +* (There is no option for "in the order provided by the user"). + +For author-year citation styles this ordering is used directly. + +* The (author, year, title) order promotes discovering citations sharing authors and year and emitting them in a shorter form. For example as "(Smith 2000a,b)". + +For numbered styles, the citations within a group are sorted again during generation of the citation marker, now by the numbers themselves. The result of this sorting is not saved, only affects the citation marker. + +* Series of consecutive number are replaced with ranges: for example "\[1-5; 11]" + +#### Order of the citation groups (`globalOrder`) + +The location of each citation group in the document is provided by the user. In a text with no insets, footnotes, figures etc. this directly provides the order. In the presence of these, it becomes more complicated, see [Order of appearance of citation groups](order-of-appearance.md). + +#### Order of the citations + +* `globalOrder` and `localOrder` together provide the order of appearance of citations +* This also provides the order of first appearance of the cited sources. + + First appearance order of sources is used + + * in `jstyle:IsSortByPosition` numbered styles + * in author-year styles: first appearance of "Smith200a" should precede that of "Smith200b".\ + To achieve this, the sources get the letters according the order of their first appearance. + * This seems to contradict the statement "The bibliography is sorted in (author, year, title) order" above.\ + It does not. As of JabRef 5.3 both are true.\ + Consequence: in the references Smith2000b may precede Smith2000a. ([reported](https://github.com/JabRef/jabref/issues/7805)) + * Some author-year citation styles prescribe a higher threshold on the number of authors for switching to "FirstAuthor et al." form (`jstyle:MaxAuthors`) at the first citation of a source (`jstyle:MaxAuthorsFirst`) + +## What is stored in a document (JabRef5.2) + +* Each group of citations has a reference mark. + + (Reference marks are shown in LibreOffice in Navigator, under "References".\ + To show the Navigator: `LibreOffice:[menu:View]/[Navigator]` or `LibreOffice:[key:F5]`) + + Its purposes: + + 1. The text range of the reference mark tells where to write or update the citation mark. + 2. The name of the reference mark + * Lets us select only those reference marks that belong to us + * Encodes the citation type + * Contains the list of citation keys that belong to this group + * It may contain an extra number, to make the name unique in the document + * Format: `"JR_cite{number}_{type}_{citationKeys}"`, where + * `{number}` is either empty or an unsigned integer (it can be zero) to make the name unique + * `{type}` is 1, 2, or 3 for parenthesized, in-text and invisible + * `{citationKeys}` contains the comma-separated list of citation keys + * Examples: + * `JR_cite_1_Smith2000` (empty number part, parenthesized, single citation) + * `JR_cite0_2_Smith2000,Jones2001` (number part is 0, in-text, two citations) + * `JR_cite1_3_Smith2000,Jones2001` (number part is 1, invisible, two citations) +* Each group of citations may have an associated pageInfo. + * In LibreOffice, these can be found at\ + `LibreOffice:/[menu:File]/[Properties]/[Custom Properties]` + * The property names are identical to the name of the reference mark corresponding to the citation group. + * JabRef 5.2 never cleans up these, they are left around.\ + `(problem)` New citations may "pick up" these unexpectedly. +* The bibliography, if not found, is created at the end of the document. + * The location and extent of the bibliography is the content of the Section named `"JR_bib"`.\ + (In LibreOffice Sections are listed in the Navigator panel, under "Sections") + * JabRef 5.2 also creates a bookmark named `"JR_bib_end"`, but does not use it. During bibliography update it attempts to create it again without removing the old bookmark. The result is a new bookmark, with a number appended to its name (by LibreOffice, to ensure unique names of bookmarks). + * [Correction in new code](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java#L147): remove the old before creating the new. + +## How does it interact with the document? + +* "stateless"\ + JabRef is only loosely coupled to the document.\ + Between two GUI actions it does not receive any information from LibreOffice.\ + It cannot distinguish between the user changing a single character in the document or rewriting everything. +* Access data + * During a `[click:cite]` or `[click:Update]` we need the reference mark names. + * Get all reference mark names + * Filter (only ours) + * Parse: gives citation type (for the group), citation keys + * Access/store pageInfo: based on reference mark name and property name being equal + * Creating a citation group: (`[click:cite]`) + * Creates a reference mark at the cursor, with a name as described above. +* Update (refreshing citation markers and bibliography): + * citation markers: the content of the reference mark + * bibliography: the content of the Section (in LibreOffice sense) named `"JR_bib"`. diff --git a/jabref/docs/code-howtos/openoffice/problems.md b/jabref/docs/code-howtos/openoffice/problems.md new file mode 100644 index 00000000..73f304a6 --- /dev/null +++ b/jabref/docs/code-howtos/openoffice/problems.md @@ -0,0 +1,70 @@ +--- +nav_order: 3 +parent: The LibreOffice Panel +grand_parent: Code Howtos +--- +# Problems + +## pageInfo should belong to citations, not citation groups + +* Creating `[click:Separate]` revealed\ + a `(problem)`: pageInfo strings are conceptually associated with citations, but the implementation associates them to citation groups.\ + The number of available pageInfo slots changes during`[click:Merge]` and `[click:Separate]` while the number of citations remains fixed. + * The proposed solution was to change the association. + * Not only reference marks (citation groups) need unique identifiers, but also citations.\ + Possible encoding for reference mark names:\ + `JR_cite{type}_{number1}_{citationKey1},{number2}_{citationKey2}`\ + where `{type}` encodes the citation type (for the group), `{citationKey1}` is made unique by choosing an appropriate number for `{number1}`\ + This would allow `JR_cite_{number1}_{citationKey1}` to be used as a property name for storing the pageInfo. + + Changes required to + + * reference mark search, name generation and parsing + * name generation and parsing for properties storing pageInfo values + * in-memory representation + * JabRef 5.2 does not collect pageInfo values, accesses only when needed.\ + So it would be change to code accessing them. + * The proposed representation does collect, to allow separation of getting from the document and processing + * insertion of pageInfo into citation markers: JabRef 5.2 injects a single pageInfo before the closing parenthesis, now we need to handle several values + * `[click:Manage citations]` should work on citations, not citation groups. + +## Backend + +The choice of how do we represent the data and the citation marks in the document has consequences on usability. + +Reference marks have some features that make it easy to mess up citations in a document + +* They are **not visible** by default, the user is not aware of their boundaries\ + (`LO:[key:Ctrl-F8]`, `LO:[View]/[Field shadings]` helps) +* They are **not atomic**: + * the user can edit the content. This will be lost on `[click:Update]`\ + If an `As character` or `To character` anchor is inserted, the corresponding frame or footnote is deleted. + * by pressing Enter within, the user can break a reference mark into two parts.\ + The second part is now outside the reference mark: `[click:Update]` will leave it as is, and replace the first part with the full text for the citation mark. + * If the space separating to citation marks is deleted, the user cannot reliably type between the marks.\ + The text typed usually becomes part of one of the marks. No visual clue as to which one.\ + Note: `[click:Merge]` then `[click:Separate]` adds a single space between. The user can position the cursor before or after it. In either case the cursor is on a boundary: it is not clear if it is in or out of a reference mark.\ + Special case: a reference mark at the start or end of a paragraph: the cursor is usually considered to be within at the corresponding edge. +* (good) They can be moved (Ctrl-X,Ctrl-V) +* They cannot be copied. (Ctrl-C, Ctrl-V) copies the text without the reference mark. +* Reference marks are lost if the document is saved as docx. +* I know of no way to insert text into an empty text range denoted by a reference mark + * JabRef 5.3 recreates the reference mark (using [insertReferenceMark](https://github.com/JabRef/jabref/blob/475b2989ffa8ec61c3327c62ed8f694149f83220/src/main/java/org/jabref/gui/openoffice/OOBibBase.java#L1072)) [here](https://github.com/JabRef/jabref/blob/475b2989ffa8ec61c3327c62ed8f694149f83220/src/main/java/org/jabref/gui/openoffice/OOBibBase.java#L706) + * `(change)` I preferred to (try to) avoid this: [NamedRangeReferenceMark.nrGetFillCursor](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java#L225) returns a cursor between two invisible spaces, to provide the caller a location it can safely write some text. [NamedRangeReferenceMark.nrCleanFillCursor](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java#L432) removes these invisible spaces unless the content would become empty or a single character. By keeping the content at least two characters, we avoid the ambiguity at the edges: a cursor positioned between two characters inside is always within the reference mark. (At the edges it may or may not be inside.) +* `(change)` `[click:Cite]` at reference mark edges: [safeInsertSpacesBetweenReferenceMarks](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java#L67) ensures the we are not inside, by starting two new paragraphs, inserting two spaces between them, then removing the new paragraph marks. +* `(change)` [guiActionInsertEntry](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/gui/openoffice/OOBibBase2.java#L624) checks if the cursor is in a citation mark or the bibliography. +* `(change)` `[click:Update]` does an [exhaustive check](https://github.com/antalk2/jabref/blob/122d5133fa6c7b44245c5ba5600d398775718664/src/main/java/org/jabref/gui/openoffice/OOBibBase2.java#L927) for overlaps between protected ranges (citation marks and bibliography). This can become slow if there are many citations. + +It would be nice if we could have a backend with better properties. We probably need multiple backends for different purposes. This would be made easier if the backend were separated from the rest of the code. This would be the purpose of [logic/openoffice/backend](https://github.com/antalk2/jabref/tree/improve-reversibility-rebased-03/src/main/java/org/jabref/logic/openoffice/backend). + +## Undo + +* JabRef 5.3 does not collect the effects of GUI actions on the document into larger Undo actions.\ + This makes the Undo functionality of LO impractical. +* `(change)` collect the effects of GUI actions into large chunks: now a GUI action can be undone with a single click. + * except the effect on pageInfo: that is stored at the document level and is not restored by Undo. + +## Block screen refresh + +* LibreOffice has support in [XModel](https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1frame_1_1XModel.html#a7b7d36374033ee9210ec0ac5c1a90d9f) to "suspend some notifications to the controllers which are used for display updates." +* `(change)` Now we are using this facility. diff --git a/jabref/docs/code-howtos/remote-storage-jabdrive.md b/jabref/docs/code-howtos/remote-storage-jabdrive.md new file mode 100644 index 00000000..1d31cfe8 --- /dev/null +++ b/jabref/docs/code-howtos/remote-storage-jabdrive.md @@ -0,0 +1,274 @@ +--- +parent: Remote Storage +grand_parent: Code Howtos +--- +# Remote JabDrive storage + +This describes the synchronization to JabDrive. +[JabRef Online](https://github.com/jabref/jabrefonline) also implements the JabDrive interface. + +The setting is that clients synchronize their local view with a server. +The server itself does not change data on itself. +If it does, it needs to create a separate client connecting to the server. +Thus, all changes are finally triggered by a client. + +The following algorithm is highly inspired by the replication protocols of [CouchDB](https://docs.couchdb.org/en/stable/replication/protocol.html) and [RxDB](https://rxdb.info/replication.html). +For the explanation, we focus on the synchronization of entries. +Nevertheless, the synchronization of other data (such as the groups tree) works similarly. + +From a high-level perspective, the sync algorithm is very similar with git: +both the server and the client have their own change histories, and the client has to first pull and merge changes from the server before pushing its new state to the server. +The sync process is incremental and only examines entries updated since the last sync. + +We call this the "pull-merge-push cycle". + +## Data structures + +We start by providing information on data structures. +There are some explanations of data structures included if they are short. +Longer explanations are put below at "The 'pull-merge-push cycle'". + +### Metadata for each item + +In order to support synchronization, additional metadata is kept for each item: + +- `ID`: An unique identifier for the entry (will be a UUID). +- `Revision`: The revision is a "generation Id" being increasing positive integer. + This is based on [Multiversion concurrency control (MVCC)](http://en.wikipedia.org/wiki/Multiversion_concurrency_control), where an increasing identifier ("time stamp") is used. +- `hash`: This is the hash of the item (i.e., of all the data except for `Revision` and `hash`). +- (Client only) `dirty`: Marks whether the user changed the entry. + +`ID` and `Revision` are handled in [`org.jabref.model.entry.SharedBibEntryData`](https://github.com/JabRef/jabref/blob/main/src/main/java/org/jabref/model/entry/SharedBibEntryData.java). + +{: .note-title } +> Dirty flags +> +> Using dirty flags, the client keeps track of the changes that happened in the library since the last time the client was synchronized with the server. +> When the client loads a library into memory, it computes the hash for each entry and compares it with the hash in the entry's metadata. +> In case of a difference between these hashes, the entry is marked dirty. +> Moreover, an entry's dirty flag is set whenever it is modified by the user in JabRef. +> The dirty flag is only cleared after a successful synchronization process. +> +> There is no need to serialize the dirty flags on the client's side since they are recomputed upon loading. + +### Global time clock + +The idea is that the server tracks a global (logical) monotone increasing "time clock" tracking the existing revisions. +Each entry has its own revision, increased "locally". +The "global revision id" keeps track of the global synchronization state. +One can view it as aggregation on the synchronization state of all entries. +Similar to the revision concept of Subversion. + +### Tombstones + +Deleted items are persisted as [tombstones](https://docs.couchbase.com/sync-gateway/current/managing-tombstones.html), which contain the metadata `ID` and `Revision` only. +Tombstones ensure that all synchronizing devices can identify that a previously existing entry has been deleted. +On the client, a tombstone is created whenever an entry is deleted. +Moreover, the client keeps a list of all entries in the library so that external deletions can be recognized when loading the library into memory. +The local list of tombstones is cleared after it is sent to the server and the server acknowledged it. +On the server, tombstones are kept for a certain time span (world time) that is strictly larger than the time devices are allowed to not sign-in before removed as registered devices. + +### Checkpoints + +Checkpoints allow a sync task to be resumed from where it stopped, without having to start from the beginning. + +The checkpoint locally stored by the client signals the logical time (generation Id) of the last server change that has been integrated into the local library. +Checkpoints are used to paginate the server-side changes. +In the implementation, the checkpoint is a tuple consisting of the server time of the latest change and the highest `ID` of the entry in the batch. +However, it is better to not depend on these semantics. + +The client has to store a checkpoint `LastSync` in its local database, and it is updated after every merge. +The checkpoint is then used as the `Since` parameter in the next Pull phase. + +## The "pull-merge-push cycle" + +Each sync cycle is divided into three phases: + +1. `Pull phase`: The server sends its local changes to the client. +2. `Merge phase`: The client and server merge their local changes. +3. `Push phase`: The client sends its local changes to the server. + +We assume that the server has some view on the library and the client has a view on the library. + +{: .note-title } +> Straight-forward synchronization +> +> When the client connects to the server, one option for synchronization is to ask the server for all up-to-date entries and then using the `Revision` information to merge with the local data. +> However, this is highly inefficient as the whole database has to be sent over the wire. +> A small improvement is gained by first asking only for tuples of `ID` and `Revision`, and only pull the complete entry if the local data is outdated or in conflict. +> However, this still requires to send quite a bit of data. +> Instead, we will use the following refinement. + +### Pull Phase + +The client pulls on first connect or when requested to pull. +The client asks the server for a list of documents that changed since the last checkpoint. (Creating a checkpoint is explained further below.) +The server responses with a batched list of these entries together with their `Revision` information. +These entries could also be tombstones. +Each batch includes also a checkpoint `To` that has the meaning "all changes to this point in time are included in the current batch". + +{: .note } +Once the pull does not give any further changes, the client switches to an event-based strategy and observes new changes by subscribing to the event bus provided by the server. +This is more an implementation detail than a conceptual difference. + +### Merge Phase + +The pulled data from the server needs to be merged with the local view of the data. +The data is merged on a per-entry basis. +Based on the "generation ID" (`Revision`) of server and client, following cases can occur: + +1. The server's `Revision` is higher than the client's `Revision`: Two cases need to be distinguished: + 1. The client's entry is dirty. That means, the user has edited the entry in the meantime. + Then the user is shown a message to resolve the conflict (see "Conflict Handling" below) + 2. The client's entry is clean. That means, the user has not edited the entry in the meantime. + In this case, the client's entry is replaced by the server's one (including the revision). +2. The server's `Revision` is equal to the client's `Revision`: Both entries are up-to-date and nothing has to be done. This case may happen if the library is synchronized by other means. +3. The server's `Revision` is lower than the client's `Revision`: This should never be the case, as revisions are only increased on the server. Show error message to user. + +If the entry returned by the server is a tombstone, then: + +- If the client's entry is also a tombstone, then we do not have to do anything. +- If the client's entry is dirty, then the user is shown a message to resolve the conflict (see "Conflict Handling") below. +- Otherwise, the client's entry is deleted. There is no need to keep track of this as a local tombstone. + +#### Conflict Handling + +If the user chooses to overwrite the local entry with the server entry, then the entry's `Revision` is updated as well, and it is no longer marked as dirty. +Otherwise, its `Revision` is updated to the one provided by the server, but it is still marked as dirty. +This will enable pushing of the entry to the server during the "Push Phase". + +After the merging is done, the client sets its local checkpoint to the value of `To`. + +### Push Phase + +The client sends the following information back to the server: + +- The list of entries that are marked dirty (along with their `Revision` data). +- The list of entries that are new, i.e., that do not have an `ID` yet. +- The list of tombstones, i.e., entries that have been deleted. + +The server accepts only changes if the provided `Revision` coincides with the `Revision` stored on the server. +If this is not the case, then the entry has been modified on the server since the last pull operation, and then the user needs to go through a new pull-merge-push cycle. + +During the push operation, the user is not allowed to locally edit these entries that are currently pushed. +After the push operation, all entries accepted by the server are marked clean. +Moreover, the server will generate a new revision number for each accepted entry, which will then be stored locally. +Entries rejected (as conflicts) by the server stay dirty and their `Revision` remains unchanged. + +### Start the "pull-merge-push cycle" again + +It is important to note that sync replicates the library only as it was at the point in time when the sync was started. +So, any additions, modifications, or deletions on the server-side after the start of sync will not be replicated. +For this reason, a new cycle is started. + +## Scenarios + +Having discussed the general algorithm, we discuss scenarios which can happen during usage. +In the following, `T` denotes the "global generation Id". + +We focus on JabRef as client and a "user" using JabRef. + +### Sync stops after Pull + +1. JabRef pulls changes since `T = 0` +2. JabRef starts with the merge and the user (in parallel) closes JabRef discarding any changes. +3. User opens JabRef again. +4. JabRef pulls changes again from `T = 0` (since the checkpoint is still `T = 0`) and JabRef has to redo the conflict resolution. + +This is the best we can do, since the user decided to not save its previous work. + +However, consider the same steps but now in step 2, the user decided to save their work. +The locally stored checkpoint is still `T = 0`. +Thus, the user has to redo the conflict resolution again. +The difference is that the local version is the previously merge result now. + +*Future improvement:* We could send checkpoints for every entry and after each conflict resolution set the local checkpoint to the checkpoint of the entry. + +### Sync stops after Merge + +1. JabRef pulls changes since `T = 0` +2. JabRef finishes the merge (this sets the checkpoint `T = 1̀`). +3. User closes JabRef with discarding any changes (in particular, the checkpoint is not persisted as well). +4. User opens JabRef again. +5. JabRef pulls changes again from `T = 0` (since the checkpoint is still `T = 0`) and has to redo the conflict resolution. + +This is the best we can do, since the user decided to not save their previous work. + +If the user decides in step 3 to save their changes, then in step 5 JabRef would pull changes starting from `T = 1` and the user does not have to redo the conflict resolution. + +### Sync after successful sync of client changes + +1. JabRef modifies local data: `{id: 1, value: 0, _rev=1, _dirty=false}` to `{id: 1, value: 1, _rev=1, _dirty=true}`. + `id` is `ID` from above, `value` summarizes all fields of the entry, `_rev` is `Revision` from above, and `_dirty` the dirty flag. +2. JabRef pulls server changes. Suppose there are none. +3. Consequently, Merge is not necessary. JabRef sets checkpoint to `T = 1`. +4. JabRef pushes its changes to the server. + Assume this corresponds to `T = 2` on the server. + On the server, this updates `{id: 1, value: 0, _rev=1, updatedAt=1}` to `{id: 1, value: 1, _rev=2, updatedAt=2}` and on the client `{id: 1, value: 1, _rev=1, _dirty=true}` to `{id: 1, value: 1, _rev=2, _dirty=false}`. +5. Client pulls changes starting from `T = 1` (the last local checkpoint). Server responds with `{id: 1, value: 1, _rev=2}, checkpoint={2}`. +6. Client merges the 'changes', which in this case is trivial since the data on the server and client is the same. + +This is not quite optimal since the last pull response contains the full data of the entry although this data is already at the client. + +*Possible future improvements:* + +- First pull only the `IDs` and `Revisions` of the server-side changes, and then filter out the ones we already have locally before querying the complete entry. + Downside is that this solution always needs one more request (per change batch) and it is not clear if this outweighs the costs of sending the full entry. +- The server can remember where a change came from and then not send these changes back to that client. Especially if the server's generation Id increased by one due to the update, this is straight-forward. + +## FAQs + +### Why do we need an identifier (`ID`)? Is the BibTeX key not enough? + +The identifier needs to be unique at the very least across the library and should stay constant in time. +Both features cannot be ensured for BibTeX keys. +Note this is similar to the `shared_id` in the case of the SQL synchronization. + +### Why do we need revisions? Are `updatedAt` timeflags not enough? + +The revision functions as "generation Id" known from [Lamport clocks](https://en.wikipedia.org/wiki/Lamport_timestamp) and common in synchronization. +For instance, the [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) also uses these kinds of clocks. + +A "generation Id" is essentially a clock local to the entry that ticks whenever the entry is synced with the server. +As for us there is only one server, strictly speaking, it would suffice to use the global server time for this. +Moreover, for the sync algorithm, the client would only need to store the revision/server time during the pull-merge-push cycle (to make sure that during this time the entry is not modified again on the server). +Nevertheless, the generation Id is only a tiny data blob, and it gives a bit of additional security/consistency during the merge operation, so we keep it around all the time. + +### Why do we need an entry hash? + +The hash is only used on the client to determine whether an entry has been changed outside of JabRef. + +#### Why don't we need to keep the whole revision history as it is done in CouchDB? + +The revision history is used by CouchDB to find a common ancestor of two given revisions. +This is needed since CouchDB provides main-main sync. +However, in our setting, we have a central server and thus the last synced revision is *the* common ancestor for both the new server and client revision. + +### Why is a dirty flag enough on the client? Why don't we need local revisions? + +In CouchDB, every client has their own history of revisions. +This is needed to have a deterministic conflict resolution that can run on both the server and client side independently. +In this setting, it is important to determine which revision is older, which is then declared to be the winner. +However, we do not need an automatic conflict resolution: +Whenever there is a conflict, the user is asked to resolve it. +For this it is not important to know how many times (and when) the user changed the entry locally. +It suffices to know that it changed at some point from the last synced version. + +Local revision histories could be helpful in scenarios such as the following: + +1. Device A is offline, and the user changes an entry. +2. The user sends this changed entry to Device B (say, via git). +3. The user further modifies the entry on Device B. +4. The user syncs Device B with the server. +5. The user syncs Device A with the server. + +Without local revisions, it is not possible for Device A to figure out that the entry from the server logically evolved from its own local version. +Instead, it shows a conflict message since the entry changed locally (step 1) and there is a newer revision on the server (from step 4). + +## More Readings + +- [CouchDB style sync and conflict resolution on Postgres with Hasura](https://hasura.io/blog/couchdb-style-conflict-resolution-rxdb-hasura/): Explains how to implement a sync algorithm in the style of CouchDB on your own +- [A Comparison of Offline Sync Protocols and Implementations](https://offlinefirst.org/sync/) +- [Offline data synchronization](https://developer.ibm.com/articles/offline-data-synchronization-strategies/): Discusses different strategies for offline data sync, and when to use which. +- [Transaction Processing: Concepts and Techniques](https://dl.acm.org/doi/10.5555/573304) +- [Transactional Information Systems: Theory, Algorithms, and the Practice of Concurrency Control and Recovery](https://www.sciencedirect.com/book/9781558605084/transactional-information-systems) diff --git a/jabref/docs/code-howtos/remote-storage-sql.md b/jabref/docs/code-howtos/remote-storage-sql.md new file mode 100644 index 00000000..267d0512 --- /dev/null +++ b/jabref/docs/code-howtos/remote-storage-sql.md @@ -0,0 +1,63 @@ +--- +parent: Remote Storage +grand_parent: Code Howtos +--- + +# Remote SQL Storage + +For user documentation, see . + +## Handling large shared databases + +Synchronization times may get long when working with a large database containing several thousand entries. Therefore, synchronization only happens if several conditions are fulfilled: + +* Edit to another field. +* Major changes have been made (pasting or deleting more than one character). + +Class `org.jabref.logic.util.CoarseChangeFilter.java` checks both conditions. + +Remaining changes that have not been synchronized yet are saved at closing the database rendering additional closing time. Saving is realized in `org.jabref.logic.shared.DBMSSynchronizer.java`. Following methods account for synchronization modes: + +* `pullChanges` synchronizes the database unconditionally. +* `pullLastEntryChanges` synchronizes only if there are remaining entry changes. It is invoked when closing the shared database (`closeSharedDatabase`). + +## Database structure + +The following examples base on PostgreSQL. +Other databases work similar. + +The database structure is created at [org.jabref.logic.shared.PostgreSQLProcessor#setUp](https://github.com/JabRef/jabref/blob/main/src/main/java/org/jabref/logic/shared/PostgreSQLProcessor.java#L37-L37). + +```mermaid +erDiagram + ENTRY ||--o{ FIELD : contains + ENTRY { + serial shared_id + varchar type + int version + } + FIELD { + int entry_shared_id + varchar name + text value + } + METADATA { + varchar key + text value + } +``` + +The "secret sauce" is the `version` of an entry. +This version is used as version in the sense of an [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html), which in turn is a well-established technique to prevent conflicts in concurrent business transactions. +It assumes that the chance of conflict is low. +Implementation details are found at . + +The `shared_id` and `version` are handled in [`org.jabref.model.entry.SharedBibEntryData`](https://github.com/JabRef/jabref/blob/main/src/main/java/org/jabref/model/entry/SharedBibEntryData.java). + +## Synchronization + +PostgreSQL supports to register listeners on the database on changes. +(MySQL does not). +The listening is implemented at [`org.jabref.logic.shared.listener.PostgresSQLNotificationListener`](https://github.com/JabRef/jabref/blob/main/src/main/java/org/jabref/logic/shared/listener/PostgresSQLNotificationListener.java#L16). +It "just" fetches updates from the server when a change occurred there. +Thus, the changes are not actively pushed from the server, but still need to be fetched by the client. diff --git a/jabref/docs/code-howtos/remote-storage.md b/jabref/docs/code-howtos/remote-storage.md new file mode 100644 index 00000000..01a402af --- /dev/null +++ b/jabref/docs/code-howtos/remote-storage.md @@ -0,0 +1,15 @@ +--- +parent: Code Howtos +has_children: true +--- +# Remote Storage + +JabRef supports kinds of remote storage: + +- JabDrive +- SQL databases + +The first one is the more modern approach allowing offline-work. +The second approach makes use of the SQL features of databases and require direct online connections. + +More details in [JabDrive](remote-storage-jabdrive.md) and [SQL Storage](remote-storage-sql.md) respectively. diff --git a/jabref/docs/code-howtos/testing.md b/jabref/docs/code-howtos/testing.md new file mode 100644 index 00000000..d32bf51b --- /dev/null +++ b/jabref/docs/code-howtos/testing.md @@ -0,0 +1,167 @@ +--- +parent: Code Howtos +--- +# Testing JabRef + +In JabRef, we mainly rely on basic [JUnit](https://junit.org/junit5/docs/current/user-guide/) unit tests to increase code coverage. + +## General hints on tests + +Imagine you want to test the method `format(String value)` in the class `BracesFormatter` which removes double braces in a given string. + +* _Placing:_ all tests should be placed in a class named `classTest`, e.g. `BracesFormatterTest`. +* _Naming:_ the name should be descriptive enough to describe the whole test. Use the format `methodUnderTest_ expectedBehavior_context` (without the dashes). So for example `formatRemovesDoubleBracesAtBeginning`. Try to avoid naming the tests with a `test` prefix since this information is already contained in the class name. Moreover, starting the name with `test` leads often to inferior test names (see also the [Stackoverflow discussion about naming](http://stackoverflow.com/questions/155436/unit-test-naming-best-practices)). +* _Test only one thing per test:_ tests should be short and test only one small part of the method. So instead of + + ```java + void format() { + assertEqual("test", format("test")); + assertEqual("{test", format("{test")); + assertEqual("test", format("test}}")); + } + ``` + + we would have five tests containing a single `assert` statement and named accordingly (`formatDoesNotChangeStringWithoutBraces`, `formatDoesNotRemoveSingleBrace`, , etc.). See [JUnit AntiPattern](https://exubero.com/junit/anti-patterns/#Multiple_Assertions) for background. +* Do _not just test happy paths_, but also wrong/weird input. +* It is recommended to write tests _before_ you actually implement the functionality (test driven development). +* _Bug fixing:_ write a test case covering the bug and then fix it, leaving the test as a security that the bug will never reappear. +* Do not catch exceptions in tests, instead use the `assertThrows(Exception.class, () -> doSomethingThrowsEx())` feature of [junit-jupiter](https://junit.org/junit5/docs/current/user-guide/) to the test method. + +## Coverage + +IntelliJ has build in test coverage reports. Choose "Run with coverage". + +For a full coverage report as HTML, execute the gradle task `jacocoTestReport` (available in the "verification" folder in IntelliJ). +Then, you will find which shows the coverage of the tests. + +## Lists in tests + +Instead of + +```java +assertTrue(actualList.isEmpty()); +``` + +use + +```java +assertEquals(List.of(), actualList); +``` + +Similarly, to compare lists, instead of following code: + +```java +assertEquals(2, actualList.size()); +assertEquals("a", actualList.get(0)); +assertEquals("b", actualList.get(1)); +``` + +use the following code: + +```java +assertEquals(List.of("a", "b"), actualList); +``` + +## BibEntries in tests + +* Use the `assertEquals` methods in `BibtexEntryAssert` to check that the correct BibEntry is returned. + +## Files and folders in tests + +If you need a temporary file in tests, use the `@TempDir` annotation: + +```java +class TestClass{ + + @Test + void deletionWorks(@TempDir Path tempDir) { + } +} +``` + +to the test class. A temporary file is now created by `Files.createFile(path)`. Using this pattern automatically ensures that the test folder is deleted after the tests are run. See for more details. + +## Loading Files from Resources + +Sometimes it is necessary to load a specific resource or to access the resource directory + +```java +Path resourceDir = Paths.get(MSBibExportFormatTestFiles.class.getResource("MsBibExportFormatTest1.bib").toURI()).getParent(); +``` + +When the directory is needed, it is important to first point to an actual existing file. Otherwise the wrong directory will be returned. + +## Preferences in tests + +If you modify preference, use following pattern to ensure that the stored preferences of a developer are not affected: + +Or even better, try to mock the preferences and insert them via dependency injection. + +```java +@Test +public void getTypeReturnsBibLatexArticleInBibLatexMode() { + // Mock preferences + PreferencesService mockedPrefs = mock(PreferencesService.class); + GeneralPreferences mockedGeneralPrefs = mock(GeneralPReferences.class); + // Switch to BibLatex mode + when(mockedPrefs.getGeneralPrefs()).thenReturn(mockedGeneralPrefs); + when(mockedGeneralPrefs.getDefaultBibDatabaseMode()) + .thenReturn(BibDatabaseMode.BIBLATEX); + + // Now test + EntryTypes biblatexentrytypes = new EntryTypes(mockedPrefs); + assertEquals(BibLatexEntryTypes.ARTICLE, biblatexentrytypes.getType("article")); +} +``` + +To test that a preferences migration works successfully, use the mockito method `verify`. See `PreferencesMigrationsTest` for an example. + +## Database tests + +### PostgreSQL + +To quickly host a local PostgreSQL database, execute following statement: + +```shell +docker run -d -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres -p 5432:5432 --name db postgres:10 postgres -c log_statement=all +``` + +Set the environment variable `DBMS` to `postgres` (or leave it unset) + +Then, all DBMS Tests (annotated with `@org.jabref.testutils.category.DatabaseTest`) run properly. + +### MySQL + +A MySQL DBMS can be started using following command: + +```shell +docker run -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=jabref -p 3800:3307 mysql:8.0 --port=3307 +``` + +Set the environment variable `DBMS` to `mysql`. + +## Fetchers in tests + +Fetcher tests can be run locally by executing the Gradle task `fetcherTest`. This can be done by running the following command in the command line: + +```shell +./gradlew fetcherTest +``` + +Alternatively, if one is using IntelliJ, this can also be done by double-clicking the `fetcherTest` task under the `other` group in the Gradle Tool window (`JabRef > Tasks > other > fetcherTest`). + +## Advanced testing and further reading + +On top of basic unit testing, there are more ways to test a software: + +| Type | Techniques | Tool (Java) | Kind of tests | Used In JabRef | +| -------------- | ------------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------- | +| Functional | Dynamics, black box, positive and negative | [JUnit-QuickCheck](https://github.com/pholser/junit-quickcheck) | Random data generation | No, not intended, because other test kinds seem more helpful. | +| Functional | Dynamics, black box, positive and negative | [GraphWalker](https://graphwalker.github.io) | Model-based | No, because the BibDatabase doesn't need to be tests | +| Functional | Dynamics, black box, positive and negative | [TestFX](https://github.com/TestFX/TestFX) | GUI Tests | Yes | +| Functional | Dynamics, black box, negative | [Lincheck](https://github.com/JetBrains/lincheck) | Testing concurrent algorithms | No | +| Functional | Dynamics, white box, negative | [PIT](https://pitest.org) | Mutation | No | +| Functional | Dynamics, white box, positive and negative | [Mockito](https://site.mockito.org) | Mocking | Yes | +| Non-functional | Dynamics, black box, positive and negative | [JETM](http://jetm.void.fm), [Apache JMeter](https://jmeter.apache.org) | Performance (performance testing vs load testing respectively) | No | +| Structural | Static, white box | [CheckStyle](https://checkstyle.sourceforge.io) | Constient formatting of the source code | Yes | +| Structural | Dynamics, white box | [SpotBugs](https://spotbugs.github.io) | Reocurreing bugs (based on experience of other projects) | No | diff --git a/jabref/docs/code-howtos/tools.md b/jabref/docs/code-howtos/tools.md new file mode 100644 index 00000000..3f57a14a --- /dev/null +++ b/jabref/docs/code-howtos/tools.md @@ -0,0 +1,72 @@ +--- +parent: Code Howtos +--- +# Useful development tooling + +This page lists some software we consider useful. + +## Browser plugins + +* [Refined GitHub](https://github.com/sindresorhus/refined-github) - GitHub on steroids +* [GitHub Issue Link Status](https://github.com/fregante/github-issue-link-status) - proper coloring of linked issues and PRs. +* [Codecov Browser Extension](https://github.com/codecov/browser-extension) - displaying code coverage directly when browsing GitHub +* [Sourcegraph Browser Extension](https://sourcegraph.com/docs/integration/browser_extension) - Navigate through source on GitHub + +## git hints + +Here, we collect some helpful git hints + +* +* [So you need to change your commit](https://github.com/RichardLitt/knowledge/blob/master/github/amending-a-commit-guide.md#so-you-need-to-change-your-commit) +* awesome hints and tools regarding git: + +### Rebase everything as one commit on main + +* Precondition: `JabRef/jabref` is [configured as upstream](https://help.github.com/articles/configuring-a-remote-for-a-fork/). +* Fetch recent commits and prune non-existing branches: `git fetch upstream --prune` +* Merge recent commits: `git merge upstream/main` +* If there are conflicts, resolve them +* Reset index to upstream/main: `git reset upstream/main` +* Review the changes and create a new commit using git gui: `git gui` +* Do a force push: `git push -f origin` + +See also: [https://help.github.com/articles/syncing-a-fork/](https://help.github.com/articles/syncing-a-fork/) + +## Tooling for Windows + +(As Administrator - one time) + +1. Install [chocolatey](https://chocolatey.org) +2. `choco install git.install -y --params "/GitAndUnixToolsOnPath /WindowsTerminal"` +3. `choco install notepadplusplus` +4. If you want to have your JDK also managed via chocolatey: `choco install temurin` + +Then, each weak do `choco upgrade all` to ensure all tooling is kept updated. + +### General git tooling on Windows + +* Use [git for windows](https://git-for-windows.github.io), no additional git tooling required + * [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows) is included. Ensure that you include that in the installation. Aim: Store password for GitHub permanently for `https` repository locations +* [Use notepad++ as editor](http://stackoverflow.com/a/2486342/873282) for `git rebase -i` + +### Better console applications + +#### ConEmu plus clink + +* `choco install conemu clink` +* [ConEmu](http://conemu.github.io) -> Preview Version - Aim: Colorful console with tabs + * At first start: + * "Choose your startup task ...": \`{Bash::Git bash\}} + * `OK` + * Upper right corner: "Settings..." (third entrry Eintrag) + * Startup/Tasks: Choose task no. 7 ("Bash::Git bash"). At "Task parameters" `/dir C:\git-repositories\jabref\jabref` + * `Save Settings` +* [clink](http://mridgers.github.io/clink/) - Aim: Unix keys (Alt+B, Ctrl+S, etc.) also available at the prompt of `cmd.exe` + +#### Other bundles + +* [Cmder](https://cmder.app/) - bundles ConEmu plus clink + +### Tools for working with XMP + +* Validate XMP: [https://www.pdflib.com/pdf-knowledge-base/xmp/free-xmp-validator/](https://www.pdflib.com/pdf-knowledge-base/xmp/free-xmp-validator/) diff --git a/jabref/docs/code-howtos/ui-recommendations.md b/jabref/docs/code-howtos/ui-recommendations.md new file mode 100644 index 00000000..def73591 --- /dev/null +++ b/jabref/docs/code-howtos/ui-recommendations.md @@ -0,0 +1,36 @@ +--- +parent: Code Howtos +--- +# UI Design Recommendations + +* [Designing More Efficient Forms: Structure, Inputs, Labels and Actions](https://uxplanet.org/designing-more-efficient-forms-structure-inputs-labels-and-actions-e3a47007114f) +* [Input form label alignment top or left?](https://ux.stackexchange.com/questions/8480/input-form-label-alignment-top-or-left) + * For a usual form, place the label above the text field + * If the user uses the form often to edit fields, then it might make sense to switch to left-aligned labels + +## Designing GUI Confirmation Dialogs + +1. Avoid asking questions +2. Be as concise as possible +3. Identify the item at risk +4. Name your buttons for the actions + +More information: + +* [StackOverflow: What are some alternatives to the phrase "Are you sure you want to XYZ" in confirmation dialogs?](https://ux.stackexchange.com/q/756/93436). +* JabRef issue discussing Yes/No/Cancel: [koppor#149](https://github.com/koppor/jabref/issues/149). + +### Name your buttons for the actions +`req~ui.dialogs.confirmation.naming~1` + +Needs: impl + +## Form validation + +* Only validate input after leaving the field (or after the user stopped typing for some time) +* The user shouldn't be able to submit the form if there are errors +* However, disabling the submit button in case there are errors is also not optimal. Instead, clicking the submit button should highlight the errors. +* Empty required files should not be marked as invalid until the user a) tried to submit the form or b) focused the field, deleted its contents and then left the field (see [Example](https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_required)). +* Ideally, the error message should be shown below the text field and not as a tooltip (so that users quickly understand what's the problem). For example as [in Boostrap](https://mdbootstrap.com/docs/jquery/forms/validation/?#custom-styles). + + diff --git a/jabref/docs/contributing.md b/jabref/docs/contributing.md new file mode 100644 index 00000000..b93fe7b5 --- /dev/null +++ b/jabref/docs/contributing.md @@ -0,0 +1,6 @@ +--- +nav_order: 2 +--- +# Contributing + +Please head to our [contributing guide in the main repository](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md#contributing). diff --git a/jabref/docs/decisions/0000-use-markdown-architectural-decision-records.md b/jabref/docs/decisions/0000-use-markdown-architectural-decision-records.md new file mode 100644 index 00000000..b8caf9ee --- /dev/null +++ b/jabref/docs/decisions/0000-use-markdown-architectural-decision-records.md @@ -0,0 +1,30 @@ +--- +parent: Decision Records +nav_order: 0 +--- +# Use Markdown Architectural Decision Records + +## Context and Problem Statement + +We want to record architectural decisions made in this project independent whether decisions concern the architecture ("architectural decision record"), the code, or other fields. +Which format and structure should these records follow? + +## Considered Options + +* [MADR](https://adr.github.io/madr/) 4.0.0 – The Markdown Architectural Decision Records +* [Michael Nygard's template](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) – The first incarnation of the term "ADR" +* [Sustainable Architectural Decisions](https://www.infoq.com/articles/sustainable-architectural-design-decisions) – The Y-Statements +* Other templates listed at +* Formless – No conventions for file format and structure + +## Decision Outcome + +Chosen option: "MADR 4.0.0", because + +* Implicit assumptions should be made explicit. + Design documentation is important to enable people understanding the decisions later on. + See also ["A rational design process: How and why to fake it"](https://doi.org/10.1109/TSE.1986.6312940). +* MADR allows for structured capturing of any decision. +* The MADR format is lean and fits our development style. +* The MADR structure is comprehensible and facilitates usage & maintenance. +* The MADR project is vivid. diff --git a/jabref/docs/decisions/0001-use-crowdin-for-translations.md b/jabref/docs/decisions/0001-use-crowdin-for-translations.md new file mode 100644 index 00000000..6f07d2c4 --- /dev/null +++ b/jabref/docs/decisions/0001-use-crowdin-for-translations.md @@ -0,0 +1,20 @@ +--- +parent: Decision Records +nav_order: 1 +--- +# Use Crowdin for translations + +## Context and Problem Statement + +The JabRef UI is offered in multiple languages. It should be easy for translators to translate the strings. + +## Considered Options + +* Use [Crowdin](http://crowdin.com/) +* Use [popeye](https://github.com/JabRef/popeye) +* Use [Lingohub](https://lingohub.com/) +* Keep current GitHub flow. See the [Step-by-step guide](https://docs.jabref.org/contributing/how-to-translate-the-ui). + +## Decision Outcome + +Chosen option: "Use Crowdin", because Crowdin is easy to use, integrates in our GitHub workflow, and is free for OSS projects. diff --git a/jabref/docs/decisions/0002-use-slf4j-for-logging.md b/jabref/docs/decisions/0002-use-slf4j-for-logging.md new file mode 100644 index 00000000..b1aa67cd --- /dev/null +++ b/jabref/docs/decisions/0002-use-slf4j-for-logging.md @@ -0,0 +1,50 @@ +--- +parent: Decision Records +nav_order: 2 +--- +# Use slf4j together with log4j2 for logging + +## Context and Problem Statement + +Up to version 4.1 JabRef uses apache-commons-logging 1.2 for logging errors and messages. However, this is not compatible with java 9 and is superseded by log4j. + +## Decision Drivers + +* SLF4J provides a façade for several logging frameworks, including log4j and supports already java 9 +* Log4j is already defined as dependency and slf4j has already been required by a third party dependency + +## Considered Alternatives + +* [Log4j2](https://logging.apache.org/log4j/2.x/) +* [SLF4J with Log4j2 binding](https://logging.apache.org/log4j/2.x/maven-artifacts.html) +* [SLF4J with Logback binding](https://logback.qos.ch/) + +## Decision Outcome + +Chosen option: "SLF4J with Log4j2 binding", because comes out best \(see below\). + +## Pros and Cons of the Options + +### Log4j2 + +* Good, because dependency already exists +* Good, because Java 9 support since version 2.10 +* Bad, because direct dependency + +### SLF4J with log4j2 binding + +* Good, because it only requires minimal changes to our logging infrastructure +* Good, because Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fixing some inherent problems in Logback’s architecture. +* Good, because supports other loggers as well +* Good, because Java 9 support +* Good, because already defined +* Good, because migration tool available +* Good, because it is a façade for several loggers. Thus, the underlying implementation can easily be changed in the future. +* Bad, because logger statements require a slight different syntax + +### SLF4J with Logback binding + +* Good, because migration tool available +* Good, because native implementation of slf4j +* Bad, because Java 9 support only available in alpha +* Bad, because different syntax than log4j/commons logging diff --git a/jabref/docs/decisions/0003-use-gradle-as-build-tool.md b/jabref/docs/decisions/0003-use-gradle-as-build-tool.md new file mode 100644 index 00000000..e805f23e --- /dev/null +++ b/jabref/docs/decisions/0003-use-gradle-as-build-tool.md @@ -0,0 +1,64 @@ +--- +parent: Decision Records +nav_order: 3 +--- +# Use Gradle as build tool + +## Context and Problem Statement + +Which build tool should be used? + +## Considered Options + +* [Maven](https://maven.apache.org/) +* [Gradle](https://gradle.org/) +* [Ant](https://ant.apache.org/) + +## Decision Outcome + +Chosen option: "Gradle", because it is lean and fits our development style. + +## Pros and Cons of the Options + +### Maven + +* Good, because [there is a plugin for almost everything](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) +* Good, because [it has good integration with third party tools](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Good, because [it has robust performance](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Good, because [it has a high popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Good, [if one favors declarative over imperative](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) +* Bad, because [getting a dependency list is not straight forward](https://stackoverflow.com/q/1677473/873282) +* Bad, because [it based on a fixed and linear model of phases](https://dzone.com/articles/gradle-vs-maven) +* Bad, because [it is hard to customize](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) +* Bad, because [it needs plugins for everything](https://www.slant.co/versus/2107/11592/~apache-maven_vs_gradle) +* Bad, because [it is verbose leading to huge build files](https://technologyconversations.com/2014/06/18/build-tools/) + +### Gradle + +* Good, because [its build scripts are short](https://technologyconversations.com/2014/06/18/build-tools/) +* Good, because [it follows the convention over configuration approach](https://www.safaribooksonline.com/library/view/building-and-testing/9781449306816/ch04.html) +* Good, because [it offers a graph-based task dependencies](https://dzone.com/articles/gradle-vs-maven) +* Good, because [it is easy to customize](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Good, because [it offers custom dependency scopes](https://gradle.org/maven-vs-gradle/) +* Good, because [it has good community support](https://linuxhint.com/ant-vs-maven-vs-gradle/) +* Good, because [its performance can be 100 times more than maven's performance](https://gradle.org/gradle-vs-maven-performance/). +* Bad, because [not that many plugins are available/maintained yet](https://phauer.com/2018/moving-back-from-gradle-to-maven/) +* Bad, because [it lacks a wide variety of application server integrations](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Bad, because [it has a medium popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Bad, because [it allows custom build scripts which need to be debugged](https://www.softwareyoga.com/10-reasons-why-we-chose-maven-over-gradle/) + +### Ant + +* Good, because [it offers a lot of control over the build process](https://technologyconversations.com/2014/06/18/build-tools/) +* Good, because [it has an agile dependency manager](https://blog.alejandrocelaya.com/2014/02/22/dependency-management-in-java-projects-with-ant-and-ivy/) +* Good, because [it has a low learning curve](https://technologyconversations.com/2014/06/18/build-tools/) +* Bad, because [build scripts can quickly become huge](https://technologyconversations.com/2014/06/18/build-tools/) +* Bad, because [everything has to be written from scratch](http://www.baeldung.com/ant-maven-gradle) +* Bad, because [no conventions are enforced which can make it hard to understand someone else's build script](http://www.baeldung.com/ant-maven-gradle) +* Bad, because [it has nearly no community support](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Bad, because [it has a low popularity](https://fdocuments.us/reader/full/java-build-tools-part-2) +* Bad, because [it offers too much freedom](https://www.slant.co/versus/2106/2107/~apache-ant_vs_apache-maven) + +## Links + +* GADR: [https://github.com/adr/gadr-java/blob/master/gadr-java--build-tool.md](https://github.com/adr/gadr-java/blob/master/gadr-java--build-tool.md) diff --git a/jabref/docs/decisions/0004-use-mariadb-connector.md b/jabref/docs/decisions/0004-use-mariadb-connector.md new file mode 100644 index 00000000..e1ef54c6 --- /dev/null +++ b/jabref/docs/decisions/0004-use-mariadb-connector.md @@ -0,0 +1,37 @@ +--- +parent: Decision Records +nav_order: 4 +--- +# Use MariaDB Connector + +## Context and Problem Statement + +JabRef needs to connect to a MySQL database. See [Shared SQL Database](https://docs.jabref.org/collaborative-work/sqldatabase) for more information. + +## Considered Options + +* Use MariaDB Connector +* Use MySQL Connector + +Other alternatives are listed at [https://stackoverflow.com/a/31312280/873282](https://stackoverflow.com/a/31312280/873282). + +## Decision Outcome + +Chosen option: "Use MariaDB Connector", because comes out best \(see below\). + +## Pros and Cons of the Options + +### Use MariaDB Connector + +The [MariaDB Connector](https://mariadb.com/kb/en/library/about-mariadb-connector-j/) is a LGPL-licensed JDBC driver to connect to MySQL and MariaDB. + +* Good, because can be used as drop-in replacement for MySQL connector + +### Use MySQL Connector + +The [MySQL Connector](https://www.mysql.com/de/products/connector/) is distributed by Oracle and licensed under GPL-2. Source: [https://github.com/mysql/mysql-connector-j/blob/release/9.x/LICENSE](https://github.com/mysql/mysql-connector-j/blob/release/9.x/LICENSE). Oracle added the [Universal FOSS Exception, Version 1.0](https://oss.oracle.com/licenses/universal-foss-exception/) to it, which seems to limit the effects of GPL. More information on the FOSS Exception are available at [https://www.mysql.com/de/about/legal/licensing/foss-exception/](https://www.mysql.com/de/about/legal/licensing/foss-exception/). + +* Good, because it stems from the same development team than MySQL +* Bad, because the "Universal FOSS Exception" makes licensing more complicated. + + diff --git a/jabref/docs/decisions/0005-fully-support-utf8-only-for-latex-files.md b/jabref/docs/decisions/0005-fully-support-utf8-only-for-latex-files.md new file mode 100644 index 00000000..d1c27170 --- /dev/null +++ b/jabref/docs/decisions/0005-fully-support-utf8-only-for-latex-files.md @@ -0,0 +1,49 @@ +--- +parent: Decision Records +nav_order: 5 +--- +# Fully Support UTF-8 Only For LaTeX Files + +## Context and Problem Statement + +The feature [search for citations](https://github.com/JabRef/user-documentation/issues/210) displays the content of LaTeX files. The LaTeX files are text files and might be encoded arbitrarily. + +## Considered Options + +* Support UTF-8 encoding only +* Support ASCII encoding only +* Support \(nearly\) all encodings + +## Decision Outcome + +Chosen option: "Support UTF-8 encoding only", because comes out best \(see below\). + +### Positive Consequences + +* All content of LaTeX files are displayed in JabRef + +### Negative Consequences + +* When a LaTeX files is encoded in another encoding, the user might see strange characters in JabRef + +## Pros and Cons of the Options + +### Support UTF-8 encoding only + +* Good, because covers most tex file encodings +* Good, because easy to implement +* Bad, because does not support encodings used before around 2010 + +### Support ASCII encoding only + +* Good, because easy to implement +* Bad, because does not support any encoding at all + +### Support \(nearly\) all encodings + +* Good, because easy to implement +* Bad, because it relies on Apache Tika's `CharsetDetector`, which resides in `tika-parsers`. + + This causes issues during compilation \(see [https://github.com/JabRef/jabref/pull/3421\#issuecomment-524532832](https://github.com/JabRef/jabref/pull/3421#issuecomment-524532832)\). + + Example: `error: module java.xml.bind reads package javax.activation from both java.activation and jakarta.activation`. diff --git a/jabref/docs/decisions/0006-only-translated-strings-in-language-file.md b/jabref/docs/decisions/0006-only-translated-strings-in-language-file.md new file mode 100644 index 00000000..8234c481 --- /dev/null +++ b/jabref/docs/decisions/0006-only-translated-strings-in-language-file.md @@ -0,0 +1,51 @@ +--- +parent: Decision Records +nav_order: 6 +--- +# Only translated strings in language file + +## Context and Problem Statement + +JabRef has translation files `JabRef_it.properties`, ... There are translated and untranslated strings. Which ones should be in the translation file? + +## Decision Drivers + +* Translators should find new strings to translate easily +* New strings to translate should be written into `JabRef_en.properties` to enable translation by the translators +* Crowdin should be kept as translation platform, because 1\) it is much easier for the translators than the GitHub workflow and 2\) it is free for OSS projects. + +## Considered Options + +* Only translated strings in language file +* Translated and untranslated strings in language file, have value the untranslated string to indicate untranslated +* Translated and untranslated strings in language file, have empty to indicate untranslated + +## Decision Outcome + +Chosen option: "Only translated strings in language file", because comes out best (see below). + +## Pros and Cons of the Options + +### Only translated strings in language file + +* Good, because Crowdin supports it +* Bad, because translators need tooling to see untranslated strings +* Bad, because issues with FXML \([https://github.com/JabRef/jabref/issues/3796](https://github.com/JabRef/jabref/issues/3796)\) + +### Translated and untranslated strings in language file, have value the untranslated string to indicate untranslated + +* Good, because no issues with FXML +* Good, because Crowdin supports it +* Bad, because untranslated strings cannot be identified easily in Latin languages + +### Translated and untranslated strings in language file, have empty to indicate untranslated + +* Good, because untranslated strings can be identified easily +* Good, because works with FMXL \(?\) +* Bad, because Crowdin does not support it + +## Links + +* Related to [ADR-0001](0001-use-crowdin-for-translations.md). + + diff --git a/jabref/docs/decisions/0007-human-readable-changelog.md b/jabref/docs/decisions/0007-human-readable-changelog.md new file mode 100644 index 00000000..9753bc28 --- /dev/null +++ b/jabref/docs/decisions/0007-human-readable-changelog.md @@ -0,0 +1,26 @@ +--- +parent: Decision Records +nav_order: 7 +--- +# Provide a human-readable changelog + +## Context and Problem Statement + +Changes of a release have to be communicated. How and which style to use? + +## Considered Options + +* Keep-a-changelog format with freedom in the bullet points +* Keep-a-changelog format and fixed terms + +## Decision Outcome + +Chosen option: "Keep-a-changelog format with freedom in the bullet points", because + +* [Keep-a-changelog](https://keepachangelog.com/) structures the changelog +* Each entry can be structured to be understandable +* Forcing to prefix each line with `We fixed`, `We changed`, ... seems to be read strange. + + We nevertheless try to follow that style. + +Further discussion can be found at [\#2277](https://github.com/JabRef/jabref/issues/2277). diff --git a/jabref/docs/decisions/0008-use-public-final-instead-of-getters.md b/jabref/docs/decisions/0008-use-public-final-instead-of-getters.md new file mode 100644 index 00000000..e18becde --- /dev/null +++ b/jabref/docs/decisions/0008-use-public-final-instead-of-getters.md @@ -0,0 +1,26 @@ +--- +parent: Decision Records +nav_order: 8 +--- +# Use `public final` instead of getters to offer access to immutable variables + +## Context and Problem Statement + +When making immutable data accessible in a java class, should it be using getters or by non-modifiable fields? + +## Considered Options + +* Offer public static field +* Offer getters + +## Decision Outcome + +Chosen option: "Offer public static field", because getters used to be a convention which was even more manifested due to libraries depending on the existence on getters/setters. In the case of immutable variables, adding public getters is just useless since one is not hiding anything. + +### Positive Consequences + +* Shorter code + +### Negative Consequences + +* newcomers could get confused, because getters/setters are still taught diff --git a/jabref/docs/decisions/0009-use-plain-junit5-for-testing.md b/jabref/docs/decisions/0009-use-plain-junit5-for-testing.md new file mode 100644 index 00000000..5ca034a3 --- /dev/null +++ b/jabref/docs/decisions/0009-use-plain-junit5-for-testing.md @@ -0,0 +1,87 @@ +--- +parent: Decision Records +nav_order: 9 +--- +# Use Plain JUnit5 for advanced test assertions + +## Context and Problem Statement + +How to write readable test assertions? +How to write readable test assertions for advanced tests? + +## Considered Options + +* Plain JUnit5 +* Hamcrest +* AssertJ + +## Decision Outcome + +Chosen option: "Plain JUnit5", because comes out best \(see below\). + +### Positive Consequences + +* Tests are more readable +* More easy to write tests +* More readable assertions + +### Negative Consequences + +* More complicated testing leads to more complicated assertions + +## Pros and Cons of the Options + +### Plain JUnit5 + +Homepage: +JabRef testing guidelines: <../testing.md> + +Example: + +```java +String actual = markdownFormatter.format(source); +assertTrue(actual.contains("Markup
")); +assertTrue(actual.contains("
  • list item one
  • ")); +assertTrue(actual.contains("
  • list item 2
  • ")); +assertTrue(actual.contains("> rest")); +assertFalse(actual.contains("\n")); +``` + +* Good, because Junit5 is "common Java knowledge" +* Bad, because complex assertions tend to get hard to read +* Bad, because no fluent API + +### Hamcrest + +Homepage: + +* Good, because offers advanced matchers (such as `contains`) +* Bad, because not full fluent API +* Bad, because entry barrier is increased + +### AssertJ + +Homepage: + +Example: + +```java +assertThat(markdownFormatter.format(source)) + .contains("Markup
    ") + .contains("
  • list item one
  • ") + .contains("
  • list item 2
  • ") + .contains("> rest") + .doesNotContain("\n"); +``` + +* Good, because offers fluent assertions +* Good, because allows partial string testing to focus on important parts +* Good, because assertions are more readable +* Bad, because not commonly used +* Bad, because newcomers have to learn an additional language to express test cases +* Bad, because entry barrier is increased +* Bad, because expressions of test cases vary from unit test to unit test + +## Links + +* German comparison between Hamcrest and AssertJ: diff --git a/jabref/docs/decisions/0010-use-h2-as-internal-database.md b/jabref/docs/decisions/0010-use-h2-as-internal-database.md new file mode 100644 index 00000000..e19640fb --- /dev/null +++ b/jabref/docs/decisions/0010-use-h2-as-internal-database.md @@ -0,0 +1,28 @@ +--- +parent: Decision Records +nav_order: 10 +--- +# Use H2 as Internal SQL Database + +## Context and Problem Statement + +We need to store data internally in a structured way to gain performance. + +## Decision Drivers + +* Easy to integrate +* Easy to use +* Common technology + +## Considered Options + +* [H2 Database Engine](http://www.h2database.com/html/main.html) +* [SQLite](https://www.sqlite.org/index.html) + +## Decision Outcome + +Chosen option: "H2 Database Engine", because it was straight-forward to use. + +## Links + +* [Comparison at SQL Workbench](https://www.sql-workbench.eu/dbms_comparison.html) diff --git a/jabref/docs/decisions/0011-test-external-links-in-documentation.md b/jabref/docs/decisions/0011-test-external-links-in-documentation.md new file mode 100644 index 00000000..eb9d5333 --- /dev/null +++ b/jabref/docs/decisions/0011-test-external-links-in-documentation.md @@ -0,0 +1,52 @@ +--- +parent: Decision Records +nav_order: 11 +--- +# Test external links in documentation + +## Context and Problem Statement + +The JabRef repository contains Markdown (`.md`) files documenting the JabRef code. +The documentation contains links to external resources. +For high-quality documentation, external links should be working. + +## Decision Drivers + +* Checking external links should not cause issues in the normal workflow + +## Considered Options + +* Check external links once a month +* Check external links in the "checkstyle" task +* Do not check external links + +## Decision Outcome + +Chosen option: "Check external links once a month", because it provides a basic quality baseline. + +### Positive Consequences + +* Automatic notification of broken external links + +### Negative Consequences + +* Some external sites need to [be disabled](https://github.com/JabRef/jabref/pull/6542/files). For instance, GitHub.com always returns "forbidden". +* Contributors find it strange if external links are broken (example: [user-documentation#526](https://github.com/JabRef/user-documentation/pull/526#issuecomment-2416462977)) + +## Pros and Cons of the Options + +### Check external links once a month + +* Good, because does not interfere with the normal development workflow +* Bad, because an additional workflow is required + +### Check external links in the "checkstyle" task + +* Good, because no separate workflow is required +* Bad, because checks fail independent of the PR (because external web sites can go down and go up independent of a PR) + +### Do not check external links + +* Good, because no testing at all is required +* Bad, because external links break without any notice +* Bad, because external links have to be checked manually diff --git a/jabref/docs/decisions/0012-handle-different-bibEntry-formats-of-fetchers.md b/jabref/docs/decisions/0012-handle-different-bibEntry-formats-of-fetchers.md new file mode 100644 index 00000000..3f454d61 --- /dev/null +++ b/jabref/docs/decisions/0012-handle-different-bibEntry-formats-of-fetchers.md @@ -0,0 +1,60 @@ +--- +parent: Decision Records +nav_order: 12 +--- +# Handle different bibentry formats of fetchers by adding a layer + +## Context and Problem Statement + +All fetchers (except IDFetchers) in JabRef return BibEntries when fetching entries from their API. +Some fetchers directly receive BibTeX entries from their API, the other fetchers receive their entries in some kind of exchange format such as JSON or XML and then parse this into BibEntries. +Currently, all fetchers either return BibEntries in BibTeX or BibLaTeX format. +This can lead to importing BibEntries of one format in a database of the other format. +How can this inconsistency between fetchers, and their used formats be addressed? + +## Considered Options + +* Pass fetchers the format, they have to create entries accordingly (in the correct format). +* Pass fetchers the format, they have to call a conversion method if necessary (in the correct format). +* Let the caller handle any format inconsistencies and the conversion. +* Introduce a new layer between fetchers and caller, such as a FetcherHandler, that manages the conversion + +## Decision Outcome + +Chosen option: "Introduce a new layer between fetchers and caller, such as a FetcherHandler, that manages the conversion", +because it can compose all steps required during importing, not only format conversion of fetched entries. +[As described here (comment)](https://github.com/JabRef/jabref/pull/6687) + +## Pros and Cons of the Options + +### Introduce a new layer between fetchers and caller, such as a FetcherHandler, that manages the conversion + +* Good, because fetchers do not have to think about conversion (Separation of concerns) +* Good, because no other code that currently relies on fetchers has to do the conversion +* Good, because this layer can be used for any kind of import to handle all conversion steps (not only format). [As described here (comment)](https://github.com/JabRef/jabref/pull/6687) +* Good, because this layer can easily be extended if the import procedure changes +* Bad, because this requires a lot of code changes +* Bad, because this has to be tested extensively + +### Pass fetchers the format, they have to call a conversion method if necessary + +* Good, because less code has to be written than with option "Pass fetchers the format, they have to create entries accordingly" +* Good, because code is already tested +* Good, because keeps all conversion code centralized (code reuse) +* Bad, because fetcher first creates the BibEntry in a possibly "wrong" format, this can easily lead to bugs due to e.g. code changes +* Bad, because adds dependency + +### Pass fetchers the format, they have to create entries accordingly + +* Good, because fetchers already handle BibEntry creation (in their format of choice). This is part of his responsibility. +* Good, because fetchers only create BibEntries of the "correct" format. At no point there exists the chance of the wrong format being passed on due to e.g. code changes. +* Good, because the conversion does not have to take place +* Bad, because fetcher has to "know" all differences of the formats -> clutters the code. +* Bad, because this code has to be tested. Conversion already exists. + +### Let the caller handle any format inconsistencies and the conversion + +* Good, because fetcher code does not have to change +* Good, because fetcher only has to fetch and does not need to know anything about the formats +* Bad, because programmers might assume that a certain format is used, e.g. the preferred format (which would not work as the databases that imports the entries does not have to conform to the preferred format) +* Bad, because at every place where fetchers are used, and the format matters, conversion has to be used, creating more dependencies diff --git a/jabref/docs/decisions/0013-add-native-support-biblatex-software.md b/jabref/docs/decisions/0013-add-native-support-biblatex-software.md new file mode 100644 index 00000000..63796078 --- /dev/null +++ b/jabref/docs/decisions/0013-add-native-support-biblatex-software.md @@ -0,0 +1,52 @@ +--- +parent: Decision Records +nav_order: 13 +--- +# Add Native Support for BibLatex-Software + +* Deciders: Oliver Kopp + +Technical Story: [6574-Adding support for biblatex-software](https://github.com/JabRef/jabref/issues/6574) + +## Context and Problem Statement + +Right now, JabRef does not have support for Biblatex-Software out of the box, users have to add custom entry types. +With citing software becoming fairly common, native support is helpful. + +## Decision Drivers + +* None of the existing flows should be impacted + +## Considered Options + +* Add the new entry types to the existing biblatex types +* Add a divider with label Biblatex-Software under which the new entries are listed: Native support for Biblatex-Software +* Support via customized entry types: A user can load a customized bib file + +## Decision Outcome + +Chosen option: "Add a new divider", because comes out best (see below). + +### Positive Consequences + +* Inbuilt coverage for a entry type that is getting more and more importance + +### Negative Consequences + +* Adds a little bit more clutter to the Add Entry pane + +## Pros and Cons of the Options + +### Add the new entry types to the existing biblatex types + +* Good, because there is no need for a new category in the add entry pane + +### Add a divider with label Biblatex-Software under which the new entries are listed: Native support for Biblatex-Software + +* Good, since this gives the user a bit more clarity + +### Support via customized entry types: A user can load a customized bib file + +* Good, because no code needs to be changed +* Bad, because documentation is needed +* Bad, because the users are not guided through the UI, but have to do other steps. diff --git a/jabref/docs/decisions/0014-separate-URL-creation-to-enable-proper-logging.md b/jabref/docs/decisions/0014-separate-URL-creation-to-enable-proper-logging.md new file mode 100644 index 00000000..8a13e206 --- /dev/null +++ b/jabref/docs/decisions/0014-separate-URL-creation-to-enable-proper-logging.md @@ -0,0 +1,108 @@ +--- +parent: Decision Records +nav_order: 14 +--- +# Separate URL creation to enable proper logging + +## Context and Problem Statement + +Fetchers are failing. +The reason why they are failing needs to be investigated. + +* Claim 1: Knowing the URL which was used to query the fetcher eases debugging +* Claim 2: Somehow logging the URL eases debugging (instead of showing it in the debugger only) + +How to properly log the URL used for fetching? + +## Decision Drivers + +* Code should be easy to read +* Include URL in the exception instead of logging in case an exception is thrown already (see ) + +## Considered Options + +* Separate URL creation +* Create URL when logging the URL +* Include URL creation as statement before the stream creation in the try-with-resources block + +## Decision Outcome + +Chosen option: "Separate URL creation", because comes out best \(see below\). + +## Pros and Cons of the Options + +### Separate URL creation + +```java + URL urlForQuery; + try { + urlForQuery = getURLForQuery(query); + } catch (URISyntaxException | MalformedURLException | FetcherException e) { + throw new FetcherException(String.format("Search URI %s is malformed", query), e); + } + try (InputStream stream = getUrlDownload(complexQueryURL).asInputStream()) { + ... + } catch (IOException e) { + throw new FetcherException("A network error occurred while fetching from " + urlForQuery.toString(), e); + } catch (ParseException e) { + throw new FetcherException("An internal parser error occurred while fetching from " + urlForQuery.toString(), e); + } +``` + +* Good, because exceptions thrown at method are directly caught +* Good, because exceptions in different statements belong to different catch blocks +* Good, because code to determine URL is written once +* OK, because "Java by Comparison" does not state anything about it +* Bad, because multiple try/catch statements are required +* Bad, because this style seems to be uncommon to Java coders + +### Create URL when logging the URL + +The "logging" is done when throwing the exception. + +Example code: + +```java + try (InputStream stream = getUrlDownload(getURLForQuery(query)).asInputStream()) { + ... + } catch (URISyntaxException | MalformedURLException | FetcherException e) { + throw new FetcherException(String.format("Search URI %s is malformed", query), e); + } catch (IOException e) { + try { + throw new FetcherException("A network error occurred while fetching from " + getURLForQuery(query), e); + } catch (URISyntaxException | MalformedURLException uriSyntaxException) { + // does not happen + throw new FetcherException("A network error occurred", e); + } + } catch (ParseException e) { + try { + throw new FetcherException("An internal parser error occurred while fetching from " + getURLForQuery(query), e); + } catch (URISyntaxException | MalformedURLException uriSyntaxException) { + // does not happen + throw new FetcherException("An internal parser error occurred", e); + } + } +``` + +* Good, because code inside the `try` statement stays the same +* OK, because "Java by Comparison" does not state anything about it +* Bad, because an additional try/catch-block is added to each catch statement +* Bad, because needs a `throw` statement in the `URISyntaxException` catch block (even though at this point the exception cannot be thrown), because Java otherwise misses a `return` statement. + +### Include URL creation as statement before the stream creation in the try-with-resources block + +```java + try (URL urlForQuery = getURLForQuery(query); InputStream stream = urlForQuery.asInputStream()) { + ... + } catch (URISyntaxException | MalformedURLException | FetcherException e) { + throw new FetcherException(String.format("Search URI %s is malformed", query), e); + } catch (IOException e) { + throw new FetcherException("A network error occurred while fetching from " + urlForQuery.toString(), e); + } catch (ParseException e) { + throw new FetcherException("An internal parser error occurred while fetching from " + urlForQuery.toString(), e); + } +``` + +* Good, because the single try/catch-block can be kept +* Good, because logical flow is kept +* Bad, because does not compile (because URL is not an `AutoClosable`) diff --git a/jabref/docs/decisions/0015-support-an-abstract-query-syntax-for-query-conversion.md b/jabref/docs/decisions/0015-support-an-abstract-query-syntax-for-query-conversion.md new file mode 100644 index 00000000..5b51e0cc --- /dev/null +++ b/jabref/docs/decisions/0015-support-an-abstract-query-syntax-for-query-conversion.md @@ -0,0 +1,73 @@ +--- +parent: Decision Records +nav_order: 15 +--- +# Query syntax design + +## Context and Problem Statement + +All libraries use their own query syntax for advanced search options. To increase usability, users should be able to formulate their (abstract) search queries in a query syntax that can be mapped to the library specific search queries. To achieve this, the query has to be parsed into an AST. + +Which query syntax should be used for the abstract queries? +Which features should the syntax support? + +## Considered Options + +* Use a simplified syntax that is derived of the [lucene](https://lucene.apache.org/core/8_6_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html) query syntax +* Formulate a own query syntax + +## Decision Outcome + +Chosen option: "Use a syntax that is derived of the lucene query syntax", because only option that is already known, and easy to implement. +Furthermore parsers for lucene already exist and are tested. +For simplicity, and lack of universal capabilities across fetchers, only basic query features and therefor syntax is supported: + +* All terms in the query are whitespace separated and will be ANDed +* Default and certain fielded terms are supported +* Fielded Terms: + * `author` + * `title` + * `journal` + * `year` (for single year) + * `year-range` (for range e.g. `year-range:2012-2015`) +* The `journal`, `year`, and `year-range` fields should only be populated once in each query +* The `year` and `year-range` fields are mutually exclusive +* Example: + * `author:"Igor Steinmacher" author:"Christoph Treude" year:2017` will be converted to + * `author:"Igor Steinmacher" AND author:"Christoph Treude" AND year:2017` + +The supported syntax can be expressed in EBNF as follows: + +Query := {Clause} \ +Clause:= \[Field\] Term \ +Field := author: | title: | journal: | year: | year-range: | default:\ +Term := Word | Phrase \ + +Word can be derived to any series of non-whitespace characters. +Phrases are multiple words wrapped in quotes and may contain white-space characters within the quotes.\ +Note: Even though this EBNF syntactically allows the creation of queries with year and year-range fields, +such a query does not make sense semantically and therefore will not be executed. + +### Positive Consequences + +* Already tested +* Well known +* Easy to implement +* Can use an existing parser + +## Pros and Cons of the Options + +### Use a syntax that is derived of the lucene query syntax + +* Good, because already exists +* Good, because already well known +* Good, because there already exists a [parser for lucene syntax](https://lucene.apache.org/core/8_0_0/queryparser/org/apache/lucene/queryparser/flexible/standard/StandardQueryParser.html) +* Good, because capabilities of query conversion can easily be extended using the [flexible lucene framework](https://lucene.apache.org/core/8_0_0/queryparser/org/apache/lucene/queryparser/flexible/core/package-summary.html) + +### Formulate a own query syntax + +* Good, because allows for flexibility +* Bad, because needs a new parser (has to be decided whether to use [ANTLR](https://www.antlr.org/), [JavaCC](https://javacc.github.io/javacc/), or [LogicNG](https://github.com/logic-ng/LogicNG)) +* Bad, because has to be tested +* Bad, because syntax is not well known +* Bad, because the design should be easily extensible, requires an appropriate design (high effort) diff --git a/jabref/docs/decisions/0016-mutable-preferences-objects.md b/jabref/docs/decisions/0016-mutable-preferences-objects.md new file mode 100644 index 00000000..74822d48 --- /dev/null +++ b/jabref/docs/decisions/0016-mutable-preferences-objects.md @@ -0,0 +1,20 @@ +--- +parent: Decision Records +nav_order: 16 +--- +# Mutable preferences objects + +## Context and Problem Statement + +To create an immutable preferences object every time seems to be a waste of time and computer memory. + +## Considered Options + +* Alter the existing object and return it (by a `with*` -method, similar to a builder, but changing the object at hand). +* Create a new object every time a preferences object should be altered. + +## Decision Outcome + +Chosen option: "Alter the exiting object", because the preferences objects are just wrappers around the basic preferences framework of JDK. They +should be mutable on-the-fly similar to objects with a Builder inside and to be stored immediately again in the +preferences. diff --git a/jabref/docs/decisions/0017-allow-model-access-logic.md b/jabref/docs/decisions/0017-allow-model-access-logic.md new file mode 100644 index 00000000..d254d0b1 --- /dev/null +++ b/jabref/docs/decisions/0017-allow-model-access-logic.md @@ -0,0 +1,48 @@ +--- +parent: Decision Records +nav_order: 17 +--- +# Allow org.jabref.model to access org.jabref.logic + +## Context and Problem Statement + +- How to create a maintainable architecture? +- How to split model, logic, and UI + +## Decision Drivers + +- Newcomers should find the architecture "split" natural +- The architecture should be a help (and not a burden) + +## Considered Options + +- `org.jabref.model` uses `org.jabref.model` (and external libraries) only +- `org.jabref.model` may use `org.jabref.logic` in defined cases +- `org.jabref.model` and `org.jabref.logic` may access each other freely + +## Decision Outcome + +Chosen option: "`org.jabref.model` may use `org.jabref.logic` in defined cases", because comes out best \(see below\). + +## Pros and Cons of the Options + +### `org.jabref.model` uses `org.jabref.model` (and external libraries) only + +The model package does not access logic or other packages of JabRef. +Access to classes of external libraries is allowed. +The logic package may use the model package. + +- Good, because clear separation of model and logic +- Bad, because this leads to an [Anemic Domain Model](https://martinfowler.com/bliki/AnemicDomainModel.html) + +### `org.jabref.model` may use `org.jabref.logic` in defined cases + +- Good, because model and logic are still separated +- Neutral, because each exception has to be discussed and agreed +- Bad, because newcomers have to be informed that there are certain (agreed) exceptions for model to access logic + +### `org.jabref.model` and `org.jabref.logic` may access each other freely + +- Bad, because may lead to spaghetti code +- Bad, because coupling between model and logic is increased +- Bad, because cohesion inside model is decreased diff --git a/jabref/docs/decisions/0018-use-regular-expression-to-split-multiple-sentence-titles.md b/jabref/docs/decisions/0018-use-regular-expression-to-split-multiple-sentence-titles.md new file mode 100644 index 00000000..21d6a269 --- /dev/null +++ b/jabref/docs/decisions/0018-use-regular-expression-to-split-multiple-sentence-titles.md @@ -0,0 +1,29 @@ +--- +parent: Decision Records +nav_order: 18 +--- +# Use regular expression to split multiple-sentence titles + +## Context and Problem Statement + +Some entry titles are composed of multiple sentences, for example: "Whose Music? A Sociology of Musical Language", therefore, it is necessary to first split the title into sentences and process them individually to ensure proper formatting using '[Sentence Case](https://en.wiktionary.org/wiki/sentence_case)' or '[Title Case](https://en.wiktionary.org/wiki/title_case#English)' + +## Considered Options + +* [Regular expression](https://docs.oracle.com/javase/tutorial/essential/regex/) +* [OpenNLP](https://opennlp.apache.org/) +* [ICU4J](https://web.archive.org/web/20210413013221/http://site.icu-project.org/home) + +## Decision Outcome + +Chosen option: "Regular expression", because we can use Java internal classes (Pattern, Matcher) instead of adding additional dependencies + +### Positive Consequences + +* Less dependencies on third party libraries +* Smaller project size (ICU4J is very large) +* No need for model data (OpenNLP is a machine learning based toolkit and needs a trained model to work properly) + +### Negative Consequences + +* Regular expressions can never cover every case, therefore, splitting may not be accurate for every title diff --git a/jabref/docs/decisions/0019-implement-special-fields-as-separate-fields.md b/jabref/docs/decisions/0019-implement-special-fields-as-separate-fields.md new file mode 100644 index 00000000..3e4d6b5e --- /dev/null +++ b/jabref/docs/decisions/0019-implement-special-fields-as-separate-fields.md @@ -0,0 +1,77 @@ +--- +parent: Decision Records +nav_order: 19 +--- +# Implement special fields as separate fields + +## Context and Problem Statement + +How to implement special fields in BibTeX databases? + +## Considered Options + +* Special fields as separate fields +* Special fields as keywords +* Special fields as values of a special field +* Special fields as sub-feature of groups + +## Decision Outcome + +Chosen option: "Special fields as separate fields", because comes out best (see below). + +## Pros and Cons of the Options + +### Special fields as separate fields + +Example: + +```bibtex +priority = {prio1}, +printed = {true}, +readstatus = {true}, +``` + +* Good, because groups are another view to fields +* Good, because a special field leads to a special rendering +* Good, because groups pull information from the main table +* Good, because hard-coding presets is easier than generic configuration +* Good, because direct inclusion in main table +* Good, because groups are shown with color bars in the main table +* Good, because there are no “hidden groups” in JabRef +* Good, because can be easily removed (e.g., by a formatter) +* Good, because prepares future power of JabRef to make field properties configurable +* Bad, because bloats BibTeX file +* Bad, because requires more writing when editing BibTeX manually by hand + +### Special fields as keywords + +Example: + +```bibtex +keywords = {prio1, printed, read} +``` + +* Good, because does not bloat the BibTeX file. Typically, 50% of the lines are special fields +* Good, because the user can easily assign a special field. E.g, typing “, prio1” into keywords instead of “`\n priority = {prio1},`” +* Bad, because they need to be synchronized to fields (because otherwise, the maintable cannot render it) +* Bad, because keywords are related to the actual content +* Bad, because some users want to keep publisher keywords + +### Special fields as values of a special field + +Example: + +```bibtex +jabrefspecial = {prio1, printed, red} +``` + +* Good, because typing effort +* Bad, because handling in table gets complicated → one field is now multiple columns + +### Special fields as sub-feature of groups + +* Good, because one concept rules them all +* Good, because groups already provide [explicit handling of certain cases](https://docs.jabref.org/finding-sorting-and-cleaning-entries/groups#types-of-groups): groups based on keywords and groups based on author's last names +* Bad, because main table implementation changes: Currently, each column in the main table represents a field. The main may [mark entries belonging to certain groups](https://docs.jabref.org/finding-sorting-and-cleaning-entries/groups#group-color-bars-in-the-entry-table), but does offer an explicit rendering of groups +* Bad, because groups are more a query on data of the entries instead of explicitly assigning entries to a group +* Bad, because explicit assignment and unassigment to a group is not supported by the main table diff --git a/jabref/docs/decisions/0020-use-Jackson-to-parse-study-yml.md b/jabref/docs/decisions/0020-use-Jackson-to-parse-study-yml.md new file mode 100644 index 00000000..4f083e8d --- /dev/null +++ b/jabref/docs/decisions/0020-use-Jackson-to-parse-study-yml.md @@ -0,0 +1,60 @@ +--- +parent: Decision Records +nav_order: 20 +--- +# Use Jackson to parse study.yml + +## Context and Problem Statement + +The study definition file is formulated as a YAML document. +To access the definition within JabRef this document has to be parsed. +What parser should be used to parse YAML files? + +## Considered Options + +* [Jackson](https://github.com/FasterXML/jackson-dataformat-yaml) +* [SnakeYAML Engine](https://bitbucket.org/snakeyaml/snakeyaml-engine/src/master/) +* [yamlbeans](https://github.com/EsotericSoftware/yamlbeans) +* [eo-yaml](https://github.com/decorators-squad/eo-yaml) +* Self-written parser + +## Decision Outcome + +Chosen option: "Jackson", because as it is a dedicated library for parsing YAML. `yamlbeans` also seem to be viable. They all offer similar functionality. + +## Pros and Cons of the Options + +### Jackson + +* Good, because established YAML parser library +* Good, because supports YAML 1.2 +* Good, because it can parse LocalDate + +### SnakeYAML Engine + +* Good, because established YAML parser library +* Good, because supports YAML 1.2 +* Bad, because cannot parse YAML into Java DTOs, only into [basic Java structures](https://bitbucket.org/snakeyaml/snakeyaml-engine/src/master/), this then has to be assembled into DTOs + +### yamlbeans + +* Good, because established YAML parser library +* Good, because [nice getting started page](https://github.com/EsotericSoftware/yamlbeans) +* Bad, because objects need to be annotated in the yaml file to be parsed into Java objects + +### eo-yaml + +* Good, because established YAML parser library +* Good, because supports YAML 1.2 +* Bad, because cannot parse YAML into Java DTOs + +### Own parser + +* Good, because easily customizable +* Bad, because high effort +* Bad, because has to be tested extensively + +## Links + +* [Winery's ADR-0009](https://github.com/eclipse/winery/blob/master/docs/adr/0009-manual-tosca-yaml-serialisation.md) +* [Winery's ADR-0010](https://github.com/eclipse/winery/blob/master/docs/adr/0010-tosca-yaml-deserialisation-using-snakeyaml.md) diff --git a/jabref/docs/decisions/0021-keep-study-as-a-dto.md b/jabref/docs/decisions/0021-keep-study-as-a-dto.md new file mode 100644 index 00000000..94ff5874 --- /dev/null +++ b/jabref/docs/decisions/0021-keep-study-as-a-dto.md @@ -0,0 +1,34 @@ +--- +parent: Decision Records +nav_order: 21 +--- +# Keep study as a DTO + +## Context and Problem Statement + +The study holds query and library entries that could be replaced respectively with complex query and fetcher instances. +This poses the question: should the study remain a pure DTO object, or should it contain direct object instances? + +## Considered Options + +* Keep study as DTO and use transformers +* Replace entries with instances + +## Decision Outcome + +Chosen option: "Keep study as DTO and use transformers", because comes out best (see below). + +## Pros and Cons of the Options + +### Keep study as DTO and use transformers + +* Good, because no need for custom serialization +* Good, because deactivated fetchers can be documented (important for traceable Searching (SLRs)) +* Bad, because Entries for databases and queries needed + +### Replace entries with instances + +* Good, because no need for database and query entries +* Bad, because custom de-/serializers for fetchers and complex queries needed +* Bad, because harder to maintain than using "vanilla" Jackson de-/serialization +* … diff --git a/jabref/docs/decisions/0022-remove-stop-words-during-query-transformation.md b/jabref/docs/decisions/0022-remove-stop-words-during-query-transformation.md new file mode 100644 index 00000000..f8331410 --- /dev/null +++ b/jabref/docs/decisions/0022-remove-stop-words-during-query-transformation.md @@ -0,0 +1,37 @@ +--- +parent: Decision Records +nav_order: 22 +--- +# Remove stop words during query transformation + +## Context and Problem Statement + +When querying for a title of a paper, the title might contain stop words such as "a", "for", "and". Some data providers return 0 results when querying for a stop word. When transforming a query to the Lucene syntax, the default Boolean operator `and` is used. When using IEEE, this often leads to zero search results. + +## Decision Drivers + +* Consistent to the Google search engine +* Allow reproducible searches +* Avoid WTFs on the user's side + +## Considered Options + +* Remove stop words from the query +* Automatically enclose in quotes if no Boolean operator is contained + +## Decision Outcome + +Chosen option: "Remove stop words from the query", because comes out best. + +## Pros and Cons of the Options + +### Remove stop words from the query + +* Good, because good search results if no Boolean operators are used +* Bad, because when using complex queries and stop words are used alone, they are silently removed + +### Automatically enclose in quotes if no Boolean operator is contained + +* Good, because good search results if no Boolean operators are used +* Bad, because silently leads to different results +* Bad, because inconsistent to Google behavior diff --git a/jabref/docs/decisions/0023-localized-preferences.md b/jabref/docs/decisions/0023-localized-preferences.md new file mode 100644 index 00000000..ab0701da --- /dev/null +++ b/jabref/docs/decisions/0023-localized-preferences.md @@ -0,0 +1,23 @@ +--- +parent: Decision Records +nav_order: 23 +--- +# Localized Preferences + +* Status: proposed +* Date: 2021-09-01 + +## Context and Problem Statement + +Currently, JabRef uses some localized preferences. We want to remove the Localization-dependency from the `JabRefPreferences` and move the Localization to where the String is used. +The problem is how to store the default values. + +## Considered Options + +* _Localize Defaults_ (current implementation) +* _Store the Preference only when it was changed by the user_. Otherwise, leave it empty. When a consumer gets such an empty preference, it knows that it needs to read the default and localize it. This won't work if users actually want something to be empty. +* _Store the unlocalized String._ Consumers then check the String they got as a preference against the defaults. If it matches, localize it. Otherwise, use it. + +## Decision Outcome + +Chosen option: "_Store the unlocalized String._ Consumers then check the String they got as a preference against the defaults. If it matches, localize it. Otherwise, use it.", because Achieves goals without requiring too much refactoring and without (known) downsides. diff --git a/jabref/docs/decisions/0024-use-#-as-indicator-for-BibTeX-string-constants.md b/jabref/docs/decisions/0024-use-#-as-indicator-for-BibTeX-string-constants.md new file mode 100644 index 00000000..881408c5 --- /dev/null +++ b/jabref/docs/decisions/0024-use-#-as-indicator-for-BibTeX-string-constants.md @@ -0,0 +1,137 @@ +--- +parent: Decision Records +nav_order: 24 +--- +# Use `#` as indicator for BibTeX string constants + +Bibtex supports string constants. The entry editor should support that, too. +Affected code is (at least) `org.jabref.logic.importer.fileformat.BibtexParser#parseFieldContent` and `org.jabref.logic.bibtex.FieldWriter#formatAndResolveStrings` + +## Decision Drivers + +* Full BibTeX compatibility +* Intuitive for the user +* UI backwards compatible + +## Considered Options + +* Wrap string constants in `#` +* Replace the internal `#` in JabRef by the non-used character `§` +* Replace the internal `#` in JabRef by the non-used character `%` +* Replace the internal `#` in JabRef by the non-used character `&` +* Replace the internal `#` in JabRef by a single `&` +* Change the data type of a field value +* Wrap plan # in curly braces: `{#}` + +## Decision Outcome + +Chosen option: "Wrap string constants in #", because already existing behavior. + +## Pros and Cons of the Options + +### Wrap string constants in `#` + +When BibTeX on the disk contains `field = value`, it is rendered as `field = #value#` in the entry editor. The user then knows that `value` is a BibTeX string. + +Example: + +left: BibTeX; right: Entry Editor + +```text +@String{ value = "example" } + +field1 = value -> #value# +field2 = {value} -> value + +field1 = value # value --> #value# # #value# +``` + +* Good, because In place in JabRef since more than 10 years +* Bad, because `#` is used in BibTeX for string concatenation +* Bad, because `#` is used in Markdown to indicate headings. When using Markdown in fields such as comments, this causes writing errors +* Bad, because `#` could appear in URLs + +### Replace the internal `#` in JabRef by the non-used character `§` + +When BibTeX on the disk contains `field = value`, it is rendered as `field = §value§` in the entry editor. The user then knows that `value` is a BibTeX string. + +Example: + +left: BibTeX; right: Entry Editor + +```text +field1 = value -> §value§ +field2 = {value} -> value + +field1 = value # value --> §value§ # §value§ +``` + +* Good, because easy to implement +* Good, because no conflict with BibTeX's `#` sign +* Bad, because documentation needs to be updated +* Bad, because `§` is not available on a US keyboard + +### Replace the internal `#` in JabRef by the non-used character `%` + +Similar to "Replace the internal # in JabRef by the non-used character §" + +Example: + +left: BibTeX; right: Entry Editor + +```text +field1 = value -> %value% +field2 = {value} -> value + +field1 = value # value --> %value% # %value% +``` + +* Good, because a user does not write LaTeX comments inside a string +* Bad, because `%` could be used in Markdown, too +* Bad, because `%` is used for comments in LaTeX and thus migtht cause confusion +* Bad, because `%` could appear in URLs: There is [url percent encoding](https://www.w3schools.com/tags/ref_urlencode.asp) + +### Replace the internal `#` in JabRef by the non-used character `&` + +Similar to "Replace the internal `#` in JabRef by the non-used character `§`" + +Example: + +left: BibTeX; right: Entry Editor + +```text +field1 = value -> &value& +field2 = {value} -> value + +field1 = value # value --> &value& # &value& +``` + +* Good, because Users do not write LaTeX tables +* Bad, because `&` is a LaTeX command for tables + +### Replace the internal `#` in JabRef by a single `&` + +Prefix strings with `&` + +Example: + +left: BibTeX; right: Entry Editor + +```text +field1 = value -> &value +field2 = {value} -> value + +field1 = value # value --> &value # &value +``` + +* Good, because near to C syntax +* Bad, because `&` is a LaTeX command for tables +* Bad, because `&` could appear in URLs + +### Change the data type of a field value + +`org.jabref.model.entry.BibEntry#setField(org.jabref.model.entry.field.Field, java.lang.String)` changes to `org.jabref.model.entry.BibEntry#setField(org.jabref.model.entry.field.Field, org.jabref.model.entry.field.Value)` (org.jabref.model.entry.BibEntry#setField(org.jabref.model.entry.field.Field, java.lang.String)). This would bring JabRef's internal data model even more close to BibTeX + +* Good, because more object-orientated design of BibTeX field storage +* Good, because eases implementation of an advanced field editor +* Bad, because high effort to implement diff --git a/jabref/docs/decisions/0025-reviewdog-reviews.md b/jabref/docs/decisions/0025-reviewdog-reviews.md new file mode 100644 index 00000000..512aed39 --- /dev/null +++ b/jabref/docs/decisions/0025-reviewdog-reviews.md @@ -0,0 +1,33 @@ +--- +parent: Decision Records +nav_order: 25 +--- +# Reviewdog findings are code reviews + +## Context and Problem Statement + +JabRef offers [guidelines to setup the local workspace](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace). +There is also a section on [JabRef's code style](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace#using-jabrefs-code-style). +There are pull requests by newcomers, which do not follow that style guide. + +How to quickly provide feedback to contributors that checkstyle was not matched? + +## Decision Drivers + +* Be friendly to newcomers +* Provide fast feedback to contributors +* Lower the workload of maintainers +* Keep maintainers focused on the "real" challanges of the code changes + +## Considered Options + +* Use [Reviewdog's PullRequest review reporter](https://github.com/reviewdog/reviewdog#reporter-github-pullrequest-review-comment--reportergithub-pr-review) +* Use [Reviewdog's check reporter](https://github.com/reviewdog/reviewdog#reporter-github-checks--reportergithub-check) +* Use [comment-failure-action](https://github.com/quipper/comment-failure-action) + +## Decision Outcome + +Chosen option: "Use Reviewdog's PullRequest review reporter", because resolves force to provide fast feedback. +We do not want to use `comment-failure-action`, because [it might procude too much comments](https://github.com/quipper/comment-failure-action/issues/224). +We accept that newcomers might be annoyed if quick automatic feedback by a bot is given: +We value the time of our maintainers and want to keep them focused on the real challanges of the code changes. diff --git a/jabref/docs/decisions/0026-use-jna-to-determine-default-directory.md b/jabref/docs/decisions/0026-use-jna-to-determine-default-directory.md new file mode 100644 index 00000000..210be28e --- /dev/null +++ b/jabref/docs/decisions/0026-use-jna-to-determine-default-directory.md @@ -0,0 +1,62 @@ +--- +nav_order: 26 +parent: Decision Records +--- +# Use Java Native Access to Determine Default Directory + +## Context and Problem Statement + +JabRef needs to propose a file directory to a user for storing files. +How to determine the "best" directory native for the OS the user runs. + +## Decision Drivers + +* Low maintenance effort +* Follow JabRef's architectural guidelines +* No additional dependencies + +## Considered Options + +* Use Swing's FileChooser to Determine Default Directory +* Use `user.home` +* [AppDirs](https://github.com/harawata/appdirs) +* [Java Native Access](https://github.com/java-native-access/jna) + +## Decision Outcome + +Chosen option: "Java Native Access", because comes out best (see below). + +## Pros and Cons of the Options + +### Use Swing's FileChooser to Determine Default Directory + +Swing's FileChooser implemented a very decent directory determination algorithm. +It thereby uses `sun.awt.shell.ShellFolder`. + +* Good, because provides best results on most platforms. +* Good, because also supports localization of the folder name. E.g., `~/Dokumente` in Germany. +* Bad, because introduces a dependency on Swing and thereby contradicts the second decision driver. +* Bad, because GraalVM's support Swing is experimental. +* Bad, because handles localization only on Windows. + +### Use `user.home` + +There is `System.getProperty("user.home");`. + +* Bad, because "The concept of a HOME directory seems to be a bit vague when it comes to Windows". See for details. +* Bad, because it does not include `Documents`: + As of 2022, `System.getProperty("user.home")` returns `c:\Users\USERNAME` on Windows 10, whereas + `FileSystemView` returns `C:\Users\USERNAME\Documents`, which is the "better" directory. + +### AppDirs + +> AppDirs is a small java library which provides a path to the platform dependent special folder/directory. + +* Good, because already used in JabRef. +* Bad, because does not use `Documents` on Windows, but rather `C:\Users\\AppData\\` as basis. + +### Java Native Access + +* Good, because no additional dependency required, as it is already loaded by AppDirs. +* Good, because it is well maintained and widely used. +* Good, because it provides direct access to `Documents` and other system variables. diff --git a/jabref/docs/decisions/0027-synchronization.md b/jabref/docs/decisions/0027-synchronization.md new file mode 100644 index 00000000..ee1599d0 --- /dev/null +++ b/jabref/docs/decisions/0027-synchronization.md @@ -0,0 +1,59 @@ +--- +nav_order: 27 +parent: Decision Records +--- + +# Synchronization with remote databases + +## Context and Problem Statement + +Synchronize the data in a library to a remote database, while handling conflicts and supporting offline-first paradigm. + +## Decision Drivers + +- Updates from the remote should be pulled in +- No updates should get lost +- Easy to implement +- Easy to maintain + +## Considered Options + +- "Optimistic offline lock" with hashes for local file support +- Algorithm based on "optimistic offline lock" +- Use CRDTs + +## Decision Outcome + +Chosen option: "'Optimistic offline lock' with hashes for local file support", because simplest option to resolves all forces. + +## Pros and Cons of the Options + +### "Optimistic offline lock" with hashes for local file support + +The [Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) is good for synchronizing clients with a server when there is no other modification of data on client side. +However, users might modify the `.bib` file external of JabRef. +They might also open an existing `.bib` file and synchronize that. +Thus, there are additions needed to handle the local synchronization. + +Moreover, the optimistic offline lock does not say how a set of data is synchronized. + +Both shortcomings are resolved by our algotihm. +This algorithm is described at [Remote JabDrive storage](../code-howtos/remote-storage-jabdrive.md). + +### Algorithm based on "optimistic offline lock" + +[Optimistic Offline Lock](https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html) is a well-established technique to prevent conflicts in concurrent business transactions. +It assumes that the chance of conflict is low. +Implementation details are found at . + +This is implemented for the SQL database synchronization, which is described at [Remote SQL Storage](../code-howtos/remote-storage-sql.md). + +- Good, because this algorithm is already in place since 2016 for JabRef synchronizing with a PostgreSQL backend and a MySQL backend. +- Bad, because it assumes the client to be online 100% and does not have handlings of cases where the client disconnects and alters data in other ways. + +### Use CRDTs + +See for details. + +- Bad, because one needs to locally store a lot more metadata (e.g. for operational CRDTs you essentially need to have the full history of all edits). So you would need another file next to the bib file to store these. +- Bad, because CRDTs are mainly used when you need low latency and high frequency of edits (e.g. multi-user chat or text editing). Not really something we care about. diff --git a/jabref/docs/decisions/0028-http-return-bibtex-string.md b/jabref/docs/decisions/0028-http-return-bibtex-string.md new file mode 100644 index 00000000..bc8ff939 --- /dev/null +++ b/jabref/docs/decisions/0028-http-return-bibtex-string.md @@ -0,0 +1,51 @@ +--- +nav_order: 28 +parent: Decision Records +--- +# Return BibTeX string and CSL Item JSON in the API + +## Context and Problem Statement + +In the context of an http server, when a http client `GETs` a JSON data structure containing BibTeX data, which format should that have? + +## Considered Options + +* Offer both, BibTeX string and CSL JSON +* Return BibTeX as is as string +* Convert BibTeX to JSON + +## Decision Outcome + +Chosen option: "Offer both, BibTeX string and CSL JSON", because there are many browser libraries out there being able to parse BibTeX. Thus, we don't need to convert it. + +## Pros and Cons of the Options + +### Offer both, BibTeX string and CSL JSON + +* Good, because this follows "Backend for Frontend" +* Good, because Word Addin works seamless with the data provided (and does not need another dependency) +* Good, because other clients can work with BibTeX data +* Bad, because two serializations have to be kept + +### Return BibTeX as is as string + +* Good, because we don't need to think about any conversion +* Bad, because it is unclear how to ship BibTeX data where the entry is dependent on +* Bad, because client needs an additional parsing logic + +### Convert BibTeX to JSON + +More thought has to be done when converting to JSON. +There seems to be a JSON format from [@citation-js/plugin-bibtex](https://www.npmjs.com/package/@citation-js/plugin-bibtex). +We could do an additional self-made JSON format, but this increases the number of available JSON serializations for BibTeX. + +* Good, because it could flatten BibTeX data (example: `author = first # " and " # second`) +* Bad, because conversion is difficult in BibTeX special cases. For instance, if Strings are used (example: `author = first # " and " # second`) and one doesn't want to flatten ("normalize") this. + +## More Information + +Existing JavaScript BibTeX libraries: + +* [bibtex-js](https://github.com/digitalheir/bibtex-js) +* [bibtexParseJS](https://github.com/ORCID/bibtexParseJs) +* [@citation-js/plugin-bibtex](https://www.npmjs.com/package/@citation-js/plugin-bibtex) diff --git a/jabref/docs/decisions/0029-cff-export-multiple-entries.md b/jabref/docs/decisions/0029-cff-export-multiple-entries.md new file mode 100644 index 00000000..ec1dab8c --- /dev/null +++ b/jabref/docs/decisions/0029-cff-export-multiple-entries.md @@ -0,0 +1,52 @@ +--- +nav_order: 29 +parent: Decision Records +--- +# Exporting multiple entries to CFF + +## Context and Problem Statement + +The need for an [exporter](https://github.com/JabRef/jabref/issues/10661) to [CFF format](https://github.com/citation-file-format/citation-file-format/blob/main/schema-guide.md) raised the following issue: How to export multiple entries at once? Citation-File-Format is intended to make software and datasets citable. It should contain one "main" entry of type `software` or `dataset`, a possible preferred citation and/or several references of any type. + +## Decision Drivers + +* Make exported files compatible with official CFF tools +* Make exporting process logical for users + +## Considered Options + +* When exporting: + * Export non-`software` entries with dummy topmost `sofware` and entries as `preferred-citation` + * Export non-`software` entries with dummy topmost `sofware` and entries as `references` + * Forbid exporting multiple entries at once + * Forbid exporting more than one software entry at once + * Export entries in several files (i.e. one / file) + * Export several `software` entries with one of them topmost and all others as `references` +* Export several `software` entries with a dummy topmost `software` element and all others as `references` +* When importing: + * Only create one entry / file, enven if there is a `preferred-citation` or `references` + * Add a JabRef `cites` relation from `software` entry to its `preferred-citation` + * Add a JabRef `cites` relation from `preferred-citation` entry to the main `software` entry + * Separate `software` entries from their `preferred-citation` or `references` + +## Decision Outcome + +The decision outcome is the following. + +* When exporting, JabRef will have a different behavior depending on entries type. + * If multiple non-`software` entries are selected, then exporter uses the `references` field with a dummy topmost `software` element. + * If several entries including a `software` or `dataset` one are selected, then exporter uses this one as topmost element and the others as `references`, adding a potential `preferred-citation` for the potential `cites` element of the topmost `software` entry. + * If several entries including several `software` ones are selected, then exporter uses a dummy topmost element, and selected entries are exported as `references`. The `cites` or `related` fields won't be exported in this case. + * JabRef will not handle `cites` or `related` fields for non-`software` elements. +* When importing, JabRef will create several entries: one main entry for the `software` and other entries for the potential `preferred-citation` and `references` fields. JabRef will link main entry to the preferred citation using a `cites` from the main entry, and wil link main entry to the references using a `related` from the main entry. + +### Positive Consequences + +* Exported results comply with CFF format +* The export process is "logic" : an user who exports multiple files to CFF might find it clear that they are all marked as `references` +* Importing a CFF file and then exporting the "main" (software) created entry is consistent and will produce the same result + +### Negative Consequences + +* Importing a CFF file and then exporting one of the `preferred-citation` or the `references` created entries won't result in the same file (i.e exported file will contain a dummy topmost `software` instead of the actual `software` that was imported) +* `cites` and `related` fields of non-`software` entries are not supported diff --git a/jabref/docs/decisions/0030-use-apache-commons-io-for-directory-monitoring.md b/jabref/docs/decisions/0030-use-apache-commons-io-for-directory-monitoring.md new file mode 100644 index 00000000..42a76213 --- /dev/null +++ b/jabref/docs/decisions/0030-use-apache-commons-io-for-directory-monitoring.md @@ -0,0 +1,48 @@ +--- +nav_order: 30 +parent: Decision Records +--- +# Use Apache Commons IO for directory monitoring + +## Context and Problem Statement + +In JabRef, there is a need to add a directory monitor that will listen for changes in a specified directory. + +Currently, the monitor is used to automatically update the [LaTeX Citations](https://docs.jabref.org/advanced/entryeditor/latex-citations) when a LaTeX file in the LaTeX directory is created, removed, or modified ([#10585](https://github.com/JabRef/jabref/issues/10585)). +Additionally, this monitor will be used to create a dynamic group that mirrors the file system structure ([#10930](https://github.com/JabRef/jabref/issues/10930)). + +## Considered Options + +* Use [java.nio.file.WatchService](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/nio/file/WatchService.html) +* Use [io.methvin.watcher.DirectoryWatcher](https://github.com/gmethvin/directory-watcher) +* Use [org.apache.commons.io.monitor](https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/monitor/package-summary.html) + +## Decision Outcome + +Chosen option: "Use [org.apache.commons.io.monitor](https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/monitor/package-summary.html)", because comes out best (see below). + +## Pros and Cons of the Options + +### java.nio.file.WatchService + +* Good, because it is a standard Java API for watching directories. +* Good, because it does not need polling, it is event-based for most operating systems. +* Bad, because: + 1. Does not detect files coming together with a new folder (JDK issue: [JDK-8162948](https://bugs.openjdk.org/browse/JDK-8162948)). + 2. Deleting a subdirectory does not detect deleted files in that directory. + 3. Access denied when trying to delete the recursively watched directory on Windows (JDK issue: [JDK-6972833](https://bugs.openjdk.org/browse/JDK-6972833)). + 4. Implemented on macOS by the generic `PollingWatchService`. (JDK issue: [JDK-8293067](https://bugs.openjdk.org/browse/JDK-8293067)) + +### io.methvin.watcher.DirectoryWatcher + +* Good, because it implemented on top of the `java.nio.file.WatchService`, which is a standard Java API for watching directories. +* Good, because it resolves some of the issues of the `java.nio.file.WatchService`. + * Uses`ExtendedWatchEventModifier.FILE_TREE` on Windows, which resolves issues (1, 3) of the `java.nio.file.WatchService`. + * On macOS have native implementation based on the Carbon File System Events API, this resolves issue (4) of the `java.nio.file.WatchService`. +* Bad, because issue (2) of the `java.nio.file.WatchService` is not resolved. + +### org.apache.commons.io.monitor + +* Good, because there are no observed issues. +* Good, because can handle huge amount of files without overflowing. +* Bad, because it uses a polling mechanism at fixed intervals, which can waste CPU cycles if no change occurs. diff --git a/jabref/docs/decisions/0031-use-current-tab-for-deciding-style-type-for-oo.md b/jabref/docs/decisions/0031-use-current-tab-for-deciding-style-type-for-oo.md new file mode 100644 index 00000000..05c9e848 --- /dev/null +++ b/jabref/docs/decisions/0031-use-current-tab-for-deciding-style-type-for-oo.md @@ -0,0 +1,21 @@ +--- +nav_order: 31 +parent: Decision Records +--- +# Use currently active tab in Select style (OO Panel) to decide style type + +## Context and Problem Statement + +In the Select Style Dialog window of the OpenOffice Panel, in case a style is selected in both the CSL Styles Tab and JStyles Tab, how to decide which of the two will be activated for use? + +## Considered Options + +* Use toggle in Select Style GUI +* Use toggle in Preferences +* Use Buttons in Select Style GUI +* Use Toggle in Main GUI +* Use currently active Tab in Select Style GUI and add a notification + +## Decision Outcome + +Chosen option: "Use currently active Tab in Select Style GUI and add a notification", because we already had two tabs indicating a clear separation of choices to the user. It was the most convenient way without adding extra steps to make the user choose "which style type to use" before selecting the style, which would be the case in the other options if chosen. The option is quite intuitive, extensible for working with multiple tabs and make only three to four clicks are necessary to select a style. Furthermore, the notification makes it clear to the user which style type as well as which style is selected. diff --git a/jabref/docs/decisions/0032-store-chats-in-local-user-folder.md b/jabref/docs/decisions/0032-store-chats-in-local-user-folder.md new file mode 100644 index 00000000..c673b81f --- /dev/null +++ b/jabref/docs/decisions/0032-store-chats-in-local-user-folder.md @@ -0,0 +1,66 @@ +--- +nav_order: 0032 +parent: Decision Records +--- +# Store Chats Alongside Database + +## Context and Problem Statement + +Chats with AI should be stored somewhere. But where and how? + +## Considered Options + +* Inside `.bib` file +* In local user folder +* Alongside `.bib` file + +## Decision Drivers + +* Should work when shared with OneDrive, Dropbox or similar asynchronous services +* Should work on network drives +* Should be "easy" for users to follow +* Should be the same in a shared and non-shared setting (e.g., if Dropbox is used or not should make a difference) + +## Decision Outcome + +Chosen option: "In local user folder", because +it's very hard to work with a shared library, if two users will work +simultaneously on one library, then AI chats file will be absolutely arbitrary +and unmergable. + +## Pros and Cons of the Options + +### Inside `.bib` file + +* Good, because we already have a machinery for managing the fields and other information of BIB entries +* Good, because chats are stored inside one file, and if the `.bib` file is moved, the chat history is preserved +* Bad, because there may be lots of chats and messages and `.bib` file become too cluttered and too big which slows down the processing of `.bib` file +* Bad, because if user shares a `.bib` file, they will also share chat messages, but chats are not ideal, so user may not + want to share them + +### In local user folder + +One can use `%APPDATA%`, where JabRef stores the Lucene index and other information. +See `org.jabref.gui.desktop.os.NativeDesktop#getFulltextIndexBaseDirectory` for use in JabRef and + for general information. + +Concrete example for backup folder: `C:\Users\${username}\AppData\Local\org.jabref\jabref\backups`. +Example filename: `4a070cf3--Chocolate.bib--2024-03-25--14.20.12.bak`. + +* Good, because `.bib` file is kept clean +* Good, because chat messages are saved locally +* Neutral, because may be a little harder to implement +* Bad, because chat messages cannot be easily shared +* Bad, because when path of a `.bib` file is changed, the chats are lost + +### Alongside `.bib` file + +* Good, because simple implementation +* Good, because, the user can send the chats file alongside the `.bib` file if they want to share the chats. If users do not want + to share the messages, then they can omit the chats file +* Good, because `.bib` files is kept clean +* Bad, because user may not expect that a new file will be created alongside their `.bib` (or other LaTeX-related) files +* Bad, because, it may be not convenient to share both files (`.bib` file and chats file) in order to share chat history. +* Bad, because if `.bib` files are edited externally (meaning, not inside the JabRef), then chats file will not be updated correspondingly +* Bad, because if user moves `.bib` file, they should move the chats file too +* Bad, because if two persons work in parallel using a OneDrive share, the file is overwritten or a conflict file is generated. ([Dropbox "conflicted copy"](https://help.dropbox.com/en-en/organize/conflicted-copy)) diff --git a/jabref/docs/decisions/0033-store-chats-in-mvstore.md b/jabref/docs/decisions/0033-store-chats-in-mvstore.md new file mode 100644 index 00000000..c2baf32a --- /dev/null +++ b/jabref/docs/decisions/0033-store-chats-in-mvstore.md @@ -0,0 +1,53 @@ +--- +nav_order: 0033 +parent: Decision Records +--- + +# Store Chats in MVStore + +## Context and Problem Statement + +This is a follow-up to [ADR-031](0032-store-chats-in-local-user-folder). + +The chats with AI should be saved on exit from JabRef and retrieved on launch. We need to decide the format of +the serialized messages. + +## Decision Drivers + +* Easy to implement and maintain +* Memory-efficient (because JabRef is said to consume much memory) + +## Considered Options + +* JSON +* MVStore +* Custom format + +## Decision Outcome + +Chosen option: "MVStore", because it is simple and memory-efficient. + +## Pros and Cons of the Options + +### JSON + +* Good, because allows for easy storing and loading of chats +* Good, because cross-platform +* Good, because widely used and accepted, so there are lots of libraries for JSON format +* Good, because it is even possible to reuse the chats file for other purposes +* Good, because has potential for being mergeable by external tooling +* Bad, because too verbose (meaning the file size could be much smaller) + +### MVStore + +* Good, because automatic loading and saving to disk +* Good, because memory-efficient +* Bad, because does not support mutable values in maps. +* Bad, because the order of messages need to be "hand-crafted" (e.g., by mapping from an Integer to the concrete message), since [MVStore does not support storing list which update](https://github.com/koppor/mvstore-mwe/pull/1). +* Bad, because it stores data as key-values, but not as a custom data type (like tables in RDBMS) + +### Custom format + +* Good, because we have the full control +* Bad, because involves writing our own language and parser +* Bad, because we need to implement optimizations found in databases on our own (storing some data in RAM, other on disk) diff --git a/jabref/docs/decisions/0034-use-citation-key-for-grouping-chat-messages.md b/jabref/docs/decisions/0034-use-citation-key-for-grouping-chat-messages.md new file mode 100644 index 00000000..f6ef24dc --- /dev/null +++ b/jabref/docs/decisions/0034-use-citation-key-for-grouping-chat-messages.md @@ -0,0 +1,69 @@ +--- +nav_order: 0034 +parent: Decision Records +--- + +# Use Citation Key for Grouping Chat Messages + +## Context and Problem Statement + +Because we store chat messages not inside a BIB entry in `.bib` filecc, the chats file is represented as a map to +BIB entry and a list of messages. We need to specify the key of this map. Turns out, it is not that easy. + +## Decision Drivers + +* The key should exist for every BIB entry +* The key should be unique along other BIB entries in one library file +* The key should not change at run-time, between launches of JabRef, and should be cross-platform (most important) + +## Considered Options + +* `BibEntry` Java object +* `BibEntry`'s `id` +* `BibEntry`'s Citation key +* `BibEntry`'s `ShareId` + +## Decision Outcome + +Chosen option: "`BibEntry`'s Citation key", because this is the only choice that complains to the third point in Decision Drivers. + +### Positive Consequences + +* Easy to implement +* Cross-platform + +### Negative Consequences + +* If the citation key is changed externally, then the chats file becomes out-of-sync +* Additional user interaction in order to make the citation key complain the first and second points of Decision Drivers + +## Pros and Cons of the Options + +### `BibEntry` Java object + +Very bad, because it works only at run-time and is not stable. + +### `BibEntry`'s `id` + +JabRef stores a unique identifier for each `BibEntry`. +This identifier is created on each load of a library (and not stored permanently). + +Very bad, for the same reasons as `BibEntry` Java object. + +### `BibEntry`'s Citation key + +* Good, because it is cross-platform, stable (meaning stays the same across launches of JabRef) +* Bad, because it is not guaranteed that citation key exists on `BibEntry`, and that it is unique across other +`BibEntriy`'s' in the library + +### `BibEntry`'s `ShareId` + +[ADR-0027](0027-synchronization.md) describes the procedure of synchronization of a Bib(La)TeX library with a server. +Thereby, also local and remote entries need to be kept consistent. +The solution chosen there is that the **server** creates a UUID for each entry. + +This approach cannot be used here, because there is no server running which we can ask for an UUID of an entry. + +## More Information + +Refer to [issue #160](https://github.com/JabRef/jabref/issues/160) in JabRef main repository diff --git a/jabref/docs/decisions/0035-generate-embeddings-online.md b/jabref/docs/decisions/0035-generate-embeddings-online.md new file mode 100644 index 00000000..ea01351b --- /dev/null +++ b/jabref/docs/decisions/0035-generate-embeddings-online.md @@ -0,0 +1,53 @@ +--- +nav_order: 0035 +parent: Decision Records +--- + +# Generate Embeddings Online + +## Context and Problem Statement + +In order to perform a question and answering (Q&A) session over research papers +with large language model (LLM), we need to process each file: each file should +be converted to string, then this string is split into chunks, and for each chunk +an embedding vector should be generated. + +Where these embeddings should be generated? + +## Considered Options + +* Local embedding model with `langchain4j` +* OpenAI embedding API + +## Decision Drivers + +* Embedding generation should be fast +* Embeddings should have good performance (performance mean they "catch the semantics" good, see also [MTEB](https://huggingface.co/blog/mteb)) +* Generating embeddings should be cheap +* Embeddings should not be of a big size +* Embedding models and library to generate embeddings shouldn't be big in distribution binary. + +## Decision Outcome + +Chosen option: "OpenAI embedding API", because +the distribution size of JabRef will be nearly unaffected. Also, it's fast +and has a better performance, in comparison to available in `langchain4j`'s model `all-MiniLM-L6-v2`. + +## Pros and Cons of the Options + +### Local embedding model with `langchain4j` + +* Good, because works locally, privacy saved, no Internet connection is required +* Good, because user doesn't pay for anything +* Neutral, because how fast embedding generation is depends on chosen model. It may be small and fast, or big and time-consuming +* Neutral, because local embedding models may have less performance than OpenAI's (for example). *Actually, most embedding models suitable for use in JabRef are about ~50% performant) +* Bad, because embedding generation takes computer resources +* Bad, because the only framework to run embedding models in Java is ONNX, and it's very heavy in distribution binary + +### OpenAI embedding API + +* Good, because we delegate the task of generating embeddings to an online service, so the user's computer is free to do some other job +* Good, because OpenAI models have typically have better performance +* Good, because JabRef distribution size will practically be unaffected +* Bad, because user should agree to send data to a third-party service, Internet connection is required +* Bad, because user pay for embedding generation (see also [OpenAI embedding models pricing](https://platform.openai.com/docs/guides/embeddings/embedding-models)) diff --git a/jabref/docs/decisions/0036-use-textarea-for-chat-content.md b/jabref/docs/decisions/0036-use-textarea-for-chat-content.md new file mode 100644 index 00000000..5f01bebf --- /dev/null +++ b/jabref/docs/decisions/0036-use-textarea-for-chat-content.md @@ -0,0 +1,84 @@ +--- +nav_order: 0036 +parent: Decision Records +--- + +# Use TextArea for Chat Message Content + +## Context and Problem Statement + +This decision record concerns the UI component that is used for rendering the content of chat messages. + +## Decision Drivers + +* Looks good (renders Markdown) +* User can select and copy text +* Has good performance + +## Considered Options + +* Use `TextArea` +* Use a [third-party package](https://github.com/JPro-one/jpro-platform) +* Use a Markdown parser and convert AST nodes to JavaFX TextFlow elements +* Use a Markdown parser to convert content into HTML and use a WebView for one message +* Use a Markdown parser and WebView for the whole chat history + +## Decision Outcome + +Chosen option: "Use `TextArea`". +All other options require more time to implement. +Some of the options do not support text selection and copying, +which for now we value more than Markdown rendering. + +## Pros and Cons of the Options + +### Use TextArea + +* Good, because it is easy to implement +* Good, because it supports text selection and copying +* Bad, because it does not offer rich text. Thus, Markdown can only be displayed in a plain text form. +* Bad, because default JavaFX's `TextArea` shrinks + +### Use a third-party package + +There seems to be only one package for JavaFX that provides a ready-to-use UI node for Markdown rendering. + +* Good, because it is easy to implement +* Good, because it renders Markdown +* Good, because it renders Markdown to JavaFX nodes (does not use a `WebView`) +* Good, because complex elements from Markdown are supported (tables, code blocks, etc.) +* Bad, because it has very strange issues and architectural flaws with styling +* Bad, because it does not support text selection and copying (because of underlying JavaFX `Text` nodes) + +### Use a Markdown parser and convert AST nodes to JavaFX TextFlow elements + +* Good, because we will support Markdown +* Good, because no need to write a Markdown parser from scratch +* Good, because does not use a WebView +* Good, because easy styling +* Bad, because we need some time to implement Markdown AST -> JavaFX nodes converter +* Bad, because rendering tables and code blocks may be hard +* Bad, because it will not support text selection and copying + +### Use a Markdown parser to convert content into HTML and use a WebView for one message + +* Good, because there are libraries to convert Markdown to HTML +* Good, because may be easier to implement than other choices (except `TextArea`) +* Good, because it supports text selection and copying +* Bad, because it may be a problem to connect JavaFX CSS to `WebView` +* Bad, because one `WebView` for one message is resourceful + +### Use a Markdown parser and WebView for the whole chat history + +* Good, because there are libraries to convert Markdown to HTML +* Good, because it supports text selection and copying +* Bad, because it may be a problem to connect JavaFX CSS to `WebView` +* Bad, because it may be a problem to correctly communicate with Java code and `WebView` to add new messages + +## More Information + +Actually we used an `ExpandingTextArea` from `GemsFX` package so the content can occupy +as much space as it needs in the `ScrollPane`. + +About the selection and copying, this goes down to fundamental issue from JavaFX. +`Text` and `Label` cannot be selected by any means. diff --git a/jabref/docs/decisions/0037-rag-architecture-implementation.md b/jabref/docs/decisions/0037-rag-architecture-implementation.md new file mode 100644 index 00000000..424f5dac --- /dev/null +++ b/jabref/docs/decisions/0037-rag-architecture-implementation.md @@ -0,0 +1,108 @@ +--- +nav_order: 0037 +parent: Decision Records +--- + +# RAG Architecture Implementation + +## Context and Problem Statement + +The current trend in questions and answering (Q&A) using large language models (LLMs) or other +AI related technology is retrieval-augmented-generation (RAG). + +RAG is related to [Open Generative QA](https://huggingface.co/tasks/question-answering) +that means LLM (which generates text) is supplied with context (chunks of information extracted +from various sources) and then it generates answer. + +RAG architecture consists of [these steps](https://www.linkedin.com/pulse/rag-architecture-deep-dive-frank-denneman-4lple) (simplified): + +How source data is processed: + +1. **Indexing**: application is supplied with information sources (PDFs, text files, web pages, etc.) +2. **Conversion**: files are converted to string (because LLM works on text data). +3. **Splitting**: the string from previous step is split into parts (because LLM has fixed context window, meaning +it cannot handle big documents). +4. **Embedding generation**: a vector consisting of float values is generated out of chunks. This vector represents meaning +of text and the main propety of such vectors is that chunks with similar meaning has vectors that are close to. +Generation of such a vector is achieved by using a separate model called *embedding model*. +5. **Store**: chunks with relevant metadata (for example, from which document they were generated) and embedding vector are stored in a vector database. + +How answer is generated: + +1. **Ask**: user asks AI a question. +2. **Question embedding**: an embedding model generates embedding vector of a query. +3. **Data finding**: vector database performs search of most relevant pieces of information (a finite count of pieces). +That's performed by vector similarity: meaning how close are chunk vector with question vector. +4. **Prompt generation**: using a prompt template the user question is *augmented* with found information. Found information +is not generally supplied to user, as it may seem strange that a user asked a question that was already supplied with +found information. These pieces of text can be either totally ignored or showed separately in UI tab "Sources". +5. **LLM generation**: LLM generates output. + +This ADR concerns about implementation of this architecture. + +## Decision Drivers + +* Prefer good and maintained libraries over self-made solutions for better quality. +* The usage of framework should be easy. It would seem strange when user wants to download a BIB editor, but they are +required to install some separate software (or even Python runtime). +* RAG shouldn't provide any additional money costs. Users should pay only for LLM generation. + +## Considered Options + +* Use a hand-crafted RAG +* Use a third-party Java library +* Use a standalone application +* Use an online service + +## Decision Outcome + +Chosen option: mix of "Use a hand-crafted RAG" and "Use a third-party Java library". + +Third-party libraries provide excellent resources for connecting to an LLM or extracting text from PDF files. For RAG, +we mostly used all the machinery provided by `langchain4j`, but there were moments that should be hand-crafted: + +* **LLM connection**: due to () this was delegated to another library `jvm-openai`. +* **Embedding generation**: due to (), + this was delegated to another library `djl`. +* **Indexing**: `langchain4j` is just a bunch of useful tools, but we still have to orchestrate when indexing should +happen and what files should be processed. +* **Vector database**: there seems to be no embedded vector database (except SQLite with `sqlite-vss` extension). We +implemented vector database using `MVStore` because that was easy. + +## Pros and Cons of the Options + +### Use a hand-crafted RAG + +* Good, because we have the full control over generation +* Good, because extendable +* Bad, because LLM connection, embedding models, vector storage, and file conversion should be implemented manually +* Bad, because it's hard to make a complex RAG architecture + +### Use a third-party Java library + +* Good, because provides well-tested and maintained tools +* Good, because libraries have many LLM integrations, as well as embedding models, vector storage, and file conversion tools +* Good, because they provide complex RAG pipelines and extensions +* Neutral, because they provide many tools and functions, but they should be orchestrated in a real application +* Bad, because some of them are raw and undocumented +* Bad, because they are all similar to `langchain` +* Bad, because they may have bugs + +### Use a standalone application + +* Good, because they provide complex RAG pipelines and extensions +* Good, because no additional code is required (except connecting to API) +* Neutral, because they provide not that many LLM integrations, embedding models, and vector storages +* Bad, because a standalone app running is required. Users may be required to set it up properly +* Bad, because the internal working of app is hidden. Additional agreement to Privacy or Terms of Service is needed +* Bad, because hard to extend + +### Use an online service + +* Good, because all data is processed and stored not on the user's machine: faster and no memory is used. +* Good, because they provide complex RAG pipelines and extensions +* Good, because no additional code is required (except connecting to API) +* Neutral, because they provide not that many LLM integrations, embedding models, and vector storages +* Bad, because requires connection to Internet +* Bad, because data is processed by a third party company +* Bad, because most of them require additional payment (in fact, it would be impossible to develop a free service like that) diff --git a/jabref/docs/decisions/0038-use-entryId-for-bibentries.md b/jabref/docs/decisions/0038-use-entryId-for-bibentries.md new file mode 100644 index 00000000..94504a6b --- /dev/null +++ b/jabref/docs/decisions/0038-use-entryId-for-bibentries.md @@ -0,0 +1,31 @@ +--- +title: Use BibEntry.getId for BibEntry at indexing +nav_order: 38 +parent: Decision Records +--- + + +# Use `BibEntry.getId` for BibEntries at Indexing + +## Context and Problem Statement + +The `BibEntry` class has `equals` and `hashCode` implemented on the content of the bib entry. +Thus, if two bib entries have the same type, the same fields, and the same content, they are equal. + +This, however, is not useful in the UI, where equal entries are not the same entries. + +## Decision Drivers + +* Simple code +* Not changing much other JabRef code +* Working Lucene + +## Considered Options + +* Use `BibEntry.getId` for indexing `BibEntry` +* Use `System.identityHashCode` for indexing `BibEntry` +* Rewrite `BibEntry` logic + +## Decision Outcome + +Chosen option: "Use `BibEntry.getId` for indexing `BibEntry`", because is the "natural" thing to ensure distinction between two instances of a `BibEntry` object - regardless of equality. diff --git a/jabref/docs/decisions/0039-use-apache-velocity-as-template-engine.md b/jabref/docs/decisions/0039-use-apache-velocity-as-template-engine.md new file mode 100644 index 00000000..46435540 --- /dev/null +++ b/jabref/docs/decisions/0039-use-apache-velocity-as-template-engine.md @@ -0,0 +1,114 @@ +--- +nav_order: 39 +parent: Decision Records +--- + +# Use Apache Velocity as template engine + +## Context and Problem Statement + +We need to choose a template engine for [custom export filters](https://docs.jabref.org/collaborative-work/export/customexports) and [AI features](https://github.com/JabRef/jabref/pull/11884). + +A discussion of template engines was also in [one of the JabRef repos](https://github.com/koppor/jabref/issues/392). + +A discussion was raised on StackOverflow ["Velocity vs. FreeMarker vs. Thymeleaf"](https://stackoverflow.com/q/1459426/10037342). + +## Decision Drivers + +* It should be fast. +* It should be possible to provide templates out of `String`s (required by the AI feature). +* It should have short and understandable syntax. Especially, it should work well with unset fields and empty `Optional`s. + +## Considered Options + +* Apache Velocity +* Apache FreeMarker +* Thymeleaf + +## Decision Outcome + +Chosen option: "Apache Velocity", because "Velocity's goal is to keep templates as simple as possible" ([source](https://stackoverflow.com/a/1984458/873282)). It is sufficient for our use case. +Furthermore, Apache Velocity is lightweight, and it allows to generate text output. This is a good fit for the AI feature. + +## Pros and Cons of the Options + +### Apache Velocity + +- Main page: . +- User guide: . +- Developer guide: . + +Example: + +```text +You are an AI assistant that analyses research papers. You answer questions about papers. + +Here are the papers you are analyzing: +#foreach( $entry in $entries ) +${CanonicalBibEntry.getCanonicalRepresentation($entry)} +#end +``` + +* Good, because supports plain text templating. +* Good, because it is possible to use `String` as a template. +* Good, because it has simple syntax, and it is designed for simple template workflows. +* Good, because it has a stable syntax ([source](https://stackoverflow.com/a/1984458/10037342)). +* Bad, because it is in maintenance mode. +* Bad, because [removed from Spring 5.0.1](https://www.baeldung.com/spring-template-engines#other-template-engines) + +### Apache FreeMarker + +- Main page: . +- User guide: . +- Developer guide: . + +Example: + +```text +You are an AI assistant that analyzes research papers. You answer questions about papers. + +Here are the papers you are analyzing: +<#list entries as entry> +${CanonicalBibEntry.getCanonicalRepresentation(entry)} + +``` + +* Good, because supports plain text templating. +* Good, because it is possible to use `String` as a template. +* Good, because in active development. +* Good, because it is powerful and flexible. +* Good, because it has extensive documentation ([source](https://stackoverflow.com/a/1984458/10037342)). +* Neutral, because it has received some API and syntax changes recently ([source](https://stackoverflow.com/a/1984458/10037342)). +* Neutral, because FreeMarker is used for complex template workflow, which we do not need in JabRef. + +### Thymeleaf + +- Main page: . +- Documentation: . + +Example: + +```text +You are an AI assistant that analyzes research papers. You answer questions about papers. + +Here are the papers you are analyzing: +[# th:each="entry : ${entries}"] +[(${CanonicalBibEntry.getCanonicalRepresentation(entry)})] +[/] +``` + +* Good, because supports plain text templating. +* Good, because it is possible to use `String` as a template. +* Good, because it has [several template modes](https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#what-kind-of-templates-can-thymeleaf-process), that helps to make HTML, XML, and other templates. +* Good, because it is powerful and flexible. +* Neutral, because the API is a bit more complex than the other options. +* Bad, because the syntax is more complex than the other options. Especially for text output. + +## More Information + +As stated in [the template discussion issue](https://github.com/koppor/jabref/issues/392), we should choose a template engine, and then slowly migrate previous code and templates to the chosen engine. + +Other template engines are discussed at , especially [`#other-template-engines`](https://www.baeldung.com/spring-template-engines#other-template-engines). +We did not find any other engine there worth switching to. + + diff --git a/jabref/docs/decisions/0040-display-front-cover-in-preview-tab.md b/jabref/docs/decisions/0040-display-front-cover-in-preview-tab.md new file mode 100644 index 00000000..48b57d64 --- /dev/null +++ b/jabref/docs/decisions/0040-display-front-cover-in-preview-tab.md @@ -0,0 +1,61 @@ +--- +parent: Decision Records +nav_order: 40 +--- + +# Display front cover for book citations in the Preview tab + +## Context and Problem Statement + +* Users have requested that the front covers of book citations are displayed in JabRef. +* This is discussed on the [JabRef forum](https://discourse.jabref.org/t/display-cover-images-for-books/3647) and raised as a [feature request](https://github.com/JabRef/jabref/issues/10120). +* We need to decide where the book cover should be placed. + +## Decision Drivers + +* It should not be obtrusive or distracting since the main use of JabRef is for articles not books. +* It should not obstruct the view of existing GUI components, specifically the MainTable or the information in the EntryEditor's tabs. + +## Considered Options + +Place the book cover in: + +1. The existing SidePane +2. A new SidePane +3. The Preview panel of the EntryEditor +4. A SplitPane next to the MainTable + +## Decision Outcome + +Chosen option: "The PreviewPanel of the EntryEditor". + +## Pros and Cons of the Options + +### Existing SidePane + +![Image: Placement in SidePane](0040-placement-in-sidepane.png) + +* Good, because it would be unobtrusive +* Bad, because it would crowd other panels in the SidePane +* Bad, because changing the size of the SidePane would affect both the MainTable and the EntryEditor. + +### New right-sided SidePane + +![Image: Placement in the new right-sided SidePane](0040-placement-in-new-sidepane.png) + +* Good, if integrated together with entry preview because it would make it easier to view a citation's preview. +* Bad, because an extra SidePane would make the interface overly complex. + +### The PreviewPanel of the EntryEditor + +![Image: Placement in the Preview Panel](0040-placement-in-preview-panel.png) + +* Good, because it would not be obtrusive or distracting. +* Bad, if the Entry Editor is closed, users will have to open the Entry Editor and navigate to the "Preview" or "Required fields" tab to see the cover. + +### SplitPane next to the MainTable + +![Image: Placement next to the Main Table](0040-placement-in-maintable.png) + +* Good, because changing the size of this SplitPane would [only affect the MainTable](https://github.com/user-attachments/assets/4e458099-ca5c-41bc-a33b-ce4240d7df82). +* Bad, because it would obstruct some columns in the MainTable. diff --git a/jabref/docs/decisions/0040-placement-in-maintable.png b/jabref/docs/decisions/0040-placement-in-maintable.png new file mode 100644 index 00000000..37704645 Binary files /dev/null and b/jabref/docs/decisions/0040-placement-in-maintable.png differ diff --git a/jabref/docs/decisions/0040-placement-in-new-sidepane.png b/jabref/docs/decisions/0040-placement-in-new-sidepane.png new file mode 100644 index 00000000..b0a1c42f Binary files /dev/null and b/jabref/docs/decisions/0040-placement-in-new-sidepane.png differ diff --git a/jabref/docs/decisions/0040-placement-in-preview-panel.png b/jabref/docs/decisions/0040-placement-in-preview-panel.png new file mode 100644 index 00000000..7ddebfdf Binary files /dev/null and b/jabref/docs/decisions/0040-placement-in-preview-panel.png differ diff --git a/jabref/docs/decisions/0040-placement-in-sidepane.png b/jabref/docs/decisions/0040-placement-in-sidepane.png new file mode 100644 index 00000000..a8829abe Binary files /dev/null and b/jabref/docs/decisions/0040-placement-in-sidepane.png differ diff --git a/jabref/docs/decisions/0041-use-one-form-for-singular-and-plural.md b/jabref/docs/decisions/0041-use-one-form-for-singular-and-plural.md new file mode 100644 index 00000000..94c53ca6 --- /dev/null +++ b/jabref/docs/decisions/0041-use-one-form-for-singular-and-plural.md @@ -0,0 +1,95 @@ +--- +nav_order: 41 +parent: Decision Records +--- + + +# Use one language string for pluralization localization + +## Context and Problem Statement + +For user-facing messages, sometimes, it needs to be counted: E.g., 1 entry updated, 2 entries updated, etc. + +In some languages, there is not only "one" and "more than one", but other forms: + +* zero → “لم نزرع أي شجرة حتى الآن” +* one → “لقد زرعنا شجرة ١ حتى الآن” +* two → “لقد زرعنا شجرتين ٢ حتى الآن” +* few → “لقد زرعنا ٣ شجرات حتى الآن” +* many → “لقد زرعنا ١١ شجرة حتى الآن” +* other → “لقد زرعنا ١٠٠ شجرة حتى الآن” + +(Example is from [Pluralization: A Guide to Localizing Plurals](https://phrase.com/blog/posts/pluralization/)) + +How to localize pluralization? + +## Decision Drivers + +* Good English language +* Good localization to other languages + +## Considered Options + +* Use one language string for pluralization (no explicit pluralization) +* Use singular and plural +* Handling of multiple forms + +## Decision Outcome + +Chosen option: "Use one form only (no explicit pluralization)", because it is the most easiest to handle in the code. + +## Pros and Cons of the Options + +### Use one language string for pluralization (no explicit pluralization) + +Example: + +- `Imported 0 entry(s)` +- `Imported 1 entry(s)` +- `Imported 12 entry(s)` + +There are sub alternatives here: + +* `Imported %0 entry(ies)`. +* `Number of entries imported: %0` (always use "other" plural form) + +These arguments are for the general case of using a single text for all kinds of numbers: + +* Good, because easy to handle in the code +* Bad, because reads strange in English UI + +### Use singular and plural + +Example: + +- `Imported 0 entries` +- `Imported 1 entry` +- `Imported 12 entries` + +* Good, because reads well in English +* Bad, because all localizations need to take an `if` check for the count +* Bad, because Arabic not localized properly + +### Handling of multiple forms + +Example: + +- `Imported 0 entries` +- `Imported 1 entry` +- `Imported 12 entries` + +Code: `Localization.lang("Imported %0 entries", "Imported %0 entry.", "Imported %0 entries.", "Imported %0 entries.", "Imported %0 entries.", "Imported %0 entries.", count)` + +* Good, because reads well in English +* Bad, because sophisticated localization handling is required +* Bad, because no Java library for handling pluralization is known +* Bad, because Arabic not localized properly + +## More Information + +- [Language Plural Rules](https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html) +- [Unicode CLDR Project's Plural Rules](https://cldr.unicode.org/index/cldr-spec/plural-rules) +- [Implementation in Mozilla Firefox](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) +- [SX discussion on plural forms](https://english.stackexchange.com/a/90283/66058) + + diff --git a/jabref/docs/decisions/adr-template.md b/jabref/docs/decisions/adr-template.md new file mode 100644 index 00000000..7321daa3 --- /dev/null +++ b/jabref/docs/decisions/adr-template.md @@ -0,0 +1,86 @@ +--- +# Configuration for the Jekyll template "Just the Docs" +# Remove it when creating a new ADR +title: ADR Template + +# Replace this with the number of the ADR +nav_order: 100 + +# Keep this +parent: Decision Records + +# These are optional metadata elements. Feel free to remove any of them. +# status: "{proposed | rejected | accepted | deprecated | … | superseded by ADR-0123" +# date: {YYYY-MM-DD when the decision was last updated} +# decision-makers: {list everyone involved in the decision} +# consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication} +# informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication} +--- + + +# {short title, representative of solved problem and found solution} + +## Context and Problem Statement + +{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. + You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.} + + +## Decision Drivers + +* {decision driver 1, e.g., a force, facing concern, …} +* {decision driver 2, e.g., a force, facing concern, …} +* … + +## Considered Options + +* {title of option 1} +* {title of option 2} +* {title of option 3} +* … + +## Decision Outcome + +Chosen option: "{title of option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}. + + +### Consequences + +* Good, because {positive consequence, e.g., improvement of one or more desired qualities, …} +* Bad, because {negative consequence, e.g., compromising one or more desired qualities, …} +* … + + +### Confirmation + +{Describe how the implementation of/compliance with the ADR can/will be confirmed. Is the chosen design and its implementation in line with the decision? E.g., a design/code review or a test with a library such as ArchUnit can help validate this. Note that although we classify this element as optional, it is included in many ADRs.} + + +## Pros and Cons of the Options + +### {title of option 1} + + +{example | description | pointer to more information | …} + +* Good, because {argument a} +* Good, because {argument b} + +* Neutral, because {argument c} +* Bad, because {argument d} +* … + +### {title of other option} + +{example | description | pointer to more information | …} + +* Good, because {argument a} +* Good, because {argument b} +* Neutral, because {argument c} +* Bad, because {argument d} +* … + + +## More Information + +{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when/how this decision the decision should be realized and if/when it should be re-visited. Links to other decisions and resources might appear here as well.} diff --git a/jabref/docs/decisions/index.md b/jabref/docs/decisions/index.md new file mode 100644 index 00000000..54c0c329 --- /dev/null +++ b/jabref/docs/decisions/index.md @@ -0,0 +1,12 @@ +--- +nav_order: 4 +has_children: true +--- +# Decision Records + +Below, all "architectural decision records" for JabRef are listed. +This list uses the TOC functionality of the [Just the Docs Jekyll template](https://just-the-docs.github.io/just-the-docs/). + +For new ADRs, please use [adr-template.md](adr-template.md) as basis. +More information on MADR is available at . +General information about architectural decision records is available at . diff --git a/jabref/docs/getting-into-the-code/development-strategy.md b/jabref/docs/getting-into-the-code/development-strategy.md new file mode 100644 index 00000000..b58387ba --- /dev/null +++ b/jabref/docs/getting-into-the-code/development-strategy.md @@ -0,0 +1,59 @@ +--- +parent: Getting into the code +nav_order: 1 +--- +# JabRef's development strategy + +We aim to keep up to high-quality code standards and use code quality tools wherever possible. + +To ensure high code-quality, + +* We follow the principles of [Java by Comparison](https://java.by-comparison.com). +* We follow the principles of [Effective Java](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/). +* We use [Design Patterns](https://java-design-patterns.com/patterns/) when applicable. +* We document our design decisions using the lightweight architectural decision records [MADR](https://adr.github.io/madr/). +* We review each external pull request by at least two [JabRef Core Developers](https://github.com/JabRef/jabref/blob/main/MAINTAINERS). + +Read on about our automated quality checks at [Code Quality](../code-howtos/code-quality.md). + +## Continuous integration + +JabRef has automatic checks using GitHub actions in place. +One of them is checking for the formatting of the code. +Consistent formatting ensures more easy reading of the code. +Thus, we pay attention that JabRef's code follows the same code style. + +Binaries are created using [gradle](https://gradle.org) and are uploaded to [https://builds.jabref.org](https://builds.jabref.org). +These binaries are created without any checks to have them available as quickly as possible, even if the localization or some fetchers are broken. +Deep link to the action: [https://github.com/JabRef/jabref/actions?workflow=Deployment](https://github.com/JabRef/jabref/actions?workflow=Deployment). + +## Branches + +The branch [main](https://github.com/JabRef/jabref/tree/main) is the main development line and is intended to incorporate fixes and improvements as soon as possible and to move JabRef forward to modern technologies such as the latest Java version. + +Other branches are used for discussing improvements with the help of [pull requests](https://github.com/JabRef/jabref/pulls). One can see the binaries of each branch at [https://builds.jabref.org/](https://builds.jabref.org). Releases mark milestones and are based on the `main` branch at a point in time. + +## How JabRef acquires contributors + +* We participate in [Hacktoberfest](https://www.hacktoberfest.com). +* We participate in [Google Summer of Code](https://developers.google.com/open-source/gsoc/). + +## Historical notes + +### JabRef 4.x + +The main roadmap for JabRef 4.x was to modernize the UI, make the installation easier and reduce the number of opened issues. + +### JabRef 3.x + +JabRef at the beginning of 2016 had a few issues: + +* Most of the code is untested, non-documented, and contains a lot of bugs and issues. +* During the lifetime of JabRef, a lot of features, UI elements and preferences have been added. All of them are loosely wired together in the UI, but the UI lacks consistency and structure. +* This makes working on JabRef interesting as in every part of the program, one can improve something. :smiley: + +JabRef 3.x is the effort to try to fix a lot of these issues. Much has been achieved, but much is still open. + +We currently use two approaches: a) rewrite and put under test to improve quality and fix bugs, b) increase code quality. This leads to pull requests being reviewed by two JabRef developers to ensure i) code quality, ii) fit within the JabRef architecture, iii) high test coverage. + +Code quality includes using latest Java features, but also readability. diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/eclipse.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/eclipse.md new file mode 100644 index 00000000..5f437f3c --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/eclipse.md @@ -0,0 +1,68 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 90 +--- + +# Advanced: Eclipse as IDE + +For advanced users, [Eclipse](https://eclipse.org) (`2023-03` or newer) is also possible. +For JDK20 you need to install the additional [support for jdk20 as extension](https://marketplace.eclipse.org/content/java-20-support-eclipse-2023-03-427)). +On Ubuntu Linux, you can follow the [documentation from the Ubuntu Community](https://help.ubuntu.com/community/EclipseIDE#Download_Eclipse) or the [step-by-step guideline from Krizna](https://www.krizna.com/ubuntu/install-eclipse-in-ubuntu-12-04/) to install Eclipse. +On Windows, download it from [www.eclipse.org](http://www.eclipse.org/downloads/) and run the installer. + +For Eclipse, a working Java (Development Kit) 20 installation is required. +In the case of IntelliJ, this will be downloaded inside the IDE (if you follow the steps below). + +In the command line (terminal in Linux, cmd in Windows) run `javac -version` and make sure that the reported version is Java 20 (e.g., `javac 20`). +If `javac` is not found or a wrong version is reported, check your `PATH` environment variable, your `JAVA_HOME` environment variable or install the most recent JDK. +Please head to to download JDK 20. + +Always make sure your Eclipse installation us up to date. + +1. Run `./gradlew run` to generate all resources and to check if JabRef runs. + * The JabRef GUI should finally appear. + * This step is only required once. + * The directory `src-gen` is now filled. +2. Run `./gradlew eclipse` + * **This must always be executed, when there are new upstream changes.** +3. Open or import the existing project in Eclipse as Java project. + * Remark: Importing it as gradle project will not work correctly. + * Refresh the project in Eclipse +4. Create a run/debug configuration for the main class `org.jabref.Launcher` and/or for `org.jabref.gui.JabRefMain` (both can be used equivalently) + * Remark: The run/debug configuration needs to be added by right-clicking the class (e.g. `Launcher` or JabRefMain) otherwise it will not work. + + ![Creating the run/debug configuration by right-clicking on the class](../../images/eclipse-create-run-config.png) + * In the tab "Arguments" of the run/debug configuration, enter the following runtime VM arguments: + + ```text + --add-exports javafx.controls/com.sun.javafx.scene.control=org.jabref + --add-exports org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref + --add-exports javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls + --add-opens javafx.graphics/javafx.scene=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.event=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.collections=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.runtime=org.controlsfx.controls + --add-exports javafx.web/com.sun.webkit=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls + --patch-module org.jabref=build/resources/main + ``` + + * In the tab "Dependencies" of the run/debug configuration tick the checkbox "Exclude test code" +5. Optional: Install the [e(fx)clipse plugin](http://www.eclipse.org/efxclipse/index.html) from the Eclipse marketplace: 1. Help -> Eclipse Marketplace... -> Search tab 2. Enter "e(fx)clipse" in the search dialogue 3. Click "Go" 4. Click "Install" button next to the plugin 5. Click "Finish" +6. Now you can build and run/debug the application by either using `Launcher` or `JabRefMain`. This is the recommended way, since the application starts quite fast. + +## Localization Test Configuration (Eclipse) + +To run the `LocalizationConsistencyTest` you need to add some extra module information: Right-click on the file -> "Run/Debug as JUnit test". Go to the Run/debug configuration created for that file and in the arguments tab under VM-configurations add: + +```text +--add-exports javafx.graphics/com.sun.javafx.application=ALL-UNNAMED +--add-exports javafx.graphics/com.sun.javafx.stage=ALL-UNNAMED +--add-exports javafx.graphics/com.sun.javafx.stage=com.jfoenix +``` diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-module.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-module.png new file mode 100644 index 00000000..ab2c3149 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-module.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-open-as-project.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-open-as-project.png new file mode 100644 index 00000000..0c69ca71 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-choose-open-as-project.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run-output.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run-output.png new file mode 100644 index 00000000..9ecbae34 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run-output.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run.png new file mode 100644 index 00000000..a1a7ed25 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-run.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-tool-windows-refresh.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-tool-windows-refresh.png new file mode 100644 index 00000000..1531a35d Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-gradle-tool-windows-refresh.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-confirmation.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-confirmation.png new file mode 100644 index 00000000..516a38d4 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-confirmation.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-final-settings.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-final-settings.png new file mode 100644 index 00000000..c49a964e Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-final-settings.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-import-file.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-import-file.png new file mode 100644 index 00000000..950d2f2c Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-import-file.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-jabref-active.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-jabref-active.png new file mode 100644 index 00000000..07225747 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-jabref-active.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-restart-ide.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-restart-ide.png new file mode 100644 index 00000000..939f5e89 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-restart-ide.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-start-import.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-start-import.png new file mode 100644 index 00000000..5d1e5266 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-start-import.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-window.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-window.png new file mode 100644 index 00000000..581b0580 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-checkstyle-window.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-choose-build-gradle.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-choose-build-gradle.png new file mode 100644 index 00000000..d9a78c3b Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-choose-build-gradle.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-as-jabref.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-as-jabref.png new file mode 100644 index 00000000..59ce0514 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-as-jabref.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-select-xml-file.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-select-xml-file.png new file mode 100644 index 00000000..a31fb636 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import-select-xml-file.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import.png new file mode 100644 index 00000000..c1a32877 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-codestyle-import.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-autoimport.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-autoimport.png new file mode 100644 index 00000000..f442df38 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-autoimport.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-javadoc-do-not-wrap.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-javadoc-do-not-wrap.png new file mode 100644 index 00000000..f8ff03b8 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-editor-javadoc-do-not-wrap.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-enable-annotation-processing.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-enable-annotation-processing.png new file mode 100644 index 00000000..a6530681 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-enable-annotation-processing.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-install-checkstyle.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-install-checkstyle.png new file mode 100644 index 00000000..672d568b Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-install-checkstyle.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-installs-temurin.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-installs-temurin.png new file mode 100644 index 00000000..320e2835 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-installs-temurin.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-locate-bibentrytest.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-locate-bibentrytest.png new file mode 100644 index 00000000..9a972b35 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-locate-bibentrytest.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-project-settings-jdk.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-project-settings-jdk.png new file mode 100644 index 00000000..47902d31 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-project-settings-jdk.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-bibentry-test.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-bibentry-test.png new file mode 100644 index 00000000..c3470e66 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-bibentry-test.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test-launch-config.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test-launch-config.png new file mode 100644 index 00000000..ccd4c998 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test-launch-config.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test.png new file mode 100644 index 00000000..8f486804 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-run-single-test.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-select-jdk-eclipse-temurin.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-select-jdk-eclipse-temurin.png new file mode 100644 index 00000000..f25d1238 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-select-jdk-eclipse-temurin.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-build-and-run-intellij.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-build-and-run-intellij.png new file mode 100644 index 00000000..7d57f041 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-build-and-run-intellij.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-gradle-gradlejvm-is-projectjvm.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-gradle-gradlejvm-is-projectjvm.png new file mode 100644 index 00000000..c879597d Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-gradle-gradlejvm-is-projectjvm.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-run-tests-using-intellij.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-run-tests-using-intellij.png new file mode 100644 index 00000000..9288ae40 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-settings-run-tests-using-intellij.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-start-window.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-start-window.png new file mode 100644 index 00000000..2ce208b4 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-start-window.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-tests-are-green.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-tests-are-green.png new file mode 100644 index 00000000..178e64d0 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-tests-are-green.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-overridden-compiler-parameters.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-overridden-compiler-parameters.png new file mode 100644 index 00000000..a492bc68 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-overridden-compiler-parameters.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-select-download-jdk.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-select-download-jdk.png new file mode 100644 index 00000000..596abe28 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-select-download-jdk.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-settings-intellij-code-foldings.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-settings-intellij-code-foldings.png new file mode 100644 index 00000000..735ca1bd Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-settings-intellij-code-foldings.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-trust-project.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-trust-project.png new file mode 100644 index 00000000..47e7286d Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-trust-project.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/index.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/index.md new file mode 100644 index 00000000..cfdec762 --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/index.md @@ -0,0 +1,14 @@ +--- +parent: Getting into the code +has_children: true +nav_order: 2 +--- +# Set up a local workspace + +{: .important } +These steps are very important. They allow you to focus on the content and ensure that the code formatting always goes well. + +This guide explains how to set up your environment for development of JabRef. It includes information about prerequisites, configuring your IDE, and running JabRef locally to verify your setup. Please follow the steps one-by-one. + +First, we work on prerequisites (software, account, code fork) you need to get started to develop JabRef. +Then, we work on a proper IDE setup. diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-11-code-into-ide.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-11-code-into-ide.md new file mode 100644 index 00000000..9e150ae1 --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-11-code-into-ide.md @@ -0,0 +1,80 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 11 +--- + +# Step 1: Get the code into IntelliJ + +Start IntelliJ. + +IntelliJ shows the following window: + +{% figure caption:"IntelliJ Start Window" %} +![IntelliJ Start Window](guidelines-intellij-start-window.png) +{% endfigure %} + +Click on "Open" + +Choose `build.gradle` in the root of the jabref source folder: + +{% figure caption:"Choose `build.gradle` in the “Open Project or File” dialog" %} +![Open File or Project dialog](guidelines-intellij-choose-build-gradle.png) +{% endfigure %} + +After pressing "OK", IntelliJ asks how that file should be opened. +Answer: "Open as Project" + +{% figure caption:"Choose “Open as Project” in the Open Project dialog" %} +![Open Project dialog](guidelines-choose-open-as-project.png) +{% endfigure %} + +Then, trust the project: + +{% figure caption:"Choose “Trust Project” in the “Trust and Open Project” dialog" %} +![Trust and Open Project dialog](guidelines-trust-project.png) +{% endfigure %} + +## Ensure that committing via IntelliJ works + +IntelliJ offers committing using the UI. +Press Alt+0 to open the commit dialog. + +Unfortunately, IntelliJ has no support for ignored sub modules [[IDEA-285237](https://youtrack.jetbrains.com/issue/IDEA-285237/ignored-changes-in-submodules-are-still-visible-in-the-commit-window)]. +Fortunately, there is a workaround: + +Go to **File > Settings... > Version Control > Directory Mappings**.
    +**Note:** In some MacBooks, `Settings` can be found at the "IntelliJ" button of the app menu instead of at "File". + +Currently, it looks as follows: + +{% figure caption:"Directory Mappings unmodified" %} +![Directory Mappings including sub modules](intellij-directory-mappings-unmodified.png) +{% endfigure %} + +You need to tell IntelliJ to ignore the submodules `buildres\abbrv.jabref.org`, `src\main\resources\csl-locales`, and `src\main\resources\csl-styles`. +Select all three (holding the Ctrl key). +Then press the red minus button on top. + +This will make these directories "Unregistered roots:", which is fine. + +{% figure caption:"Directory Mappings having three unregistered roots" %} +![Directory Mappings having three repositories unregsitered](intellij-directory-mappings-unregistered-roots.png) +{% endfigure %} + +## Ensure that committing with other tools work + +Open a "git bash". +On Windows, navigate to `C:\git-repositories\JabRef`. +Open the context menu of the file explorer (using the right mouse button), choose "Open Git Bash here". + +Execute following command: + +```shell +git update-index --assume-unchanged buildres/abbrv.jabref.org src/main/resources/csl-styles src/main/resources/csl-locales +``` + +{: .tip } +If you do not see the context menu, re-install git following the steps given at [StackOverflow](https://stackoverflow.com/a/50667280/873282). + + diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md new file mode 100644 index 00000000..85fd85cf --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md @@ -0,0 +1,200 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 12 +--- + +# Step 2: Set up the build system: JDK and Gradle + +## Ensure that JDK 23 is available to IntelliJ + +Ensure you have a Java 23 SDK configured by navigating to **File > Project Structure... > Platform Settings > SDKs**.
    +**Note:** In some MacBooks, `Project Structure` can be found at the "IntelliJ" button of the app menu instead of at "File". + +{% figure caption:"JDKs 11, 14, and 15 shown in available SDKs. JDK 23 is missing." %} +![Plattform Settings - SDKs](intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png) +{% endfigure %} + +If there is another JDK than JDK 23 selected, click on the plus button and choose "Download JDK..." + +{% figure caption:"Download JDK..." %} +![Plattform Settings - SDKs - plus button - Download JDK...](guidelines-select-download-jdk.png) +{% endfigure %} + +Select JDK version 23 and then Eclipse Temurin. + +{% figure caption:"Example for JDK 23 - Choose Eclipse Temurin" %} +![Download Eclipse Temurin](guidelines-intellij-select-jdk-eclipse-temurin.png) +{% endfigure %} + +After clicking "Download", IntelliJ installs Eclipse Temurin: + +{% figure caption:"IntelliJ installs Eclipse Temurin" %} +![IntelliJ installs Eclipse Temurin](guidelines-intellij-installs-temurin.png) +{% endfigure %} + +Navigate to **Project Settings > Project** and ensure that the projects' SDK is Java 23. + +{% figure caption:"Project SDK is pinned to the downloaded SDK (showing JDK 23 as example)" %} +![Project SDK is JDK 23](guidelines-intellij-project-settings-jdk.png) +{% endfigure %} + +Click "OK" to store the changes. + +## Ensure correct JDK setting for Gradle + +Navigate to **File > Settings... > Build, Execution, Deployment > Build Tools > Gradle** and select the "Project SDK" as the Gradle JVM at the bottom. +If that does not exist, just select JDK 23. + +{% figure caption:"Gradle JVM is project SDK (showing "Projekt SDK temurin-23" as example)" %} +![Gradle JVM is project SDK](guidelines-intellij-settings-gradle-gradlejvm-is-projectjvm.png) +{% endfigure %} + +## Enable compilation by IntelliJ + +To prepare IntelliJ's build system additional steps are required: + +Navigate to **Build, Execution, Deployment > Compiler > Java Compiler**, and under "Override compiler parameters per-module", click add (\[+]) and choose `JabRef.main`: + +{% figure caption:"Choose JabRef.main" %} +![Gradle JVM is project SDK](guidelines-choose-module.png) +{% endfigure %} + +Copy following text into your clipboard: + +```text +--add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref +--add-exports=org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref +--add-reads org.jabref=org.apache.commons.csv +--add-reads org.jabref=org.fxmisc.flowless +--add-reads org.jabref=langchain4j.core +--add-reads org.jabref=langchain4j.open.ai +``` + +Then double click inside the cell "Compilation options". +Press Ctrl+A to mark all text. +Press Ctrl+V to paste all text. +Press Enter to have the value really stored. +Otherwise, it seems like the setting is stored, but it is not there if you reopen this preference dialog. + +Note: If you use the expand arrow, you need to press Shift+Enter to close the expansion and then Enter to commit the value. + +{% figure caption:"Resulting settings for module JabRef.main" %} +![Overridden compiler parameters](guidelines-overridden-compiler-parameters.png) +{% endfigure %} + +Then click on "Apply" to store the setting. + +Note: If this step is omitted, you will get: `java: package com.sun.javafx.scene.control is not visible (package com.sun.javafx.scene.control is declared in module javafx.controls, which does not export it to module org.jabref)`. + +## Enable annotation processors + +Enable annotation processors by navigating to **Build, Execution, Deployment > Compiler > Annotation processors** and check "Enable annotation processing" + +{% figure caption:"Enabled annotation processing" %} +![Enable annotation processing](guidelines-intellij-enable-annotation-processing.png) +{% endfigure %} + +## Using Gradle from within IntelliJ IDEA + +{: .note } +Ensuring JabRef builds with Gradle should always be the first step because, e.g. it generates additional sources that are required for compiling the code. + +Open the Gradle Tool Window with the small button that can usually be found on the right side of IDEA or navigate to **View > Tool Windows > Gradle**. +In the Gradle Tool Window, press the "Reload All Gradle Projects" button to ensure that all settings are up-to-date with the setting changes. + +{% figure caption:"Reload of Gradle project" %} +![Highlighted reload button](guidelines-gradle-tool-windows-refresh.png) +{% endfigure %} + +After that, you can use the Gradle Tool Window to build all parts of JabRef and run it. +To do so, expand the JabRef project in the Gradle Tool Window and navigate to Tasks. +From there, you can build and run JabRef by double-clicking **JabRef > Tasks > application > run**. + +{% figure caption:"JabRef > Tasks > application > run" %} +![JabRef > Tasks > application > run](guidelines-gradle-run.png) +{% endfigure %} + +The Gradle run window opens, shows compilation and then the output of JabRef. +The spinner will run as long as JabRef is open. + +{% figure caption:"Gradle run Window" %} +![Gradle run window](guidelines-gradle-run-output.png) +{% endfigure %} + +You can close JabRef again. + +After that a new entry called "jabref \[run]" appears in the run configurations. +Now you can also select "jabref \[run]" and either run or debug the application from within IntelliJ. + +{: .note } +You can run any other development task similarly. + +## Using IntelliJ's internal build system for tests + +In **File > Settings... > Build, Execution, Deployment > Build Tools > Gradle** the setting "Run tests using:" is set to "IntelliJ IDEA". + +{% figure caption:"IntelliJ setting: Run tests using IntelliJ" %} +![IntelliJ setting: Run tests using IntelliJ"](guidelines-intellij-settings-run-tests-using-intellij.png) +{% endfigure %} + +{: .note } +In case there are difficulties later, this is the place to switch back to gradle. + +Click "OK" to close the preference dialog. + +In the menu bar, select **Build > Rebuild project**. + +IntelliJ now compiles JabRef. +This should happen without any error. + +Now you can use IntelliJ IDEA's internal build system by using **Build > Build Project**. + +## Final build system checks + +To run an example test from IntelliJ, we let IntelliJ create a launch configuration: + +Locate the class `BibEntryTest`: +Press Ctrl+N. +Then, the "Search for classes dialog" pops up. +Enter `bibentrytest`. +Now, `BibEntryTest` should appear first: + +{% figure caption:"IntelliJ search for class “BibEntryTest”" %} +![IntelliJ search for class "BibEntryTest"](guidelines-intellij-locate-bibentrytest.png) +{% endfigure %} + +Press Enter to jump to that class. + +Hover on the green play button on `defaultConstructor`: + +{% figure caption:"However on green play button" %} +![However on green play button](guidelines-intellij-run-single-test.png) +{% endfigure %} + +Then, click on it. +A popup menu opens. +Choose the first entry "Run testDefaultConstructor" and click on it. + +{% figure caption:"Run testDefaultConstructor" %} +![Popup menu - Run testDefaultConstructor](guidelines-intellij-run-single-test-launch-config.png) +{% endfigure %} + +Then, the single test starts. + +You also have an entry in the Launch configurations to directly launch the test. +You can also click on the debug symbol next to it to enable stopping at breakpoints. + +{% figure caption:"Launch menu contains BibEntry test case" %} +![Launch menu contains BibEntry test case](guidelines-intellij-run-bibentry-test.png) +{% endfigure %} + +The tests are green after the run. +You can also use the play button there to re-execute the tests. +A right-click on "BibEntryTests" enables the debugger to start. + +{% figure caption:"Run window for the BibEntry test case" %} +![Run window for the BibEntry test case](guidelines-intellij-tests-are-green.png) +{% endfigure %} + + diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md new file mode 100644 index 00000000..622ab550 --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md @@ -0,0 +1,164 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 13 +--- + +# Step 3: Set up JabRef's code style + +Contributions to JabRef's source code need to have a code formatting that is consistent with existing source code. For that purpose, JabRef provides code-style and check-style definitions. + +Install the [CheckStyle-IDEA plugin](http://plugins.jetbrains.com/plugin/1065?pr=idea), it can be found via the plug-in repository: +Navigate to **File > Settings... > Plugins"**. +On the top, click on "Marketplace". +Then, search for "Checkstyle". +Click on "Install" choose "CheckStyle-IDEA".
    +**Note:** In some MacBooks, `Settings` can be found at the "IntelliJ" button of the app menu instead of at "File". + +{% figure caption:"Install CheckStyle" %} +![Install CheckStyle](guidelines-intellij-install-checkstyle.png) +{% endfigure %} + +After clicking, IntelliJ asks for confirmation: + +{% figure caption:"Third Party Plugin Privacy Notice" %} +![Third Party Plugin Privacy Notice](guidelines-intellij-checkstyle-confirmation.png) +{% endfigure %} + +If you agree, click on "Agree" and you can continue. + +Afterwards, use the "Restart IDE" button to restart IntelliJ. + +{% figure caption:"IntelliJ restart IDE" %} +![IntelliJ restart IDE](guidelines-intellij-checkstyle-restart-ide.png) +{% endfigure %} + +Click on "Restart" to finally restart. + +Wait for IntelliJ coming up again. + +Go to **File > Settings... > Editor > Code Style** + +Click on the settings wheel (next to the scheme chooser), +then click "Import Scheme >", +then click "IntelliJ IDEA code style XML" + +{% figure caption:"Location of “Import Scheme > IntelliJ IDEA code style XML”" %} +![Location of IntelliJ IDEA code style XML](guidelines-intellij-codestyle-import.png) +{% endfigure %} + +You have to browse for the directory `config` in JabRef's code. +There is an `IntelliJ Code Style.xml`. + +{% figure caption:"Browsing for `config/IntelliJ Code Style.xml`" %} +![Browsing for config/IntelliJ Code Style.xml](guidelines-intellij-codestyle-import-select-xml-file.png) +{% endfigure %} + +Click "OK". + +At following dialog is "Import Scheme". +Click there "OK", too. + +{% figure caption:"Import to JabRef" %} +![Import to JabRef](guidelines-intellij-codestyle-import-as-jabref.png) +{% endfigure %} + +Click on "Apply" to store the preferences. + +## Put JabRef's checkstyle configuration in place + +Now, put the checkstyle configuration file is in place: + +Go to **File > Settings... > Tools > Checkstyle > Configuration File** + +Trigger the import dialog of a CheckStyle style by clicking the \[+] button: + +{% figure caption:"Trigger the rule import dialog" %} +![Trigger the rule import dialog](guidelines-intellij-checkstyle-start-import.png) +{% endfigure %} + +Then: + +* Put "JabRef" as description. +* Browse for `config/checkstyle/checkstyle.xml` +* Tick "Store relative to project location" +* Click "Next" + +{% figure caption:"Filled Rule Import Dialog" %} +![Filled Rule Import Dialog](guidelines-intellij-checkstyle-import-file.png) +{% endfigure %} + +Click on "Finish" + +Activate the CheckStyle configuration file by ticking it in the list + +{% figure caption:"JabRef's checkstyle config is activated" %} +![JabRef's checkstyle config is activated](guidelines-intellij-checkstyle-jabref-active.png) +{% endfigure %} + +Ensure that the [latest CheckStyle version](https://checkstyle.org/releasenotes.html) is selected (10.3.4 or higher). +Also, set the "Scan Scope" to "Only Java sources (including tests)". + +{% figure caption:"Checkstyle is the highest version - and tests are also scanned" %} +![Checkstyle is the highest version - and tests are also scanned](guidelines-intellij-checkstyle-final-settings.png) +{% endfigure %} + +Save settings by clicking "Apply" and then "OK" + +## Run checkstyle + +In the lower part of IntelliJ's window, click on "Checkstyle". +In "Rules", change to "JabRef". +Then, you can run a check on all modified files. + +{% figure caption:"JabRef's style is active - and we are ready to run a check on all modified files" %} +![JabRef's style is active - and we are ready to run a check on all modified files](guidelines-intellij-checkstyle-window.png) +{% endfigure %} + +## Have auto format working properly in JavaDoc + +To have auto format working properly in the context of JavaDoc and line wrapping, "Wrap at right margin" has to be disabled. Details are found in [IntelliJ issue 240517](https://youtrack.jetbrains.com/issue/IDEA-240517). + +Go to **File > Settings... > Editor > Code Style > Java > JavaDoc**. + +At "Other", disable "Wrap at right margin" + +{% figure caption:"”Wrap at right margin” disabled" %} +!["Wrap at right margin" disabled](guidelines-intellij-editor-javadoc-do-not-wrap.png) +{% endfigure %} + +## Enable proper import cleanup + +To enable "magic" creation and auto cleanup of imports, go to **File > Settings... > Editor > General > Auto Import**. +There, enable both "Add unambiguous imports on the fly" and "Optimize imports on the fly" +(Source: [JetBrains help](https://www.jetbrains.com/help/idea/creating-and-optimizing-imports.html#automatically-add-import-statements)). + +{% figure caption:"Auto import enabled" %} +![Enable auto import](guidelines-intellij-editor-autoimport.png) +{% endfigure %} + +Press "OK". + +## Disable too advanced code folding + +Go to **File > Settings... > Editor > General > Code Folding**. +At section "General", disable "File header" and "Imports". +At section "Java", disable "One-line methods". + +{% figure caption:"Code foldings disabled" %} +![Code foldings disabled](guidelines-settings-intellij-code-foldings.png) +{% endfigure %} + +Press "OK". + +## Final comments + +{: .highlight } +> Now you have configured IntelliJ completely. +> You can run the main application using Gradle and the test cases using IntelliJ. +> The code formatting rules are imported - and the most common styling issue at imports is automatically resolved by IntelliJ. +> Finally, you have Checkstyle running locally so that you can check for styling errors before submitting the pull request. + +Got it running? GREAT! You are ready to lurk the code and contribute to JabRef. Please make sure to also read our [contribution guide](https://devdocs.jabref.org/contributing#contribute-code). + + diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-89-run-with-intellij.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-89-run-with-intellij.md new file mode 100644 index 00000000..1f728d63 --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-89-run-with-intellij.md @@ -0,0 +1,82 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 89 +--- + +# Advanced: Build and run using IntelliJ IDEA + +In "Step 2: Setup the build system: JDK and Gradle", IntelliJ was configured to use Gradle as tool for launching JabRef. +It is also possible to use IntelliJ's internal build and run system to launch JabRef. +Due to [IDEA-119280](https://youtrack.jetbrains.com/issue/IDEA-119280), it is a bit more work. + +1. Navigate to **File > Settings... > Build, Execution, Deployment > Build Tools > Gradle**. +2. Change the setting "Build and run using:" to "IntelliJ IDEA". +3. Navigate to **File > Settings... > Build, Execution, Deployment > Compiler**. +4. Uncheck `Clear output directory on rebuild`. +5. Navigate to **File > Settings... > Build, Execution, Deployment > Compiler > Java Compiler**. +6. Uncheck `--Use 'release' option for cross-compilation`. +7. Click "OK" to store the preferences and close the dialog. +8. **Build > Build Project** (Ctrl+F9) +9. Open the project view (Alt+1, on macOS cmd+1) +10. Copy all build resources to the folder of the build classes + 1. Navigate to the folder `build/resources/main` + 1. Right click -> "Open In" -> "Explorer (Finder on macOS)" + 1. Navigate into directory "main" + 1. Select the folder `out/production/classes` + 1. Right click -> "Open In" -> "Explorer (Finder on macOS)" + 1. Navigate into directory "classes" + 1. Now you have two Explorer windows opened. Copy all files and directories from the first one to the second one. +11. Locate the class `Launcher` (e.g., by ctrl+N and then typing `Launcher`). Press Enter to jump to that class. +
    + IntelliJ search for class “Launcher” +
    IntelliJ search for class “Launcher”
    +
    +12. Click on the green play button next to the `main` method to create a Launch configuration. IntelliJ will fail in launching. +
    + However on green play +
    However on green play
    +
    + +
    + Run JabRef via launcher +
    Run JabRef via launcher
    +
    + +13. On the top right of the IntelliJ window, next to the newly created launch configuration, click on the drop down +14. Click on "Edit Configurations..." +15. On the right, click on "Modify options" +16. Ensure that "Use classpath of module" is checked +17. Select "Add VM options" +18. In the newly appearing field for VM options, insert: + ```text + --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref + --add-opens=org.controlsfx.controls/org.controlsfx.control.textfield=org.jabref + --add-exports=org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref + --add-exports javafx.controls/com.sun.javafx.scene.control=org.jabref + --add-exports org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref + --add-exports javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls + --add-opens javafx.graphics/javafx.scene=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control=org.controlsfx.controls + --add-opens=javafx.controls/javafx.scene.control.skin=org.controlsfx.controls + --add-exports javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.event=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.collections=org.controlsfx.controls + --add-exports javafx.base/com.sun.javafx.runtime=org.controlsfx.controls + --add-exports javafx.web/com.sun.webkit=org.controlsfx.controls + --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls + --add-reads org.jabref=org.fxmisc.flowless + --add-reads org.jabref=org.apache.commons.csv + ``` +19. Click "Apply" +20. Click "Run". You can also click on the debug symbol next to it to enable stopping at breakpoints. +
    + Launch menu contains “Launcher” +
    Launch menu contains “Launcher”
    +
    + + + diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png new file mode 100644 index 00000000..76207c5d Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-choose-jdk-adoptopenjdk-on-windows-project-settings.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-directory-mappings-unmodified.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-directory-mappings-unmodified.png new file mode 100644 index 00000000..a3d64ff5 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-directory-mappings-unmodified.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-directory-mappings-unregistered-roots.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-directory-mappings-unregistered-roots.png new file mode 100644 index 00000000..8c7e0106 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-directory-mappings-unregistered-roots.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-hover-on-play-button.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-hover-on-play-button.png new file mode 100644 index 00000000..fb824f73 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-hover-on-play-button.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-run-jabref-from-launcher.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-run-jabref-from-launcher.png new file mode 100644 index 00000000..a2717408 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-run-jabref-from-launcher.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-run-launcher.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-run-launcher.png new file mode 100644 index 00000000..be78b643 Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-run-launcher.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-search-for-launcher.png b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-search-for-launcher.png new file mode 100644 index 00000000..87f2dd7e Binary files /dev/null and b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-search-for-launcher.png differ diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-01-github-account.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-01-github-account.md new file mode 100644 index 00000000..3b9c939b --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-01-github-account.md @@ -0,0 +1,25 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 1 +--- + +# Pre Condition 1: GitHub Account + +If you do not yet have a GitHub account, please [create one](https://github.com/join). + +Proposals for account names: + +* Login similar to your university account. Example: `koppor` +* Use your last name prefixed by the first letter of your first name. Example: `okopp` +* Use `firstname.lastname`. Example: `oliver.kopp` + +You can hide your email address by following the recommendations at [https://saraford.net/2017/02/19/how-to-hide-your-email-address-in-your-git-commits-but-still-get-contributions-to-show-up-on-your-github-profile-050/](https://saraford.net/2017/02/19/how-to-hide-your-email-address-in-your-git-commits-but-still-get-contributions-to-show-up-on-your-github-profile-050/). + +Most developers, though, do not hide their email address. They use one which may get public. Mostly, they create a new email account for development only. That account then be used for development mailing lists, mail exchange with other developers, etc. + +Examples: + +* Same login as in GitHub (see above). Example: `koppor@gmail.com` +* "`it`" in the name. Example: `kopp.it@gmail.com` +* Use the university login. Example: `st342435@stud.uni-stuttgart.de` diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-02-software.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-02-software.md new file mode 100644 index 00000000..354771b9 --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-02-software.md @@ -0,0 +1,28 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 2 +--- + +# Pre Condition 2: Required Software + +## git + +It is strongly recommended that you have git installed. + +* On Debian-based distros: `sudo apt-get install git` +* On Windows: [Download the installer](http://git-scm.com/download/win) and install it. Using [chocolatey](https://chocolatey.org/), you can run `choco install git.install -y --params "/GitAndUnixToolsOnPath /WindowsTerminal` to a) install git and b) have Linux commands such as `grep` available in your `PATH`. +* [Official installation instructions](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) + +## Installed IDE + +We highly encourdage [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=jabref), because all other IDEs work less good. +Especially using VS.Code has issues. + +IntelliJ's Community Edition works well. +Most contributors use the Ultimate Edition, because they are students getting that edition for free. + +## Other Tooling + +We collected some other tooling recommendations. +We invite you to read on at our [tool recommendations](../../code-howtos/tools.md). diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-03-code.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-03-code.md new file mode 100644 index 00000000..e932bc29 --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/pre-03-code.md @@ -0,0 +1,61 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 3 +--- + +# Pre Condition 3: Code on the local machine + +This section explains how you get the JabRef code onto your machine in a form allowing you to make contributions. + +## Fork JabRef into your GitHub account + +1. Log into your GitHub account +2. Go to [https://github.com/JabRef/jabref](https://github.com/JabRef/jabref) +3. Create a fork by clicking at fork button on the right top corner +4. A fork repository will be created under your account `https://github.com/YOUR_USERNAME/jabref`. + +## Clone your forked repository on your local machine + +In a command line, navigate to the folder where you want to place the source code (parent folder of `jabref`). +To prevent issues along the way, it is strongly recommend choosing a path that does not contain any special (non-ASCII or whitespace) characters. +In the following, we will use `c:\git-repositories` as base folder: + +```cmd +cd \ +mkdir git-repositories +cd git-repositories +git clone --recurse-submodules https://github.com/JabRef/jabref.git JabRef +cd JabRef +git remote rename origin upstream +git remote add origin https://github.com/YOUR_USERNAME/jabref.git +git fetch --all +git branch --set-upstream-to=origin/main main +``` + +{: .important } +> `--recurse-submodules` is necessary to have the required files available to JabRef. (Background: It concerns the files from [citation-style-language/styles](https://github.com/citation-style-language/styles) and more). +> +> Note that putting the repo JabRef directly on `C:\` or any other drive letter on Windows causes compile errors (**negative example**: `C:\jabref`). +> +> Please really ensure that you pass `JabRef` as parameter. Otherwise, you will get `java.lang.IllegalStateException: Module entity with name: jabref should be available`. See [IDEA-317606](https://youtrack.jetbrains.com/issue/IDEA-317606/Changing-only-the-case-of-the-Gradle-root-project-name-causes-exception-while-importing-project-java.lang.IllegalStateException) for details. + +{: .note-title } +> Background +> +> Initial cloning might be very slow (`27.00 KiB/s`). +> +> To prevent this, first the `upstream` repository is cloned. +> This repository seems to live in the caches of GitHub. +> +> Now, you have two remote repositories, where `origin` is yours and `upstream` is the one of the JabRef organization. +> +> You can see it with `git remote -v`: +> +> ```cmd +> c:\git-repositories\jabref> git remote -v +> origin https://github.com/YOURUSERNAME/jabref.git (fetch) +> origin https://github.com/YOURUSERNAME/jabref.git (push) +> upstream https://github.com/jabref/jabref.git (fetch) +> upstream https://github.com/jabref/jabref.git (push) +> ``` diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/trouble-shooting.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/trouble-shooting.md new file mode 100644 index 00000000..a264643f --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/trouble-shooting.md @@ -0,0 +1,97 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 99 +--- + +# Trouble shooting + +## Changes in `src/main/resources/csl-styles` are shown + +You need to remove these directories from the "Directory Mappings" in IntelliJ. +Look for the setting in preferences. +A long how-to is contained in [Step 1: Get the code into IntelliJ](intellij-11-code-into-ide.md). + +## Issues with `buildSrc` + +1. Open the context menu of `buildSrc`. +2. Select "Load/Unload modules". +3. Unload `jabRef.buildSrc`. + +## Issues with generated source files + +In rare cases you might encounter problems due to out-dated automatically generated source files. Running gradle task "clean" (Command line: `./gradlew clean`) deletes these old copies. Do not forget to run at least `./gradlew assemble` or `./gradlew eclipse` afterwards to regenerate the source files. + +## Issue with "Module org.jsoup" not found, required by org.jabref + +Following error message appears: + +```text +Error occurred during initialization of boot layer +java.lang.module.FindException: Module org.jsoup not found, required by org.jabref +``` + +This can include different modules. + +1. Go to File -> Invalidate caches... +2. Check "Clear file system cache and Local History". +3. Check "Clear VCS Log caches and indexes". +4. Uncheck the others. +5. Click on "Invalidate and Restart". +6. After IntelliJ restarted, you have to do the "buildSrc", "Log4JAppender", and "src-gen" steps again. + +## Issues with OpenJFX libraries in local maven repository + +There might be problems with building if you have OpenJFX libraries in local maven repository, resulting in errors like this: + +```text + > Could not find javafx-fxml-20-mac.jar (org.openjfx:javafx-fxml:20). + Searched in the following locations: + file:/repository/org/openjfx/javafx-fxml/20/javafx-fxml-20-mac.jar +``` + +As a workaround, you can remove all local OpenJFX artifacts by deleting the whole OpenJFX folder from specified location. + +## Issues with `JournalAbbreviationLoader` + +In case of a NPE at `Files.copy` at `org.jabref.logic.journals.JournalAbbreviationLoader.loadRepository(JournalAbbreviationLoader.java:30) ~[classes/:?]`, invalidate caches and restart IntelliJ. Then, Build -> Rebuild Project. + +If that does not help: + +1. Save/Commit all your work +2. Close IntelliJ +3. Delete all non-versioned items: `git clean -xdf`. This really destroys data +4. Execute `./gradlew run` +5. Start IntelliJ and try again. + +## Java installation + +An indication that `JAVA_HOME` is not correctly set or no JDK 21 is installed in the IDE is following error message: + +```text +compileJava FAILED + +FAILURE: Build failed with an exception. + +* What went wrong: +Execution failed for task ':compileJava'. +> java.lang.ExceptionInInitializerError (no error message) +``` + +Another indication is following output + +```text +java.lang.UnsupportedClassVersionError: org/javamodularity/moduleplugin/ModuleSystemPlugin has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 +``` + +## Attempts to open preferences panel freezes application + +This is likely caused by improper integration of your OS or Desktop Environment with your password prompting program or password manager. Ensure that these are working properly, then restart your machine and attempt to run the program. + +In an ideal scenario, a password prompt should appear when the program starts, provided the keyring your OS uses has not already been unlocked. However, the implementation details vary depending on the operating system, which makes troubleshooting more complex. + +For Windows and macOS users, specific configurations may differ based on the password management tools and settings used, so ensure your OS's password management system is properly set up and functioning. + +For Linux users, ensure that your [xdg-desktop-portal](https://wiki.archlinux.org/title/XDG_Desktop_Portal) settings refer to active and valid portal implementations installed on your system. However, there might be other factors involved, so additional research or guidance specific to your distribution may be necessary. + +For reference, see the discussion at issue [#11766](https://github.com/JabRef/jabref/issues/11766). diff --git a/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/vscode.md b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/vscode.md new file mode 100644 index 00000000..f2dfde15 --- /dev/null +++ b/jabref/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/vscode.md @@ -0,0 +1,56 @@ +--- +parent: Set up a local workspace +grand_parent: Getting into the code +nav_order: 91 +--- + +# Advanced: VS Code as IDE + +We are working on supporting VS Code for development. +There is basic support, but important things such as our code conventions are not in place. +Thus, use at your own risk. + +Quick howto: + +1. Start VS Code in the JabRef directory: `code .`. +2. There will be a poup asking "Reopen in Container". Click on that link. +3. VS Code restarts. Wait about 3 minutes until the dev container is build. You can click on "Starting Dev Container (show log)" to see the progress. +4. Afterwards, the Java project is imported. You can open the log (Click on "Check details"). Do that. +5. The terminal (tab "Java Build Status") will show some project synchronization and hang at `80% [797/1000]`. + It keeps hanging at `Importing root project: 80% Refreshing '/jabref'`. + Just wait. + Then it hangs at `Synchronizing Gradle build at /workspaces/jabref: 80%`. + Just wait. + Then it takes long for `Refreshing workspace:`. + Just wait. + **Note:** If you had the project opened in IntelliJ before, this might cause issues (as outlined at ). + Close everything, ensure that you committed your changes (if any), then execute `git clean -xdf` to wipe out all changes and created files - and start from step 1 again. +6. On the left, you will see a gradle button. +7. Click on the gradle button and open **JabRef -> Tasks -> application**. +8. Double click on **run**. +9. In the terminal, a new tab "run" opens. +10. On your desktop machine, open in a web browser. + Do not open the proposed port `6050`. + This is JabRef's remote command port. +11. Use `vscode` as password. +12. You will see an opened JabRef. + +Alternative to steps 9 to 10: + +In case interaction using the web browser is too slow, you can use a VNC connection: + +1. Install [VNC Connect](https://www.realvnc.com/en/connect/) +2. Use `vscode` as password + +## Trouble shooting + +In case there are reading errors on the file system, the docker container probably is out of order. +Close VS Code. +Stop the docker container, kill docker process in the Task Manager (if necessary). +Start docker again. +Start VS Code again. + +## Background + +We use VS Code's [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) feature. +Thereby, we use [desktop-lite](https://github.com/devcontainers/features/tree/main/src/desktop-lite#options) to enable viewing the JabRef app. diff --git a/jabref/docs/getting-into-the-code/high-level-documentation.md b/jabref/docs/getting-into-the-code/high-level-documentation.md new file mode 100644 index 00000000..c1d88ba3 --- /dev/null +++ b/jabref/docs/getting-into-the-code/high-level-documentation.md @@ -0,0 +1,48 @@ +--- +parent: Getting into the code +nav_order: 3 +--- +# High-level documentation + +This page describes relevant information about the code structure of JabRef precisely and succinctly. Closer-to-code documentation is available at [Code HowTos](../code-howtos). + +We have been successfully transitioning from a spaghetti to a more structured architecture with the `model` in the center, and the `logic` as an intermediate layer towards the `gui` which is the outer shell. There are additional utility packages for `preferences` and the `cli`. The dependencies are only directed towards the center. We have JUnit tests to detect violations of the most crucial dependencies (between `logic`, `model`, and `gui`), and the build will fail automatically in these cases. + +The `model` represents the most important data structures (`BibDatases`, `BibEntries`, `Events`, and related aspects) and has only a little bit of logic attached. +The `logic` is responsible for reading/writing/importing/exporting and manipulating the `model`, and it is structured often as an API the `gui` can call and use. +Only the `gui` knows the user and their preferences and can interact with them to help them solving tasks. +For each layer, we form packages according to their responsibility, i.e., vertical structuring. +The `model` should have no dependencies to other classes of JabRef and the `logic` should only depend on `model` classes. +The `cli` package bundles classes that are responsible for JabRef's command line interface. +The `preferences` package represents all information customizable by a user for her personal needs. + +We use an event bus to publish events from the `model` to the other layers. +This allows us to keep the architecture but still react upon changes within the core in the outer layers. +Note that we are currently switching to JavaFX's observables, as this concepts seems as we aim for a stronger coupling to the data producers. + +## Package Structure + +Permitted dependencies in our architecture are: + +```monospaced +gui --> logic --> model +gui ------------> model +gui ------------> preferences +gui ------------> cli +gui ------------> global classes + +logic ------------> model + +global classes ------------> everywhere + +cli ------------> model +cli ------------> logic +cli ------------> global classes +cli ------------> preferences +``` + +All packages and classes which are currently not part of these packages (we are still in the process of structuring) are considered as gui classes from a dependency stand of view. + +## Most Important Classes and their Relation + +Both GUI and CLI are started via the `JabRefMain` which will in turn call `JabRef` which then decides whether the GUI (`JabRefFrame`) or the CLI (`JabRefCLI` and a lot of code in `JabRef`) will be started. The `JabRefFrame` represents the Window which contains a `SidePane` on the left used for the fetchers/groups Each tab is a `BasePanel` which has a `SearchBar` at the top, a `MainTable` at the center and a `PreviewPanel` or an `EntryEditor` at the bottom. Any right click on the `MainTable` is handled by the `RightClickMenu`. Each `BasePanel` holds a `BibDatabaseContext` consisting of a `BibDatabase` and the `MetaData`, which are the only relevant data of the currently shown database. A `BibDatabase` has a list of `BibEntries`. Each `BibEntry` has an ID, a citation key and a key/value store for the fields with their values. Interpreted data (such as the type or the file field) is stored in the `TypedBibentry` type. The user can change the `JabRefPreferences` through the `PreferencesDialog`. diff --git a/jabref/docs/getting-into-the-code/index.md b/jabref/docs/getting-into-the-code/index.md new file mode 100644 index 00000000..7104747e --- /dev/null +++ b/jabref/docs/getting-into-the-code/index.md @@ -0,0 +1,5 @@ +--- +nav_order: 5 +has_children: true +--- +# Getting into the code diff --git a/jabref/docs/images/clean.png b/jabref/docs/images/clean.png new file mode 100644 index 00000000..c4c0df89 Binary files /dev/null and b/jabref/docs/images/clean.png differ diff --git a/jabref/docs/images/contribution-process-reviews-with-instructor.bpmn b/jabref/docs/images/contribution-process-reviews-with-instructor.bpmn new file mode 100644 index 00000000..3635bf1f --- /dev/null +++ b/jabref/docs/images/contribution-process-reviews-with-instructor.bpmn @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + Flow_1rx5tid + + + Flow_1j0t6uk + + + Flow_1rx5tid + Flow_1590snp + + + Flow_1590snp + Flow_1j0t6uk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jabref/docs/images/contribution-process-reviews-with-instructor.svg b/jabref/docs/images/contribution-process-reviews-with-instructor.svg new file mode 100644 index 00000000..7e196fe5 --- /dev/null +++ b/jabref/docs/images/contribution-process-reviews-with-instructor.svg @@ -0,0 +1,4 @@ + + + +ContributorPrepare SubmissionSubmit and Refine ContributionJabRef TeamCheck, Support,and MergeInstructorReview and Support Submission \ No newline at end of file diff --git a/jabref/docs/images/contribution-process-reviews.bpmn b/jabref/docs/images/contribution-process-reviews.bpmn new file mode 100644 index 00000000..d3984725 --- /dev/null +++ b/jabref/docs/images/contribution-process-reviews.bpmn @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + StartEvent_1y45yut + Activity_1s07thr + Gateway_0ynhdad + Task_1hcentk + Gateway_02k1xqy + Activity_0khk6qw + Event_1u5dh9a + + + Activity_128gn0k + Activity_1igjavi + Event_19nae0c + Gateway_05556xu + Activity_1btqbs6 + + + + Flow_0r3g541 + + + + Flow_17vz23d + Flow_0c4fwpj + + + Flow_0c4fwpj + Flow_0lgsoh4 + Flow_03i2ivp + + + Flow_0lgsoh4 + Flow_092pmqs + + + Flow_092pmqs + Flow_02lh9ep + + + Flow_1nn75tr + + + Flow_0eye3fk + Flow_17vz23d + + + Flow_0r3g541 + Flow_0dhe45e + Flow_0eye3fk + + + Flow_02lh9ep + Flow_0oegfa3 + Flow_1m35o86 + + + Flow_03i2ivp + Flow_1m35o86 + Flow_1d4hm5u + + + Flow_0oegfa3 + Flow_1nn75tr + + + Flow_1d4hm5u + Flow_0dhe45e + + + + + + + + + + + + + + + + + + + + Event_12e1v92 + Activity_1xkda0e + Activity_1u4hroq + Gateway_0gv16ci + Gateway_05tk0q6 + Event_1d57wd9 + Event_14530lv + Activity_0ho6twy + Event_17bbxgk + Activity_0l2aoea + + + + Flow_0xnbekr + + + Flow_0xnbekr + Flow_19d4vb4 + + + Flow_19d4vb4 + Flow_0gk2fm4 + + + Flow_0gk2fm4 + Flow_03itv3s + Flow_0qg5vp0 + + + Flow_0qg5vp0 + Flow_0agupxr + Flow_1ddwsfq + + + Flow_0agupxr + Flow_0jf3ewz + + + + Flow_1ddwsfq + Flow_1ys4n2y + + + + Flow_1ys4n2y + Flow_1umge8n + + + Flow_0jf3ewz + + + Flow_1umge8n + Flow_03itv3s + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jabref/docs/images/contribution-process-reviews.svg b/jabref/docs/images/contribution-process-reviews.svg new file mode 100644 index 00000000..39951a21 --- /dev/null +++ b/jabref/docs/images/contribution-process-reviews.svg @@ -0,0 +1,4 @@ + + + +ContributorCreate ContributionSubmit ContributionUpdate CodeComment Pull RequestJabRef TeamFrist DeveloperSecond DeveloperProvide FeedbackReview CodeProvide FeedbackReview CodeRequest ChangesMerge Pull ReuquestQuality OKQuality does not meet JabRef's requirementsQuality does not meet JabRef's requirementsPull Request incomingQuality OK`?Quality OK?Quality OKEverything OKChanges Requested \ No newline at end of file diff --git a/jabref/docs/images/eclipse-create-run-config.png b/jabref/docs/images/eclipse-create-run-config.png new file mode 100644 index 00000000..1412291a Binary files /dev/null and b/jabref/docs/images/eclipse-create-run-config.png differ diff --git a/jabref/docs/images/favicon.ico b/jabref/docs/images/favicon.ico new file mode 100644 index 00000000..575d1bda Binary files /dev/null and b/jabref/docs/images/favicon.ico differ diff --git a/jabref/docs/images/github-flow.png b/jabref/docs/images/github-flow.png new file mode 100644 index 00000000..dc9fdeb8 Binary files /dev/null and b/jabref/docs/images/github-flow.png differ diff --git a/jabref/docs/images/gradle-tests.png b/jabref/docs/images/gradle-tests.png new file mode 100644 index 00000000..fc24bc75 Binary files /dev/null and b/jabref/docs/images/gradle-tests.png differ diff --git a/jabref/docs/images/intellij-run-configuration-command-line.png b/jabref/docs/images/intellij-run-configuration-command-line.png new file mode 100644 index 00000000..cc71d5d0 Binary files /dev/null and b/jabref/docs/images/intellij-run-configuration-command-line.png differ diff --git a/jabref/docs/images/jabref-mainscreen.png b/jabref/docs/images/jabref-mainscreen.png new file mode 100644 index 00000000..997645f6 Binary files /dev/null and b/jabref/docs/images/jabref-mainscreen.png differ diff --git a/jabref/docs/images/layers-v1.svg b/jabref/docs/images/layers-v1.svg new file mode 100644 index 00000000..f9b8239d --- /dev/null +++ b/jabref/docs/images/layers-v1.svg @@ -0,0 +1,915 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + document content (UNO) + + frontend + + actions + + OOBibBase2 + + backend + + style + + OOTextIntoOO + + rangesort + data in doc, ranges + order ranges + fill ranges + markup text + XTextDocument + Backend, CitationGroups + + + + GUI: + BibEntry, BibDatabase, OOBibStyle + provides input in terms of these types + + provides connection to doc + Cite, Update, Merge, Separate, Manage, Export + Connect + Load Style + Create OOFrontend instance + Catch exceptions, Undo + Forward requests to actions + Check preconditions + + locations + citation keys + pageInfo + citation type + + + lookup, localOrder, number, + uniqueLetter, sort bibliography, + format citationMarkers, + format bibliography + + + or visually + within XText + + checkRangeOverlaps, checkRangeOverlapsWithCursor + connects the parts below + getVisuallySortedCitationGroups, imposeGlobalOrder + UpdateCitationMarkers, UpdateBibliography + lock screen refresh + GUI-independent part of actions + + + diff --git a/jabref/docs/images/refactor-moving.png b/jabref/docs/images/refactor-moving.png new file mode 100644 index 00000000..fb7b5e7f Binary files /dev/null and b/jabref/docs/images/refactor-moving.png differ diff --git a/jabref/docs/images/rewriteRun.png b/jabref/docs/images/rewriteRun.png new file mode 100644 index 00000000..2bfde7b2 Binary files /dev/null and b/jabref/docs/images/rewriteRun.png differ diff --git a/jabref/docs/images/submodule-commit.png b/jabref/docs/images/submodule-commit.png new file mode 100644 index 00000000..80986102 Binary files /dev/null and b/jabref/docs/images/submodule-commit.png differ diff --git a/jabref/docs/images/submodules.png b/jabref/docs/images/submodules.png new file mode 100644 index 00000000..6540210a Binary files /dev/null and b/jabref/docs/images/submodules.png differ diff --git a/jabref/docs/index.md b/jabref/docs/index.md new file mode 100644 index 00000000..72314acc --- /dev/null +++ b/jabref/docs/index.md @@ -0,0 +1,57 @@ +--- +nav_order: 1 +layout: home +--- +# Overview on Developing JabRef + +This page presents all development information around JabRef. +In case you are a end user, please head to the [user documentation](https://docs.jabref.org) or to the [general homepage](https://www.jabref.org) of JabRef. + +## Starting point for new developers + +On the page [Setting up a local workspace](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace), we wrote about the initial steps to get your IDE running. +We strongly recommend to continue reading there. +After you successfully cloned and build JabRef, you are invited to continue reading here. + +## How tos + +* External: [Sync your fork with the JabRef repository](https://help.github.com/articles/syncing-a-fork/) +* External (🇩🇪): Branches and pull requests: [https://github.com/unibas-marcelluethi/software-engineering/blob/master/docs/week2/exercises/practical-exercises.md](https://github.com/unibas-marcelluethi/software-engineering/blob/master/docs/week2/exercises/practical-exercises.md) + +## Teaching Exercises + +We are very happy that JabRef is part of [Software Engineering](https://en.wikipedia.org/wiki/Software_engineering) trainings. Please head to [Teaching](teaching.md) for more information on using JabRef as a teaching object and on previous courses where JabRef was used. + +## Miscellaneous Hints + +### Command Line + +The package `org.jabref.cli` is responsible for handling the command line options. + +During development, one can configure IntelliJ to pass command line parameters: + +![IntelliJ-run-configuration](images/intellij-run-configuration-command-line.png) + +Passing command line arguments using gradle is currently not possible as all arguments (such as `-Dfile.encoding=windows-1252`) are passed to the application. + +Without jlink, it is not possible to generate a fat jar any more. During development, the capabilities of the IDE has to be used. + +### Groups + +Diagram showing aspects of groups: [Groups.uml](https://github.com/JabRef/jabref/tree/3b3716b1e05a0d3273c886e102a8efe5e96472e0/docs/Groups.uml). + +## Architectural Decision Records + +[Architectural decisions for JabRef](https://devdocs.jabref.org/decisions/) are recorded. + +For new ADRs, please use [adr-template.md](https://github.com/JabRef/jabref/blob/main/docs/decisions/adr-template.md) as basis. +More information on MADR is available at . +General information about architectural decision records is available at . + +## FAQ + +* Q: I get `java: package org.jabref.logic.journals does not exist`. + + A: You have to ignore `buildSrc/src/main` as source directory in IntelliJ as indicated in our [setup guide](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace). + + Also filed as IntelliJ issue [IDEA-240250](https://youtrack.jetbrains.com/issue/IDEA-240250). diff --git a/jabref/docs/mkdocs-custom.css b/jabref/docs/mkdocs-custom.css new file mode 100644 index 00000000..cee505fe --- /dev/null +++ b/jabref/docs/mkdocs-custom.css @@ -0,0 +1,7 @@ +li.toctree-l2:first-child { + display: none; +} + +li.toctree-l3:first-child { + display: none; +} diff --git a/jabref/docs/requirements/ai.md b/jabref/docs/requirements/ai.md new file mode 100644 index 00000000..dfe6be9f --- /dev/null +++ b/jabref/docs/requirements/ai.md @@ -0,0 +1,16 @@ +--- +parent: Requirements +--- +# AI + +## User Interface + +### Chatting with AI +`req~ai.chat.new-message-based-on-previous~1` + +To enable simple editing and resending of previous messages, Cursor Up should show last message. +This should only happen if the current text field is empty. + +Needs: impl + + diff --git a/jabref/docs/requirements/index.md b/jabref/docs/requirements/index.md new file mode 100644 index 00000000..295ca2af --- /dev/null +++ b/jabref/docs/requirements/index.md @@ -0,0 +1,49 @@ +--- +nav_order: 7 +has_children: true +--- +# Requirements + +This part of the documentation collects requirements using [OpenFastTrace](https://github.com/itsallcode/openfasttrace). + +## Specifying requirements + +One writes directly below a Markdown heading a requirement identifier. + +Example: + +```markdown +### Example +`req~ai.example~1` +``` + +It is important that there is no empty line directly after the heading. + +{: note} +One needs to add `` to the end of the file, because the ID of the requirement needs to follow the heading directly. + +## Linking implementations + +Then, one writes down at the requirement. +Directly at the end, one writes that it requires an implementation: + +```markdown +Needs: impl +``` + +One can also state that there should be detailed design document (`dsn`). +However, typically in JabRef, we go from the requirement directly to the implementation. + +Then, at the implementation, a comment is added this implementation is covered: + +```java +// [impl->req~ai.example~1] +``` + +When executing the gradle task `traceRequirements`, `build/tracing.txt` is generated. +In case of a tracing error, one can inspect this file to see which requirements were not covered. + +## More Information + +- [User manual of OpenFastTrace](https://github.com/itsallcode/openfasttrace/blob/main/doc/user_guide.md) +- We cannot copy and paste real examples here, because of [openfasttrace#280](https://github.com/itsallcode/openfasttrace/issues/280). diff --git a/jabref/docs/teaching.md b/jabref/docs/teaching.md new file mode 100644 index 00000000..2c805f7b --- /dev/null +++ b/jabref/docs/teaching.md @@ -0,0 +1,164 @@ +--- +nav_order: 3 +--- +# JabRef and Software Engineering Training + +By using JabRef as training object in exercises and labs, students can level-up their coding and project management skills. When taking part in JabRef development, one will learn modern Java coding practices, how code reviews work and how to properly address reviewing feedback. + +## Why university instructors should cooperate with us? + +* High-quality student education due to real-world tooling and real-world code base +* Sustainability of student works: No more thrown-away solved exercises: They now are incorporated in a real-world product +* No need to provision infra structure +* Visibility of your research groups +* No need to think about basic software engineering exercises anymore: JabRef cooperation partners have them. + +## How to integrate JabRef in your class + +1. Choose task from the board [Candidates for university projects](https://github.com/orgs/JabRef/projects/3/views/3). + There, new functionality is categorized in small, medium, and large effort. + Moreover, categorization on the main focus (UI, logic, or both), + implementation effort, testing effort, and "issue understanding effort". + The latter category is important, because some issues are "quick wins" and others need thorough thinking. + + In general, all issues of JabRef are free to take. + Be aware that the difficulty of bugs and feature vary. + For the brave, the [Bug Board](https://github.com/orgs/JabRef/projects/7) or the [Feature Board](https://github.com/JabRef/jabref/projects/6) provide other issue sources. + Especially for Master students, these are excellent boards to find issues that train maintenance knowledge (which is essential for industry work). + Finally, there is a [collection of good first issues](https://github.com/orgs/JabRef/projects/5), if you search for something to start guiding you though a focused aspect of JabRef's code. +2. Get in touch with the JabRef team to reserve issues for your student group and possibly to discuss details. We offer email, skype, [gitter.im](https://gitter.im/JabRef/jabref), discord. Get in touch with [@koppor](https://github.com/koppor/) to find the right channel and to start forming the success of your course. +3. Schedule tasks with students +4. Students implement code +5. Students review other student's code (recommended: students of a previous year's project review current year's project code) +6. Students address review feedback +7. Students submit pull request +8. Code reviews by JabRef maintainers +9. Students address feedback and learn more about good coding practices by incorporating feedback +10. Students update their pull request +11. Pull request is merged + +For a near-to-perfect preparation and effect of the course, we ask you to get in touch with us **four weeks** in advance. Then, the JabRef team can a) learn about the starting skill level of the students, b) the aimed skill level at the end of the course, c) the amount of time the students are given to learn about and contribute to JabRef, d) check the [Candidates for university projects](https://github.com/orgs/JabRef/projects/3/views/3) for appropriate tasks (and fill it as needed), e) recommend appropriate features. + +It is also possible to just direct students to our [Contribution Guide](https://devdocs.jabref.org/contributing.html#contribute-code). The learning effect may be lower as the time of the students has to be spent to a) learn about JabRef and b) select an appropriate issue. + +Since a huge fraction of software costs is spent on [software maintenance](https://en.wikipedia.org/wiki/Software_maintenance), adding new features also educates in that aspect: perfective maintenance[1](teaching.md#LientzSwanson) is trained. When fixing bugs, corrective maintenance [2](teaching.md#LientzSwanson) is trained. + +## Process for contributions + +There is no special process for student contributions. We want to discuss it nevertheless to increase awareness of the time required from starting the contribution until the inclusion in a release of JabRef. + +The process for accepting contributions is as below. The syntax is [BPMN](https://en.wikipedia.org/wiki/Business_Process_Model_and_Notation) modeled using [bpmn.io](https://bpmn.io). + +[![process](images/contribution-process-reviews.svg)](https://raw.githubusercontent.com/JabRef/jabref/main/docs/images/contribution-process-reviews.svg) + +In short, the contribution is **reviewed by two JabRef developers**. Typically, they have constructive feedback on their contribution. This means, that the contributors get comments on their contribution enabling them to level-up their coding skill. Incorporating improvements takes time, too. The benefit is two-fold: a) contributors improve their coding skills and b) JabRef's code quality improves. All in all, we ask to respect the aims of the JabRef team and to reserve time to incorporate the reviewer's comments. + +GitHub describes that in their page [Understanding the GitHub flow](https://guides.github.com/introduction/flow/): + +[![GitHub flow](images/github-flow.png)](https://raw.githubusercontent.com/JabRef/jabref/main/docs/images/github-flow.png) + +## Process for Java newcomers + +Newcomers contributing in the context of a university teaching experience are invited to follow the process described above. In case the capacity of the instructing university allows, we propose a three-step approach. First, the contributors prepare their contribution as usual. Then, they submit the pull request _to a separate repository_. There, the instructor reviews the pull request and provides feedback. This happens in a loop until the instructor shows the green light. Then, the pull request can be submitted to the main JabRef repository. This will help to reduce the load on the JabRef team and improve the quality of the initial pull request. + +[![process with instructor](images/contribution-process-reviews-with-instructor.svg)](https://raw.githubusercontent.com/JabRef/jabref/main/docs/images/contribution-process-reviews-with-instructor.svg) + +## Past courses + +> In case your course is missing, feel free to add it. + +### English as course language + +#### Harbin Institute of Technology (HIT), China + +Course: Open Source Software Development, 2018, 2019 + +> In this course, students will be introduced to the processes and tools specific to Open Source Software development, and they will analyze existing projects to understand the architecture and processes of these projects. Besides, students will attempt to contribute source code to a large existing Open Source Software project. + +#### King's College London + +Course: BSc Computer Science Individual Project, 2022/2023 + +> Students experience the procedure of finding and fixing small and medium issues in an open source project. + +#### Northern Arizona University (NAU), USA + +Course [CS499 - Open Source Software Development](https://github.com/igorsteinmacher/CS499-OSS), 2018 + +> Students experience the process of getting involved in an Open Source project by engaging with a real project. Their goal is to make a "substantial" contribution to a project. + +#### University of Tennessee, Knoxville, USA + +Diversity awareness course by [Vandana Singh](https://sis.utk.edu/vandana/), 2022 + +#### University of Victoria, Canada + +Course [SENG371: Software Evolution](https://heat.csc.uvic.ca/coview/course/2024011/SENG371) by [Roberto A. Bittencourt](https://sites.google.com/site/robertoabprof/) + +> Introduces problems and solutions of long-term software maintenance/evolution and large-scale, long-lived software systems. Topics include software engineering techniques for programming-in-the-large, programming-in-the-many, legacy software systems, software architecture, software evolution, software maintenance, reverse engineering, program understanding, software visualization, advanced issues in object-oriented programming, design patterns, antipatterns, and client-server computing. Culminates in a team project. +> +> During the course, students work in small groups to solve three assignments, each assignment handling three JabRef issues. First assignment deals with small bug fixes. Second assignment handles testing and refactoring. Third assignment handles features or bug fixes that deal with both the GUI and the business logic. + +### German as course language + +#### Universität Basel, Switzerland + +Course [10915-01: Software Engineering](https://dmi.unibas.ch/de/studium/computer-science-informatik/lehrangebot-hs18/vorlesung-software-engineering/), 2019 to 2023 + +* Lecture Materials: [https://github.com/unibas-marcelluethi/software-engineering](https://github.com/unibas-marcelluethi/software-engineering) +* Excercise touching JabRef: + * General idea: identify a feature missing in JabRef and develop the specification, system design, and implementation of the feature. + * Introduction to JabRef's code: [Exercise 5](https://github.com/unibas-marcelluethi/software-engineering/blob/master/docs/week5/exercises/practical-exercises.md): Introduction into JabRef code. + * Prominent feature implemented: Parse full-text references using Grobid. PR [#5614](https://github.com/JabRef/jabref/pull/5614). + +#### University of Stuttgart, Germany + +Course "Softwarepraktikum" as part of the [BSc Informatik](https://www.uni-stuttgart.de/studium/bachelor/informatik-b.sc./), 2012 + +> A group of three students experienced the full software engineering process within one semester. They worked part-time for the project. + +Course [Studienprojekt](https://www.f05.uni-stuttgart.de/informatik/studierende/bachelor/stupro/) as part of the [BSc Software Engineering](https://www.uni-stuttgart.de/en/study/study-programs/Software-Engineering-B.Sc-00001./), 2015/2016 + +> A group of nine students experienced the full software engineering process within one year. They worked part-time for the project. + +Course "Programming and Software Development" as part of the [BSc Software Engineering](https://www.uni-stuttgart.de/en/study/study-programs/Software-Engineering-B.Sc-00001./), 2018 + +> One exercise to contribute a minor fix or feature to JabRef. Goal: learn contribution to an open-source project using git and GitHub. + +### Swedish + +#### KTH Royal Institute of Technology, Sweden + +Course [DD2480 Software Engineering Fundamentals](https://www.kth.se/student/kurser/kurs/DD2480?l=en), 2020, 2024 + +> Groups of students from three to five persons experienced the whole software engineering process within a week: From the requirements' specification to the final pull request. + +### Portuguese + +#### Federal University of Technology, Paraná, Brazil + +Course [Open Source Software](https://github.com/igorsteinmacher/DSL-UTFPR), 2013 to 2016 + +> Students are requested to contribute to an Open Source project to learn about the maintenance and evolution of software projects. This project is the predecessor of NAU's CS499. + +## Praises and media coverage + +* [O. Kopp et al.: JabRef: BibTeX-based literature management software, TUGboat 44(3)](https://doi.org/10.47397/tb/44-3/tb138kopp-jabref) - explains the motivation and the concept of the curated issues. +* "I have learnt more from this single pull request regarding production-ready code than I ever have from my three years of CS degree." +* JabRef mentioned as one of "Top 8 Open Source GitHub Projects to Level-Up Your Coding" by [CodeGym](https://codegym.cc/groups/posts/383-top-8-open-source-github-projects-to-level-up-your-coding). + +(Please send us your praise if you enjoyed the experience) + +## Notes + +1. JabRef tries to achieve high code quality. This ultimately leads to improved software engineering knowledge of contributors. After contributing for JabRef, both coding and general software engineering skills will have increased. Our [development strategy](getting-into-the-code/development-strategy.md) provides more details. +2. We recommend to start early and constantly, since students working earlier and more often produce projects that are more correct and completed earlier at the same overall invested time [1](teaching.md#Ayaankazerouni). +3. Be aware that JabRef is run by volunteers. This implies that the development team cannot ensure to provide feedback on code within hours. +4. Be aware that from the first pull request to the final acceptance the typical time needed is two weeks. +5. Be aware that JabRef tries to achieve high code quality. This leads to code reviews requiring actions from the contributors. This also applies for code of students. Read on at our [Development Strategy](getting-into-the-code/development-strategy.md) for more details. + +## References + +[1](teaching.md#a1): [@ayaankazerouni](https://github.com/ayaankazerouni): [Developing Procrastination Feedback for Student Software Developers](https://medium.com/@ayaankazerouni/developing-procrastination-feedback-for-student-software-developers-1652de60db7f) [2](teaching.md#a2): Lientz B., Swanson E., 1980: Software Maintenance Management. Addison Wesley, Reading, MA. + + diff --git a/jabref/eclipse.gradle b/jabref/eclipse.gradle new file mode 100644 index 00000000..9d61901d --- /dev/null +++ b/jabref/eclipse.gradle @@ -0,0 +1,626 @@ +apply plugin: "eclipse" + +// ensure that source code is generated, otherwise class `BstLexer` cannot be found +tasks.eclipseClasspath.dependsOn "generateSource" + +// workaround until https://github.com/gradle/gradle/issues/898 is resolved +eclipseJdt.doLast { + File f = file('.settings/org.eclipse.core.resources.prefs') + f.write('eclipse.preferences.version=1\n') + f.append('encoding/=UTF-8') +} +eclipse { + classpath { + file { + whenMerged { + entries.findAll { isModule(it) }.each { //this was already necessary to build modular projects + it.entryAttributes['module'] = 'true' + } + def controlsfx = entries.find { isControlsfx(it) }; + controlsfx.entryAttributes['add-exports'] = 'org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref:org.controlsfx.controls/org.controlsfx.control.textfield=org.jabref:org.controlsfx.controls/impl.org.controlsfx.autocompletion=org.jabref'; + controlsfx.entryAttributes['add-opens'] = 'org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref:org.controlsfx.controls/org.controlsfx.control.textfield=org.jabref:org.controlsfx.controls/impl.org.controlsfx.autocompletion=org.jabref'; + + entries.findAll { isSource(it) && isTestScope(it) }.each { //mark test source folders + it.entryAttributes['test'] = 'true' + } + + def javafxcontrols = entries.find { isJavafxControls(it) }; + javafxcontrols.entryAttributes['add-exports'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref'; + javafxcontrols.entryAttributes['add-opens'] = 'javafx.controls/com.sun.javafx.scene.control=org.jabref:javafx.controls/com.sun.javafx.scene.control.behavior=org.jabref:javafx.controls/javafx.scene.control=org.jabref:javafx.controls/javafx.scene.control.skin=org.controlsfx.controls'; + + def javafxgraphics = entries.find { isJavafxGraphics(it) }; + javafxgraphics.entryAttributes['add-opens'] = 'javafx.graphics/javafx.scene=org.controlsfx.controls'; + + def javafxbase = entries.find { isJavafxBase(it) }; + javafxbase.entryAttributes['add-exports'] = 'javafx.base/com.sun.javafx.event=org.controlsfx.controls:'; + + def javafxfxml = entries.find { isJavafxFXML(it) }; + javafxfxml.entryAttributes['add-opens'] = 'javafx.fxml/javafx.fxml=org.jabref'; + + entries.findAll { isLibrary(it) && isTestScope(it) }.each { //mark test source files + it.entryAttributes['test'] = 'true' + } + } + } + + defaultOutputDir = file('bin/main') + downloadSources = true + downloadJavadoc = true + } +} + +boolean isLibrary(entry) { return entry.properties.kind.equals('lib') } + +boolean isTestScope(entry) { return !entry.entryAttributes.get('gradle_used_by_scope').contains('main') } + +boolean isModule(entry) { + isLibrary(entry) && !isTestScope(entry); +} //a test-scope library should be put on the classpath instead of the modulepath +boolean isSource(entry) { return entry.properties.kind.equals('src'); } + +boolean isControlsfx(entry) { return entry.properties.path.contains('controlsfx'); } + +boolean isJavafxControls(entry) { return entry.properties.path.contains('javafx-controls'); } + +boolean isJavafxGraphics(entry) { return entry.properties.path.contains('javafx-graphics'); } + +boolean isJavafxBase(entry) { return entry.properties.path.contains('javafx-base'); } + +boolean isJavafxFXML(entry) { return entry.properties.path.contains('javafx-fxml'); } + +// add formatter and cleanup settings to Eclipse settings +// see http://stackoverflow.com/a/27461890/873282 + +tasks.cleanEclipse.doLast { + delete("${project.projectDir}/.settings/org.eclipse.core.resources.prefs") + delete("${project.projectDir}/.settings/org.eclipse.jdt.core.prefs") + delete("${project.projectDir}/.settings/org.eclipse.jdt.ui.prefs") +} + +// Currently, the eclipse task is not intelligent enough to replace generated lines: +// It just adds the lines. +// To avoid broken configurations, the existing configuration is deleted completely. +tasks.eclipse.dependsOn(cleanEclipse) + +tasks.eclipse.doFirst { + File jdt_core_prefs = file("${project.projectDir}/.settings/org.eclipse.jdt.core.prefs") + // existence check doesn't work as gradle generates the file on its own + /* following entries are not written as they are generated by gradle: + org.eclipse.jdt.core.compiler.debug.lineNumber=generate + org.eclipse.jdt.core.compiler.debug.localVariable=generate + org.eclipse.jdt.core.compiler.debug.sourceFile=generate + org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled + org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve + org.eclipse.jdt.core.compiler.problem.assertIdentifier=error + org.eclipse.jdt.core.compiler.problem.enumIdentifier=error + */ + jdt_core_prefs.append(''' + org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled + org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore + org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull + org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault + org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable + org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled + org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning + org.eclipse.jdt.core.compiler.problem.autoboxing=ignore + org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning + org.eclipse.jdt.core.compiler.problem.deadCode=warning + org.eclipse.jdt.core.compiler.problem.deprecation=warning + org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled + org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled + org.eclipse.jdt.core.compiler.problem.discouragedReference=warning + org.eclipse.jdt.core.compiler.problem.emptyStatement=warning + org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning + org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore + org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled + org.eclipse.jdt.core.compiler.problem.fieldHiding=warning + org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning + org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning + org.eclipse.jdt.core.compiler.problem.forbiddenReference=error + org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning + org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled + org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning + org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning + org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore + org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning + org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning + org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore + org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning + org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled + org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning + org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning + org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled + org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore + org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore + org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning + org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning + org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore + org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning + org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error + org.eclipse.jdt.core.compiler.problem.nullReference=warning + org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error + org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning + org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning + org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore + org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning + org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore + org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning + org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning + org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning + org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore + org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning + org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore + org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore + org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore + org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled + org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=ignore + org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled + org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled + org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled + org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore + org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning + org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled + org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning + org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning + org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning + org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning + org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore + org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning + org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore + org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning + org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled + org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled + org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled + org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore + org.eclipse.jdt.core.compiler.problem.unusedImport=warning + org.eclipse.jdt.core.compiler.problem.unusedLabel=warning + org.eclipse.jdt.core.compiler.problem.unusedLocal=warning + org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning + org.eclipse.jdt.core.compiler.problem.unusedParameter=warning + org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=disabled + org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled + org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled + org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning + org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning + org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning + org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning + org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines + org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert + org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert + org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert + org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines + org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false + org.eclipse.jdt.core.formatter.indentation.size=4 + org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert + org.eclipse.jdt.core.formatter.disabling_tag=@formatter\\:off + org.eclipse.jdt.core.formatter.continuation_indentation=1 + org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48 + org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 + org.eclipse.jdt.core.formatter.blank_lines_after_package=1 + org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=0 + org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.comment.indent_root_tags=true + org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=false + org.eclipse.jdt.core.formatter.enabling_tag=@formatter\\:on + org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert + org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false + org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert + org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert + org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert + org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert + org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 + org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert + org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert + org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false + org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert + org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true + org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert + org.eclipse.jdt.core.formatter.comment.line_length=9999 + org.eclipse.jdt.core.formatter.use_on_off_tags=true + org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert + org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert + org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert + org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=18 + org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert + org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 + org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert + org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false + org.eclipse.jdt.core.formatter.alignment_for_binary_expression=18 + org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=18 + org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert + org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line + org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line + org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line + org.eclipse.jdt.core.formatter.compact_else_if=true + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert + org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true + org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=18 + org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=18 + org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=18 + org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false + org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert + org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert + org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert + org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert + org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert + org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=18 + org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true + org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 + org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines + org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert + org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert + org.eclipse.jdt.core.formatter.comment.format_line_comments=false + org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert + org.eclipse.jdt.core.formatter.align_type_members_on_columns=false + org.eclipse.jdt.core.formatter.alignment_for_assignment=2 + org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert + org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert + org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 + org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1 + org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=2 + org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert + org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert + org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert + org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line + org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.comment.format_header=false + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=18 + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert + org.eclipse.jdt.core.formatter.alignment_for_method_declaration=16 + org.eclipse.jdt.core.formatter.join_wrapped_lines=false + org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true + org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true + org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert + org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 + org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true + org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert + org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=26 + org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false + org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines + org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=18 + org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert + org.eclipse.jdt.core.formatter.tabulation.size=4 + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert + org.eclipse.jdt.core.formatter.comment.format_source_code=true + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert + org.eclipse.jdt.core.formatter.blank_lines_before_field=0 + org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert + org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 + org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert + org.eclipse.jdt.core.formatter.blank_lines_before_method=1 + org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 + org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=0 + org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert + org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert + org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert + org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert + org.eclipse.jdt.core.formatter.comment.format_html=true + org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines + org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 + org.eclipse.jdt.core.formatter.indent_empty_lines=false + org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert + org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false + org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true + org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 + org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert + org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true + org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert + org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 + org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert + org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=18 + org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert + org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert + org.eclipse.jdt.core.formatter.comment.format_block_comments=false + org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert + org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert + org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=18 + org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert + org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true + org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert + org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert + org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line + org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert + org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines + org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert + org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true + org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert + org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line + org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line + org.eclipse.jdt.core.formatter.blank_lines_before_package=0 + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert + org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert + org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert + org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true + org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert + org.eclipse.jdt.core.formatter.join_lines_in_comments=false + org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert + org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true + org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert + org.eclipse.jdt.core.formatter.tabulation.char=space + org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert + org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 + org.eclipse.jdt.core.formatter.lineSplit=9999 + org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert + org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert + org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter + '''.stripIndent()) + + File jdt_ui_prefs = file("${project.projectDir}/.settings/org.eclipse.jdt.ui.prefs") + if (jdt_ui_prefs.exists()) { + logger.warn("UI preferences already exist and will not be overridden. Use task 'cleanEclipse' first.") + } else { + jdt_ui_prefs.append(''' + cleanup.add_default_serial_version_id=true + cleanup.add_generated_serial_version_id=false + cleanup.add_missing_annotations=true + cleanup.add_missing_deprecated_annotations=true + cleanup.add_missing_methods=false + cleanup.add_missing_nls_tags=false + cleanup.add_missing_override_annotations=true + cleanup.add_missing_override_annotations_interface_methods=true + cleanup.add_serial_version_id=false + cleanup.always_use_blocks=true + cleanup.always_use_parentheses_in_expressions=true + cleanup.always_use_this_for_non_static_field_access=false + cleanup.always_use_this_for_non_static_method_access=false + cleanup.convert_functional_interfaces=false + cleanup.convert_to_enhanced_for_loop=false + cleanup.correct_indentation=true + cleanup.format_source_code=true + cleanup.format_source_code_changes_only=false + cleanup.insert_inferred_type_arguments=false + cleanup.make_local_variable_final=false + cleanup.make_parameters_final=false + cleanup.make_private_fields_final=true + cleanup.make_type_abstract_if_missing_method=false + cleanup.make_variable_declarations_final=true + cleanup.never_use_blocks=false + cleanup.never_use_parentheses_in_expressions=false + cleanup.organize_imports=true + cleanup.qualify_static_field_accesses_with_declaring_class=true + cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=false + cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=false + cleanup.qualify_static_member_accesses_with_declaring_class=false + cleanup.qualify_static_method_accesses_with_declaring_class=false + cleanup.remove_private_constructors=true + cleanup.remove_redundant_type_arguments=true + cleanup.remove_trailing_whitespaces=true + cleanup.remove_trailing_whitespaces_all=true + cleanup.remove_trailing_whitespaces_ignore_empty=false + cleanup.remove_unnecessary_casts=true + cleanup.remove_unnecessary_nls_tags=true + cleanup.remove_unused_imports=true + cleanup.remove_unused_local_variables=false + cleanup.remove_unused_private_fields=true + cleanup.remove_unused_private_members=false + cleanup.remove_unused_private_methods=true + cleanup.remove_unused_private_types=true + cleanup.sort_members=false + cleanup.sort_members_all=true + cleanup.use_anonymous_class_creation=false + cleanup.use_blocks=true + cleanup.use_blocks_only_for_return_and_throw=false + cleanup.use_lambda=true + cleanup.use_parentheses_in_expressions=true + cleanup.use_this_for_non_static_field_access=false + cleanup.use_this_for_non_static_field_access_only_if_necessary=false + cleanup.use_this_for_non_static_method_access=false + cleanup.use_this_for_non_static_method_access_only_if_necessary=false + cleanup.use_type_arguments=false + cleanup_profile=_JabRef + cleanup_settings_version=2 + eclipse.preferences.version=1 + editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true + formatter_profile=_JabRef + formatter_settings_version=12 + org.eclipse.jdt.ui.ignorelowercasenames=true + org.eclipse.jdt.ui.importorder=java;javax;javafx;net.sf.jabref;org.jabref;;\\#; + org.eclipse.jdt.ui.ondemandthreshold=99 + org.eclipse.jdt.ui.staticondemandthreshold=99 + sp_cleanup.add_default_serial_version_id=true + sp_cleanup.add_generated_serial_version_id=false + sp_cleanup.add_missing_annotations=true + sp_cleanup.add_missing_deprecated_annotations=true + sp_cleanup.add_missing_methods=false + sp_cleanup.add_missing_nls_tags=false + sp_cleanup.add_missing_override_annotations=true + sp_cleanup.add_missing_override_annotations_interface_methods=true + sp_cleanup.add_serial_version_id=false + sp_cleanup.always_use_blocks=true + sp_cleanup.always_use_parentheses_in_expressions=true + sp_cleanup.always_use_this_for_non_static_field_access=false + sp_cleanup.always_use_this_for_non_static_method_access=false + sp_cleanup.convert_functional_interfaces=false + sp_cleanup.convert_to_enhanced_for_loop=false + sp_cleanup.correct_indentation=false + sp_cleanup.format_source_code=true + sp_cleanup.format_source_code_changes_only=true + sp_cleanup.insert_inferred_type_arguments=false + sp_cleanup.make_local_variable_final=false + sp_cleanup.make_parameters_final=false + sp_cleanup.make_private_fields_final=true + sp_cleanup.make_type_abstract_if_missing_method=false + sp_cleanup.make_variable_declarations_final=true + sp_cleanup.never_use_blocks=false + sp_cleanup.never_use_parentheses_in_expressions=false + sp_cleanup.on_save_use_additional_actions=true + sp_cleanup.organize_imports=false + sp_cleanup.qualify_static_field_accesses_with_declaring_class=true + sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=false + sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=false + sp_cleanup.qualify_static_member_accesses_with_declaring_class=false + sp_cleanup.qualify_static_method_accesses_with_declaring_class=false + sp_cleanup.remove_private_constructors=true + sp_cleanup.remove_redundant_type_arguments=true + sp_cleanup.remove_trailing_whitespaces=true + sp_cleanup.remove_trailing_whitespaces_all=true + sp_cleanup.remove_trailing_whitespaces_ignore_empty=false + sp_cleanup.remove_unnecessary_casts=true + sp_cleanup.remove_unnecessary_nls_tags=true + sp_cleanup.remove_unused_imports=true + sp_cleanup.remove_unused_local_variables=false + sp_cleanup.remove_unused_private_fields=true + sp_cleanup.remove_unused_private_members=false + sp_cleanup.remove_unused_private_methods=true + sp_cleanup.remove_unused_private_types=true + sp_cleanup.sort_members=false + sp_cleanup.sort_members_all=true + sp_cleanup.use_anonymous_class_creation=false + sp_cleanup.use_blocks=true + sp_cleanup.use_blocks_only_for_return_and_throw=false + sp_cleanup.use_lambda=true + sp_cleanup.use_parentheses_in_expressions=true + sp_cleanup.use_this_for_non_static_field_access=false + sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=false + sp_cleanup.use_this_for_non_static_method_access=false + sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=false + sp_cleanup.use_type_arguments=false + '''.stripIndent()) + } +} diff --git a/jabref/external-libraries.md b/jabref/external-libraries.md new file mode 100644 index 00000000..257aea18 --- /dev/null +++ b/jabref/external-libraries.md @@ -0,0 +1,906 @@ +# External libraries + +This document lists the fonts, icons, and libraries used by JabRef. +This file is manually kept in sync with build.gradle and the binary jars contained in the lib/ directory. + +One can list all dependencies by using Gradle task `dependencyReport`. +It generates the file `build/reports/project/dependencies.txt`. +Below, there is a howto to generate the content at "Sorted list of runtime dependencies output by gradle". + +## Legend + +### License + +We follow the [SPDX license identifiers](https://spdx.org/licenses/). +In case you add a library, please use these identifiers. +For instance, "BSD" is not exact enough, there are numerous variants out there: BSD-2-Clause, BSD-3-Clause-No-Nuclear-Warranty, ... +Note that the SPDX license identifiers are different from the ones used by debian. See for more information. + +## bst files + +```yaml +Project: IEEEtran +Path: src/main/resources/bst/IEEEtran.bst +URL: https://www.ctan.org/tex-archive/macros/latex/contrib/IEEEtran/bibtex +License: LPPL-1.3 +``` + +## Fonts and Icons + +The loading animation during loading of recommendations from Mr. DLib is created by and is free of use under license CC0 1.0. + +```yaml +Id: material-design-icons.font +Project: Material Design Icons +Version: v1.5.54 +URL: https://materialdesignicons.com/ +License: SIL Open Font License, Version 1.1 +Note: It is important to include v1.5.54 or later as v1.5.54 is the first version offering fixed code points. Do not confuse with http://zavoloklom.github.io/material-design-iconic-font/ +``` + +## Libraries + +(Sorted alphabetically by Id) + +```yaml +Id: ai.djl.*:* +Project: Deep Java Library +URL: https://djl.ai/ +License: Apache-2.0 +``` + +```yaml +Id: at.favre.lib +Project: HMAC-based Key Derivation Function (HKDF) RFC 5869 +URL: https://github.com/patrickfav/hkdf +License: Apache-2.0 +``` + +```yaml +Id: com.dlsc.gemsfx:gemsfx +Project: GemsFX +URL: https://github.com/dlsc-software-consulting-gmbh/GemsFX +License: Apache-2.0 +``` + +```yaml +Id: com.dlsc.pickerfx:pickerfx +Project: GemsFX +URL: https://github.com/dlsc-software-consulting-gmbh/PickerFX +License: Apache-2.0 +``` + +```yaml +Id: com.dlsc.unitfx:unitfx +Project: UnitFX +URL: https://github.com/dlsc-software-consulting-gmbh/UnitFX +License: Apache-2.0 +``` + +```yaml +Id: com.fasterxml:aalto-xml +Project: Jackson Project +URL: https://github.com/FasterXML/aalto-xml +License: Apache-2.0 +``` + +```yaml +Id: com.fasterxml.jackson +Project: Jackson Project +URL: https://github.com/FasterXML/jackson +License: Apache-2.0 +``` + +```yaml +Id: com.knuddels:jtokkit +Project: JTokkit - Java Tokenizer Kit +URL: https://github.com/knuddelsgmbh/jtokkit +License: MIT +``` + +```yaml +Id: com.github.hypfvieh.dbus-java +Project: dbus-java +URL: https://github.com/hypfvieh/dbus-java +License: MIT +``` + +```yaml +Id: com.github.hypfvieh.java-utils +Project: java-utils +URL: https://github.com/hypfvieh/java-utils +License: MIT +``` + +```yaml +Id: com.github.javakeyring +Project: Java Keyring +URL: https://github.com/javakeyring/java-keyring +License: BSD-3-Clause +``` + +```yaml +Id: com.github.JabRef +Project: afterburner.fx +URL: https://github.com/JabRef/afterburner.fx +License: Apache-2.0 +``` + +```yaml +Id: com.github.tomtung +Project: latex2unicode +URL: https://github.com/tomtung/latex2unicode +License: Apache-2.0 +``` + +```yaml +Id: com.github.vatbub:mslinks +Project: mslinks +URL: https://github.com/vatbub/mslinks +License: Apache-2.0 +``` + +```yaml +Id: com.github.weisj:jsvg +Project: JSVG - A Java SVG implementation +URL: https://github.com/weisJ/jsvg +License: MIT +``` + +```yaml +Id: com.google.code.gson:gson +Project: Google Guava +URL: https://github.com/google/gson +License: Apache-2.0 +``` + +```yaml +Id: com.google.guava:failureaccess +Project: Google Guava +URL: https://github.com/google/guava +License: Apache-2.0 +Note: See https://github.com/google/guava/issues/3437 for a discussion that this dependency is really required. +``` + +```yaml +Id: com.google.guava:guava +Project: Google Guava +URL: https://github.com/google/guava +License: Apache-2.0 +``` + +```yaml +Id: com.google.j2objc:j2objc-annotations +Project: j2objc-annotations +URL: https://github.com/google/j2objc +License: Apache-2.0 +``` + +```yaml +Id: com.googlecode.plist +Project: com.dd.plist +URL: https://github.com/3breadt/dd-plist +License: MIT +``` + +```yaml +Id: com.googlecode.javaewah:JavaEWAH +Project: JavaEWAH +URL: https://github.com/lemire/javaewah +License: Apache-2.0 +``` + +```yaml +Id: com.jthemedetecor.OsThemeDetector +Project: jSystemThemeDetector +URL: https://github.com/Dansoftowner/jSystemThemeDetector +License: Apache-2.0 +``` + +```yaml +Id: com.kohlschutter.junixsocket +Project: junixsocket +URL: https://github.com/kohlschutter/junixsocket +License: Apache-2.0 +``` + +```yaml +Id: com.konghq.unirest +Project: Unirest for Java +URL: https://github.com/Kong/unirest-java +License: MIT +``` + +```yaml +Id: com.oracle.ojdbc:ojdbc10 +Project: Oracle's JDBC drivers +URL: https://repo1.maven.org/maven2/com/oracle/ojdbc/ojdbc10/19.3.0.0/ojdbc10-19.3.0.0.pom +License: Oracle Free Use Terms and Conditions (FUTC) +``` + +```yaml +Id: com.sun.istack:istack-commons-runtime +Project: iStack Common Utility Code +URL: https://github.com/eclipse-ee4j/jaxb-istack-commons +License: BSD-3-Clause (with copyright as described in Eclipse Distribution License - v 1.0 - see https://wiki.spdx.org/view/Legal_Team/License_List/Licenses_Under_Consideration for details) +``` + +```yaml +Id: com.vladsch.flexmark:flexmark-all +Project: flexmark-java +URL: https://github.com/vsch/flexmark-java +License: BSD-2-Clause +``` + +```yaml +Id: com.vladsch.flexmark:flexmark-html2md-converter +Project: flexmark-java +URL: https://github.com/vsch/flexmark-java +License: BSD-2-Clause +``` + +```yaml +Id: commons-beanutils:commons-beanutils +Project: Apache Commons Beanutils +URL: https://commons.apache.org/proper/commons-beanutils/ +License: Apache-2.0 +``` + +```yaml +Id: commons-cli:commons-cli +Project: Apache Commons CLI +URL: http://commons.apache.org/cli/ +License: Apache-2.0 +``` + +```yaml +Id: commons-codec:commons-codec +Project: Apache Commons Codec +URL: https://commons.apache.org/proper/commons-codec/ +License: Apache-2.0 +``` + +```yaml +Id: commons-collections:commons-collections +Project: Apache Commons Collections +URL: https://commons.apache.org/proper/commons-collections/ +License: Apache-2.0 +``` + +```yaml +Id: commons-io:commons-io +Project: Apache Commons IO +URL: https://commons.apache.org/proper/commons-io/ +License: Apache-2.0 +``` + +```yaml +Id: commons-logging:commons-logging +Project: Apache Commons Logging +URL: http://commons.apache.org/logging/ +License: Apache-2.0 +``` + +```yaml +Id: commons-digester:commons-digester +Project: Apache Commons Digester +URL: https://commons.apache.org/proper/commons-digester/ +``` + +```yaml +Id: commons-io:commons-io +Project: Apache Commons IO +URL: https://commons.apache.org/proper/commons-io/ +``` + +```yaml +Id: de.rototor.jeuclid:jeuclid-core +Project: JEuclid +URL: https://github.com/rototor/jeuclid +License: Apache-2.0 +``` + +```yaml +Id: de.rototor.snuggletex:snuggletex-core +Project: SnuggleTeX +URL: https://github.com/rototor/snuggletex +License: BSD +``` + +```yaml +Id: de.saxsys:mvvmfx +Project: mvvm(fx) +URL: https://github.com/sialcasa/mvvmFX +License: Apache-2.0 +``` + +```yaml +Id: de.swiesend:secret-service +Project: Secret Service +URL: https://github.com/swiesend/secret-service +License: MIT +``` + +```yaml +Id: de.saxsys:mvvmfx-validation +Project: mvvm(fx) +URL: https://github.com/sialcasa/mvvmFX +License: Apache-2.0 +``` + +```yaml +Id: de.undercouch.citeproc-java +Project: Citeproc-Java +URL: http://michel-kraemer.github.io/citeproc-java/ +Licence: Apache-2.0 +``` + +```yaml +Id: eu.lestard:doc-annotations +Project: doc annotations +URL: https://github.com/lestard/doc-annotations +License: MIT +``` + +```yaml +Id: info.debatty:java-string-similarity +Project: Java String Similarity +URL: https://github.com/tdebatty/java-string-similarity +License: MIT +``` + +```yaml +Id: io.github.adr:e-adr +Project: EmbeddedArchitecturalDecisionRecords +URL: https://github.com/adr/e-adr/ +License: EPL-2.0 +``` + +```yaml +Id: io.github.java-diff-utils:java-diff-utils +Project: java-diff-utils +URL: https://github.com/java-diff-utils/java-diff-utils +License: Apache-2.0 +``` + +```yaml +Id: io.zonky.test:embedded-postgres +Project: embedded-postgres +URL: https://github.com/zonkyio/embedded-postgres +License: Apache-2.0 +``` + +```yaml +Id: jakarta.annotation:jakarata.annotation-api +Project: Jakarta Annotations +URL: https://projects.eclipse.org/projects/ee4j.ca +License: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: jakarta.activation:jakarata.activation-api +Project: Jakarta Activation +URL: https://projects.eclipse.org/projects/ee4j.ca +License: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: jakarta.inject:jakarata.inject-api +Project: Jakarta Inject +URL: https://projects.eclipse.org/projects/ee4j.ca +License: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: jakarta.xml.bind:jakarta.xml.bind-api +Project: Jakarta XML Binding project +URL: https://github.com/eclipse-ee4j/jaxb-api +License: BSD-3-Clause; sometimes EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: net.harawata:appdirs +Project: AppDirs +URL: https://github.com/harawata/appdirs +License: Apache-2.0 +``` + +```yaml +Id: net.java.dev.jna +Project: Java Native Access (JNA) +URL: https://github.com/java-native-access/jna +License: Apache-2.0 +``` + +```yaml +Id: net.java.dev.jna-platform +Project: Java Native Access (JNA) +URL: https://github.com/java-native-access/jna +License: Apache-2.0 +``` + +```yaml +Id: net.jcip:jcip-annotations +Project: JCIP (Java Concurrency in Practice) Annotations under Apache License +URL: http://stephenc.github.io/jcip-annotations/ +License: Apache-2.0 +``` + +```yaml +Id: net.jodah:typetools +Project: TypeTools +URL: https://github.com/jhalterman/typetools +License: Apache-2.0 +``` + +```yaml +Id: net.synedra:validatorfx +Project: ValidatorFX +URL: https://github.com/effad/ValidatorFX +License: BSD 3-Clause +``` + +```yaml +Id: org.antlr:antlr4-runtime +Project: ANTLR 4 +URL: http://www.antlr.org/ +License: BSD-3-Clause +``` + +```yaml +Id: org.apache.commons:* +Project: Apache Commons * +URL: https://commons.apache.org/ +License: Apache-2.0 +``` + +```yaml +Id: org.apache.lucene:* +Project: Apache Lucene +URL: https://lucene.apache.org/ +License: Apache-2.0 +``` + +```yaml +Id: org.apache.pdfbox:fontbox +Project: Apache PDFBox +URL: http://pdfbox.apache.org +License: Apache-2.0 +``` + +```yaml +Id: org.apache.pdfbox:jempbox +Project: Apache PDFBox +URL: http://pdfbox.apache.org +License: Apache-2.0 +``` + +```yaml +Id: org.apache.pdfbox:pdfbox +Project: Apache PDFBox +URL: http://pdfbox.apache.org +License: Apache-2.0 +``` + +```yaml +Id: org.apiguardian:apiguardian-api +Project: @API Guardian +URL: https://github.com/apiguardian-team/apiguardian +License: Apache-2.0 +``` + +```yaml +Id: org.bouncycastle:bcprov-jdk15on +Project: The Legion of the Bouncy Castle +URL: https://www.bouncycastle.org/ +License: MIT +``` + +```yaml +Id: org.citationstyles.styles +Project: CSL Styles +URL: https://github.com/citation-style-language/styles +Licence: Creative Commons Attribution-ShareAlike 3.0 Unported license +``` + +```yaml +Id: org.citationstyles.locales +Project: CSL Locales +URL: https://github.com/citation-style-language/locales +Licence: CC-BY-SA-3.0 +``` + +```yaml +Id: org.controlsfx:controlsfx +Project: ControlsFX +URL: http://fxexperience.com/controlsfx/ +License: BSD-3-Clause +``` + +```yaml +Id: org.eclipse.jgit:org.eclipse.jgit +Project: Eclipse JGit +URL: https://www.eclipse.org/jgit/ +License: BSD-3-Clause +``` + +```yaml +Id: org.fxmisc.flowless:flowless +Project: Flowless +URL: https://github.com/TomasMikula/Flowless +License: BSD-2-Clause +``` + +```yaml +Id: org.fxmisc.richtext:richtextfx +Project: RichTextFX +URL: https://github.com/TomasMikula/RichTextFX +License: BSD-2-Clause +``` + +```yaml +Id: org.glassfish.* +Project: Eclipse GlassFish +URL: https://glassfish.org/ +License: BSD-3-Clause (with copyright as described in Eclipse Distribution License - v 1.0 - see https://wiki.spdx.org/view/Legal_Team/License_List/Licenses_Under_Consideration for details) +``` + +```yaml +Id: com.ibm.icu:* +Project: International Components for Unicode +URL: https://icu.unicode.org/ +License: Unicode License (https://www.unicode.org/copyright.html) +Note: Our own fork https://github.com/JabRef/icu. [Upstream PR](https://github.com/unicode-org/icu/pull/2127) +Path: lib/icu4j.jar +SourcePath: lib/ic4j-src.jar +``` + +```yaml +Id: org.jabref:easybind +Project: EasyBind +URL: https://github.com/JabRef/EasyBind +License: BSD-2-Clause +``` + +```yaml +Id: org.jooq:jool +Project: JOOλ +URL: https://github.com/jOOQ/jOOL +License: Apache-2.0 +``` + +```yaml +Id: org.jsoup:jsoup +Project: jsoup +URL: https://github.com/jhy/jsoup/ +License: MIT +``` + +```yaml +Id: org.jspecify:jspecify +Project: jspecify +URL: https://jspecify.dev/ +License: Apache-2.0 +``` + +```yaml +Id: org.kordamp.ikonli +Project: Ikonli +URL: https://kordamp.org/ikonli/ +License: Apache-2.0 +``` + +```yaml +Id: org.mariadb.jdbc:mariadb-java-client +Project: MariaDB Java Client +URL: https://mariadb.com/kb/en/library/about-mariadb-connector-j/ +License: LGPL-2.1-or-later +``` + +```yaml +Id: org.openjfx:javafx-base +Project: JavaFX +URL: https://openjfx.io/ +License: GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: org.openjfx:javafx-controls +Project: JavaFX +URL: https://openjfx.io/ +License: GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: org.openjfx:javafx-fxml +Project: JavaFX +URL: https://openjfx.io/ +License: GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: org.openjfx:javafx-graphics +Project: JavaFX +URL: https://openjfx.io/ +License: GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: org.openjfx:javafx-media +Project: JavaFX +URL: https://openjfx.io/ +License: GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: org.openjfx:javafx-swing +Project: JavaFX +URL: https://openjfx.io/ +License: GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: org.openjfx:javafx-web +Project: JavaFX +URL: https://openjfx.io/ +License: GPL-2.0 WITH Classpath-exception-2.0 +``` + +```yaml +Id: org.libreoffice:libreoffice +Project: LibreOffice +URL: https://api.libreoffice.org/ +License: MPL-2.0 OR LGPL 3.0+ +``` + +```yaml +Id: org.libreoffice:unloader +Project: LibreOffice UNO Loader +URL: https://api.libreoffice.org/ +License: MPL-2.0 AND Apache-2.0 +``` + +```yaml +Id: org.tinylog:slf4j-tinylog +Project: tinylog 2 +URL: https://github.com/tinylog-org/tinylog +License: Apache-2.0 +``` + +```yaml +Id: org.tinylog:tinylog-api +Project: tinylog 2 +URL: https://github.com/tinylog-org/tinylog +License: Apache-2.0 +``` + +```yaml +Id: org.tinylog:tinylog-impl +Project: tinylog 2 +URL: https://github.com/tinylog-org/tinylog +License: Apache-2.0 +``` + +```yaml +Id: org.yaml:snakeyaml +Project: SnakeYAML +URL: https://bitbucket.org/snakeyaml/snakeyaml-engine/src/master/ +License: Apache-2.0 +``` + +```yaml +Id: pt.davidafsilva.apple:jkeychain +Project: JKeyChain +URL: https://github.com/davidafsilva/jkeychain +License: BSD-2-Clause +``` + +```yaml +Id: tech.units:indriya +Project: Indriya - JSR 385 - Reference Implementation +URL: https://github.com/unitsofmeasurement/indriya +License: BSD-3-Clause +``` + +```yaml +Id: tech.uom.lib:uom-lib-common +Project: Units of Measurement Libraries - extending and complementing JSR 385 +URL: https://github.com/unitsofmeasurement/uom-lib +License: BSD-3-Clause +``` + +## Sorted list of runtime dependencies output by gradle + +1. `./gradlew dependencyReport --configuration compileClasspath` +2. Fix `build/reports/project/dependencies.txt` + + - Change line endings to `LF` + - Remove text above and below the tree + +3. (on WSL) `sed 's/[^a-z]*//' < build/reports/project/dependencies.txt | sed "s/\(.*\) .*/\1/" | grep -v "\->" | sort | uniq > build/dependencies-for-external-libraries.txt` + +```text +ai.djl.huggingface:tokenizers:0.30.0 +ai.djl.pytorch:pytorch-engine:0.30.0 +ai.djl.pytorch:pytorch-model-zoo:0.30.0 +ai.djl:api:0.30.0 +ai.djl:bom:0.30.0 +at.favre.lib:hkdf:1.1.0 +com.dlsc.gemsfx:gemsfx:2.48.0 +com.dlsc.pickerfx:pickerfx:1.3.1 +com.dlsc.unitfx:unitfx:1.0.10 +com.fasterxml.jackson.core:jackson-annotations:2.17.2 +com.fasterxml.jackson.core:jackson-core:2.17.2 +com.fasterxml.jackson.core:jackson-databind:2.17.2 +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2 +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2 +com.fasterxml.jackson:jackson-bom:2.17.2 +com.fasterxml:aalto-xml:1.3.3 +com.github.hypfvieh:dbus-java-core:4.2.1 +com.github.hypfvieh:dbus-java-transport-native-unixsocket:4.2.1 +com.github.javakeyring:java-keyring:1.0.4 +com.github.sialcasa.mvvmFX:mvvmfx-validation:f195849ca9 +com.github.tomtung:latex2unicode_2.13:0.3.2 +com.github.vatbub:mslinks:1.0.6.2 +com.github.weisj:jsvg:1.2.0 +com.google.code.gson:gson:2.11.0 +com.google.errorprone:error_prone_annotations:2.27.0 +com.google.guava:failureaccess:1.0.2 +com.google.guava:guava:33.1.0-jre +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava +com.google.j2objc:j2objc-annotations:3.0.0 +com.googlecode.javaewah:JavaEWAH:1.2.3 +com.googlecode.plist:dd-plist:1.28 +com.h2database:h2-mvstore:2.3.232 +com.knuddels:jtokkit:1.1.0 +com.kohlschutter.junixsocket:junixsocket-common:2.10.0 +com.kohlschutter.junixsocket:junixsocket-core:2.10.0 +com.kohlschutter.junixsocket:junixsocket-mysql:2.10.0 +com.kohlschutter.junixsocket:junixsocket-native-common:2.10.0 +com.konghq:unirest-java-core:4.4.4 +com.konghq:unirest-modules-gson:4.4.4 +com.oracle.ojdbc:ojdbc10:19.3.0.0 +com.oracle.ojdbc:ons:19.3.0.0 +com.oracle.ojdbc:osdt_cert:19.3.0.0 +com.oracle.ojdbc:osdt_core:19.3.0.0 +com.oracle.ojdbc:simplefan:19.3.0.0 +com.oracle.ojdbc:ucp:19.3.0.0 +com.sun.istack:istack-commons-runtime:4.1.2 +com.vladsch.flexmark:flexmark-ext-emoji:0.64.8 +com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.64.8 +com.vladsch.flexmark:flexmark-ext-ins:0.64.8 +com.vladsch.flexmark:flexmark-ext-superscript:0.64.8 +com.vladsch.flexmark:flexmark-ext-tables:0.64.8 +com.vladsch.flexmark:flexmark-ext-wikilink:0.64.8 +com.vladsch.flexmark:flexmark-html2md-converter:0.64.8 +com.vladsch.flexmark:flexmark-jira-converter:0.64.8 +com.vladsch.flexmark:flexmark-util-ast:0.64.8 +com.vladsch.flexmark:flexmark-util-builder:0.64.8 +com.vladsch.flexmark:flexmark-util-collection:0.64.8 +com.vladsch.flexmark:flexmark-util-data:0.64.8 +com.vladsch.flexmark:flexmark-util-dependency:0.64.8 +com.vladsch.flexmark:flexmark-util-format:0.64.8 +com.vladsch.flexmark:flexmark-util-html:0.64.8 +com.vladsch.flexmark:flexmark-util-misc:0.64.8 +com.vladsch.flexmark:flexmark-util-options:0.64.8 +com.vladsch.flexmark:flexmark-util-sequence:0.64.8 +com.vladsch.flexmark:flexmark-util-visitor:0.64.8 +com.vladsch.flexmark:flexmark-util:0.64.8 +com.vladsch.flexmark:flexmark:0.64.8 +commons-beanutils:commons-beanutils:1.9.4 +commons-cli:commons-cli:1.9.0 +commons-codec:commons-codec:1.17.1 +commons-collections:commons-collections:3.2.2 +commons-digester:commons-digester:2.1 +commons-io:commons-io:2.16.1 +commons-logging:commons-logging:1.3.4 +commons-validator:commons-validator:1.8.0 +de.rototor.jeuclid:jeuclid-core:3.1.11 +de.rototor.snuggletex:snuggletex-core:1.3.0 +de.rototor.snuggletex:snuggletex-jeuclid:1.3.0 +de.rototor.snuggletex:snuggletex:1.3.0 +de.saxsys:mvvmfx:1.8.0 +de.swiesend:secret-service:1.8.1-jdk17 +de.undercouch:citeproc-java:3.1.0 +eu.lestard:doc-annotations:0.2 +info.debatty:java-string-similarity:2.0.0 +io.github.java-diff-utils:java-diff-utils:4.12 +io.zonky.test:embedded-postgres:2.0.7 +jakarta.activation:jakarta.activation-api:2.1.3 +jakarta.annotation:jakarta.annotation-api:2.1.1 +jakarta.inject:jakarta.inject-api:2.0.1 +jakarta.validation:jakarta.validation-api:3.0.2 +jakarta.ws.rs:jakarta.ws.rs-api:4.0.0 +jakarta.xml.bind:jakarta.xml.bind-api:4.0.2 +javax.measure:unit-api:2.2 +net.harawata:appdirs:1.2.2 +net.java.dev.jna:jna-platform:5.13.0 +net.java.dev.jna:jna:5.14.0 +net.jcip:jcip-annotations:1.0 +net.jodah:typetools:0.6.1 +net.synedra:validatorfx:0.5.0 +one.jpro.jproutils:tree-showing:0.2.2 +org.antlr:antlr4-runtime:4.13.2 +org.apache.commons:commons-compress:1.27.1 +org.apache.commons:commons-csv:1.11.0 +org.apache.commons:commons-lang3:3.17.0 +org.apache.commons:commons-text:1.12.0 +org.apache.httpcomponents.client5:httpclient5:5.3.1 +org.apache.httpcomponents.core5:httpcore5-h2:5.2.4 +org.apache.httpcomponents.core5:httpcore5:5.2.4 +org.apache.logging.log4j:log4j-api:2.24.0 +org.apache.logging.log4j:log4j-to-slf4j:2.24.0 +org.apache.lucene:lucene-analysis-common:9.11.1 +org.apache.lucene:lucene-core:9.11.1 +org.apache.lucene:lucene-highlighter:9.11.1 +org.apache.lucene:lucene-queries:9.11.1 +org.apache.lucene:lucene-queryparser:9.11.1 +org.apache.lucene:lucene-sandbox:9.11.1 +org.apache.pdfbox:fontbox:3.0.3 +org.apache.pdfbox:pdfbox-io:3.0.3 +org.apache.pdfbox:pdfbox:3.0.3 +org.apache.pdfbox:xmpbox:3.0.3 +org.apiguardian:apiguardian-api:1.1.2 +org.bouncycastle:bcprov-jdk18on:1.78.1 +org.checkerframework:checker-qual:3.42.0 +org.codehaus.woodstox:stax2-api:4.2.2 +org.controlsfx:controlsfx:11.2.1 +org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r +org.fxmisc.flowless:flowless:0.7.3 +org.fxmisc.richtext:richtextfx:0.11.3 +org.fxmisc.undo:undofx:2.1.1 +org.fxmisc.wellbehaved:wellbehavedfx:0.3.3 +org.glassfish.grizzly:grizzly-framework:4.0.2 +org.glassfish.grizzly:grizzly-http-server:4.0.2 +org.glassfish.grizzly:grizzly-http:4.0.2 +org.glassfish.hk2.external:aopalliance-repackaged:3.1.1 +org.glassfish.hk2:hk2-api:3.1.1 +org.glassfish.hk2:hk2-locator:3.0.6 +org.glassfish.hk2:hk2-utils:3.1.1 +org.glassfish.hk2:osgi-resource-locator:1.0.3 +org.glassfish.jaxb:jaxb-core:4.0.3 +org.glassfish.jaxb:jaxb-runtime:4.0.3 +org.glassfish.jaxb:txw2:4.0.3 +org.glassfish.jersey.containers:jersey-container-grizzly2-http:3.1.8 +org.glassfish.jersey.core:jersey-client:3.1.8 +org.glassfish.jersey.core:jersey-common:3.1.8 +org.glassfish.jersey.core:jersey-server:3.1.8 +org.glassfish.jersey.inject:jersey-hk2:3.1.8 +org.jabref:afterburner.fx:2.0.0 +org.jabref:easybind:2.2.1-SNAPSHOT +org.javassist:javassist:3.30.2-GA +org.jbibtex:jbibtex:1.0.20 +org.jetbrains:annotations:24.0.1 +org.jooq:jool:0.9.15 +org.jsoup:jsoup:1.18.1 +org.jspecify:jspecify:1.0.0 +org.kordamp.ikonli:ikonli-bootstrapicons-pack:12.3.1 +org.kordamp.ikonli:ikonli-core:12.3.1 +org.kordamp.ikonli:ikonli-javafx:12.3.1 +org.kordamp.ikonli:ikonli-material-pack:12.3.1 +org.kordamp.ikonli:ikonli-materialdesign-pack:12.3.1 +org.kordamp.ikonli:ikonli-materialdesign2-pack:12.3.1 +org.libreoffice:libreoffice:24.2.3 +org.libreoffice:unoloader:24.2.3 +org.mariadb.jdbc:mariadb-java-client:2.7.9 +org.openjfx:javafx-base:23 +org.openjfx:javafx-controls:23 +org.openjfx:javafx-fxml:23 +org.openjfx:javafx-graphics:23 +org.openjfx:javafx-media:23 +org.openjfx:javafx-swing:23 +org.openjfx:javafx-web:23 +org.postgresql:postgresql:42.7.4 +org.reactfx:reactfx:2.0-M5 +org.scala-lang:scala-library:2.13.8 +org.slf4j:jul-to-slf4j:2.0.16 +org.slf4j:slf4j-api:2.0.16 +org.tinylog:slf4j-tinylog:2.7.0 +org.tinylog:tinylog-api:2.7.0 +org.tinylog:tinylog-impl:2.7.0 +org.tukaani:xz:1.9 +org.yaml:snakeyaml:2.3 +pt.davidafsilva.apple:jkeychain:1.1.0 +tech.units:indriya:2.2 +tech.uom.lib:uom-lib-common:2.2 +``` diff --git a/jabref/flatpak/org.jabref.jabref.desktop b/jabref/flatpak/org.jabref.jabref.desktop new file mode 100644 index 00000000..cb4f0acd --- /dev/null +++ b/jabref/flatpak/org.jabref.jabref.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=JabRef +GenericName=BibTeX Editor +Comment=JabRef is an open source bibliography reference manager. The native file format used by JabRef is BibTeX, the standard LaTeX bibliography format. +Type=Application +Terminal=false +Icon=org.jabref.jabref +Exec=JabRef %U +Keywords=bibtex;biblatex;latex;bibliography +Categories=Office; +StartupWMClass=org-jabref-JabRefMain +MimeType=text/x-bibtex; diff --git a/jabref/flatpak/org.jabref.jabref.json b/jabref/flatpak/org.jabref.jabref.json new file mode 100644 index 00000000..f9d7373f --- /dev/null +++ b/jabref/flatpak/org.jabref.jabref.json @@ -0,0 +1,35 @@ +{ + "app-id" : "org.jabref.jabref", + "runtime" : "org.freedesktop.Platform", + "runtime-version" : "18.08", + "sdk" : "org.freedesktop.Sdk", + "command" : "JabRef", + "modules" : [ + { + "name" : "JabRef", + "buildsystem" : "simple", + "build-commands" : [ + "tar -xzf JabRef-portable_linux.tar.gz --directory=/app/ --strip-components=1", + "install -D -m0644 /app/lib/JabRef.png /app/share/icons/hicolor/64x64/apps/org.jabref.jabref.png", + "install -D -m0644 org.jabref.jabref.desktop /app/share/applications/org.jabref.jabref.desktop" + ], + "sources" : [ + { + "type" : "file", + "url": "http://builds.jabref.org/master/JabRef-portable_linux.tar.gz", + "sha256": "be69feee0536fa8500dda867290dbeeeda6191c60e636a0b6e254fbaac02d471" + }, + { + "type": "file", + "path": "org.jabref.jabref.desktop" + } + ] + } + ], + "finish-args" : [ + "--socket=wayland", + "--socket=fallback-x11", + "--share=network", + "--filesystem=home" + ] +} diff --git a/jabref/gradle/wrapper/gradle-wrapper.jar b/jabref/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..a4b76b95 Binary files /dev/null and b/jabref/gradle/wrapper/gradle-wrapper.jar differ diff --git a/jabref/gradle/wrapper/gradle-wrapper.properties b/jabref/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..fb602ee2 --- /dev/null +++ b/jabref/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,8 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/jabref/gradlew b/jabref/gradlew new file mode 100644 index 00000000..f5feea6d --- /dev/null +++ b/jabref/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/jabref/gradlew.bat b/jabref/gradlew.bat new file mode 100644 index 00000000..9d21a218 --- /dev/null +++ b/jabref/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jabref/javafx/scene/control/annotations.xml b/jabref/javafx/scene/control/annotations.xml new file mode 100644 index 00000000..240687d5 --- /dev/null +++ b/jabref/javafx/scene/control/annotations.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/jabref/lib/fastparse-2.3.3.jar b/jabref/lib/fastparse-2.3.3.jar new file mode 100644 index 00000000..2bcb2523 Binary files /dev/null and b/jabref/lib/fastparse-2.3.3.jar differ diff --git a/jabref/lib/geny-0.6.10.jar b/jabref/lib/geny-0.6.10.jar new file mode 100644 index 00000000..02846e35 Binary files /dev/null and b/jabref/lib/geny-0.6.10.jar differ diff --git a/jabref/lib/icu4j-src.jar b/jabref/lib/icu4j-src.jar new file mode 100644 index 00000000..032936ef Binary files /dev/null and b/jabref/lib/icu4j-src.jar differ diff --git a/jabref/lib/icu4j.jar b/jabref/lib/icu4j.jar new file mode 100644 index 00000000..207c4a6a Binary files /dev/null and b/jabref/lib/icu4j.jar differ diff --git a/jabref/lib/sourcecode-0.2.3.jar b/jabref/lib/sourcecode-0.2.3.jar new file mode 100644 index 00000000..a0a649fc Binary files /dev/null and b/jabref/lib/sourcecode-0.2.3.jar differ diff --git a/jabref/licenses/com.apple_AppleJavaExtensions.txt b/jabref/licenses/com.apple_AppleJavaExtensions.txt new file mode 100644 index 00000000..08fb3d1c --- /dev/null +++ b/jabref/licenses/com.apple_AppleJavaExtensions.txt @@ -0,0 +1,51 @@ +AppleJavaExtensions + +This is a pluggable jar of stub classes representing the new Apple eAWT and +eIO APIs for Java on Mac OS X. The purpose of these stubs is to allow for +compilation of eAWT- or eIO-referencing code on platforms other than Mac OS X. +The jar file is enclosed in a zip archive for easy expansion on other +platforms. + +These stubs are not intended for the runtime classpath on non-Mac platforms. +Please see the OSXAdapter sample for how to write cross-platform code that uses + eAWT. + +Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +Computer, Inc. ("Apple") in consideration of your agreement to the +following terms, and your use, installation, modification or +redistribution of this Apple software constitutes acceptance of these +terms. If you do not agree with these terms, please do not use, +install, modify or redistribute this Apple software. + +In consideration of your agreement to abide by the following terms, and +subject to these terms, Apple grants you a personal, non-exclusive +license, under Apple's copyrights in this original Apple software (the +"Apple Software"), to use, reproduce, modify and redistribute the Apple +Software, with or without modifications, in source and/or binary forms; +provided that if you redistribute the Apple Software in its entirety and +without modifications, you must retain this notice and the following +text and disclaimers in all such redistributions of the Apple Software. +Neither the name, trademarks, service marks or logos of Apple Computer, +Inc. may be used to endorse or promote products derived from the Apple +Software without specific prior written permission from Apple. Except +as expressly stated in this notice, no other rights or licenses, express +or implied, are granted by Apple herein, including but not limited to +any patent rights that may be infringed by your derivative works or by +other works in which the Apple Software may be incorporated. + +The Apple Software is provided by Apple on an "AS IS" basis. APPLE +MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + +IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +Copyright © 2003-2010 Apple Inc., All Rights Reserved diff --git a/jabref/licenses/com.googlecode.plist_ddplist.txt b/jabref/licenses/com.googlecode.plist_ddplist.txt new file mode 100644 index 00000000..ab9e4668 --- /dev/null +++ b/jabref/licenses/com.googlecode.plist_ddplist.txt @@ -0,0 +1,20 @@ +dd-plist - An open source library to parse and generate property lists +Copyright (C) 2016 Daniel Dreibrodt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/jabref/licenses/com.mashape.unirest.txt b/jabref/licenses/com.mashape.unirest.txt new file mode 100644 index 00000000..bc937f13 --- /dev/null +++ b/jabref/licenses/com.mashape.unirest.txt @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2013-2015 Mashape (https://www.mashape.com) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/jabref/licenses/commons-cli_commons-cli.txt b/jabref/licenses/commons-cli_commons-cli.txt new file mode 100644 index 00000000..4215ee5e --- /dev/null +++ b/jabref/licenses/commons-cli_commons-cli.txt @@ -0,0 +1,213 @@ +Apache Commons CLI +Copyright 2001-2015 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + + +Information about the Apache license is available at +http://www.apache.org/licenses/. + +Verbatim copy of the Apache License: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/jabref/licenses/commons-logging_commons-logging.txt b/jabref/licenses/commons-logging_commons-logging.txt new file mode 100644 index 00000000..9eef0465 --- /dev/null +++ b/jabref/licenses/commons-logging_commons-logging.txt @@ -0,0 +1 @@ +See Apache Commons CLI \ No newline at end of file diff --git a/jabref/licenses/de.undercouch.citeproc-java.txt b/jabref/licenses/de.undercouch.citeproc-java.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/jabref/licenses/de.undercouch.citeproc-java.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/jabref/licenses/material-design-icons.font.txt b/jabref/licenses/material-design-icons.font.txt new file mode 100644 index 00000000..699786ed --- /dev/null +++ b/jabref/licenses/material-design-icons.font.txt @@ -0,0 +1,97 @@ +Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/), +with Reserved Font Name Material Design Icons. +Copyright (c) 2014, Google (http://www.google.com/design/) +uses the license at +https://github.com/google/material-design-icons/blob/master/LICENSE + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/jabref/licenses/microba.txt b/jabref/licenses/microba.txt new file mode 100644 index 00000000..849028b2 --- /dev/null +++ b/jabref/licenses/microba.txt @@ -0,0 +1,27 @@ +Copyright (c) 2005-2014, Michael Baranov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/jabref/licenses/net.java.dev.glazedlists_glazedlists_java15.txt b/jabref/licenses/net.java.dev.glazedlists_glazedlists_java15.txt new file mode 100644 index 00000000..04afdfab --- /dev/null +++ b/jabref/licenses/net.java.dev.glazedlists_glazedlists_java15.txt @@ -0,0 +1,13 @@ + +Glazed Lists +Copyright (c) 2003-2006, publicobject.com, O'Dell Engineering Ltd. + +Glazed Lists is free software and business friendly. It allows you to + * distribute Glazed Lists free of charge + * use Glazed Lists in a commercial or closed source application +It does not allow you to + * create a fork of Glazed Lists that is closed-source + +It is made available under two licenses: + LGPL, http://creativecommons.org/licenses/LGPL/2.1/ + MPL, http://www.mozilla.org/MPL/ \ No newline at end of file diff --git a/jabref/licenses/net.java.dev.jna_jna.txt b/jabref/licenses/net.java.dev.jna_jna.txt new file mode 100644 index 00000000..3ed9ca47 --- /dev/null +++ b/jabref/licenses/net.java.dev.jna_jna.txt @@ -0,0 +1,3 @@ +This library is licensed under the LGPL, version 2.1 or later, and (from +version 4.0 onward) the Apache Software License, version 2.0. Commercial +license arrangements are negotiable. \ No newline at end of file diff --git a/jabref/licenses/org.antlr_antlr.txt b/jabref/licenses/org.antlr_antlr.txt new file mode 100644 index 00000000..359ae48e --- /dev/null +++ b/jabref/licenses/org.antlr_antlr.txt @@ -0,0 +1,11 @@ +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/jabref/licenses/org.antlr_antlr4.txt b/jabref/licenses/org.antlr_antlr4.txt new file mode 100644 index 00000000..95d0a255 --- /dev/null +++ b/jabref/licenses/org.antlr_antlr4.txt @@ -0,0 +1,26 @@ +[The "BSD license"] +Copyright (c) 2015 Terence Parr, Sam Harwell +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/jabref/licenses/org.apache.pdfbox_fontbox.txt b/jabref/licenses/org.apache.pdfbox_fontbox.txt new file mode 100644 index 00000000..9eef0465 --- /dev/null +++ b/jabref/licenses/org.apache.pdfbox_fontbox.txt @@ -0,0 +1 @@ +See Apache Commons CLI \ No newline at end of file diff --git a/jabref/licenses/org.apache.pdfbox_jempbox.txt b/jabref/licenses/org.apache.pdfbox_jempbox.txt new file mode 100644 index 00000000..9eef0465 --- /dev/null +++ b/jabref/licenses/org.apache.pdfbox_jempbox.txt @@ -0,0 +1 @@ +See Apache Commons CLI \ No newline at end of file diff --git a/jabref/licenses/org.apache.pdfbox_pdfbox.txt b/jabref/licenses/org.apache.pdfbox_pdfbox.txt new file mode 100644 index 00000000..9eef0465 --- /dev/null +++ b/jabref/licenses/org.apache.pdfbox_pdfbox.txt @@ -0,0 +1 @@ +See Apache Commons CLI \ No newline at end of file diff --git a/jabref/licenses/org.citationstyles.locales.txt b/jabref/licenses/org.citationstyles.locales.txt new file mode 100644 index 00000000..604209a8 --- /dev/null +++ b/jabref/licenses/org.citationstyles.locales.txt @@ -0,0 +1,359 @@ +Creative Commons Legal Code + +Attribution-ShareAlike 3.0 Unported + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR + DAMAGES RESULTING FROM ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE +COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY +COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS +AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE +TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY +BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND +CONDITIONS. + +1. Definitions + + a. "Adaptation" means a work based upon the Work, or upon the Work and + other pre-existing works, such as a translation, adaptation, + derivative work, arrangement of music or other alterations of a + literary or artistic work, or phonogram or performance and includes + cinematographic adaptations or any other form in which the Work may be + recast, transformed, or adapted including in any form recognizably + derived from the original, except that a work that constitutes a + Collection will not be considered an Adaptation for the purpose of + this License. For the avoidance of doubt, where the Work is a musical + work, performance or phonogram, the synchronization of the Work in + timed-relation with a moving image ("synching") will be considered an + Adaptation for the purpose of this License. + b. "Collection" means a collection of literary or artistic works, such as + encyclopedias and anthologies, or performances, phonograms or + broadcasts, or other works or subject matter other than works listed + in Section 1(f) below, which, by reason of the selection and + arrangement of their contents, constitute intellectual creations, in + which the Work is included in its entirety in unmodified form along + with one or more other contributions, each constituting separate and + independent works in themselves, which together are assembled into a + collective whole. A work that constitutes a Collection will not be + considered an Adaptation (as defined below) for the purposes of this + License. + c. "Creative Commons Compatible License" means a license that is listed + at https://creativecommons.org/compatiblelicenses that has been + approved by Creative Commons as being essentially equivalent to this + License, including, at a minimum, because that license: (i) contains + terms that have the same purpose, meaning and effect as the License + Elements of this License; and, (ii) explicitly permits the relicensing + of adaptations of works made available under that license under this + License or a Creative Commons jurisdiction license with the same + License Elements as this License. + d. "Distribute" means to make available to the public the original and + copies of the Work or Adaptation, as appropriate, through sale or + other transfer of ownership. + e. "License Elements" means the following high-level license attributes + as selected by Licensor and indicated in the title of this License: + Attribution, ShareAlike. + f. "Licensor" means the individual, individuals, entity or entities that + offer(s) the Work under the terms of this License. + g. "Original Author" means, in the case of a literary or artistic work, + the individual, individuals, entity or entities who created the Work + or if no individual or entity can be identified, the publisher; and in + addition (i) in the case of a performance the actors, singers, + musicians, dancers, and other persons who act, sing, deliver, declaim, + play in, interpret or otherwise perform literary or artistic works or + expressions of folklore; (ii) in the case of a phonogram the producer + being the person or legal entity who first fixes the sounds of a + performance or other sounds; and, (iii) in the case of broadcasts, the + organization that transmits the broadcast. + h. "Work" means the literary and/or artistic work offered under the terms + of this License including without limitation any production in the + literary, scientific and artistic domain, whatever may be the mode or + form of its expression including digital form, such as a book, + pamphlet and other writing; a lecture, address, sermon or other work + of the same nature; a dramatic or dramatico-musical work; a + choreographic work or entertainment in dumb show; a musical + composition with or without words; a cinematographic work to which are + assimilated works expressed by a process analogous to cinematography; + a work of drawing, painting, architecture, sculpture, engraving or + lithography; a photographic work to which are assimilated works + expressed by a process analogous to photography; a work of applied + art; an illustration, map, plan, sketch or three-dimensional work + relative to geography, topography, architecture or science; a + performance; a broadcast; a phonogram; a compilation of data to the + extent it is protected as a copyrightable work; or a work performed by + a variety or circus performer to the extent it is not otherwise + considered a literary or artistic work. + i. "You" means an individual or entity exercising rights under this + License who has not previously violated the terms of this License with + respect to the Work, or who has received express permission from the + Licensor to exercise rights under this License despite a previous + violation. + j. "Publicly Perform" means to perform public recitations of the Work and + to communicate to the public those public recitations, by any means or + process, including by wire or wireless means or public digital + performances; to make available to the public Works in such a way that + members of the public may access these Works from a place and at a + place individually chosen by them; to perform the Work to the public + by any means or process and the communication to the public of the + performances of the Work, including by public digital performance; to + broadcast and rebroadcast the Work by any means including signs, + sounds or images. + k. "Reproduce" means to make copies of the Work by any means including + without limitation by sound or visual recordings and the right of + fixation and reproducing fixations of the Work, including storage of a + protected performance or phonogram in digital form or other electronic + medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, +limit, or restrict any uses free from copyright or rights arising from +limitations or exceptions that are provided for in connection with the +copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, +Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +perpetual (for the duration of the applicable copyright) license to +exercise the rights in the Work as stated below: + + a. to Reproduce the Work, to incorporate the Work into one or more + Collections, and to Reproduce the Work as incorporated in the + Collections; + b. to create and Reproduce Adaptations provided that any such Adaptation, + including any translation in any medium, takes reasonable steps to + clearly label, demarcate or otherwise identify that changes were made + to the original Work. For example, a translation could be marked "The + original work was translated from English to Spanish," or a + modification could indicate "The original work has been modified."; + c. to Distribute and Publicly Perform the Work including as incorporated + in Collections; and, + d. to Distribute and Publicly Perform Adaptations. + e. For the avoidance of doubt: + + i. Non-waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme cannot be waived, the Licensor + reserves the exclusive right to collect such royalties for any + exercise by You of the rights granted under this License; + ii. Waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme can be waived, the Licensor waives the + exclusive right to collect such royalties for any exercise by You + of the rights granted under this License; and, + iii. Voluntary License Schemes. The Licensor waives the right to + collect royalties, whether individually or, in the event that the + Licensor is a member of a collecting society that administers + voluntary licensing schemes, via that society, from any exercise + by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now +known or hereafter devised. The above rights include the right to make +such modifications as are technically necessary to exercise the rights in +other media and formats. Subject to Section 8(f), all rights not expressly +granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made +subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms + of this License. You must include a copy of, or the Uniform Resource + Identifier (URI) for, this License with every copy of the Work You + Distribute or Publicly Perform. You may not offer or impose any terms + on the Work that restrict the terms of this License or the ability of + the recipient of the Work to exercise the rights granted to that + recipient under the terms of the License. You may not sublicense the + Work. You must keep intact all notices that refer to this License and + to the disclaimer of warranties with every copy of the Work You + Distribute or Publicly Perform. When You Distribute or Publicly + Perform the Work, You may not impose any effective technological + measures on the Work that restrict the ability of a recipient of the + Work from You to exercise the rights granted to that recipient under + the terms of the License. This Section 4(a) applies to the Work as + incorporated in a Collection, but this does not require the Collection + apart from the Work itself to be made subject to the terms of this + License. If You create a Collection, upon notice from any Licensor You + must, to the extent practicable, remove from the Collection any credit + as required by Section 4(c), as requested. If You create an + Adaptation, upon notice from any Licensor You must, to the extent + practicable, remove from the Adaptation any credit as required by + Section 4(c), as requested. + b. You may Distribute or Publicly Perform an Adaptation only under the + terms of: (i) this License; (ii) a later version of this License with + the same License Elements as this License; (iii) a Creative Commons + jurisdiction license (either this or a later license version) that + contains the same License Elements as this License (e.g., + Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible + License. If you license the Adaptation under one of the licenses + mentioned in (iv), you must comply with the terms of that license. If + you license the Adaptation under the terms of any of the licenses + mentioned in (i), (ii) or (iii) (the "Applicable License"), you must + comply with the terms of the Applicable License generally and the + following provisions: (I) You must include a copy of, or the URI for, + the Applicable License with every copy of each Adaptation You + Distribute or Publicly Perform; (II) You may not offer or impose any + terms on the Adaptation that restrict the terms of the Applicable + License or the ability of the recipient of the Adaptation to exercise + the rights granted to that recipient under the terms of the Applicable + License; (III) You must keep intact all notices that refer to the + Applicable License and to the disclaimer of warranties with every copy + of the Work as included in the Adaptation You Distribute or Publicly + Perform; (IV) when You Distribute or Publicly Perform the Adaptation, + You may not impose any effective technological measures on the + Adaptation that restrict the ability of a recipient of the Adaptation + from You to exercise the rights granted to that recipient under the + terms of the Applicable License. This Section 4(b) applies to the + Adaptation as incorporated in a Collection, but this does not require + the Collection apart from the Adaptation itself to be made subject to + the terms of the Applicable License. + c. If You Distribute, or Publicly Perform the Work or any Adaptations or + Collections, You must, unless a request has been made pursuant to + Section 4(a), keep intact all copyright notices for the Work and + provide, reasonable to the medium or means You are utilizing: (i) the + name of the Original Author (or pseudonym, if applicable) if supplied, + and/or if the Original Author and/or Licensor designate another party + or parties (e.g., a sponsor institute, publishing entity, journal) for + attribution ("Attribution Parties") in Licensor's copyright notice, + terms of service or by other reasonable means, the name of such party + or parties; (ii) the title of the Work if supplied; (iii) to the + extent reasonably practicable, the URI, if any, that Licensor + specifies to be associated with the Work, unless such URI does not + refer to the copyright notice or licensing information for the Work; + and (iv) , consistent with Ssection 3(b), in the case of an + Adaptation, a credit identifying the use of the Work in the Adaptation + (e.g., "French translation of the Work by Original Author," or + "Screenplay based on original Work by Original Author"). The credit + required by this Section 4(c) may be implemented in any reasonable + manner; provided, however, that in the case of a Adaptation or + Collection, at a minimum such credit will appear, if a credit for all + contributing authors of the Adaptation or Collection appears, then as + part of these credits and in a manner at least as prominent as the + credits for the other contributing authors. For the avoidance of + doubt, You may only use the credit required by this Section for the + purpose of attribution in the manner set out above and, by exercising + Your rights under this License, You may not implicitly or explicitly + assert or imply any connection with, sponsorship or endorsement by the + Original Author, Licensor and/or Attribution Parties, as appropriate, + of You or Your use of the Work, without the separate, express prior + written permission of the Original Author, Licensor and/or Attribution + Parties. + d. Except as otherwise agreed in writing by the Licensor or as may be + otherwise permitted by applicable law, if You Reproduce, Distribute or + Publicly Perform the Work either by itself or as part of any + Adaptations or Collections, You must not distort, mutilate, modify or + take other derogatory action in relation to the Work which would be + prejudicial to the Original Author's honor or reputation. Licensor + agrees that in those jurisdictions (e.g. Japan), in which any exercise + of the right granted in Section 3(b) of this License (the right to + make Adaptations) would be deemed to be a distortion, mutilation, + modification or other derogatory action prejudicial to the Original + Author's honor and reputation, the Licensor will waive or not assert, + as appropriate, this Section, to the fullest extent permitted by the + applicable national law, to enable You to reasonably exercise Your + right under Section 3(b) of this License (right to make Adaptations) + but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR +OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY +KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, +INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, +FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF +LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION +OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE +LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR +ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES +ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. This License and the rights granted hereunder will terminate + automatically upon any breach by You of the terms of this License. + Individuals or entities who have received Adaptations or Collections + from You under this License, however, will not have their licenses + terminated provided such individuals or entities remain in full + compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will + survive any termination of this License. + b. Subject to the above terms and conditions, the license granted here is + perpetual (for the duration of the applicable copyright in the Work). + Notwithstanding the above, Licensor reserves the right to release the + Work under different license terms or to stop distributing the Work at + any time; provided, however that any such election will not serve to + withdraw this License (or any other license that has been, or is + required to be, granted under the terms of this License), and this + License will continue in full force and effect unless terminated as + stated above. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, + the Licensor offers to the recipient a license to the Work on the same + terms and conditions as the license granted to You under this License. + b. Each time You Distribute or Publicly Perform an Adaptation, Licensor + offers to the recipient a license to the original Work on the same + terms and conditions as the license granted to You under this License. + c. If any provision of this License is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this License, and without further action + by the parties to this agreement, such provision shall be reformed to + the minimum extent necessary to make such provision valid and + enforceable. + d. No term or provision of this License shall be deemed waived and no + breach consented to unless such waiver or consent shall be in writing + and signed by the party to be charged with such waiver or consent. + e. This License constitutes the entire agreement between the parties with + respect to the Work licensed here. There are no understandings, + agreements or representations with respect to the Work not specified + here. Licensor shall not be bound by any additional provisions that + may appear in any communication from You. This License may not be + modified without the mutual written agreement of the Licensor and You. + f. The rights granted under, and the subject matter referenced, in this + License were drafted utilizing the terminology of the Berne Convention + for the Protection of Literary and Artistic Works (as amended on + September 28, 1979), the Rome Convention of 1961, the WIPO Copyright + Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 + and the Universal Copyright Convention (as revised on July 24, 1971). + These rights and subject matter take effect in the relevant + jurisdiction in which the License terms are sought to be enforced + according to the corresponding provisions of the implementation of + those treaty provisions in the applicable national law. If the + standard suite of rights granted under applicable copyright law + includes additional rights not granted under this License, such + additional rights are deemed to be included in the License; this + License is not intended to restrict the license of any rights under + applicable law. + + +Creative Commons Notice + + Creative Commons is not a party to this License, and makes no warranty + whatsoever in connection with the Work. Creative Commons will not be + liable to You or any party on any legal theory for any damages + whatsoever, including without limitation any general, special, + incidental or consequential damages arising in connection to this + license. Notwithstanding the foregoing two (2) sentences, if Creative + Commons has expressly identified itself as the Licensor hereunder, it + shall have all rights and obligations of Licensor. + + Except for the limited purpose of indicating to the public that the + Work is licensed under the CCPL, Creative Commons does not authorize + the use by either party of the trademark "Creative Commons" or any + related trademark or logo of Creative Commons without the prior + written consent of Creative Commons. Any permitted use will be in + compliance with Creative Commons' then-current trademark usage + guidelines, as may be published on its website or otherwise made + available upon request from time to time. For the avoidance of doubt, + this trademark restriction does not form part of the License. + + Creative Commons may be contacted at https://creativecommons.org/. diff --git a/jabref/licenses/org.citationstyles.styles.txt b/jabref/licenses/org.citationstyles.styles.txt new file mode 100644 index 00000000..604209a8 --- /dev/null +++ b/jabref/licenses/org.citationstyles.styles.txt @@ -0,0 +1,359 @@ +Creative Commons Legal Code + +Attribution-ShareAlike 3.0 Unported + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR + DAMAGES RESULTING FROM ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE +COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY +COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS +AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE +TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY +BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND +CONDITIONS. + +1. Definitions + + a. "Adaptation" means a work based upon the Work, or upon the Work and + other pre-existing works, such as a translation, adaptation, + derivative work, arrangement of music or other alterations of a + literary or artistic work, or phonogram or performance and includes + cinematographic adaptations or any other form in which the Work may be + recast, transformed, or adapted including in any form recognizably + derived from the original, except that a work that constitutes a + Collection will not be considered an Adaptation for the purpose of + this License. For the avoidance of doubt, where the Work is a musical + work, performance or phonogram, the synchronization of the Work in + timed-relation with a moving image ("synching") will be considered an + Adaptation for the purpose of this License. + b. "Collection" means a collection of literary or artistic works, such as + encyclopedias and anthologies, or performances, phonograms or + broadcasts, or other works or subject matter other than works listed + in Section 1(f) below, which, by reason of the selection and + arrangement of their contents, constitute intellectual creations, in + which the Work is included in its entirety in unmodified form along + with one or more other contributions, each constituting separate and + independent works in themselves, which together are assembled into a + collective whole. A work that constitutes a Collection will not be + considered an Adaptation (as defined below) for the purposes of this + License. + c. "Creative Commons Compatible License" means a license that is listed + at https://creativecommons.org/compatiblelicenses that has been + approved by Creative Commons as being essentially equivalent to this + License, including, at a minimum, because that license: (i) contains + terms that have the same purpose, meaning and effect as the License + Elements of this License; and, (ii) explicitly permits the relicensing + of adaptations of works made available under that license under this + License or a Creative Commons jurisdiction license with the same + License Elements as this License. + d. "Distribute" means to make available to the public the original and + copies of the Work or Adaptation, as appropriate, through sale or + other transfer of ownership. + e. "License Elements" means the following high-level license attributes + as selected by Licensor and indicated in the title of this License: + Attribution, ShareAlike. + f. "Licensor" means the individual, individuals, entity or entities that + offer(s) the Work under the terms of this License. + g. "Original Author" means, in the case of a literary or artistic work, + the individual, individuals, entity or entities who created the Work + or if no individual or entity can be identified, the publisher; and in + addition (i) in the case of a performance the actors, singers, + musicians, dancers, and other persons who act, sing, deliver, declaim, + play in, interpret or otherwise perform literary or artistic works or + expressions of folklore; (ii) in the case of a phonogram the producer + being the person or legal entity who first fixes the sounds of a + performance or other sounds; and, (iii) in the case of broadcasts, the + organization that transmits the broadcast. + h. "Work" means the literary and/or artistic work offered under the terms + of this License including without limitation any production in the + literary, scientific and artistic domain, whatever may be the mode or + form of its expression including digital form, such as a book, + pamphlet and other writing; a lecture, address, sermon or other work + of the same nature; a dramatic or dramatico-musical work; a + choreographic work or entertainment in dumb show; a musical + composition with or without words; a cinematographic work to which are + assimilated works expressed by a process analogous to cinematography; + a work of drawing, painting, architecture, sculpture, engraving or + lithography; a photographic work to which are assimilated works + expressed by a process analogous to photography; a work of applied + art; an illustration, map, plan, sketch or three-dimensional work + relative to geography, topography, architecture or science; a + performance; a broadcast; a phonogram; a compilation of data to the + extent it is protected as a copyrightable work; or a work performed by + a variety or circus performer to the extent it is not otherwise + considered a literary or artistic work. + i. "You" means an individual or entity exercising rights under this + License who has not previously violated the terms of this License with + respect to the Work, or who has received express permission from the + Licensor to exercise rights under this License despite a previous + violation. + j. "Publicly Perform" means to perform public recitations of the Work and + to communicate to the public those public recitations, by any means or + process, including by wire or wireless means or public digital + performances; to make available to the public Works in such a way that + members of the public may access these Works from a place and at a + place individually chosen by them; to perform the Work to the public + by any means or process and the communication to the public of the + performances of the Work, including by public digital performance; to + broadcast and rebroadcast the Work by any means including signs, + sounds or images. + k. "Reproduce" means to make copies of the Work by any means including + without limitation by sound or visual recordings and the right of + fixation and reproducing fixations of the Work, including storage of a + protected performance or phonogram in digital form or other electronic + medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, +limit, or restrict any uses free from copyright or rights arising from +limitations or exceptions that are provided for in connection with the +copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, +Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +perpetual (for the duration of the applicable copyright) license to +exercise the rights in the Work as stated below: + + a. to Reproduce the Work, to incorporate the Work into one or more + Collections, and to Reproduce the Work as incorporated in the + Collections; + b. to create and Reproduce Adaptations provided that any such Adaptation, + including any translation in any medium, takes reasonable steps to + clearly label, demarcate or otherwise identify that changes were made + to the original Work. For example, a translation could be marked "The + original work was translated from English to Spanish," or a + modification could indicate "The original work has been modified."; + c. to Distribute and Publicly Perform the Work including as incorporated + in Collections; and, + d. to Distribute and Publicly Perform Adaptations. + e. For the avoidance of doubt: + + i. Non-waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme cannot be waived, the Licensor + reserves the exclusive right to collect such royalties for any + exercise by You of the rights granted under this License; + ii. Waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme can be waived, the Licensor waives the + exclusive right to collect such royalties for any exercise by You + of the rights granted under this License; and, + iii. Voluntary License Schemes. The Licensor waives the right to + collect royalties, whether individually or, in the event that the + Licensor is a member of a collecting society that administers + voluntary licensing schemes, via that society, from any exercise + by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now +known or hereafter devised. The above rights include the right to make +such modifications as are technically necessary to exercise the rights in +other media and formats. Subject to Section 8(f), all rights not expressly +granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made +subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms + of this License. You must include a copy of, or the Uniform Resource + Identifier (URI) for, this License with every copy of the Work You + Distribute or Publicly Perform. You may not offer or impose any terms + on the Work that restrict the terms of this License or the ability of + the recipient of the Work to exercise the rights granted to that + recipient under the terms of the License. You may not sublicense the + Work. You must keep intact all notices that refer to this License and + to the disclaimer of warranties with every copy of the Work You + Distribute or Publicly Perform. When You Distribute or Publicly + Perform the Work, You may not impose any effective technological + measures on the Work that restrict the ability of a recipient of the + Work from You to exercise the rights granted to that recipient under + the terms of the License. This Section 4(a) applies to the Work as + incorporated in a Collection, but this does not require the Collection + apart from the Work itself to be made subject to the terms of this + License. If You create a Collection, upon notice from any Licensor You + must, to the extent practicable, remove from the Collection any credit + as required by Section 4(c), as requested. If You create an + Adaptation, upon notice from any Licensor You must, to the extent + practicable, remove from the Adaptation any credit as required by + Section 4(c), as requested. + b. You may Distribute or Publicly Perform an Adaptation only under the + terms of: (i) this License; (ii) a later version of this License with + the same License Elements as this License; (iii) a Creative Commons + jurisdiction license (either this or a later license version) that + contains the same License Elements as this License (e.g., + Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible + License. If you license the Adaptation under one of the licenses + mentioned in (iv), you must comply with the terms of that license. If + you license the Adaptation under the terms of any of the licenses + mentioned in (i), (ii) or (iii) (the "Applicable License"), you must + comply with the terms of the Applicable License generally and the + following provisions: (I) You must include a copy of, or the URI for, + the Applicable License with every copy of each Adaptation You + Distribute or Publicly Perform; (II) You may not offer or impose any + terms on the Adaptation that restrict the terms of the Applicable + License or the ability of the recipient of the Adaptation to exercise + the rights granted to that recipient under the terms of the Applicable + License; (III) You must keep intact all notices that refer to the + Applicable License and to the disclaimer of warranties with every copy + of the Work as included in the Adaptation You Distribute or Publicly + Perform; (IV) when You Distribute or Publicly Perform the Adaptation, + You may not impose any effective technological measures on the + Adaptation that restrict the ability of a recipient of the Adaptation + from You to exercise the rights granted to that recipient under the + terms of the Applicable License. This Section 4(b) applies to the + Adaptation as incorporated in a Collection, but this does not require + the Collection apart from the Adaptation itself to be made subject to + the terms of the Applicable License. + c. If You Distribute, or Publicly Perform the Work or any Adaptations or + Collections, You must, unless a request has been made pursuant to + Section 4(a), keep intact all copyright notices for the Work and + provide, reasonable to the medium or means You are utilizing: (i) the + name of the Original Author (or pseudonym, if applicable) if supplied, + and/or if the Original Author and/or Licensor designate another party + or parties (e.g., a sponsor institute, publishing entity, journal) for + attribution ("Attribution Parties") in Licensor's copyright notice, + terms of service or by other reasonable means, the name of such party + or parties; (ii) the title of the Work if supplied; (iii) to the + extent reasonably practicable, the URI, if any, that Licensor + specifies to be associated with the Work, unless such URI does not + refer to the copyright notice or licensing information for the Work; + and (iv) , consistent with Ssection 3(b), in the case of an + Adaptation, a credit identifying the use of the Work in the Adaptation + (e.g., "French translation of the Work by Original Author," or + "Screenplay based on original Work by Original Author"). The credit + required by this Section 4(c) may be implemented in any reasonable + manner; provided, however, that in the case of a Adaptation or + Collection, at a minimum such credit will appear, if a credit for all + contributing authors of the Adaptation or Collection appears, then as + part of these credits and in a manner at least as prominent as the + credits for the other contributing authors. For the avoidance of + doubt, You may only use the credit required by this Section for the + purpose of attribution in the manner set out above and, by exercising + Your rights under this License, You may not implicitly or explicitly + assert or imply any connection with, sponsorship or endorsement by the + Original Author, Licensor and/or Attribution Parties, as appropriate, + of You or Your use of the Work, without the separate, express prior + written permission of the Original Author, Licensor and/or Attribution + Parties. + d. Except as otherwise agreed in writing by the Licensor or as may be + otherwise permitted by applicable law, if You Reproduce, Distribute or + Publicly Perform the Work either by itself or as part of any + Adaptations or Collections, You must not distort, mutilate, modify or + take other derogatory action in relation to the Work which would be + prejudicial to the Original Author's honor or reputation. Licensor + agrees that in those jurisdictions (e.g. Japan), in which any exercise + of the right granted in Section 3(b) of this License (the right to + make Adaptations) would be deemed to be a distortion, mutilation, + modification or other derogatory action prejudicial to the Original + Author's honor and reputation, the Licensor will waive or not assert, + as appropriate, this Section, to the fullest extent permitted by the + applicable national law, to enable You to reasonably exercise Your + right under Section 3(b) of this License (right to make Adaptations) + but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR +OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY +KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, +INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, +FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF +LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION +OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE +LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR +ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES +ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. This License and the rights granted hereunder will terminate + automatically upon any breach by You of the terms of this License. + Individuals or entities who have received Adaptations or Collections + from You under this License, however, will not have their licenses + terminated provided such individuals or entities remain in full + compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will + survive any termination of this License. + b. Subject to the above terms and conditions, the license granted here is + perpetual (for the duration of the applicable copyright in the Work). + Notwithstanding the above, Licensor reserves the right to release the + Work under different license terms or to stop distributing the Work at + any time; provided, however that any such election will not serve to + withdraw this License (or any other license that has been, or is + required to be, granted under the terms of this License), and this + License will continue in full force and effect unless terminated as + stated above. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, + the Licensor offers to the recipient a license to the Work on the same + terms and conditions as the license granted to You under this License. + b. Each time You Distribute or Publicly Perform an Adaptation, Licensor + offers to the recipient a license to the original Work on the same + terms and conditions as the license granted to You under this License. + c. If any provision of this License is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this License, and without further action + by the parties to this agreement, such provision shall be reformed to + the minimum extent necessary to make such provision valid and + enforceable. + d. No term or provision of this License shall be deemed waived and no + breach consented to unless such waiver or consent shall be in writing + and signed by the party to be charged with such waiver or consent. + e. This License constitutes the entire agreement between the parties with + respect to the Work licensed here. There are no understandings, + agreements or representations with respect to the Work not specified + here. Licensor shall not be bound by any additional provisions that + may appear in any communication from You. This License may not be + modified without the mutual written agreement of the Licensor and You. + f. The rights granted under, and the subject matter referenced, in this + License were drafted utilizing the terminology of the Berne Convention + for the Protection of Literary and Artistic Works (as amended on + September 28, 1979), the Rome Convention of 1961, the WIPO Copyright + Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 + and the Universal Copyright Convention (as revised on July 24, 1971). + These rights and subject matter take effect in the relevant + jurisdiction in which the License terms are sought to be enforced + according to the corresponding provisions of the implementation of + those treaty provisions in the applicable national law. If the + standard suite of rights granted under applicable copyright law + includes additional rights not granted under this License, such + additional rights are deemed to be included in the License; this + License is not intended to restrict the license of any rights under + applicable law. + + +Creative Commons Notice + + Creative Commons is not a party to this License, and makes no warranty + whatsoever in connection with the Work. Creative Commons will not be + liable to You or any party on any legal theory for any damages + whatsoever, including without limitation any general, special, + incidental or consequential damages arising in connection to this + license. Notwithstanding the foregoing two (2) sentences, if Creative + Commons has expressly identified itself as the Licensor hereunder, it + shall have all rights and obligations of Licensor. + + Except for the limited purpose of indicating to the public that the + Work is licensed under the CCPL, Creative Commons does not authorize + the use by either party of the trademark "Creative Commons" or any + related trademark or logo of Creative Commons without the prior + written consent of Creative Commons. Any permitted use will be in + compliance with Creative Commons' then-current trademark usage + guidelines, as may be published on its website or otherwise made + available upon request from time to time. For the avoidance of doubt, + this trademark restriction does not form part of the License. + + Creative Commons may be contacted at https://creativecommons.org/. diff --git a/jabref/licenses/org.jsoup_jsoup.txt b/jabref/licenses/org.jsoup_jsoup.txt new file mode 100644 index 00000000..a72d2b72 --- /dev/null +++ b/jabref/licenses/org.jsoup_jsoup.txt @@ -0,0 +1,21 @@ +The MIT License + +© 2009-2015, Jonathan Hedley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/jabref/licenses/org.postgresql_postgresql.txt b/jabref/licenses/org.postgresql_postgresql.txt new file mode 100644 index 00000000..fd416d2e --- /dev/null +++ b/jabref/licenses/org.postgresql_postgresql.txt @@ -0,0 +1,26 @@ +Copyright (c) 1997-2011, PostgreSQL Global Development Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the PostgreSQL Global Development Group nor the names + of its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/jabref/lychee.toml b/jabref/lychee.toml new file mode 100644 index 00000000..ffc6ffe8 --- /dev/null +++ b/jabref/lychee.toml @@ -0,0 +1 @@ +exclude_path = ["build/", "buildres/abbrv.jabref.org", "out/", "src/main/resources/csl-locales", "src/main/resources/csl-styles"] diff --git a/jabref/mkdocs.yml b/jabref/mkdocs.yml new file mode 100644 index 00000000..81dc22cc --- /dev/null +++ b/jabref/mkdocs.yml @@ -0,0 +1,6 @@ +site_name: JabRef Developer Documentation +repo_url: https://github.com/jabref/jabref/ +theme: + name: readthedocs +extra_css: + - mkdocs-custom.css diff --git a/jabref/rewrite.yml b/jabref/rewrite.yml new file mode 100644 index 00000000..ecaf9b7b --- /dev/null +++ b/jabref/rewrite.yml @@ -0,0 +1,215 @@ +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.jabref.config.rewrite.cleanup +recipeList: + # generated by extracting class names out of https://github.com/openrewrite/rewrite-static-analysis + # + # 1. git clone https://github.com/openrewrite/rewrite-static-analysis.git + # 2. cd rewrite-static-analysis/src/main/java/org/openrewrite/staticanalysis + # 3. find . -type f -name "*.java" -exec basename {} .java \; | awk '{print " - org.openrewrite.staticanalysis." $1}' | grep -v "Visitor" | grep -v "package-info" + # + # Then, following to are removed: + # Sometimes fails to produce correct results + # - org.openrewrite.staticanalysis.FinalizePrivateFields + # Creates constructor at the end of the class which violates JabRef's code style + # - org.openrewrite.staticanalysis.HideUtilityClassConstructor + # We are not relying on serialization, thus we do not want to maintain the serialVersionUID manually + # - org.openrewrite.staticanalysis.AddSerialVersionUidToSerializable + # Leads to "if (Boolean.TRUE.equals(proxyPrefs.shouldUseProxy())) {", which reads strange + # - org.openrewrite.staticanalysis.AvoidBoxedBooleanExpressions + # Leads to exception + # - org.openrewrite.staticanalysis.CombineSemanticallyEqualCatchBlocks + # Needs parameters + # - org.openrewrite.staticanalysis.DeclarationSiteTypeVariance + # Unreadable code + # .ifPresent((Path selectedDirectory) -> { + # - org.openrewrite.staticanalysis.ExplicitLambdaArgumentTypes + # Unreable code (e.g., public final class BindingsHelper) + # - org.openrewrite.staticanalysis.FinalClass + # Unreable code + # - org.openrewrite.staticanalysis.FinalizeLocalVariables + # Unreadable code + # - org.openrewrite.staticanalysis.FinalizeMethodArguments + # Not sure about the consequences (LibraryTab). + # - org.openrewrite.staticanalysis.FixStringFormatExpressions + # Leads to unreadable code (this.shouldAutoComplete.set(shouldAutoComplete1);) + # - org.openrewrite.staticanalysis.HiddenField + # Leads to unreadable code + # - org.openrewrite.staticanalysis.InlineVariable + # Leads to unreadable code + # - org.openrewrite.staticanalysis.LambdaBlockToExpression + # Works on case-senstive file systems only + # - org.openrewrite.staticanalysis.LowercasePackage + # Needs manual intervention (not fixable at existing code) + # - org.openrewrite.staticanalysis.MethodNameCasing + # Needs manual intervention (not fixable at existing code) + # - org.openrewrite.staticanalysis.MinimumSwitchCases + # Leads to unreadable code + # - org.openrewrite.staticanalysis.NoFinalizedLocalVariables + # Contradicts JabRef's checkstyle + # - org.openrewrite.staticanalysis.NoWhitespaceAfter + # We sometimes need == instead of equals + # - org.openrewrite.staticanalysis.ReferentialEqualityToObjectEquals + # AuthorListTest uses System.gc + # - org.openrewrite.staticanalysis.RemoveCallsToSystemGc + # We need that for our CLI + # - org.openrewrite.staticanalysis.RemoveSystemOutPrintln + # Removes a field - and that wrong + # - org.openrewrite.staticanalysis.RemoveUnusedPrivateMethods + # Crashes + # - org.openrewrite.staticanalysis.RenameExceptionInEmptyCatch + # Also renames private static final varialbes (e.g., LOGGER, ALL_ENTRIES_GROUP_DEFAULT_ICON, ...) + # - org.openrewrite.staticanalysis.RenamePrivateFieldsToCamelCase + # Does something else (deleting a field in TreeNode) + # - org.openrewrite.staticanalysis.ReplaceDeprecatedRuntimeExecMethods + # Leads to compile errors + # - org.openrewrite.staticanalysis.ReplaceDuplicateStringLiterals + # Not everything correctly transformed + # - org.openrewrite.staticanalysis.ReplaceLambdaWithMethodReference + # Crashes + # - org.openrewrite.staticanalysis.ReplaceStackWithDeque + # We want to keep JDK16 (and not downgrade to JDK11) + # - org.openrewrite.staticanalysis.ReplaceStreamToListWithCollect + # Changes one item only + # - org.openrewrite.staticanalysis.ReplaceStringBuilderWithString + # We like text blocks + # - org.openrewrite.staticanalysis.ReplaceTextBlockWithString + # Hangs + # - org.openrewrite.staticanalysis.ReplaceValidateNotNullHavingVarargsWithObjectsRequireNonNull + # We need that in OOBibStyleTest + # - org.openrewrite.staticanalysis.SimplifyConstantIfBranchExecution + + # We log and wrap the exceptions + # - org.openrewrite.staticanalysis.UnnecessaryCatch + # Does not work + # - org.openrewrite.staticanalysis.UnnecessaryCloseInTryWithResources + # Does not work + # - org.openrewrite.staticanalysis.UnnecessaryExplicitTypeArguments + - org.openrewrite.staticanalysis.UnnecessaryParentheses + # Does not work + # - org.openrewrite.staticanalysis.UnnecessaryPrimitiveAnnotations + # Removes too much throws + # - org.openrewrite.staticanalysis.UnnecessaryThrows + # Crashes + # - org.openrewrite.staticanalysis.UseAsBuilder + # "Upgrades" too much, deletes fields + # - org.openrewrite.staticanalysis.UseCollectionInterfaces + # States that it gains performance, but the code is more unreable + # - org.openrewrite.staticanalysis.UseForEachRemoveInsteadOfSetRemoveAll + # We voted against it + # - org.openrewrite.staticanalysis.ExplicitInitialization + + - org.openrewrite.java.migrate.io.ReplaceFileInOrOutputStreamFinalizeWithClose + - org.openrewrite.java.migrate.net.JavaNetAPIs + - org.openrewrite.java.migrate.net.URLConstructorsToURIRecipes + - org.openrewrite.java.migrate.util.OptionalNotEmptyToIsPresent + - org.openrewrite.java.migrate.util.OptionalNotPresentToIsEmpty + - org.openrewrite.java.migrate.util.SequencedCollection + - org.openrewrite.java.migrate.lang.StringFormatted + + - org.openrewrite.java.RemoveObjectsIsNull + - org.openrewrite.java.ShortenFullyQualifiedTypeReferences + + - org.openrewrite.java.testing.junit5.AssertTrueInstanceofToAssertInstanceOf + - org.openrewrite.java.testing.junit5.RemoveTryCatchFailBlocks + + # needs another openrewrite dependency + # - org.openrewrite.okhttp.ReorderRequestBodyCreateArguments + + - org.openrewrite.staticanalysis.AtomicPrimitiveEqualsUsesGet + - org.openrewrite.staticanalysis.BigDecimalRoundingConstantsToEnums + - org.openrewrite.staticanalysis.BooleanChecksNotInverted + - org.openrewrite.staticanalysis.CaseInsensitiveComparisonsDoNotChangeCase + - org.openrewrite.staticanalysis.CatchClauseOnlyRethrows + - org.openrewrite.staticanalysis.ChainStringBuilderAppendCalls + # Might need manual intervention, because negations are not treated properly + - org.openrewrite.staticanalysis.CompareEnumsWithEqualityOperator + - org.openrewrite.staticanalysis.ControlFlowIndentation + - org.openrewrite.staticanalysis.CovariantEquals + # Needs manual intervention +# - org.openrewrite.staticanalysis.DefaultComesLast + - org.openrewrite.staticanalysis.EmptyBlock + - org.openrewrite.staticanalysis.EqualsAvoidsNull + - org.openrewrite.staticanalysis.EqualsToContentEquals + # Needs manual intervention +# - org.openrewrite.staticanalysis.ExplicitCharsetOnStringGetBytes + - org.openrewrite.staticanalysis.ExternalizableHasNoArgsConstructor + - org.openrewrite.staticanalysis.FallThrough +# - org.openrewrite.staticanalysis.ForLoopControlVariablePostfixOperators + - org.openrewrite.staticanalysis.ForLoopIncrementInUpdate + - org.openrewrite.staticanalysis.IndexOfChecksShouldUseAStartPosition + - org.openrewrite.staticanalysis.IndexOfReplaceableByContains +# - org.openrewrite.staticanalysis.IndexOfShouldNotCompareGreaterThanZero + - org.openrewrite.staticanalysis.InstanceOfPatternMatch + - org.openrewrite.staticanalysis.IsEmptyCallOnCollections +# - org.openrewrite.staticanalysis.MissingOverrideAnnotation +# - org.openrewrite.staticanalysis.ModifierOrder + - org.openrewrite.staticanalysis.MultipleVariableDeclarations + - org.openrewrite.staticanalysis.NeedBraces + - org.openrewrite.staticanalysis.NestedEnumsAreNotStatic + - org.openrewrite.staticanalysis.NewStringBuilderBufferWithCharArgument + - org.openrewrite.staticanalysis.NoDoubleBraceInitialization +# - org.openrewrite.staticanalysis.NoEmptyCollectionWithRawType + - org.openrewrite.staticanalysis.NoEqualityInForCondition + - org.openrewrite.staticanalysis.NoFinalizer +# - org.openrewrite.staticanalysis.NoPrimitiveWrappersForToStringOrCompareTo +# - org.openrewrite.staticanalysis.NoRedundantJumpStatements + - org.openrewrite.staticanalysis.NoToStringOnStringType + - org.openrewrite.staticanalysis.NoValueOfOnStringType +# - org.openrewrite.staticanalysis.NoWhitespaceBefore + - org.openrewrite.staticanalysis.ObjectFinalizeCallsSuper +# - org.openrewrite.staticanalysis.OperatorWrap + - org.openrewrite.staticanalysis.PrimitiveWrapperClassConstructorToValueOf + - org.openrewrite.staticanalysis.RedundantFileCreation + - org.openrewrite.staticanalysis.RemoveCallsToObjectFinalize + - org.openrewrite.staticanalysis.RemoveEmptyJavaDocParameters + - org.openrewrite.staticanalysis.RemoveExtraSemicolons + - org.openrewrite.staticanalysis.RemoveJavaDocAuthorTag + - org.openrewrite.staticanalysis.RemoveHashCodeCallsFromArrayInstances +# - org.openrewrite.staticanalysis.RemoveRedundantTypeCast + - org.openrewrite.staticanalysis.RemoveToStringCallsFromArrayInstances + - org.openrewrite.staticanalysis.RemoveUnneededAssertion + - org.openrewrite.staticanalysis.RemoveUnneededBlock +# - org.openrewrite.staticanalysis.RemoveUnusedLocalVariables +# - org.openrewrite.staticanalysis.RemoveUnusedPrivateFields +# - org.openrewrite.staticanalysis.RenameLocalVariablesToCamelCase + - org.openrewrite.staticanalysis.RenameMethodsNamedHashcodeEqualOrToString + - org.openrewrite.staticanalysis.ReplaceRedundantFormatWithPrintf + - org.openrewrite.staticanalysis.ReplaceStringBuilderWithString + - org.openrewrite.staticanalysis.ReplaceWeekYearWithYear +# - org.openrewrite.staticanalysis.ShortenFullyQualifiedTypeReferences +# - org.openrewrite.staticanalysis.SimplifyConsecutiveAssignments +# - org.openrewrite.staticanalysis.SimplifyCompoundStatement + - org.openrewrite.staticanalysis.SimplifyBooleanExpression + - org.openrewrite.staticanalysis.SimplifyBooleanReturn + - org.openrewrite.staticanalysis.SimplifyDurationCreationUnits + - org.openrewrite.staticanalysis.SortedSetStreamToLinkedHashSet + - org.openrewrite.staticanalysis.StaticMethodNotFinal + - org.openrewrite.staticanalysis.StringLiteralEquality + - org.openrewrite.staticanalysis.TypecastParenPad + - org.openrewrite.staticanalysis.UnwrapRepeatableAnnotations + - org.openrewrite.staticanalysis.UpperCaseLiteralSuffixes + - org.openrewrite.staticanalysis.UseDiamondOperator +# - org.openrewrite.staticanalysis.UseJavaStyleArrayDeclarations + - org.openrewrite.staticanalysis.UseLambdaForFunctionalInterface +# - org.openrewrite.staticanalysis.UseListSort + - org.openrewrite.staticanalysis.UseObjectNotifyAll + - org.openrewrite.staticanalysis.UseStandardCharset + - org.openrewrite.staticanalysis.UseStringReplace + - org.openrewrite.staticanalysis.UseSystemLineSeparator + - org.openrewrite.staticanalysis.WhileInsteadOfFor +# - org.openrewrite.staticanalysis.WriteOctalValuesAsDecimal + + # - org.openrewrite.java.testing.junit5.JUnit5BestPractices -- cannot be used directly, because one recipe is not working (see below) + - org.openrewrite.java.testing.cleanup.AssertLiteralBooleanToFailRecipe + - org.openrewrite.java.testing.cleanup.RemoveTestPrefix + # - org.openrewrite.java.testing.cleanup.TestsShouldNotBePublic -- does not work due to https://github.com/openrewrite/rewrite-testing-frameworks/issues/458 + - org.openrewrite.java.testing.junit5.CleanupAssertions + - org.openrewrite.java.testing.junit5.AddParameterizedTestAnnotation + - org.openrewrite.java.testing.junit5.RemoveDuplicateTestTemplates + - org.openrewrite.java.testing.junit5.RemoveTryCatchFailBlocks + - org.openrewrite.java.testing.junit5.LifecycleNonPrivate + - org.openrewrite.java.testing.junit5.StaticImports + + # Logging + - org.openrewrite.java.logging.slf4j.Slf4jBestPractices diff --git a/jabref/scripts/.gitignore b/jabref/scripts/.gitignore new file mode 100644 index 00000000..236350bb --- /dev/null +++ b/jabref/scripts/.gitignore @@ -0,0 +1,5 @@ +*.bib + +# python +*.pyc +*$py.class diff --git a/jabref/scripts/after-failure.sh b/jabref/scripts/after-failure.sh new file mode 100644 index 00000000..4770f6c7 --- /dev/null +++ b/jabref/scripts/after-failure.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# based on https://github.com/lhotari/travis-gradle-test-failures-to-console/blob/master/travis/junit-errors-to-stdout.sh +IFS=' +' +DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +ROOTDIR="$1" +if [ -z "$ROOTDIR" ]; then + ROOTDIR="." +fi +echo 'Formatting results...' +FILES=$(find "$ROOTDIR" -path '*/build/test-results/*.xml' | xargs --no-run-if-empty xml_grep --files --cond 'testsuite[@failures > 0 or @errors > 0]') +if [ -n "$FILES" ]; then + for file in $FILES; do + echo "Formatting $file" + if [ -f "$file" ]; then + echo '=====================================================' + xsltproc "$DIR/junit-xml-format-errors.xsl" "$file" + fi + done + echo '=====================================================' +else + echo 'No */build/test-results/*.xml files found with failing tests.' +fi diff --git a/jabref/scripts/bib-file-generator.py b/jabref/scripts/bib-file-generator.py new file mode 100644 index 00000000..a924339c --- /dev/null +++ b/jabref/scripts/bib-file-generator.py @@ -0,0 +1,45 @@ +# Creates "large-library.bib" with 100k entries. + +# The file is written in UTF8 and makes use of the unicode character U+0304 (https://www.compart.com/en/unicode/U+0304) +# to create an overline on large roman numbers using the technicue "Vinculum" (https://en.wikipedia.org/wiki/Roman_numerals#Vinculum). +# The numbers are used in the journal title. + +# For pseudonymization BibTeX files, org.jabref.logic.pseudonymization.PseudonymizationTest#pseudonymizeLibraryFiley can be used. + +number_of_entries = 100_000 + +# Adapted from: https://stackoverflow.com/a/50012689/873282 +def int_to_roman(num): + _values = [ + 1000000, 900000, 500000, 400000, 100000, 90000, 50000, 40000, 10000, 9000, 5000, 4000, 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] + + _strings = [ + 'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] + + result = "" + decimal = num + + while decimal > 0: + for i in range(len(_values)): + if decimal >= _values[i]: + if _values[i] > 1000: + result += u'\u0304'.join(list(_strings[i])) + u'\u0304' + else: + result += _strings[i] + decimal -= _values[i] + break + return result + +with open("generated-large-library.bib", 'w', encoding='utf-8') as file: + for i in range(1, number_of_entries + 1): + year = 1900 + (i - 1) % (2025 - 1900) + entry = f"""@article{{id{i:06d}, + title = {{This is my title{i}}}, + author = {{FirstnameA{i} LastnameA{i} and FirstnameB{i} LastnameB{i} and FirstnameC{i} LastnameC{i}}}, + journal = {{Journal Title {int_to_roman(i)}}}, + volume = {{{i}}}, + year = {{{year}}}, +}} + +""" + file.write(entry) diff --git a/jabref/scripts/generate_toc.rb b/jabref/scripts/generate_toc.rb new file mode 100644 index 00000000..f231ea8a --- /dev/null +++ b/jabref/scripts/generate_toc.rb @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +# Source: https://gist.github.com/albertodebortoli/9310424 +# Via: http://stackoverflow.com/a/22131019/873282 + +File.open("Code-Howtos.md", 'r') do |f| + f.each_line do |line| + forbidden_words = ['Table of contents', 'define', 'pragma'] + next if !line.start_with?("#") || forbidden_words.any? { |w| line =~ /#{w}/ } + + title = line.gsub("#", "").strip + href = title.gsub(" ", "-").gsub('"',"").downcase + puts " " * (line.count("#")-1) + "* [#{title}](\##{href})" + end +end diff --git a/jabref/scripts/junit-xml-format-errors.xsl b/jabref/scripts/junit-xml-format-errors.xsl new file mode 100644 index 00000000..614c7748 --- /dev/null +++ b/jabref/scripts/junit-xml-format-errors.xsl @@ -0,0 +1,61 @@ + + + + + + Testsuite: + +Tests run: + + , Failures: + + , Errors: + + , Time elapsed: + + sec + +--------- ----------- --------- + + + + + + + +Testcase: + + took + + + FAILURE + ERROR + SUCCESS + + + + + + + + + + + + + + + +------ Standard output ------ + + + + + + +------ Error output ------ + + + + + diff --git a/jabref/scripts/logger.py b/jabref/scripts/logger.py new file mode 100644 index 00000000..6226645e --- /dev/null +++ b/jabref/scripts/logger.py @@ -0,0 +1,29 @@ +def enum(**enums): + return type('Enum', (), enums) + + +OUTPUT_COLORS = enum( + OK='\033[0;32m', + WARN='\033[0;33m', + ERROR='\033[0;31m', + ENDC='\033[0;38m' +) + + +def error(content): + print u"{color_error}{content}{color_end}".encode('utf8') \ + .format(color_error=OUTPUT_COLORS.ERROR, content=str(content.encode('utf8')), color_end=OUTPUT_COLORS.ENDC) + + +def warn(content): + print u"{color_error}{content}{color_end}".encode('utf8') \ + .format(color_error=OUTPUT_COLORS.WARN, content=str(content.encode('utf8')), color_end=OUTPUT_COLORS.ENDC).encode('utf8') + + +def ok(content): + print u"{color_error}{content}{color_end}".encode('utf8') \ + .format(color_error=OUTPUT_COLORS.OK, content=str(content.encode('utf8')), color_end=OUTPUT_COLORS.ENDC).encode('utf8') + + +def neutral(content): + print content.encode('utf8') diff --git a/jabref/scripts/vms/.gitignore b/jabref/scripts/vms/.gitignore new file mode 100644 index 00000000..e915ab04 --- /dev/null +++ b/jabref/scripts/vms/.gitignore @@ -0,0 +1,2 @@ +**/.vagrant +**/vagrant-ssh diff --git a/jabref/scripts/vms/README.md b/jabref/scripts/vms/README.md new file mode 100644 index 00000000..a25e8b9d --- /dev/null +++ b/jabref/scripts/vms/README.md @@ -0,0 +1,83 @@ +# Virtual Machines for testing JabRef + +This folder contains directories making use of [Vagrant](https://www.vagrantup.com/) to install virtual machines on [VirtualBox](https://www.virtualbox.org/) + +## Usage + +### Prerequisites + +1. [Install VirtualBox](https://www.virtualbox.org/wiki/Downloads) + - Windows: `winget install -e --id Oracle.VirtualBox` +2. [Install Vagrant](https://developer.hashicorp.com/vagrant/install?product_intent=vagrant) + - Windows: `winget install -e --id Hashicorp.Vagrant` +3. [Install Vagrant Virtual Box Guest Additions Plugin](https://subscription.packtpub.com/book/cloud-and-networking/9781786464910/1/ch01lvl1sec12/enabling-virtualbox-guest-additions-in-vagrant). + This helps ensururing that the guest additions of each box are automatically updated. + - `vagrant plugin install vagrant-vbguest` + +### Setup VM + +1. `cd` into `{vmdir}`, e..g, `cd ubuntu` +2. Start the vm `vagrant up` +3. Restart the vm +4. Linux virtual machines: Store ssh configuration: `vagrant ssh-config > vagrant-ssh` + +### Use VM + +You can use the UI offered by the VirtualBox client. +On Linux, you can also do `ssh -Y -F vagrant-ssh default` to SSH into the machine. + +If asked for a password, this is `vagrant`. + +#### Multiple Screens + +One can configure mulitple screen as the virtual machine settings of Virtual Box: +Navigate to "Display" (1), set "Video Memory" to the maximum value (2), and then adjust the number of displays (3). + +![Virtual Box settings for multiple screens at VM settings](virtualbox-multiple-screens-1.png) + +In case you want to have a fixed resoltuion, one can pin the screen resolution as follows: +During running of Virutal Box, +(1) unselect "Auto-size Guest Display" and +(2) adjust the screen resolution to full HD + +![Virtual Box settings for multiple screens at running VM](virtualbox-multiple-screens-1.png) + +### Remove VM + +Execute `vagrant destroy`. +Then, everything is removed. + +## Available VMs + +| VM | JabRef | Browser | LibreOffice | IntelliJ | +|-------------------------------------------------|---------|---------|-------------| --------- | +| [`Debian 12`](debian-12/) | source | Firefox | -- | yes | +| [`fedora`](fedora/) | source | -- | -- | -- | +| [`Linux Mint (Cinnamon)`](linux-mint-cinnamon/) | source | Firefox | yes | -- | +| [`ubuntu`](ubuntu/) | snap | Firefox | yes | -- | +| [`windows 10`](windows10/) | source | Edge | -- | -- | +| [`windows 11`](windows11/) | source | Edge | -- | -- | + +## Troubleshooting + +> VBoxManage.exe: error: Could not rename the directory '`C:\Users\$username\VirtualBox VMs\output-ubuntu_source_1720167378145_42641_1720548095320_67904`' to '`C:\Users\$username\VirtualBox VMs\jabref-ubuntu`' to save the settings file (`VERR_ALREADY_EXISTS`) + +Solution: Delete folder `C:\Users\$username\VirtualBox VMs\jabref-ubuntu` + +> How to use another JabRef snap image? + +Solution: `snap refresh --edge jabref` (or `--stable`, ...). +More info on snaps is available at . + +> `An error occurred during installation of VirtualBox Guest Additions 7.0.20. Some functionality may not work as intended.` + +Install the guest additions manually by "Devices" > "Install Virtual Box Gueat Additions". + +> I get a strange installation error. + +Look at . + +> How can I update the guest additions? + +They should be automatically updated during a reboot. +A manual update can be triggered by `vagrant vbguest --do install`. diff --git a/jabref/scripts/vms/debian-12/README.md b/jabref/scripts/vms/debian-12/README.md new file mode 100644 index 00000000..0b347535 --- /dev/null +++ b/jabref/scripts/vms/debian-12/README.md @@ -0,0 +1,21 @@ +# Debian 12 + +Uses . + +Reproducer for . + +Installs [Just Perfection GNOME Shell Extension](https://gitlab.gnome.org/jrahmatzadeh/just-perfection). + +After `vagrant up`: + +1. Terminate the VM. +2. Open settings of the VM. +3. Reconfigure the Virtual Box display to "VMSVGA", "Enable 3D Acceleration", use 32 MB of Video RAM. +4. Power on. +5. Log in. + +Then, start JabRef by following steps: + +1. Open termminal +2. `cd jabref` +3. `./gradlew run` diff --git a/jabref/scripts/vms/debian-12/Vagrantfile b/jabref/scripts/vms/debian-12/Vagrantfile new file mode 100644 index 00000000..f500ee77 --- /dev/null +++ b/jabref/scripts/vms/debian-12/Vagrantfile @@ -0,0 +1,59 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +NAME = "jabref-debian-12" + +Vagrant.configure("2") do |config| + + config.vm.box = "alvistack/debian-12" + + config.vm.define NAME + config.vm.hostname = NAME + + config.vm.provider "virtualbox" do |v| + v.name = NAME + v.gui = true + v.customize ["modifyvm", :id, "--memory", "2048", "--cpus", "2"] + end + + config.vm.provision "shell", inline: <<-SHELL + sudo apt-get update + sudo apt-get -y upgrade + + # enable building of the virtual box guest addons + # hint from https://forums.virtualbox.org/viewtopic.php?p=508680&sid=fe86fb0f6c03c7443f1fba9a5c29a861#p508680 + # sudo apt install -y build-essential dkms + + sudo apt-get install -y git + + # sudo apt-get install -y task-cinnamon-desktop + + # install IntelliJ Community Edition - source: https://snapcraft.io/install/intellij-idea-community/debian + sudo rm /etc/apt/preferences.d/nosnap.pref # source: https://stackoverflow.com/a/77235743/873282 + sudo apt-get install -y snapd + sudo snap install snapd + sudo snap install intellij-idea-community --classic + SHELL + + config.vm.provision "shell", privileged: false, inline: <<-SHELL + # Install "Just Perfection GNOME Shell Extension" + cd /tmp + wget https://extensions.gnome.org/extension-data/just-perfection-desktopjust-perfection.v26.shell-extension.zip -O shell-extension.zip + gnome-extensions install --force shell-extension.zip + + # Install JDK, clone JabRef's source code, and do an initial build + curl -s "https://get.sdkman.io" | bash + source "$HOME/.sdkman/bin/sdkman-init.sh" + sdk install java 21.0.4-tem < /dev/null + + cd ~ + git clone --recurse-submodules https://github.com/JabRef/jabref.git + cd jabref + sdk use java 21.0.4-tem + ./gradlew jar + SHELL + + config.vm.provision "shell", inline: "sudo reboot" + + config.ssh.forward_x11 = true +end diff --git a/jabref/scripts/vms/fedora/README.md b/jabref/scripts/vms/fedora/README.md new file mode 100644 index 00000000..19a251ec --- /dev/null +++ b/jabref/scripts/vms/fedora/README.md @@ -0,0 +1,6 @@ +# Fedora VM + +Fedora 39 with KDE plasma and JDK. +During the build, the JabRef sources will be fetched and an initial build will be triggered. + +Login and then type `startx`. Now KDE Plasma should start. Open Konsole. Then `cd jabref`. Then `./gradlew run`. diff --git a/jabref/scripts/vms/fedora/Vagrantfile b/jabref/scripts/vms/fedora/Vagrantfile new file mode 100644 index 00000000..d49f35a3 --- /dev/null +++ b/jabref/scripts/vms/fedora/Vagrantfile @@ -0,0 +1,40 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +NAME = "jabref-fedora-39" + +Vagrant.configure("2") do |config| + + config.vm.box = "fedora/39-cloud-base" + + config.vm.define NAME + config.vm.hostname = NAME + + config.vm.provider "virtualbox" do |v| + v.name = NAME + v.gui = true + v.customize ["modifyvm", :id, "--memory", "4096", "--cpus", "2"] + end + + config.vm.provision "shell", inline: <<-SHELL + sudo dnf update -y + sudo dnf install -y @kde-desktop-environment + + # Following X11 packages are NOT required even the clipboard functionality seems to ask for (cf. https://github.com/JabRef/jabref/issues/11464) + # sudo dnf install -y kwin-x11 plasma-workspace-x11 + + # We need exactly the java version required by JabRef. Auto download does not work on Fedora. + sudo dnf install -y git java-21-openjdk-devel.x86_64 + + sudo systemctl set-default graphical.target + SHELL + + config.vm.provision "shell", privileged:false, inline: <<-SHELL + git clone --recurse-submodules https://github.com/JabRef/jabref.git + cd jabref && ./gradlew jar || true + SHELL + + config.vm.provision "shell", inline: "sudo reboot" + + config.ssh.forward_x11 = true +end diff --git a/jabref/scripts/vms/linux-mint-cinnamon/README.md b/jabref/scripts/vms/linux-mint-cinnamon/README.md new file mode 100644 index 00000000..5518a6e1 --- /dev/null +++ b/jabref/scripts/vms/linux-mint-cinnamon/README.md @@ -0,0 +1,34 @@ +# Linux Mint Cinnamon VM + +[Linux Mint](https://linuxmint.com/) with JabRef sources. + +Uses . + +Start JabRef by following steps: + +1. Open termminal +2. `cd jabref` +3. `./gradlew run` + +## Alternatives + +### Using `archman/linuxmint` + +This image does not work with multiple monitors. + +Issues + +- [VirtualBox: Mouse pointer offset](https://forums.linuxmint.com/viewtopic.php?t=427855) +- [VirtualBox 3D acceleration: blank screen](https://forums.linuxmint.com/viewtopic.php?t=427853) + +### Building an own image + +We could have build our own image. +First creating an image using packer with . +Then, building a `Vagrantfile` on top of it. +Seemed to be too much issues for the users. + +1. Install packer +2. `packer plugins install github.com/hashicorp/virtualbox` +3. `packer plugins install github.com/hashicorp/vagrant` +4. `packer build -var-file=mint-cinnamon-22.json core_template.json` diff --git a/jabref/scripts/vms/linux-mint-cinnamon/Vagrantfile b/jabref/scripts/vms/linux-mint-cinnamon/Vagrantfile new file mode 100644 index 00000000..b54bbe86 --- /dev/null +++ b/jabref/scripts/vms/linux-mint-cinnamon/Vagrantfile @@ -0,0 +1,49 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +NAME = "jabref-linux-mint-cinnamon" + +Vagrant.configure("2") do |config| + + # https://portal.cloud.hashicorp.com/vagrant/discover/aaronvonawesome/linux-mint-21-cinnamon + config.vm.box = "aaronvonawesome/linux-mint-21-cinnamon" + # config.vm.box = "archman/linuxmint" # v22 + + config.vm.define NAME + config.vm.hostname = NAME + + config.vm.provider "virtualbox" do |v| + v.name = NAME + v.gui = true + v.customize ["modifyvm", :id, "--memory", "2048", "--cpus", "2"] + end + + config.vm.provision "shell", inline: <<-SHELL + sudo apt-get update + sudo apt-get -y upgrade + + # enable building of the virtual box guest addons + # hint from https://forums.virtualbox.org/viewtopic.php?p=508680&sid=fe86fb0f6c03c7443f1fba9a5c29a861#p508680 + sudo apt install -y build-essential dkms + + # switch kernel - hint by https://askubuntu.com/a/1521872/196423 + sudo apt install -y linux-oem-22.04 linux-tools-oem-22.04 + + sudo apt-get install -y git + SHELL + + # Install JDK, clone JabRef's source code, and do an initial build + config.vm.provision "shell", privileged: false, inline: <<-SHELL + curl -s "https://get.sdkman.io" | bash + source "$HOME/.sdkman/bin/sdkman-init.sh" + sdk install java 21.0.4-tem < /dev/null + git clone --recurse-submodules https://github.com/JabRef/jabref.git + cd jabref + sdk use java 21.0.4-tem + ./gradlew jar + SHELL + + config.vm.provision "shell", inline: "sudo reboot" + + config.ssh.forward_x11 = true +end diff --git a/jabref/scripts/vms/ubuntu/README.md b/jabref/scripts/vms/ubuntu/README.md new file mode 100644 index 00000000..82285163 --- /dev/null +++ b/jabref/scripts/vms/ubuntu/README.md @@ -0,0 +1,5 @@ +# Ubuntu VM + +Ubuntu with JabRef snap and libreoffice-connection pre-installed. + +One has to install the [JabRef Browser Extension](https://addons.mozilla.org/en-US/firefox/addon/jabref/) manually. diff --git a/jabref/scripts/vms/ubuntu/Vagrantfile b/jabref/scripts/vms/ubuntu/Vagrantfile new file mode 100644 index 00000000..038e97a3 --- /dev/null +++ b/jabref/scripts/vms/ubuntu/Vagrantfile @@ -0,0 +1,46 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +NAME = "jabref-ubuntu-24.04" + +Vagrant.configure("2") do |config| + + # config.vm.box = "ubuntu/trusty64" + # config.vm.box = "ubuntu/jammy64" + # config.vm.box = "alvistack/ubuntu-24.04" + + # https://app.vagrantup.com/caspermeijn/boxes/ubuntu-desktop-24.04 + # GitHub: https://github.com/caspermeijn/vagrant-ubuntu-desktop/tree/main/ubuntu-desktop-24.04 + # Here, the ubuntu-desktop works without usses + config.vm.box = "caspermeijn/ubuntu-desktop-24.04" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "C:/TEMP/JabRef", "/tmp/jabref" + + config.vm.define NAME + config.vm.hostname = NAME + + config.vm.provider "virtualbox" do |v| + v.name = NAME + v.gui = true + v.customize ["modifyvm", :id, "--memory", "2048", "--cpus", "2"] + end + + config.vm.provision "shell", inline: <<-SHELL + # Update package index + sudo apt-get update + sudo apt-get upgrade -y + + # Install latest development build of JabRef + sudo snap install --edge jabref + + sudo apt-get install -y libreoffice-java-common + sudo mkdir -p /usr/lib/mozilla/native-messaging-hosts + snap connect jabref:hostfs-mozilla-native-messaging-jabref + SHELL + + config.ssh.forward_x11 = true +end diff --git a/jabref/scripts/vms/virtualbox-multiple-screens-1.png b/jabref/scripts/vms/virtualbox-multiple-screens-1.png new file mode 100644 index 00000000..1811e941 Binary files /dev/null and b/jabref/scripts/vms/virtualbox-multiple-screens-1.png differ diff --git a/jabref/scripts/vms/virtualbox-multiple-screens-2.png b/jabref/scripts/vms/virtualbox-multiple-screens-2.png new file mode 100644 index 00000000..e85ec48d Binary files /dev/null and b/jabref/scripts/vms/virtualbox-multiple-screens-2.png differ diff --git a/jabref/scripts/vms/windows10/README.md b/jabref/scripts/vms/windows10/README.md new file mode 100644 index 00000000..a9efe6fa --- /dev/null +++ b/jabref/scripts/vms/windows10/README.md @@ -0,0 +1,49 @@ +# Windows 10 VM + +A Windows-based VM to test JabRef. +As user, you need to ensure to have the proper Windows license to use this VM. + +In case you have many CPU cores, you can adapt `vb.cpus` in `Vagrantfile` to a higher number. + +One has to install the [JabRef Browser Extension](https://addons.mozilla.org/en-US/firefox/addon/jabref/) manually. + +## Troubleshooting + +### "Waiting for machine to reboot..." + +In case Vagrant reports "Waiting for machine to reboot..." and nothing happens, one has to "power off" the machine, execute `vagrant destory`, and then run `vagrant up` again. + +### `fatal: early EOF` + +```console +jabref-windows-sandbox: Cloning into 'jabref'... +jabref-windows-sandbox: error: RPC failed; curl 92 HTTP/2 stream 5 was not closed cleanly: CANCEL (err 8) +jabref-windows-sandbox: error: 6846 bytes of body are still expected +jabref-windows-sandbox: fetch-pack: unexpected disconnect while reading sideband packet +jabref-windows-sandbox: fatal: early EOF +jabref-windows-sandbox: fatal: fetch-pack: invalid index-pack output +``` + +The `git clone` command did not work. + +Login, open `cmd` and then execute following commands: + +```cmd +git clone --recurse-submodules https://github.com/JabRef/jabref.git +cd jabref +gradlew run +``` + +## Background + +`Vagrantfile` is based on [SeisoLLC/windows-sandbox](https://github.com/SeisoLLC/windows-sandbox/tree/main). + +The most use image seems to be the [Windows 10 image by `gusztavvargadr`](https://portal.cloud.hashicorp.com/vagrant/discover/gusztavvargadr/windows-10). +List of all images at . + +[Chocolatey](https://chocolatey.org/) is used instead of [winget-cli](https://learn.microsoft.com/en-us/windows/package-manager/), because Chocolatey installation does not hit GitHub's rate limits during unattended installation. + +## Atlernatives + +- Atlernative Vagrant images: . +- [Windows Sandbox](https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/windows-sandbox/windows-sandbox-overview) diff --git a/jabref/scripts/vms/windows10/Vagrantfile b/jabref/scripts/vms/windows10/Vagrantfile new file mode 100644 index 00000000..8ccefd51 --- /dev/null +++ b/jabref/scripts/vms/windows10/Vagrantfile @@ -0,0 +1,46 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +NAME = "jabref-windows-10-sandbox" + +Vagrant.configure("2") do |config| + config.vm.box = "gusztavvargadr/windows-10" + config.vm.box_url = "https://app.vagrantup.com/gusztavvargadr/boxes/windows-10" + + config.vm.define NAME + config.vm.hostname = NAME + + config.vm.provider "virtualbox" do |vb| + vb.name = NAME + + vb.memory = 6000 + vb.cpus = 2 + + vb.customize ['modifyvm', :id, '--clipboard-mode', 'bidirectional'] + vb.gui = true + end + + config.vm.provision "shell", privileged: "true", powershell_elevated_interactive: "true", inline: <<-SHELL + # Install chocolatey + Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + choco feature enable -n=allowGlobalConfirmation + + choco install libericajdk + choco install git.install -y --params "/GitAndUnixToolsOnPath /WindowsTerminal /WindowsTerminalProfile" + + # Required by AI functionaltiy + choco choco install vcredist140 + + # choco install firefox + # choco install libreoffice-fresh + SHELL + + config.vm.provision "shell", reboot: true + + config.vm.provision "shell", privileged: "false", inline: <<-SHELL + cd \\users\\vagrant + git clone --recurse-submodules https://github.com/JabRef/jabref.git + # cd jabref + # .\\gradlew jar + SHELL +end diff --git a/jabref/scripts/vms/windows11/README.md b/jabref/scripts/vms/windows11/README.md new file mode 100644 index 00000000..0a8e1f9a --- /dev/null +++ b/jabref/scripts/vms/windows11/README.md @@ -0,0 +1,49 @@ +# Windows 11 VM + +A Windows-based VM to test JabRef. +As user, you need to ensure to have the proper Windows license to use this VM. + +In case you have many CPU cores, you can adapt `vb.cpus` in `Vagrantfile` to a higher number. + +One has to install the [JabRef Browser Extension](https://addons.mozilla.org/en-US/firefox/addon/jabref/) manually. + +## Troubleshooting + +### "Waiting for machine to reboot..." + +In case Vagrant reports "Waiting for machine to reboot..." and nothing happens, one has to "power off" the machine, execute `vagrant destory`, and then run `vagrant up` again. + +### `fatal: early EOF` + +```console +jabref-windows-sandbox: Cloning into 'jabref'... +jabref-windows-sandbox: error: RPC failed; curl 92 HTTP/2 stream 5 was not closed cleanly: CANCEL (err 8) +jabref-windows-sandbox: error: 6846 bytes of body are still expected +jabref-windows-sandbox: fetch-pack: unexpected disconnect while reading sideband packet +jabref-windows-sandbox: fatal: early EOF +jabref-windows-sandbox: fatal: fetch-pack: invalid index-pack output +``` + +The `git clone` command did not work. + +Login, open `cmd` and then execute following commands: + +```cmd +git clone --recurse-submodules https://github.com/JabRef/jabref.git +cd jabref +gradlew run +``` + +## Background + +`Vagrantfile` is based on [SeisoLLC/windows-sandbox](https://github.com/SeisoLLC/windows-sandbox/tree/main). + +The most use image seems to be the [Windows 10 image by `gusztavvargadr`](https://portal.cloud.hashicorp.com/vagrant/discover/gusztavvargadr/windows-10). +List of all images at . + +[Chocolatey](https://chocolatey.org/) is used instead of [winget-cli](https://learn.microsoft.com/en-us/windows/package-manager/), because Chocolatey installation does not hit GitHub's rate limits during unattended installation. + +## Atlernatives + +- Atlernative Vagrant images: . +- [Windows Sandbox](https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/windows-sandbox/windows-sandbox-overview) diff --git a/jabref/scripts/vms/windows11/Vagrantfile b/jabref/scripts/vms/windows11/Vagrantfile new file mode 100644 index 00000000..0d9cdf27 --- /dev/null +++ b/jabref/scripts/vms/windows11/Vagrantfile @@ -0,0 +1,46 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +NAME = "jabref-windows-11-sandbox" + +Vagrant.configure("2") do |config| + # config.vm.box = "Caden/windows-11-pro-jp-22h2-22621.1992" + config.vm.box = "stromweld/windows-11" + + config.vm.define NAME + config.vm.hostname = NAME + + config.vm.provider "virtualbox" do |vb| + vb.name = NAME + + vb.memory = 6000 + vb.cpus = 2 + + vb.customize ['modifyvm', :id, '--clipboard-mode', 'bidirectional'] + vb.gui = true + end + + config.vm.provision "shell", privileged: "true", powershell_elevated_interactive: "true", inline: <<-SHELL + # Install chocolatey + Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + choco feature enable -n=allowGlobalConfirmation + + choco install libericajdk + choco install git.install -y --params "/GitAndUnixToolsOnPath /WindowsTerminal /WindowsTerminalProfile" + + # Required by AI functionaltiy + choco choco install vcredist140 + + # choco install firefox + # choco install libreoffice-fresh + SHELL + + config.vm.provision "shell", reboot: true + + config.vm.provision "shell", privileged: "false", inline: <<-SHELL + cd \\users\\vagrant + git clone --depth=1 --recurse-submodules https://github.com/JabRef/jabref.git + # cd jabref + # .\\gradlew jar + SHELL +end diff --git a/jabref/settings.gradle b/jabref/settings.gradle new file mode 100644 index 00000000..3221d2ed --- /dev/null +++ b/jabref/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + resolutionStrategy { + eachPlugin { + // Hint from https://github.com/jitpack/jitpack.io/issues/1459#issuecomment-1279851731 + // Updated solution at https://github.com/foodiestudio/convention-plugins?tab=readme-ov-file#convention-plugins + if (requested.id.id.startsWith("com.github.koppor")) { + // This is https://github.com/java9-modularity/gradle-modules-plugin/pull/282 + useModule("com.github.koppor:gradle-modules-plugin:1.8.15-cmd-1") + } + } + } + + repositories { + maven { + url 'https://jitpack.io' + } + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + +rootProject.name = "JabRef" diff --git a/jabref/snap/gui/jabref.desktop b/jabref/snap/gui/jabref.desktop new file mode 100644 index 00000000..3444b73f --- /dev/null +++ b/jabref/snap/gui/jabref.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=JabRef +GenericName=BibTeX Editor +Comment=JabRef is an open source bibliography reference manager. The native file format used by JabRef is BibTeX, the standard LaTeX bibliography format. +Type=Application +Terminal=false +Icon=${SNAP}/meta/gui/jabref.png +Exec=jabref %U +Keywords=bibtex;biblatex;latex;bibliography +Categories=Office; +StartupWMClass=org-jabref-JabRefMain +MimeType=text/x-bibtex; diff --git a/jabref/snap/gui/jabref.png b/jabref/snap/gui/jabref.png new file mode 100644 index 00000000..518f6e27 Binary files /dev/null and b/jabref/snap/gui/jabref.png differ diff --git a/jabref/snap/hooks/connect-plug-etc-chromium-native-messaging-jabref b/jabref/snap/hooks/connect-plug-etc-chromium-native-messaging-jabref new file mode 100644 index 00000000..bcd982e7 --- /dev/null +++ b/jabref/snap/hooks/connect-plug-etc-chromium-native-messaging-jabref @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ ! -d /etc/chromium/native-messaging-hosts ]; then + echo "Missing directory, create it manually then try again:" + echo "sudo mkdir -p /etc/chromium/native-messaging-hosts" + exit 1 +fi + +cp "$SNAP/lib/native-messaging-host/chromium/org.jabref.jabref.json" /etc/chromium/native-messaging-hosts/org.jabref.jabref.json diff --git a/jabref/snap/hooks/connect-plug-etc-opt-chrome-native-messaging-jabref b/jabref/snap/hooks/connect-plug-etc-opt-chrome-native-messaging-jabref new file mode 100644 index 00000000..bb5d8c7e --- /dev/null +++ b/jabref/snap/hooks/connect-plug-etc-opt-chrome-native-messaging-jabref @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ ! -d /etc/opt/chrome/native-messaging-hosts ]; then + echo "Missing directory, create it manually then try again:" + echo "sudo mkdir -p /etc/opt/chrome/native-messaging-hosts" + exit 1 +fi + +cp "$SNAP/lib/native-messaging-host/chromium/org.jabref.jabref.json" /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json diff --git a/jabref/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref b/jabref/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref new file mode 100644 index 00000000..c3dca7ea --- /dev/null +++ b/jabref/snap/hooks/connect-plug-etc-opt-edge-native-messaging-jabref @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ ! -d /etc/opt/edge/native-messaging-hosts ]; then + echo "Missing directory, create it manually then try again:" + echo "sudo mkdir -p /etc/opt/edge/native-messaging-hosts" + exit 1 +fi + +cp "$SNAP/lib/native-messaging-host/chromium/org.jabref.jabref.json" /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json diff --git a/jabref/snap/hooks/connect-plug-hostfs-mozilla-native-messaging-jabref b/jabref/snap/hooks/connect-plug-hostfs-mozilla-native-messaging-jabref new file mode 100644 index 00000000..ac597118 --- /dev/null +++ b/jabref/snap/hooks/connect-plug-hostfs-mozilla-native-messaging-jabref @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ ! -d /var/lib/snapd/hostfs/usr/lib/mozilla/native-messaging-hosts ]; then + echo "Missing directory, create it manually then try again:" + echo "sudo mkdir -p /usr/lib/mozilla/native-messaging-hosts" + exit 1 +fi + +cp "$SNAP/lib/native-messaging-host/firefox/org.jabref.jabref.json" /var/lib/snapd/hostfs/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json diff --git a/jabref/snap/hooks/disconnect-plug-etc-chromium-native-messaging-jabref b/jabref/snap/hooks/disconnect-plug-etc-chromium-native-messaging-jabref new file mode 100644 index 00000000..e68ad21c --- /dev/null +++ b/jabref/snap/hooks/disconnect-plug-etc-chromium-native-messaging-jabref @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ ! -f /etc/chromium/native-messaging-hosts/org.jabref.jabref.json ]; then + exit 0 +elif grep --quiet '"path": "/snap' /etc/chromium/native-messaging-hosts/org.jabref.jabref.json; then + rm /etc/chromium/native-messaging-hosts/org.jabref.jabref.json +fi diff --git a/jabref/snap/hooks/disconnect-plug-etc-opt-chrome-native-messaging-jabref b/jabref/snap/hooks/disconnect-plug-etc-opt-chrome-native-messaging-jabref new file mode 100644 index 00000000..50eff0b9 --- /dev/null +++ b/jabref/snap/hooks/disconnect-plug-etc-opt-chrome-native-messaging-jabref @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ ! -f /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json ]; then + exit 0 +elif grep --quiet '"path": "/snap' /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json; then + rm /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json +fi diff --git a/jabref/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref b/jabref/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref new file mode 100644 index 00000000..cfb9af59 --- /dev/null +++ b/jabref/snap/hooks/disconnect-plug-etc-opt-edge-native-messaging-jabref @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ ! -f /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json ]; then + exit 0 +elif grep --quiet '"path": "/snap' /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json; then + rm /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json +fi diff --git a/jabref/snap/hooks/disconnect-plug-hostfs-mozilla-native-messaging-jabref b/jabref/snap/hooks/disconnect-plug-hostfs-mozilla-native-messaging-jabref new file mode 100644 index 00000000..280d1afc --- /dev/null +++ b/jabref/snap/hooks/disconnect-plug-hostfs-mozilla-native-messaging-jabref @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ ! -f /var/lib/snapd/hostfs/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json ]; then + exit 0 +elif grep --quiet '"path": "/snap' /var/lib/snapd/hostfs/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json; then + rm /var/lib/snapd/hostfs/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json +fi diff --git a/jabref/snap/local/JabRef-launcher b/jabref/snap/local/JabRef-launcher new file mode 100644 index 00000000..997030c2 --- /dev/null +++ b/jabref/snap/local/JabRef-launcher @@ -0,0 +1,2 @@ +#! /bin/sh +"$SNAP/lib/runtime/bin/JabRef" "$@" diff --git a/jabref/snap/snapcraft.yaml b/jabref/snap/snapcraft.yaml new file mode 100644 index 00000000..11591ded --- /dev/null +++ b/jabref/snap/snapcraft.yaml @@ -0,0 +1,94 @@ +name: jabref +adopt-info: jabref +icon: snap/gui/jabref.png +license: MIT +summary: Bibliography manager +description: | + JabRef is an open source bibliography reference manager. + The native file format used by JabRef is BibTeX, the standard LaTeX bibliography format. + To access files in external media (i.e., USB drives) you must run: + `snap connect jabref:removable-media` +grade: stable +confinement: strict +base: core22 +compression: lzo +architectures: + - build-on: [amd64, arm64] + build-for: [amd64] + +plugs: + home: + unity7: + opengl: + network-bind: + removable-media: + hostfs-mozilla-native-messaging-jabref: + interface: system-files + write: + - /var/lib/snapd/hostfs/usr/lib/mozilla/native-messaging-hosts/org.jabref.jabref.json + etc-opt-chrome-native-messaging-jabref: + interface: system-files + write: + - /etc/opt/chrome/native-messaging-hosts/org.jabref.jabref.json + etc-opt-edge-native-messaging-jabref: + interface: system-files + write: + - /etc/opt/edge/native-messaging-hosts/org.jabref.jabref.json + etc-chromium-native-messaging-jabref: + interface: system-files + write: + - /etc/chromium/native-messaging-hosts/org.jabref.jabref.json + +layout: + /usr/share/libdrm: + bind: $SNAP/gnome-platform/usr/share/libdrm + +apps: + jabref: + command: bin/JabRef + extensions: [gnome] + browser-proxy: + command: lib/jabrefHost.py + extensions: [gnome] + +environment: + _JAVA_OPTIONS: "-Duser.home=$SNAP_USER_DATA" + GTK_USE_PORTAL: "1" + +parts: + jabref: + plugin: dump + source: https://builds.jabref.org/main/JabRef-6.0-portable_linux.tar.gz + stage-packages: + - x11-utils + override-build: | + snapcraftctl build + snapcraftctl set-version "$(cat $SNAPCRAFT_PART_INSTALL/lib/app/.jpackage.xml | grep "app-version" | cut -d">" -f2 | cut -d"<" -f1)" + sed -i 's|/opt/jabref/lib/jabrefHost.py|/snap/bin/jabref.browser-proxy|g' $SNAPCRAFT_PART_INSTALL/lib/native-messaging-host/*/org.jabref.jabref.json + sed -i 's/usr\/bin\/env python3/usr\/bin\/python3/g' $SNAPCRAFT_PART_INSTALL/lib/jabrefHost.py + rm $SNAPCRAFT_PART_INSTALL/bin/JabRef + jabref-launcher: + after: + - jabref + source: snap/local + source-type: local + plugin: dump + organize: + JabRef-launcher: bin/JabRef + cleanup: + after: + - jabref + - jabref-launcher + plugin: nil + build-snaps: + - gnome-42-2204 + override-prime: | + set -eux + for snap in "gnome-42-2204"; do # List all content-snaps you're using here + cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$SNAPCRAFT_PRIME/{}" "$SNAPCRAFT_PRIME/usr/{}" \; + done + for CRUFT in bug lintian man; do + rm -rf $SNAPCRAFT_PRIME/usr/share/$CRUFT + done + find $SNAPCRAFT_PRIME/usr/share/doc/ -type f -not -name 'copyright' -delete + find $SNAPCRAFT_PRIME/usr/share -type d -empty -delete diff --git a/jabref/src/jmh/java/org/jabref/benchmarks/Benchmarks.java b/jabref/src/jmh/java/org/jabref/benchmarks/Benchmarks.java new file mode 100644 index 00000000..e553c87c --- /dev/null +++ b/jabref/src/jmh/java/org/jabref/benchmarks/Benchmarks.java @@ -0,0 +1,146 @@ +package org.jabref.benchmarks; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.List; +import java.util.Random; + +import org.jabref.logic.bibtex.FieldPreferences; +import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.exporter.BibtexDatabaseWriter; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; +import org.jabref.logic.formatter.bibtexfields.HtmlToLatexFormatter; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.layout.format.HTMLChars; +import org.jabref.logic.layout.format.LatexToUnicodeFormatter; +import org.jabref.logic.os.OS; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.preferences.JabRefCliPreferences; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.database.BibDatabaseModeDetection; +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.groups.GroupHierarchyType; +import org.jabref.model.groups.KeywordGroup; +import org.jabref.model.groups.WordKeywordGroup; +import org.jabref.model.metadata.MetaData; + +import com.airhacks.afterburner.injection.Injector; +import org.openjdk.jmh.Main; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.runner.RunnerException; + +import static org.mockito.Mockito.mock; + +@State(Scope.Thread) +public class Benchmarks { + + private String bibtexString; + private final BibDatabase database = new BibDatabase(); + private String latexConversionString; + private String htmlConversionString; + + @Setup + public void init() throws Exception { + Injector.setModelOrService(CliPreferences.class, JabRefCliPreferences.getInstance()); + + Random randomizer = new Random(); + for (int i = 0; i < 1000; i++) { + BibEntry entry = new BibEntry(); + entry.setCitationKey("id" + i); + entry.setField(StandardField.TITLE, "This is my title " + i); + entry.setField(StandardField.AUTHOR, "Firstname Lastname and FirstnameA LastnameA and FirstnameB LastnameB" + i); + entry.setField(StandardField.JOURNAL, "Journal Title " + i); + entry.setField(StandardField.KEYWORDS, "testkeyword"); + entry.setField(StandardField.YEAR, "1" + i); + entry.setField(new UnknownField("rnd"), "2" + randomizer.nextInt()); + database.insertEntry(entry); + } + + bibtexString = getOutputWriter().toString(); + + latexConversionString = "{A} \\textbf{bold} approach {\\it to} ${{\\Sigma}}{\\Delta}$ modulator \\textsuperscript{2} \\$"; + + htmlConversionString = "Österreich – & characters ⪢ italic"; + } + + private StringWriter getOutputWriter() throws IOException { + StringWriter outputWriter = new StringWriter(); + BibWriter bibWriter = new BibWriter(outputWriter, OS.NEWLINE); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter( + bibWriter, + mock(SelfContainedSaveConfiguration.class), + mock(FieldPreferences.class), + mock(CitationKeyPatternPreferences.class), + new BibEntryTypesManager()); + databaseWriter.savePartOfDatabase(new BibDatabaseContext(database, new MetaData()), database.getEntries()); + return outputWriter; + } + + @Benchmark + public ParserResult parse() throws IOException { + CliPreferences preferences = Injector.instantiateModelOrService(CliPreferences.class); + BibtexParser parser = new BibtexParser(preferences.getImportFormatPreferences()); + return parser.parse(new StringReader(bibtexString)); + } + + @Benchmark + public String write() throws Exception { + return getOutputWriter().toString(); + } + + @Benchmark + public List search() { + // TODO: Create Benchmark for LuceneSearch + return List.of(); + } + + @Benchmark + public List index() { + // TODO: Create Benchmark for LuceneIndexer + return List.of(); + } + + @Benchmark + public BibDatabaseMode inferBibDatabaseMode() { + return BibDatabaseModeDetection.inferMode(database); + } + + @Benchmark + public String latexToUnicodeConversion() { + LatexToUnicodeFormatter f = new LatexToUnicodeFormatter(); + return f.format(latexConversionString); + } + + @Benchmark + public String latexToHTMLConversion() { + HTMLChars f = new HTMLChars(); + return f.format(latexConversionString); + } + + @Benchmark + public String htmlToLatexConversion() { + HtmlToLatexFormatter f = new HtmlToLatexFormatter(); + return f.format(htmlConversionString); + } + + @Benchmark + public boolean keywordGroupContains() { + KeywordGroup group = new WordKeywordGroup("testGroup", GroupHierarchyType.INDEPENDENT, StandardField.KEYWORDS, "testkeyword", false, ',', false); + return group.containsAll(database.getEntries()); + } + + public static void main(String[] args) throws IOException, RunnerException { + Main.main(args); + } +} diff --git a/jabref/src/main/antlr4/org/jabref/bst/Bst.g4 b/jabref/src/main/antlr4/org/jabref/bst/Bst.g4 new file mode 100644 index 00000000..92b96ce1 --- /dev/null +++ b/jabref/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -0,0 +1,85 @@ +grammar Bst; + +// Lexer + +STRINGS : 'STRINGS'; +INTEGERS : 'INTEGERS'; +FUNCTION : 'FUNCTION'; +EXECUTE : 'EXECUTE'; +SORT : 'SORT'; +ITERATE : 'ITERATE'; +REVERSE : 'REVERSE'; +ENTRY : 'ENTRY'; +READ : 'READ'; +MACRO : 'MACRO'; + +GT : '>'; +LT : '<'; +EQUAL : '='; +ASSIGN : ':='; +ADD : '+'; +SUB : '-'; +CONCAT : '*'; +LBRACE : '{'; +RBRACE : '}'; + +fragment LETTER : ('a'..'z'|'A'..'Z'|'.'|'$'); +fragment NUMERAL : ('0'..'9'); + +IDENTIFIER : LETTER (LETTER|NUMERAL|'_')*; +INTEGER : '#' ('+'|'-')? NUMERAL+; +QUOTED : '\'' IDENTIFIER; +STRING : '"' (~('"'))* '"'; + +WS: [ \r\n\t]+ -> skip; +LINE_COMMENT : '%' ~('\n'|'\r')* '\r'? '\n' -> skip; + +// Parser + +bstFile + : commands+ EOF + ; + +commands + : STRINGS ids=idListObl #stringsCommand + | INTEGERS ids=idListObl #integersCommand + | FUNCTION LBRACE id=identifier RBRACE function=stack #functionCommand + | MACRO LBRACE id=identifier RBRACE LBRACE repl=STRING RBRACE #macroCommand + | READ #readCommand + | EXECUTE LBRACE bstFunction RBRACE #executeCommand + | ITERATE LBRACE bstFunction RBRACE #iterateCommand + | REVERSE LBRACE bstFunction RBRACE #reverseCommand + | ENTRY idListOpt idListOpt idListOpt #entryCommand + | SORT #sortCommand + ; + +identifier + : IDENTIFIER + ; + +// Obligatory identifier list +idListObl + : LBRACE identifier+ RBRACE + ; + +// Optional identifier list +idListOpt + : LBRACE identifier* RBRACE + ; + +bstFunction + : LT | GT | EQUAL | ADD | SUB | ASSIGN | CONCAT + | identifier + ; + +stack + : LBRACE stackitem+ RBRACE + ; + +stackitem + : bstFunction + | STRING + | INTEGER + | QUOTED + | stack + ; diff --git a/jabref/src/main/antlr4/org/jabref/search/Search.g4 b/jabref/src/main/antlr4/org/jabref/search/Search.g4 new file mode 100644 index 00000000..0a8c28c2 --- /dev/null +++ b/jabref/src/main/antlr4/org/jabref/search/Search.g4 @@ -0,0 +1,85 @@ +/** + * This is the antlr v4 grammar for defining search expressions. + * + * These search expressions are used for searching the bibtex library. They are heavily used for search groups. + */ +grammar Search; +options { caseInsensitive = true; } + +WS: [ \t\n\r]+ -> skip; // whitespace is ignored/skipped + +LPAREN: '('; +RPAREN: ')'; + +EQUAL: '='; // case insensitive contains, semantically the same as CONTAINS +CEQUAL: '=!'; // case sensitive contains + +EEQUAL: '=='; // exact match case insensitive, semantically the same as MATCHES +CEEQUAL: '==!'; // exact match case sensitive + +REQUAL: '=~'; // regex check case insensitive +CREEQUAL: '=~!'; // regex check case sensitive + +NEQUAL: '!='; // negated case insensitive contains +NCEQUAL: '!=!'; // negated case sensitive contains + +NEEQUAL: '!=='; // negated case insensitive exact match +NCEEQUAL: '!==!'; // negated case sensitive exact match + +NREQUAL: '!=~'; // negated regex check case insensitive +NCREEQUAL: '!=~!'; // negated regex check case sensitive + +AND: 'AND'; +OR: 'OR'; +CONTAINS: 'CONTAINS'; +MATCHES: 'MATCHES'; +NOT: 'NOT'; + +FIELD: [A-Z]+; +STRING_LITERAL: '"' ('\\"' | ~["])* '"'; // " should be escaped with a backslash +TERM: ('\\' [=!~()] | ~[ \t\n\r=!~()])+; // =!~() should be escaped with a backslash + +start + : EOF + | andExpression EOF + ; + +andExpression + : expression+ #implicitAndExpression // example: author = miller year = 2010 --> equivalent to: author = miller AND year = 2010 + ; + +expression + : LPAREN andExpression RPAREN #parenExpression // example: (author = miller) + | NOT expression #negatedExpression // example: NOT author = miller + | left = expression bin_op = AND right = expression #binaryExpression // example: author = miller AND year = 2010 + | left = expression bin_op = OR right = expression #binaryExpression // example: author = miller OR year = 2010 + | comparison #comparisonExpression // example: miller OR author = miller + ; + +comparison + : FIELD operator searchValue // example: author = miller + | searchValue // example: miller + ; + +operator + : EQUAL + | CEQUAL + | EEQUAL + | CEEQUAL + | REQUAL + | CREEQUAL + | NEQUAL + | NCEQUAL + | NEEQUAL + | NCEEQUAL + | NREQUAL + | NCREEQUAL + | CONTAINS + | MATCHES + ; + +searchValue + : STRING_LITERAL + | FIELD + | TERM + ; diff --git a/jabref/src/main/java/module-info.java b/jabref/src/main/java/module-info.java new file mode 100644 index 00000000..f0151b89 --- /dev/null +++ b/jabref/src/main/java/module-info.java @@ -0,0 +1,197 @@ +open module org.jabref { + // Swing + requires java.desktop; + + // SQL + requires java.sql; + requires java.sql.rowset; + + // region JavaFX + requires javafx.base; + requires javafx.graphics; + requires javafx.controls; + requires javafx.web; + requires javafx.fxml; + + requires afterburner.fx; + provides com.airhacks.afterburner.views.ResourceLocator + with org.jabref.gui.util.JabRefResourceLocator; + + requires com.dlsc.gemsfx; + uses com.dlsc.gemsfx.TagsField; + // Provides number input fields for parameters in AI expert settings + requires com.dlsc.unitfx; + + requires com.tobiasdiez.easybind; + + requires de.saxsys.mvvmfx; + requires de.saxsys.mvvmfx.validation; + + requires org.controlsfx.controls; + requires org.fxmisc.flowless; + requires org.fxmisc.richtext; + + requires org.kordamp.ikonli.core; + requires org.kordamp.ikonli.javafx; + requires org.kordamp.ikonli.materialdesign2; + uses org.kordamp.ikonli.IkonHandler; + uses org.kordamp.ikonli.IkonProvider; + + provides org.kordamp.ikonli.IkonHandler + with org.jabref.gui.icon.JabRefIkonHandler; + provides org.kordamp.ikonli.IkonProvider + with org.jabref.gui.icon.JabrefIconProvider; + + requires reactfx; + // endregion + + // region: Logging + requires org.slf4j; + requires jul.to.slf4j; + requires org.apache.logging.log4j.to.slf4j; + requires org.tinylog.api; + requires org.tinylog.api.slf4j; + requires org.tinylog.impl; + // endregion + + provides org.tinylog.writers.Writer + with org.jabref.gui.logging.GuiWriter; + + // Preferences and XML + requires java.prefs; + requires com.fasterxml.aalto; + + // YAML + requires org.yaml.snakeyaml; + + // region: Annotations (@PostConstruct) + requires jakarta.annotation; + requires jakarta.inject; + // endregion + + // region: http server and client exchange + requires java.net.http; + requires jakarta.ws.rs; + requires org.glassfish.grizzly; + // endregion + + // region: data mapping + requires jakarta.xml.bind; + requires jdk.xml.dom; + requires com.google.gson; + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.dataformat.yaml; + requires com.fasterxml.jackson.datatype.jsr310; + // needs to be loaded here as it's otherwise not found at runtime + requires org.glassfish.jaxb.runtime; + // endregion + + // dependency injection using HK2 + requires org.glassfish.hk2.api; + + // region HTTP clients + requires org.apache.httpcomponents.core5.httpcore5; + requires org.jsoup; + requires unirest.java.core; + requires unirest.modules.gson; + // endregion + + // region: SQL databases + requires embedded.postgres; + requires org.tukaani.xz; + requires ojdbc10; + requires org.postgresql.jdbc; + requires org.mariadb.jdbc; + uses org.mariadb.jdbc.credential.CredentialPlugin; + // endregion + + // region: Apache Commons and other (similar) helper libraries + requires com.google.common; + requires io.github.javadiffutils; + requires java.string.similarity; + requires org.apache.commons.cli; + requires org.apache.commons.compress; + requires org.apache.commons.csv; + requires org.apache.commons.io; + requires org.apache.commons.lang3; + requires org.apache.commons.text; + requires org.apache.commons.logging; + // endregion + + // region: latex2unicode + requires com.github.tomtung.latex2unicode; + requires fastparse; + requires scala.library; + // endregion + + requires jbibtex; + requires citeproc.java; + + requires snuggletex.core; + + requires org.apache.pdfbox; + requires org.apache.xmpbox; + requires com.ibm.icu; + + requires flexmark; + requires flexmark.html2md.converter; + requires flexmark.util.ast; + requires flexmark.util.data; + + requires com.h2database.mvstore; + + requires java.keyring; + requires org.freedesktop.dbus; + + requires org.jooq.jool; + + // region AI + requires ai.djl.api; + requires ai.djl.pytorch_model_zoo; + requires ai.djl.tokenizers; + requires jvm.openai; + requires langchain4j; + requires langchain4j.core; + requires langchain4j.google.ai.gemini; + requires langchain4j.hugging.face; + requires langchain4j.mistral.ai; + requires langchain4j.open.ai; + uses ai.djl.engine.EngineProvider; + uses ai.djl.repository.RepositoryFactory; + uses ai.djl.repository.zoo.ZooProvider; + uses dev.langchain4j.spi.prompt.PromptTemplateFactory; + requires velocity.engine.core; + // endregion + + // region: Lucene + /* + * In case the version is updated, please also increment {@link org.jabref.model.search.LinkedFilesConstants.VERSION} to trigger reindexing. + */ + uses org.apache.lucene.codecs.lucene100.Lucene100Codec; + requires org.apache.lucene.analysis.common; + requires org.apache.lucene.core; + requires org.apache.lucene.highlighter; + requires org.apache.lucene.queryparser; + // endregion + + requires net.harawata.appdirs; + requires com.sun.jna; + requires com.sun.jna.platform; + + requires org.eclipse.jgit; + uses org.eclipse.jgit.transport.SshSessionFactory; + uses org.eclipse.jgit.lib.Signer; + + requires transitive org.jspecify; + + // region: other libraries (alphabetically) + requires cuid; + requires dd.plist; + requires io.github.adr; + // required by okhttp and some AI library + requires kotlin.stdlib; + requires mslinks; + requires org.antlr.antlr4.runtime; + requires org.libreoffice.uno; + // endregion +} diff --git a/jabref/src/main/java/org/jabref/Launcher.java b/jabref/src/main/java/org/jabref/Launcher.java new file mode 100644 index 00000000..a60a05be --- /dev/null +++ b/jabref/src/main/java/org/jabref/Launcher.java @@ -0,0 +1,48 @@ +package org.jabref; + +import java.util.List; + +import org.jabref.cli.JabKit; +import org.jabref.gui.JabRefGUI; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.preferences.JabRefGuiPreferences; +import org.jabref.gui.util.DefaultFileUpdateMonitor; +import org.jabref.logic.UiCommand; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.search.PostgreServer; +import org.jabref.logic.util.HeadlessExecutorService; +import org.jabref.migrations.PreferencesMigrations; + +import com.airhacks.afterburner.injection.Injector; + +/// The main entry point for the JabRef application. +/// +/// It has two main functions: +/// +/// - Handle the command line arguments +/// - Start the JavaFX application (if not in CLI mode) +public class Launcher { + + public static void main(String[] args) { + JabKit.initLogging(args); + + // Initialize preferences + final JabRefGuiPreferences preferences = JabRefGuiPreferences.getInstance(); + Injector.setModelOrService(CliPreferences.class, preferences); + Injector.setModelOrService(GuiPreferences.class, preferences); + + DefaultFileUpdateMonitor fileUpdateMonitor = new DefaultFileUpdateMonitor(); + HeadlessExecutorService.INSTANCE.executeInterruptableTask(fileUpdateMonitor, "FileUpdateMonitor"); + + List uiCommands = JabKit.processArguments(args, preferences, fileUpdateMonitor); + // The method `processArguments` quites the whole JVM if no GUI is needed. + + PreferencesMigrations.runMigrations(preferences); + + PostgreServer postgreServer = new PostgreServer(); + Injector.setModelOrService(PostgreServer.class, postgreServer); + + JabRefGUI.setup(uiCommands, preferences, fileUpdateMonitor); + JabRefGUI.launch(JabRefGUI.class, args); + } +} diff --git a/jabref/src/main/java/org/jabref/architecture/AllowedToUseApacheCommonsLang3.java b/jabref/src/main/java/org/jabref/architecture/AllowedToUseApacheCommonsLang3.java new file mode 100644 index 00000000..c50d8917 --- /dev/null +++ b/jabref/src/main/java/org/jabref/architecture/AllowedToUseApacheCommonsLang3.java @@ -0,0 +1,11 @@ +package org.jabref.architecture; + +/** + * Annotation to indicate that usage of ApacheCommonsLang3 is explicitly allowed. + * The intention is to fully switch to Google Guava and only use Apache Commons Lang3 if there is no other possibility + */ +public @interface AllowedToUseApacheCommonsLang3 { + + // The rationale + String value(); +} diff --git a/jabref/src/main/java/org/jabref/architecture/AllowedToUseAwt.java b/jabref/src/main/java/org/jabref/architecture/AllowedToUseAwt.java new file mode 100644 index 00000000..d4cdb549 --- /dev/null +++ b/jabref/src/main/java/org/jabref/architecture/AllowedToUseAwt.java @@ -0,0 +1,10 @@ +package org.jabref.architecture; + +/** + * Annotation to indicate that this logic class can access AWT + */ +public @interface AllowedToUseAwt { + + // The rationale + String value(); +} diff --git a/jabref/src/main/java/org/jabref/architecture/AllowedToUseClassGetResource.java b/jabref/src/main/java/org/jabref/architecture/AllowedToUseClassGetResource.java new file mode 100644 index 00000000..a8f98bdc --- /dev/null +++ b/jabref/src/main/java/org/jabref/architecture/AllowedToUseClassGetResource.java @@ -0,0 +1,11 @@ +package org.jabref.architecture; + +/** + * Annotation to indicate that this logic class can use class.getResource(). + * Mostly, because {@link java.nio.file.Path} is not used. + * See https://github.com/oracle/graal/issues/7682 for a longer discussion. + */ +public @interface AllowedToUseClassGetResource { + // The rationale + String value(); +} diff --git a/jabref/src/main/java/org/jabref/architecture/AllowedToUseLogic.java b/jabref/src/main/java/org/jabref/architecture/AllowedToUseLogic.java new file mode 100644 index 00000000..ed6be5be --- /dev/null +++ b/jabref/src/main/java/org/jabref/architecture/AllowedToUseLogic.java @@ -0,0 +1,10 @@ +package org.jabref.architecture; + +/** + * Annotation to indicate that this logic class can be used in the model package. + */ +public @interface AllowedToUseLogic { + + // The rationale + String value(); +} diff --git a/jabref/src/main/java/org/jabref/architecture/AllowedToUseStandardStreams.java b/jabref/src/main/java/org/jabref/architecture/AllowedToUseStandardStreams.java new file mode 100644 index 00000000..1fe80af5 --- /dev/null +++ b/jabref/src/main/java/org/jabref/architecture/AllowedToUseStandardStreams.java @@ -0,0 +1,10 @@ +package org.jabref.architecture; + +/** + * Annotation to indicate that this class can use System.Out.* instead of using the logging framework + */ +public @interface AllowedToUseStandardStreams { + + // The rationale + String value(); +} diff --git a/jabref/src/main/java/org/jabref/architecture/AllowedToUseSwing.java b/jabref/src/main/java/org/jabref/architecture/AllowedToUseSwing.java new file mode 100644 index 00000000..6820421c --- /dev/null +++ b/jabref/src/main/java/org/jabref/architecture/AllowedToUseSwing.java @@ -0,0 +1,9 @@ +package org.jabref.architecture; + +/** + * Annotation to indicate that this logic class can access swing + */ +public @interface AllowedToUseSwing { + // The rationale + String value(); +} diff --git a/jabref/src/main/java/org/jabref/cli/ArgumentProcessor.java b/jabref/src/main/java/org/jabref/cli/ArgumentProcessor.java new file mode 100644 index 00000000..bc7cb7d3 --- /dev/null +++ b/jabref/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -0,0 +1,794 @@ +package org.jabref.cli; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.prefs.BackingStoreException; + +import org.jabref.logic.FilePreferences; +import org.jabref.logic.JabRefException; +import org.jabref.logic.UiCommand; +import org.jabref.logic.bibtex.FieldPreferences; +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.exporter.AtomicFileWriter; +import org.jabref.logic.exporter.BibDatabaseWriter; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.exporter.BibtexDatabaseWriter; +import org.jabref.logic.exporter.EmbeddedBibFilePdfExporter; +import org.jabref.logic.exporter.Exporter; +import org.jabref.logic.exporter.ExporterFactory; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; +import org.jabref.logic.exporter.XmpPdfExporter; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.ImportException; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.ImportFormatReader; +import org.jabref.logic.importer.OpenDatabase; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.logic.importer.WebFetchers; +import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.journals.JournalAbbreviationRepository; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.net.URLDownload; +import org.jabref.logic.os.OS; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.search.DatabaseSearcher; +import org.jabref.logic.search.SearchPreferences; +import org.jabref.logic.shared.prefs.SharedDatabasePreferences; +import org.jabref.logic.util.CurrentThreadTaskExecutor; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.logic.xmp.XmpPreferences; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.search.query.SearchQuery; +import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.model.util.FileUpdateMonitor; + +import com.airhacks.afterburner.injection.Injector; +import com.google.common.base.Throwables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ArgumentProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentProcessor.class); + + public enum Mode { INITIAL_START, REMOTE_START } + + private final CliOptions cli; + + private final Mode startupMode; + + private final CliPreferences cliPreferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final BibEntryTypesManager entryTypesManager; + + private boolean guiNeeded; + private final List uiCommands = new ArrayList<>(); + + /** + * First call the constructor, then call {@link #processArguments()}. + * Afterward, you can access the {@link #getUiCommands()}. + * + * @implNote both cli and gui preferences are passed to make the dependency to GUI parts explicit + */ + public ArgumentProcessor(String[] args, + Mode startupMode, + CliPreferences cliPreferences, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager) + throws org.apache.commons.cli.ParseException { + this.cli = new CliOptions(args); + this.startupMode = startupMode; + this.cliPreferences = cliPreferences; + this.fileUpdateMonitor = fileUpdateMonitor; + this.entryTypesManager = entryTypesManager; + } + + /** + * Will open a file (like {@link #importFile(String)}, but will also request JabRef to focus on this library. + * + * @return ParserResult with setToOpenTab(true) + */ + private Optional importToOpenBase(String importArguments) { + Optional result = importFile(importArguments); + result.ifPresent(ParserResult::setToOpenTab); + return result; + } + + private Optional importBibtexToOpenBase(String argument, ImportFormatPreferences importFormatPreferences) { + BibtexParser parser = new BibtexParser(importFormatPreferences); + try { + List entries = parser.parseEntries(argument); + ParserResult result = new ParserResult(entries); + result.setToOpenTab(); + return Optional.of(result); + } catch (ParseException e) { + System.err.println(Localization.lang("Error occurred when parsing entry") + ": " + e.getLocalizedMessage()); + return Optional.empty(); + } + } + + /** + * + * @param importArguments Format: fileName[,format] + */ + private Optional importFile(String importArguments) { + LOGGER.debug("Importing file {}", importArguments); + String[] data = importArguments.split(","); + + String address = data[0]; + Path file; + if (address.startsWith("http://") || address.startsWith("https://") || address.startsWith("ftp://")) { + // Download web resource to temporary file + try { + file = new URLDownload(address).toTemporaryFile(); + } catch (FetcherException | MalformedURLException e) { + System.err.println(Localization.lang("Problem downloading from %1", address) + e.getLocalizedMessage()); + return Optional.empty(); + } + } else { + if (OS.WINDOWS) { + file = Path.of(address); + } else { + file = Path.of(address.replace("~", System.getProperty("user.home"))); + } + } + + String importFormat; + if (data.length > 1) { + importFormat = data[1]; + } else { + importFormat = "*"; + } + + Optional importResult = importFile(file, importFormat); + importResult.ifPresent(result -> { + if (result.hasWarnings()) { + System.out.println(result.getErrorMessage()); + } + }); + return importResult; + } + + private Optional importFile(Path file, String importFormat) { + try { + ImportFormatReader importFormatReader = new ImportFormatReader( + cliPreferences.getImporterPreferences(), + cliPreferences.getImportFormatPreferences(), + cliPreferences.getCitationKeyPatternPreferences(), + fileUpdateMonitor + ); + + if (!"*".equals(importFormat)) { + System.out.println(Localization.lang("Importing %0", file)); + ParserResult result = importFormatReader.importFromFile(importFormat, file); + return Optional.of(result); + } else { + // * means "guess the format": + System.out.println(Localization.lang("Importing file %0 as unknown format", file)); + + ImportFormatReader.UnknownFormatImport importResult = + importFormatReader.importUnknownFormat(file, new DummyFileUpdateMonitor()); + + System.out.println(Localization.lang("Format used: %0", importResult.format())); + return Optional.of(importResult.parserResult()); + } + } catch (ImportException ex) { + System.err.println(Localization.lang("Error opening file '%0'", file) + "\n" + ex.getLocalizedMessage()); + return Optional.empty(); + } + } + + public void processArguments() { + uiCommands.clear(); + + if ((startupMode == Mode.INITIAL_START) && cli.isShowVersion()) { + cli.displayVersion(); + } + + if ((startupMode == Mode.INITIAL_START) && cli.isHelp()) { + CliOptions.printUsage(cliPreferences); + guiNeeded = false; + return; + } + + guiNeeded = true; + + // Check if we should reset all preferences to default values: + if (cli.isPreferencesReset()) { + resetPreferences(cli.getPreferencesReset()); + } + + // Check if we should import preferences from a file: + if (cli.isPreferencesImport()) { + importPreferences(); + } + + List loaded = importAndOpenFiles(); + + if (!cli.isBlank() && cli.isFetcherEngine()) { + fetch(cli.getFetcherEngine()).ifPresent(loaded::add); + } + + if (cli.isExportMatches()) { + if (!loaded.isEmpty()) { + if (!exportMatches(loaded)) { + return; + } + } else { + System.err.println(Localization.lang("The output option depends on a valid input option.")); + } + } + + if (cli.isGenerateCitationKeys()) { + regenerateCitationKeys(loaded); + } + + if ((cli.isWriteXmpToPdf() && cli.isEmbedBibFileInPdf()) || (cli.isWriteMetadataToPdf() && (cli.isWriteXmpToPdf() || cli.isEmbedBibFileInPdf()))) { + System.err.println("Give only one of [writeXmpToPdf, embedBibFileInPdf, writeMetadataToPdf]"); + } + + if (cli.isWriteMetadataToPdf() || cli.isWriteXmpToPdf() || cli.isEmbedBibFileInPdf()) { + if (!loaded.isEmpty()) { + writeMetadataToPdf(loaded, + cli.getWriteMetadataToPdf(), + cliPreferences.getXmpPreferences(), + cliPreferences.getFilePreferences(), + cliPreferences.getLibraryPreferences().getDefaultBibDatabaseMode(), + cliPreferences.getCustomEntryTypesRepository(), + cliPreferences.getFieldPreferences(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), + cli.isWriteXmpToPdf() || cli.isWriteMetadataToPdf(), + cli.isEmbedBibFileInPdf() || cli.isWriteMetadataToPdf()); + } + } + + if (cli.isFileExport()) { + if (!loaded.isEmpty()) { + exportFile(loaded, cli.getFileExport().split(",")); + LOGGER.debug("Finished export"); + } else { + System.err.println(Localization.lang("The output option depends on a valid import option.")); + } + } + + if (cli.isPreferencesExport()) { + try { + cliPreferences.exportPreferences(Path.of(cli.getPreferencesExport())); + } catch (JabRefException ex) { + LOGGER.error("Cannot export preferences", ex); + } + } + + if (!cli.isBlank() && cli.isAuxImport()) { + doAuxImport(loaded); + } + + if (cli.isBlank()) { + uiCommands.add(new UiCommand.BlankWorkspace()); + } + + if (!cli.isBlank() && cli.isJumpToKey()) { + uiCommands.add(new UiCommand.JumpToEntryKey(cli.getJumpToKey())); + } + + if (!cli.isBlank() && !loaded.isEmpty()) { + uiCommands.add(new UiCommand.OpenDatabases(loaded)); + } + } + + private static void writeMetadataToPdf(List loaded, + String filesAndCiteKeys, + XmpPreferences xmpPreferences, + FilePreferences filePreferences, + BibDatabaseMode databaseMode, + BibEntryTypesManager entryTypesManager, + FieldPreferences fieldPreferences, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { + if (loaded.isEmpty()) { + LOGGER.error("The write xmp option depends on a valid import option."); + return; + } + ParserResult pr = loaded.getLast(); + BibDatabaseContext databaseContext = pr.getDatabaseContext(); + + XmpPdfExporter xmpPdfExporter = new XmpPdfExporter(xmpPreferences); + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter = new EmbeddedBibFilePdfExporter(databaseMode, entryTypesManager, fieldPreferences); + + if ("all".equals(filesAndCiteKeys)) { + for (BibEntry entry : databaseContext.getEntries()) { + writeMetadataToPDFsOfEntry( + databaseContext, + entry.getCitationKey().orElse(""), + entry, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); + } + return; + } + + List citeKeys = new ArrayList<>(); + List pdfs = new ArrayList<>(); + for (String fileOrCiteKey : filesAndCiteKeys.split(",")) { + if (fileOrCiteKey.toLowerCase(Locale.ROOT).endsWith(".pdf")) { + pdfs.add(fileOrCiteKey); + } else { + citeKeys.add(fileOrCiteKey); + } + } + + writeMetadataToPdfByCitekey( + databaseContext, + citeKeys, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); + writeMetadataToPdfByFileNames( + databaseContext, + pdfs, + filePreferences, + xmpPdfExporter, + embeddedBibFilePdfExporter, + abbreviationRepository, + writeXMP, + embeddBibfile); + } + + private static void writeMetadataToPDFsOfEntry(BibDatabaseContext databaseContext, + String citeKey, + BibEntry entry, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embedBibfile) { + try { + if (writeXMP) { + if (xmpPdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { + System.out.printf("Successfully written XMP metadata on at least one linked file of %s%n", citeKey); + } else { + System.err.printf("Cannot write XMP metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); + } + } + if (embedBibfile) { + if (embeddedBibFilePdfExporter.exportToAllFilesOfEntry(databaseContext, filePreferences, entry, List.of(entry), abbreviationRepository)) { + System.out.printf("Successfully embedded metadata on at least one linked file of %s%n", citeKey); + } else { + System.out.printf("Cannot embed metadata on any linked files of %s. Make sure there is at least one linked file and the path is correct.%n", citeKey); + } + } + } catch (Exception e) { + LOGGER.error("Failed writing metadata on a linked file of {}.", citeKey); + } + } + + private static void writeMetadataToPdfByCitekey(BibDatabaseContext databaseContext, + List citeKeys, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { + for (String citeKey : citeKeys) { + List bibEntryList = databaseContext.getDatabase().getEntriesByCitationKey(citeKey); + if (bibEntryList.isEmpty()) { + System.err.printf("Skipped - Cannot find %s in library.%n", citeKey); + continue; + } + for (BibEntry entry : bibEntryList) { + writeMetadataToPDFsOfEntry(databaseContext, citeKey, entry, filePreferences, xmpPdfExporter, embeddedBibFilePdfExporter, abbreviationRepository, writeXMP, embeddBibfile); + } + } + } + + private static void writeMetadataToPdfByFileNames(BibDatabaseContext databaseContext, + List pdfs, + FilePreferences filePreferences, + XmpPdfExporter xmpPdfExporter, + EmbeddedBibFilePdfExporter embeddedBibFilePdfExporter, + JournalAbbreviationRepository abbreviationRepository, + boolean writeXMP, + boolean embeddBibfile) { + for (String fileName : pdfs) { + Path filePath = Path.of(fileName); + if (!filePath.isAbsolute()) { + filePath = FileUtil.find(fileName, databaseContext.getFileDirectories(filePreferences)).orElse(FileUtil.find(fileName, List.of(Path.of("").toAbsolutePath())).orElse(filePath)); + } + if (Files.exists(filePath)) { + try { + if (writeXMP) { + if (xmpPdfExporter.exportToFileByPath(databaseContext, filePreferences, filePath, abbreviationRepository)) { + System.out.printf("Successfully written XMP metadata of at least one entry to %s%n", fileName); + } else { + System.out.printf("File %s is not linked to any entry in database.%n", fileName); + } + } + if (embeddBibfile) { + if (embeddedBibFilePdfExporter.exportToFileByPath(databaseContext, filePreferences, filePath, abbreviationRepository)) { + System.out.printf("Successfully embedded XMP metadata of at least one entry to %s%n", fileName); + } else { + System.out.printf("File %s is not linked to any entry in database.%n", fileName); + } + } + } catch (IOException e) { + LOGGER.error("Error accessing file '{}'.", fileName); + } catch (Exception e) { + LOGGER.error("Error writing entry to {}.", fileName); + } + } else { + LOGGER.error("Skipped - PDF {} does not exist", fileName); + } + } + } + + private boolean exportMatches(List loaded) { + String[] data = cli.getExportMatches().split(","); + String searchTerm = data[0].replace("\\$", " "); // enables blanks within the search term: + // $ stands for a blank + ParserResult pr = loaded.getLast(); + BibDatabaseContext databaseContext = pr.getDatabaseContext(); + + SearchPreferences searchPreferences = cliPreferences.getSearchPreferences(); + SearchQuery query = new SearchQuery(searchTerm, searchPreferences.getSearchFlags()); + + List matches; + try { + // extract current thread task executor from indexManager + matches = new DatabaseSearcher(query, databaseContext, new CurrentThreadTaskExecutor(), cliPreferences).getMatches(); + } catch (IOException e) { + LOGGER.error("Error occurred when searching", e); + return false; + } + + // export matches + if (!matches.isEmpty()) { + String formatName; + + // read in the export format, take default format if no format entered + switch (data.length) { + case 3 -> formatName = data[2]; + case 2 -> + // default exporter: bib file + formatName = "bib"; + default -> { + System.err.println(Localization.lang("Output file missing").concat(". \n \t ") + .concat(Localization.lang("Usage")).concat(": ") + CliOptions.getExportMatchesSyntax()); + guiNeeded = false; + return false; + } + } + + if ("bib".equals(formatName)) { + // output a bib file as default or if + // provided exportFormat is "bib" + saveDatabase(new BibDatabase(matches), data[1]); + LOGGER.debug("Finished export"); + } else { + // export new database + ExporterFactory exporterFactory = ExporterFactory.create(cliPreferences); + Optional exporter = exporterFactory.getExporterByName(formatName); + if (exporter.isEmpty()) { + System.err.println(Localization.lang("Unknown export format %0", formatName)); + } else { + // We have an TemplateExporter instance: + try { + System.out.println(Localization.lang("Exporting %0", data[1])); + exporter.get().export( + databaseContext, + Path.of(data[1]), + matches, + Collections.emptyList(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + } catch (Exception ex) { + System.err.println(Localization.lang("Could not export file '%0' (reason: %1)", data[1], Throwables.getStackTraceAsString(ex))); + } + } + } + } else { + System.err.println(Localization.lang("No search matches.")); + } + return true; + } + + private void doAuxImport(List loaded) { + boolean usageMsg; + + if (!loaded.isEmpty()) { + usageMsg = generateAux(loaded, cli.getAuxImport().split(",")); + } else { + usageMsg = true; + } + + if (usageMsg) { + System.out.println(Localization.lang("no base-BibTeX-file specified!")); + System.out.println(Localization.lang("usage") + " :"); + System.out.println("jabref --aux infile[.aux],outfile[.bib] base-BibTeX-file"); + } + } + + /** + * @return List of opened files (could be .bib, but also other formats). May also contain error results. + */ + private List importAndOpenFiles() { + List loaded = new ArrayList<>(); + List toImport = new ArrayList<>(); + if (!cli.isBlank() && (!cli.getLeftOver().isEmpty())) { + for (String aLeftOver : cli.getLeftOver()) { + // Leftover arguments that have a "bib" extension are interpreted as + // BIB files to open. Other files, and files that could not be opened + // as bib, we try to import instead. + boolean bibExtension = aLeftOver.toLowerCase(Locale.ENGLISH).endsWith("bib"); + + ParserResult pr = new ParserResult(); + if (bibExtension) { + try { + pr = OpenDatabase.loadDatabase( + Path.of(aLeftOver), + cliPreferences.getImportFormatPreferences(), + fileUpdateMonitor); + // In contrast to org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed, we do not execute OpenDatabaseAction.performPostOpenActions(result, dialogService); + } catch (IOException ex) { + pr = ParserResult.fromError(ex); + LOGGER.error("Error opening file '{}'", aLeftOver, ex); + } + } + + if (!bibExtension || (pr.isEmpty())) { + // We will try to import this file. Normally we + // will import it into a new tab, but if this import has + // been initiated by another instance through the remote + // listener, we will instead import it into the current library. + // This will enable easy integration with web browsers that can + // open a reference file in JabRef. + if (startupMode == Mode.INITIAL_START) { + toImport.add(aLeftOver); + } else { + loaded.add(importToOpenBase(aLeftOver).orElse(new ParserResult())); + } + } else { + loaded.add(pr); + } + } + } + + if (!cli.isBlank() && cli.isFileImport()) { + toImport.add(cli.getFileImport()); + } + + for (String filenameString : toImport) { + importFile(filenameString).ifPresent(loaded::add); + } + + if (!cli.isBlank() && cli.isImportToOpenBase()) { + importToOpenBase(cli.getImportToOpenBase()).ifPresent(loaded::add); + } + + if (!cli.isBlank() && cli.isBibtexImport()) { + importBibtexToOpenBase(cli.getBibtexImport(), cliPreferences.getImportFormatPreferences()).ifPresent(loaded::add); + } + + return loaded; + } + + private boolean generateAux(List loaded, String[] data) { + if (data.length == 2) { + ParserResult pr = loaded.getFirst(); + AuxCommandLine acl = new AuxCommandLine(data[0], pr.getDatabase()); + BibDatabase newBase = acl.perform(); + + boolean notSavedMsg = false; + + // write an output, if something could be resolved + if ((newBase != null) && newBase.hasEntries()) { + String subName = StringUtil.getCorrectFileName(data[1], "bib"); + saveDatabase(newBase, subName); + notSavedMsg = true; + } + + if (!notSavedMsg) { + System.out.println(Localization.lang("no library generated")); + } + return false; + } else { + return true; + } + } + + private void saveDatabase(BibDatabase newBase, String subName) { + try { + System.out.println(Localization.lang("Saving") + ": " + subName); + try (AtomicFileWriter fileWriter = new AtomicFileWriter(Path.of(subName), StandardCharsets.UTF_8)) { + BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); + SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() + .withReformatOnSave(cliPreferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); + BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter( + bibWriter, + saveConfiguration, + cliPreferences.getFieldPreferences(), + cliPreferences.getCitationKeyPatternPreferences(), + entryTypesManager); + databaseWriter.saveDatabase(new BibDatabaseContext(newBase)); + + // Show just a warning message if encoding did not work for all characters: + if (fileWriter.hasEncodingProblems()) { + System.err.println(Localization.lang("Warning") + ": " + + Localization.lang("UTF-8 could not be used to encode the following characters: %0", fileWriter.getEncodingProblems())); + } + } + } catch (IOException ex) { + System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage()); + } + } + + private void exportFile(List loaded, String[] data) { + if (data.length == 1) { + // This signals that the latest import should be stored in BibTeX + // format to the given file. + if (!loaded.isEmpty()) { + ParserResult pr = loaded.getLast(); + if (!pr.isInvalid()) { + saveDatabase(pr.getDatabase(), data[0]); + } + } else { + System.err.println(Localization.lang("The output option depends on a valid import option.")); + } + } else if (data.length == 2) { + // This signals that the latest import should be stored in the given + // format to the given file. + ParserResult parserResult = loaded.getLast(); + + Path path = parserResult.getPath().get().toAbsolutePath(); + BibDatabaseContext databaseContext = parserResult.getDatabaseContext(); + databaseContext.setDatabasePath(path); + List fileDirForDatabase = databaseContext + .getFileDirectories(cliPreferences.getFilePreferences()); + System.out.println(Localization.lang("Exporting %0", data[0])); + ExporterFactory exporterFactory = ExporterFactory.create(cliPreferences); + Optional exporter = exporterFactory.getExporterByName(data[1]); + if (exporter.isEmpty()) { + System.err.println(Localization.lang("Unknown export format %0", data[1])); + } else { + // We have an exporter: + try { + exporter.get().export( + parserResult.getDatabaseContext(), + Path.of(data[0]), + parserResult.getDatabaseContext().getDatabase().getEntries(), + fileDirForDatabase, + Injector.instantiateModelOrService(JournalAbbreviationRepository.class)); + } catch (Exception ex) { + System.err.println(Localization.lang("Could not export file '%0' (reason: %1)", data[0], Throwables.getStackTraceAsString(ex))); + } + } + } + } + + private void importPreferences() { + try { + cliPreferences.importPreferences(Path.of(cli.getPreferencesImport())); + Injector.setModelOrService(BibEntryTypesManager.class, cliPreferences.getCustomEntryTypesRepository()); + } catch (JabRefException ex) { + LOGGER.error("Cannot import preferences", ex); + } + } + + private void resetPreferences(String value) { + if ("all".equals(value.trim())) { + try { + System.out.println(Localization.lang("Setting all preferences to default values.")); + cliPreferences.clear(); + new SharedDatabasePreferences().clear(); + } catch (BackingStoreException e) { + System.err.println(Localization.lang("Unable to clear preferences.")); + LOGGER.error("Unable to clear preferences", e); + } + } else { + String[] keys = value.split(","); + for (String key : keys) { + try { + cliPreferences.deleteKey(key.trim()); + System.out.println(Localization.lang("Resetting preference key '%0'", key.trim())); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + } + } + } + + private void regenerateCitationKeys(List loaded) { + for (ParserResult parserResult : loaded) { + BibDatabase database = parserResult.getDatabase(); + + LOGGER.info(Localization.lang("Regenerating citation keys according to metadata")); + + CitationKeyGenerator keyGenerator = new CitationKeyGenerator( + parserResult.getDatabaseContext(), + cliPreferences.getCitationKeyPatternPreferences()); + for (BibEntry entry : database.getEntries()) { + keyGenerator.generateAndSetKey(entry); + } + } + } + + /** + * Run an entry fetcher from the command line. + * + * @param fetchCommand A string containing both the name of the fetcher to use and the search query, separated by a : + * @return A parser result containing the entries fetched or null if an error occurred. + */ + private Optional fetch(String fetchCommand) { + if ((fetchCommand == null) || !fetchCommand.contains(":")) { + System.out.println(Localization.lang("Expected syntax for --fetch=':'")); + System.out.println(Localization.lang("The following fetchers are available:")); + return Optional.empty(); + } + + String[] split = fetchCommand.split(":"); + String engine = split[0]; + String query = split[1]; + + Set fetchers = WebFetchers.getSearchBasedFetchers( + cliPreferences.getImportFormatPreferences(), + cliPreferences.getImporterPreferences()); + Optional selectedFetcher = fetchers.stream() + .filter(fetcher -> fetcher.getName().equalsIgnoreCase(engine)) + .findFirst(); + if (selectedFetcher.isEmpty()) { + System.out.println(Localization.lang("Could not find fetcher '%0'", engine)); + + System.out.println(Localization.lang("The following fetchers are available:")); + fetchers.forEach(fetcher -> System.out.println(" " + fetcher.getName())); + + return Optional.empty(); + } else { + System.out.println(Localization.lang("Running query '%0' with fetcher '%1'.", query, engine)); + System.out.print(Localization.lang("Please wait...")); + try { + List matches = selectedFetcher.get().performSearch(query); + if (matches.isEmpty()) { + System.out.println("\r" + Localization.lang("No results found.")); + return Optional.empty(); + } else { + System.out.println("\r" + Localization.lang("Found %0 results.", String.valueOf(matches.size()))); + return Optional.of(new ParserResult(matches)); + } + } catch (FetcherException e) { + LOGGER.error("Error while fetching", e); + return Optional.empty(); + } + } + } + + public boolean shouldShutDown() { + return cli.isDisableGui() || cli.isShowVersion() || !guiNeeded; + } + + public List getUiCommands() { + return uiCommands; + } +} diff --git a/jabref/src/main/java/org/jabref/cli/AuxCommandLine.java b/jabref/src/main/java/org/jabref/cli/AuxCommandLine.java new file mode 100644 index 00000000..ea566f44 --- /dev/null +++ b/jabref/src/main/java/org/jabref/cli/AuxCommandLine.java @@ -0,0 +1,33 @@ +package org.jabref.cli; + +import java.nio.file.Path; + +import org.jabref.logic.auxparser.AuxParser; +import org.jabref.logic.auxparser.AuxParserResult; +import org.jabref.logic.auxparser.AuxParserStatisticsProvider; +import org.jabref.logic.auxparser.DefaultAuxParser; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.strings.StringUtil; + +public class AuxCommandLine { + private final String auxFile; + private final BibDatabase database; + + public AuxCommandLine(String auxFile, BibDatabase database) { + this.auxFile = StringUtil.getCorrectFileName(auxFile, "aux"); + this.database = database; + } + + public BibDatabase perform() { + BibDatabase subDatabase = null; + + if (!auxFile.isEmpty() && (database != null)) { + AuxParser auxParser = new DefaultAuxParser(database); + AuxParserResult result = auxParser.parse(Path.of(auxFile)); + subDatabase = result.getGeneratedBibDatabase(); + // print statistics + System.out.println(new AuxParserStatisticsProvider(result).getInformation(true)); + } + return subDatabase; + } +} diff --git a/jabref/src/main/java/org/jabref/cli/CliOptions.java b/jabref/src/main/java/org/jabref/cli/CliOptions.java new file mode 100644 index 00000000..9a4d1636 --- /dev/null +++ b/jabref/src/main/java/org/jabref/cli/CliOptions.java @@ -0,0 +1,367 @@ +package org.jabref.cli; + +import java.util.List; +import java.util.Objects; + +import javafx.util.Pair; + +import org.jabref.logic.exporter.ExporterFactory; +import org.jabref.logic.importer.ImportFormatReader; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.os.OS; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.BuildInfo; +import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * Holds the command line options. It parses it using Apache Commons CLI. + */ +public class CliOptions { + private static final int WIDTH = 100; // Number of characters per line before a line break must be added. + private static final String WRAPPED_LINE_PREFIX = ""; // If a line break is added, this prefix will be inserted at the beginning of the next line + private static final String STRING_TABLE_DELIMITER = " : "; + + private final CommandLine commandLine; + private final List leftOver; + + public CliOptions(String[] args) throws ParseException { + Options options = getOptions(); + this.commandLine = new DefaultParser().parse(options, args, true); + this.leftOver = commandLine.getArgList(); + } + + public static String getExportMatchesSyntax() { + return "[%s]searchTerm,outputFile:%s[,%s]".formatted( + Localization.lang("field"), + Localization.lang("file"), + Localization.lang("exportFormat")); + } + + public boolean isHelp() { + return commandLine.hasOption("help"); + } + + public boolean isShowVersion() { + return commandLine.hasOption("version"); + } + + public boolean isBlank() { + return commandLine.hasOption("blank"); + } + + public boolean isDisableGui() { + return commandLine.hasOption("nogui"); + } + + public boolean isPreferencesExport() { + return commandLine.hasOption("prexp"); + } + + public String getPreferencesExport() { + return commandLine.getOptionValue("prexp", "jabref_prefs.xml"); + } + + public boolean isPreferencesImport() { + return commandLine.hasOption("primp"); + } + + public String getPreferencesImport() { + return commandLine.getOptionValue("primp", "jabref_prefs.xml"); + } + + public boolean isPreferencesReset() { + return commandLine.hasOption("prdef"); + } + + public String getPreferencesReset() { + return commandLine.getOptionValue("prdef"); + } + + public boolean isFileExport() { + return commandLine.hasOption("output"); + } + + public String getFileExport() { + return commandLine.getOptionValue("output"); + } + + public boolean isBibtexImport() { + return commandLine.hasOption("importBibtex"); + } + + public String getBibtexImport() { + return commandLine.getOptionValue("importBibtex"); + } + + public boolean isFileImport() { + return commandLine.hasOption("import"); + } + + public String getFileImport() { + return commandLine.getOptionValue("import"); + } + + public boolean isAuxImport() { + return commandLine.hasOption("aux"); + } + + public String getAuxImport() { + return commandLine.getOptionValue("aux"); + } + + public boolean isImportToOpenBase() { + return commandLine.hasOption("importToOpen"); + } + + public String getImportToOpenBase() { + return commandLine.getOptionValue("importToOpen"); + } + + public boolean isDebugLogging() { + return commandLine.hasOption("debug"); + } + + public boolean isFetcherEngine() { + return commandLine.hasOption("fetch"); + } + + public String getFetcherEngine() { + return commandLine.getOptionValue("fetch"); + } + + public boolean isExportMatches() { + return commandLine.hasOption("exportMatches"); + } + + public String getExportMatches() { + return commandLine.getOptionValue("exportMatches"); + } + + public boolean isGenerateCitationKeys() { + return commandLine.hasOption("generateCitationKeys"); + } + + public boolean isWriteXmpToPdf() { + return commandLine.hasOption("writeXmpToPdf"); + } + + public boolean isEmbedBibFileInPdf() { + return commandLine.hasOption("embedBibFileInPdf"); + } + + public boolean isWriteMetadataToPdf() { + return commandLine.hasOption("writeMetadataToPdf"); + } + + public String getWriteMetadataToPdf() { + return commandLine.hasOption("writeMetadatatoPdf") ? commandLine.getOptionValue("writeMetadataToPdf") : + commandLine.hasOption("writeXMPtoPdf") ? commandLine.getOptionValue("writeXmpToPdf") : + commandLine.hasOption("embeddBibfileInPdf") ? commandLine.getOptionValue("embeddBibfileInPdf") : null; + } + + public String getJumpToKey() { + return commandLine.getOptionValue("jumpToKey"); + } + + public boolean isJumpToKey() { + return commandLine.hasOption("jumpToKey"); + } + + private static Options getOptions() { + Options options = new Options(); + + // boolean options + options.addOption("h", "help", false, Localization.lang("Display help on command line options")); + options.addOption("n", "nogui", false, Localization.lang("No GUI. Only process command line options")); + options.addOption("g", "generateCitationKeys", false, Localization.lang("Regenerate all keys for the entries in a BibTeX file")); + options.addOption("b", "blank", false, Localization.lang("Do not open any files at startup")); + options.addOption("v", "version", false, Localization.lang("Display version")); + options.addOption(null, "debug", false, Localization.lang("Show debug level messages")); + + options.addOption(Option + .builder("i") + .longOpt("import") + .desc("%s: '%s'".formatted(Localization.lang("Import file"), "-i library.bib")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption(Option + .builder() + .longOpt("importToOpen") + .desc(Localization.lang("Same as --import, but will be imported to the opened tab")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption(Option + .builder("ib") + .longOpt("importBibtex") + .desc("%s: '%s'".formatted(Localization.lang("Import BibTeX"), "-ib @article{entry}")) + .hasArg() + .argName("BIBTEX_STRING") + .build()); + + options.addOption(Option + .builder("o") + .longOpt("output") + .desc("%s: '%s'".formatted(Localization.lang("Export an input to a file"), "-i db.bib -o db.htm,html")) + .hasArg() + .argName("FILE[,FORMAT]") + .build()); + + options.addOption(Option + .builder("m") + .longOpt("exportMatches") + .desc("%s: '%s'".formatted(Localization.lang("Matching"), "-i db.bib -m author=Newton,search.htm,html")) + .hasArg() + .argName("QUERY,FILE[,FORMAT]") + .build()); + + options.addOption(Option + .builder("f") + .longOpt("fetch") + .desc("%s: '%s'".formatted(Localization.lang("Run fetcher"), "-f Medline/PubMed:cancer")) + .hasArg() + .argName("FETCHER:QUERY") + .build()); + + options.addOption(Option + .builder("a") + .longOpt("aux") + .desc("%s: '%s'".formatted(Localization.lang("Sublibrary from AUX to BibTeX"), "-a thesis.aux,new.bib")) + .hasArg() + .argName("FILE[.aux],FILE[.bib] FILE") + .build()); + + options.addOption(Option + .builder("x") + .longOpt("prexp") + .desc("%s: '%s'".formatted(Localization.lang("Export preferences to a file"), "-x prefs.xml")) + .hasArg() + .argName("[FILE]") + .build()); + + options.addOption(Option + .builder("p") + .longOpt("primp") + .desc("%s: '%s'".formatted(Localization.lang("Import preferences from a file"), "-p prefs.xml")) + .hasArg() + .argName("[FILE]") + .build()); + + options.addOption(Option + .builder("d") + .longOpt("prdef") + .desc("%s: '%s'".formatted(Localization.lang("Reset preferences"), "-d mainFontSize,newline' or '-d all")) + .hasArg() + .argName("KEY1[,KEY2][,KEYn] | all") + .build()); + + options.addOption(Option + .builder() + .longOpt("writeXmpToPdf") + .desc("%s: '%s'".formatted(Localization.lang("Write BibTeX as XMP metadata to PDF."), "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption(Option + .builder() + .longOpt("embedBibFileInPdf") + .desc("%s: '%s'".formatted(Localization.lang("Embed BibTeX as attached file in PDF."), "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption(Option + .builder("w") + .longOpt("writeMetadataToPdf") + .desc("%s: '%s'".formatted(Localization.lang("Write BibTeX to PDF (XMP and embedded)"), "-w pathToMyOwnPaper.pdf")) + .hasArg() + .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") + .build()); + + options.addOption(Option + .builder("j") + .longOpt("jumpToKey") + .desc("%s: '%s'".formatted(Localization.lang("Jump to the entry of the given citation key."), "-j key")) + .hasArg() + .argName("CITATIONKEY") + .build()); + + return options; + } + + public void displayVersion() { + System.out.println(getVersionInfo()); + } + + public static void printUsage(CliPreferences preferences) { + String header = ""; + + ImportFormatReader importFormatReader = new ImportFormatReader( + preferences.getImporterPreferences(), + preferences.getImportFormatPreferences(), + preferences.getCitationKeyPatternPreferences(), + new DummyFileUpdateMonitor() + ); + List> importFormats = importFormatReader + .getImportFormats().stream() + .map(format -> new Pair<>(format.getName(), format.getId())) + .toList(); + String importFormatsIntro = Localization.lang("Available import formats"); + String importFormatsList = "%s:%n%s%n".formatted(importFormatsIntro, alignStringTable(importFormats)); + + ExporterFactory exporterFactory = ExporterFactory.create(preferences); + List> exportFormats = exporterFactory + .getExporters().stream() + .map(format -> new Pair<>(format.getName(), format.getId())) + .toList(); + String outFormatsIntro = Localization.lang("Available export formats"); + String outFormatsList = "%s:%n%s%n".formatted(outFormatsIntro, alignStringTable(exportFormats)); + + String footer = '\n' + importFormatsList + outFormatsList + "\nPlease report issues at https://github.com/JabRef/jabref/issues."; + + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp(WIDTH, "jabref [OPTIONS] [BIBTEX_FILE]\n\nOptions:", header, getOptions(), footer, true); + } + + private String getVersionInfo() { + return "JabRef %s".formatted(new BuildInfo().version); + } + + public List getLeftOver() { + return leftOver; + } + + protected static String alignStringTable(List> table) { + StringBuilder sb = new StringBuilder(); + + int maxLength = table.stream() + .mapToInt(pair -> Objects.requireNonNullElse(pair.getKey(), "").length()) + .max().orElse(0); + + for (Pair pair : table) { + int padding = Math.max(0, maxLength - pair.getKey().length()); + sb.append(WRAPPED_LINE_PREFIX); + sb.append(pair.getKey()); + + sb.append(StringUtil.repeatSpaces(padding)); + + sb.append(STRING_TABLE_DELIMITER); + sb.append(pair.getValue()); + sb.append(OS.NEWLINE); + } + + return sb.toString(); + } +} diff --git a/jabref/src/main/java/org/jabref/cli/JabKit.java b/jabref/src/main/java/org/jabref/cli/JabKit.java new file mode 100644 index 00000000..72cab155 --- /dev/null +++ b/jabref/src/main/java/org/jabref/cli/JabKit.java @@ -0,0 +1,241 @@ +package org.jabref.cli; + +import java.io.File; +import java.io.IOException; +import java.net.Authenticator; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import org.jabref.logic.UiCommand; +import org.jabref.logic.journals.JournalAbbreviationLoader; +import org.jabref.logic.journals.JournalAbbreviationRepository; +import org.jabref.logic.net.ProxyAuthenticator; +import org.jabref.logic.net.ProxyPreferences; +import org.jabref.logic.net.ProxyRegisterer; +import org.jabref.logic.net.ssl.SSLPreferences; +import org.jabref.logic.net.ssl.TrustStoreManager; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.preferences.JabRefCliPreferences; +import org.jabref.logic.protectedterms.ProtectedTermsLoader; +import org.jabref.logic.remote.RemotePreferences; +import org.jabref.logic.remote.client.RemoteClient; +import org.jabref.logic.util.BuildInfo; +import org.jabref.logic.util.Directories; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.DummyFileUpdateMonitor; +import org.jabref.model.util.FileUpdateMonitor; + +import com.airhacks.afterburner.injection.Injector; +import org.apache.commons.cli.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.tinylog.configuration.Configuration; + +/// Entrypoint for a command-line only version of JabRef. +/// It does not open any dialogs, just parses the command line arguments and outputs text and creates/modifies files. +/// +/// See [Command Line Interface Guidelines](https://clig.dev/) for general guidelines how to design a good CLI interface. +/// +/// It does not open any GUI. +/// For the GUI application see {@link org.jabref.Launcher}. +/// +/// Does not do any preference migrations. +public class JabKit { + private static Logger LOGGER; + + public static void main(String[] args) { + initLogging(args); + + final JabRefCliPreferences preferences = JabRefCliPreferences.getInstance(); + Injector.setModelOrService(CliPreferences.class, preferences); + + FileUpdateMonitor fileUpdateMonitor = new DummyFileUpdateMonitor(); + + List uiCommands = processArguments(args, preferences, fileUpdateMonitor); + if (!uiCommands.isEmpty()) { + LOGGER.error("No GUI needed, but UI commands were returned. Exiting."); + } + } + + private static void systemExit() { + LOGGER.debug("JabRef shut down after processing command line arguments"); + // A clean shutdown takes 60s time + // We don't need the clean shutdown here + System.exit(0); + } + + public static List processArguments(String[] args, JabRefCliPreferences preferences, FileUpdateMonitor fileUpdateMonitor) { + try { + Injector.setModelOrService(BuildInfo.class, new BuildInfo()); + + // Early exit in case another instance is already running + if (!handleMultipleAppInstances(args, preferences.getRemotePreferences())) { + systemExit(); + } + + BibEntryTypesManager entryTypesManager = preferences.getCustomEntryTypesRepository(); + Injector.setModelOrService(BibEntryTypesManager.class, entryTypesManager); + + Injector.setModelOrService(JournalAbbreviationRepository.class, JournalAbbreviationLoader.loadRepository(preferences.getJournalAbbreviationPreferences())); + Injector.setModelOrService(ProtectedTermsLoader.class, new ProtectedTermsLoader(preferences.getProtectedTermsPreferences())); + + configureProxy(preferences.getProxyPreferences()); + configureSSL(preferences.getSSLPreferences()); + + clearOldSearchIndices(); + + try { + Injector.setModelOrService(FileUpdateMonitor.class, fileUpdateMonitor); + + // Process arguments + ArgumentProcessor argumentProcessor = new ArgumentProcessor( + args, + ArgumentProcessor.Mode.INITIAL_START, + preferences, + fileUpdateMonitor, + entryTypesManager); + argumentProcessor.processArguments(); + if (argumentProcessor.shouldShutDown()) { + LOGGER.debug("JabRef shut down after processing command line arguments"); + // A clean shutdown takes 60s time + // We don't need the clean shutdown here + System.exit(0); + return null; + } + + return new ArrayList<>(argumentProcessor.getUiCommands()); + } catch (ParseException e) { + LOGGER.error("Problem parsing arguments", e); + CliOptions.printUsage(preferences); + systemExit(); + return null; + } + } catch (Exception ex) { + LOGGER.error("Unexpected exception", ex); + systemExit(); + return null; + } + } + + /** + * This needs to be called as early as possible. After the first log write, it + * is not possible to alter the log configuration programmatically anymore. + */ + public static void initLogging(String[] args) { + // routeLoggingToSlf4J + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + + // We must configure logging as soon as possible, which is why we cannot wait for the usual + // argument parsing workflow to parse logging options .e.g. --debug + boolean isDebugEnabled; + try { + CliOptions cliOptions = new CliOptions(args); + isDebugEnabled = cliOptions.isDebugLogging(); + } catch (ParseException e) { + isDebugEnabled = false; + } + + // addLogToDisk + // We cannot use `Injector.instantiateModelOrService(BuildInfo.class).version` here, because this initializes logging + Path directory = Directories.getLogDirectory(new BuildInfo().version); + try { + Files.createDirectories(directory); + } catch (IOException e) { + LOGGER = LoggerFactory.getLogger(JabKit.class); + LOGGER.error("Could not create log directory {}", directory, e); + return; + } + + // The "Shared File Writer" is explained at + // https://tinylog.org/v2/configuration/#shared-file-writer + Map configuration = Map.of( + "level", isDebugEnabled ? "debug" : "info", + "writerFile", "rolling file", + "writerFile.level", isDebugEnabled ? "debug" : "info", + // We need to manually join the path, because ".resolve" does not work on Windows, because ":" is not allowed in file names on Windows + "writerFile.file", directory + File.separator + "log_{date:yyyy-MM-dd_HH-mm-ss}.txt", + "writerFile.charset", "UTF-8", + "writerFile.policies", "startup", + "writerFile.backups", "30"); + configuration.forEach(Configuration::set); + + LOGGER = LoggerFactory.getLogger(JabKit.class); + } + + /** + * @return true if JabRef should continue starting up, false if it should quit. + */ + private static boolean handleMultipleAppInstances(String[] args, RemotePreferences remotePreferences) throws InterruptedException { + LOGGER.trace("Checking for remote handling..."); + if (remotePreferences.useRemoteServer()) { + // Try to contact already running JabRef + RemoteClient remoteClient = new RemoteClient(remotePreferences.getPort()); + if (remoteClient.ping()) { + LOGGER.debug("Pinging other instance succeeded."); + if (args.length == 0) { + // There is already a server out there, avoid showing log "Passing arguments" while no arguments are provided. + LOGGER.warn("This JabRef instance is already running. Please switch to that instance."); + } else { + // We are not alone, there is already a server out there, send command line arguments to other instance + LOGGER.debug("Passing arguments passed on to running JabRef..."); + if (remoteClient.sendCommandLineArguments(args)) { + // So we assume it's all taken care of, and quit. + // Output to both to the log and the screen. Therefore, we do not have an additional System.out.println. + LOGGER.info("Arguments passed on to running JabRef instance. Shutting down."); + } else { + LOGGER.warn("Could not communicate with other running JabRef instance."); + } + } + // We do not launch a new instance in presence if there is another instance running + return false; + } else { + LOGGER.debug("Could not ping JabRef instance."); + } + } + return true; + } + + private static void configureProxy(ProxyPreferences proxyPreferences) { + ProxyRegisterer.register(proxyPreferences); + if (proxyPreferences.shouldUseProxy() && proxyPreferences.shouldUseAuthentication()) { + Authenticator.setDefault(new ProxyAuthenticator()); + } + } + + private static void configureSSL(SSLPreferences sslPreferences) { + TrustStoreManager.createTruststoreFileIfNotExist(Path.of(sslPreferences.getTruststorePath())); + } + + private static void clearOldSearchIndices() { + Path currentIndexPath = Directories.getFulltextIndexBaseDirectory(); + Path appData = currentIndexPath.getParent(); + + try { + Files.createDirectories(currentIndexPath); + } catch (IOException e) { + LOGGER.error("Could not create index directory {}", appData, e); + } + + try (DirectoryStream stream = Files.newDirectoryStream(appData)) { + for (Path path : stream) { + if (Files.isDirectory(path) && !path.toString().endsWith("ssl") && path.toString().contains("lucene") + && !path.equals(currentIndexPath)) { + LOGGER.info("Deleting out-of-date fulltext search index at {}.", path); + Files.walk(path) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + } catch (IOException e) { + LOGGER.error("Could not access app-directory at {}", appData, e); + } + } +} diff --git a/jabref/src/main/java/org/jabref/cli/JournalListMvGenerator.java b/jabref/src/main/java/org/jabref/cli/JournalListMvGenerator.java new file mode 100644 index 00000000..dad187dc --- /dev/null +++ b/jabref/src/main/java/org/jabref/cli/JournalListMvGenerator.java @@ -0,0 +1,75 @@ +package org.jabref.cli; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.logic.journals.Abbreviation; +import org.jabref.logic.journals.JournalAbbreviationLoader; + +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.jooq.lambda.Unchecked; + +public class JournalListMvGenerator { + + public static void main(String[] args) throws IOException { + boolean verbose = (args.length == 1) && ("--verbose".equals(args[0])); + + Path abbreviationsDirectory = Path.of("buildres", "abbrv.jabref.org", "journals"); + if (!Files.exists(abbreviationsDirectory)) { + System.out.println("Path " + abbreviationsDirectory.toAbsolutePath() + " does not exist"); + System.exit(0); + } + Path journalListMvFile = Path.of("build", "resources", "main", "journals", "journal-list.mv"); + + Set ignoredNames = Set.of( + // remove all lists without dot in them: + // we use abbreviation lists containing dots in them only (to be consistent) + "journal_abbreviations_entrez.csv", + "journal_abbreviations_medicus.csv", + "journal_abbreviations_webofscience-dotless.csv", + + // we currently do not have good support for BibTeX strings + "journal_abbreviations_ieee_strings.csv" + ); + + Files.createDirectories(journalListMvFile.getParent()); + + try (DirectoryStream stream = Files.newDirectoryStream(abbreviationsDirectory, "*.csv"); + MVStore store = new MVStore.Builder(). + fileName(journalListMvFile.toString()). + compressHigh(). + open()) { + MVMap fullToAbbreviation = store.openMap("FullToAbbreviation"); + stream.forEach(Unchecked.consumer(path -> { + String fileName = path.getFileName().toString(); + System.out.print("Checking "); + System.out.print(fileName); + if (ignoredNames.contains(fileName)) { + System.out.println(" ignored"); + } else { + System.out.println("..."); + Collection abbreviations = JournalAbbreviationLoader.readAbbreviationsFromCsvFile(path); + Map abbreviationMap = abbreviations + .stream() + .collect(Collectors.toMap( + Abbreviation::getName, + abbreviation -> abbreviation, + (abbreviation1, abbreviation2) -> { + if (verbose) { + System.out.println("Double entry " + abbreviation1.getName()); + } + return abbreviation2; + })); + fullToAbbreviation.putAll(abbreviationMap); + } + })); + } + } +} diff --git a/jabref/src/main/java/org/jabref/gui/AbstractViewModel.java b/jabref/src/main/java/org/jabref/gui/AbstractViewModel.java new file mode 100644 index 00000000..d294a280 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/AbstractViewModel.java @@ -0,0 +1,5 @@ +package org.jabref.gui; + +public class AbstractViewModel { + // empty +} diff --git a/jabref/src/main/java/org/jabref/gui/Base.css b/jabref/src/main/java/org/jabref/gui/Base.css new file mode 100644 index 00000000..635c295a --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/Base.css @@ -0,0 +1,1735 @@ +.root { + /* Note that counting for odd/even starts at 0, thus the first displayed row is even */ + -jr-row-odd-background: -fx-control-inner-background-alt; + -jr-row-even-background: -fx-control-inner-background; + /* + On light theme, the text is hard to see when it's on top of the accent color. This is an alternative lighter accent color + for better text visibility. + */ + -jr-accent-alt: derive(-jr-accent, 15%); + + /* + The theme color and some derived colors from it are used for icons, tab-headers, marking of selected inputs and + hover colors for the main menu. It generally defines the look of JabRef. The highlighting colors below should + work nicely with this base color + */ + + /* original JabRef dark blue color */ + -jr-theme: #50618F; + + -jr-accent: #a3b7e6; + -jr-transparent-accent: rgba(163, 183, 230, 0.16); + -jr-checked: -jr-theme; + + -jr-selected: -jr-accent; + + -jr-hover: #0002; + -jr-hover-text: -fx-focused-text-base-color; + + /* The base gray. Most gray-tones in the application are derived from this color. */ + -jr-base: #ebebeb; + + -jr-white: #ffffff; + -jr-gray-0: #f2f2f2; + -jr-gray-1: #dddddd; + -jr-gray-2: #808080; + -jr-gray-3: #404040; + -jr-black: #000; + + /* Some blueish greys - currently not used */ + -jr-blue-gray-1: #c8d6e5; + -jr-blue-gray-1-darker: derive(-jr-blue-gray-1, -5%); + -jr-blue-gray-2: #8395a7; + -jr-blue-gray-2-darker: derive(-jr-blue-gray-2, -5%); + -jr-blue-gray-3: #576574; + -jr-blue-gray-3-darker: derive(-jr-blue-gray-3, -5%); + -jr-blue-gray-4: #222f3e; + -jr-blue-gray-4-darker: derive(-jr-blue-gray-4, -5%); + + /* Highlights */ + -jr-blue: #0abde3; + -jr-light-blue: #48dbfb; + -jr-purple: #7559C2; + -jr-light-purple: #ff9ff3; + -jr-green: #10ac84; + -jr-light-green: #1dd1a1; + -jr-red: #ee5253; + -jr-light-red: #ff6b6b; + -jr-yellow: #feca57; + -jr-orange: #ff9f43; + + /* Background specs */ + -jr-background-alt: -fx-background; + -jr-text-area-background: derive(-jr-base, 80%); + -jr-search-background: -jr-text-area-background; + -jr-toolbar: derive(-jr-base, 46.4%); + -jr-menu-background: derive(-jr-base, 46.4%); + -jr-menu-background-active: -jr-hover; + + -jr-menu-foreground: -fx-dark-text-color; + -jr-menu-item-foreground: -fx-dark-text-color; + -jr-menu-forground-active: -fx-dark-text-color; + + -jr-head-fg: -fx-text-inner-color; + + /* All icons/text on toolbars */ + -jr-theme-text: -jr-theme; + + -jr-icon-background: transparent; + -jr-icon-background-active: #0001; + -jr-icon-background-armed: #0002; + + /* Colors for messages and errors */ + -jr-info: -jr-light-green; + -jr-warn: -jr-orange; + -jr-error: -jr-light-red; + + /* Color for the small group view indicator for the number of hits */ + -jr-group-hits-bg: derive(-jr-sidepane-background, -50%); + -jr-group-hits-fg: ladder( + -jr-group-hits-bg, + -fx-light-text-color 45%, + -fx-dark-text-color 46%, + -fx-dark-text-color 59%, + -fx-mid-text-color 60% + ); + + /* Specific color for general tooltips */ + -jr-tooltip-bg: -jr-accent; + -jr-tooltip-fg: -jr-black; + + /* Finally, some specific jr styles that depend on -fx definitions in *this* style */ + -jr-sidepane-background: -jr-gray-1; + -jr-sidepane-header-background: -jr-gray-1; + -jr-sidepane-header-color: -jr-theme-text; + + /* Specs for the scrollbars */ + -jr-scrollbar-thumb: derive(-fx-outer-border, -30%); + -jr-scrollbar-track: derive(-fx-control-inner-background, -10%); + + -jr-separator: derive(-fx-color, -5%); + + -jr-search-text: -fx-text-base-color; + + /* For drag and drop actions */ + -jr-drag-target: -jr-purple; + -jr-drag-target-hover: derive(-jr-purple, 80%); + + -js-summary-text-color: #000000; + -js-summary-text-color-selected: #000000; + + /* + Here are redefinitions of the default properties of modena. They should in principle all be derived from the + above colors. Goal should be to make as few as possible direct color-changes to elements and only do this for + very specific purposes. + */ + -fx-base: -jr-base; + + /* A very light grey used for the background of windows. See also + * -fx-text-background-color, which should be used as the -fx-text-fill + * value for text painted on top of backgrounds colored with -fx-background. + */ + -fx-background: derive(-fx-base, 26.4%); + + /* Used for the inside of text boxes, password boxes, lists, trees, and + * tables. See also -fx-text-inner-color, which should be used as the + * -fx-text-fill value for text painted on top of backgrounds colored + * with -fx-control-inner-background. + */ + -fx-control-inner-background: derive(-fx-base, 95%); + /* Version of -fx-control-inner-background for alternative rows */ + -fx-control-inner-background-alt: derive(-fx-control-inner-background, -6%); + + /* One of these colors will be chosen based upon a ladder calculation + * that uses the brightness of a background color. Instead of using these + * colors directly as -fx-text-fill values, the sections in this file should + * use a derived color to match the background in use. See also: + * + * -fx-text-base-color for text on top of -fx-base, -fx-color, and -fx-body-color + * -fx-text-background-color for text on top of -fx-background + * -fx-text-inner-color for text on top of -fx-control-inner-color + * -fx-selection-bar-text for text on top of -fx-selection-bar + */ + -fx-dark-text-color: -jr-black; + -fx-mid-text-color: -jr-gray-3; + -fx-light-text-color: -jr-white; + + /* We overwrite accents -> make old stick out */ + -fx-accent: red; + + /* Default buttons color, this is similar to accent but more subtle */ + -fx-default-button: derive(-jr-accent, 50%); + + /* A bright blue for the focus indicator of objects. Typically used as the + * first color in -fx-background-color for the "focused" pseudo-class. Also + * typically used with insets of -1.4 to provide a glowing effect. + */ + -fx-focus-color: -jr-accent; + -fx-faint-focus-color: derive(-jr-accent, 50%); + + /* The color that is used in styling controls. The default value is based + * on -fx-base, but is changed by pseudoclasses to change the base color. + * For example, the "hover" pseudoclass will typically set -fx-color to + * -fx-hover-base (see below) and the "armed" pseudoclass will typically + * set -fx-color to -fx-pressed-base. + */ + -fx-color: -fx-base; + + -fx-hover-base: derive(-fx-base, 30%); + + /* A little darker than -fx-base and used as the -fx-color for the + * "armed" pseudoclass state. + * + * TODO: should this be renamed to -fx-armed-base? + */ + -fx-pressed-base: derive(-fx-base, -6%); + + /* A little darker than -fx-color and used to draw boxes around objects such + * as progress bars, scroll bars, scroll panes, trees, tables, and lists. + */ + -fx-box-border: derive(-fx-color, -5%); + + /* Darker than -fx-background and used to draw boxes around text boxes and + * password boxes. + */ + -fx-text-box-border: derive(-fx-background, -15%); + + /* Lighter than -fx-background and used to provide a small highlight when + * needed on top of -fx-background. This is never a shadow in Modena but + * keep -fx-shadow-highlight-color name to be compatible with Caspian. + */ + -fx-shadow-highlight-color: rgba(255, 255, 255, 0.07) 70%; + + /* A gradient that goes from a little darker than -fx-color on the top to + * even more darker than -fx-color on the bottom. Typically is the second + * color in the -fx-background-color list as the small thin border around + * a control. It is typically the same size as the control (i.e., insets + * are 0). + */ + -fx-outer-border: derive(-fx-color, -5%); + + /* A gradient that goes from a bit lighter than -fx-color on the top to + * a little darker at the bottom. Typically is the third color in the + * -fx-background-color list as a thin highlight inside the outer border. + * Insets are typically 1. + */ + -fx-inner-border: derive(-fx-color, 65%); + -fx-inner-border-horizontal: derive(-fx-color, 65%); + -fx-inner-border-bottomup: derive(-fx-color, 65%); + + /*-fx-inner-border: red;*/ + /*-fx-inner-border-horizontal: green;*/ + /*-fx-inner-border-bottomup: blue;*/ + + /* A gradient that goes from a little lighter than -fx-color at the top to + * a little darker than -fx-color at the bottom and is used to fill the + * body of many controls such as buttons. + */ + -fx-body-color: derive(-fx-color, 20%); + -fx-body-color-bottomup: derive(-fx-color, 20%); + -fx-body-color-to-right: derive(-fx-color, 20%); + + /* The small thin light "shadow" for mark-like objects. Typically used in + * conjunction with -fx-mark-color with an insets of 1 0 -1 0. */ + -fx-mark-color: -fx-text-base-color; + -fx-mark-highlight-color: transparent; + /*-fx-mark-highlight-color: derive(-fx-color,80%);*/ + + /* Background for items in list like things such as menus, lists, trees, + * and tables. */ + -fx-selection-bar: -jr-accent; + + /* Background color to use for selection of list cells etc. This is when + * the control doesn't have focus or the row of a previously selected item. */ + -fx-selection-bar-non-focused: lightgrey; + + /* The color to use as -fx-text-fill when painting text on top of + * backgrounds filled with -fx-selection-bar. */ + -fx-selection-bar-text: -fx-text-background-color; + + /* These are needed for Popup */ + -fx-background-color: inherit; + -fx-background-radius: inherit; + -fx-background-insets: inherit; + -fx-padding: inherit; + + /** Focus line for keyboard focus traversal on cell based controls */ + -fx-cell-focus-inner-border: derive(-fx-selection-bar, 30%); + + -fx-focused-mark-color: -fx-focused-text-base-color; + + /* Consistent size for headers of tab-pane and side-panels*/ + -jr-header-height: 3em; + + /* AI chat style */ + -jr-ai-message-user: -jr-accent; + -jr-ai-message-user-border: -jr-theme; + -jr-ai-message-ai: -jr-accent; + -jr-ai-message-ai-border: -jr-theme; + -jr-ai-message-error: -jr-error; + -jr-ai-message-error-border: derive(-jr-error, -40%); + + /* region: maintable base colors **/ + + -jr-match-1-odd: -jr-row-odd-background; + -jr-match-1-even: -jr-row-even-background; + -jr-match-1-text-color: -fx-mid-text-color; + + -jr-match-2-odd: derive(-jr-theme, 100%); + -jr-match-2-even: derive(-jr-match-2-odd, 10%); + -jr-match-2-text-color: -fx-mid-text-color; + + -jr-match-3-odd: derive(-jr-theme, 50%); + -jr-match-3-even: derive(-jr-match-3-odd, 10%); + -jr-match-3-text-color: derive(-jr-accent, 30%); + + -jr-match-4-odd: -jr-theme; + -jr-match-4-even: derive(-jr-match-4-odd, 10%); + -jr-match-4-text-color: -jr-accent; + + -jr-maintable-focused-hover-text: -jr-white; + + /* endregion */ +} + +.unchanged { + -rtfx-background-color: #0000; +} + +.updated { + -rtfx-background-color: rgba(41, 166, 236, 0.66); +} + +.addition { + -rtfx-background-color: rgba(29, 209, 161, 0.5); +} + +.deletion { + -rtfx-background-color: rgba(255, 107, 107, 0.55); +} + +#frame { + -fx-background-color: -jr-background-alt; +} + +/* + * The base css file defining the style that is valid for every pane and dialog. + */ + +TextFlow > * { + -fx-fill: -fx-text-background-color; +} + +TextFlow > .hyperlink, +.hyperlink { + -fx-padding: 0; + -fx-underline: false; + -fx-border-style: null; + -fx-border-color: null; + -fx-text-fill: -jr-theme; + -fx-fill: -jr-theme; +} + +TextFlow > .hyperlink:visited, +.hyperlink:visited { + -fx-text-fill: -jr-accent; + -fx-fill: -jr-accent; +} + +.TextFlow > .hyperlink:hover, +.hyperlink:hover { + -fx-underline: true; +} + +.glyph-icon { + /* This adjusts text alignment within the bounds of text nodes so that + the text is always vertically centered within the bounds. Based on + the cap height of the text. */ + -fx-bounds-type: logical_vertical_center; + + /* The base color of icons should always be the same as the text. */ + -fx-fill: -fx-text-base-color; +} + +.ikonli-font-icon { + -fx-icon-color: -fx-text-base-color; +} + +.tooltip { + -fx-background-color: -jr-tooltip-bg; + -fx-opacity: 95%; + -fx-text-fill: -jr-tooltip-fg; + -fx-font-size: 1em; +} + +.tooltip > TextFlow > Text { + -fx-font-size: 1em; +} + +TextFlow > .tooltip-text-bold { + -fx-font-weight: bold; +} + +TextFlow > .tooltip-text-italic { + -fx-font-style: italic; +} + +TextFlow > .tooltip-text-monospaced { + -fx-font-family: monospace; +} + + +.radio-button > .radio, +.check-box > .box { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-background-radius: 0; + -fx-text-fill: -fx-text-base-color; +} + +.button, +.toggle-button, +.menu-button, +.choice-box, +.combo-box-base, +.combo-box-base:editable > .arrow-button { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-background-radius: 4px; + -fx-text-fill: -fx-text-base-color; +} + + +.button { + -fx-background-color: transparent; + -fx-border-color: -fx-outer-border; /* rgba(0, 0, 0, 0.23); */ + -fx-border-width: 1px; + -fx-border-radius: 4px; + -fx-padding: 0.5em 1em 0.5em 1em; +} + +.menu-button > .label { + -fx-padding: 0 8 0 8; +} + +.button:hover { + -fx-background-color: rgba(0, 0, 0, 0.12); +} + +.button:focused, +.button:pressed { + -fx-background-color: rgba(0, 0, 0, 0.3); +} + +.button:default { + -fx-background-color: -fx-default-button; +} + +.button:default:hover { + -fx-background-color: derive(-fx-default-button, -10%); +} + +.button:default:focused, +.button:default:pressed { + -fx-background-color: derive(-fx-default-button, -20%); +} + +.text-button { + -fx-border-width: 0px; +} + +.contained-button { + -fx-background-color: -jr-accent; + -fx-border-color: -jr-accent; +} + +.icon-buttonNoSpaceBottom, +.icon-buttonNoSpaceTop, +.icon-button { + -fx-border-width: 0px; + -fx-background-color: -jr-icon-background; + -fx-padding: 0.5em; +} + +.toggle-button:hover, +.toggle-button:selected:hover, +.icon-buttonNoSpaceBottom:hover, +.icon-buttonNoSpaceTop:hover, +.icon-button:hover { + -fx-background-color: -jr-icon-background-active; +} + +.icon-buttonNoSpaceBottom:armed, +.icon-buttonNoSpaceTop:armed, +.icon-button:armed { + -fx-background-color: -jr-icon-background-armed; +} + +.icon-button:selected { + -fx-background-color: -jr-icon-background-active; + -fx-text-fill: white; + -fx-fill: white; +} + +.icon-button:disabled { + -fx-opacity: 0.4; +} + +.toggle-button:selected { + -fx-background-color: -jr-icon-background-active; + -fx-text-fill: -jr-selected; + -fx-fill: -jr-selected; +} + +.icon-buttonNoSpaceBottom { + -fx-padding: 0.5em 0.5em -0.1em 0.5em; +} + +.icon-buttonNoSpaceTop { + -fx-padding: -0.1em 0.5em 0.5em 0.5em; +} + +.progress-indicator { + -fx-progress-color: -jr-theme; + -fx-border-width: 0px; + -fx-background-color: -jr-icon-background; +} + +.progress-indicator:hover { + -fx-background-color: -jr-icon-background-active; +} + +.progress-indicatorToolbar { + -fx-padding: 0.5em; +} + +.progress-indicatorToolbar .percentage { + -fx-fill: null; +} + +.check-box { + -fx-label-padding: 0.0em 0.0em 0.0em 0.75em; + -fx-text-fill: -fx-text-background-color; +} + +.check-box > .box { + -fx-border-color: -fx-outer-border; /* rgba(0, 0, 0, 0.54); */ + -fx-border-width: 2px; + -fx-border-radius: 1px; + -fx-padding: 0.1em 0.1em 0.2em 0.2em; +} + +.check-box:selected > .box { + -fx-background-insets: 2px; + -fx-border-color: -jr-checked; + -fx-background-color: -jr-checked; +} + +.check-box > .box > .mark { + -fx-padding: 0.2em 0.2em 0.2em 0.2em; +} + +.check-box:selected > .box > .mark { + -fx-background-color: -fx-control-inner-background; + -fx-shape: "M6.61 11.89L3.5 8.78 2.44 9.84 6.61 14l8.95-8.95L14.5 4z"; + -fx-stroke-width: 5; +} + +.radio-button > .radio { + -fx-background-radius: 1.0em; /* large value to make sure this remains circular */ + -fx-padding: 0.35em; /* padding from outside edge to the inner dot */ + -fx-background-color: rgba(0, 0, 0, 0.54), -fx-control-inner-background; + -fx-background-insets: 0, 2px; +} + +.radio-button:selected > .radio { + -fx-background-color: -jr-checked, -fx-background; +} + +.radio-button > .radio > .dot { + -fx-padding: 0.25em; /* radius of the inner dot when selected */ + -fx-background-insets: 0; +} + +.radio-button:selected > .radio > .dot { + -fx-background-color: -jr-checked; +} + +.menu-bar { + -fx-background-color: -jr-menu-background; + -fx-background-insets: 0; + -fx-background-radius: 0; +} + +.menu-bar > .container > .menu-button > .label { + -fx-padding: 0.41777em 0.41777em 0.41777em 0.41777em; +} + +.menu-bar > .menu { + -fx-padding: 0.0em 0.666667em 0.0em 0.666667em; +} + +.menu-bar .menu-button { + -fx-border-color: transparent; + -fx-border-width: 0; + -fx-border-radius: 0; +} + +.menu-item { + -fx-padding: 0.5em 0.41777em 0.5em 0.41777em; +} + +.tab-pane { + -fx-open-tab-animation: NONE; + -fx-close-tab-animation: NONE; +} + +.tab-pane > .tab-header-area > .headers-region > .tab { + -fx-background-insets: 0; + -fx-background-radius: 0; + -fx-background-color: -jr-background-alt; + -fx-border-color: -fx-outer-border; + -fx-border-width: 0.5 0.5 0.5 0.5; + -fx-padding: 0.3em 0.9em 0.3em 0.9em; + -fx-pref-height: -jr-header-height; +} + +.tab-pane > .tab-header-area > .headers-region > .tab .tab-label { + -fx-text-fill: -fx-mid-text-color; +} + +.tab-pane > .tab-header-area > .headers-region > .control-buttons-tab { + -fx-border-color: -jr-theme; + -fx-fill: -jr-theme-text; + -fx-text-fill: -jr-theme-text; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected { + -fx-background-color: -fx-control-inner-background; + -fx-border-color: -jr-theme; + -fx-border-width: 3 0 0 0; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .tab-label { + -fx-fill: -jr-theme-text; + -fx-text-fill: -jr-theme-text; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .tab-close-button { + -fx-background-color: -jr-theme-text; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon { + -fx-fill: -fx-mid-text-color; + -fx-text-fill: -fx-mid-text-color; +} + +.tab-pane:focused > .tab-header-area > .headers-region > .tab:selected .focus-indicator { + -fx-border-width: 0; + -fx-border-insets: 0; + -fx-border-radius: 0; +} + +.tab-pane > .tab-header-area > .headers-region > .tab.drop { + -fx-border-color: -jr-drag-target; + -fx-background-color: -jr-drag-target-hover; + -fx-border-width: 3 1 1 1; +} + +.tab-pane > .tab-header-area > .headers-region > .tab.drop .tab-label { + -fx-fill: -jr-drag-target; + -fx-text-fill: -jr-drag-target; +} + +.tab-pane > .tab-header-area > .tab-header-background { + -fx-background-color: -jr-background-alt; +} + +.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon { + -glyph-size: 13px; + -fx-fill: -fx-mid-text-color; + -fx-text-fill: -fx-mid-text-color; +} + +.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon { + -fx-fill: -jr-theme-text; + -fx-text-fill: -jr-theme-text; +} + +.tab-pane > .tab-header-area { + -fx-padding: 0 0 0 0; +} + +.numberColumn > .hits:any-selected { + -fx-background-color: derive(-jr-green, 70%); +} + +.numberColumn > .hits:all-selected { + -fx-background-color: -jr-green; +} + +.table-view, +.tree-table-view { + -fx-background-insets: 0; + -fx-padding: 0; +} + +.table-view:focused, +.tree-table-view:focused { + -fx-background-insets: 0; +} + +.split-pane > .split-pane-divider { + -fx-background-color: -jr-sidepane-background; + -fx-padding: 0 .5 0 .5; +} + +.table-row-cell:hover, +.table-row-cell:hover:even, +.tree-table-row-cell:hover { + -fx-background-color: -jr-hover; + -fx-fill: -jr-hover-text; + -fx-text-fill: -jr-hover-text; +} + +.tree-table-row-cell:selected > .tree-table-cell > .glyph-icon { + -fx-fill: white; + -fx-text-fill: white; +} + +.table-view > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected, +.tree-table-view > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell .tree-table-cell:selected { + -fx-border-color: transparent; + -fx-background-insets: 0; +} + +.merge-field-value .action-icon { + -fx-blend-mode: multiply; + -fx-opacity: 69%; + -fx-icon-size: 14; + -fx-icon-color: -fx-text-background-color; +} + +.merge-field-value:disabled .action-icon { + -fx-opacity: 0%; +} + +.merge-header-cell { + -fx-border-width: 0 0 1 0; + -fx-border-color: -jr-gray-1; + -fx-background-color: -jr-row-even-background; +} + +.merge-header { + -fx-background-color: -jr-row-even-background; +} + +.lib-change-header { + -fx-font-size: 130%; + -fx-padding: 0.2em 0em 0.2em 0.2em; +} + +.table-view .groupColumnBackground { + -fx-stroke: -jr-gray-2; +} + +.scroll-pane:focused, +.split-pane:focused, +.list-view:focused, +.tree-view:focused, +.table-view:focused, +.tree-table-view:focused, +.html-editor:contains-focus { + -fx-background-color: -fx-control-inner-background; + -fx-background-insets: 0; + -fx-background-radius: 0; +} + +/* Selected rows */ +.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, +.tree-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-cell:filled:selected, +.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected, +.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell:filled:selected, +.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected, +.tree-table-view:focused > .virtual-flow > .clipped-container > .sheet > .tree-table-row-cell .tree-table-cell:selected { + -fx-background: -jr-selected; + -fx-background-color: -jr-selected; + -fx-table-cell-border-color: transparent; +} + +/* Selected when control is not focused + * In the context of the MainTable: an element is focused at a double click. A single click "just" selects it. + */ +.list-cell:filled:selected, +.tree-cell:filled:selected, +.table-row-cell:filled:selected, +.tree-table-row-cell:filled:selected, +.table-row-cell:filled > .table-cell:selected, +.tree-table-row-cell:filled > .tree-table-cell:selected { + -fx-background: -jr-selected; + -fx-background-color: -jr-selected; + -fx-table-cell-border-color: transparent; + -fx-text-fill: -jr-black; +} + +.table-cell:invalid, +.list-cell:invalid { + -fx-background-color: -jr-warn; +} + +.duplicate-entry { + -fx-background-color: -jr-light-green; +} + +.file-row-text { + -fx-fill: -jr-search-text; +} + +.file-row-text:opacity { + -fx-fill: derive(-jr-search-text, 70%); +} + +.combo-box-base { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-background-radius: 0, 0; +} + +.combo-box > .list-cell { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1 0 1 1; + -fx-text-fill: -fx-text-base-color; +} + +.combo-box-popup > .list-view { + -fx-background-color: -fx-control-inner-background; + -fx-background-insets: 0; + -fx-effect: null; + -fx-border-width: 1; + -fx-border-color: -fx-outer-border; + -fx-padding: 0; +} + +.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell { + -fx-padding: 4 0 4 5; + /* No alternate highlighting */ + -fx-background: -fx-control-inner-background; +} + +.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover { + -fx-background: -fx-control-inner-background; + -fx-background-color: -jr-hover; +} + +.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected, +.combo-box-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover { + -fx-background: -fx-control-inner-background; + -fx-background-insets: 0; +} + +.combo-box-popup > .list-view > .placeholder > .label { + -fx-text-fill: -fx-text-base-color; +} + +.stack-pane, +.scroll-pane, +.scroll-pane > .viewport, +.split-pane { + -fx-background-color: transparent; + -fx-background-insets: 0, 0; + -fx-padding: 0; +} + +.accordion .titled-pane { + -fx-skin: "org.jabref.gui.util.CustomTitledPaneSkin"; + -fx-arrow-side: right; +} + +.accordion .titled-pane .title { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-background-insets: 5 5 5 5; +} + +.accordion .titled-pane > *.content { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +/* + * The arrow button has some right padding that's added + * by "modena.css". This simply puts the padding on the + * left since the arrow is positioned on the right. + */ +.titled-pane > .title > .arrow-button { + -fx-padding: 0.0em 0.0em 0.0em 0.583em; +} + +.text-input { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-prompt-text-fill: -jr-gray-2; +} + +.text-input:focused { + -fx-highlight-fill: derive(-jr-accent, 20%); + -fx-background-color: -jr-accent, -fx-control-inner-background; + -fx-background-insets: 0, 2; + -fx-highlight-text-fill: -fx-text-inner-color; +} + +.text-area { + -fx-background-color: -fx-control-inner-background; +} + +.text-area .content { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-padding: 0.333333em 0.583em 0.333333em 0.583em; + -fx-background-radius: 0; +} + +.text-area:focused .content { + -fx-background-color: -jr-accent, -fx-control-inner-background; + -fx-background-insets: 0, 2; + -fx-background-radius: 0; +} + +.text-area > .scroll-pane > .corner { + -fx-background-radius: 0 0 0 0; + -fx-background-color: -fx-background; +} + +.combo-box-base:editable > .text-field, +.date-picker > .text-field { + -fx-background-color: -fx-outer-border, -fx-control-inner-background; + -fx-background-insets: 0, 1; + -fx-background-radius: 0; +} + +.combo-box-base:editable:focused > .text-field, +.combo-box-base:editable > .text-field:focused, +.date-picker > .text-field:focused { + -fx-background-color: -jr-accent, -fx-control-inner-background; + -fx-background-insets: 0, 2; + -fx-background-radius: 0; +} + +.date-picker:focused > .text-field { + -fx-background-color: -fx-control-inner-background; + -fx-background-insets: 0; +} + +.scroll-bar { + -fx-background-color: transparent; + -fx-opacity: 0.3; +} + +.scroll-bar:horizontal .track, +.scroll-bar:vertical .track { + -fx-background-color: -jr-scrollbar-track; + -fx-opacity: 0.6; + -fx-background-radius: 0em; +} + + +.scroll-bar:horizontal .thumb, +.scroll-bar:vertical .thumb { + -fx-background-color: -jr-scrollbar-thumb; + -fx-background-insets: 0, 0, 0; + -fx-background-radius: 0em; +} + +.scroll-bar .thumb:hover, +.scroll-bar .thumb:pressed { + -fx-background-color: derive(-jr-scrollbar-thumb, -30%); +} + +/* Hide increment and decrement buttons */ +.scroll-bar > .increment-button, +.scroll-bar > .decrement-button { + -fx-background-color: null; + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-padding: 0; +} + +/* Hide increment and decrement arrows */ +.scroll-bar:horizontal > .decrement-button > .decrement-arrow, +.scroll-bar:horizontal > .increment-button > .increment-arrow, +.scroll-bar:vertical > .decrement-button > .decrement-arrow, +.scroll-bar:vertical > .increment-button > .increment-arrow { + -fx-background-color: null; + -fx-background-radius: 0; + -fx-background-insets: 0; + -fx-shape: null; + -fx-padding: 0; +} + +/* Need some padding since otherwise no scroll-bar is displayed at all */ +.scroll-bar:horizontal > .decrement-button > .decrement-arrow { + -fx-padding: 0.333em 0.167em 0.333em 0.167em; /* 4 2 4 2 */ +} + +.scroll-bar:vertical > .decrement-button > .decrement-arrow { + -fx-padding: 0em 0.333em 0em 0.333em; /* 2 4 2 4 */ +} + +/* Restore full visibility of scrollbars for active elements */ +.list-view:hover .scroll-bar, +.tree-view:hover .scroll-bar, +.table-view:hover .scroll-bar, +.tree-table-view:hover .scroll-bar, +.text-input:hover .scroll-bar, +.scroll-pane:hover .scroll-bar { + -fx-opacity: 1; +} + +.sidePaneComponentHeader { + -fx-background-color: -jr-sidepane-header-background; + -fx-padding: 0.3em 0.9em 0.3em 0.9em; + -fx-pref-height: -jr-header-height; +} + +.sidePaneComponentHeader > .label { + -fx-text-fill: -jr-sidepane-header-color; + -fx-font-weight: bold; + -fx-padding: 0.3em 0.9em 0.3em 0.9em; +} + +.sidePaneComponentHeader .glyph-icon, +.sidePaneComponentHeader .ikonli-font-icon { + -fx-fill: -jr-sidepane-header-color; + -fx-text-fill: -jr-sidepane-header-color; + -fx-font-size: 16px; + -fx-icon-color: -jr-theme-text; +} + +.mainMenu { + -fx-background-color: -jr-menu-background; + -fx-background-insets: 0; +} + +.menu-bar > .container { + -fx-border-width: 0; +} + +.menu-bar > .container > .menu-button:hover, +.menu-bar > .container > .menu-button:focused, +.menu-bar > .container > .menu-button:showing { + -fx-background-color: -jr-menu-background-active; + -fx-background: -jr-menu-background-active; +} + +.menu-bar > .container > .menu-button:hover > .label, +.menu-bar > .container > .menu-button:focused > .label, +.menu-bar > .container > .menu-button:showing > .label { + -fx-text-fill: -jr-menu-forground-active; +} + +.mainMenu > .container > .menu-button > .label { + -fx-text-fill: -jr-menu-foreground; +} + +.menu-item > .label { + -fx-text-fill: -jr-menu-item-foreground; +} + +.menu-item:focused { + -fx-text-fill: -jr-menu-background-active; + -fx-background: -jr-menu-background-active; + -fx-background-color: -jr-menu-background-active; +} + +.menu-item:focused > .label { + -fx-text-fill: -jr-menu-forground-active; +} + +.menu-item .glyph-icon { + -fx-fill: -jr-menu-item-foreground; + -fx-text-fill: -jr-menu-item-foreground; +} + +.menu-item:focused .glyph-icon { + -fx-fill: -jr-menu-forground-active; + -fx-text-fill: -jr-menu-forground-active; +} + +.context-menu { + -fx-border-color: -fx-outer-border; + -fx-border-width: 1; +} + +.separator:horizontal .line { + -fx-border-color: -jr-separator; + -fx-border-width: 0.3; + -fx-border-insets: 1 15 0 20; +} + +.separator:vertical .line { + -fx-border-color: -jr-separator; + -fx-border-width: 1; + -fx-border-insets: 5 15 5 15; +} + +.mainToolbar { + -fx-background-color: -jr-toolbar; + -fx-border-color: derive(-jr-toolbar, 50%); + -fx-border-width: 0; +} + +.mainToolbar .glyph-icon, +.mainToolbar .ikonli-font-icon { + -fx-font-size: 1.7em; + -fx-fill: -jr-theme-text; + -fx-text-fill: -jr-theme-text; + -fx-icon-color: -jr-theme-text; +} + +.search-field { + -fx-background-color: -jr-search-background; + -fx-border-width: 1; + -fx-border-color: -jr-separator; + -fx-border-radius: 2; + -fx-fill: -jr-search-text; +} + +.search-field .button .glyph-icon { + -fx-fill: -jr-search-text; + -fx-text-fill: -jr-search-text; + -fx-icon-color: -jr-search-text; +} + +/* + * The magnifying glass icon left of the search text field. + * Currently, hits "Web search" and "Groups" only (not the searchbar on the top). +*/ +.search-field-icon { + -fx-icon-color: -jr-search-text; + -fx-font-size: 1.7em; +} + +/* search modifier buttons */ +.global-search-bar .toggle-button:selected { + -fx-background-color: transparent; +} + +.global-search-bar .toggle-button:hover, +.global-search-bar .toggle-button:selected:hover { + -fx-background-color: -jr-icon-background-active; +} + +.global-search-bar .toggle-button .glyph-icon { + -fx-icon-color: derive(-jr-search-text, 80%); +} + +.global-search-bar .toggle-button:selected .glyph-icon { + -fx-icon-color: -jr-search-text; +} + +/* search text */ +.global-search-bar .label { + -fx-padding: 0em 1.8em 0em 0em; +} + +.global-search-bar:illegal-search { + -fx-background-color: derive(-jr-light-red, 70%); +} + +/* The little arrow that shows up when not all tool-bar icons fit into the tool-bar. +We want to have a look that matches our icons in the tool-bar */ +.mainToolbar .tool-bar-overflow-button > .arrow { + -fx-background-color: -jr-theme-text; +} + +.mainToolbar .tool-bar-overflow-button:hover > .arrow { + -fx-background-color: -fx-mark-highlight-color, derive(-jr-theme-text, -30%); +} + +.table-view, +.tree-table-view { + -fx-border-width: 0; + -fx-padding: 0; + -fx-border-insets: 0; + -fx-table-cell-border-color: transparent; +} + +.column-header-background { + -fx-background-color: -fx-control-inner-background; + -fx-border-width: 0; +} + +.column-header-background:hover { + -fx-background-color: -fx-outer-border; +} + +.column-header, +.table-view .filler, +.tree-table-view .filler { + -fx-background-color: transparent, -fx-control-inner-background; + -fx-background-insets: 0, 0 0.02em 0 0.02em; + -fx-font-weight: bold; + -fx-size: 3em; + -fx-border-width: 0 0 1 0; + -fx-border-color: -fx-outer-border; +} + +.column-header > .label { + -fx-padding: 0 1em 0 1em; + -fx-alignment: center-left; + -fx-text-fill: -jr-head-fg; +} + +.column-header .glyph-icon { + -fx-alignment: baseline-center; + -fx-text-fill: -jr-head-fg; + -fx-fill: -jr-head-fg; +} + +.table-cell, +.tree-table-cell, +.tree-table-cell .glyph-icon { + -fx-cell-size: 4.0em; + -fx-padding: 0.5em 1em 0.5em 1em; + -fx-fill: -fx-text-background-color; + -fx-text-fill: -fx-text-background-color; +} + +/* Drag and drop colored indicator */ + +.table-row-cell:dragOver-bottom { + -fx-border-color: -jr-drag-target; + -fx-border-width: 0 0 2 0; + -fx-padding: 0 0 -2 0; +} + +.table-row-cell:dragOver-center { + -fx-border-color: -jr-drag-target; + -fx-border-width: 1 1 1 1; + -fx-padding: -1 -1 -1 -1; + -fx-background-color: -jr-drag-target-hover; +} + +.table-row-cell:dragOver-top { + -fx-border-color: -jr-drag-target; + -fx-border-width: 2 0 0 0; + -fx-padding: -2 0 0 0; +} + +/* Improve the context menu of the main toolbar, when icons don't fit and you have to press the little arrow to see them */ +.mainToolbar .context-menu .glyph-icon { + -fx-fill: -jr-theme-text; +} + +.mainToolbar .context-menu .glyph-icon:hover { + -fx-fill: -jr-menu-forground-active; + -fx-text-fill: -jr-menu-forground-active; + -fx-background-color: -jr-icon-background-active; /* TODO: This has no effect */ +} + +/* This is awful, but I don't know a better way*/ +.mainToolbar .context-menu * { + -fx-background-color: -fx-control-inner-background; +} + +#sidePane { + -fx-background-color: -jr-sidepane-background; +} + +.sidePaneComponent { + -fx-background-color: -jr-sidepane-background; +} + +.notification-bar > .pane { + -fx-background-color: -jr-gray-3; + -fx-background-radius: 4px; +} + +.notification-bar > .pane > .title, +.notification-bar > .pane > .label, +.notification-bar > .pane > .button-bar > .container > .action { + -fx-text-fill: -jr-base; + -fx-padding: 1ex 1ex 1ex 1ex; +} + +.notification-bar > .pane { + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.3), 9, 0, 0, 2); + -fx-padding: 6px 7px 6px 32px; + -fx-background-radius: 0px; + -fx-border-radius: 0px; +} + +.notification-bar > .pane > .button-bar > .container { + -fx-padding: 0px 24px 0px 0px; +} + +.notification-bar > .pane > .label { + -fx-graphic-text-gap: 8px; +} + +.notification-pane .notification-bar > .pane .close-button > .graphic { + -fx-background-color: -jr-base; +} + +.notification-bar > .pane > .label > .ikonli-font-icon { + -fx-icon-size: 16px; + -fx-icon-color: -jr-base; +} + +.progress-bar > .bar { + -fx-background-color: -jr-theme; +} + +.progress-bar:indeterminate > .bar { + -fx-background-color: -jr-theme; +} + +.progress-bar > .track { + -fx-background-color: -jr-accent; +} + +.color-picker:armed, +.color-picker:hover, +.color-picker:focused, +.color-picker { + -fx-background-color: transparent, transparent, transparent, transparent; + -fx-background-radius: 0px; + -fx-background-insets: 0px; + -fx-effect: null; +} + +.color-palette { + -fx-background-color: -fx-background; + -fx-background-radius: 0px; + -fx-background-insets: 0px; +} + +.color-palette-region .button { + -fx-border-width: 0px; +} + +.bibEntry { + +} + +.bibEntry .type { + -fx-font-size: 110%; +} + +.bibEntry .title { + -fx-font-size: 110%; + -fx-font-weight: bold; +} + +.bibEntry .year { + -fx-font-size: 101%; + -fx-font-weight: bold; +} + +.bibEntry .journal { + -fx-font-size: 101%; +} + +.bibEntry .authors { + -fx-font-size: 101%; +} + +.bibEntry .summary { + -fx-padding: 1ex 0ex 0ex 0ex; +} + +.warning-icon { + -fx-fill: -jr-warn; +} + +.info-message { + -fx-fill: -jr-info; + -fx-text-fill: -jr-info; +} + +.warning-message { + -fx-fill: -jr-warn; + -fx-text-fill: -jr-warn; +} + +.error-message { + -fx-fill: -jr-error; + -fx-text-fill: -jr-error; +} + +.error-icon { + -fx-text-fill: -jr-error; + -fx-fill: -jr-error; +} + +.tooltip-warning { + -fx-background-color: -jr-warn; +} + +.tooltip-error { + -fx-background-color: -jr-error; +} + +.titleHeader { + -fx-font-size: 150%; + -fx-padding: 0em 0em 0.5em 0em; +} + +.sectionHeader { + -fx-font-size: 130%; + -fx-padding: 1em 0em 0.2em 0em; +} + +.dialog-pane { + -fx-background-color: -fx-control-inner-background; +} + +.code-area .text { + -fx-fill: -fx-text-background-color; +} + +#bibtexcodearea { + -fx-padding: 4 4 4 4; + -fx-background-color: -fx-control-inner-background; +} + +.code-area .selection { + -fx-fill: -jr-accent; +} + +.code-area .caret { + -fx-stroke: -fx-text-background-color; +} + +.code-area .context-menu { + -fx-font-family: sans-serif; +} + +.citationsList { + -fx-text-fill: -fx-text-base-color; +} + +.citationsList .contextBox { + -fx-border-color: -fx-outer-border; + -fx-border-insets: 5; + -fx-border-style: dashed; + -fx-border-width: 2; + -fx-padding: 12; +} + +.citationsList .contextBox * { + -fx-fill: -fx-text-base-color; +} + +.citationsList .label { + -fx-font-family: monospace; + -fx-font-weight: bold; + -fx-label-padding: 5 0 10 10; +} + +.tags-field { + -fx-pref-height: 30px; + -fx-margin: 0em; + -fx-border-style: none; + -fx-background-color: -fx-outer-border, -fx-control-inner-background; +} + +.tags-field:focused { + -fx-border-color: -jr-accent; +} + +.tags-field > .flow-pane > .tag-view { + -fx-background-color: -fx-default-button; + -fx-text-fill: -fx-focused-text-base-color; +} + +.tags-field > .flow-pane > .tag-view:selected { + -fx-background-color: derive(-fx-default-button, -20%); + -fx-text-fill: -fx-focused-text-base-color; +} + +.tags-field-editor { + -fx-border-width: 0; + -fx-text-fill: -fx-focused-text-base-color; + -fx-highlight-text-fill: -fx-text-inner-color; + -fx-highlight-fill: derive(-jr-accent, 20%); +} + +.searchBar:invalid { + -fx-background-color: rgba(240, 128, 128, 0.5); +} + +.searchBar:unsupported { + -fx-background-color: rgba(255, 159, 67, 0.5); + +} + +.mainTable-header { + -fx-fill: -fx-mid-text-color; +} + +.fontsizeSpinner { + -fx-pref-width: 5em; +} + +.text-field:invalid { + -fx-background-color: rgba(240, 128, 128, 0.5); +} + +.rating { + -fx-skin: "org.jabref.gui.util.CustomRatingSkin"; + -fx-padding: 0.5em 0px 0px 0px; +} + +.rating > .container > .button { + -fx-icon-color: derive(-fx-text-base-color, 85%); +} + +.rating > .container > .button.strong { + -fx-icon-color: -fx-text-base-color; +} + +.table-column .rotated > .label { + -fx-content-display: graphic-only; +} + +.customGenerateButton { + -fx-padding: 0.5em 2em; + -fx-min-width: 10em; +} + +.chat-message-text-area { + -fx-border-radius: 10; + -fx-background-radius: 10; +} + +/* region: fix for making text area round corners (source: https://stackoverflow.com/a/49617953) */ + +.chat-message-text-area .scroll-pane { + -fx-background-color: transparent; +} + +.chat-message-text-area .scroll-pane .viewport { + -fx-background-color: transparent; +} + +.chat-message-text-area .scroll-pane .content { + -fx-background-color: transparent; +} + +/* endregion */ + +/* region: maintable css */ + + +.main-table .column-icon { + -fx-alignment: baseline-center; + -fx-padding: 0; +} + +.main-table .column-header.column-icon > .label { + -fx-padding: 0; + -fx-alignment: baseline-center; +} + +.main-table .empty-special-field { + visibility: hidden; +} + +.main-table .table-row-cell:hover .empty-special-field { + visibility: visible; + -fx-icon-color: -jr-gray-2; + -fx-fill: -jr-gray-2; +} + +.main-table .table-row-cell:dragOver-bottom { + -fx-border-color: -jr-drag-target; + -fx-border-width: 0 0 2 0; + -fx-padding: 0 0 -2 0; +} + +.main-table .table-row-cell:dragOver-center { + -fx-border-color: -jr-drag-target; + -fx-border-width: 1 1 1 1; + -fx-padding: -1 -1 -1 -1; + -fx-background-color: -jr-drag-target-hover; +} + +.main-table .table-row-cell:dragOver-top { + -fx-border-color: -jr-drag-target; + -fx-border-width: 2 0 0 0; + -fx-padding: -2 0 0 0; +} + +/** even and odd are swapped around somehow. Below "odd" matches lines 2, 4, ... **/ + +.main-table .table-row-cell:matching-search-and-groups { + -fx-background-color: -jr-match-1-even; +} +.main-table .table-row-cell:matching-search-and-groups > .table-cell { + -fx-text-fill: -jr-match-1-text-color; +} +.main-table .table-row-cell:matching-search-and-groups:focused > .table-cell { + -fx-text-fill: -fx-focused-text-base-color; +} +.main-table .table-row-cell:matching-search-and-groups:focused:hover > .table-cell { + -fx-text-fill: -jr-maintable-focused-hover-text; +} +.main-table .table-row-cell:matching-search-and-groups:focused:hover > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-maintable-focused-hover-text; +} +.main-table .table-row-cell:matching-search-and-groups:hover > .table-cell { + -fx-text-fill: -jr-hover-text; +} +.main-table .table-row-cell:matching-search-and-groups > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-match-1-text-color; +} +.main-table .table-row-cell:matching-search-and-groups:hover > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-hover-text; +} +.main-table .table-row-cell:matching-search-and-groups:odd { + -fx-background-color: -jr-match-1-odd; +} +.main-table .table-row-cell:matching-search-and-groups:selected, +.main-table .table-row-cell:matching-search-and-groups:focused, +.main-table .table-row-cell:matching-search-and-groups:focused:hover { + -fx-background-color: -jr-selected; +} +.main-table .table-row-cell:matching-search-and-groups:hover { + -fx-background-color: -jr-hover; +} + +.main-table .table-row-cell:matching-search-not-groups { + -fx-background-color: -jr-match-2-even; +} +.main-table .table-row-cell:matching-search-not-groups > .table-cell { + -fx-text-fill: -jr-match-2-text-color; +} +.main-table .table-row-cell:matching-search-not-groups:focused > .table-cell { + -fx-text-fill: -fx-focused-text-base-color; +} +.main-table .table-row-cell:matching-search-not-groups:focused:hover > .table-cell { + -fx-text-fill: -jr-maintable-focused-hover-text; +} +.main-table .table-row-cell:matching-search-not-groups:focused:hover > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-maintable-focused-hover-text; +} +.main-table .table-row-cell:matching-search-not-groups:hover > .table-cell { + -fx-text-fill: -jr-hover-text; +} +.main-table .table-row-cell:matching-search-not-groups > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-match-2-text-color; +} +.main-table .table-row-cell:matching-search-not-groups:hover > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-hover-text; +} +.main-table .table-row-cell:matching-search-not-groups:odd { + -fx-background-color: -jr-match-2-odd; +} +.main-table .table-row-cell:matching-search-not-groups:selected, +.main-table .table-row-cell:matching-search-not-groups:focused, +.main-table .table-row-cell:matching-search-not-groups:focused:hover, +.main-table .table-row-cell:matching-search-not-groups:hover { + -fx-background-color: -jr-selected; +} +.main-table .table-row-cell:matching-search-not-groups:hover { + -fx-background-color: -jr-hover; +} + +.main-table .table-row-cell:matching-groups-not-search { + -fx-background-color: -jr-match-3-even; +} +.main-table .table-row-cell:matching-groups-not-search > .table-cell { + -fx-text-fill: -jr-match-3-text-color; +} +.main-table .table-row-cell:matching-groups-not-search:focused > .table-cell { + -fx-text-fill: -fx-focused-text-base-color; +} +.main-table .table-row-cell:matching-groups-not-search:focused:hover > .table-cell { + -fx-text-fill: -jr-maintable-focused-hover-text; +} +.main-table .table-row-cell:matching-groups-not-search:focused:hover > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-maintable-focused-hover-text; +} +.main-table .table-row-cell:matching-groups-not-search:hover > .table-cell { + -fx-text-fill: -jr-hover-text; +} +.main-table .table-row-cell:matching-groups-not-search > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-match-3-text-color; +} +.main-table .table-row-cell:matching-groups-not-search:hover > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-hover-text; +} +.main-table .table-row-cell:matching-groups-not-search:odd { + -fx-background-color: -jr-match-3-odd; +} +.main-table .table-row-cell:matching-groups-not-search:selected, +.main-table .table-row-cell:matching-groups-not-search:focused, +.main-table .table-row-cell:matching-groups-not-search:focused:hover, +.main-table .table-row-cell:matching-groups-not-search:hover { + -fx-background-color: -jr-selected; +} +.main-table .table-row-cell:matching-groups-not-search:hover { + -fx-background-color: -jr-hover; +} + +.main-table .table-row-cell:not-matching-search-and-groups { + -fx-background-color: -jr-match-4-even; +} +.main-table .table-row-cell:not-matching-search-and-groups > .table-cell { + -fx-text-fill: -jr-match-4-text-color; +} +.main-table .table-row-cell:not-matching-search-and-groups:focused > .table-cell { + -fx-text-fill: -fx-focused-text-base-color; +} +.main-table .table-row-cell:not-matching-search-and-groups:focused:hover > .table-cell { + -fx-text-fill: -jr-maintable-focused-hover-text; +} +.main-table .table-row-cell:not-matching-search-and-groups:focused:hover > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-maintable-focused-hover-text; +} +.main-table .table-row-cell:not-matching-search-and-groups:hover > .table-cell { + -fx-text-fill: -jr-hover-text; +} +.main-table .table-row-cell:not-matching-search-and-groups > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-match-4-text-color; +} +.main-table.table-row-cell:not-matching-search-and-groups:hover > .table-cell > .ikonli-font-icon { + -fx-icon-color: -jr-hover-text; +} +.main-table .table-row-cell:not-matching-search-and-groups:odd { + -fx-background-color: -jr-match-4-odd; +} +.main-table .table-row-cell:not-matching-search-and-groups:selected, +.main-table .table-row-cell:not-matching-search-and-groups:focused, +.main-table .table-row-cell:not-matching-search-and-groups:focused:hover { + -fx-background-color: -jr-selected; +} +.main-table .table-row-cell:not-matching-search-and-groups:hover { + -fx-background-color: -jr-hover; +} + +.rating > .container { + -fx-spacing: 2; +} + +.rating > .container > .button { + -fx-pref-width: 16; + -fx-pref-height: 10; + -fx-background-repeat: no-repeat no-repeat; + -fx-background-size: 16 16; + -fx-border-style: none; + -fx-border-width: 0; + -fx-padding: 0; +} + +.rating > .container > .button.strong { + +} + +.rating > .container > .button:hover { + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.6), 8, 0.0, 0, 0); +} + + +/* endregion */ diff --git a/jabref/src/main/java/org/jabref/gui/ClipBoardManager.java b/jabref/src/main/java/org/jabref/gui/ClipBoardManager.java new file mode 100644 index 00000000..3e145590 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -0,0 +1,177 @@ +package org.jabref.gui; + +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.List; + +import javafx.application.Platform; +import javafx.scene.control.TextInputControl; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DataFormat; +import javafx.scene.input.MouseButton; + +import org.jabref.architecture.AllowedToUseAwt; +import org.jabref.logic.bibtex.BibEntryWriter; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.BibtexString; + +import com.airhacks.afterburner.injection.Injector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@AllowedToUseAwt("Requires ava.awt.datatransfer.Clipboard") +public class ClipBoardManager { + + public static final DataFormat XML = new DataFormat("application/xml"); + + private static final Logger LOGGER = LoggerFactory.getLogger(ClipBoardManager.class); + + private static Clipboard clipboard; + private static java.awt.datatransfer.Clipboard primary; + + public ClipBoardManager() { + this(Clipboard.getSystemClipboard(), Toolkit.getDefaultToolkit().getSystemSelection()); + } + + public ClipBoardManager(Clipboard clipboard, java.awt.datatransfer.Clipboard primary) { + ClipBoardManager.clipboard = clipboard; + ClipBoardManager.primary = primary; + } + + /** + * Add X11 clipboard support to a text input control. It is necessary to call this method in every input where you + * want to use it: {@code ClipBoardManager.addX11Support(TextInputControl input);}. + * + * @param input the TextInputControl (e.g., TextField, TextArea, and children) where adding this functionality. + * @see Short summary for X11 + * clipboards + * @see Longer + * text over clipboards + */ + public static void addX11Support(TextInputControl input) { + input.selectedTextProperty().addListener( + // using InvalidationListener because of https://bugs.openjdk.java.net/browse/JDK-8176270 + observable -> Platform.runLater(() -> { + String newValue = input.getSelectedText(); + if (!newValue.isEmpty() && (primary != null)) { + primary.setContents(new StringSelection(newValue), null); + } + })); + input.setOnMouseClicked(event -> { + if (event.getButton() == MouseButton.MIDDLE) { + input.insertText(input.getCaretPosition(), getContentsPrimary()); + } + }); + } + + /** + * Get the String residing on the system clipboard. + * + * @return any text found on the Clipboard; if none found, return an empty String. + */ + public static String getContents() { + String result = clipboard.getString(); + if (result == null) { + return ""; + } + return result; + } + + public static String getHtmlContents() { + String result = clipboard.getHtml(); + if (result == null) { + return ""; + } + return result; + } + + public static boolean hasHtml() { + return clipboard.hasHtml(); + } + + /** + * Get the String residing on the primary clipboard (if it exists). + * + * @return any text found on the primary Clipboard; if none found, try with the system clipboard. + */ + public static String getContentsPrimary() { + if (primary != null) { + Transferable contents = primary.getContents(null); + if ((contents != null) && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { + try { + return (String) contents.getTransferData(DataFlavor.stringFlavor); + } catch (UnsupportedFlavorException | IOException e) { + LOGGER.warn("", e); + } + } + } + return getContents(); + } + + /** + * Puts content onto the system clipboard. + * + * @param content the ClipboardContent to set as current value of the system clipboard. + */ + public void setContent(ClipboardContent content) { + clipboard.setContent(content); + setPrimaryClipboardContent(content); + } + + /** + * Puts content onto the primary clipboard (if it exists). + * + * @param content the ClipboardContent to set as current value of the primary clipboard. + */ + public void setPrimaryClipboardContent(ClipboardContent content) { + if (primary != null) { + primary.setContents(new StringSelection(content.getString()), null); + } + } + + public void setHtmlContent(String html, String fallbackPlain) { + final ClipboardContent content = new ClipboardContent(); + content.putHtml(html); + content.putString(fallbackPlain); + clipboard.setContent(content); + setPrimaryClipboardContent(content); + } + + public void setContent(String string) { + final ClipboardContent content = new ClipboardContent(); + content.putString(string); + clipboard.setContent(content); + setPrimaryClipboardContent(content); + } + + public void setContent(List entries, BibEntryTypesManager entryTypesManager) throws IOException { + String serializedEntries = serializeEntries(entries, entryTypesManager); + setContent(serializedEntries); + } + + public void setContent(List entries, BibEntryTypesManager entryTypesManager, List stringConstants) throws IOException { + StringBuilder builder = new StringBuilder(); + stringConstants.forEach(strConst -> builder.append(strConst.getParsedSerialization() == null ? "" : strConst.getParsedSerialization())); + String serializedEntries = serializeEntries(entries, entryTypesManager); + builder.append(serializedEntries); + setContent(builder.toString()); + } + + private String serializeEntries(List entries, BibEntryTypesManager entryTypesManager) throws IOException { + CliPreferences preferences = Injector.instantiateModelOrService(CliPreferences.class); + // BibEntry is not Java serializable. Thus, we need to do the serialization manually + // At reading of the clipboard in JabRef, we parse the plain string in all cases, so we don't need to flag we put BibEntries here + // Furthermore, storing a string also enables other applications to work with the data + BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferences.getFieldPreferences()), entryTypesManager); + return writer.serializeAll(entries, BibDatabaseMode.BIBTEX); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/CoreGuiPreferences.java b/jabref/src/main/java/org/jabref/gui/CoreGuiPreferences.java new file mode 100644 index 00000000..23212a62 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/CoreGuiPreferences.java @@ -0,0 +1,121 @@ +package org.jabref.gui; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class CoreGuiPreferences { + private final DoubleProperty positionX; + private final DoubleProperty positionY; + private final DoubleProperty sizeX; + private final DoubleProperty sizeY; + + private final BooleanProperty windowMaximised; + + private final DoubleProperty sidePaneWidth; + + private final StringProperty lastSelectedIdBasedFetcher; + + public CoreGuiPreferences(double positionX, + double positionY, + double sizeX, + double sizeY, + boolean windowMaximised, + String lastSelectedIdBasedFetcher, + double sidePaneWidth) { + this.positionX = new SimpleDoubleProperty(positionX); + this.positionY = new SimpleDoubleProperty(positionY); + this.sizeX = new SimpleDoubleProperty(sizeX); + this.sizeY = new SimpleDoubleProperty(sizeY); + this.windowMaximised = new SimpleBooleanProperty(windowMaximised); + this.lastSelectedIdBasedFetcher = new SimpleStringProperty(lastSelectedIdBasedFetcher); + this.sidePaneWidth = new SimpleDoubleProperty(sidePaneWidth); + } + + public double getPositionX() { + return positionX.get(); + } + + public DoubleProperty positionXProperty() { + return positionX; + } + + public void setPositionX(double positionX) { + this.positionX.set(positionX); + } + + public double getPositionY() { + return positionY.get(); + } + + public DoubleProperty positionYProperty() { + return positionY; + } + + public void setPositionY(double positionY) { + this.positionY.set(positionY); + } + + public double getSizeX() { + return sizeX.get(); + } + + public DoubleProperty sizeXProperty() { + return sizeX; + } + + public void setSizeX(double sizeX) { + this.sizeX.set(sizeX); + } + + public double getSizeY() { + return sizeY.get(); + } + + public DoubleProperty sizeYProperty() { + return sizeY; + } + + public void setSizeY(double sizeY) { + this.sizeY.set(sizeY); + } + + public boolean isWindowMaximised() { + return windowMaximised.get(); + } + + public BooleanProperty windowMaximisedProperty() { + return windowMaximised; + } + + public void setWindowMaximised(boolean windowMaximised) { + this.windowMaximised.set(windowMaximised); + } + + public String getLastSelectedIdBasedFetcher() { + return lastSelectedIdBasedFetcher.get(); + } + + public StringProperty lastSelectedIdBasedFetcherProperty() { + return lastSelectedIdBasedFetcher; + } + + public void setLastSelectedIdBasedFetcher(String lastSelectedIdBasedFetcher) { + this.lastSelectedIdBasedFetcher.set(lastSelectedIdBasedFetcher); + } + + public double getSidePaneWidth() { + return sidePaneWidth.get(); + } + + public DoubleProperty sidePaneWidthProperty() { + return sidePaneWidth; + } + + public void setSidePaneWidth(double sidePaneWidth) { + this.sidePaneWidth.set(sidePaneWidth); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/Dark.css b/jabref/src/main/java/org/jabref/gui/Dark.css new file mode 100644 index 00000000..06bec9e6 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/Dark.css @@ -0,0 +1,181 @@ +.root { + -jr-theme: #2c9490; + -jr-accent: #255652; + -jr-selected: -jr-accent; + -jr-hover: #fff1; + + -jr-row-odd-background: #272b38; + -jr-row-even-background: #212330; + -jr-accent-alt: -jr-accent; + + -jr-red: #b71c1f; + -jr-light-red: #db1d2b; + -jr-green: #1cb631; + -jr-light-green: #28d93c; + -jr-blue: #2c2cb7; + -jr-light-blue: #3a3ad9; + -jr-purple: #b72486; + -jr-light-purple: #d927a8; + -jr-yellow: #b5b021; + -jr-orange: #b77620; + + -jr-base: #141824; + + -jr-background-alt: #151924; + -jr-menu-background: #141824; + -jr-toolbar: -jr-menu-background; + -jr-sidepane-background: #212330; + -jr-search-background: #2c2e3b; + + -jr-sidepane-header-background: -jr-background-alt; + -jr-group-hits-bg: -jr-background-alt; + -jr-group-hits-fg: -fx-light-text-color; + -fx-control-inner-background: #272b38; + + -fx-control-inner-background-alt: -fx-control-inner-background; + + -fx-dark-text-color: black; + -fx-mid-text-color: #7d8591; + -fx-light-text-color: #e5e7ea; + -jr-separator: #333744; + -fx-outer-border: #424758; + + -jr-icon: -fx-light-text-color; + -jr-icon-active: derive(-fx-light-text-color, 50%); + -jr-icon-background-active: -jr-hover; + -jr-icon-background-armed: #fff2; + + -jr-menu-foreground: -fx-light-text-color; + -jr-menu-item-foreground: -fx-light-text-color; + -jr-menu-forground-active: derive(-fx-light-text-color, 50%); + + -jr-scrollbar-thumb: -fx-light-text-color; + -jr-scrollbar-track: derive(-fx-control-inner-background, -90%); + + -fx-focused-text-base-color: -fx-light-text-color; + + -jr-tooltip-fg: derive(-fx-light-text-color, 50%); + + -jr-drag-target: -jr-accent; + -jr-drag-target-hover: -jr-accent; + + -js-summary-text-color: derive(-fx-light-text-color, 70%); + -js-summary-text-color-selected: derive( -fx-dark-text-color, 70%); + + -jr-match-1-odd: -jr-row-odd-background; + -jr-match-1-even: -jr-row-even-background; + -jr-match-1-text-color: -fx-mid-text-color; + + -jr-match-2-odd: derive(-jr-theme, -60%); + -jr-match-2-even: derive(-jr-match-2, 4%); + -jr-match-2-text-color: -fx-mid-text-color; + + -jr-match-3-odd: derive(-jr-theme, -70%); + -jr-match-3-even: derive(-jr-match-3, 4%); + -jr-match-3-text-color: derive(-jr-accent, 30%); + + -jr-match-4-odd: derive(-jr-black, 10%); + -jr-match-4-even: -jr-background-alt; + -jr-match-4-text-color: derive(-jr-match-3-text-color, -15%); +} + +.unchanged { + +} + +.addition { + -fx-text-background-color: black; + -rtfx-background-color: #FDAF56; +} + +.deletion { + -fx-text-background-color: black; + -rtfx-background-color: #FFEECC; +} + +#previewBody { + background-color: #272b38; /* -fx-control-inner-background*/ + color: #7d8591; /* -fx-mid-text-color*/ +} + +.custom-color-dialog { + -fx-background-color: -fx-control-inner-background; +} + +.table-row-cell:odd{ + -fx-background-color:#272b38; +} + +.table-row-cell:even{ + -fx-background-color: #212330; +} + +.table-row-cell:hover, +.tree-table-row-cell:hover { + -fx-background-color: -jr-hover; +} + +.merge-field-value .action-icon { + -fx-blend-mode: none; + -fx-opacity: 90%; +} + +.merge-header-cell { + -fx-border-width: 0 0 1 0; + -fx-border-color: -fx-outer-border; + -fx-background-color: -jr-row-odd-background; +} + +.merge-header { + -fx-background-color: -jr-row-odd-background; +} + +.table-view .groupColumnBackground { + -fx-stroke: -jr-gray-3; +} + +.code-area .lineno { + -fx-background-color: -jr-background-alt; + -fx-text-fill: -fx-mid-text-color; +} + +.text-unchanged { + -fx-fill: -fx-light-text-color; +} + +.radio-button > .radio { + -fx-background-color: -fx-light-text-color, -fx-control-inner-background; +} + +.numberColumn > .hits:any-selected { + -fx-background-color: derive(-jr-gray-3, 25%); +} + +.numberColumn > .hits:all-selected { + -fx-background-color: -jr-gray-3; +} + +#preferencesContainer .tab-pane > .tab-header-area > .tab-header-background { + -fx-background-color: -jr-background; +} + +.global-search-bar .toggle-button .glyph-icon { + -fx-icon-color: derive(-jr-search-background, 50%); +} + +.notification-bar > .pane { + -fx-background-color: -fx-light-text-color; +} + +.rating > .container > .button { + -fx-icon-color: derive(-fx-light-text-color, -50%); +} + +.rating > .container > .button.strong { + -fx-icon-color: -fx-light-text-color; +} + + +.file-row-text { + -fx-text-fill: -fx-light-text-color; +} diff --git a/jabref/src/main/java/org/jabref/gui/DialogService.java b/jabref/src/main/java/org/jabref/gui/DialogService.java new file mode 100644 index 00000000..7248be47 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/DialogService.java @@ -0,0 +1,314 @@ +package org.jabref.gui; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import javafx.concurrent.Task; +import javafx.print.PrinterJob; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ChoiceDialog; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.TextInputDialog; +import javafx.util.StringConverter; + +import org.jabref.gui.util.BaseDialog; +import org.jabref.gui.util.BaseWindow; +import org.jabref.gui.util.DirectoryDialogConfiguration; +import org.jabref.gui.util.FileDialogConfiguration; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.util.NotificationService; + +import org.controlsfx.control.textfield.CustomPasswordField; +import org.controlsfx.dialog.ProgressDialog; + +/** + * This interface provides methods to create dialogs and show them to the user. + */ +public interface DialogService extends NotificationService { + + /** + * This will create and display new {@link ChoiceDialog} of type T with a default choice and a collection of possible choices + * + * @implNote The implementation should accept {@code null} for {@code defaultChoice}, but callers should use {@link #showChoiceDialogAndWait(String, String, String, Collection)}. + */ + Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices); + + /** + * This will create and display new {@link ChoiceDialog} of type T with a collection of possible choices + */ + default Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, Collection choices) { + return showChoiceDialogAndWait(title, content, okButtonLabel, null, choices); + } + + Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices, StringConverter converter); + + default Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, Collection choices, StringConverter converter) { + return showEditableChoiceDialogAndWait(title, content, okButtonLabel, null, choices, converter); + } + + /** + * This will create and display new {@link TextInputDialog} with a text fields to enter data + */ + Optional showInputDialogAndWait(String title, String content); + + /** + * This will create and display new {@link TextInputDialog} with a text field with a default value to enter data + */ + Optional showInputDialogWithDefaultAndWait(String title, String content, String defaultValue); + + /** + * This will create and display a new information dialog. + * It will include a blue information icon on the left and + * a single OK Button. To create an information dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + */ + void showInformationDialogAndWait(String title, String content); + + /** + * This will create and display a new information dialog. + * It will include a yellow warning icon on the left and + * a single OK Button. To create a warning dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + */ + void showWarningDialogAndWait(String title, String content); + + /** + * This will create and display a new error dialog. + * It will include a red error icon on the left and + * a single OK Button. To create a error dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + */ + void showErrorDialogAndWait(String title, String content); + + /** + * Create and display error dialog displaying the given exception. + * + * @param message the error message + * @param exception the exception causing the error + */ + void showErrorDialogAndWait(String message, Throwable exception); + + /** + * Create and display error dialog displaying the given exception. + * + * @param exception the exception causing the error + */ + void showErrorDialogAndWait(Exception exception); + + void showErrorDialogAndWait(FetcherException fetcherException); + + /** + * Create and display error dialog displaying the given exception. + * + * @param exception the exception causing the error + */ + void showErrorDialogAndWait(String title, String content, Throwable exception); + + /** + * Create and display error dialog displaying the given message. + * + * @param message the error message + */ + void showErrorDialogAndWait(String message); + + /** + * This will create and display a new confirmation dialog. + * It will include a blue question icon on the left and + * a OK and Cancel button. To create a confirmation dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)} + * + * @return true if the use clicked "OK" otherwise false + */ + boolean showConfirmationDialogAndWait(String title, String content); + + /** + * Create and display a new confirmation dialog. + * It will include a blue question icon on the left and + * a OK (with given label) and Cancel button. To create a confirmation dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. + * + * @return true if the use clicked "OK" otherwise false + */ + boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel); + + /** + * Create and display a new confirmation dialog. + * It will include a blue question icon on the left and + * a OK (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. + * + * @return true if the use clicked "OK" otherwise false + */ + boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel, String cancelButtonLabel); + + /** + * Create and display a new confirmation dialog. + * It will include a blue question icon on the left and + * a YES (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. + * Moreover, the dialog contains a opt-out checkbox with the given text to support "Do not ask again"-behaviour. + * + * @return true if the use clicked "YES" otherwise false + */ + boolean showConfirmationDialogWithOptOutAndWait(String title, String content, + String optOutMessage, Consumer optOutAction); + + /** + * Create and display a new confirmation dialog. + * It will include a blue question icon on the left and + * a YES (with given label) and Cancel (also with given label) button. To create a confirmation dialog with custom + * buttons see also {@link #showCustomButtonDialogAndWait(Alert.AlertType, String, String, ButtonType...)}. + * Moreover, the dialog contains a opt-out checkbox with the given text to support "Do not ask again"-behaviour. + * + * @return true if the use clicked "YES" otherwise false + */ + boolean showConfirmationDialogWithOptOutAndWait(String title, String content, + String okButtonLabel, String cancelButtonLabel, + String optOutMessage, Consumer optOutAction); + + /** + * This will create and display new {@link CustomPasswordField} that doesn't show the text, and two buttons + * one cancel and one ok. + * + * @return the entered password if pressed "OK", null otherwise + */ + Optional showPasswordDialogAndWait(String title, String header, String content); + + /** + * Shows a custom dialog without returning any results. + * + * @param dialog dialog to show + */ + void showCustomDialog(BaseDialog dialog); + + /** + * Shows a custom window. + * + * @param window window to show + */ + void showCustomWindow(BaseWindow window); + + /** + * This will create and display a new dialog of the specified + * {@link Alert.AlertType} but with user defined buttons as optional + * {@link ButtonType}s. + * + * @return Optional with the pressed Button as ButtonType + */ + Optional showCustomButtonDialogAndWait(Alert.AlertType type, String title, String content, + ButtonType... buttonTypes); + + /** + * This will create and display a new dialog showing a custom {@link DialogPane} + * and using custom {@link ButtonType}s. + * + * @return Optional with the pressed Button as ButtonType + */ + Optional showCustomDialogAndWait(String title, DialogPane contentPane, ButtonType... buttonTypes); + + /** + * Shows a custom dialog and returns the result. + * + * @param dialog dialog to show + * @param type of result + */ + Optional showCustomDialogAndWait(Dialog dialog); + + /** + * Constructs and shows a cancelable {@link ProgressDialog}. + * Clicking cancel will cancel the underlying service and close the dialog + * + * @param title title of the dialog + * @param content message to show above the progress bar + * @param task The {@link Task} which executes the work and for which to show the dialog + */ + void showProgressDialog(String title, String content, Task task); + + /** + * Constructs and shows a cancelable {@link ProgressDialog}. + * Clicking cancel will cancel the underlying service and close the dialog, + * otherwise will wait for the task to finish. + * + * @param title title of the dialog + * @param content message to show above the progress bar + * @param task The {@link Task} which executes the work and for which to show the dialog + */ + void showProgressDialogAndWait(String title, String content, Task task); + + /** + * Constructs and shows a dialog showing the progress of running background tasks. + * Clicking cancel will cancel the underlying service and close the dialog. + * The dialog will exit as soon as none of the background tasks are running + * + * @param title title of the dialog + * @param content message to show below the list of background tasks + * @param stateManager The {@link StateManager} which contains the background tasks + */ + Optional showBackgroundProgressDialogAndWait(String title, String content, StateManager stateManager); + + /** + * Shows a new file save dialog. The method doesn't return until the + * displayed file save dialog is dismissed. The return value specifies the + * file chosen by the user or an empty {@link Optional} if no selection has been made. + * After a file was selected, the given file dialog configuration is updated with the selected extension type (if any). + * + * @return the selected file or an empty {@link Optional} if no file has been selected + */ + Optional showFileSaveDialog(FileDialogConfiguration fileDialogConfiguration); + + /** + * Shows a new file open dialog. The method doesn't return until the + * displayed open dialog is dismissed. The return value specifies + * the file chosen by the user or an empty {@link Optional} if no selection has been + * made. + * After a file was selected, the given file dialog configuration is updated with the selected extension type (if any). + * + * @return the selected file or an empty {@link Optional} if no file has been selected + */ + Optional showFileOpenDialog(FileDialogConfiguration fileDialogConfiguration); + + /** + * Shows a new file open dialog. The method doesn't return until the + * displayed open dialog is dismissed. The return value specifies + * the files chosen by the user or an empty {@link List} if no selection has been + * made. + * + * @return the selected files or an empty {@link List} if no file has been selected + */ + List showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration); + + /** + * Shows a new directory selection dialog. The method doesn't return until the + * displayed open dialog is dismissed. The return value specifies + * the file chosen by the user or an empty {@link Optional} if no selection has been + * made. + * + * @return the selected directory or an empty {@link Optional} if no directory has been selected + */ + Optional showDirectorySelectionDialog(DirectoryDialogConfiguration directoryDialogConfiguration); + + /** + * Displays a Print Dialog. Allow the user to update job state such as printer and settings. These changes will be + * available in the appropriate properties after the print dialog has returned. The print dialog is also used to + * confirm the user wants to proceed with printing. + * + * @param job the print job to customize + * @return false if the user opts to cancel printing + */ + boolean showPrintDialog(PrinterJob job); + + /** + * Shows a new dialog that list all files contained in the given archive and which lets the user select one of these + * files. The method doesn't return until the displayed open dialog is dismissed. The return value specifies the + * file chosen by the user or an empty {@link Optional} if no selection has been made. + * + * @return the selected file or an empty {@link Optional} if no file has been selected + */ + Optional showFileOpenFromArchiveDialog(Path archivePath) throws IOException; +} diff --git a/jabref/src/main/java/org/jabref/gui/DragAndDropDataFormats.java b/jabref/src/main/java/org/jabref/gui/DragAndDropDataFormats.java new file mode 100644 index 00000000..4ab27396 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/DragAndDropDataFormats.java @@ -0,0 +1,20 @@ +package org.jabref.gui; + +import java.util.List; + +import javafx.scene.input.DataFormat; + +import org.jabref.logic.preview.PreviewLayout; + +/** + * Contains all the different {@link DataFormat}s that may occur in JabRef. + */ +public class DragAndDropDataFormats { + + public static final DataFormat FIELD = new DataFormat("dnd/org.jabref.model.entry.field.Field"); + public static final DataFormat GROUP = new DataFormat("dnd/org.jabref.model.groups.GroupTreeNode"); + public static final DataFormat LINKED_FILE = new DataFormat("dnd/org.jabref.model.entry.LinkedFile"); + public static final DataFormat ENTRIES = new DataFormat("dnd/org.jabref.model.entry.BibEntries"); + public static final DataFormat PREVIEWLAYOUTS = new DataFormat("dnd/org.jabref.logic.citationstyle.PreviewLayouts"); + @SuppressWarnings("unchecked") public static final Class> PREVIEWLAYOUT_LIST_CLASS = (Class>) (Class) List.class; +} diff --git a/jabref/src/main/java/org/jabref/gui/FXDialog.java b/jabref/src/main/java/org/jabref/gui/FXDialog.java new file mode 100644 index 00000000..891eae58 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/FXDialog.java @@ -0,0 +1,82 @@ +package org.jabref.gui; + +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Alert; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.image.Image; +import javafx.stage.Modality; +import javafx.stage.Stage; + +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.keyboard.KeyBinding; +import org.jabref.gui.keyboard.KeyBindingRepository; + +import com.airhacks.afterburner.injection.Injector; + +/** + * This class provides a super class for all dialogs implemented in JavaFX. + *

    + * To create a custom JavaFX dialog one should create an instance of this class and set a dialog + * pane through the inherited {@link Dialog#setDialogPane(DialogPane)} method. + * The dialog can be shown via {@link Dialog#show()} or {@link Dialog#showAndWait()}. + *

    + * The layout of the pane should be defined in an external fxml file and loaded it via the + * {@link FXMLLoader}. + */ +public class FXDialog extends Alert { + + public FXDialog(AlertType type, String title, Image image, boolean isModal) { + this(type, title, isModal); + setDialogIcon(image); + } + + public FXDialog(AlertType type, String title, Image image) { + this(type, title, true); + setDialogIcon(image); + } + + public FXDialog(AlertType type, String title, boolean isModal) { + this(type, isModal); + setTitle(title); + } + + public FXDialog(AlertType type, String title) { + this(type); + setTitle(title); + } + + public FXDialog(AlertType type, boolean isModal) { + super(type); + + setDialogIcon(IconTheme.getJabRefImage()); + + Stage dialogWindow = getDialogWindow(); + dialogWindow.setOnCloseRequest(evt -> this.close()); + if (isModal) { + initModality(Modality.APPLICATION_MODAL); + } else { + initModality(Modality.NONE); + } + + dialogWindow.getScene().setOnKeyPressed(event -> { + KeyBindingRepository keyBindingRepository = Injector.instantiateModelOrService(KeyBindingRepository.class); + if (keyBindingRepository.checkKeyCombinationEquality(KeyBinding.CLOSE, event)) { + dialogWindow.close(); + } + }); + } + + public FXDialog(AlertType type) { + this(type, true); + } + + private void setDialogIcon(Image image) { + Stage fxDialogWindow = getDialogWindow(); + fxDialogWindow.getIcons().add(image); + } + + private Stage getDialogWindow() { + return (Stage) getDialogPane().getScene().getWindow(); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/JabRefDialogService.java b/jabref/src/main/java/org/jabref/gui/JabRefDialogService.java new file mode 100644 index 00000000..dccdc178 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/JabRefDialogService.java @@ -0,0 +1,524 @@ +package org.jabref.gui; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import javafx.concurrent.Task; +import javafx.geometry.Pos; +import javafx.print.PrinterJob; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceDialog; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextInputDialog; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.DirectoryChooser; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import javafx.stage.Window; +import javafx.util.Duration; +import javafx.util.StringConverter; + +import org.jabref.gui.help.ErrorConsoleAction; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.util.BaseDialog; +import org.jabref.gui.util.BaseWindow; +import org.jabref.gui.util.DirectoryDialogConfiguration; +import org.jabref.gui.util.FileDialogConfiguration; +import org.jabref.gui.util.UiTaskExecutor; +import org.jabref.gui.util.ZipFileChooser; +import org.jabref.http.dto.SimpleHttpResponse; +import org.jabref.logic.importer.FetcherClientException; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.FetcherServerException; +import org.jabref.logic.l10n.Localization; + +import com.tobiasdiez.easybind.EasyBind; +import org.controlsfx.control.Notifications; +import org.controlsfx.control.TaskProgressView; +import org.controlsfx.control.textfield.CustomPasswordField; +import org.controlsfx.dialog.ExceptionDialog; +import org.controlsfx.dialog.ProgressDialog; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class provides methods to create default + * JavaFX dialogs which will also work on top of Swing + * windows. The created dialogs are instances of the + * {@link FXDialog} class. The available dialogs in this class + * are useful for displaying small information graphic dialogs + * rather than complex windows. For more complex dialogs it is + * advised to rather create a new sub class of {@link FXDialog}. + */ +public class JabRefDialogService implements DialogService { + // Snackbar dialog maximum size + public static final int DIALOG_SIZE_LIMIT = 300; + + private static final Duration TOAST_MESSAGE_DISPLAY_TIME = Duration.millis(3000); + private static final Logger LOGGER = LoggerFactory.getLogger(JabRefDialogService.class); + + private final Window mainWindow; + + public JabRefDialogService(Window mainWindow) { + this.mainWindow = mainWindow; + } + + private FXDialog createDialog(AlertType type, String title, String content) { + FXDialog alert = new FXDialog(type, title, true); + alert.setHeaderText(null); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.setResizable(true); + + TextArea area = new TextArea(content); + area.setWrapText(true); + + alert.getDialogPane().setContent(area); + alert.initOwner(mainWindow); + return alert; + } + + private FXDialog createDialogWithOptOut(String title, String content, + String optOutMessage, Consumer optOutAction) { + FXDialog alert = new FXDialog(AlertType.CONFIRMATION, title, true); + // Need to force the alert to layout in order to grab the graphic as we are replacing the dialog pane with a custom pane + alert.getDialogPane().applyCss(); + Node graphic = alert.getDialogPane().getGraphic(); + + // Create a new dialog pane that has a checkbox instead of the hide/show details button + // Use the supplied callback for the action of the checkbox + alert.setDialogPane(new DialogPane() { + @Override + protected Node createDetailsButton() { + CheckBox optOut = new CheckBox(); + optOut.setText(optOutMessage); + optOut.setOnAction(e -> optOutAction.accept(optOut.isSelected())); + return optOut; + } + }); + + // Fool the dialog into thinking there is some expandable content; a group won't take up any space if it has no children + alert.getDialogPane().setExpandableContent(new Group()); + alert.getDialogPane().setExpanded(true); + + // Reset the dialog graphic using the default style + alert.getDialogPane().setGraphic(graphic); + alert.setHeaderText(null); + alert.setContentText(content); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.initOwner(mainWindow); + return alert; + } + + public static String shortenDialogMessage(String dialogMessage) { + if (dialogMessage.length() < JabRefDialogService.DIALOG_SIZE_LIMIT) { + return dialogMessage.trim(); + } + return (dialogMessage.substring(0, JabRefDialogService.DIALOG_SIZE_LIMIT) + "...").trim(); + } + + private ChoiceDialog createChoiceDialog(String title, String content, String okButtonLabel, T defaultChoice, Collection choices) { + ChoiceDialog choiceDialog = new ChoiceDialog<>(defaultChoice, choices); + ((Stage) choiceDialog.getDialogPane().getScene().getWindow()).getIcons().add(IconTheme.getJabRefImage()); + ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); + choiceDialog.getDialogPane().getButtonTypes().setAll(ButtonType.CANCEL, okButtonType); + choiceDialog.setHeaderText(title); + choiceDialog.setTitle(title); + choiceDialog.setContentText(content); + choiceDialog.initOwner(mainWindow); + return choiceDialog; + } + + @Override + public Optional showChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices) { + return createChoiceDialog(title, content, okButtonLabel, defaultChoice, choices).showAndWait(); + } + + @Override + public Optional showEditableChoiceDialogAndWait(String title, String content, String okButtonLabel, T defaultChoice, Collection choices, StringConverter converter) { + ChoiceDialog choiceDialog = createChoiceDialog(title, content, okButtonLabel, defaultChoice, choices); + ComboBox comboBox = (ComboBox) choiceDialog.getDialogPane().lookup(".combo-box"); + comboBox.setEditable(true); + comboBox.setConverter(converter); + EasyBind.subscribe(comboBox.getEditor().textProperty(), text -> comboBox.setValue(converter.fromString(text))); + return choiceDialog.showAndWait(); + } + + @Override + public Optional showInputDialogAndWait(String title, String content) { + TextInputDialog inputDialog = new TextInputDialog(); + inputDialog.setHeaderText(title); + inputDialog.setContentText(content); + inputDialog.initOwner(mainWindow); + return inputDialog.showAndWait(); + } + + @Override + public Optional showInputDialogWithDefaultAndWait(String title, String content, String defaultValue) { + TextInputDialog inputDialog = new TextInputDialog(defaultValue); + inputDialog.setHeaderText(title); + inputDialog.setContentText(content); + inputDialog.initOwner(mainWindow); + return inputDialog.showAndWait(); + } + + @Override + public void showInformationDialogAndWait(String title, String content) { + FXDialog alert = createDialog(AlertType.INFORMATION, title, content); + alert.showAndWait(); + } + + @Override + public void showWarningDialogAndWait(String title, String content) { + FXDialog alert = createDialog(AlertType.WARNING, title, content); + alert.showAndWait(); + } + + @Override + public void showErrorDialogAndWait(String title, String content) { + FXDialog alert = createDialog(AlertType.ERROR, title, content); + alert.showAndWait(); + } + + @Override + public void showErrorDialogAndWait(String message, Throwable exception) { + ExceptionDialog exceptionDialog = new ExceptionDialog(exception); + exceptionDialog.getDialogPane().setMaxWidth(mainWindow.getWidth() / 2); + exceptionDialog.setHeaderText(message); + exceptionDialog.initOwner(mainWindow); + exceptionDialog.showAndWait(); + } + + @Override + public void showErrorDialogAndWait(Exception exception) { + if (exception instanceof FetcherException fetcherException) { + // Somehow, Java does not route correctly to the other method + showErrorDialogAndWait(fetcherException); + } else { + showErrorDialogAndWait(Localization.lang("Unhandled exception occurred."), exception); + } + } + + @Override + public void showErrorDialogAndWait(FetcherException fetcherException) { + String failedTitle = Localization.lang("Failed to download from URL"); + String localizedMessage = fetcherException.getLocalizedMessage(); + Optional httpResponse = fetcherException.getHttpResponse(); + if (httpResponse.isPresent()) { + this.showInformationDialogAndWait(failedTitle, getContentByCode(httpResponse.get().statusCode()) + "\n\n" + localizedMessage); + } else if (fetcherException instanceof FetcherClientException) { + this.showErrorDialogAndWait(failedTitle, Localization.lang("Something is wrong on JabRef side. Please check the URL and try again.") + "\n\n" + localizedMessage); + } else if (fetcherException instanceof FetcherServerException) { + this.showInformationDialogAndWait(failedTitle, + Localization.lang("Error downloading from URL. Cause is likely the server side.\nPlease try again later or contact the server administrator.") + "\n\n" + localizedMessage); + } else { + this.showErrorDialogAndWait(failedTitle, localizedMessage); + } + } + + @Override + public void showErrorDialogAndWait(String title, String content, Throwable exception) { + ExceptionDialog exceptionDialog = new ExceptionDialog(exception); + exceptionDialog.setHeaderText(title); + exceptionDialog.setContentText(content); + exceptionDialog.initOwner(mainWindow); + exceptionDialog.showAndWait(); + } + + @Override + public void showErrorDialogAndWait(String message) { + FXDialog alert = createDialog(AlertType.ERROR, Localization.lang("Error Occurred"), message); + alert.showAndWait(); + } + + @Override + public boolean showConfirmationDialogAndWait(String title, String content) { + FXDialog alert = createDialog(AlertType.CONFIRMATION, title, content); + return alert.showAndWait().filter(buttonType -> buttonType == ButtonType.OK).isPresent(); + } + + @Override + public boolean showConfirmationDialogAndWait(String title, String content, String okButtonLabel) { + FXDialog alert = createDialog(AlertType.CONFIRMATION, title, content); + ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); + alert.getButtonTypes().setAll(ButtonType.CANCEL, okButtonType); + return alert.showAndWait().filter(buttonType -> buttonType == okButtonType).isPresent(); + } + + @Override + public boolean showConfirmationDialogAndWait(String title, String content, + String okButtonLabel, String cancelButtonLabel) { + FXDialog alert = createDialog(AlertType.CONFIRMATION, title, content); + ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.OK_DONE); + ButtonType cancelButtonType = new ButtonType(cancelButtonLabel, ButtonBar.ButtonData.NO); + alert.getButtonTypes().setAll(okButtonType, cancelButtonType); + return alert.showAndWait().filter(buttonType -> buttonType == okButtonType).isPresent(); + } + + @Override + public boolean showConfirmationDialogWithOptOutAndWait(String title, String content, + String optOutMessage, Consumer optOutAction) { + FXDialog alert = createDialogWithOptOut(title, content, optOutMessage, optOutAction); + alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO); + return alert.showAndWait().filter(buttonType -> buttonType == ButtonType.YES).isPresent(); + } + + @Override + public boolean showConfirmationDialogWithOptOutAndWait(String title, String content, + String okButtonLabel, String cancelButtonLabel, + String optOutMessage, Consumer optOutAction) { + FXDialog alert = createDialogWithOptOut(title, content, optOutMessage, optOutAction); + ButtonType okButtonType = new ButtonType(okButtonLabel, ButtonBar.ButtonData.YES); + ButtonType cancelButtonType = new ButtonType(cancelButtonLabel, ButtonBar.ButtonData.NO); + alert.getButtonTypes().setAll(okButtonType, cancelButtonType); + return alert.showAndWait().filter(buttonType -> buttonType == okButtonType).isPresent(); + } + + @Override + public Optional showCustomButtonDialogAndWait(AlertType type, String title, String content, + ButtonType... buttonTypes) { + FXDialog alert = createDialog(type, title, content); + alert.getButtonTypes().setAll(buttonTypes); + return alert.showAndWait(); + } + + @Override + public Optional showCustomDialogAndWait(String title, DialogPane contentPane, + ButtonType... buttonTypes) { + FXDialog alert = new FXDialog(AlertType.NONE, title); + alert.setDialogPane(contentPane); + alert.getButtonTypes().setAll(buttonTypes); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.setResizable(true); + alert.initOwner(mainWindow); + return alert.showAndWait(); + } + + @Override + public Optional showCustomDialogAndWait(Dialog dialog) { + if (dialog.getOwner() == null) { + dialog.initOwner(mainWindow); + } + return dialog.showAndWait(); + } + + @Override + public Optional showPasswordDialogAndWait(String title, String header, String content) { + Dialog dialog = new Dialog<>(); + dialog.setTitle(title); + dialog.setHeaderText(header); + + CustomPasswordField passwordField = new CustomPasswordField(); + + HBox box = new HBox(); + box.setSpacing(10); + box.getChildren().addAll(new Label(content), passwordField); + dialog.setTitle(title); + dialog.getDialogPane().setContent(box); + + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK); + dialog.setResultConverter(dialogButton -> { + if (dialogButton == ButtonType.OK) { + return passwordField.getText(); + } + return null; + }); + return dialog.showAndWait(); + } + + private ProgressDialog createProgressDialog(String title, String content, Task task) { + ProgressDialog progressDialog = new ProgressDialog(task); + progressDialog.setHeaderText(null); + progressDialog.setTitle(title); + progressDialog.setContentText(content); + progressDialog.setGraphic(null); + ((Stage) progressDialog.getDialogPane().getScene().getWindow()).getIcons().add(IconTheme.getJabRefImage()); + progressDialog.setOnCloseRequest(evt -> task.cancel()); + DialogPane dialogPane = progressDialog.getDialogPane(); + dialogPane.getButtonTypes().add(ButtonType.CANCEL); + Button cancelButton = (Button) dialogPane.lookupButton(ButtonType.CANCEL); + cancelButton.setOnAction(evt -> { + task.cancel(); + progressDialog.close(); + }); + progressDialog.initOwner(mainWindow); + return progressDialog; + } + + @Override + public void showProgressDialog(String title, String content, Task task) { + ProgressDialog progressDialog = createProgressDialog(title, content, task); + progressDialog.show(); + } + + @Override + public void showProgressDialogAndWait(String title, String content, Task task) { + ProgressDialog progressDialog = createProgressDialog(title, content, task); + progressDialog.showAndWait(); + } + + @Override + public Optional showBackgroundProgressDialogAndWait(String title, String content, StateManager stateManager) { + TaskProgressView> taskProgressView = new TaskProgressView<>(); + EasyBind.bindContent(taskProgressView.getTasks(), stateManager.getRunningBackgroundTasks()); + taskProgressView.setRetainTasks(false); + taskProgressView.setGraphicFactory(task -> ThemeManager.getDownloadIconTitleMap.getOrDefault(task.getTitle(), null)); + + Label message = new Label(content); + + VBox box = new VBox(taskProgressView, message); + + DialogPane contentPane = new DialogPane(); + contentPane.setContent(box); + + FXDialog alert = new FXDialog(AlertType.WARNING, title); + alert.setDialogPane(contentPane); + alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.CANCEL); + alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + alert.setResizable(true); + alert.initOwner(mainWindow); + + stateManager.getAnyTasksThatWillNotBeRecoveredRunning().addListener((observable, oldValue, newValue) -> { + if (!newValue) { + alert.setResult(ButtonType.YES); + alert.close(); + } + }); + + return alert.showAndWait(); + } + + @Override + public void notify(String message) { + // TODO: Change to a notification overview instead of event log when that is available. + // The event log is not that user friendly (different purpose). + LOGGER.info(message); + + UiTaskExecutor.runInJavaFXThread(() -> + Notifications.create() + .text(message) + .position(Pos.BOTTOM_CENTER) + .hideAfter(TOAST_MESSAGE_DISPLAY_TIME) + .owner(mainWindow) + .threshold(5, + Notifications.create() + .title(Localization.lang("Last notification")) + .text( + "(" + Localization.lang("Check the event log to see all notifications") + ")" + + "\n\n" + message) + .onAction(e -> { + ErrorConsoleAction ec = new ErrorConsoleAction(); + ec.execute(); + })) + .hideCloseButton() + .show()); + } + + @Override + public Optional showFileSaveDialog(FileDialogConfiguration fileDialogConfiguration) { + FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); + File file = chooser.showSaveDialog(mainWindow); + Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); + return Optional.ofNullable(file).map(File::toPath); + } + + @Override + public Optional showFileOpenDialog(FileDialogConfiguration fileDialogConfiguration) { + FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); + File file = chooser.showOpenDialog(mainWindow); + Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter); + return Optional.ofNullable(file).map(File::toPath); + } + + @Override + public Optional showDirectorySelectionDialog(DirectoryDialogConfiguration directoryDialogConfiguration) { + DirectoryChooser chooser = getConfiguredDirectoryChooser(directoryDialogConfiguration); + File file = chooser.showDialog(mainWindow); + return Optional.ofNullable(file).map(File::toPath); + } + + @Override + public List showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration) { + FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration); + List files = chooser.showOpenMultipleDialog(mainWindow); + return files != null ? files.stream().map(File::toPath).toList() : List.of(); + } + + private DirectoryChooser getConfiguredDirectoryChooser(DirectoryDialogConfiguration directoryDialogConfiguration) { + DirectoryChooser chooser = new DirectoryChooser(); + directoryDialogConfiguration.getInitialDirectory().map(Path::toFile).ifPresent(chooser::setInitialDirectory); + return chooser; + } + + private FileChooser getConfiguredFileChooser(FileDialogConfiguration fileDialogConfiguration) { + FileChooser chooser = new FileChooser(); + chooser.getExtensionFilters().addAll(fileDialogConfiguration.getExtensionFilters()); + chooser.setSelectedExtensionFilter(fileDialogConfiguration.getDefaultExtension()); + chooser.setInitialFileName(fileDialogConfiguration.getInitialFileName()); + fileDialogConfiguration.getInitialDirectory().map(Path::toFile).ifPresent(chooser::setInitialDirectory); + return chooser; + } + + @Override + public boolean showPrintDialog(PrinterJob job) { + return job.showPrintDialog(mainWindow); + } + + @Override + public Optional showFileOpenFromArchiveDialog(Path archivePath) throws IOException { + try (FileSystem zipFile = FileSystems.newFileSystem(archivePath, (ClassLoader) null)) { + return new ZipFileChooser(zipFile).showAndWait(); + } catch (NoClassDefFoundError exc) { + throw new IOException("Could not instantiate ZIP-archive reader.", exc); + } + } + + @Override + public void showCustomDialog(BaseDialog aboutDialogView) { + if (aboutDialogView.getOwner() == null) { + aboutDialogView.initOwner(mainWindow); + } + aboutDialogView.show(); + } + + @Override + public void showCustomWindow(BaseWindow window) { + if (window.getOwner() == null) { + window.initOwner(mainWindow); + } + window.applyStylesheets(mainWindow.getScene().getStylesheets()); + window.show(); + } + + private String getContentByCode(int statusCode) { + return switch (statusCode) { + case 401 -> + Localization.lang("Access denied. You are not authorized to access this resource. Please check your credentials and try again. If you believe you should have access, please contact the administrator for assistance."); + case 403 -> + Localization.lang("Access denied. You do not have permission to access this resource. Please contact the administrator for assistance or try a different action."); + case 404 -> + Localization.lang("The requested resource could not be found. It seems that the file you are trying to download is not available or has been moved. Please verify the URL and try again. If you believe this is an error, please contact the administrator for further assistance."); + default -> + Localization.lang("Something is wrong on JabRef side. Please check the URL and try again."); + }; + } +} diff --git a/jabref/src/main/java/org/jabref/gui/JabRefGUI.java b/jabref/src/main/java/org/jabref/gui/JabRefGUI.java new file mode 100644 index 00000000..be405430 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/JabRefGUI.java @@ -0,0 +1,406 @@ +package org.jabref.gui; + +import java.util.List; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.geometry.Rectangle2D; +import javafx.scene.Scene; +import javafx.scene.input.KeyEvent; +import javafx.stage.Screen; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; + +import org.jabref.gui.frame.JabRefFrame; +import org.jabref.gui.help.VersionWorker; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.gui.keyboard.TextInputKeyBindings; +import org.jabref.gui.openoffice.OOBibBaseConnect; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.remote.CLIMessageHandler; +import org.jabref.gui.theme.ThemeManager; +import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.util.DefaultDirectoryMonitor; +import org.jabref.gui.util.UiTaskExecutor; +import org.jabref.logic.UiCommand; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.net.ProxyRegisterer; +import org.jabref.logic.remote.RemotePreferences; +import org.jabref.logic.remote.server.RemoteListenerServerManager; +import org.jabref.logic.search.PostgreServer; +import org.jabref.logic.util.BuildInfo; +import org.jabref.logic.util.FallbackExceptionHandler; +import org.jabref.logic.util.HeadlessExecutorService; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.logic.util.WebViewStore; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.DirectoryMonitor; +import org.jabref.model.util.FileUpdateMonitor; + +import com.airhacks.afterburner.injection.Injector; +import com.tobiasdiez.easybind.EasyBind; +import kong.unirest.core.Unirest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents the outer stage and the scene of the JabRef window. + */ +public class JabRefGUI extends Application { + + private static final Logger LOGGER = LoggerFactory.getLogger(JabRefGUI.class); + + private static List uiCommands; + private static GuiPreferences preferences; + private static FileUpdateMonitor fileUpdateMonitor; + + // AI Service handles chat messages etc. Therefore, it is tightly coupled to the GUI. + private static AiService aiService; + + private static StateManager stateManager; + private static ThemeManager themeManager; + private static CountingUndoManager countingUndoManager; + private static TaskExecutor taskExecutor; + private static ClipBoardManager clipBoardManager; + private static DialogService dialogService; + private static JabRefFrame mainFrame; + + private static RemoteListenerServerManager remoteListenerServerManager; + + private Stage mainStage; + + public static void setup(List uiCommands, + GuiPreferences preferences, + FileUpdateMonitor fileUpdateMonitor) { + JabRefGUI.uiCommands = uiCommands; + JabRefGUI.preferences = preferences; + JabRefGUI.fileUpdateMonitor = fileUpdateMonitor; + } + + @Override + public void start(Stage stage) { + this.mainStage = stage; + + FallbackExceptionHandler.installExceptionHandler((exception, thread) -> { + UiTaskExecutor.runInJavaFXThread(() -> { + DialogService dialogService = Injector.instantiateModelOrService(DialogService.class); + dialogService.showErrorDialogAndWait("Uncaught exception occurred in " + thread, exception); + }); + }); + + initialize(); + + JabRefGUI.mainFrame = new JabRefFrame( + mainStage, + dialogService, + fileUpdateMonitor, + preferences, + aiService, + stateManager, + countingUndoManager, + Injector.instantiateModelOrService(BibEntryTypesManager.class), + clipBoardManager, + taskExecutor); + + openWindow(); + + startBackgroundTasks(); + + if (!fileUpdateMonitor.isActive()) { + dialogService.showErrorDialogAndWait( + Localization.lang("Unable to monitor file changes. Please close files " + + "and processes and restart. You may encounter errors if you continue " + + "with this session.")); + } + + BuildInfo buildInfo = Injector.instantiateModelOrService(BuildInfo.class); + EasyBind.subscribe(preferences.getInternalPreferences().versionCheckEnabledProperty(), enabled -> { + if (enabled) { + new VersionWorker(buildInfo.version, + dialogService, + taskExecutor, + preferences) + .checkForNewVersionDelayed(); + } + }); + + setupProxy(); + } + + public void initialize() { + WebViewStore.init(); + + DirectoryMonitor directoryMonitor = new DefaultDirectoryMonitor(); + Injector.setModelOrService(DirectoryMonitor.class, directoryMonitor); + + JabRefGUI.remoteListenerServerManager = new RemoteListenerServerManager(); + Injector.setModelOrService(RemoteListenerServerManager.class, remoteListenerServerManager); + + JabRefGUI.stateManager = new StateManager(); + Injector.setModelOrService(StateManager.class, stateManager); + + Injector.setModelOrService(KeyBindingRepository.class, preferences.getKeyBindingRepository()); + + JabRefGUI.themeManager = new ThemeManager( + preferences.getWorkspacePreferences(), + fileUpdateMonitor, + Runnable::run); + Injector.setModelOrService(ThemeManager.class, themeManager); + + JabRefGUI.countingUndoManager = new CountingUndoManager(); + Injector.setModelOrService(UndoManager.class, countingUndoManager); + Injector.setModelOrService(CountingUndoManager.class, countingUndoManager); + + // our Default task executor is the UITaskExecutor which can use the fx thread + JabRefGUI.taskExecutor = new UiTaskExecutor(); + Injector.setModelOrService(TaskExecutor.class, taskExecutor); + + JabRefGUI.dialogService = new JabRefDialogService(mainStage); + Injector.setModelOrService(DialogService.class, dialogService); + + JabRefGUI.clipBoardManager = new ClipBoardManager(); + Injector.setModelOrService(ClipBoardManager.class, clipBoardManager); + + JabRefGUI.aiService = new AiService( + preferences.getAiPreferences(), + preferences.getFilePreferences(), + preferences.getCitationKeyPatternPreferences(), + dialogService, + taskExecutor); + Injector.setModelOrService(AiService.class, aiService); + } + + private void setupProxy() { + if (!preferences.getProxyPreferences().shouldUseProxy() + || !preferences.getProxyPreferences().shouldUseAuthentication()) { + return; + } + + if (preferences.getProxyPreferences().shouldPersistPassword() + && StringUtil.isNotBlank(preferences.getProxyPreferences().getPassword())) { + ProxyRegisterer.register(preferences.getProxyPreferences()); + return; + } + + Optional password = dialogService.showPasswordDialogAndWait( + Localization.lang("Proxy configuration"), + Localization.lang("Proxy requires password"), + Localization.lang("Password")); + + if (password.isPresent()) { + preferences.getProxyPreferences().setPassword(password.get()); + ProxyRegisterer.register(preferences.getProxyPreferences()); + } else { + LOGGER.warn("No proxy password specified"); + } + } + + private void openWindow() { + LOGGER.debug("Initializing frame"); + + CoreGuiPreferences coreGuiPreferences = preferences.getGuiPreferences(); + LOGGER.debug("Reading from prefs: isMaximized {}", coreGuiPreferences.isWindowMaximised()); + + mainStage.setMinWidth(580); + mainStage.setMinHeight(330); + + // maximized target state is stored, because "saveWindowState" saves x and y only if not maximized + boolean windowMaximised = coreGuiPreferences.isWindowMaximised(); + + LOGGER.debug("Screens: {}", Screen.getScreens()); + debugLogWindowState(mainStage); + + if (isWindowPositionInBounds()) { + LOGGER.debug("The JabRef window is inside screen bounds."); + mainStage.setX(coreGuiPreferences.getPositionX()); + mainStage.setY(coreGuiPreferences.getPositionY()); + mainStage.setWidth(coreGuiPreferences.getSizeX()); + mainStage.setHeight(coreGuiPreferences.getSizeY()); + LOGGER.debug("NOT saving window positions"); + } else { + LOGGER.info("The JabRef window is outside of screen bounds. Position and size will be corrected to 1024x768. Primary screen will be used."); + Rectangle2D bounds = Screen.getPrimary().getBounds(); + mainStage.setX(bounds.getMinX()); + mainStage.setY(bounds.getMinY()); + mainStage.setWidth(Math.min(bounds.getWidth(), 1024.0)); + mainStage.setHeight(Math.min(bounds.getHeight(), 786.0)); + LOGGER.debug("Saving window positions"); + saveWindowState(); + } + // after calling "saveWindowState" the maximized state can be set + mainStage.setMaximized(windowMaximised); + debugLogWindowState(mainStage); + + Scene scene = new Scene(JabRefGUI.mainFrame); + + LOGGER.debug("installing CSS"); + themeManager.installCss(scene); + + LOGGER.debug("Handle TextEditor key bindings"); + scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> TextInputKeyBindings.call( + scene, + event, + preferences.getKeyBindingRepository())); + + mainStage.setTitle(JabRefFrame.FRAME_TITLE); + mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); + mainStage.setScene(scene); + mainStage.setOnShowing(this::onShowing); + mainStage.setOnCloseRequest(this::onCloseRequest); + mainStage.setOnHiding(this::onHiding); + + LOGGER.debug("Showing mainStage"); + mainStage.show(); + + LOGGER.debug("frame initialized"); + + Platform.runLater(() -> mainFrame.handleUiCommands(uiCommands)); + } + + public void onShowing(WindowEvent event) { + Platform.runLater(() -> mainFrame.updateDividerPosition()); + + // Open last edited databases + if (uiCommands.stream().noneMatch(UiCommand.BlankWorkspace.class::isInstance) + && preferences.getWorkspacePreferences().shouldOpenLastEdited()) { + mainFrame.openLastEditedDatabases(); + } + } + + public void onCloseRequest(WindowEvent event) { + if (!mainFrame.close()) { + event.consume(); + } + } + + public void onHiding(WindowEvent event) { + saveWindowState(); + preferences.flush(); + Platform.exit(); + } + + private void saveWindowState() { + CoreGuiPreferences preferences = JabRefGUI.preferences.getGuiPreferences(); + if (!mainStage.isMaximized()) { + preferences.setPositionX(mainStage.getX()); + preferences.setPositionY(mainStage.getY()); + preferences.setSizeX(mainStage.getWidth()); + preferences.setSizeY(mainStage.getHeight()); + } + preferences.setWindowMaximised(mainStage.isMaximized()); + debugLogWindowState(mainStage); + } + + /** + * prints the data from the screen (only in debug mode) + * + * @param mainStage JabRef's stage + */ + private void debugLogWindowState(Stage mainStage) { + LOGGER.debug(""" + screen data: + mainStage.WINDOW_MAXIMISED: {} + mainStage.POS_X: {} + mainStage.POS_Y: {} + mainStage.SIZE_X: {} + mainStage.SIZE_Y: {} + """, + mainStage.isMaximized(), mainStage.getX(), mainStage.getY(), mainStage.getWidth(), mainStage.getHeight()); + } + + /** + * Tests if the window coordinates are inside any screen + */ + private boolean isWindowPositionInBounds() { + CoreGuiPreferences coreGuiPreferences = preferences.getGuiPreferences(); + + if (LOGGER.isDebugEnabled()) { + Screen.getScreens().forEach(screen -> LOGGER.debug("Screen bounds: {}", screen.getBounds())); + } + + return lowerLeftIsInBounds(coreGuiPreferences) && upperRightIsInBounds(coreGuiPreferences); + } + + private boolean lowerLeftIsInBounds(CoreGuiPreferences coreGuiPreferences) { + // Windows/PowerToys somehow removes 10 pixels to the left; they are re-added + double leftX = coreGuiPreferences.getPositionX() + 10.0; + double bottomY = coreGuiPreferences.getPositionY() + coreGuiPreferences.getSizeY(); + LOGGER.debug("left x: {}, bottom y: {}", leftX, bottomY); + + boolean inBounds = Screen.getScreens().stream().anyMatch((screen -> screen.getBounds().contains(leftX, bottomY))); + LOGGER.debug("lower left corner is in bounds: {}", inBounds); + return inBounds; + } + + private boolean upperRightIsInBounds(CoreGuiPreferences coreGuiPreferences) { + // The upper right corner is checked as there are most probably the window controls. + // Windows/PowerToys somehow adds 10 pixels to the right and top of the screen, they are removed + double rightX = coreGuiPreferences.getPositionX() + coreGuiPreferences.getSizeX() - 10.0; + double topY = coreGuiPreferences.getPositionY(); + LOGGER.debug("right x: {}, top y: {}", rightX, topY); + + boolean inBounds = Screen.getScreens().stream().anyMatch((screen -> screen.getBounds().contains(rightX, topY))); + LOGGER.debug("upper right corner is in bounds: {}", inBounds); + return inBounds; + } + + // Background tasks + public void startBackgroundTasks() { + RemotePreferences remotePreferences = preferences.getRemotePreferences(); + BibEntryTypesManager bibEntryTypesManager = Injector.instantiateModelOrService(BibEntryTypesManager.class); + if (remotePreferences.useRemoteServer()) { + remoteListenerServerManager.openAndStart( + new CLIMessageHandler( + mainFrame, + preferences, + fileUpdateMonitor, + bibEntryTypesManager), + remotePreferences.getPort()); + } + } + + @Override + public void stop() { + LOGGER.trace("Closing AI service"); + try { + aiService.close(); + } catch (Exception e) { + LOGGER.error("Unable to close AI service", e); + } + LOGGER.trace("Closing OpenOffice connection"); + OOBibBaseConnect.closeOfficeConnection(); + LOGGER.trace("Stopping background tasks"); + stopBackgroundTasks(); + LOGGER.trace("Shutting down thread pools"); + shutdownThreadPools(); + LOGGER.trace("Finished stop"); + } + + public void stopBackgroundTasks() { + Unirest.shutDown(); + } + + public static void shutdownThreadPools() { + LOGGER.trace("Shutting down taskExecutor"); + if (taskExecutor != null) { + taskExecutor.shutdown(); + } + LOGGER.trace("Shutting down fileUpdateMonitor"); + fileUpdateMonitor.shutdown(); + LOGGER.trace("Shutting down directoryMonitor"); + DirectoryMonitor directoryMonitor = Injector.instantiateModelOrService(DirectoryMonitor.class); + directoryMonitor.shutdown(); + LOGGER.trace("Shutting down postgreServer"); + PostgreServer postgreServer = Injector.instantiateModelOrService(PostgreServer.class); + postgreServer.shutdown(); + LOGGER.trace("Shutting down HeadlessExecutorService"); + HeadlessExecutorService.INSTANCE.shutdownEverything(); + LOGGER.trace("Finished shutdownThreadPools"); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/LibraryTab.java b/jabref/src/main/java/org/jabref/gui/LibraryTab.java new file mode 100644 index 00000000..208e91da --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/LibraryTab.java @@ -0,0 +1,1193 @@ +package org.jabref.gui; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + +import javafx.animation.PauseTransition; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.value.ObservableBooleanValue; +import javafx.collections.ListChangeListener; +import javafx.event.Event; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.SplitPane; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; +import javafx.util.Duration; + +import org.jabref.gui.actions.StandardActions; +import org.jabref.gui.autocompleter.AutoCompletePreferences; +import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; +import org.jabref.gui.autocompleter.SuggestionProvider; +import org.jabref.gui.autocompleter.SuggestionProviders; +import org.jabref.gui.autosaveandbackup.AutosaveManager; +import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.collab.DatabaseChangeMonitor; +import org.jabref.gui.dialogs.AutosaveUiManager; +import org.jabref.gui.entryeditor.EntryEditor; +import org.jabref.gui.exporter.SaveDatabaseAction; +import org.jabref.gui.externalfiles.ImportHandler; +import org.jabref.gui.fieldeditors.LinkedFileViewModel; +import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.linkedfile.DeleteFileAction; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.MainTable; +import org.jabref.gui.maintable.MainTableDataModel; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.RedoAction; +import org.jabref.gui.undo.UndoAction; +import org.jabref.gui.undo.UndoableFieldChange; +import org.jabref.gui.undo.UndoableInsertEntries; +import org.jabref.gui.undo.UndoableRemoveEntries; +import org.jabref.gui.util.OptionalObjectProperty; +import org.jabref.gui.util.UiTaskExecutor; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.citationstyle.CitationStyleCache; +import org.jabref.logic.importer.FetcherClientException; +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.FetcherServerException; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.journals.JournalAbbreviationRepository; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.pdf.FileAnnotationCache; +import org.jabref.logic.search.IndexManager; +import org.jabref.logic.shared.DatabaseLocation; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.FieldChange; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.event.BibDatabaseContextChangedEvent; +import org.jabref.model.database.event.EntriesAddedEvent; +import org.jabref.model.database.event.EntriesRemovedEvent; +import org.jabref.model.entry.Author; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.BibtexString; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.event.EntriesEventSource; +import org.jabref.model.entry.event.FieldChangedEvent; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.groups.GroupTreeNode; +import org.jabref.model.search.query.SearchQuery; +import org.jabref.model.util.DirectoryMonitor; +import org.jabref.model.util.DirectoryMonitorManager; +import org.jabref.model.util.FileUpdateMonitor; + +import com.airhacks.afterburner.injection.Injector; +import com.google.common.eventbus.Subscribe; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; +import org.controlsfx.control.NotificationPane; +import org.controlsfx.control.action.Action; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents the ui area where the notifier pane, the library table and the entry editor are shown. + */ +public class LibraryTab extends Tab { + /** + * Defines the different modes that the tab can operate in + */ + private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } + + private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); + private final LibraryTabContainer tabContainer; + private final CountingUndoManager undoManager; + private final DialogService dialogService; + private final GuiPreferences preferences; + private final FileUpdateMonitor fileUpdateMonitor; + private final StateManager stateManager; + private final BibEntryTypesManager entryTypesManager; + private final BooleanProperty changedProperty = new SimpleBooleanProperty(false); + private final BooleanProperty nonUndoableChangeProperty = new SimpleBooleanProperty(false); + + private BibDatabaseContext bibDatabaseContext; + private MainTableDataModel tableModel; + private FileAnnotationCache annotationCache; + private EntryEditor entryEditor; + private MainTable mainTable; + private PanelMode mode = PanelMode.MAIN_TABLE; + private SplitPane splitPane; + private DatabaseNotification databaseNotificationPane; + + // Indicates whether the tab is loading data using a dataloading task + // The constructors take care to the right true/false assignment during start. + private final SimpleBooleanProperty loading = new SimpleBooleanProperty(false); + + // initially, the dialog is loading, not saving + private boolean saving = false; + + private PersonNameSuggestionProvider searchAutoCompleter; + + // Used to track whether the base has changed since last save. + private BibEntry showing; + + private SuggestionProviders suggestionProviders; + + @SuppressWarnings({"FieldCanBeLocal"}) + private Subscription dividerPositionSubscription; + + private ListProperty selectedGroupsProperty; + private final OptionalObjectProperty searchQueryProperty = OptionalObjectProperty.empty(); + private final IntegerProperty resultSize = new SimpleIntegerProperty(0); + + private Optional changeMonitor = Optional.empty(); + + private BackgroundTask dataLoadingTask; + + private final ClipBoardManager clipBoardManager; + private final TaskExecutor taskExecutor; + private final DirectoryMonitorManager directoryMonitorManager; + + private ImportHandler importHandler; + private IndexManager indexManager; + + private final AiService aiService; + + /** + * @param isDummyContext Indicates whether the database context is a dummy. A dummy context is used to display a progress indicator while parsing the database. + * If the context is a dummy, the Lucene index should not be created, as both the dummy context and the actual context share the same index path {@link BibDatabaseContext#getFulltextIndexPath()}. + * If the index is created for the dummy context, the actual context will not be able to open the index until it is closed by the dummy context. + * Closing the index takes time and will slow down opening the library. + */ + private LibraryTab(BibDatabaseContext bibDatabaseContext, + LibraryTabContainer tabContainer, + DialogService dialogService, + AiService aiService, + GuiPreferences preferences, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor, + boolean isDummyContext) { + this.tabContainer = Objects.requireNonNull(tabContainer); + this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); + this.undoManager = undoManager; + this.dialogService = dialogService; + this.preferences = Objects.requireNonNull(preferences); + this.stateManager = Objects.requireNonNull(stateManager); + this.fileUpdateMonitor = fileUpdateMonitor; + this.entryTypesManager = entryTypesManager; + this.clipBoardManager = clipBoardManager; + this.taskExecutor = taskExecutor; + this.directoryMonitorManager = new DirectoryMonitorManager(Injector.instantiateModelOrService(DirectoryMonitor.class)); + this.aiService = aiService; + + initializeComponentsAndListeners(isDummyContext); + + // set LibraryTab ID for drag'n'drop + // ID content doesn't matter, we only need different tabs to have different ID + this.setId(Long.valueOf(new Random().nextLong()).toString()); + + setOnCloseRequest(this::onCloseRequest); + setOnClosed(this::onClosed); + } + + private void initializeComponentsAndListeners(boolean isDummyContext) { + if (!isDummyContext) { + createIndexManager(); + } + + if (tableModel != null) { + tableModel.unbind(); + } + + bibDatabaseContext.getDatabase().registerListener(this); + bibDatabaseContext.getMetaData().registerListener(this); + + this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext)); + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, getIndexManager(), selectedGroupsProperty(), searchQueryProperty(), resultSizeProperty()); + + new CitationStyleCache(bibDatabaseContext); + annotationCache = new FileAnnotationCache(bibDatabaseContext, preferences.getFilePreferences()); + importHandler = new ImportHandler( + bibDatabaseContext, + preferences, + fileUpdateMonitor, + undoManager, + stateManager, + dialogService, + taskExecutor); + + setupMainPanel(); + setupAutoCompletion(); + + this.getDatabase().registerListener(new IndexUpdateListener()); + this.getDatabase().registerListener(new EntriesRemovedListener()); + + // ensure that at each addition of a new entry, the entry is added to the groups interface + this.bibDatabaseContext.getDatabase().registerListener(new GroupTreeListener()); + // ensure that all entry changes mark the panel as changed + this.bibDatabaseContext.getDatabase().registerListener(this); + + this.getDatabase().registerListener(new UpdateTimestampListener(preferences)); + + this.entryEditor = createEntryEditor(); + + aiService.setupDatabase(bibDatabaseContext); + + Platform.runLater(() -> { + EasyBind.subscribe(changedProperty, this::updateTabTitle); + stateManager.getOpenDatabases().addListener((ListChangeListener) c -> + updateTabTitle(changedProperty.getValue())); + }); + } + + private EntryEditor createEntryEditor() { + Supplier tabSupplier = () -> this; + return new EntryEditor(this, + // Actions are recreated here since this avoids passing more parameters and the amount of additional memory consumption is neglegtable. + new UndoAction(tabSupplier, undoManager, dialogService, stateManager), + new RedoAction(tabSupplier, undoManager, dialogService, stateManager)); + } + + private static void addChangedInformation(StringBuilder text, String fileName) { + text.append("\n"); + text.append(Localization.lang("Library '%0' has changed.", fileName)); + } + + private static void addModeInfo(StringBuilder text, BibDatabaseContext bibDatabaseContext) { + String mode = bibDatabaseContext.getMode().getFormattedName(); + String modeInfo = "\n%s".formatted(Localization.lang("%0 mode", mode)); + text.append(modeInfo); + } + + private static void addSharedDbInformation(StringBuilder text, BibDatabaseContext bibDatabaseContext) { + text.append(bibDatabaseContext.getDBMSSynchronizer().getDBName()); + text.append(" ["); + text.append(Localization.lang("shared")); + text.append("]"); + } + + private void setDataLoadingTask(BackgroundTask dataLoadingTask) { + this.loading.set(true); + this.dataLoadingTask = dataLoadingTask; + } + + /** + * The layout to display in the tab when it is loading + */ + private Node createLoadingAnimationLayout() { + ProgressIndicator progressIndicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); + BorderPane pane = new BorderPane(); + pane.setCenter(progressIndicator); + return pane; + } + + private void onDatabaseLoadingStarted() { + Node loadingLayout = createLoadingAnimationLayout(); + getMainTable().placeholderProperty().setValue(loadingLayout); + } + + private void onDatabaseLoadingSucceed(ParserResult result) { + OpenDatabaseAction.performPostOpenActions(result, dialogService, preferences); + if (result.getChangedOnMigration()) { + this.markBaseChanged(); + } + + setDatabaseContext(result.getDatabaseContext()); + + LOGGER.trace("loading.set(false);"); + loading.set(false); + dataLoadingTask = null; + } + + public void createIndexManager() { + indexManager = new IndexManager(bibDatabaseContext, taskExecutor, preferences); + stateManager.setIndexManager(bibDatabaseContext, indexManager); + } + + public IndexManager getIndexManager() { + return indexManager; + } + + public void closeIndexManger() { + indexManager.close(); + } + + private void onDatabaseLoadingFailed(Exception ex) { + loading.set(false); + + String title = Localization.lang("Connection error"); + String content = "%s\n\n%s".formatted(ex.getMessage(), Localization.lang("A local copy will be opened.")); + + dialogService.showErrorDialogAndWait(title, content, ex); + } + + private void setDatabaseContext(BibDatabaseContext bibDatabaseContext) { + TabPane tabPane = this.getTabPane(); + if (tabPane == null) { + LOGGER.debug("User interrupted loading. Not showing any library."); + return; + } + if (tabPane.getSelectionModel().selectedItemProperty().get().equals(this)) { + LOGGER.debug("This case should not happen."); + stateManager.setActiveDatabase(bibDatabaseContext); + stateManager.activeTabProperty().set(Optional.of(this)); + } + + // Remove existing dummy BibDatabaseContext and add correct BibDatabaseContext from ParserResult to trigger changes in the openDatabases list in the stateManager + Optional foundExistingBibDatabase = stateManager.getOpenDatabases().stream().filter(databaseContext -> databaseContext.equals(this.bibDatabaseContext)).findFirst(); + foundExistingBibDatabase.ifPresent(databaseContext -> stateManager.getOpenDatabases().remove(databaseContext)); + + this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); + + stateManager.getOpenDatabases().add(bibDatabaseContext); + + initializeComponentsAndListeners(false); + installAutosaveManagerAndBackupManager(); + } + + public void installAutosaveManagerAndBackupManager() { + if (isDatabaseReadyForAutoSave(bibDatabaseContext)) { + AutosaveManager autosaveManager = AutosaveManager.start(bibDatabaseContext); + autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); + } + if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { + BackupManager.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); + } + } + + private boolean isDatabaseReadyForAutoSave(BibDatabaseContext context) { + return ((context.getLocation() == DatabaseLocation.SHARED) + || ((context.getLocation() == DatabaseLocation.LOCAL) + && preferences.getLibraryPreferences().shouldAutoSave())) + && context.getDatabasePath().isPresent(); + } + + private boolean isDatabaseReadyForBackup(BibDatabaseContext context) { + return (context.getLocation() == DatabaseLocation.LOCAL) && context.getDatabasePath().isPresent(); + } + + /** + * Sets the title of the tab modification-asterisk filename – path-fragment + *

    + * The modification-asterisk (*) is shown if the file was modified since last save (path-fragment is only shown if filename is not (globally) unique) + *

    + * Example: *jabref-authors.bib – testbib + */ + public void updateTabTitle(boolean isChanged) { + boolean isAutosaveEnabled = preferences.getLibraryPreferences().shouldAutoSave(); + + DatabaseLocation databaseLocation = bibDatabaseContext.getLocation(); + Optional file = bibDatabaseContext.getDatabasePath(); + + StringBuilder tabTitle = new StringBuilder(); + StringBuilder toolTipText = new StringBuilder(); + + if (file.isPresent()) { + // Modification asterisk + if (isChanged && !isAutosaveEnabled) { + tabTitle.append('*'); + } + + // Filename + Path databasePath = file.get(); + String fileName = databasePath.getFileName().toString(); + tabTitle.append(fileName); + toolTipText.append(databasePath.toAbsolutePath()); + + if (databaseLocation == DatabaseLocation.SHARED) { + tabTitle.append(" \u2013 "); + addSharedDbInformation(tabTitle, bibDatabaseContext); + toolTipText.append(' '); + addSharedDbInformation(toolTipText, bibDatabaseContext); + } + + // Database mode + addModeInfo(toolTipText, bibDatabaseContext); + + // Changed information (tooltip) + if (isChanged && !isAutosaveEnabled) { + addChangedInformation(toolTipText, fileName); + } + + // Unique path fragment + Optional uniquePathPart = FileUtil.getUniquePathDirectory(stateManager.collectAllDatabasePaths(), databasePath); + uniquePathPart.ifPresent(part -> tabTitle.append(" \u2013 ").append(part)); + } else { + if (databaseLocation == DatabaseLocation.LOCAL) { + tabTitle.append('*'); + tabTitle.append(Localization.lang("untitled")); + } else { + addSharedDbInformation(tabTitle, bibDatabaseContext); + addSharedDbInformation(toolTipText, bibDatabaseContext); + } + addModeInfo(toolTipText, bibDatabaseContext); + if ((databaseLocation == DatabaseLocation.LOCAL) && bibDatabaseContext.getDatabase().hasEntries()) { + addChangedInformation(toolTipText, Localization.lang("untitled")); + } + } + + UiTaskExecutor.runInJavaFXThread(() -> { + textProperty().setValue(tabTitle.toString()); + setTooltip(new Tooltip(toolTipText.toString())); + }); + } + + @Subscribe + public void listen(BibDatabaseContextChangedEvent event) { + this.changedProperty.setValue(true); + } + + /** + * Returns a collection of suggestion providers, which are populated from the current library. + */ + public SuggestionProviders getSuggestionProviders() { + return suggestionProviders; + } + + public void registerUndoableChanges(List changes) { + NamedCompound ce = new NamedCompound(Localization.lang("Save actions")); + for (FieldChange change : changes) { + ce.addEdit(new UndoableFieldChange(change)); + } + ce.end(); + if (ce.hasEdits()) { + getUndoManager().addEdit(ce); + } + } + + public void editEntryAndFocusField(BibEntry entry, Field field) { + showAndEdit(entry); + Platform.runLater(() -> { + // Focus field and entry in main table (async to give entry editor time to load) + entryEditor.setFocusToField(field); + }); + } + + private void createMainTable() { + mainTable = new MainTable(tableModel, + this, + tabContainer, + bibDatabaseContext, + preferences, + dialogService, + stateManager, + preferences.getKeyBindingRepository(), + clipBoardManager, + entryTypesManager, + taskExecutor, + importHandler); + // Add the listener that binds selection to state manager (TODO: should be replaced by proper JavaFX binding as soon as table is implemented in JavaFX) + // content binding between StateManager#getselectedEntries and mainTable#getSelectedEntries does not work here as it does not trigger the ActionHelper#needsEntriesSelected checker for the menubar + mainTable.addSelectionListener(event -> { + List entries = event.getList().stream().map(BibEntryTableViewModel::getEntry).toList(); + stateManager.setSelectedEntries(entries); + if (!entries.isEmpty()) { + // Update entry editor and preview according to selected entries + entryEditor.setCurrentlyEditedEntry(entries.getFirst()); + } + }); + } + + public void setupMainPanel() { + splitPane = new SplitPane(); + splitPane.setOrientation(Orientation.VERTICAL); + + createMainTable(); + + splitPane.getItems().add(mainTable); + databaseNotificationPane = new DatabaseNotification(splitPane); + setContent(databaseNotificationPane); + + // Saves the divider position as soon as it changes + // We need to keep a reference to the subscription, otherwise the binding gets garbage collected + dividerPositionSubscription = EasyBind.valueAt(splitPane.getDividers(), 0) + .mapObservable(SplitPane.Divider::positionProperty) + .subscribeToValues(this::saveDividerLocation); + + // Add changePane in case a file is present - otherwise just add the splitPane to the panel + Optional file = bibDatabaseContext.getDatabasePath(); + if (file.isPresent()) { + resetChangeMonitor(); + } else { + if (bibDatabaseContext.getDatabase().hasEntries()) { + // if the database is not empty and no file is assigned, + // the database came from an import and has to be treated somehow + // -> mark as changed + this.changedProperty.setValue(true); + } + } + } + + /** + * Set up autocompletion for this database + */ + private void setupAutoCompletion() { + AutoCompletePreferences autoCompletePreferences = preferences.getAutoCompletePreferences(); + if (autoCompletePreferences.shouldAutoComplete()) { + suggestionProviders = new SuggestionProviders( + getDatabase(), + Injector.instantiateModelOrService(JournalAbbreviationRepository.class), + autoCompletePreferences); + } else { + // Create empty suggestion providers if auto-completion is deactivated + suggestionProviders = new SuggestionProviders(); + } + searchAutoCompleter = new PersonNameSuggestionProvider(FieldFactory.getPersonNameFields(), getDatabase()); + } + + public SuggestionProvider getAutoCompleter() { + return searchAutoCompleter; + } + + public EntryEditor getEntryEditor() { + return entryEditor; + } + + /** + * Sets the entry editor as the bottom component in the split pane. If an entry editor already was shown, makes sure that the divider doesn't move. Updates the mode to {@link PanelMode#MAIN_TABLE_AND_ENTRY_EDITOR}. + * Then shows the given entry. + * + * Additionally, selects the entry in the main table - so that the selected entry in the main table always corresponds to the edited entry. + * + * @param entry The entry to edit. + */ + public void showAndEdit(BibEntry entry) { + this.clearAndSelect(entry); + if (!splitPane.getItems().contains(entryEditor)) { + splitPane.getItems().addLast(entryEditor); + mode = PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR; + splitPane.setDividerPositions(preferences.getEntryEditorPreferences().getDividerPosition()); + } + + // We use != instead of equals because of performance reasons + if (entry != showing) { + entryEditor.setCurrentlyEditedEntry(entry); + showing = entry; + } + entryEditor.requestFocus(); + } + + public void closeBottomPane() { + mode = PanelMode.MAIN_TABLE; + splitPane.getItems().remove(entryEditor); + } + + /** + * This method selects the given entry, and scrolls it into view in the table. If an entryEditor is shown, it is given focus afterwards. + */ + public void clearAndSelect(final BibEntry bibEntry) { + mainTable.clearAndSelect(bibEntry); + } + + public void selectPreviousEntry() { + mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() - 1); + } + + public void selectNextEntry() { + mainTable.getSelectionModel().clearAndSelect(mainTable.getSelectionModel().getSelectedIndex() + 1); + } + + /** + * This method is called from an EntryEditor when it should be closed. We relay to the selection listener, which takes care of the rest. + */ + public void entryEditorClosing() { + closeBottomPane(); + mainTable.requestFocus(); + } + + /** + * Closes the entry editor if it is showing any of the given entries. + */ + private void ensureNotShowingBottomPanel(List entriesToCheck) { + // This method is not able to close the bottom pane currently + + if ((mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) && (entriesToCheck.contains(entryEditor.getCurrentlyEditedEntry()))) { + closeBottomPane(); + } + } + + /** + * Put an asterisk behind the filename to indicate the database has changed. + */ + public synchronized void markChangedOrUnChanged() { + if (undoManager.hasChanged()) { + this.changedProperty.setValue(true); + } else if (changedProperty.getValue() && !nonUndoableChangeProperty.getValue()) { + this.changedProperty.setValue(false); + } + } + + public BibDatabase getDatabase() { + return bibDatabaseContext.getDatabase(); + } + + /** + * Initializes a pop-up dialog box to confirm whether the user wants to delete the selected entry + * Keep track of user preference: + * if the user prefers not to ask before deleting, delete the selected entry without displaying the dialog box + * + * @param numberOfEntries number of entries user is selecting + * @return true if user confirm to delete entry + */ + private boolean showDeleteConfirmationDialog(int numberOfEntries) { + if (preferences.getWorkspacePreferences().shouldConfirmDelete()) { + String title = Localization.lang("Delete entry"); + String message = Localization.lang("Really delete the selected entry?"); + String okButton = Localization.lang("Delete entry"); + String cancelButton = Localization.lang("Keep entry"); + if (numberOfEntries > 1) { + title = Localization.lang("Delete multiple entries"); + message = Localization.lang("Really delete the %0 selected entries?", Integer.toString(numberOfEntries)); + okButton = Localization.lang("Delete entries"); + cancelButton = Localization.lang("Keep entries"); + } + + return dialogService.showConfirmationDialogWithOptOutAndWait( + title, + message, + okButton, + cancelButton, + Localization.lang("Do not ask again"), + optOut -> preferences.getWorkspacePreferences().setConfirmDelete(!optOut)); + } else { + return true; + } + } + + /** + * Depending on whether a preview or an entry editor is showing, save the current divider location in the correct preference setting. + */ + private void saveDividerLocation(Number position) { + if (mode == PanelMode.MAIN_TABLE_AND_ENTRY_EDITOR) { + preferences.getEntryEditorPreferences().setDividerPosition(position.doubleValue()); + } + } + + public boolean requestClose() { + if (bibDatabaseContext.getLocation() == DatabaseLocation.LOCAL) { + if (isModified()) { + return confirmClose(); + } + } + return true; + } + + /** + * Ask if the user really wants to close the given database. + * Offers to save or discard the changes -- or return to the library + * + * @return true if the user choose to close the database + */ + private boolean confirmClose() { + // Database could not have been changed, since it is still loading + if (dataLoadingTask != null) { + dataLoadingTask.cancel(); + loading.setValue(false); + return true; + } + + String filename = getBibDatabaseContext() + .getDatabasePath() + .map(Path::toAbsolutePath) + .map(Path::toString) + .orElse(Localization.lang("untitled")); + + ButtonType saveChanges = new ButtonType(Localization.lang("Save changes"), ButtonBar.ButtonData.YES); + ButtonType discardChanges = new ButtonType(Localization.lang("Discard changes"), ButtonBar.ButtonData.NO); + ButtonType returnToLibrary = new ButtonType(Localization.lang("Return to library"), ButtonBar.ButtonData.CANCEL_CLOSE); + + Optional response = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.CONFIRMATION, + Localization.lang("Save before closing"), + Localization.lang("Library '%0' has changed.", filename), + saveChanges, discardChanges, returnToLibrary); + + if (response.isEmpty()) { + return true; + } + + ButtonType buttonType = response.get(); + + if (buttonType.equals(returnToLibrary)) { + return false; + } + + if (buttonType.equals(saveChanges)) { + try { + SaveDatabaseAction saveAction = new SaveDatabaseAction(this, dialogService, preferences, Injector.instantiateModelOrService(BibEntryTypesManager.class)); + if (saveAction.save()) { + return true; + } + // The action was either canceled or unsuccessful. + dialogService.notify(Localization.lang("Unable to save library")); + } catch (Throwable ex) { + LOGGER.error("A problem occurred when trying to save the file", ex); + dialogService.showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file."), ex); + } + // Save was cancelled or an error occurred. + return false; + } + + if (buttonType.equals(discardChanges)) { + BackupManager.discardBackup(bibDatabaseContext, preferences.getFilePreferences().getBackupDirectory()); + return true; + } + + return false; + } + + private void onCloseRequest(Event event) { + if (!requestClose()) { + event.consume(); + } + } + + /** + * Perform necessary cleanup when this Library is closed. + */ + private void onClosed(Event event) { + if (dataLoadingTask != null) { + dataLoadingTask.cancel(); + } + if (bibDatabaseContext.getLocation() == DatabaseLocation.SHARED) { + bibDatabaseContext.convertToLocalDatabase(); + bibDatabaseContext.getDBMSSynchronizer().closeSharedDatabase(); + bibDatabaseContext.clearDBMSSynchronizer(); + } + try { + changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); + } catch (RuntimeException e) { + LOGGER.error("Problem when closing change monitor", e); + } + try { + directoryMonitorManager.unregister(); + } catch (RuntimeException e) { + LOGGER.error("Problem when closing directory monitor", e); + } + try { + if (indexManager != null) { + indexManager.close(); + } + } catch (RuntimeException e) { + LOGGER.error("Problem when closing index manager", e); + } + try { + AutosaveManager.shutdown(bibDatabaseContext); + } catch (RuntimeException e) { + LOGGER.error("Problem when shutting down autosave manager", e); + } + try { + BackupManager.shutdown(bibDatabaseContext, + preferences.getFilePreferences().getBackupDirectory(), + preferences.getFilePreferences().shouldCreateBackup()); + } catch (RuntimeException e) { + LOGGER.error("Problem when shutting down backup manager", e); + } + + if (tableModel != null) { + tableModel.unbind(); + } + // clean up the groups map + stateManager.clearSelectedGroups(bibDatabaseContext); + } + + /** + * Get an array containing the currently selected entries. The array is stable and not changed if the selection changes + * + * @return A list containing the selected entries. Is never null. + */ + public List getSelectedEntries() { + return mainTable.getSelectedEntries(); + } + + public BibDatabaseContext getBibDatabaseContext() { + return this.bibDatabaseContext; + } + + public DirectoryMonitorManager getDirectoryMonitorManager() { + return directoryMonitorManager; + } + + public boolean isSaving() { + return saving; + } + + public void setSaving(boolean saving) { + this.saving = saving; + } + + public ObservableBooleanValue getLoading() { + return loading; + } + + public CountingUndoManager getUndoManager() { + return undoManager; + } + + public MainTable getMainTable() { + return mainTable; + } + + public ListProperty selectedGroupsProperty() { + return selectedGroupsProperty; + } + + public OptionalObjectProperty searchQueryProperty() { + return searchQueryProperty; + } + + public IntegerProperty resultSizeProperty() { + return resultSize; + } + + public FileAnnotationCache getAnnotationCache() { + return annotationCache; + } + + public void resetChangeMonitor() { + changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); + changeMonitor = Optional.of(new DatabaseChangeMonitor(bibDatabaseContext, + fileUpdateMonitor, + taskExecutor, + dialogService, + preferences, + databaseNotificationPane, + undoManager, + stateManager)); + } + + public void insertEntry(final BibEntry bibEntry) { + insertEntries(List.of(bibEntry)); + } + + public void insertEntries(final List entries) { + if (entries.isEmpty()) { + return; + } + + importHandler.importCleanedEntries(entries); + getUndoManager().addEdit(new UndoableInsertEntries(bibDatabaseContext.getDatabase(), entries)); + markBaseChanged(); + if (preferences.getEntryEditorPreferences().shouldOpenOnNewEntry()) { + showAndEdit(entries.getFirst()); + } else { + clearAndSelect(entries.getFirst()); + } + } + + public void copyEntry() { + int entriesCopied = doCopyEntry(getSelectedEntries()); + if (entriesCopied >= 0) { + dialogService.notify(Localization.lang("Copied %0 entry(s)", entriesCopied)); + } else { + dialogService.notify(Localization.lang("Copy failed", entriesCopied)); + } + } + + private int doCopyEntry(List selectedEntries) { + if (selectedEntries.isEmpty()) { + return 0; + } + + List stringConstants = bibDatabaseContext.getDatabase().getUsedStrings(selectedEntries); + try { + if (stringConstants.isEmpty()) { + clipBoardManager.setContent(selectedEntries, entryTypesManager); + } else { + clipBoardManager.setContent(selectedEntries, entryTypesManager, stringConstants); + } + return selectedEntries.size(); + } catch (IOException e) { + LOGGER.error("Error while copying selected entries to clipboard.", e); + return -1; + } + } + + public void pasteEntry() { + List entriesToAdd; + String content = ClipBoardManager.getContents(); + entriesToAdd = importHandler.handleBibTeXData(content); + if (entriesToAdd.isEmpty()) { + entriesToAdd = handleNonBibTeXStringData(content); + } + if (entriesToAdd.isEmpty()) { + return; + } + + importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); + } + + private List handleNonBibTeXStringData(String data) { + try { + return this.importHandler.handleStringData(data); + } catch ( + FetcherException exception) { + if (exception instanceof FetcherClientException) { + dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), Localization.lang("No data was found for the identifier")); + } else if (exception instanceof FetcherServerException) { + dialogService.showInformationDialogAndWait(Localization.lang("Look up identifier"), Localization.lang("Server not available")); + } else { + dialogService.showErrorDialogAndWait(exception); + } + return List.of(); + } + } + + public void dropEntry(List entriesToAdd) { + importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); + } + + public void cutEntry() { + int entriesCopied = doCopyEntry(getSelectedEntries()); + int entriesDeleted = doDeleteEntry(StandardActions.CUT, mainTable.getSelectedEntries()); + + if (entriesCopied == entriesDeleted) { + dialogService.notify(Localization.lang("Cut %0 entry(s)", entriesCopied)); + } else { + dialogService.notify(Localization.lang("Cut failed", entriesCopied)); + undoManager.undo(); + clipBoardManager.setContent(""); + } + } + + /** + * Removes the selected entries and files linked to selected entries from the database + */ + public void deleteEntry() { + int entriesDeleted = doDeleteEntry(StandardActions.DELETE_ENTRY, mainTable.getSelectedEntries()); + dialogService.notify(Localization.lang("Deleted %0 entry(s)", entriesDeleted)); + } + + public void deleteEntry(BibEntry entry) { + doDeleteEntry(StandardActions.DELETE_ENTRY, Collections.singletonList(entry)); + } + + /** + * Removes the selected entries and files linked to selected entries from the database + * + * @param mode If DELETE_ENTRY the user will get asked if he really wants to delete the entries, and it will be localized as "deleted". If true the action will be localized as "cut" + */ + private int doDeleteEntry(StandardActions mode, List entries) { + if (entries.isEmpty()) { + return 0; + } + if (mode == StandardActions.DELETE_ENTRY && !showDeleteConfirmationDialog(entries.size())) { + return -1; + } + + // Delete selected entries + getUndoManager().addEdit(new UndoableRemoveEntries(bibDatabaseContext.getDatabase(), entries, mode == StandardActions.CUT)); + bibDatabaseContext.getDatabase().removeEntries(entries); + + if (mode != StandardActions.CUT) { + List linkedFileList = entries.stream() + .flatMap(entry -> entry.getFiles().stream()) + .distinct() + .toList(); + + if (!linkedFileList.isEmpty()) { + List viewModels = linkedFileList.stream() + .map(linkedFile -> LinkedFileViewModel.fromLinkedFile(linkedFile, null, bibDatabaseContext, null, null, preferences)) + .collect(Collectors.toList()); + + new DeleteFileAction(dialogService, preferences.getFilePreferences(), bibDatabaseContext, viewModels).execute(); + } + } + + ensureNotShowingBottomPanel(entries); + markBaseChanged(); + + // prevent the main table from loosing focus + mainTable.requestFocus(); + + return entries.size(); + } + + public boolean isModified() { + return changedProperty.getValue(); + } + + public void markBaseChanged() { + this.changedProperty.setValue(true); + } + + public void markNonUndoableBaseChanged() { + this.nonUndoableChangeProperty.setValue(true); + this.changedProperty.setValue(true); + } + + public void resetChangedProperties() { + this.nonUndoableChangeProperty.setValue(false); + this.changedProperty.setValue(false); + } + + /** + * Creates a new library tab. Contents are loaded by the {@code dataLoadingTask}. Most of the other parameters are required by {@code resetChangeMonitor()}. + * + * @param dataLoadingTask The task to execute to load the data asynchronously. + * @param file the path to the file (loaded by the dataLoadingTask) + */ + public static LibraryTab createLibraryTab(BackgroundTask dataLoadingTask, + Path file, + DialogService dialogService, + AiService aiService, + GuiPreferences preferences, + StateManager stateManager, + LibraryTabContainer tabContainer, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { + BibDatabaseContext context = new BibDatabaseContext(); + context.setDatabasePath(file); + + LibraryTab newTab = new LibraryTab( + context, + tabContainer, + dialogService, + aiService, + preferences, + stateManager, + fileUpdateMonitor, + entryTypesManager, + undoManager, + clipBoardManager, + taskExecutor, + true); + + newTab.setDataLoadingTask(dataLoadingTask); + dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted) + .onSuccess(newTab::onDatabaseLoadingSucceed) + .onFailure(newTab::onDatabaseLoadingFailed) + .executeWith(taskExecutor); + + return newTab; + } + + public static LibraryTab createLibraryTab(BibDatabaseContext databaseContext, + LibraryTabContainer tabContainer, + DialogService dialogService, + AiService aiService, + GuiPreferences preferences, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + UndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { + Objects.requireNonNull(databaseContext); + + return new LibraryTab( + databaseContext, + tabContainer, + dialogService, + aiService, + preferences, + stateManager, + fileUpdateMonitor, + entryTypesManager, + (CountingUndoManager) undoManager, + clipBoardManager, + taskExecutor, + false); + } + + private class GroupTreeListener { + + @Subscribe + public void listen(EntriesAddedEvent addedEntriesEvent) { + // if the event is an undo, don't add it to the current group + if (addedEntriesEvent.getEntriesEventSource() == EntriesEventSource.UNDO) { + return; + } + + // Automatically add new entries to the selected group (or set of groups) + if (preferences.getGroupsPreferences().shouldAutoAssignGroup()) { + stateManager.getSelectedGroups(bibDatabaseContext).forEach( + selectedGroup -> selectedGroup.addEntriesToGroup(addedEntriesEvent.getBibEntries())); + } + } + } + + private class EntriesRemovedListener { + + @Subscribe + public void listen(EntriesRemovedEvent entriesRemovedEvent) { + ensureNotShowingBottomPanel(entriesRemovedEvent.getBibEntries()); + } + } + + private class IndexUpdateListener { + + @Subscribe + public void listen(EntriesAddedEvent addedEntryEvent) { + indexManager.addToIndex(addedEntryEvent.getBibEntries()); + } + + @Subscribe + public void listen(EntriesRemovedEvent removedEntriesEvent) { + indexManager.removeFromIndex(removedEntriesEvent.getBibEntries()); + } + + @Subscribe + public void listen(FieldChangedEvent fieldChangedEvent) { + indexManager.updateEntry(fieldChangedEvent); + } + } + + public static class DatabaseNotification extends NotificationPane { + public DatabaseNotification(Node content) { + super(content); + } + + public void notify(Node graphic, String text, List actions, Duration duration) { + this.setGraphic(graphic); + this.setText(text); + this.getActions().setAll(actions); + this.show(); + if ((duration != null) && !duration.equals(Duration.ZERO)) { + PauseTransition delay = new PauseTransition(duration); + delay.setOnFinished(e -> this.hide()); + delay.play(); + } + } + } + + public DatabaseNotification getNotificationPane() { + return databaseNotificationPane; + } + + @Override + public String toString() { + return "LibraryTab{" + + "bibDatabaseContext=" + bibDatabaseContext + + ", showing=" + showing + + '}'; + } + + public LibraryTabContainer getLibraryTabContainer() { + return tabContainer; + } +} diff --git a/jabref/src/main/java/org/jabref/gui/LibraryTabContainer.java b/jabref/src/main/java/org/jabref/gui/LibraryTabContainer.java new file mode 100644 index 00000000..139653c8 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/LibraryTabContainer.java @@ -0,0 +1,38 @@ +package org.jabref.gui; + +import java.util.List; + +import org.jabref.model.database.BibDatabaseContext; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public interface LibraryTabContainer { + List getLibraryTabs(); + + @Nullable + LibraryTab getCurrentLibraryTab(); + + void showLibraryTab(LibraryTab libraryTab); + + void addTab(BibDatabaseContext bibDatabaseContext, boolean raisePanel); + + void addTab(LibraryTab libraryTab, boolean raisePanel); + + /** + * Closes a designated libraryTab + * + * @param tab to be closed. + * @return true if closing the tab was successful + */ + boolean closeTab(@Nullable LibraryTab tab); + + boolean closeTabs(@NonNull List tabs); + + /** + * Refreshes the ui after changes to the preferences + */ + void refresh(); +} diff --git a/jabref/src/main/java/org/jabref/gui/StateManager.java b/jabref/src/main/java/org/jabref/gui/StateManager.java new file mode 100644 index 00000000..435c0186 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/StateManager.java @@ -0,0 +1,234 @@ +package org.jabref.gui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import javafx.collections.transformation.FilteredList; +import javafx.concurrent.Task; +import javafx.scene.Node; +import javafx.util.Pair; + +import org.jabref.gui.ai.components.aichat.AiChatWindow; +import org.jabref.gui.edit.automaticfiededitor.LastAutomaticFieldEditorEdit; +import org.jabref.gui.search.SearchType; +import org.jabref.gui.sidepane.SidePaneType; +import org.jabref.gui.util.CustomLocalDragboard; +import org.jabref.gui.util.DialogWindowState; +import org.jabref.gui.util.OptionalObjectProperty; +import org.jabref.logic.search.IndexManager; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.groups.GroupTreeNode; +import org.jabref.model.search.query.SearchQuery; + +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyBinding; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class manages the GUI-state of JabRef, including: + * + *

      + *
    • currently selected database
    • + *
    • currently selected group
    • + *
    • active search
    • + *
    • active number of search results
    • + *
    • focus owner
    • + *
    • dialog window sizes/positions
    • + *
    • opened AI chat window (controlled by {@link org.jabref.logic.ai.AiService})
    • + *
    + */ +public class StateManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(StateManager.class); + private final CustomLocalDragboard localDragboard = new CustomLocalDragboard(); + private final ObservableList openDatabases = FXCollections.observableArrayList(); + private final OptionalObjectProperty activeDatabase = OptionalObjectProperty.empty(); + private final OptionalObjectProperty activeTab = OptionalObjectProperty.empty(); + private final ObservableList selectedEntries = FXCollections.observableArrayList(); + private final ObservableMap> selectedGroups = FXCollections.observableHashMap(); + private final ObservableMap indexManagers = FXCollections.observableHashMap(); + private final OptionalObjectProperty activeSearchQuery = OptionalObjectProperty.empty(); + private final OptionalObjectProperty activeGlobalSearchQuery = OptionalObjectProperty.empty(); + private final IntegerProperty searchResultSize = new SimpleIntegerProperty(0); + private final IntegerProperty globalSearchResultSize = new SimpleIntegerProperty(0); + private final OptionalObjectProperty focusOwner = OptionalObjectProperty.empty(); + private final ObservableList, Task>> backgroundTasksPairs = FXCollections.observableArrayList(task -> new Observable[] {task.getValue().progressProperty(), task.getValue().runningProperty()}); + private final ObservableList> backgroundTasks = EasyBind.map(backgroundTasksPairs, Pair::getValue); + private final FilteredList> runningBackgroundTasks = new FilteredList<>(backgroundTasks, Task::isRunning); + private final BooleanBinding anyTaskRunning = Bindings.createBooleanBinding(() -> !runningBackgroundTasks.isEmpty(), runningBackgroundTasks); + private final EasyBinding anyTasksThatWillNotBeRecoveredRunning = EasyBind.reduce(backgroundTasksPairs, tasks -> tasks.anyMatch(task -> !task.getKey().willBeRecoveredAutomatically() && task.getValue().isRunning())); + private final EasyBinding tasksProgress = EasyBind.reduce(backgroundTasksPairs, tasks -> tasks.map(Pair::getValue).filter(Task::isRunning).mapToDouble(Task::getProgress).average().orElse(1)); + private final ObservableMap dialogWindowStates = FXCollections.observableHashMap(); + private final ObservableList visibleSidePanes = FXCollections.observableArrayList(); + private final ObjectProperty lastAutomaticFieldEditorEdit = new SimpleObjectProperty<>(); + private final ObservableList searchHistory = FXCollections.observableArrayList(); + private final List aiChatWindows = new ArrayList<>(); + + public ObservableList getVisibleSidePaneComponents() { + return visibleSidePanes; + } + + public CustomLocalDragboard getLocalDragboard() { + return localDragboard; + } + + public ObservableList getOpenDatabases() { + return openDatabases; + } + + public OptionalObjectProperty activeDatabaseProperty() { + return activeDatabase; + } + + public OptionalObjectProperty activeTabProperty() { + return activeTab; + } + + public OptionalObjectProperty activeSearchQuery(SearchType type) { + return type == SearchType.NORMAL_SEARCH ? activeSearchQuery : activeGlobalSearchQuery; + } + + public IntegerProperty searchResultSize(SearchType type) { + return type == SearchType.NORMAL_SEARCH ? searchResultSize : globalSearchResultSize; + } + + public ObservableList getSelectedEntries() { + return selectedEntries; + } + + public void setSelectedEntries(List newSelectedEntries) { + selectedEntries.setAll(newSelectedEntries); + } + + public void setSelectedGroups(BibDatabaseContext context, List newSelectedGroups) { + Objects.requireNonNull(newSelectedGroups); + selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()).setAll(newSelectedGroups); + } + + public ObservableList getSelectedGroups(BibDatabaseContext context) { + return selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()); + } + + public void clearSelectedGroups(BibDatabaseContext context) { + selectedGroups.computeIfAbsent(context.getUid(), k -> FXCollections.observableArrayList()).clear(); + } + + public void setIndexManager(BibDatabaseContext database, IndexManager indexManager) { + indexManagers.put(database.getUid(), indexManager); + } + + public Optional getIndexManager(BibDatabaseContext database) { + return Optional.ofNullable(indexManagers.get(database.getUid())); + } + + public Optional getActiveDatabase() { + return activeDatabase.get(); + } + + public void setActiveDatabase(BibDatabaseContext database) { + if (database == null) { + LOGGER.info("No open database detected"); + activeDatabaseProperty().set(Optional.empty()); + } else { + activeDatabaseProperty().set(Optional.of(database)); + } + } + + public OptionalObjectProperty focusOwnerProperty() { + return focusOwner; + } + + public Optional getFocusOwner() { + return focusOwner.get(); + } + + public ObservableList> getBackgroundTasks() { + return backgroundTasks; + } + + public ObservableList> getRunningBackgroundTasks() { + return runningBackgroundTasks; + } + + public void addBackgroundTask(BackgroundTask backgroundTask, Task task) { + this.backgroundTasksPairs.addFirst(new Pair<>(backgroundTask, task)); + } + + public BooleanBinding getAnyTaskRunning() { + return anyTaskRunning; + } + + public EasyBinding getAnyTasksThatWillNotBeRecoveredRunning() { + return anyTasksThatWillNotBeRecoveredRunning; + } + + public EasyBinding getTasksProgress() { + return tasksProgress; + } + + public DialogWindowState getDialogWindowState(String className) { + return dialogWindowStates.get(className); + } + + public void setDialogWindowState(String className, DialogWindowState state) { + dialogWindowStates.put(className, state); + } + + public ObjectProperty lastAutomaticFieldEditorEditProperty() { + return lastAutomaticFieldEditorEdit; + } + + public void setLastAutomaticFieldEditorEdit(LastAutomaticFieldEditorEdit automaticFieldEditorEdit) { + lastAutomaticFieldEditorEditProperty().set(automaticFieldEditorEdit); + } + + public List collectAllDatabasePaths() { + List list = new ArrayList<>(); + getOpenDatabases().stream() + .map(BibDatabaseContext::getDatabasePath) + .forEachOrdered(pathOptional -> pathOptional.ifPresentOrElse( + path -> list.add(path.toAbsolutePath().toString()), + () -> list.add(""))); + return list; + } + + public void addSearchHistory(String search) { + searchHistory.remove(search); + searchHistory.add(search); + } + + public ObservableList getWholeSearchHistory() { + return searchHistory; + } + + public List getLastSearchHistory(int size) { + int sizeSearches = searchHistory.size(); + if (size < sizeSearches) { + return searchHistory.subList(sizeSearches - size, sizeSearches); + } + return searchHistory; + } + + public void clearSearchHistory() { + searchHistory.clear(); + } + + public List getAiChatWindows() { + return aiChatWindows; + } +} diff --git a/jabref/src/main/java/org/jabref/gui/UpdateTimestampListener.java b/jabref/src/main/java/org/jabref/gui/UpdateTimestampListener.java new file mode 100644 index 00000000..b975773d --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/UpdateTimestampListener.java @@ -0,0 +1,29 @@ +package org.jabref.gui; + +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.model.entry.event.EntriesEventSource; +import org.jabref.model.entry.event.EntryChangedEvent; +import org.jabref.model.entry.field.StandardField; + +import com.google.common.eventbus.Subscribe; + +/** + * Updates the timestamp of changed entries if the feature is enabled + */ +class UpdateTimestampListener { + private final CliPreferences preferences; + + UpdateTimestampListener(CliPreferences preferences) { + this.preferences = preferences; + } + + @Subscribe + public void listen(EntryChangedEvent event) { + // The event source needs to be checked, since the timestamp is always updated on every change. The cleanup formatter is an exception to that behaviour, + // since it just should move the contents from the timestamp field to modificationdate or creationdate. + if (preferences.getTimestampPreferences().shouldAddModificationDate() && event.getEntriesEventSource() != EntriesEventSource.CLEANUP_TIMESTAMP) { + event.getBibEntry().setField(StandardField.MODIFICATIONDATE, + preferences.getTimestampPreferences().now()); + } + } +} diff --git a/jabref/src/main/java/org/jabref/gui/WorkspacePreferences.java b/jabref/src/main/java/org/jabref/gui/WorkspacePreferences.java new file mode 100644 index 00000000..2948be75 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/WorkspacePreferences.java @@ -0,0 +1,173 @@ +package org.jabref.gui; + +import java.util.List; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.gui.theme.Theme; +import org.jabref.logic.l10n.Language; + +public class WorkspacePreferences { + private final ObjectProperty language; + private final BooleanProperty shouldOverrideDefaultFontSize; + private final IntegerProperty mainFontSize; + private final IntegerProperty defaultFontSize; + private final ObjectProperty theme; + private final BooleanProperty themeSyncOs; + private final BooleanProperty shouldOpenLastEdited; + private final BooleanProperty showAdvancedHints; + private final BooleanProperty warnAboutDuplicatesInInspection; + private final BooleanProperty confirmDelete; + private final ObservableList selectedSlrCatalogs; + + public WorkspacePreferences(Language language, + boolean shouldOverrideDefaultFontSize, + int mainFontSize, + int defaultFontSize, + Theme theme, + boolean themeSyncOs, + boolean shouldOpenLastEdited, + boolean showAdvancedHints, + boolean warnAboutDuplicatesInInspection, + boolean confirmDelete, + List selectedSlrCatalogs) { + this.language = new SimpleObjectProperty<>(language); + this.shouldOverrideDefaultFontSize = new SimpleBooleanProperty(shouldOverrideDefaultFontSize); + this.mainFontSize = new SimpleIntegerProperty(mainFontSize); + this.defaultFontSize = new SimpleIntegerProperty(defaultFontSize); + this.theme = new SimpleObjectProperty<>(theme); + this.themeSyncOs = new SimpleBooleanProperty(themeSyncOs); + this.shouldOpenLastEdited = new SimpleBooleanProperty(shouldOpenLastEdited); + this.showAdvancedHints = new SimpleBooleanProperty(showAdvancedHints); + this.warnAboutDuplicatesInInspection = new SimpleBooleanProperty(warnAboutDuplicatesInInspection); + this.confirmDelete = new SimpleBooleanProperty(confirmDelete); + this.selectedSlrCatalogs = FXCollections.observableArrayList(selectedSlrCatalogs); + } + + public Language getLanguage() { + return language.get(); + } + + public ObjectProperty languageProperty() { + return language; + } + + public void setLanguage(Language language) { + this.language.set(language); + } + + public boolean shouldOverrideDefaultFontSize() { + return shouldOverrideDefaultFontSize.get(); + } + + public void setShouldOverrideDefaultFontSize(boolean newValue) { + shouldOverrideDefaultFontSize.set(newValue); + } + + public BooleanProperty shouldOverrideDefaultFontSizeProperty() { + return shouldOverrideDefaultFontSize; + } + + public int getMainFontSize() { + return mainFontSize.get(); + } + + public int getDefaultFontSize() { + return defaultFontSize.get(); + } + + public void setMainFontSize(int mainFontSize) { + this.mainFontSize.set(mainFontSize); + } + + public IntegerProperty mainFontSizeProperty() { + return mainFontSize; + } + + public Theme getTheme() { + return theme.get(); + } + + public void setTheme(Theme theme) { + this.theme.set(theme); + } + + public ObjectProperty themeProperty() { + return theme; + } + + public boolean shouldThemeSyncOs() { + return themeSyncOs.get(); + } + + public BooleanProperty themeSyncOsProperty() { + return themeSyncOs; + } + + public void setThemeSyncOs(boolean themeSyncOs) { + this.themeSyncOs.set(themeSyncOs); + } + + public boolean shouldOpenLastEdited() { + return shouldOpenLastEdited.get(); + } + + public BooleanProperty openLastEditedProperty() { + return shouldOpenLastEdited; + } + + public void setOpenLastEdited(boolean shouldOpenLastEdited) { + this.shouldOpenLastEdited.set(shouldOpenLastEdited); + } + + public boolean shouldShowAdvancedHints() { + return showAdvancedHints.get(); + } + + public BooleanProperty showAdvancedHintsProperty() { + return showAdvancedHints; + } + + public void setShowAdvancedHints(boolean showAdvancedHints) { + this.showAdvancedHints.set(showAdvancedHints); + } + + public boolean shouldWarnAboutDuplicatesInInspection() { + return warnAboutDuplicatesInInspection.get(); + } + + public BooleanProperty warnAboutDuplicatesInInspectionProperty() { + return warnAboutDuplicatesInInspection; + } + + public void setWarnAboutDuplicatesInInspection(boolean warnAboutDuplicatesInInspection) { + this.warnAboutDuplicatesInInspection.set(warnAboutDuplicatesInInspection); + } + + public boolean shouldConfirmDelete() { + return confirmDelete.get(); + } + + public BooleanProperty confirmDeleteProperty() { + return confirmDelete; + } + + public void setConfirmDelete(boolean confirmDelete) { + this.confirmDelete.set(confirmDelete); + } + + public ObservableList getSelectedSlrCatalogs() { + return selectedSlrCatalogs; + } + + public void setSelectedSlrCatalogs(List catalogs) { + selectedSlrCatalogs.setAll(catalogs); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/actions/Action.java b/jabref/src/main/java/org/jabref/gui/actions/Action.java new file mode 100644 index 00000000..e6ccfe48 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/actions/Action.java @@ -0,0 +1,22 @@ +package org.jabref.gui.actions; + +import java.util.Optional; + +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.keyboard.KeyBinding; + +public interface Action { + default Optional getIcon() { + return Optional.empty(); + } + + default Optional getKeyBinding() { + return Optional.empty(); + } + + String getText(); + + default String getDescription() { + return ""; + } +} diff --git a/jabref/src/main/java/org/jabref/gui/actions/ActionFactory.java b/jabref/src/main/java/org/jabref/gui/actions/ActionFactory.java new file mode 100644 index 00000000..74f1b25e --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/actions/ActionFactory.java @@ -0,0 +1,171 @@ +package org.jabref.gui.actions; + +import java.lang.reflect.InaccessibleObjectException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javafx.beans.binding.BooleanExpression; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBase; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.Label; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tooltip; + +import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.model.strings.StringUtil; + +import com.airhacks.afterburner.injection.Injector; +import com.sun.javafx.scene.control.ContextMenuContent; +import com.tobiasdiez.easybind.EasyBind; +import de.saxsys.mvvmfx.utils.commands.Command; +import org.controlsfx.control.action.ActionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper class to create and style controls according to an {@link Action}. + */ +public class ActionFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(ActionFactory.class); + + private final KeyBindingRepository keyBindingRepository; + + public ActionFactory() { + this.keyBindingRepository = Injector.instantiateModelOrService(KeyBindingRepository.class); + } + + /** + * For some reason the graphic is not set correctly by the {@link ActionUtils} class, so we have to fix this by hand + */ + private static void setGraphic(MenuItem node, Action action) { + node.graphicProperty().unbind(); + action.getIcon().ifPresent(icon -> node.setGraphic(icon.getGraphicNode())); + } + + /* + * Returns MenuItemContainer node associated with this menu item + * which can contain: + * 1. label node of type Label for displaying menu item text, + * 2. right node of type Label for displaying accelerator text, + * or an arrow if it's a Menu, + * 3. graphic node for displaying menu item icon, and + * 4. left node for displaying either radio button or check box. + * + * This is basically rewritten impl_styleableGetNode() which + * should not be used since it's marked as deprecated. + */ + private static Label getAssociatedNode(MenuItem menuItem) { + ContextMenuContent.MenuItemContainer container = (ContextMenuContent.MenuItemContainer) menuItem.getStyleableNode(); + + if (container == null) { + return null; + } else { + // We have to use reflection to get the associated label + try { + Method getLabel = ContextMenuContent.MenuItemContainer.class.getDeclaredMethod("getLabel"); + getLabel.setAccessible(true); + return (Label) getLabel.invoke(container); + } catch (InaccessibleObjectException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + LOGGER.warn("Could not get label of menu item", e); + } + } + return null; + } + + public MenuItem configureMenuItem(Action action, Command command, MenuItem menuItem) { + ActionUtils.configureMenuItem(new JabRefAction(action, command, keyBindingRepository), menuItem); + setGraphic(menuItem, action); + enableTooltips(command, menuItem); + return menuItem; + } + + private static void enableTooltips(Command command, MenuItem menuItem) { + if (command instanceof SimpleCommand simpleCommand) { + EasyBind.subscribe( + simpleCommand.statusMessageProperty(), + message -> { + Label label = getAssociatedNode(menuItem); + if (label != null) { + label.setMouseTransparent(false); + if (StringUtil.isBlank(message)) { + label.setTooltip(null); + } else { + label.setTooltip(new Tooltip(message)); + } + } + } + ); + } + } + + public MenuItem createMenuItem(Action action, Command command) { + MenuItem menuItem = new MenuItem(); + configureMenuItem(action, command, menuItem); + return menuItem; + } + + public CheckMenuItem createCheckMenuItem(Action action, Command command, boolean selected) { + CheckMenuItem checkMenuItem = ActionUtils.createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository)); + checkMenuItem.setSelected(selected); + setGraphic(checkMenuItem, action); + + return checkMenuItem; + } + + public CheckMenuItem createCheckMenuItem(Action action, Command command, BooleanExpression selectedBinding) { + CheckMenuItem checkMenuItem = ActionUtils.createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository)); + EasyBind.subscribe(selectedBinding, checkMenuItem::setSelected); + setGraphic(checkMenuItem, action); + + return checkMenuItem; + } + + public Menu createMenu(Action action) { + Menu menu = ActionUtils.createMenu(new JabRefAction(action, keyBindingRepository)); + + // For some reason the graphic is not set correctly, so let's fix this + setGraphic(menu, action); + return menu; + } + + public Menu createSubMenu(Action action, MenuItem... children) { + Menu menu = createMenu(action); + menu.getItems().addAll(children); + return menu; + } + + public Button createIconButton(Action action, Command command) { + Button button = ActionUtils.createButton(new JabRefAction(action, command, keyBindingRepository), ActionUtils.ActionTextBehavior.HIDE); + + button.getStyleClass().setAll("icon-button"); + + // For some reason the graphic is not set correctly, so let's fix this + button.graphicProperty().unbind(); + action.getIcon().ifPresent(icon -> button.setGraphic(icon.getGraphicNode())); + + // Prevent the buttons from stealing the focus + button.setFocusTraversable(false); + + return button; + } + + public ButtonBase configureIconButton(Action action, Command command, ButtonBase button) { + ActionUtils.unconfigureButton(button); + ActionUtils.configureButton( + new JabRefAction(action, command, keyBindingRepository), + button, + ActionUtils.ActionTextBehavior.HIDE); + + button.getStyleClass().add("icon-button"); + + // For some reason the graphic is not set correctly, so let's fix this + // ToDO: Find a way to reuse JabRefIconView + button.graphicProperty().unbind(); + action.getIcon().ifPresent(icon -> button.setGraphic(icon.getGraphicNode())); + + return button; + } +} diff --git a/jabref/src/main/java/org/jabref/gui/actions/ActionHelper.java b/jabref/src/main/java/org/jabref/gui/actions/ActionHelper.java new file mode 100644 index 00000000..437d38d9 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/actions/ActionHelper.java @@ -0,0 +1,104 @@ +package org.jabref.gui.actions; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import javafx.beans.binding.Binding; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanExpression; +import javafx.collections.ObservableList; +import javafx.scene.control.TabPane; + +import org.jabref.gui.StateManager; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.shared.DatabaseLocation; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.field.Field; + +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyBinding; + +public class ActionHelper { + + public static BooleanExpression needsDatabase(StateManager stateManager) { + return stateManager.activeDatabaseProperty().isPresent(); + } + + public static BooleanExpression needsSharedDatabase(StateManager stateManager) { + EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), context -> context.filter(c -> c.getLocation() == DatabaseLocation.SHARED).isPresent()); + return BooleanExpression.booleanExpression(binding); + } + + public static BooleanExpression needsMultipleDatabases(TabPane tabbedPane) { + return Bindings.size(tabbedPane.getTabs()).greaterThan(1); + } + + public static BooleanExpression needsStudyDatabase(StateManager stateManager) { + EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), context -> context.filter(BibDatabaseContext::isStudy).isPresent()); + return BooleanExpression.booleanExpression(binding); + } + + public static BooleanExpression needsEntriesSelected(StateManager stateManager) { + return Bindings.isNotEmpty(stateManager.getSelectedEntries()); + } + + public static BooleanExpression needsEntriesSelected(int numberOfEntries, StateManager stateManager) { + return Bindings.createBooleanBinding(() -> stateManager.getSelectedEntries().size() == numberOfEntries, + stateManager.getSelectedEntries()); + } + + public static BooleanExpression isFieldSetForSelectedEntry(Field field, StateManager stateManager) { + return isAnyFieldSetForSelectedEntry(Collections.singletonList(field), stateManager); + } + + public static BooleanExpression isAnyFieldSetForSelectedEntry(List fields, StateManager stateManager) { + ObservableList selectedEntries = stateManager.getSelectedEntries(); + Binding fieldsAreSet = EasyBind.valueAt(selectedEntries, 0) + .mapObservable(entry -> Bindings.createBooleanBinding(() -> { + return entry.getFields().stream().anyMatch(fields::contains); + }, entry.getFieldsObservable())) + .orElseOpt(false); + return BooleanExpression.booleanExpression(fieldsAreSet); + } + + public static BooleanExpression isFilePresentForSelectedEntry(StateManager stateManager, CliPreferences preferences) { + ObservableList selectedEntries = stateManager.getSelectedEntries(); + Binding fileIsPresent = EasyBind.valueAt(selectedEntries, 0).mapOpt(entry -> { + List files = entry.getFiles(); + + if ((!entry.getFiles().isEmpty()) && stateManager.getActiveDatabase().isPresent()) { + if (files.getFirst().isOnlineLink()) { + return true; + } + + Optional filename = FileUtil.find( + stateManager.getActiveDatabase().get(), + files.getFirst().getLink(), + preferences.getFilePreferences()); + return filename.isPresent(); + } else { + return false; + } + }).orElseOpt(false); + + return BooleanExpression.booleanExpression(fileIsPresent); + } + + /** + * Check if at least one of the selected entries has linked files + *
    + * Used in {@link org.jabref.gui.maintable.OpenExternalFileAction} when multiple entries selected + * + * @param stateManager manager for the state of the GUI + * @return a boolean binding + */ + public static BooleanExpression hasLinkedFileForSelectedEntries(StateManager stateManager) { + return BooleanExpression.booleanExpression(EasyBind.reduce(stateManager.getSelectedEntries(), + entries -> entries.anyMatch(entry -> !entry.getFiles().isEmpty()))); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/actions/JabRefAction.java b/jabref/src/main/java/org/jabref/gui/actions/JabRefAction.java new file mode 100644 index 00000000..50d6be4d --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/actions/JabRefAction.java @@ -0,0 +1,33 @@ +package org.jabref.gui.actions; + +import javafx.beans.binding.Bindings; + +import org.jabref.gui.keyboard.KeyBindingRepository; + +import de.saxsys.mvvmfx.utils.commands.Command; + +/** + * Wrapper around one of our actions from {@link Action} to convert them to controlsfx {@link org.controlsfx.control.action.Action}. + */ +class JabRefAction extends org.controlsfx.control.action.Action { + + public JabRefAction(Action action, KeyBindingRepository keyBindingRepository) { + super(action.getText()); + action.getIcon().ifPresent(icon -> setGraphic(icon.getGraphicNode())); + action.getKeyBinding().flatMap(keyBindingRepository::getKeyCombination).ifPresent(this::setAccelerator); + + setLongText(action.getDescription()); + } + + public JabRefAction(Action action, Command command, KeyBindingRepository keyBindingRepository) { + this(action, keyBindingRepository); + + setEventHandler(event -> command.execute()); + + disabledProperty().bind(command.executableProperty().not()); + + if (command instanceof SimpleCommand simpleCommand) { + longTextProperty().bind(Bindings.concat(action.getDescription(), simpleCommand.statusMessageProperty())); + } + } +} diff --git a/jabref/src/main/java/org/jabref/gui/actions/SimpleCommand.java b/jabref/src/main/java/org/jabref/gui/actions/SimpleCommand.java new file mode 100644 index 00000000..89ef7ad1 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/actions/SimpleCommand.java @@ -0,0 +1,35 @@ +package org.jabref.gui.actions; + +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.ReadOnlyStringWrapper; + +import org.jabref.gui.util.BindingsHelper; + +import de.saxsys.mvvmfx.utils.commands.CommandBase; + +/** + * A simple command that does not track progress of the action. + */ +public abstract class SimpleCommand extends CommandBase { + + protected ReadOnlyStringWrapper statusMessage = new ReadOnlyStringWrapper(""); + + public ReadOnlyStringProperty statusMessageProperty() { + return statusMessage.getReadOnlyProperty(); + } + + @Override + public double getProgress() { + return 0; + } + + @Override + public ReadOnlyDoubleProperty progressProperty() { + return null; + } + + public void setExecutable(boolean executable) { + this.executable.bind(BindingsHelper.constantOf(executable)); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/actions/StandardActions.java b/jabref/src/main/java/org/jabref/gui/actions/StandardActions.java new file mode 100644 index 00000000..df789a51 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -0,0 +1,291 @@ +package org.jabref.gui.actions; + +import java.util.Objects; +import java.util.Optional; + +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIcon; +import org.jabref.gui.keyboard.KeyBinding; +import org.jabref.logic.l10n.Localization; + +public enum StandardActions implements Action { + + COPY_MORE(Localization.lang("Copy") + "..."), + COPY_TITLE(Localization.lang("Copy title"), KeyBinding.COPY_TITLE), + COPY_KEY(Localization.lang("Copy citation key"), KeyBinding.COPY_CITATION_KEY), + COPY_CITE_KEY(Localization.lang("Copy citation key with configured cite command"), KeyBinding.COPY_CITE_CITATION_KEY), + COPY_KEY_AND_TITLE(Localization.lang("Copy citation key and title"), KeyBinding.COPY_CITATION_KEY_AND_TITLE), + COPY_KEY_AND_LINK(Localization.lang("Copy citation key and link"), KeyBinding.COPY_CITATION_KEY_AND_LINK), + COPY_CITATION_HTML(Localization.lang("Copy citation (html)"), KeyBinding.COPY_PREVIEW), + COPY_CITATION_TEXT(Localization.lang("Copy citation (text)")), + COPY_CITATION_PREVIEW(Localization.lang("Copy preview"), KeyBinding.COPY_PREVIEW), + EXPORT_TO_CLIPBOARD(Localization.lang("Export to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), + EXPORT_SELECTED_TO_CLIPBOARD(Localization.lang("Export selected entries to clipboard"), IconTheme.JabRefIcons.EXPORT_TO_CLIPBOARD), + COPY(Localization.lang("Copy"), IconTheme.JabRefIcons.COPY, KeyBinding.COPY), + PASTE(Localization.lang("Paste"), IconTheme.JabRefIcons.PASTE, KeyBinding.PASTE), + CUT(Localization.lang("Cut"), IconTheme.JabRefIcons.CUT, KeyBinding.CUT), + DELETE(Localization.lang("Delete"), IconTheme.JabRefIcons.DELETE_ENTRY), + DELETE_ENTRY(Localization.lang("Delete entry"), IconTheme.JabRefIcons.DELETE_ENTRY, KeyBinding.DELETE_ENTRY), + SEND(Localization.lang("Send"), IconTheme.JabRefIcons.EMAIL), + SEND_AS_EMAIL(Localization.lang("As Email")), + SEND_TO_KINDLE(Localization.lang("To Kindle")), + REBUILD_FULLTEXT_SEARCH_INDEX(Localization.lang("Rebuild fulltext search index"), IconTheme.JabRefIcons.FILE), + REDOWNLOAD_MISSING_FILES(Localization.lang("Redownload missing files"), IconTheme.JabRefIcons.DOWNLOAD), + OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), + EXTRACT_FILE_REFERENCES_ONLINE(Localization.lang("Extract references from file (online)"), IconTheme.JabRefIcons.FILE_STAR), + EXTRACT_FILE_REFERENCES_OFFLINE(Localization.lang("Extract references from file (offline)"), IconTheme.JabRefIcons.FILE_STAR), + OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI), + SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")), + MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/...")), + ATTACH_FILE(Localization.lang("Attach file"), IconTheme.JabRefIcons.ATTACH_FILE), + ATTACH_FILE_FROM_URL(Localization.lang("Attach file from URL"), IconTheme.JabRefIcons.DOWNLOAD_FILE), + PRIORITY(Localization.lang("Priority"), IconTheme.JabRefIcons.PRIORITY), + CLEAR_PRIORITY(Localization.lang("Clear priority")), + PRIORITY_HIGH(Localization.lang("Set priority to high"), IconTheme.JabRefIcons.PRIORITY_HIGH), + PRIORITY_MEDIUM(Localization.lang("Set priority to medium"), IconTheme.JabRefIcons.PRIORITY_MEDIUM), + PRIORITY_LOW(Localization.lang("Set priority to low"), IconTheme.JabRefIcons.PRIORITY_LOW), + QUALITY(Localization.lang("Quality"), IconTheme.JabRefIcons.QUALITY), + QUALITY_ASSURED(Localization.lang("Toggle quality assured"), IconTheme.JabRefIcons.QUALITY_ASSURED), + RANKING(Localization.lang("Rank"), IconTheme.JabRefIcons.RANKING), + CLEAR_RANK(Localization.lang("Clear rank")), + RANK_1(Localization.lang("Set rank to one"), IconTheme.JabRefIcons.RANK1), + RANK_2(Localization.lang("Set rank to two"), IconTheme.JabRefIcons.RANK2), + RANK_3(Localization.lang("Set rank to three"), IconTheme.JabRefIcons.RANK3), + RANK_4(Localization.lang("Set rank to four"), IconTheme.JabRefIcons.RANK4), + RANK_5(Localization.lang("Set rank to five"), IconTheme.JabRefIcons.RANK5), + PRINTED(Localization.lang("Printed"), IconTheme.JabRefIcons.PRINTED), + TOGGLE_PRINTED(Localization.lang("Toggle print status"), IconTheme.JabRefIcons.PRINTED), + READ_STATUS(Localization.lang("Read status"), IconTheme.JabRefIcons.READ_STATUS), + CLEAR_READ_STATUS(Localization.lang("Clear read status"), KeyBinding.CLEAR_READ_STATUS), + READ(Localization.lang("Set read status to read"), IconTheme.JabRefIcons.READ_STATUS_READ, KeyBinding.READ), + SKIMMED(Localization.lang("Set read status to skimmed"), IconTheme.JabRefIcons.READ_STATUS_SKIMMED, KeyBinding.SKIMMED), + RELEVANCE(Localization.lang("Relevance"), IconTheme.JabRefIcons.RELEVANCE), + RELEVANT(Localization.lang("Toggle relevance"), IconTheme.JabRefIcons.RELEVANCE), + NEW_LIBRARY(Localization.lang("New library"), IconTheme.JabRefIcons.NEW), + OPEN_LIBRARY(Localization.lang("Open library"), IconTheme.JabRefIcons.OPEN, KeyBinding.OPEN_DATABASE), + IMPORT(Localization.lang("Import"), IconTheme.JabRefIcons.IMPORT), + EXPORT(Localization.lang("Export"), IconTheme.JabRefIcons.EXPORT, KeyBinding.EXPORT), + SAVE_LIBRARY(Localization.lang("Save library"), IconTheme.JabRefIcons.SAVE, KeyBinding.SAVE_DATABASE), + SAVE_LIBRARY_AS(Localization.lang("Save library as..."), KeyBinding.SAVE_DATABASE_AS), + SAVE_SELECTED_AS_PLAIN_BIBTEX(Localization.lang("Save selected as plain BibTeX...")), + SAVE_ALL(Localization.lang("Save all"), Localization.lang("Save all open libraries"), IconTheme.JabRefIcons.SAVE_ALL, KeyBinding.SAVE_ALL), + IMPORT_INTO_NEW_LIBRARY(Localization.lang("Import into new library"), KeyBinding.IMPORT_INTO_NEW_DATABASE), + IMPORT_INTO_CURRENT_LIBRARY(Localization.lang("Import into current library"), KeyBinding.IMPORT_INTO_CURRENT_DATABASE), + EXPORT_ALL(Localization.lang("Export all entries")), + REMOTE_DB(Localization.lang("Shared database"), IconTheme.JabRefIcons.REMOTE_DATABASE), + EXPORT_SELECTED(Localization.lang("Export selected entries"), KeyBinding.EXPORT_SELECTED), + CONNECT_TO_SHARED_DB(Localization.lang("Connect to shared database"), IconTheme.JabRefIcons.CONNECT_DB), + PULL_CHANGES_FROM_SHARED_DB(Localization.lang("Pull changes from shared database"), KeyBinding.PULL_CHANGES_FROM_SHARED_DATABASE), + CLOSE_LIBRARY(Localization.lang("Close library"), Localization.lang("Close the current library"), IconTheme.JabRefIcons.CLOSE, KeyBinding.CLOSE_DATABASE), + CLOSE_OTHER_LIBRARIES(Localization.lang("Close others"), Localization.lang("Close other libraries"), IconTheme.JabRefIcons.CLOSE), + CLOSE_ALL_LIBRARIES(Localization.lang("Close all"), Localization.lang("Close all libraries"), IconTheme.JabRefIcons.CLOSE), + QUIT(Localization.lang("Quit"), Localization.lang("Quit JabRef"), IconTheme.JabRefIcons.CLOSE_JABREF, KeyBinding.QUIT_JABREF), + UNDO(Localization.lang("Undo"), IconTheme.JabRefIcons.UNDO, KeyBinding.UNDO), + REDO(Localization.lang("Redo"), IconTheme.JabRefIcons.REDO, KeyBinding.REDO), + REPLACE_ALL(Localization.lang("Find and replace"), KeyBinding.REPLACE_STRING), + MANAGE_KEYWORDS(Localization.lang("Manage keywords")), + MASS_SET_FIELDS(Localization.lang("Manage field names & content")), + + AUTOMATIC_FIELD_EDITOR(Localization.lang("Automatic field editor")), + TOGGLE_GROUPS(Localization.lang("Groups"), IconTheme.JabRefIcons.TOGGLE_GROUPS, KeyBinding.TOGGLE_GROUPS_INTERFACE), + TOGGLE_OO(Localization.lang("OpenOffice/LibreOffice"), IconTheme.JabRefIcons.FILE_OPENOFFICE, KeyBinding.OPEN_OPEN_OFFICE_LIBRE_OFFICE_CONNECTION), + TOGGLE_WEB_SEARCH(Localization.lang("Web search"), Localization.lang("Toggle web search interface"), IconTheme.JabRefIcons.WWW, KeyBinding.WEB_SEARCH), + + 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), + NEW_LIBRARY_FROM_PDF_ONLINE(Localization.lang("New library based on references in PDF file... (online)"), Localization.lang("This feature generates a new library based on the list of references in a PDF file. Thereby, it uses Grobid's functionality."), IconTheme.JabRefIcons.NEW), + NEW_LIBRARY_FROM_PDF_OFFLINE(Localization.lang("New library based on references in PDF file... (offline)"), Localization.lang("This feature generates a new library based on the list of references in a PDF file. Thereby, it uses JabRef's built-in functionality."), IconTheme.JabRefIcons.NEW), + WRITE_METADATA_TO_PDF(Localization.lang("Write metadata to PDF files"), Localization.lang("Will write metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_METADATA_TO_PDF), + + START_NEW_STUDY(Localization.lang("Start new systematic literature review")), + UPDATE_SEARCH_RESULTS_OF_STUDY(Localization.lang("Update study search results")), + EDIT_EXISTING_STUDY(Localization.lang("Manage study definition")), + + OPEN_DATABASE_FOLDER(Localization.lang("Reveal in file explorer")), + OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), IconTheme.JabRefIcons.FOLDER, KeyBinding.OPEN_FOLDER), + OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), + OPEN_CONSOLE(Localization.lang("Open terminal here"), Localization.lang("Open terminal here"), IconTheme.JabRefIcons.CONSOLE, KeyBinding.OPEN_CONSOLE), + COPY_LINKED_FILES(Localization.lang("Copy linked files to folder...")), + COPY_DOI(Localization.lang("Copy DOI")), + COPY_DOI_URL(Localization.lang("Copy DOI url")), + ABBREVIATE(Localization.lang("Abbreviate journal names")), + ABBREVIATE_DEFAULT(Localization.lang("default"), Localization.lang("Abbreviate journal names of the selected entries (DEFAULT abbreviation)"), KeyBinding.ABBREVIATE), + ABBREVIATE_DOTLESS(Localization.lang("dotless"), Localization.lang("Abbreviate journal names of the selected entries (DOTLESS abbreviation)")), + ABBREVIATE_SHORTEST_UNIQUE(Localization.lang("shortest unique"), Localization.lang("Abbreviate journal names of the selected entries (SHORTEST UNIQUE abbreviation)")), + UNABBREVIATE(Localization.lang("Unabbreviate journal names"), Localization.lang("Unabbreviate journal names of the selected entries"), KeyBinding.UNABBREVIATE), + + MANAGE_CUSTOM_EXPORTS(Localization.lang("Manage custom exports")), + MANAGE_CUSTOM_IMPORTS(Localization.lang("Manage custom imports")), + CUSTOMIZE_ENTRY_TYPES(Localization.lang("Customize entry types")), + SETUP_GENERAL_FIELDS(Localization.lang("Set up general fields")), + MANAGE_PROTECTED_TERMS(Localization.lang("Manage protected terms")), + CITATION_KEY_PATTERN(Localization.lang("Citation key patterns")), + SHOW_PREFS(Localization.lang("Preferences"), IconTheme.JabRefIcons.PREFERENCES, KeyBinding.SHOW_PREFS), + MANAGE_JOURNALS(Localization.lang("Manage journal abbreviations")), + CUSTOMIZE_KEYBINDING(Localization.lang("Customize keyboard shortcuts"), IconTheme.JabRefIcons.KEY_BINDINGS), + EDIT_ENTRY(Localization.lang("Open entry editor"), IconTheme.JabRefIcons.EDIT_ENTRY, KeyBinding.OPEN_CLOSE_ENTRY_EDITOR), + SHOW_PDF_VIEWER(Localization.lang("Open document viewer"), IconTheme.JabRefIcons.PDF_FILE), + NEXT_PREVIEW_STYLE(Localization.lang("Next preview style"), KeyBinding.NEXT_PREVIEW_LAYOUT), + PREVIOUS_PREVIEW_STYLE(Localization.lang("Previous preview style"), KeyBinding.PREVIOUS_PREVIEW_LAYOUT), + SELECT_ALL(Localization.lang("Select all"), KeyBinding.SELECT_ALL), + UNSELECT_ALL(Localization.lang("Unselect all")), + + EXPAND_ALL(Localization.lang("Expand all")), + COLLAPSE_ALL(Localization.lang("Collapse all")), + + NEW_ENTRY(Localization.lang("New entry"), IconTheme.JabRefIcons.ADD_ENTRY, KeyBinding.NEW_ENTRY), + NEW_ARTICLE(Localization.lang("New article"), IconTheme.JabRefIcons.ADD_ARTICLE), + NEW_ENTRY_FROM_PLAIN_TEXT(Localization.lang("New entry from plain text"), IconTheme.JabRefIcons.NEW_ENTRY_FROM_PLAIN_TEXT), + LIBRARY_PROPERTIES(Localization.lang("Library properties")), + FIND_DUPLICATES(Localization.lang("Find duplicates"), IconTheme.JabRefIcons.FIND_DUPLICATES), + MERGE_ENTRIES(Localization.lang("Merge entries"), IconTheme.JabRefIcons.MERGE_ENTRIES, KeyBinding.MERGE_ENTRIES), + RESOLVE_DUPLICATE_KEYS(Localization.lang("Resolve duplicate citation keys"), Localization.lang("Find and remove duplicate citation keys"), KeyBinding.RESOLVE_DUPLICATE_CITATION_KEYS), + CHECK_INTEGRITY(Localization.lang("Check integrity"), KeyBinding.CHECK_INTEGRITY), + FIND_UNLINKED_FILES(Localization.lang("Search for unlinked local files"), IconTheme.JabRefIcons.SEARCH, KeyBinding.FIND_UNLINKED_FILES), + AUTO_LINK_FILES(Localization.lang("Automatically set file links"), IconTheme.JabRefIcons.AUTO_FILE_LINK, KeyBinding.AUTOMATICALLY_LINK_FILES), + LOOKUP_DOC_IDENTIFIER(Localization.lang("Search document identifier online")), + LOOKUP_FULLTEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, KeyBinding.DOWNLOAD_FULL_TEXT), + GENERATE_CITE_KEY(Localization.lang("Generate citation key"), IconTheme.JabRefIcons.MAKE_KEY, KeyBinding.AUTOGENERATE_CITATION_KEYS), + GENERATE_CITE_KEYS(Localization.lang("Generate citation keys"), IconTheme.JabRefIcons.MAKE_KEY, KeyBinding.AUTOGENERATE_CITATION_KEYS), + DOWNLOAD_FULL_TEXT(Localization.lang("Search full text documents online"), IconTheme.JabRefIcons.FILE_SEARCH, KeyBinding.DOWNLOAD_FULL_TEXT), + CLEANUP_ENTRIES(Localization.lang("Cleanup entries"), IconTheme.JabRefIcons.CLEANUP_ENTRIES, KeyBinding.CLEANUP), + SET_FILE_LINKS(Localization.lang("Automatically set file links"), KeyBinding.AUTOMATICALLY_LINK_FILES), + + EDIT_FILE_LINK(Localization.lang("Edit"), IconTheme.JabRefIcons.EDIT, KeyBinding.OPEN_CLOSE_ENTRY_EDITOR), + DOWNLOAD_FILE(Localization.lang("Download file"), IconTheme.JabRefIcons.DOWNLOAD_FILE), + REDOWNLOAD_FILE(Localization.lang("Redownload file"), IconTheme.JabRefIcons.DOWNLOAD_FILE), + RENAME_FILE_TO_PATTERN(Localization.lang("Rename file to defined pattern"), IconTheme.JabRefIcons.AUTO_RENAME), + RENAME_FILE_TO_NAME(Localization.lang("Rename file to a given name"), IconTheme.JabRefIcons.RENAME, KeyBinding.REPLACE_STRING), + MOVE_FILE_TO_FOLDER(Localization.lang("Move file to file directory"), IconTheme.JabRefIcons.MOVE_TO_FOLDER), + MOVE_FILE_TO_FOLDER_AND_RENAME(Localization.lang("Move file to file directory and rename file")), + COPY_FILE_TO_FOLDER(Localization.lang("Copy linked file to folder..."), IconTheme.JabRefIcons.COPY_TO_FOLDER, KeyBinding.COPY), + REMOVE_LINK(Localization.lang("Remove link"), IconTheme.JabRefIcons.REMOVE_LINK), + DELETE_FILE(Localization.lang("Permanently delete local file"), IconTheme.JabRefIcons.DELETE_FILE, KeyBinding.DELETE_ENTRY), + + HELP(Localization.lang("Online help"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + HELP_GROUPS(Localization.lang("Open Help page"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + HELP_KEY_PATTERNS(Localization.lang("Help on key patterns"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + HELP_REGEX_SEARCH(Localization.lang("Help on regular expression search"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + HELP_NAME_FORMATTER(Localization.lang("Help on Name Formatting"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + HELP_SPECIAL_FIELDS(Localization.lang("Help on special fields"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + HELP_PUSH_TO_APPLICATION(Localization.lang("Help on external applications"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP), + WEB_MENU(Localization.lang("JabRef resources")), + OPEN_WEBPAGE(Localization.lang("Website"), Localization.lang("Opens JabRef's website"), IconTheme.JabRefIcons.HOME), + OPEN_FACEBOOK("Facebook", Localization.lang("Opens JabRef's Facebook page"), IconTheme.JabRefIcons.FACEBOOK), + OPEN_TWITTER("Twitter", Localization.lang("Opens JabRef's Twitter page"), IconTheme.JabRefIcons.TWITTER), + OPEN_BLOG(Localization.lang("Blog"), Localization.lang("Opens JabRef's blog"), IconTheme.JabRefIcons.BLOG), + OPEN_DEV_VERSION_LINK(Localization.lang("Development version"), Localization.lang("Opens a link where the current development version can be downloaded")), + OPEN_CHANGELOG(Localization.lang("View change log"), Localization.lang("See what has been changed in the JabRef versions")), + OPEN_GITHUB("GitHub", Localization.lang("Opens JabRef's GitHub page"), IconTheme.JabRefIcons.GITHUB), + DONATE(Localization.lang("Donate to JabRef"), Localization.lang("Donate to JabRef"), IconTheme.JabRefIcons.DONATE), + OPEN_FORUM(Localization.lang("Online help forum"), Localization.lang("Online help forum"), IconTheme.JabRefIcons.FORUM), + ERROR_CONSOLE(Localization.lang("View event log"), Localization.lang("Display all error messages")), + SEARCH_FOR_UPDATES(Localization.lang("Check for updates")), + ABOUT(Localization.lang("About JabRef"), Localization.lang("About JabRef")), + + EDIT_LIST(Localization.lang("Edit"), IconTheme.JabRefIcons.EDIT), + VIEW_LIST(Localization.lang("View"), IconTheme.JabRefIcons.FILE), + REMOVE_LIST(Localization.lang("Remove"), IconTheme.JabRefIcons.REMOVE), + RELOAD_LIST(Localization.lang("Reload"), IconTheme.JabRefIcons.REFRESH), + + GROUP_REMOVE(Localization.lang("Remove group")), + GROUP_REMOVE_KEEP_SUBGROUPS(Localization.lang("Keep subgroups")), + GROUP_REMOVE_WITH_SUBGROUPS(Localization.lang("Also remove subgroups")), + GROUP_CHAT(Localization.lang("Chat with group")), + GROUP_EDIT(Localization.lang("Edit group")), + GROUP_GENERATE_SUMMARIES(Localization.lang("Generate summaries for entries in the group")), + GROUP_GENERATE_EMBEDDINGS(Localization.lang("Generate embeddings for linked files in the group")), + GROUP_SUBGROUP_ADD(Localization.lang("Add subgroup")), + GROUP_SUBGROUP_REMOVE(Localization.lang("Remove subgroups")), + GROUP_SUBGROUP_SORT(Localization.lang("Sort subgroups A-Z")), + GROUP_SUBGROUP_SORT_REVERSE(Localization.lang("Sort subgroups Z-A")), + GROUP_SUBGROUP_SORT_ENTRIES(Localization.lang("Sort subgroups by # of entries (Descending)")), + GROUP_SUBGROUP_SORT_ENTRIES_REVERSE(Localization.lang("Sort subgroups by # of entries (Ascending)")), + GROUP_ENTRIES_ADD(Localization.lang("Add selected entries to this group")), + GROUP_ENTRIES_REMOVE(Localization.lang("Remove selected entries from this group")), + + CLEAR_EMBEDDINGS_CACHE(Localization.lang("Clear embeddings cache")); + + private String text; + private final String description; + private final Optional icon; + private final Optional keyBinding; + + StandardActions(String text) { + this(text, ""); + } + + StandardActions(String text, IconTheme.JabRefIcons icon) { + this.text = text; + this.description = ""; + this.icon = Optional.of(icon); + this.keyBinding = Optional.empty(); + } + + StandardActions(String text, IconTheme.JabRefIcons icon, KeyBinding keyBinding) { + this.text = text; + this.description = ""; + this.icon = Optional.of(icon); + this.keyBinding = Optional.of(keyBinding); + } + + StandardActions(String text, String description, IconTheme.JabRefIcons icon) { + this.text = text; + this.description = description; + this.icon = Optional.of(icon); + this.keyBinding = Optional.empty(); + } + + StandardActions(String text, String description, IconTheme.JabRefIcons icon, KeyBinding keyBinding) { + this.text = text; + this.description = description; + this.icon = Optional.of(icon); + this.keyBinding = Optional.of(keyBinding); + } + + StandardActions(String text, KeyBinding keyBinding) { + this.text = text; + this.description = ""; + this.keyBinding = Optional.of(keyBinding); + this.icon = Optional.empty(); + } + + StandardActions(String text, String description) { + this.text = text; + this.description = description; + this.icon = Optional.empty(); + this.keyBinding = Optional.empty(); + } + + StandardActions(String text, String description, KeyBinding keyBinding) { + this.text = text; + this.description = description; + this.icon = Optional.empty(); + this.keyBinding = Optional.of(keyBinding); + } + + @Override + public Optional getIcon() { + return icon; + } + + @Override + public Optional getKeyBinding() { + return keyBinding; + } + + @Override + public String getText() { + return text; + } + + @Override + public String getDescription() { + return description; + } + + public Action withText(String text) { + this.text = Objects.requireNonNull(text); + return this; + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java b/jabref/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java new file mode 100644 index 00000000..91b3e926 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/ClearEmbeddingsAction.java @@ -0,0 +1,61 @@ +package org.jabref.gui.ai; + +import java.util.List; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.model.entry.LinkedFile; + +import static org.jabref.gui.actions.ActionHelper.needsDatabase; + +public class ClearEmbeddingsAction extends SimpleCommand { + private final StateManager stateManager; + private final DialogService dialogService; + private final AiService aiService; + private final TaskExecutor taskExecutor; + + public ClearEmbeddingsAction(StateManager stateManager, + DialogService dialogService, + AiService aiService, + TaskExecutor taskExecutor) { + this.stateManager = stateManager; + this.dialogService = dialogService; + this.taskExecutor = taskExecutor; + this.aiService = aiService; + this.executable.bind(needsDatabase(stateManager)); + } + + @Override + public void execute() { + if (stateManager.getActiveDatabase().isEmpty()) { + return; + } + + boolean confirmed = dialogService.showConfirmationDialogAndWait( + Localization.lang("Clear embeddings cache"), + Localization.lang("Clear embeddings cache for current library?")); + + if (!confirmed) { + return; + } + + dialogService.notify(Localization.lang("Clearing embeddings cache...")); + + List linkedFiles = stateManager + .getActiveDatabase() + .get() + .getDatabase() + .getEntries() + .stream() + .flatMap(entry -> entry.getFiles().stream()) + .toList(); + + BackgroundTask.wrap(() -> aiService.getIngestionService().clearEmbeddingsFor(linkedFiles)) + .executeWith(taskExecutor); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml new file mode 100644 index 00000000..a479ace3 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.fxml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java new file mode 100644 index 00000000..25e52d29 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatComponent.java @@ -0,0 +1,272 @@ +package org.jabref.gui.ai.components.aichat; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; + +import org.jabref.gui.DialogService; +import org.jabref.gui.ai.components.aichat.chathistory.ChatHistoryComponent; +import org.jabref.gui.ai.components.aichat.chatprompt.ChatPromptComponent; +import org.jabref.gui.ai.components.util.Loadable; +import org.jabref.gui.ai.components.util.notifications.Notification; +import org.jabref.gui.ai.components.util.notifications.NotificationsComponent; +import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.util.UiTaskExecutor; +import org.jabref.logic.ai.AiPreferences; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.ai.chatting.AiChatLogic; +import org.jabref.logic.ai.util.CitationKeyCheck; +import org.jabref.logic.ai.util.ErrorMessage; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.ListUtil; + +import com.airhacks.afterburner.views.ViewLoader; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.UserMessage; +import org.controlsfx.control.PopOver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AiChatComponent extends VBox { + private static final Logger LOGGER = LoggerFactory.getLogger(AiChatComponent.class); + + private final AiService aiService; + private final ObservableList entries; + private final BibDatabaseContext bibDatabaseContext; + private final AiPreferences aiPreferences; + private final DialogService dialogService; + private final TaskExecutor taskExecutor; + + private final AiChatLogic aiChatLogic; + + private final ObservableList notifications = FXCollections.observableArrayList(); + + @FXML private Loadable uiLoadableChatHistory; + @FXML private ChatHistoryComponent uiChatHistory; + @FXML private Button notificationsButton; + @FXML private ChatPromptComponent chatPrompt; + @FXML private Label noticeText; + + public AiChatComponent(AiService aiService, + StringProperty name, + ObservableList chatHistory, + ObservableList entries, + BibDatabaseContext bibDatabaseContext, + AiPreferences aiPreferences, + DialogService dialogService, + TaskExecutor taskExecutor + ) { + this.aiService = aiService; + this.entries = entries; + this.bibDatabaseContext = bibDatabaseContext; + this.aiPreferences = aiPreferences; + this.dialogService = dialogService; + this.taskExecutor = taskExecutor; + + this.aiChatLogic = aiService.getAiChatService().makeChat(name, chatHistory, entries, bibDatabaseContext); + + aiService.getIngestionService().ingest(name, ListUtil.getLinkedFiles(entries).toList(), bibDatabaseContext); + + ViewLoader.view(this) + .root(this) + .load(); + } + + @FXML + public void initialize() { + uiChatHistory.setItems(aiChatLogic.getChatHistory()); + initializeChatPrompt(); + initializeNotice(); + initializeNotifications(); + } + + private void initializeNotifications() { + ListUtil.getLinkedFiles(entries).forEach(file -> + aiService.getIngestionService().ingest(file, bibDatabaseContext).stateProperty().addListener(obs -> updateNotifications())); + + updateNotifications(); + } + + private void initializeNotice() { + String newNotice = noticeText + .getText() + .replaceAll("%0", aiPreferences.getAiProvider().getLabel() + " " + aiPreferences.getSelectedChatModel()); + + noticeText.setText(newNotice); + } + + private void initializeChatPrompt() { + notificationsButton.setOnAction(event -> + new PopOver(new NotificationsComponent(notifications)) + .show(notificationsButton) + ); + + chatPrompt.setSendCallback(this::onSendMessage); + + chatPrompt.setCancelCallback(() -> chatPrompt.switchToNormalState()); + + chatPrompt.setRetryCallback(userMessage -> { + deleteLastMessage(); + deleteLastMessage(); + chatPrompt.switchToNormalState(); + onSendMessage(userMessage); + }); + + chatPrompt.requestPromptFocus(); + + updatePromptHistory(); + } + + private void updateNotifications() { + notifications.clear(); + notifications.addAll(entries.stream().map(this::updateNotificationsForEntry).flatMap(List::stream).toList()); + + notificationsButton.setVisible(!notifications.isEmpty()); + notificationsButton.setManaged(!notifications.isEmpty()); + + if (!notifications.isEmpty()) { + UiTaskExecutor.runInJavaFXThread(() -> notificationsButton.setGraphic(IconTheme.JabRefIcons.WARNING.withColor(Color.YELLOW).getGraphicNode())); + } + } + + private List updateNotificationsForEntry(BibEntry entry) { + List notifications = new ArrayList<>(); + + if (entries.size() == 1) { + if (entry.getCitationKey().isEmpty()) { + notifications.add(new Notification( + Localization.lang("No citation key for %0", entry.getAuthorTitleYear()), + Localization.lang("The chat history will not be stored in next sessions") + )); + } else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) { + notifications.add(new Notification( + Localization.lang("Invalid citation key for %0 (%1)", entry.getCitationKey().get(), entry.getAuthorTitleYear()), + Localization.lang("The chat history will not be stored in next sessions") + )); + } + } + + entry.getFiles().forEach(file -> { + if (!FileUtil.isPDFFile(Path.of(file.getLink()))) { + notifications.add(new Notification( + Localization.lang("File %0 is not a PDF file", file.getLink()), + Localization.lang("Only PDF files can be used for chatting") + )); + } + }); + + entry.getFiles().stream().map(file -> aiService.getIngestionService().ingest(file, bibDatabaseContext)).forEach(ingestionStatus -> { + switch (ingestionStatus.getState()) { + case PROCESSING -> notifications.add(new Notification( + Localization.lang("File %0 is currently being processed", ingestionStatus.getObject().getLink()), + Localization.lang("After the file will be ingested, you will be able to chat with it.") + )); + + case ERROR -> { + assert ingestionStatus.getException().isPresent(); // When the state is ERROR, the exception must be present. + + notifications.add(new Notification( + Localization.lang("File %0 could not be ingested", ingestionStatus.getObject().getLink()), + ingestionStatus.getException().get().getLocalizedMessage() + )); + } + + case SUCCESS -> { } + } + }); + + return notifications; + } + + private void onSendMessage(String userPrompt) { + UserMessage userMessage = new UserMessage(userPrompt); + updatePromptHistory(); + setLoading(true); + + BackgroundTask task = + BackgroundTask + .wrap(() -> aiChatLogic.execute(userMessage)) + .showToUser(true) + .onSuccess(aiMessage -> { + setLoading(false); + chatPrompt.requestPromptFocus(); + }) + .onFailure(e -> { + LOGGER.error("Got an error while sending a message to AI", e); + setLoading(false); + + // Typically, if user has entered an invalid API base URL, we get either "401 - null" or "404 - null" strings. + // Since there might be other strings returned from other API endpoints, we use startsWith() here. + if (e.getMessage().startsWith("404") || e.getMessage().startsWith("401")) { + addError(Localization.lang("API base URL setting appears to be incorrect. Please check it in AI expert settings.")); + } else { + addError(e.getMessage()); + } + + chatPrompt.switchToErrorState(userPrompt); + }); + + task.titleProperty().set(Localization.lang("Waiting for AI reply...")); + + task.executeWith(taskExecutor); + } + + private void addError(String error) { + ErrorMessage chatMessage = new ErrorMessage(error); + aiChatLogic.getChatHistory().add(chatMessage); + } + + private void updatePromptHistory() { + chatPrompt.getHistory().clear(); + chatPrompt.getHistory().addAll(getReversedUserMessagesStream().map(UserMessage::singleText).toList()); + } + + private Stream getReversedUserMessagesStream() { + return aiChatLogic + .getChatHistory() + .reversed() + .stream() + .filter(message -> message instanceof UserMessage) + .map(UserMessage.class::cast); + } + + private void setLoading(boolean loading) { + uiLoadableChatHistory.setLoading(loading); + chatPrompt.setDisableToButtons(loading); + } + + @FXML + private void onClearChatHistory() { + boolean agreed = dialogService.showConfirmationDialogAndWait( + Localization.lang("Clear chat history"), + Localization.lang("Are you sure you want to clear the chat history of this entry?") + ); + + if (agreed) { + aiChatLogic.getChatHistory().clear(); + } + } + + private void deleteLastMessage() { + if (!aiChatLogic.getChatHistory().isEmpty()) { + int index = aiChatLogic.getChatHistory().size() - 1; + aiChatLogic.getChatHistory().remove(index); + } + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java new file mode 100644 index 00000000..c8f48ecb --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatGuardedComponent.java @@ -0,0 +1,74 @@ +package org.jabref.gui.ai.components.aichat; + +import javafx.beans.property.StringProperty; +import javafx.collections.ObservableList; +import javafx.scene.Node; + +import org.jabref.gui.DialogService; +import org.jabref.gui.ai.components.util.EmbeddingModelGuardedComponent; +import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.jabref.logic.ai.AiPreferences; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; + +import dev.langchain4j.data.message.ChatMessage; + +/** + * Main class for AI chatting. It checks if the AI features are enabled and if the embedding model is properly set up. + */ +public class AiChatGuardedComponent extends EmbeddingModelGuardedComponent { + /// This field is used for two purposes: + /// 1. Logging + /// 2. Title of group chat window + /// Thus, if you use {@link AiChatGuardedComponent} for one entry in {@link EntryEditor}, then you may not localize + /// this parameter. However, for group chat window, you should. + private final StringProperty name; + + private final ObservableList chatHistory; + private final BibDatabaseContext bibDatabaseContext; + private final ObservableList entries; + private final AiService aiService; + private final DialogService dialogService; + private final AiPreferences aiPreferences; + private final TaskExecutor taskExecutor; + + public AiChatGuardedComponent(StringProperty name, + ObservableList chatHistory, + BibDatabaseContext bibDatabaseContext, + ObservableList entries, + AiService aiService, + DialogService dialogService, + AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + TaskExecutor taskExecutor + ) { + super(aiService, aiPreferences, externalApplicationsPreferences, dialogService); + + this.name = name; + this.chatHistory = chatHistory; + this.bibDatabaseContext = bibDatabaseContext; + this.entries = entries; + this.aiService = aiService; + this.dialogService = dialogService; + this.aiPreferences = aiPreferences; + this.taskExecutor = taskExecutor; + + rebuildUi(); + } + + @Override + protected Node showEmbeddingModelGuardedContent() { + return new AiChatComponent( + aiService, + name, + chatHistory, + entries, + bibDatabaseContext, + aiPreferences, + dialogService, + taskExecutor + ); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java new file mode 100644 index 00000000..7e4dcd8b --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/AiChatWindow.java @@ -0,0 +1,67 @@ +package org.jabref.gui.ai.components.aichat; + +import javafx.beans.property.StringProperty; +import javafx.collections.ObservableList; +import javafx.scene.Scene; + +import org.jabref.gui.DialogService; +import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.jabref.gui.util.BaseWindow; +import org.jabref.logic.ai.AiPreferences; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; + +import dev.langchain4j.data.message.ChatMessage; + +public class AiChatWindow extends BaseWindow { + private final AiService aiService; + private final DialogService dialogService; + private final AiPreferences aiPreferences; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + private final TaskExecutor taskExecutor; + + // This field is used for finding an existing AI chat window when user wants to chat with the same group again. + private String chatName; + + public AiChatWindow(AiService aiService, + DialogService dialogService, + AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + TaskExecutor taskExecutor + ) { + this.aiService = aiService; + this.dialogService = dialogService; + this.aiPreferences = aiPreferences; + this.externalApplicationsPreferences = externalApplicationsPreferences; + this.taskExecutor = taskExecutor; + } + + public void setChat(StringProperty name, ObservableList chatHistory, BibDatabaseContext bibDatabaseContext, ObservableList entries) { + setTitle(Localization.lang("AI chat with %0", name.getValue())); + chatName = name.getValue(); + setScene( + new Scene( + new AiChatGuardedComponent( + name, + chatHistory, + bibDatabaseContext, + entries, + aiService, + dialogService, + aiPreferences, + externalApplicationsPreferences, + taskExecutor + ), + 800, + 600 + ) + ); + } + + public String getChatName() { + return chatName; + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.fxml b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.fxml new file mode 100644 index 00000000..20836e0e --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java new file mode 100644 index 00000000..c99e125b --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chathistory/ChatHistoryComponent.java @@ -0,0 +1,53 @@ +package org.jabref.gui.ai.components.aichat.chathistory; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.VBox; + +import org.jabref.gui.ai.components.aichat.chatmessage.ChatMessageComponent; +import org.jabref.gui.util.UiTaskExecutor; + +import com.airhacks.afterburner.views.ViewLoader; +import dev.langchain4j.data.message.ChatMessage; + +public class ChatHistoryComponent extends ScrollPane { + @FXML private VBox vBox; + + public ChatHistoryComponent() { + ViewLoader.view(this) + .root(this) + .load(); + + this.needsLayoutProperty().addListener((obs, oldValue, newValue) -> { + if (newValue) { + scrollDown(); + } + }); + } + + /** + * @implNote You must call this method only once. + */ + public void setItems(ObservableList items) { + fill(items); + items.addListener((ListChangeListener) obs -> fill(items)); + } + + private void fill(ObservableList items) { + UiTaskExecutor.runInJavaFXThread(() -> { + vBox.getChildren().clear(); + items.forEach(chatMessage -> + vBox.getChildren().add(new ChatMessageComponent(chatMessage, chatMessageComponent -> { + int index = vBox.getChildren().indexOf(chatMessageComponent); + items.remove(index); + }))); + }); + } + + public void scrollDown() { + this.layout(); + this.setVvalue(this.getVmax()); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.fxml b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.fxml new file mode 100644 index 00000000..92f42887 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java new file mode 100644 index 00000000..511c3d06 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatmessage/ChatMessageComponent.java @@ -0,0 +1,109 @@ +package org.jabref.gui.ai.components.aichat.chatmessage; + +import java.util.function.Consumer; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXML; +import javafx.geometry.NodeOrientation; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import org.jabref.logic.ai.util.ErrorMessage; +import org.jabref.logic.l10n.Localization; + +import com.airhacks.afterburner.views.ViewLoader; +import com.dlsc.gemsfx.ExpandingTextArea; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.UserMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ChatMessageComponent extends HBox { + private static final Logger LOGGER = LoggerFactory.getLogger(ChatMessageComponent.class); + + private final ObjectProperty chatMessage = new SimpleObjectProperty<>(); + private final ObjectProperty> onDelete = new SimpleObjectProperty<>(); + + @FXML private HBox wrapperHBox; + @FXML private VBox vBox; + @FXML private Label sourceLabel; + @FXML private ExpandingTextArea contentTextArea; + @FXML private VBox buttonsVBox; + + public ChatMessageComponent() { + ViewLoader.view(this) + .root(this) + .load(); + + chatMessage.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + loadChatMessage(); + } + }); + } + + public ChatMessageComponent(ChatMessage chatMessage, Consumer onDeleteCallback) { + this(); + setChatMessage(chatMessage); + setOnDelete(onDeleteCallback); + } + + public void setChatMessage(ChatMessage chatMessage) { + this.chatMessage.set(chatMessage); + } + + public ChatMessage getChatMessage() { + return chatMessage.get(); + } + + public void setOnDelete(Consumer onDeleteCallback) { + this.onDelete.set(onDeleteCallback); + } + + private void loadChatMessage() { + switch (chatMessage.get()) { + case UserMessage userMessage -> { + setColor("-jr-ai-message-user", "-jr-ai-message-user-border"); + setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT); + sourceLabel.setText(Localization.lang("User")); + contentTextArea.setText(userMessage.singleText()); + } + + case AiMessage aiMessage -> { + setColor("-jr-ai-message-ai", "-jr-ai-message-ai-border"); + setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); + sourceLabel.setText(Localization.lang("AI")); + contentTextArea.setText(aiMessage.text()); + } + + case ErrorMessage errorMessage -> { + setColor("-jr-ai-message-error", "-jr-ai-message-error-border"); + setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT); + sourceLabel.setText(Localization.lang("Error")); + contentTextArea.setText(errorMessage.getText()); + } + + default -> + LOGGER.error("ChatMessageComponent supports only user, AI, or error messages, but other type was passed: {}", chatMessage.get().type().name()); + } + } + + @FXML + private void initialize() { + buttonsVBox.visibleProperty().bind(wrapperHBox.hoverProperty()); + } + + @FXML + private void onDeleteClick() { + if (onDelete.get() != null) { + onDelete.get().accept(this); + } + } + + private void setColor(String fillColor, String borderColor) { + vBox.setStyle("-fx-background-color: " + fillColor + "; -fx-border-radius: 10; -fx-background-radius: 10; -fx-border-color: " + borderColor + "; -fx-border-width: 3;"); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.fxml b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.fxml new file mode 100644 index 00000000..04c53eaa --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/aichat/chatprompt/ChatPromptComponent.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java b/jabref/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java new file mode 100644 index 00000000..4ef78da8 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.java @@ -0,0 +1,111 @@ +package org.jabref.gui.ai.components.privacynotice; + +import java.io.IOException; + +import javafx.fxml.FXML; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.ScrollPane; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + +import org.jabref.gui.DialogService; +import org.jabref.gui.desktop.os.NativeDesktop; +import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.jabref.logic.ai.AiPreferences; +import org.jabref.model.ai.AiProvider; + +import com.airhacks.afterburner.views.ViewLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PrivacyNoticeComponent extends ScrollPane { + private final Logger LOGGER = LoggerFactory.getLogger(PrivacyNoticeComponent.class); + + @FXML private TextFlow openAiPrivacyTextFlow; + @FXML private TextFlow mistralAiPrivacyTextFlow; + @FXML private TextFlow geminiPrivacyTextFlow; + @FXML private TextFlow huggingFacePrivacyTextFlow; + @FXML private TextFlow gpt4AllTextFlow; + @FXML private Text embeddingModelText; + + private final AiPreferences aiPreferences; + private final Runnable onIAgreeButtonClickCallback; + private final DialogService dialogService; + private final ExternalApplicationsPreferences externalApplicationsPreferences; + + public PrivacyNoticeComponent(AiPreferences aiPreferences, Runnable onIAgreeButtonClickCallback, ExternalApplicationsPreferences externalApplicationsPreferences, DialogService dialogService) { + this.aiPreferences = aiPreferences; + this.onIAgreeButtonClickCallback = onIAgreeButtonClickCallback; + this.externalApplicationsPreferences = externalApplicationsPreferences; + this.dialogService = dialogService; + + ViewLoader.view(this) + .root(this) + .load(); + } + + @FXML + private void initialize() { + initPrivacyHyperlink(openAiPrivacyTextFlow, AiProvider.OPEN_AI); + initPrivacyHyperlink(mistralAiPrivacyTextFlow, AiProvider.MISTRAL_AI); + initPrivacyHyperlink(geminiPrivacyTextFlow, AiProvider.GEMINI); + initPrivacyHyperlink(huggingFacePrivacyTextFlow, AiProvider.HUGGING_FACE); + initPrivacyHyperlink(gpt4AllTextFlow, AiProvider.GPT4ALL); + + String newEmbeddingModelText = embeddingModelText.getText().replaceAll("%0", aiPreferences.getEmbeddingModel().sizeInfo()); + embeddingModelText.setText(newEmbeddingModelText); + + // Because of the https://bugs.openjdk.org/browse/JDK-8090400 bug, the text in the privacy policy cannot be + // fully wrapped. + + embeddingModelText.wrappingWidthProperty().bind(this.widthProperty()); + } + + private void initPrivacyHyperlink(TextFlow textFlow, AiProvider aiProvider) { + if (textFlow.getChildren().isEmpty() || !(textFlow.getChildren().getFirst() instanceof Text text)) { + return; + } + + String replacedText = text.getText().replaceAll("%0", aiProvider.getLabel()).replace("%1", ""); + + replacedText = replacedText.endsWith(".") ? replacedText.substring(0, replacedText.length() - 1) : replacedText; + + text.setText(replacedText); + text.wrappingWidthProperty().bind(this.widthProperty()); + + Hyperlink hyperlink = new Hyperlink(aiProvider.getApiUrl()); + hyperlink.setWrapText(true); + hyperlink.setFont(text.getFont()); + hyperlink.setOnAction(event -> { + openBrowser(aiProvider.getApiUrl()); + }); + + textFlow.getChildren().add(hyperlink); + + Text dot = new Text("."); + dot.setFont(text.getFont()); + dot.wrappingWidthProperty().bind(this.widthProperty()); + + textFlow.getChildren().add(dot); + } + + @FXML + private void onIAgreeButtonClick() { + aiPreferences.setEnableAi(true); + onIAgreeButtonClickCallback.run(); + } + + @FXML + private void onDjlPrivacyPolicyClick() { + openBrowser("https://github.com/deepjavalibrary/djl/discussions/3370#discussioncomment-10233632"); + } + + private void openBrowser(String link) { + try { + NativeDesktop.openBrowser(link, externalApplicationsPreferences); + } catch (IOException e) { + LOGGER.error("Error opening the browser to the Privacy Policy page of the AI provider.", e); + dialogService.showErrorDialogAndWait(e); + } + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java b/jabref/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java new file mode 100644 index 00000000..ed8e6698 --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/summary/SummaryComponent.java @@ -0,0 +1,157 @@ +package org.jabref.gui.ai.components.summary; + +import java.nio.file.Path; + +import javafx.scene.Node; + +import org.jabref.gui.DialogService; +import org.jabref.gui.ai.components.privacynotice.AiPrivacyNoticeGuardedComponent; +import org.jabref.gui.ai.components.util.errorstate.ErrorStateComponent; +import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.jabref.logic.ai.AiPreferences; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.ai.processingstatus.ProcessingInfo; +import org.jabref.logic.ai.summarization.Summary; +import org.jabref.logic.ai.util.CitationKeyCheck; +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SummaryComponent extends AiPrivacyNoticeGuardedComponent { + private static final Logger LOGGER = LoggerFactory.getLogger(SummaryComponent.class); + + private final BibDatabaseContext bibDatabaseContext; + private final BibEntry entry; + private final CitationKeyGenerator citationKeyGenerator; + private final AiService aiService; + private final AiPreferences aiPreferences; + + public SummaryComponent(BibDatabaseContext bibDatabaseContext, + BibEntry entry, + AiService aiService, + AiPreferences aiPreferences, + ExternalApplicationsPreferences externalApplicationsPreferences, + CitationKeyPatternPreferences citationKeyPatternPreferences, + DialogService dialogService + ) { + super(aiPreferences, externalApplicationsPreferences, dialogService); + + this.bibDatabaseContext = bibDatabaseContext; + this.entry = entry; + this.citationKeyGenerator = new CitationKeyGenerator(bibDatabaseContext, citationKeyPatternPreferences); + this.aiService = aiService; + this.aiPreferences = aiPreferences; + + aiService.getSummariesService().summarize(entry, bibDatabaseContext).stateProperty().addListener(o -> rebuildUi()); + + rebuildUi(); + } + + @Override + protected Node showPrivacyPolicyGuardedContent() { + if (bibDatabaseContext.getDatabasePath().isEmpty()) { + return showErrorNoDatabasePath(); + } else if (entry.getFiles().isEmpty()) { + return showErrorNoFiles(); + } else if (entry.getFiles().stream().map(LinkedFile::getLink).map(Path::of).noneMatch(FileUtil::isPDFFile)) { + return showErrorNotPdfs(); + } else if (!CitationKeyCheck.citationKeyIsPresentAndUnique(bibDatabaseContext, entry)) { + return tryToGenerateCitationKeyThenBind(entry); + } else { + return tryToShowSummary(); + } + } + + private Node showErrorNoDatabasePath() { + return new ErrorStateComponent( + Localization.lang("Unable to generate summary"), + Localization.lang("The path of the current library is not set, but it is required for summarization") + ); + } + + private Node showErrorNotPdfs() { + return new ErrorStateComponent( + Localization.lang("Unable to generate summary"), + Localization.lang("Only PDF files are supported.") + ); + } + + private Node showErrorNoFiles() { + return new ErrorStateComponent( + Localization.lang("Unable to generate summary"), + Localization.lang("Please attach at least one PDF file to enable summarization of PDF file(s).") + ); + } + + private Node tryToGenerateCitationKeyThenBind(BibEntry entry) { + if (citationKeyGenerator.generateAndSetKey(entry).isEmpty()) { + return new ErrorStateComponent( + Localization.lang("Unable to generate summary"), + Localization.lang("Please provide a non-empty and unique citation key for this entry.") + ); + } else { + return showPrivacyPolicyGuardedContent(); + } + } + + private Node tryToShowSummary() { + ProcessingInfo processingInfo = aiService.getSummariesService().summarize(entry, bibDatabaseContext); + + return switch (processingInfo.getState()) { + case SUCCESS -> { + assert processingInfo.getData().isPresent(); // When the state is SUCCESS, the data must be present. + yield showSummary(processingInfo.getData().get()); + } + case ERROR -> + showErrorWhileSummarizing(processingInfo); + case PROCESSING, + STOPPED -> + showErrorNotSummarized(); + }; + } + + private Node showErrorWhileSummarizing(ProcessingInfo processingInfo) { + assert processingInfo.getException().isPresent(); // When the state is ERROR, the exception must be present. + + LOGGER.error("Got an error while generating a summary for entry {}", entry.getCitationKey().orElse(""), processingInfo.getException().get()); + + return ErrorStateComponent.withTextAreaAndButton( + Localization.lang("Unable to chat"), + Localization.lang("Got error while processing the file:"), + processingInfo.getException().get().getLocalizedMessage(), + Localization.lang("Regenerate"), + () -> aiService.getSummariesService().regenerateSummary(entry, bibDatabaseContext) + ); + } + + private Node showErrorNotSummarized() { + return ErrorStateComponent.withSpinner( + Localization.lang("Processing..."), + Localization.lang("The attached file(s) are currently being processed by %0. Once completed, you will be able to see the summary.", aiPreferences.getAiProvider().getLabel()) + ); + } + + private Node showSummary(Summary summary) { + return new SummaryShowingComponent(summary, () -> { + if (bibDatabaseContext.getDatabasePath().isEmpty()) { + LOGGER.error("Bib database path is not set, but it was expected to be present. Unable to regenerate summary"); + return; + } + + if (entry.getCitationKey().isEmpty()) { + LOGGER.error("Citation key is not set, but it was expected to be present. Unable to regenerate summary"); + return; + } + + aiService.getSummariesService().regenerateSummary(entry, bibDatabaseContext); + // No need to rebuildUi(), because this class listens to the state of ProcessingInfo of the summary. + }); + } +} diff --git a/jabref/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.fxml b/jabref/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.fxml new file mode 100644 index 00000000..f248bcca --- /dev/null +++ b/jabref/src/main/java/org/jabref/gui/ai/components/summary/SummaryShowingComponent.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + +