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

Last change on this file since 10601 was 10601, checked in by Don-vip, 3 years ago

see #11390 - sonar - squid:S1604 - Java 8: Anonymous inner classes containing only one method should become lambdas

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