This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit e473ef299fc0af0e4915a0cfd6f300ca7a18b601
Author: Andrus Adamchik <[email protected]>
AuthorDate: Mon May 25 10:54:15 2026 -0400

    CAY-2948 Modeler: use native FileDialog on Mac
    
    for performance reasons turning individual dialogs to reusable singletons
---
 .../modeler/platform/mac/MacFileChooser.java       |  8 ++--
 .../toolkit/filechooser/SwingDirChooserDialog.java | 52 +++++++++++++++------
 .../toolkit/filechooser/SwingFileChooser.java      | 18 +++-----
 .../toolkit/filechooser/SwingFileOpenerDialog.java | 54 +++++++++++++++-------
 .../toolkit/filechooser/SwingFileSaverDialog.java  | 52 +++++++++++++++------
 5 files changed, 121 insertions(+), 63 deletions(-)

diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooser.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooser.java
index c81e4533f..8924f9974 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooser.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooser.java
@@ -57,15 +57,13 @@ public class MacFileChooser implements CMFileChooser {
     @Override
     public File openDir(File initialDir) {
         // must use custom JFileChooser, as macOS native dialog doesn't allow 
to block file name selection
-        return new SwingDirChooserDialog(parent, title, initialDir).open();
+        return SwingDirChooserDialog.getInstance().open(parent, title, 
initialDir, null);
     }
 
     @Override
     public File openDir(FileChooserPrefs prefs) {
         // must use custom JFileChooser, as macOS native dialog doesn't allow 
to block file name selection
-        SwingDirChooserDialog dialog = new SwingDirChooserDialog(parent, 
title, prefs.getDir());
-        prefs.bind(dialog);
-        return dialog.open();
+        return SwingDirChooserDialog.getInstance().open(parent, title, 
prefs.getDir(), prefs);
     }
 
     @Override
@@ -76,7 +74,7 @@ public class MacFileChooser implements CMFileChooser {
     @Override
     public File saveDir(File initialDir) {
         // must use custom JFileChooser, as macOS native dialog doesn't allow 
to block file name selection
-        return new SwingDirChooserDialog(parent, title, initialDir).open();
+        return SwingDirChooserDialog.getInstance().open(parent, title, 
initialDir, null);
     }
 
     private File showOpen(FilenameFilter fnFilter, File startDir, 
Consumer<FileDialog> init) {
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingDirChooserDialog.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingDirChooserDialog.java
index 8638c2a97..0c91a1870 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingDirChooserDialog.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingDirChooserDialog.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.modeler.toolkit.filechooser;
 
+import org.apache.cayenne.modeler.pref.adapters.FileChooserPrefs;
+
 import javax.swing.JButton;
 import javax.swing.JDialog;
 import javax.swing.JFileChooser;
@@ -27,6 +29,7 @@ import javax.swing.JPanel;
 import java.awt.BorderLayout;
 import java.awt.Component;
 import java.awt.FlowLayout;
+import java.awt.event.ActionListener;
 import java.io.File;
 
 /**
@@ -35,32 +38,50 @@ import java.io.File;
  */
 public final class SwingDirChooserDialog extends JFileChooser {
 
-    private final Component parent;
+    private static SwingDirChooserDialog instance;
+
+    /**
+     * Returns the lazily-initialized cached singleton. Constructing a {@link 
JFileChooser} is
+     * expensive — on Windows it triggers a full L&amp;F UI delegate install 
and a Shell32 scan
+     * that can take hundreds of milliseconds, making first-time dialog open 
feel sluggish.
+     * Since Swing is single-threaded, one instance is safely reused across 
all callers; transient
+     * state (listeners, current directory, selection) is reset on each {@link 
#open}.
+     */
+    public static SwingDirChooserDialog getInstance() {
+        if (instance == null) {
+            instance = new SwingDirChooserDialog();
+        }
+        return instance;
+    }
+
     private final JButton selectButton = new JButton("Select");
     private final JButton cancelButton = new JButton("Cancel");
     private final JButton newFolderButton = new JButton("New Folder");
 
-    public SwingDirChooserDialog(Component parent, String title, File 
startDir) {
-        super(startDir);
-        this.parent = parent;
-
+    private SwingDirChooserDialog() {
         setFileSelectionMode(DIRECTORIES_ONLY);
 
         // Using setControlButtonsAreShown(false) because on macOS Aqua L&F 
the built-in approve button is
         // continuously re-disabled when nothing is selected (e.g. after 
navigating into a directory).
         setControlButtonsAreShown(false);
 
-        if (title != null) {
-            setDialogTitle(title);
-        }
-
-        initBindings();
+        selectButton.addActionListener(e -> approveSelection());
+        cancelButton.addActionListener(e -> cancelSelection());
+        newFolderButton.addActionListener(e -> promptNewFolder());
     }
 
     /**
      * Shows the dialog modally and returns the selected directory, or {@code 
null} if the user cancelled.
      */
-    public File open() {
+    public File open(Component parent, String title, File startDir, 
FileChooserPrefs prefs) {
+        reset();
+        if (startDir != null) {
+            setCurrentDirectory(startDir);
+        }
+        setDialogTitle(title);
+        if (prefs != null) {
+            prefs.bind(this);
+        }
         if (showOpenDialog(parent) != APPROVE_OPTION) {
             return null;
         }
@@ -90,10 +111,11 @@ public final class SwingDirChooserDialog extends 
JFileChooser {
         return dialog;
     }
 
-    private void initBindings() {
-        selectButton.addActionListener(e -> approveSelection());
-        cancelButton.addActionListener(e -> cancelSelection());
-        newFolderButton.addActionListener(e -> promptNewFolder());
+    private void reset() {
+        for (ActionListener l : getActionListeners()) {
+            removeActionListener(l);
+        }
+        setSelectedFile(null);
     }
 
     private void promptNewFolder() {
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileChooser.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileChooser.java
index b4d4f46eb..3fa85c92c 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileChooser.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileChooser.java
@@ -37,37 +37,31 @@ public class SwingFileChooser implements CMFileChooser {
 
     @Override
     public File openFile(File initialDir, FileFilter filter) {
-        return new SwingFileOpenerDialog(parent, title, initialDir, 
filter).open();
+        return SwingFileOpenerDialog.getInstance().open(parent, title, 
initialDir, filter, null);
     }
 
     @Override
     public File openFile(FileChooserPrefs prefs, FileFilter filter) {
-        SwingFileOpenerDialog dialog = new SwingFileOpenerDialog(parent, 
title, prefs.getDir(), filter);
-        prefs.bind(dialog);
-        return dialog.open();
+        return SwingFileOpenerDialog.getInstance().open(parent, title, 
prefs.getDir(), filter, prefs);
     }
 
     @Override
     public File openDir(File initialDir) {
-        return new SwingDirChooserDialog(parent, title, initialDir).open();
+        return SwingDirChooserDialog.getInstance().open(parent, title, 
initialDir, null);
     }
 
     @Override
     public File openDir(FileChooserPrefs prefs) {
-        SwingDirChooserDialog dialog = new SwingDirChooserDialog(parent, 
title, prefs.getDir());
-        prefs.bind(dialog);
-        return dialog.open();
+        return SwingDirChooserDialog.getInstance().open(parent, title, 
prefs.getDir(), prefs);
     }
 
     @Override
     public File saveFile(FileChooserPrefs prefs, String defaultName) {
-        SwingFileSaverDialog dialog = new SwingFileSaverDialog(parent, title, 
prefs.getDir(), defaultName);
-        prefs.bind(dialog);
-        return dialog.open();
+        return SwingFileSaverDialog.getInstance().open(parent, title, 
prefs.getDir(), defaultName, prefs);
     }
 
     @Override
     public File saveDir(File initialDir) {
-        return new SwingDirChooserDialog(parent, title, initialDir).open();
+        return SwingDirChooserDialog.getInstance().open(parent, title, 
initialDir, null);
     }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileOpenerDialog.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileOpenerDialog.java
index 849b8b210..ebb570adc 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileOpenerDialog.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileOpenerDialog.java
@@ -19,9 +19,12 @@
 
 package org.apache.cayenne.modeler.toolkit.filechooser;
 
+import org.apache.cayenne.modeler.pref.adapters.FileChooserPrefs;
+
 import javax.swing.JFileChooser;
 import javax.swing.filechooser.FileFilter;
 import java.awt.Component;
+import java.awt.event.ActionListener;
 import java.io.File;
 
 /**
@@ -29,31 +32,50 @@ import java.io.File;
  */
 public final class SwingFileOpenerDialog extends JFileChooser {
 
-    private final Component parent;
-
-    public SwingFileOpenerDialog(
-            Component parent,
-            String title,
-            File startDir,
-            FileFilter filter) {
+    private static SwingFileOpenerDialog instance;
 
-        super(startDir);
+    /**
+     * Returns the lazily-initialized cached singleton. Constructing a {@link 
JFileChooser} is
+     * expensive — on Windows it triggers a full L&amp;F UI delegate install 
and a Shell32 scan
+     * that can take hundreds of milliseconds, making first-time dialog open 
feel sluggish.
+     * Since Swing is single-threaded, one instance is safely reused across 
all callers; transient
+     * state (listeners, filters, current directory, selection) is reset on 
each {@link #open}.
+     */
+    public static SwingFileOpenerDialog getInstance() {
+        if (instance == null) {
+            instance = new SwingFileOpenerDialog();
+        }
+        return instance;
+    }
 
-        this.parent = parent;
+    private SwingFileOpenerDialog() {
         setFileSelectionMode(FILES_ONLY);
-        if (title != null) {
-            setDialogTitle(title);
+    }
+
+    /**
+     * Shows the dialog modally and returns the selected file, or {@code null} 
if the user cancelled.
+     */
+    public File open(Component parent, String title, File startDir, FileFilter 
filter, FileChooserPrefs prefs) {
+        reset();
+        if (startDir != null) {
+            setCurrentDirectory(startDir);
         }
+        setDialogTitle(title);
         if (filter != null) {
             addChoosableFileFilter(filter);
             setFileFilter(filter);
         }
+        if (prefs != null) {
+            prefs.bind(this);
+        }
+        return showOpenDialog(parent) == APPROVE_OPTION ? getSelectedFile() : 
null;
     }
 
-    /**
-     * Shows the dialog modally and returns the selected file, or {@code null} 
if the user cancelled.
-     */
-    public File open() {
-        return showOpenDialog(parent) == APPROVE_OPTION ? getSelectedFile() : 
null;
+    private void reset() {
+        for (ActionListener l : getActionListeners()) {
+            removeActionListener(l);
+        }
+        resetChoosableFileFilters();
+        setSelectedFile(null);
     }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileSaverDialog.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileSaverDialog.java
index 661f4ef6d..980e3662f 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileSaverDialog.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/SwingFileSaverDialog.java
@@ -19,8 +19,11 @@
 
 package org.apache.cayenne.modeler.toolkit.filechooser;
 
+import org.apache.cayenne.modeler.pref.adapters.FileChooserPrefs;
+
 import javax.swing.JFileChooser;
 import java.awt.Component;
+import java.awt.event.ActionListener;
 import java.io.File;
 
 /**
@@ -28,29 +31,48 @@ import java.io.File;
  */
 public final class SwingFileSaverDialog extends JFileChooser {
 
-    private final Component parent;
+    private static SwingFileSaverDialog instance;
 
-    public SwingFileSaverDialog(
-            Component parent,
-            String title,
-            File startDir,
-            String defaultName) {
+    /**
+     * Returns the lazily-initialized cached singleton. Constructing a {@link 
JFileChooser} is
+     * expensive — on Windows it triggers a full L&amp;F UI delegate install 
and a Shell32 scan
+     * that can take hundreds of milliseconds, making first-time dialog open 
feel sluggish.
+     * Since Swing is single-threaded, one instance is safely reused across 
all callers; transient
+     * state (listeners, filters, current directory, selection) is reset on 
each {@link #open}.
+     */
+    public static SwingFileSaverDialog getInstance() {
+        if (instance == null) {
+            instance = new SwingFileSaverDialog();
+        }
+        return instance;
+    }
 
-        super(startDir);
+    private SwingFileSaverDialog() {
+    }
 
-        this.parent = parent;
-        if (title != null) {
-            setDialogTitle(title);
+    /**
+     * Shows the dialog modally and returns the selected file, or {@code null} 
if the user cancelled.
+     */
+    public File open(Component parent, String title, File startDir, String 
defaultName, FileChooserPrefs prefs) {
+        reset();
+        if (startDir != null) {
+            setCurrentDirectory(startDir);
         }
+        setDialogTitle(title);
         if (defaultName != null) {
             setSelectedFile(new File(defaultName));
         }
+        if (prefs != null) {
+            prefs.bind(this);
+        }
+        return showSaveDialog(parent) == APPROVE_OPTION ? getSelectedFile() : 
null;
     }
 
-    /**
-     * Shows the dialog modally and returns the selected file, or {@code null} 
if the user cancelled.
-     */
-    public File open() {
-        return showSaveDialog(parent) == APPROVE_OPTION ? getSelectedFile() : 
null;
+    private void reset() {
+        for (ActionListener l : getActionListeners()) {
+            removeActionListener(l);
+        }
+        resetChoosableFileFilters();
+        setSelectedFile(null);
     }
 }

Reply via email to