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

Last change on this file since 12809 was 12671, checked in by Don-vip, 7 years 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.