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

Last change on this file since 13664 was 12894, checked in by bastiK, 7 years ago

see #15229 - update method name and signature for consistency

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