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


The following commit(s) were added to refs/heads/master by this push:
     new 4848fadf6 CAY-2948 Modeler: use native FileDialog on Mac
4848fadf6 is described below

commit 4848fadf62efc9ad35d5e356f7d2f368c863c059
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sat May 23 12:41:07 2026 -0400

    CAY-2948 Modeler: use native FileDialog on Mac
---
 RELEASE-NOTES.txt                                  |   1 +
 .../org/apache/cayenne/modeler/Application.java    |   7 ++
 .../cayenne/modeler/platform/UIInitializer.java    |  10 ++
 .../platform/mac/MacFileChooserFactory.java        | 121 +++++++++++++++++++++
 .../modeler/platform/mac/MacUIInitializer.java     |   7 +-
 .../modeler/pref/adapters/FileChooserPrefs.java    |  42 +++++--
 .../filechooser/FileChooserFactory.java}           |  29 +++--
 .../toolkit/filechooser/JFileChooserFactory.java   |  82 ++++++++++++++
 .../modeler/ui/action/ImportDataMapAction.java     |  20 +---
 .../modeler/ui/action/ImportEOModelAction.java     | 102 ++++-------------
 .../cayenne/modeler/ui/action/ProjectOpener.java   |  89 ++++-----------
 .../modeler/ui/dbgen/DBGeneratorOptionsDialog.java |  16 +--
 .../modeler/ui/dbmerge/MergerOptionsDialog.java    |  22 +---
 .../preferences/classpath/ClasspathPrefsPanel.java |  36 +++---
 .../editor/datamap/cgen/CgenConfigPanel.java       |  19 +---
 15 files changed, 352 insertions(+), 251 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index d1ae3bafb..03c7bba2c 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -45,6 +45,7 @@ CAY-2943 CayenneModeler MCP: open_project tool
 CAY-2945 CayenneModeler universal "main"
 CAY-2946 Claude Code "plugin" for agentic coding with Cayenne
 CAY-2947 Merge "cayenne-commitlog" into the core
+CAY-2948 Modeler: use native FileDialog on Mac
 
 Bug Fixes:
 
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Application.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Application.java
index a2ece0876..de844a0c0 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Application.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Application.java
@@ -31,6 +31,7 @@ import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.modeler.dbconnector.DBConnectors;
 import org.apache.cayenne.modeler.log.ModelerLogFactory;
 import org.apache.cayenne.modeler.platform.UIInitializer;
+import org.apache.cayenne.modeler.toolkit.filechooser.FileChooserFactory;
 import org.apache.cayenne.modeler.pref.adapters.ClasspathPrefs;
 import org.apache.cayenne.modeler.pref.adapters.DBConnectorPrefs;
 import org.apache.cayenne.modeler.pref.adapters.GeneralPrefs;
@@ -88,6 +89,7 @@ public class Application {
 
     private final Injector injector;
     private final UIInitializer platformInit;
+    private final FileChooserFactory fileChooserFactory;
     private final ModelerClassLoader classLoader;
     private final PrefsLocator prefsLocator;
     private final PrefsManager prefsManager;
@@ -102,6 +104,7 @@ public class Application {
     public Application(Injector injector, UIInitializer platformInit, CliArgs 
cli) {
         this.injector = injector;
         this.platformInit = platformInit;
+        this.fileChooserFactory = platformInit.fileChooserFactory();
         this.cli = cli;
 
         this.classLoader = new ModelerClassLoader();
@@ -143,6 +146,10 @@ public class Application {
         return platformInit;
     }
 
+    public FileChooserFactory getFileChooserFactory() {
+        return fileChooserFactory;
+    }
+
     public ConfigurationNodeParentGetter getConfigurationNodeParentGetter() {
         return injector.getInstance(ConfigurationNodeParentGetter.class);
     }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/UIInitializer.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/UIInitializer.java
index a544193e9..cc299d637 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/UIInitializer.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/UIInitializer.java
@@ -19,6 +19,8 @@
 package org.apache.cayenne.modeler.platform;
 
 import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.toolkit.filechooser.FileChooserFactory;
+import org.apache.cayenne.modeler.toolkit.filechooser.JFileChooserFactory;
 
 /**
  * A base callback for platform-specific Modeler initialization.
@@ -38,4 +40,12 @@ public interface UIInitializer {
      */
     default void afterFrameCreated(Application app) {
     }
+
+    /**
+     * Returns the platform-appropriate {@link FileChooserFactory}. Defaults 
to Swing's
+     * {@code JFileChooser}; overridden on macOS to use the native {@code 
FileDialog}.
+     */
+    default FileChooserFactory fileChooserFactory() {
+        return new JFileChooserFactory();
+    }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooserFactory.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooserFactory.java
new file mode 100644
index 000000000..5b636b82b
--- /dev/null
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacFileChooserFactory.java
@@ -0,0 +1,121 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.
+ ****************************************************************/
+
+package org.apache.cayenne.modeler.platform.mac;
+
+import org.apache.cayenne.modeler.toolkit.filechooser.FileChooserFactory;
+
+import javax.swing.SwingUtilities;
+import javax.swing.filechooser.FileFilter;
+import java.awt.Component;
+import java.awt.FileDialog;
+import java.awt.Frame;
+import java.awt.Window;
+import java.io.File;
+import java.io.FilenameFilter;
+
+/**
+ * {@link FileChooserFactory} implementation backed by the native OS {@link 
FileDialog}.
+ * On macOS this delegates to NSOpenPanel / NSSavePanel, providing Quick Look 
preview,
+ * Spotlight search, and the standard Finder sidebar.
+ *
+ * <p>Directory picking requires {@code 
apple.awt.fileDialogForDirectories=true} to be set
+ * as a global JVM property before Swing starts (set by {@code 
MacUIInitializer}).
+ */
+public class MacFileChooserFactory extends FileChooserFactory {
+
+    private static final String DIRS_DIALOG_PROPERTY = 
"apple.awt.fileDialogForDirectories";
+
+    @Override
+    public File openFile(Component parent, String title, File initialDir, 
FileFilter filter) {
+        Frame frame = toFrame(parent);
+        String titleStr = title != null ? title : "";
+        FilenameFilter fnFilter = filter != null
+                ? (dir, name) -> filter.accept(new File(dir, name))
+                : null;
+
+        File startDir = initialDir;
+        while (true) {
+            FileDialog fd = new FileDialog(frame, titleStr, FileDialog.LOAD);
+            if (startDir != null) {
+                fd.setDirectory(startDir.getAbsolutePath());
+            }
+            if (fnFilter != null) {
+                fd.setFilenameFilter(fnFilter);
+            }
+            fd.setVisible(true);
+
+            String file = fd.getFile();
+            if (file == null) {
+                return null; // canceled
+            }
+            File selected = new File(fd.getDirectory(), file);
+            if (selected.isFile()) {
+                return selected;
+            }
+            // User selected a directory (possible when 
apple.awt.fileDialogForDirectories=true);
+            // re-show starting inside it so they can navigate to a file.
+            startDir = selected;
+        }
+    }
+
+    @Override
+    public File saveFile(Component parent, String title, File initialDir, 
String defaultName) {
+        FileDialog fd = new FileDialog(toFrame(parent), title != null ? title 
: "", FileDialog.SAVE);
+        if (initialDir != null) {
+            fd.setDirectory(initialDir.getAbsolutePath());
+        }
+        if (defaultName != null) {
+            fd.setFile(defaultName);
+        }
+        fd.setVisible(true);
+        String file = fd.getFile();
+        return file != null ? new File(fd.getDirectory(), file) : null;
+    }
+
+    @Override
+    public File openDirectory(Component parent, String title, File initialDir) 
{
+        // Toggle the property only for the duration of this modal dialog.
+        // FileDialog is EDT-blocking, so no other dialog can run concurrently.
+        System.setProperty(DIRS_DIALOG_PROPERTY, "true");
+        try {
+            FileDialog fd = new FileDialog(toFrame(parent), title != null ? 
title : "", FileDialog.LOAD);
+            if (initialDir != null) {
+                fd.setDirectory(initialDir.getAbsolutePath());
+            }
+            fd.setVisible(true);
+            String file = fd.getFile();
+            // getFile() returns the selected directory name; getDirectory() 
is its parent.
+            return file != null ? new File(fd.getDirectory(), file) : null;
+        } finally {
+            System.clearProperty(DIRS_DIALOG_PROPERTY);
+        }
+    }
+
+    private static Frame toFrame(Component c) {
+        Window w = SwingUtilities.getWindowAncestor(c);
+        while (w != null) {
+            if (w instanceof Frame f) {
+                return f;
+            }
+            w = w.getOwner();
+        }
+        return null;
+    }
+}
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacUIInitializer.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacUIInitializer.java
index 09f272939..b48796f10 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacUIInitializer.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/mac/MacUIInitializer.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.modeler.platform.mac;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.service.action.GlobalActions;
 import org.apache.cayenne.modeler.platform.UIInitializer;
+import org.apache.cayenne.modeler.toolkit.filechooser.FileChooserFactory;
 import org.apache.cayenne.modeler.ui.action.AboutAction;
 import org.apache.cayenne.modeler.ui.action.ConfigurePreferencesAction;
 import org.apache.cayenne.modeler.ui.action.ExitAction;
@@ -40,7 +41,6 @@ public class MacUIInitializer implements UIInitializer {
         System.setProperty("apple.awt.application.name", "CayenneModeler");
 
         // override some default styles and colors, assuming that Aqua theme 
will be used
-
         Color lightGrey = new Color(0xEEEEEE);
         Color darkGrey = new Color(225, 225, 225);
         Border darkBorder = BorderFactory.createLineBorder(darkGrey);
@@ -91,6 +91,11 @@ public class MacUIInitializer implements UIInitializer {
         UIManager.put("MenuItem.selectionForeground", Color.BLACK);
     }
 
+    @Override
+    public FileChooserFactory fileChooserFactory() {
+        return new MacFileChooserFactory();
+    }
+
     @Override
     public void afterFrameCreated(Application app) {
 
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/adapters/FileChooserPrefs.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/adapters/FileChooserPrefs.java
index 119ea52d4..1a339f489 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/adapters/FileChooserPrefs.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/pref/adapters/FileChooserPrefs.java
@@ -22,6 +22,9 @@ package org.apache.cayenne.modeler.pref.adapters;
 import org.apache.cayenne.modeler.pref.PreferenceAdapter;
 
 import javax.swing.JFileChooser;
+import java.awt.FileDialog;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
 import java.io.File;
 import java.util.prefs.Preferences;
 
@@ -34,24 +37,47 @@ public final class FileChooserPrefs extends 
PreferenceAdapter {
     }
 
     public void bind(JFileChooser chooser) {
-        File startDir = resolveExistingDirectory(prefs.get(PATH_PROPERTY, 
null));
+        File startDir = loadDir();
         if (startDir != null) {
             chooser.setCurrentDirectory(startDir);
         }
-
         chooser.addActionListener(e -> {
             if (JFileChooser.APPROVE_SELECTION.equals(e.getActionCommand())) {
-                File selected = chooser.getSelectedFile();
-                if (selected != null) {
-                    prefs.put(PATH_PROPERTY,
-                            selected.isFile()
-                                    ? 
selected.getParentFile().getAbsolutePath()
-                                    : selected.getAbsolutePath());
+                saveDir(chooser.getSelectedFile());
+            }
+        });
+    }
+
+    public void bind(FileDialog dialog) {
+        File startDir = loadDir();
+        if (startDir != null) {
+            dialog.setDirectory(startDir.getAbsolutePath());
+        }
+        dialog.addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentHidden(ComponentEvent e) {
+                String file = dialog.getFile();
+                String dir = dialog.getDirectory();
+                if (file != null && dir != null) {
+                    prefs.put(PATH_PROPERTY, dir);
                 }
             }
         });
     }
 
+    public File loadDir() {
+        return resolveExistingDirectory(prefs.get(PATH_PROPERTY, null));
+    }
+
+    public void saveDir(File f) {
+        if (f != null) {
+            prefs.put(PATH_PROPERTY,
+                    f.isFile()
+                            ? f.getParentFile().getAbsolutePath()
+                            : f.getAbsolutePath());
+        }
+    }
+
     private static File resolveExistingDirectory(String path) {
         if (path == null) {
             return null;
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/UIInitializer.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/FileChooserFactory.java
similarity index 51%
copy from 
modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/UIInitializer.java
copy to 
modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/FileChooserFactory.java
index a544193e9..0362c73cb 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/platform/UIInitializer.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/FileChooserFactory.java
@@ -16,26 +16,31 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
-package org.apache.cayenne.modeler.platform;
 
-import org.apache.cayenne.modeler.Application;
+package org.apache.cayenne.modeler.toolkit.filechooser;
+
+import javax.swing.filechooser.FileFilter;
+import java.awt.Component;
+import java.io.File;
 
 /**
- * A base callback for platform-specific Modeler initialization.
+ * Platform-specific factory for file and directory chooser dialogs.
+ * Implementations may use either a native OS dialog or Swing's JFileChooser.
  */
-public interface UIInitializer {
+public abstract class FileChooserFactory {
+
+    /**
+     * Shows a file-open dialog and returns the selected file, or {@code null} 
if canceled.
+     */
+    public abstract File openFile(Component parent, String title, File 
initialDir, FileFilter filter);
 
     /**
-     * Sets platform-specific system properties and initializes the 
application look and feel.
-     * Called before AWT/Swing is touched, so implementations may set 
properties that AWT reads
-     * only during its own init (e.g. {@code apple.awt.application.name}) 
before installing the L&F.
+     * Shows a file-save dialog and returns the selected file, or {@code null} 
if canceled.
      */
-    default void beforeSwingLaunch() {
-    }
+    public abstract File saveFile(Component parent, String title, File 
initialDir, String defaultName);
 
     /**
-     * Updates default frame menus according to the platform specifics.
+     * Shows a directory-selection dialog and returns the selected directory, 
or {@code null} if canceled.
      */
-    default void afterFrameCreated(Application app) {
-    }
+    public abstract File openDirectory(Component parent, String title, File 
initialDir);
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/JFileChooserFactory.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/JFileChooserFactory.java
new file mode 100644
index 000000000..9d8e89efb
--- /dev/null
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/toolkit/filechooser/JFileChooserFactory.java
@@ -0,0 +1,82 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.
+ ****************************************************************/
+
+package org.apache.cayenne.modeler.toolkit.filechooser;
+
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+import java.awt.Component;
+import java.io.File;
+
+/**
+ * {@link FileChooserFactory} implementation backed by Swing's {@link 
JFileChooser}.
+ */
+public class JFileChooserFactory extends FileChooserFactory {
+
+    @Override
+    public File openFile(Component parent, String title, File initialDir, 
FileFilter filter) {
+        JFileChooser chooser = new JFileChooser();
+        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+        if (title != null) {
+            chooser.setDialogTitle(title);
+        }
+        if (initialDir != null) {
+            chooser.setCurrentDirectory(initialDir);
+        }
+        if (filter != null) {
+            chooser.addChoosableFileFilter(filter);
+            chooser.setFileFilter(filter);
+        }
+        return chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION
+                ? chooser.getSelectedFile()
+                : null;
+    }
+
+    @Override
+    public File saveFile(Component parent, String title, File initialDir, 
String defaultName) {
+        JFileChooser chooser = new JFileChooser();
+        if (title != null) {
+            chooser.setDialogTitle(title);
+        }
+        if (initialDir != null) {
+            chooser.setCurrentDirectory(initialDir);
+        }
+        if (defaultName != null) {
+            chooser.setSelectedFile(new File(defaultName));
+        }
+        return chooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION
+                ? chooser.getSelectedFile()
+                : null;
+    }
+
+    @Override
+    public File openDirectory(Component parent, String title, File initialDir) 
{
+        JFileChooser chooser = new JFileChooser();
+        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+        if (title != null) {
+            chooser.setDialogTitle(title);
+        }
+        if (initialDir != null) {
+            chooser.setCurrentDirectory(initialDir);
+        }
+        return chooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION
+                ? chooser.getSelectedFile()
+                : null;
+    }
+}
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportDataMapAction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportDataMapAction.java
index 0a0408659..9246c236d 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportDataMapAction.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportDataMapAction.java
@@ -35,7 +35,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.swing.*;
-import java.awt.*;
+import java.awt.Frame;
 import java.awt.event.ActionEvent;
 import java.io.File;
 import java.net.URL;
@@ -98,19 +98,9 @@ public class ImportDataMapAction extends AppAction {
     }
 
     protected File selectDataMap(Frame f) {
-
-        JFileChooser chooser = new JFileChooser();
-        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
-
-        new 
FileChooserPrefs(app.getPrefsManager().uiNode("importDataMap/lastDir")).bind(chooser);
-
-        chooser.addChoosableFileFilter(FileFilters.getDataMapFilter());
-
-        int status = chooser.showDialog(f, "Select DataMap");
-        if (status == JFileChooser.APPROVE_OPTION) {
-            return chooser.getSelectedFile();
-        }
-
-        return null;
+        FileChooserPrefs prefs = new 
FileChooserPrefs(app.getPrefsManager().uiNode("importDataMap/lastDir"));
+        File selected = app.getFileChooserFactory().openFile(f, "Select 
DataMap", prefs.loadDir(), FileFilters.getDataMapFilter());
+        prefs.saveDir(selected);
+        return selected;
     }
 }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportEOModelAction.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportEOModelAction.java
index 916d203c2..cf0ef10ba 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportEOModelAction.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ImportEOModelAction.java
@@ -58,9 +58,7 @@ import org.apache.cayenne.wocompat.EOModelProcessor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.swing.*;
 import javax.swing.filechooser.FileFilter;
-import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.io.File;
 import java.net.URL;
@@ -77,11 +75,11 @@ public class ImportEOModelAction extends AppAction {
 
     private static final Logger LOGGER = 
LoggerFactory.getLogger(ImportEOModelAction.class);
 
+    private static final FileFilter eomodelFilter = new EOModelFileFilter();
+
     private final Map<String, String> adaptersByEofPlugin;
     private final Map<String, String> adaptersByDriver;
 
-    private JFileChooser eoModelChooser;
-
     public ImportEOModelAction(Application application) {
         super(application, "Import EOModel");
 
@@ -125,34 +123,27 @@ public class ImportEOModelAction extends AppAction {
      * Allows user to select an EOModel, then imports it as a DataMap.
      */
     protected void importEOModel() {
-        JFileChooser fileChooser = getEOModelChooser();
-        int status = fileChooser.showOpenDialog(app.getFrame());
-
-        if (status == JFileChooser.APPROVE_OPTION) {
-
-            File file = fileChooser.getSelectedFile().getParentFile();
-
-            DataMap currentMap = getProjectSession().getSelectedDataMap();
-
-            try {
-                URL url = file.toURI().toURL();
-
-                EOModelProcessor processor = new EOModelProcessor();
-
-                // load DataNode if we are not merging with an existing map
-                if (currentMap == null) {
-                    loadDataNode(processor.loadModeIndex(url));
-                }
+        FileChooserPrefs prefs = new 
FileChooserPrefs(app.getPrefsManager().uiNode("importEOModel/lastDir"));
+        File selected = app.getFileChooserFactory().openFile(app.getFrame(), 
"Select EOModel", prefs.loadDir(), eomodelFilter);
+        if (selected == null) {
+            return;
+        }
+        prefs.saveDir(selected);
 
-                // load DataMap
-                DataMap map = processor.loadEOModel(url);
-                addDataMap(map, currentMap);
+        File file = selected.getParentFile();
+        DataMap currentMap = getProjectSession().getSelectedDataMap();
 
-            } catch (Exception ex) {
-                LOGGER.info("EOModel Loading Exception", ex);
-                new ErrorDialog(app, "Import EOModel Error", ex).open();
+        try {
+            URL url = file.toURI().toURL();
+            EOModelProcessor processor = new EOModelProcessor();
+            if (currentMap == null) {
+                loadDataNode(processor.loadModeIndex(url));
             }
-
+            DataMap map = processor.loadEOModel(url);
+            addDataMap(map, currentMap);
+        } catch (Exception ex) {
+            LOGGER.info("EOModel Loading Exception", ex);
+            new ErrorDialog(app, "Import EOModel Error", ex).open();
         }
     }
 
@@ -306,59 +297,6 @@ public class ImportEOModelAction extends AppAction {
         }
     }
 
-    /**
-     * Returns EOModel chooser.
-     */
-    public JFileChooser getEOModelChooser() {
-
-        if (eoModelChooser == null) {
-            eoModelChooser = new EOModelChooser("Select EOModel");
-        }
-
-        new 
FileChooserPrefs(app.getPrefsManager().uiNode("importEOModel/lastDir")).bind(eoModelChooser);
-
-        return eoModelChooser;
-    }
-
-    /**
-     * Custom file chooser that will pop up again if an invalid file is 
selected.
-     */
-    static class EOModelChooser extends JFileChooser {
-
-        static final FileFilter eomodelFilter = new EOModelFileFilter();
-
-        private JDialog cachedDialog;
-
-        public EOModelChooser(String title) {
-            super.setFileFilter(eomodelFilter);
-            super.setDialogTitle(title);
-            super.setFileSelectionMode(JFileChooser.FILES_ONLY);
-        }
-
-        public int showOpenDialog(Component parent) {
-            int status = super.showOpenDialog(parent);
-            if (status != JFileChooser.APPROVE_OPTION) {
-                cachedDialog = null;
-                return status;
-            }
-
-            File file = this.getSelectedFile();
-            if (file.isFile() && eomodelFilter.accept(file)) {
-                cachedDialog = null;
-                return JFileChooser.APPROVE_OPTION;
-            }
-
-            return this.showOpenDialog(parent);
-        }
-
-        protected JDialog createDialog(Component parent) throws 
HeadlessException {
-            if (cachedDialog == null) {
-                cachedDialog = super.createDialog(parent);
-            }
-            return cachedDialog;
-        }
-    }
-
     static final class EOModelFileFilter extends FileFilter {
 
         static final String EOM_SUFFIX = ".eomodeld";
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectOpener.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectOpener.java
index 01d4fb85c..32d49032e 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectOpener.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/action/ProjectOpener.java
@@ -24,21 +24,20 @@ import 
org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.pref.adapters.RecentProjectsPrefs;
 import org.apache.cayenne.modeler.ui.project.overwrite.OverwriteDialog;
+import org.apache.cayenne.modeler.toolkit.filechooser.FileChooserFactory;
 import org.apache.cayenne.modeler.toolkit.filechooser.FileFilters;
 import org.apache.cayenne.project.Project;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.swing.*;
-import javax.swing.filechooser.FileFilter;
 import java.io.File;
 import java.util.List;
 
 
 /**
- * File chooser panel used to select a directory to store project files.
+ * Helper that shows file-chooser dialogs for opening and saving Cayenne 
projects.
  */
-class ProjectOpener extends JFileChooser {
+class ProjectOpener {
 
     private static final Logger LOGGER = 
LoggerFactory.getLogger(ProjectOpener.class);
 
@@ -46,94 +45,52 @@ class ProjectOpener extends JFileChooser {
      * Selects a directory to store the project.
      */
     public File newProjectDir(Application application, Project p) {
-        if (p != null) {
-            StringBuilder nameProject = new StringBuilder("cayenne");
-            if (((DataChannelDescriptor) p.getRootNode()).getName() != null) {
-                nameProject.append("-").append(((DataChannelDescriptor) 
p.getRootNode()).getName());
-            }
-            nameProject.append(".xml");
-            // configure for application project
-            return newProjectDir(nameProject.toString(), 
FileFilters.getApplicationFilter(), application);
-        } else {
+        if (p == null) {
             throw new CayenneRuntimeException("Null project.");
         }
-    }
 
-    private File newProjectDir(String location, FileFilter filter, Application 
application) {
-        // configure dialog
-        setDialogTitle("Select Project Directory");
-        setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
-        setCurrentDirectory(getDefaultStartDir(application));
-
-        // preselect current directory
-        if (getCurrentDirectory() != null) {
-            setSelectedFile(getCurrentDirectory());
+        StringBuilder nameProject = new StringBuilder("cayenne");
+        if (((DataChannelDescriptor) p.getRootNode()).getName() != null) {
+            nameProject.append("-").append(((DataChannelDescriptor) 
p.getRootNode()).getName());
         }
+        nameProject.append(".xml");
 
-        // configure filters
-        resetChoosableFileFilters();
-        // allow users to see directories with cayenne.xml files
-        addChoosableFileFilter(filter);
-
-        File selectedDir;
+        FileChooserFactory factory = application.getFileChooserFactory();
+        File startDir = getDefaultStartDir(application);
 
         while (true) {
-            int status = showDialog(application.getFrame(), "Select");
-            selectedDir = getSelectedFile();
-            if (status != JFileChooser.APPROVE_OPTION || selectedDir == null) {
+            File selectedDir = factory.openDirectory(application.getFrame(), 
"Select Project Directory", startDir);
+            if (selectedDir == null) {
                 LOGGER.info("Save canceled.");
                 return null;
             }
 
-            // normalize selection
-            LOGGER.info("Selected: " + selectedDir);
-            if (!selectedDir.isDirectory()) {
-                selectedDir = getSelectedFile().getParentFile();
-            }
-
-            // check for overwrite
-            File projectFile = new File(selectedDir, location);
+            LOGGER.info("Selected: {}", selectedDir);
+            File projectFile = new File(selectedDir, nameProject.toString());
             if (projectFile.exists()) {
                 OverwriteDialog dialog = new OverwriteDialog(projectFile, 
application.getFrame());
                 dialog.show();
-
                 if (dialog.shouldOverwrite()) {
-                    break;
+                    return selectedDir;
                 } else if (!dialog.shouldSelectAnother()) {
-                    // canceled
                     return null;
                 }
+                // else loop again
             } else {
-                break;
+                return selectedDir;
             }
         }
-
-        return selectedDir;
     }
 
     /**
-     * Runs a dialog to open Cayenne project.
+     * Runs a dialog to open a Cayenne project.
      */
     public File openProjectFile(Application application) {
-
-        // configure dialog
-        setDialogTitle("Select Project File");
-        setFileSelectionMode(JFileChooser.FILES_ONLY);
-        setCurrentDirectory(getDefaultStartDir(application));
-
-        // configure filters
-        resetChoosableFileFilters();
-        addChoosableFileFilter(FileFilters.getApplicationFilter());
-
-        // default to App projects
-        setFileFilter(FileFilters.getApplicationFilter());
-
-        int status = showOpenDialog(application.getFrame());
-        if (status != JFileChooser.APPROVE_OPTION) {
-            return null;
-        }
-
-        return getSelectedFile();
+        return application.getFileChooserFactory().openFile(
+                application.getFrame(),
+                "Select Project File",
+                getDefaultStartDir(application),
+                FileFilters.getApplicationFilter());
     }
 
     private File getDefaultStartDir(Application application) {
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsDialog.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsDialog.java
index 520e13b05..318676d51 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsDialog.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbgen/DBGeneratorOptionsDialog.java
@@ -42,7 +42,6 @@ import javax.swing.DefaultComboBoxModel;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JComboBox;
-import javax.swing.JFileChooser;
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
@@ -350,25 +349,16 @@ public class DBGeneratorOptionsDialog extends 
ProjectDialog {
     }
 
     private void storeSQLAction() {
-        JFileChooser fc = new JFileChooser();
-        fc.setDialogType(JFileChooser.SAVE_DIALOG);
-        fc.setDialogTitle("Save SQL Script");
-
         File projectDir = new File(app
                 .getFrame().getProjectSession().project()
                 .getConfigurationResource()
                 .getURL()
                 .getPath());
-        fc.setCurrentDirectory(projectDir);
-        if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+        File file = app.getFileChooserFactory().saveFile(this, "Save SQL 
Script", projectDir, null);
+        if (file != null) {
             refreshGeneratorAction();
-            try {
-                File file = fc.getSelectedFile();
-                FileWriter fw = new FileWriter(file);
-                PrintWriter pw = new PrintWriter(fw);
+            try (FileWriter fw = new FileWriter(file); PrintWriter pw = new 
PrintWriter(fw)) {
                 pw.print(textForSQL);
-                pw.flush();
-                pw.close();
             } catch (IOException ex) {
                 reportError("Error Saving SQL", ex);
             }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbmerge/MergerOptionsDialog.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbmerge/MergerOptionsDialog.java
index 8593087ee..5bad5ae0e 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbmerge/MergerOptionsDialog.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbmerge/MergerOptionsDialog.java
@@ -61,7 +61,6 @@ import org.slf4j.LoggerFactory;
 import javax.sql.DataSource;
 import javax.swing.Box;
 import javax.swing.JButton;
-import javax.swing.JFileChooser;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
@@ -359,24 +358,13 @@ public class MergerOptionsDialog extends ProjectDialog {
     }
 
     private void storeSQLAction() {
-        JFileChooser fc = new JFileChooser();
-        fc.setDialogType(JFileChooser.SAVE_DIALOG);
-        fc.setDialogTitle("Save SQL Script");
-
-        Resource projectDir = 
app.getFrame().getProjectSession().project().getConfigurationResource();
-        if (projectDir != null) {
-            fc.setCurrentDirectory(new File(projectDir.getURL().getPath()));
-        }
-
-        if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+        Resource configRes = 
app.getFrame().getProjectSession().project().getConfigurationResource();
+        File initialDir = configRes != null ? new 
File(configRes.getURL().getPath()) : null;
+        File file = app.getFileChooserFactory().saveFile(this, "Save SQL 
Script", initialDir, null);
+        if (file != null) {
             refreshSQLAction();
-            try {
-                File file = fc.getSelectedFile();
-                FileWriter fw = new FileWriter(file);
-                PrintWriter pw = new PrintWriter(fw);
+            try (FileWriter fw = new FileWriter(file); PrintWriter pw = new 
PrintWriter(fw)) {
                 pw.print(textForSQL);
-                pw.flush();
-                pw.close();
             } catch (IOException ex) {
                 reportError("Error Saving SQL", ex);
             }
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/preferences/classpath/ClasspathPrefsPanel.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/preferences/classpath/ClasspathPrefsPanel.java
index 9aacfa88b..34bb4d619 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/preferences/classpath/ClasspathPrefsPanel.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/preferences/classpath/ClasspathPrefsPanel.java
@@ -33,12 +33,10 @@ import 
org.apache.cayenne.modeler.toolkit.filechooser.FileFilters;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
-import javax.swing.JFileChooser;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.SwingUtilities;
-import javax.swing.filechooser.FileFilter;
 import javax.swing.table.AbstractTableModel;
 import java.awt.BorderLayout;
 import java.io.File;
@@ -99,10 +97,8 @@ public class ClasspathPrefsPanel extends AppPanel {
         JButton addMvnButton = new JButton("Get From Maven Central");
         JButton deleteEntryButton = new JButton("Delete");
 
-        addJarButton.addActionListener(e ->
-                chooseClassEntry(FileFilters.getExtensionFileFilter("jar", 
"JAR Files"), "Select JAR File.", JFileChooser.FILES_ONLY));
-        addDirButton.addActionListener(e ->
-                chooseClassEntry(null, "Select Java Class Directory.", 
JFileChooser.DIRECTORIES_ONLY));
+        addJarButton.addActionListener(e -> chooseJarEntry());
+        addDirButton.addActionListener(e -> chooseDirEntry());
         addMvnButton.addActionListener(e ->
                 new MavenDependencyDialog(app, 
SwingUtilities.getWindowAncestor(this), this).open());
         deleteEntryButton.addActionListener(e -> removeEntryClicked());
@@ -133,24 +129,18 @@ public class ClasspathPrefsPanel extends AppPanel {
         add(outer.getPanel(), BorderLayout.CENTER);
     }
 
-    private void chooseClassEntry(FileFilter filter, String title, int 
selectionMode) {
-        JFileChooser chooser = new JFileChooser();
-        chooser.setFileSelectionMode(selectionMode);
-        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
-        chooser.setAcceptAllFileFilterUsed(true);
-
-        new 
FileChooserPrefs(app.getPrefsManager().uiNode("classpath/lastDir")).bind(chooser);
-        if (filter != null) {
-            chooser.addChoosableFileFilter(filter);
-            chooser.setFileFilter(filter);
-        }
-        chooser.setDialogTitle(title);
-
-        File selected = null;
-        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
-            selected = chooser.getSelectedFile();
-        }
+    private void chooseJarEntry() {
+        FileChooserPrefs prefs = new 
FileChooserPrefs(app.getPrefsManager().uiNode("classpath/lastDir"));
+        File selected = app.getFileChooserFactory().openFile(
+                this, "Select JAR File.", prefs.loadDir(), 
FileFilters.getExtensionFileFilter("jar", "JAR Files"));
+        prefs.saveDir(selected);
+        entryAdded(selected);
+    }
 
+    private void chooseDirEntry() {
+        FileChooserPrefs prefs = new 
FileChooserPrefs(app.getPrefsManager().uiNode("classpath/lastDir"));
+        File selected = app.getFileChooserFactory().openDirectory(this, 
"Select Java Class Directory.", prefs.loadDir());
+        prefs.saveDir(selected);
         entryAdded(selected);
     }
 
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigPanel.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigPanel.java
index 61ba591c1..50ae2d49c 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigPanel.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/datamap/cgen/CgenConfigPanel.java
@@ -397,21 +397,12 @@ public class CgenConfigPanel extends ProjectPanel {
 
     private void selectOutputFolderAction() {
         String currentDir = outputFolder.getText();
+        File initialDir = !Util.isEmptyString(currentDir)
+                ? new File(currentDir)
+                : CgenOps.baseDir(session).toFile();
 
-        JFileChooser chooser = new JFileChooser();
-        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
-        chooser.setDialogType(JFileChooser.OPEN_DIALOG);
-
-        if (!Util.isEmptyString(currentDir)) {
-            chooser.setCurrentDirectory(new File(currentDir));
-        } else {
-            chooser.setCurrentDirectory(CgenOps.baseDir(session).toFile());
-        }
-
-        int result = chooser.showOpenDialog(this);
-        if (result == JFileChooser.APPROVE_OPTION) {
-            File selected = chooser.getSelectedFile();
-
+        File selected = app.getFileChooserFactory().openDirectory(this, 
"Select Output Folder", initialDir);
+        if (selected != null) {
             String path = selected.getAbsolutePath();
             outputFolder.setText(path);
             applyOutputFolder(path);


Reply via email to