source: josm/trunk/src/org/openstreetmap/josm/actions/OpenFileAction.java @ 12671

Last change on this file since 12671 was 12671, checked in by Don-vip, 4 months ago

see #15182 - move file importers/exporters from io package to gui.io.importexport package, as they rely heavily on GUI and are mainly used from Open/Save actions

  • Property svn:eol-style set to native
File size: 15.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.io.BufferedReader;
11import java.io.File;
12import java.io.IOException;
13import java.nio.charset.StandardCharsets;
14import java.nio.file.Files;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.HashSet;
20import java.util.LinkedHashSet;
21import java.util.LinkedList;
22import java.util.List;
23import java.util.Set;
24import java.util.concurrent.Future;
25import java.util.regex.Matcher;
26import java.util.regex.Pattern;
27import java.util.regex.PatternSyntaxException;
28
29import javax.swing.JOptionPane;
30import javax.swing.SwingUtilities;
31import javax.swing.filechooser.FileFilter;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.gui.HelpAwareOptionPane;
35import org.openstreetmap.josm.gui.MainApplication;
36import org.openstreetmap.josm.gui.MapFrame;
37import org.openstreetmap.josm.gui.PleaseWaitRunnable;
38import org.openstreetmap.josm.gui.io.importexport.AllFormatsImporter;
39import org.openstreetmap.josm.gui.io.importexport.FileImporter;
40import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
41import org.openstreetmap.josm.io.OsmTransferException;
42import org.openstreetmap.josm.tools.Logging;
43import org.openstreetmap.josm.tools.MultiMap;
44import org.openstreetmap.josm.tools.Shortcut;
45import org.openstreetmap.josm.tools.Utils;
46import org.xml.sax.SAXException;
47
48/**
49 * Open a file chooser dialog and select a file to import.
50 *
51 * @author imi
52 * @since 1146
53 */
54public class OpenFileAction extends DiskAccessAction {
55
56    /**
57     * The {@link ExtensionFileFilter} matching .url files
58     */
59    public static final ExtensionFileFilter URL_FILE_FILTER = new ExtensionFileFilter("url", "url", tr("URL Files") + " (*.url)");
60
61    /**
62     * Create an open action. The name is "Open a file".
63     */
64    public OpenFileAction() {
65        super(tr("Open..."), "open", tr("Open a file."),
66                Shortcut.registerShortcut("system:open", tr("File: {0}", tr("Open...")), KeyEvent.VK_O, Shortcut.CTRL));
67        putValue("help", ht("/Action/Open"));
68    }
69
70    @Override
71    public void actionPerformed(ActionEvent e) {
72        AbstractFileChooser fc = createAndOpenFileChooser(true, true, null);
73        if (fc == null)
74            return;
75        File[] files = fc.getSelectedFiles();
76        OpenFileTask task = new OpenFileTask(Arrays.asList(files), fc.getFileFilter());
77        task.setRecordHistory(true);
78        MainApplication.worker.submit(task);
79    }
80
81    @Override
82    protected void updateEnabledState() {
83        setEnabled(true);
84    }
85
86    /**
87     * Open a list of files. The complete list will be passed to batch importers.
88     * Filenames will not be saved in history.
89     * @param fileList A list of files
90     * @return the future task
91     * @since 11986 (return task)
92     */
93    public static Future<?> openFiles(List<File> fileList) {
94        return openFiles(fileList, false);
95    }
96
97    /**
98     * Open a list of files. The complete list will be passed to batch importers.
99     * @param fileList A list of files
100     * @param recordHistory {@code true} to save filename in history (default: false)
101     * @return the future task
102     * @since 11986 (return task)
103     */
104    public static Future<?> openFiles(List<File> fileList, boolean recordHistory) {
105        OpenFileTask task = new OpenFileTask(fileList, null);
106        task.setRecordHistory(recordHistory);
107        return MainApplication.worker.submit(task);
108    }
109
110    /**
111     * Task to open files.
112     */
113    public static class OpenFileTask extends PleaseWaitRunnable {
114        private final List<File> files;
115        private final List<File> successfullyOpenedFiles = new ArrayList<>();
116        private final Set<String> fileHistory = new LinkedHashSet<>();
117        private final Set<String> failedAll = new HashSet<>();
118        private final FileFilter fileFilter;
119        private boolean canceled;
120        private boolean recordHistory;
121
122        /**
123         * Constructs a new {@code OpenFileTask}.
124         * @param files files to open
125         * @param fileFilter file filter
126         * @param title message for the user
127         */
128        public OpenFileTask(final List<File> files, final FileFilter fileFilter, final String title) {
129            super(title, false /* don't ignore exception */);
130            this.fileFilter = fileFilter;
131            this.files = new ArrayList<>(files.size());
132            for (final File file : files) {
133                if (file.exists()) {
134                    this.files.add(file);
135                } else if (file.getParentFile() != null) {
136                    // try to guess an extension using the specified fileFilter
137                    final File[] matchingFiles = file.getParentFile().listFiles((dir, name) ->
138                            name.startsWith(file.getName()) && fileFilter != null && fileFilter.accept(new File(dir, name)));
139                    if (matchingFiles != null && matchingFiles.length == 1) {
140                        // use the unique match as filename
141                        this.files.add(matchingFiles[0]);
142                    } else {
143                        // add original filename for error reporting later on
144                        this.files.add(file);
145                    }
146                }
147            }
148        }
149
150        /**
151         * Constructs a new {@code OpenFileTask}.
152         * @param files files to open
153         * @param fileFilter file filter
154         */
155        public OpenFileTask(List<File> files, FileFilter fileFilter) {
156            this(files, fileFilter, tr("Opening files"));
157        }
158
159        /**
160         * Sets whether to save filename in history (for list of recently opened files).
161         * @param recordHistory {@code true} to save filename in history (default: false)
162         */
163        public void setRecordHistory(boolean recordHistory) {
164            this.recordHistory = recordHistory;
165        }
166
167        /**
168         * Determines if filename must be saved in history (for list of recently opened files).
169         * @return {@code true} if filename must be saved in history
170         */
171        public boolean isRecordHistory() {
172            return recordHistory;
173        }
174
175        @Override
176        protected void cancel() {
177            this.canceled = true;
178        }
179
180        @Override
181        protected void finish() {
182            MapFrame map = MainApplication.getMap();
183            if (map != null) {
184                map.repaint();
185            }
186        }
187
188        protected void alertFilesNotMatchingWithImporter(Collection<File> files, FileImporter importer) {
189            final StringBuilder msg = new StringBuilder(128).append("<html>").append(
190                    trn("Cannot open {0} file with the file importer ''{1}''.",
191                        "Cannot open {0} files with the file importer ''{1}''.",
192                        files.size(),
193                        files.size(),
194                        Utils.escapeReservedCharactersHTML(importer.filter.getDescription())
195                    )
196            ).append("<br><ul>");
197            for (File f: files) {
198                msg.append("<li>").append(f.getAbsolutePath()).append("</li>");
199            }
200            msg.append("</ul></html>");
201
202            HelpAwareOptionPane.showMessageDialogInEDT(
203                    Main.parent,
204                    msg.toString(),
205                    tr("Warning"),
206                    JOptionPane.WARNING_MESSAGE,
207                    ht("/Action/Open#ImporterCantImportFiles")
208            );
209        }
210
211        protected void alertFilesWithUnknownImporter(Collection<File> files) {
212            final StringBuilder msg = new StringBuilder(128).append("<html>").append(
213                    trn("Cannot open {0} file because file does not exist or no suitable file importer is available.",
214                        "Cannot open {0} files because files do not exist or no suitable file importer is available.",
215                        files.size(),
216                        files.size()
217                    )
218            ).append("<br><ul>");
219            for (File f: files) {
220                msg.append("<li>").append(f.getAbsolutePath()).append(" (<i>")
221                   .append(f.exists() ? tr("no importer") : tr("does not exist"))
222                   .append("</i>)</li>");
223            }
224            msg.append("</ul></html>");
225
226            HelpAwareOptionPane.showMessageDialogInEDT(
227                    Main.parent,
228                    msg.toString(),
229                    tr("Warning"),
230                    JOptionPane.WARNING_MESSAGE,
231                    ht("/Action/Open#MissingImporterForFiles")
232            );
233        }
234
235        @Override
236        protected void realRun() throws SAXException, IOException, OsmTransferException {
237            if (files == null || files.isEmpty()) return;
238
239            /**
240             * Find the importer with the chosen file filter
241             */
242            FileImporter chosenImporter = null;
243            if (fileFilter != null) {
244                for (FileImporter importer : ExtensionFileFilter.getImporters()) {
245                    if (fileFilter.equals(importer.filter)) {
246                        chosenImporter = importer;
247                    }
248                }
249            }
250            /**
251             * If the filter hasn't been changed in the dialog, chosenImporter is null now.
252             * When the filter has been set explicitly to AllFormatsImporter, treat this the same.
253             */
254            if (chosenImporter instanceof AllFormatsImporter) {
255                chosenImporter = null;
256            }
257            getProgressMonitor().setTicksCount(files.size());
258
259            if (chosenImporter != null) {
260                // The importer was explicitly chosen, so use it.
261                List<File> filesNotMatchingWithImporter = new LinkedList<>();
262                List<File> filesMatchingWithImporter = new LinkedList<>();
263                for (final File f : files) {
264                    if (!chosenImporter.acceptFile(f)) {
265                        if (f.isDirectory()) {
266                            SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(Main.parent, tr(
267                                    "<html>Cannot open directory ''{0}''.<br>Please select a file.</html>",
268                                    f.getAbsolutePath()), tr("Open file"), JOptionPane.ERROR_MESSAGE));
269                            // TODO when changing to Java 6: Don't cancel the task here but use different modality. (Currently 2 dialogs
270                            // would block each other.)
271                            return;
272                        } else {
273                            filesNotMatchingWithImporter.add(f);
274                        }
275                    } else {
276                        filesMatchingWithImporter.add(f);
277                    }
278                }
279
280                if (!filesNotMatchingWithImporter.isEmpty()) {
281                    alertFilesNotMatchingWithImporter(filesNotMatchingWithImporter, chosenImporter);
282                }
283                if (!filesMatchingWithImporter.isEmpty()) {
284                    importData(chosenImporter, filesMatchingWithImporter);
285                }
286            } else {
287                // find appropriate importer
288                MultiMap<FileImporter, File> importerMap = new MultiMap<>();
289                List<File> filesWithUnknownImporter = new LinkedList<>();
290                List<File> urlFiles = new LinkedList<>();
291                FILES: for (File f : files) {
292                    for (FileImporter importer : ExtensionFileFilter.getImporters()) {
293                        if (importer.acceptFile(f)) {
294                            importerMap.put(importer, f);
295                            continue FILES;
296                        }
297                    }
298                    if (URL_FILE_FILTER.accept(f)) {
299                        urlFiles.add(f);
300                    } else {
301                        filesWithUnknownImporter.add(f);
302                    }
303                }
304                if (!filesWithUnknownImporter.isEmpty()) {
305                    alertFilesWithUnknownImporter(filesWithUnknownImporter);
306                }
307                List<FileImporter> importers = new ArrayList<>(importerMap.keySet());
308                Collections.sort(importers);
309                Collections.reverse(importers);
310
311                for (FileImporter importer : importers) {
312                    importData(importer, new ArrayList<>(importerMap.get(importer)));
313                }
314
315                for (File urlFile: urlFiles) {
316                    try (BufferedReader reader = Files.newBufferedReader(urlFile.toPath(), StandardCharsets.UTF_8)) {
317                        String line;
318                        while ((line = reader.readLine()) != null) {
319                            Matcher m = Pattern.compile(".*(https?://.*)").matcher(line);
320                            if (m.matches()) {
321                                String url = m.group(1);
322                                MainApplication.getMenu().openLocation.openUrl(false, url);
323                            }
324                        }
325                    } catch (IOException | PatternSyntaxException | IllegalStateException | IndexOutOfBoundsException e) {
326                        Logging.error(e);
327                    }
328                }
329            }
330
331            if (recordHistory) {
332                Collection<String> oldFileHistory = Main.pref.getCollection("file-open.history");
333                fileHistory.addAll(oldFileHistory);
334                // remove the files which failed to load from the list
335                fileHistory.removeAll(failedAll);
336                int maxsize = Math.max(0, Main.pref.getInteger("file-open.history.max-size", 15));
337                Main.pref.putCollectionBounded("file-open.history", maxsize, fileHistory);
338            }
339        }
340
341        /**
342         * Import data files with the given importer.
343         * @param importer file importer
344         * @param files data files to import
345         */
346        public void importData(FileImporter importer, List<File> files) {
347            if (importer.isBatchImporter()) {
348                if (canceled) return;
349                String msg = trn("Opening {0} file...", "Opening {0} files...", files.size(), files.size());
350                getProgressMonitor().setCustomText(msg);
351                getProgressMonitor().indeterminateSubTask(msg);
352                if (importer.importDataHandleExceptions(files, getProgressMonitor().createSubTaskMonitor(files.size(), false))) {
353                    successfullyOpenedFiles.addAll(files);
354                }
355            } else {
356                for (File f : files) {
357                    if (canceled) return;
358                    getProgressMonitor().indeterminateSubTask(tr("Opening file ''{0}'' ...", f.getAbsolutePath()));
359                    if (importer.importDataHandleExceptions(f, getProgressMonitor().createSubTaskMonitor(1, false))) {
360                        successfullyOpenedFiles.add(f);
361                    }
362                }
363            }
364            if (recordHistory && !importer.isBatchImporter()) {
365                for (File f : files) {
366                    try {
367                        if (successfullyOpenedFiles.contains(f)) {
368                            fileHistory.add(f.getCanonicalPath());
369                        } else {
370                            failedAll.add(f.getCanonicalPath());
371                        }
372                    } catch (IOException e) {
373                        Logging.warn(e);
374                    }
375                }
376            }
377        }
378
379        /**
380         * Replies the list of files that have been successfully opened.
381         * @return The list of files that have been successfully opened.
382         */
383        public List<File> getSuccessfullyOpenedFiles() {
384            return successfullyOpenedFiles;
385        }
386    }
387}
Note: See TracBrowser for help on using the repository browser.