// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.actions; import static org.openstreetmap.josm.gui.help.HelpUtil.ht; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.io.BufferedReader; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.gui.HelpAwareOptionPane; import org.openstreetmap.josm.gui.PleaseWaitRunnable; import org.openstreetmap.josm.gui.help.HelpUtil; import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; import org.openstreetmap.josm.io.AllFormatsImporter; import org.openstreetmap.josm.io.FileImporter; import org.openstreetmap.josm.io.OsmTransferException; import org.openstreetmap.josm.tools.MultiMap; import org.openstreetmap.josm.tools.Shortcut; import org.xml.sax.SAXException; /** * Open a file chooser dialog and select a file to import. * * @author imi * @since 1146 */ public class OpenFileAction extends DiskAccessAction { /** * The {@link ExtensionFileFilter} matching .url files */ public static final ExtensionFileFilter URL_FILE_FILTER = new ExtensionFileFilter("url", "url", tr("URL Files") + " (*.url)"); /** * Create an open action. The name is "Open a file". */ public OpenFileAction() { super(tr("Open..."), "open", tr("Open a file."), Shortcut.registerShortcut("system:open", tr("File: {0}", tr("Open...")), KeyEvent.VK_O, Shortcut.CTRL)); putValue("help", ht("/Action/Open")); } @Override public void actionPerformed(ActionEvent e) { AbstractFileChooser fc = createAndOpenFileChooser(true, true, null); if (fc == null) return; File[] files = fc.getSelectedFiles(); OpenFileTask task = new OpenFileTask(Arrays.asList(files), fc.getFileFilter()); task.setRecordHistory(true); Main.worker.submit(task); } @Override protected void updateEnabledState() { setEnabled(true); } /** * Open a list of files. The complete list will be passed to batch importers. * @param fileList A list of files */ public static void openFiles(List fileList) { openFiles(fileList, false); } public static void openFiles(List fileList, boolean recordHistory) { OpenFileTask task = new OpenFileTask(fileList, null); task.setRecordHistory(recordHistory); Main.worker.submit(task); } public static class OpenFileTask extends PleaseWaitRunnable { private final List files; private final List successfullyOpenedFiles = new ArrayList<>(); private final Set fileHistory = new LinkedHashSet<>(); private final Set failedAll = new HashSet<>(); private final FileFilter fileFilter; private boolean canceled; private boolean recordHistory; public OpenFileTask(final List files, final FileFilter fileFilter, final String title) { super(title, false /* don't ignore exception */); this.fileFilter = fileFilter; this.files = new ArrayList<>(files.size()); for (final File file : files) { if (file.exists()) { this.files.add(file); } else if (file.getParentFile() != null) { // try to guess an extension using the specified fileFilter final File[] matchingFiles = file.getParentFile().listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith(file.getName()) && fileFilter != null && fileFilter.accept(new File(dir, name)); } }); if (matchingFiles != null && matchingFiles.length == 1) { // use the unique match as filename this.files.add(matchingFiles[0]); } else { // add original filename for error reporting later on this.files.add(file); } } } } public OpenFileTask(List files, FileFilter fileFilter) { this(files, fileFilter, tr("Opening files")); } /** * save filename in history (for list of recently opened files) * default: false */ public void setRecordHistory(boolean recordHistory) { this.recordHistory = recordHistory; } public boolean isRecordHistory() { return recordHistory; } @Override protected void cancel() { this.canceled = true; } @Override protected void finish() { // do nothing } protected void alertFilesNotMatchingWithImporter(Collection files, FileImporter importer) { final StringBuilder msg = new StringBuilder(); msg.append("").append( trn( "Cannot open {0} file with the file importer ''{1}''.", "Cannot open {0} files with the file importer ''{1}''.", files.size(), files.size(), importer.filter.getDescription() ) ).append("
    "); for (File f: files) { msg.append("
  • ").append(f.getAbsolutePath()).append("
  • "); } msg.append("
"); HelpAwareOptionPane.showMessageDialogInEDT( Main.parent, msg.toString(), tr("Warning"), JOptionPane.WARNING_MESSAGE, HelpUtil.ht("/Action/Open#ImporterCantImportFiles") ); } protected void alertFilesWithUnknownImporter(Collection files) { final StringBuilder msg = new StringBuilder(); msg.append("").append( trn( "Cannot open {0} file because file does not exist or no suitable file importer is available.", "Cannot open {0} files because files do not exist or no suitable file importer is available.", files.size(), files.size() ) ).append("
    "); for (File f: files) { msg.append("
  • ").append(f.getAbsolutePath()).append(" (") .append(f.exists() ? tr("no importer") : tr("does not exist")) .append(")
  • "); } msg.append("
"); HelpAwareOptionPane.showMessageDialogInEDT( Main.parent, msg.toString(), tr("Warning"), JOptionPane.WARNING_MESSAGE, HelpUtil.ht("/Action/Open#MissingImporterForFiles") ); } @Override protected void realRun() throws SAXException, IOException, OsmTransferException { if (files == null || files.isEmpty()) return; /** * Find the importer with the chosen file filter */ FileImporter chosenImporter = null; if (fileFilter != null) { for (FileImporter importer : ExtensionFileFilter.importers) { if (fileFilter.equals(importer.filter)) { chosenImporter = importer; } } } /** * If the filter hasn't been changed in the dialog, chosenImporter is null now. * When the filter has been set explicitly to AllFormatsImporter, treat this the same. */ if (chosenImporter instanceof AllFormatsImporter) { chosenImporter = null; } getProgressMonitor().setTicksCount(files.size()); if (chosenImporter != null) { // The importer was explicitly chosen, so use it. List filesNotMatchingWithImporter = new LinkedList<>(); List filesMatchingWithImporter = new LinkedList<>(); for (final File f : files) { if (!chosenImporter.acceptFile(f)) { if (f.isDirectory()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JOptionPane.showMessageDialog(Main.parent, tr( "Cannot open directory ''{0}''.
Please select a file.", f.getAbsolutePath()), tr("Open file"), JOptionPane.ERROR_MESSAGE); } }); // TODO when changing to Java 6: Don't cancel the // task here but use different modality. (Currently 2 dialogs // would block each other.) return; } else { filesNotMatchingWithImporter.add(f); } } else { filesMatchingWithImporter.add(f); } } if (!filesNotMatchingWithImporter.isEmpty()) { alertFilesNotMatchingWithImporter(filesNotMatchingWithImporter, chosenImporter); } if (!filesMatchingWithImporter.isEmpty()) { importData(chosenImporter, filesMatchingWithImporter); } } else { // find appropriate importer MultiMap importerMap = new MultiMap<>(); List filesWithUnknownImporter = new LinkedList<>(); List urlFiles = new LinkedList<>(); FILES: for (File f : files) { for (FileImporter importer : ExtensionFileFilter.importers) { if (importer.acceptFile(f)) { importerMap.put(importer, f); continue FILES; } } if (URL_FILE_FILTER.accept(f)) { urlFiles.add(f); } else { filesWithUnknownImporter.add(f); } } if (!filesWithUnknownImporter.isEmpty()) { alertFilesWithUnknownImporter(filesWithUnknownImporter); } List importers = new ArrayList<>(importerMap.keySet()); Collections.sort(importers); Collections.reverse(importers); for (FileImporter importer : importers) { importData(importer, new ArrayList<>(importerMap.get(importer))); } for (File urlFile: urlFiles) { try (BufferedReader reader = Files.newBufferedReader(urlFile.toPath(), StandardCharsets.UTF_8)) { String line; while ((line = reader.readLine()) != null) { Matcher m = Pattern.compile(".*(https?://.*)").matcher(line); if (m.matches()) { String url = m.group(1); Main.main.menu.openLocation.openUrl(false, url); } } } catch (Exception e) { Main.error(e); } } } if (recordHistory) { Collection oldFileHistory = Main.pref.getCollection("file-open.history"); fileHistory.addAll(oldFileHistory); // remove the files which failed to load from the list fileHistory.removeAll(failedAll); int maxsize = Math.max(0, Main.pref.getInteger("file-open.history.max-size", 15)); Main.pref.putCollectionBounded("file-open.history", maxsize, fileHistory); } } public void importData(FileImporter importer, List files) { if (importer.isBatchImporter()) { if (canceled) return; String msg = trn("Opening {0} file...", "Opening {0} files...", files.size(), files.size()); getProgressMonitor().setCustomText(msg); getProgressMonitor().indeterminateSubTask(msg); if (importer.importDataHandleExceptions(files, getProgressMonitor().createSubTaskMonitor(files.size(), false))) { successfullyOpenedFiles.addAll(files); } } else { for (File f : files) { if (canceled) return; getProgressMonitor().indeterminateSubTask(tr("Opening file ''{0}'' ...", f.getAbsolutePath())); if (importer.importDataHandleExceptions(f, getProgressMonitor().createSubTaskMonitor(1, false))) { successfullyOpenedFiles.add(f); } } } if (recordHistory && !importer.isBatchImporter()) { for (File f : files) { try { if (successfullyOpenedFiles.contains(f)) { fileHistory.add(f.getCanonicalPath()); } else { failedAll.add(f.getCanonicalPath()); } } catch (IOException e) { Main.warn(e); } } } } /** * Replies the list of files that have been successfully opened. * @return The list of files that have been successfully opened. */ public List getSuccessfullyOpenedFiles() { return successfullyOpenedFiles; } } }