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

Last change on this file since 12106 was 11986, checked in by Don-vip, 7 years ago

add unit test for Main.postConstructorProcessCmdLine

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