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

Last change on this file was 18871, checked in by taylor.smock, 6 months ago

See #23218: Use newer error_prone versions when compiling on Java 11+

error_prone 2.11 dropped support for compiling with Java 8, although it still
supports compiling for Java 8. The "major" new check for us is NotJavadoc since
we used /** in quite a few places which were not javadoc.

Other "new" checks that are of interest:

  • AlreadyChecked: if (foo) { doFoo(); } else if (!foo) { doBar(); }
  • UnnecessaryStringBuilder: Avoid StringBuilder (Java converts + to StringBuilder behind-the-scenes, but may also do something else if it performs better)
  • NonApiType: Avoid specific interface types in function definitions
  • NamedLikeContextualKeyword: Avoid using restricted names for classes and methods
  • UnusedMethod: Unused private methods should be removed

This fixes most of the new error_prone issues and some SonarLint issues.

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