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

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

global use of Utils.isEmpty/isBlank

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