Skip to content

Conversation

@Hamza-Azeem
Copy link

@Hamza-Azeem Hamza-Azeem commented Dec 22, 2025

User description

Closes #14410

Handled PushToApplicationPreferences removed default config from JabRefCliPreferences, added private constructor for
PushToApplicationPreferences to handle default config with getDefault method, setAll method and followed the rest of instructions. Moved logic of getEmptyIsDefault method from JabRefCliPreferences to PushToApplicationPreferences but didnt delete it from JabRefCliPreferences. Modified PushApplications class by adding a new instance variable(key).

Steps to test

After running the application click on File then preferences, navigate to External programs.
Try changing Application to push entries to option, restart the application try resetting preferences.

Mandatory checks

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • [/] I added JUnit tests for changes (if applicable)
  • [/] I added screenshots in the PR description (if change is visible to the user)
  • I described the change in CHANGELOG.md in a way that is understandable for the average user (if change is visible to the user)
  • I checked the user documentation: Is the information available and up to date? If not, I created an issue at https://github.com/JabRef/user-documentation/issues or, even better, I submitted a pull request updating file(s) in https://github.com/JabRef/user-documentation/tree/main/en.

PR Type

Enhancement, Bug fix


Description

  • Moved PushToApplicationPreferences default configuration from JabRefCliPreferences to PushToApplicationPreferences class

  • Added private constructor and getDefault() method for proper default initialization

  • Implemented setAll() method to enable resetting preferences to defaults

  • Updated getEmptyIsDefault() method signature to accept default value parameter

  • Added preference reset logic in clear() and importPreferences() methods


Diagram Walkthrough

flowchart LR
  A["JabRefCliPreferences"] -->|removed defaults| B["PushToApplicationPreferences"]
  B -->|added private constructor| C["getDefault()"]
  B -->|added method| D["setAll()"]
  E["clear() method"] -->|calls| D
  F["importPreferences() method"] -->|calls| D
Loading

File Walkthrough

Relevant files
Enhancement
JabRefCliPreferences.java
Refactored PushToApplicationPreferences initialization and reset

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java

  • Removed PushToApplicationPreferences default configuration block from
    constructor
  • Added getPushToApplicationPreferencesFromBackingStore() method to load
    preferences from backing store with defaults
  • Modified getPushToApplicationPreferences() to use new method and
    PushToApplicationPreferences.getDefault()
  • Updated getEmptyIsDefault() signature to accept defaultValue parameter
  • Added setAll() calls in clear() and importPreferences() methods for
    preference reset
+34/-46 
PushToApplicationPreferences.java
Added default initialization and reset functionality         

jablib/src/main/java/org/jabref/logic/push/PushToApplicationPreferences.java

  • Added private constructor with default configuration initialization
  • Added getDefault() static method to create default preferences
    instance
  • Added setAll() method to reset all preference properties to another
    instance's values
  • Added imports for File, HashMap, and OS classes
+44/-0   
Formatting
PushApplications.java
Minor formatting adjustments                                                         

jablib/src/main/java/org/jabref/logic/push/PushApplications.java

  • Added blank lines for formatting consistency
+4/-0     

@github-actions
Copy link
Contributor

Hey @Hamza-Azeem! 👋

Thank you for contributing to JabRef!

We have automated checks in place, based on which you will soon get feedback if any of them are failing.

After all automated checks pass, a maintainer will also review your contribution. Once that happens, you can go through their comments in the "Files changed" tab and act on them, or reply to the conversation if you have further inputs.

Please re-check our AI Usage Policy to ensure that your pull request is in line with it. It also contains links to our contribution guide in case of any other doubts related to our contribution workflow.

@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🟡
🎫 #14410
🔴 Enable resetting of PreviewPreferences (following the steps described in issue #14400).
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Unclear inconsistent naming: Newly added preference key values and display names are inconsistently capitalized/typoed
(e.g., VScode, TeXstudioPath) and key is not immutable, reducing readability and intent
clarity.

Referred Code
EMACS("emacs", "Emacs", "emacsPath"),
LYX("lyx", "LyX/Kile", "lyxpipe"),
TEXMAKER("texmaker", "Texmaker", "texmakerPath"),
TEXSTUDIO("texstudio", "TeXstudio", "TeXstudioPath"),
TEXWORKS("texworks", "TeXworks", "TeXworksPath"),
VIM("vim", "Vim", "vim"),
WIN_EDT("winedt", "WinEdt", "winEdtPath"),
SUBLIME_TEXT("sublime", "Sublime Text", "sublimeTextPath"),
TEXSHOP("texshop", "TeXShop"),
VSCODE("vscode", "VScode", "VScodePath");

private final String id;
private final String displayName;
private String key;

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Silent default fallback: The new null-handling in getBooleanDefault logs and returns false for missing defaults,
which can silently mask configuration errors and change behavior without a clear failure
signal.

Referred Code
private boolean getBooleanDefault(String key) {

    if (defaults.get(key) == null) {
        // Couldn't run the application without this condition.
        LOGGER.info("************************* THIS KEY IS NULL {}", key);
        return false;
    }
    return (Boolean) defaults.get(key);
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured noisy logging: A new LOGGER.info message uses asterisks and logs for a potentially normal missing-default
condition, producing unstructured/noisy logs and potentially leaking internal preference
key names.

Referred Code
if (defaults.get(key) == null) {
    // Couldn't run the application without this condition.
    LOGGER.info("************************* THIS KEY IS NULL {}", key);
    return false;

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Direct Preferences access: The new getEmptyIsDefault directly reads from Preferences.userRoot() using a hard-coded
node and key strings without validation, which may bypass existing preference-layer
constraints and needs verification for correctness and security expectations.

Referred Code
private String getEmptyIsDefault(String key, String defaultValue) {
    final Preferences PREFS_NODE = Preferences.userRoot().node("/org/jabref");
    String result = PREFS_NODE.get(key, defaultValue);
    if ("".equals(result)) {
        return defaultValue;
    }
    return result;
}

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Load stored citation command

In getPushToApplicationPreferencesFromBackingStore, load the stored citation
command from preferences by wrapping the get(PUSH_CITE_COMMAND, ...) call with
CitationCommandString.from(...).

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java [792-799]

 new PushToApplicationPreferences(
         get(PUSH_TO_APPLICATION, defaults.getActiveApplicationName()),
         defaults.getCommandPaths(),
         get(PUSH_EMACS_ADDITIONAL_PARAMETERS, defaults.getEmacsArguments()),
         get(PUSH_VIM_SERVER, defaults.getVimServer()),
-        defaults.getCiteCommand(),
+        CitationCommandString.from(get(PUSH_CITE_COMMAND, defaults.getCiteCommand().toString())),
         defaults.getDefaultCiteCommand()
 )

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion fixes a bug where the user's configured citation command was ignored, and the default value was always used instead. The proposed change correctly loads the stored value from preferences, restoring expected behavior.

Medium
Remove direct preference access from model

Remove the getEmptyIsDefault method from PushToApplicationPreferences and its
usage in the constructor to decouple the model from the preferences persistence
layer. The constructor should only set application-level defaults directly.

jablib/src/main/java/org/jabref/logic/push/PushToApplicationPreferences.java [28-60]

 private PushToApplicationPreferences(){
     this.activeApplicationName = new SimpleStringProperty(PushApplications.TEXSTUDIO.getDisplayName());
     Map<String, String> commands = new HashMap<>();
-    commands.put(PushApplications.TEXMAKER.getDisplayName(), getEmptyIsDefault(PushApplications.TEXMAKER.getKey(), OS.detectProgramPath("texmaker", "Texmaker")));
-    commands.put(PushApplications.WIN_EDT.getDisplayName(),  getEmptyIsDefault(PushApplications.WIN_EDT.getKey(), OS.detectProgramPath("WinEdt", "WinEdt Team\\WinEdt")));
-    commands.put(PushApplications.TEXSTUDIO.getDisplayName(),  getEmptyIsDefault(PushApplications.TEXSTUDIO.getKey(), OS.detectProgramPath("texstudio", "TeXstudio")));
-    commands.put(PushApplications.TEXWORKS.getDisplayName(), getEmptyIsDefault(PushApplications.TEXWORKS.getKey(), OS.detectProgramPath("texworks", "TeXworks")));
-    commands.put(PushApplications.SUBLIME_TEXT.getDisplayName(), getEmptyIsDefault(PushApplications.SUBLIME_TEXT.getKey(), OS.detectProgramPath("subl", "Sublime")));
-    commands.put(PushApplications.LYX.getDisplayName(), getEmptyIsDefault(PushApplications.LYX.getKey(), System.getProperty("user.home") + File.separator + ".lyx/lyxpipe"));
-    commands.put(PushApplications.VSCODE.getDisplayName(), getEmptyIsDefault(PushApplications.VSCODE.getKey(), OS.detectProgramPath("Code", "Microsoft VS Code")));
-    commands.put(PushApplications.VIM.getDisplayName(), getEmptyIsDefault(PushApplications.VIM.getKey(), "vim"));
+    commands.put(PushApplications.TEXMAKER.getDisplayName(), OS.detectProgramPath("texmaker", "Texmaker"));
+    commands.put(PushApplications.WIN_EDT.getDisplayName(),  OS.detectProgramPath("WinEdt", "WinEdt Team\\WinEdt"));
+    commands.put(PushApplications.TEXSTUDIO.getDisplayName(),  OS.detectProgramPath("texstudio", "TeXstudio"));
+    commands.put(PushApplications.TEXWORKS.getDisplayName(), OS.detectProgramPath("texworks", "TeXworks"));
+    commands.put(PushApplications.SUBLIME_TEXT.getDisplayName(), OS.detectProgramPath("subl", "Sublime"));
+    commands.put(PushApplications.LYX.getDisplayName(), System.getProperty("user.home") + File.separator + ".lyx/lyxpipe");
+    commands.put(PushApplications.VSCODE.getDisplayName(), OS.detectProgramPath("Code", "Microsoft VS Code"));
+    commands.put(PushApplications.VIM.getDisplayName(), "vim");
 
     if(OS.WINDOWS){
         commands.put(PushApplications.EMACS.getDisplayName(), "emacsclient.exe");
     }else if(OS.OS_X || OS.LINUX){
         commands.put(PushApplications.EMACS.getDisplayName(), "emacsclient");
     }
     this.commandPaths = new SimpleMapProperty<>(FXCollections.observableMap(commands));
 
     this.emacsArguments = new SimpleStringProperty("-n -e");
     this.vimServer = new SimpleStringProperty("vim");
     this.citeCommand = new SimpleObjectProperty<>(CitationCommandString.from("\\cite{key1,key2}"));
     this.defaultCiteCommand = new SimpleObjectProperty<>(CitationCommandString.from("\\cite{key1,key2}"));
 }
 
-private String getEmptyIsDefault(String key, String defaultValue) {
-    final Preferences PREFS_NODE = Preferences.userRoot().node("/org/jabref");
-    String result = PREFS_NODE.get(key, defaultValue);
-    if ("".equals(result)) {
-        return defaultValue;
-    }
-    return result;
-}
-
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a design flaw where the model class PushToApplicationPreferences directly accesses the persistence layer, violating separation of concerns. Applying this change improves the design and maintainability of the code.

Medium
Add missing preference key

Add a preference key to the TEXSHOP enum constant in PushApplications.java to
prevent a NullPointerException when its key is accessed.

jablib/src/main/java/org/jabref/logic/push/PushApplications.java [15]

-TEXSHOP("texshop", "TexShop"),
+TEXSHOP("texshop", "TexShop", "texshopPath"),
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the TEXSHOP enum constant is missing a preference key, which would lead to a NullPointerException if its path were accessed. Adding the key prevents this potential runtime crash.

Medium
General
Replace debug log with production-ready warning

In getBooleanDefault, replace the temporary INFO level debug log message with a
production-appropriate WARN level log and a more formal message.

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java [921-929]

 private boolean getBooleanDefault(String key) {
-
-     if (defaults.get(key) == null) {
-         // Couldn't run the application without this condition.
-         LOGGER.info("************************* THIS KEY IS NULL {}", key);
-         return false;
-     }
-    return (Boolean) defaults.get(key);
+    Object defaultValue = defaults.get(key);
+    if (defaultValue == null) {
+        LOGGER.warn("No default value found for preference key '{}'. Returning false.", key);
+        return false;
+    }
+    return (Boolean) defaultValue;
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out a temporary debug log message (************************* THIS KEY IS NULL {}) that should not be in production code. Replacing it with a proper warning improves code quality and logging standards.

Low
  • More

@github-actions github-actions bot added status: changes-required Pull requests that are not yet complete good first issue An issue intended for project-newcomers. Varies in difficulty. labels Dec 22, 2025
Comment on lines 923 to 927
if (defaults.get(key) == null) {
// Couldn't run the application without this condition.
LOGGER.info("************************* THIS KEY IS NULL {}", key);
return false;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, Calixtus

Sorry if I made a mistake here, please can you guide me into how can I fix this?
I followed the steps written in https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/
and when I got to the step where I should run the application from gradle menu the compilation fails and I get a null pointer exception.

I will also work on the instructions given by ai to improve the code, but I would like your opinion on how to handle: getBooleanDefault method
I could have made a map linking keys in JabRefCliPreferences to the displayed names without modifying of PushApplications class or is there a better approach ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont touch getBooleanDefault method. Goal is to get rid of the defaults map and to put all the default values into the high level prefs objects.

Comment on lines 53 to 60
private String getEmptyIsDefault(String key, String defaultValue) {
final Preferences PREFS_NODE = Preferences.userRoot().node("/org/jabref");
String result = PREFS_NODE.get(key, defaultValue);
if ("".equals(result)) {
return defaultValue;
}
return result;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope! Low level implementation of BackingStore access in highlevel preferences object.

TEXWORKS("texworks", "TeXworks", "TeXworksPath"),
VIM("vim", "Vim", "vim"),
WIN_EDT("winedt", "WinEdt", "winEdtPath"),
SUBLIME_TEXT("sublime", "Sublime Text", "sublimeTextPath"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mixes lowest level (name in BackingStore) with high level implementation.
This is totally wrong.

Image

Comment on lines 791 to 820
private PushToApplicationPreferences getPushToApplicationPreferencesFromBackingStore(PushToApplicationPreferences defaults) {
return new PushToApplicationPreferences(
get(PUSH_TO_APPLICATION, defaults.getActiveApplicationName()),
defaults.getCommandPaths(),
get(PUSH_EMACS_ADDITIONAL_PARAMETERS, defaults.getEmacsArguments()),
get(PUSH_VIM_SERVER, defaults.getVimServer()),
defaults.getCiteCommand(),
defaults.getDefaultCiteCommand()
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are the others not read from the prefs node here?

Comment on lines 31 to 38
commands.put(PushApplications.TEXMAKER.getDisplayName(), getEmptyIsDefault(PushApplications.TEXMAKER.getKey(), OS.detectProgramPath("texmaker", "Texmaker")));
commands.put(PushApplications.WIN_EDT.getDisplayName(), getEmptyIsDefault(PushApplications.WIN_EDT.getKey(), OS.detectProgramPath("WinEdt", "WinEdt Team\\WinEdt")));
commands.put(PushApplications.TEXSTUDIO.getDisplayName(), getEmptyIsDefault(PushApplications.TEXSTUDIO.getKey(), OS.detectProgramPath("texstudio", "TeXstudio")));
commands.put(PushApplications.TEXWORKS.getDisplayName(), getEmptyIsDefault(PushApplications.TEXWORKS.getKey(), OS.detectProgramPath("texworks", "TeXworks")));
commands.put(PushApplications.SUBLIME_TEXT.getDisplayName(), getEmptyIsDefault(PushApplications.SUBLIME_TEXT.getKey(), OS.detectProgramPath("subl", "Sublime")));
commands.put(PushApplications.LYX.getDisplayName(), getEmptyIsDefault(PushApplications.LYX.getKey(), System.getProperty("user.home") + File.separator + ".lyx/lyxpipe"));
commands.put(PushApplications.VSCODE.getDisplayName(), getEmptyIsDefault(PushApplications.VSCODE.getKey(), OS.detectProgramPath("Code", "Microsoft VS Code")));
commands.put(PushApplications.VIM.getDisplayName(), getEmptyIsDefault(PushApplications.VIM.getKey(), "vim"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be default values, not read from BackingStore. They are always static.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted. I will work on my mistakes

@Hamza-Azeem Hamza-Azeem marked this pull request as draft December 22, 2025 23:27
@Hamza-Azeem Hamza-Azeem force-pushed the enable-resetting-of-push-to-application-notification branch from 252b5fd to 2d0f027 Compare December 23, 2025 22:14
@Hamza-Azeem Hamza-Azeem marked this pull request as ready for review December 23, 2025 22:21
@qodo-free-for-open-source-projects
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Unvalidated path construction

Description: The code constructs a file path using System.getProperty("user.home") without validating
or sanitizing the user home directory value, which could be manipulated to point to
arbitrary locations if the system property is compromised or set maliciously.
PushToApplicationPreferences.java [35-35]

Referred Code
commands.put("LyX/Kile", System.getProperty("user.home") + File.separator + ".lyx/lyxpipe");
commands.put("VScode", OS.detectProgramPath("Code", "Microsoft VS Code"));
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Generic variable names: Variables lookup, entry, oldKey, and defaultValue in the new method use generic names that
don't clearly express their specific purpose in the context of mapping push
application preferences.

Referred Code
private PushToApplicationPreferences getPushToApplicationPreferencesFromBackingStore(PushToApplicationPreferences defaults) {
    Map<String, String> commandPaths = new HashMap<>(defaults.getCommandPaths());
    Map<String, String> lookup = new HashMap<>();
    lookup.put(PushApplications.EMACS.getDisplayName(), PUSH_EMACS_PATH);
    lookup.put(PushApplications.LYX.getDisplayName(), PUSH_LYXPIPE);
    lookup.put(PushApplications.TEXMAKER.getDisplayName(), PUSH_TEXMAKER_PATH);
    lookup.put(PushApplications.TEXSTUDIO.getDisplayName(), PUSH_TEXSTUDIO_PATH);
    lookup.put(PushApplications.TEXWORKS.getDisplayName(), PUSH_TEXWORKS_PATH);
    lookup.put(PushApplications.VIM.getDisplayName(), PUSH_VIM);
    lookup.put(PushApplications.WIN_EDT.getDisplayName(), PUSH_WINEDT_PATH);
    lookup.put(PushApplications.SUBLIME_TEXT.getDisplayName(), PUSH_SUBLIME_TEXT_PATH);
    lookup.put(PushApplications.VSCODE.getDisplayName(), PUSH_VSCODE_PATH);
    for(Map.Entry<String, String> entry : commandPaths.entrySet()) {
        String oldKey = lookup.get(entry.getKey());
        String defaultValue =  entry.getValue();
        entry.setValue(getEmptyIsDefault(oldKey, defaultValue));
    }

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing null validation: The setAll() method does not validate that the input preferences parameter is non-null
before accessing its properties, which could result in a NullPointerException.

Referred Code
public void setAll(PushToApplicationPreferences preferences){
    this.activeApplicationName.set(preferences.activeApplicationName.get());
    this.commandPaths.set(preferences.commandPaths);
    this.vimServer.set(preferences.getVimServer());
    this.emacsArguments.set(preferences.getEmacsArguments());
    this.citeCommand.set(preferences.getCiteCommand());
    this.defaultCiteCommand.set(preferences.getDefaultCiteCommand());
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Missing input validation: The setAll() method accepts external input without validation, and the private constructor
uses System.getProperty("user.home") without validating the returned value
before constructing file paths.

Referred Code
private PushToApplicationPreferences(){
    this.activeApplicationName = new SimpleStringProperty("Texmaker");
    Map<String, String> commands = new HashMap<>();
    commands.put("Texmaker", OS.detectProgramPath("texmaker", "Texmaker"));
    commands.put("WinEdt",  OS.detectProgramPath("WinEdt", "WinEdt Team\\WinEdt"));
    commands.put("TeXstudio",  OS.detectProgramPath("texstudio", "TeXstudio"));
    commands.put("TeXworks", OS.detectProgramPath("texworks", "TeXworks"));
    commands.put("Sublime Text", OS.detectProgramPath("subl", "Sublime"));
    commands.put("LyX/Kile", System.getProperty("user.home") + File.separator + ".lyx/lyxpipe");
    commands.put("VScode", OS.detectProgramPath("Code", "Microsoft VS Code"));
    commands.put("Vim", "vim");

    if(OS.WINDOWS){
        commands.put("Emacs", "emacsclient.exe");
    }else if(OS.OS_X || OS.LINUX){
        commands.put("Emacs", "emacsclient");
    }
    this.commandPaths = new SimpleMapProperty<>(FXCollections.observableMap(commands));

    this.emacsArguments = new SimpleStringProperty("-n -e");
    this.vimServer = new SimpleStringProperty("vim");


 ... (clipped 17 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-free-for-open-source-projects
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Avoid shared state by copying map

In the setAll method, copy the contents of the commandPaths map instead of
assigning the reference to prevent shared mutable state between
PushToApplicationPreferences instances.

jablib/src/main/java/org/jabref/logic/push/PushToApplicationPreferences.java [57-64]

 public void setAll(PushToApplicationPreferences preferences){
     this.activeApplicationName.set(preferences.activeApplicationName.get());
-    this.commandPaths.set(preferences.commandPaths);
+    this.commandPaths.clear();
+    this.commandPaths.putAll(preferences.commandPaths);
     this.vimServer.set(preferences.getVimServer());
     this.emacsArguments.set(preferences.getEmacsArguments());
     this.citeCommand.set(preferences.getCiteCommand());
     this.defaultCiteCommand.set(preferences.getDefaultCiteCommand());
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a critical issue where commandPaths is shared by reference, which could lead to unintended side effects and state corruption between preference instances.

Medium
Prevent NullPointerException on missing key

Add a null check for the oldKey variable to prevent a potential
NullPointerException if an application's display name is not found in the lookup
map.

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java [806-810]

 for(Map.Entry<String, String> entry : commandPaths.entrySet()) {
     String oldKey = lookup.get(entry.getKey());
     String defaultValue =  entry.getValue();
+    if (oldKey == null) {
+        // No preference key mapping for this application, use default
+        entry.setValue(defaultValue);
+        continue;
+    }
     entry.setValue(getEmptyIsDefault(oldKey, defaultValue));
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential NullPointerException if the lookup map lacks a key present in commandPaths, improving code robustness against future changes.

Low
High-level
Refactor preference key mapping logic

Replace the hardcoded map that links application names to preference keys in
getPushToApplicationPreferencesFromBackingStore. Instead, derive the preference
key programmatically from the PushApplications enum to reduce maintenance
overhead.

Examples:

jablib/src/main/java/org/jabref/logic/preferences/JabRefCliPreferences.java [794-810]
    private PushToApplicationPreferences getPushToApplicationPreferencesFromBackingStore(PushToApplicationPreferences defaults) {
        Map<String, String> commandPaths = new HashMap<>(defaults.getCommandPaths());
        Map<String, String> lookup = new HashMap<>();
        lookup.put(PushApplications.EMACS.getDisplayName(), PUSH_EMACS_PATH);
        lookup.put(PushApplications.LYX.getDisplayName(), PUSH_LYXPIPE);
        lookup.put(PushApplications.TEXMAKER.getDisplayName(), PUSH_TEXMAKER_PATH);
        lookup.put(PushApplications.TEXSTUDIO.getDisplayName(), PUSH_TEXSTUDIO_PATH);
        lookup.put(PushApplications.TEXWORKS.getDisplayName(), PUSH_TEXWORKS_PATH);
        lookup.put(PushApplications.VIM.getDisplayName(), PUSH_VIM);
        lookup.put(PushApplications.WIN_EDT.getDisplayName(), PUSH_WINEDT_PATH);

 ... (clipped 7 lines)

Solution Walkthrough:

Before:

private PushToApplicationPreferences getPushToApplicationPreferencesFromBackingStore(...) {
    Map<String, String> commandPaths = ...;
    Map<String, String> lookup = new HashMap<>();
    lookup.put(PushApplications.EMACS.getDisplayName(), PUSH_EMACS_PATH);
    lookup.put(PushApplications.LYX.getDisplayName(), PUSH_LYXPIPE);
    // ... more hardcoded entries
    lookup.put(PushApplications.VSCODE.getDisplayName(), PUSH_VSCODE_PATH);

    for(Map.Entry<String, String> entry : commandPaths.entrySet()) {
        String oldKey = lookup.get(entry.getKey());
        String defaultValue =  entry.getValue();
        entry.setValue(getEmptyIsDefault(oldKey, defaultValue));
    }
    return new PushToApplicationPreferences(...);
}

After:

// In PushApplications enum
public enum PushApplications {
    EMACS("emacs", "Emacs", "pushToEmacs"),
    // ... other applications with their preference key
    ;
    // ...
    public String getPreferenceKey() { return preferenceKey; }
}

// In JabRefCliPreferences
private PushToApplicationPreferences getPushToApplicationPreferencesFromBackingStore(...) {
    Map<String, String> commandPaths = ...;
    for(Map.Entry<String, String> entry : commandPaths.entrySet()) {
        String prefKey = PushApplications.getApplicationByDisplayName(entry.getKey())
                                         .map(PushApplications::getPreferenceKey)
                                         .orElse(null);
        if (prefKey != null) {
            entry.setValue(getEmptyIsDefault(prefKey, entry.getValue()));
        }
    }
    return new PushToApplicationPreferences(...);
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a maintenance issue with the hardcoded lookup map, which could lead to inconsistencies; refactoring this would improve code robustness and maintainability.

Medium
  • More

@Hamza-Azeem
Copy link
Author

Hello @calixtus,

I have removed getEmptyIsDefault from PushToApplicationPreference class, added the static keys instead of getting them from PushApplications Enum and got PushApplications into it's initial state without the modifications I did in the last commit.
Please tell me if there is anything else to do.

@koppor
Copy link
Member

koppor commented Dec 23, 2025

Hello @calixtus,

I have removed getEmptyIsDefault from PushToApplicationPreference class, added the static keys instead of getting them from PushApplications Enum and got PushApplications into it's initial state without the modifications I did in the last commit. Please tell me if there is anything else to do.

Maybe check the CI output?

This is what you can address without someone telling you the exact steps

image image

This is not respecting our time.

@koppor
Copy link
Member

koppor commented Dec 23, 2025

I think, this project could be too hard for you @Hamza-Azeem

@Hamza-Azeem
Copy link
Author

Sorry @koppor I definitely didn't mean to disrespect you in anyway.
It's my first time contributing to an open source project and I am sorry that I left a bad impression.
Thank you @calixtus for your time and again sorry for the mistakes.

@koppor
Copy link
Member

koppor commented Dec 23, 2025

It's my first time contributing to an open source project

And maybe your first time using GitHub, seeing a CI pipeline running, seeing automated tests, interacting with a code hosting platform, using Google, using AI to assist you, etc. I can understand, that the whole development tooling used in practice might be overwhelming. I can also understand that English texts are hard to understand.

I also can understand, that one cannot see the text "3 failing checks" and see not understanding it. Therefore, we created a bot posting comments.

Not sure why you did not see #14691 (comment).

@Hamza-Azeem Hamza-Azeem marked this pull request as draft December 24, 2025 07:13
Comment on lines 795 to 810
Map<String, String> commandPaths = new HashMap<>(defaults.getCommandPaths());
Map<String, String> lookup = new HashMap<>();
lookup.put(PushApplications.EMACS.getDisplayName(), PUSH_EMACS_PATH);
lookup.put(PushApplications.LYX.getDisplayName(), PUSH_LYXPIPE);
lookup.put(PushApplications.TEXMAKER.getDisplayName(), PUSH_TEXMAKER_PATH);
lookup.put(PushApplications.TEXSTUDIO.getDisplayName(), PUSH_TEXSTUDIO_PATH);
lookup.put(PushApplications.TEXWORKS.getDisplayName(), PUSH_TEXWORKS_PATH);
lookup.put(PushApplications.VIM.getDisplayName(), PUSH_VIM);
lookup.put(PushApplications.WIN_EDT.getDisplayName(), PUSH_WINEDT_PATH);
lookup.put(PushApplications.SUBLIME_TEXT.getDisplayName(), PUSH_SUBLIME_TEXT_PATH);
lookup.put(PushApplications.VSCODE.getDisplayName(), PUSH_VSCODE_PATH);
for(Map.Entry<String, String> entry : commandPaths.entrySet()) {
String oldKey = lookup.get(entry.getKey());
String defaultValue = entry.getValue();
entry.setValue(getEmptyIsDefault(oldKey, defaultValue));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks incredibly complicated for a very simple task.


public String getEmptyIsDefault(String key) {
String defaultValue = (String) defaults.get(key);
public String getEmptyIsDefault(String key, String defaultValue) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be modified.

Copy link
Member

@calixtus calixtus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkstyle

private final String id;
private final String displayName;


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkstyle

this.displayName = displayName;
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkstyle

Comment on lines 53 to 54


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checkstyle



private PushToApplicationPreferences(){
this.activeApplicationName = new SimpleStringProperty("Texmaker");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

commands.put("TeXstudio", OS.detectProgramPath("texstudio", "TeXstudio"));
commands.put("TeXworks", OS.detectProgramPath("texworks", "TeXworks"));
commands.put("Sublime Text", OS.detectProgramPath("subl", "Sublime"));
commands.put("LyX/Kile", System.getProperty("user.home") + File.separator + ".lyx/lyxpipe");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
commands.put("LyX/Kile", System.getProperty("user.home") + File.separator + ".lyx/lyxpipe");
commands.put("LyX/Kile", USER_HOME + File.separator + ".lyx/lyxpipe");

Comment on lines 39 to 43
if(OS.WINDOWS){
commands.put("Emacs", "emacsclient.exe");
}else if(OS.OS_X || OS.LINUX){
commands.put("Emacs", "emacsclient");
}
Copy link
Member

@calixtus calixtus Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(OS.WINDOWS){
commands.put("Emacs", "emacsclient.exe");
}else if(OS.OS_X || OS.LINUX){
commands.put("Emacs", "emacsclient");
}
commands.put("Emacs", OS.WINDOWS ? "emacsclient.exe" : "emacsclient");


private PushToApplicationPreferences(){
this.activeApplicationName = new SimpleStringProperty("Texmaker");
Map<String, String> commands = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not FXCollections.observableMap here already?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI generated code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI generated code

@github-actions
Copy link
Contributor

Your pull request conflicts with the target branch.

Please merge with your code. For a step-by-step guide to resolve merge conflicts, see https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/resolving-a-merge-conflict-using-the-command-line.

@jabref-machine
Copy link
Collaborator

Your code currently does not meet JabRef's code guidelines. We use Checkstyle to identify issues. You can see which checks are failing by locating the box "Some checks were not successful" on the pull request page. To see the test output, locate "Source Code Tests / Checkstyle (pull_request)" and click on it.

In case of issues with the import order, double check that you activated Auto Import. You can trigger fixing imports by pressing Ctrl+Alt+O to trigger Optimize Imports.

Please carefully follow the setup guide for the codestyle. Afterwards, please run checkstyle locally and fix the issues, commit, and push.

@github-actions
Copy link
Contributor

The requested changes were not addressed for 14 days. Please follow-up in the next 7 days or your PR will be automatically closed. You can check the contributing guidelines for hints on the pull request process.

@github-actions github-actions bot added the status: stale Issues marked by a bot as "stale". All issues need to be investigated manually. label Jan 15, 2026
@calixtus
Copy link
Member

Closing after 3 weeks unfinished draft

@calixtus calixtus closed this Jan 15, 2026
@github-actions
Copy link
Contributor

This pull requests was closed without merging. You have been unassigned from the respective issue #14410. In case you closed the PR for yourself, you can re-open it. Please also check After submission of a pull request in CONTRIBUTING.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

first contrib good first issue An issue intended for project-newcomers. Varies in difficulty. Review effort 3/5 status: changes-required Pull requests that are not yet complete status: stale Issues marked by a bot as "stale". All issues need to be investigated manually.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable resetting of PreviewPreferences

4 participants