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

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

fix #14613 - Special HTML characters not escaped in GUI error messages

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