001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.actions;
003    
004    import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005    import static org.openstreetmap.josm.tools.I18n.tr;
006    import static org.openstreetmap.josm.tools.I18n.trn;
007    
008    import java.awt.event.ActionEvent;
009    import java.awt.event.KeyEvent;
010    import java.io.BufferedReader;
011    import java.io.File;
012    import java.io.FileReader;
013    import java.io.IOException;
014    import java.util.ArrayList;
015    import java.util.Arrays;
016    import java.util.Collection;
017    import java.util.Collections;
018    import java.util.HashSet;
019    import java.util.LinkedHashSet;
020    import java.util.LinkedList;
021    import java.util.List;
022    import java.util.Set;
023    import java.util.regex.Matcher;
024    import java.util.regex.Pattern;
025    
026    import javax.swing.JFileChooser;
027    import javax.swing.JOptionPane;
028    import javax.swing.SwingUtilities;
029    import javax.swing.filechooser.FileFilter;
030    
031    import org.openstreetmap.josm.Main;
032    import org.openstreetmap.josm.gui.HelpAwareOptionPane;
033    import org.openstreetmap.josm.gui.PleaseWaitRunnable;
034    import org.openstreetmap.josm.gui.help.HelpUtil;
035    import org.openstreetmap.josm.io.AllFormatsImporter;
036    import org.openstreetmap.josm.io.FileImporter;
037    import org.openstreetmap.josm.io.OsmTransferException;
038    import org.openstreetmap.josm.tools.MultiMap;
039    import org.openstreetmap.josm.tools.Shortcut;
040    import org.xml.sax.SAXException;
041    
042    /**
043     * Open a file chooser dialog and select an file to import. Then call the gpx-import driver. Finally
044     * open an internal frame into the main window with the gpx data shown.
045     *
046     * @author imi
047     */
048    public class OpenFileAction extends DiskAccessAction {
049    
050        public static final ExtensionFileFilter urlFileFilter = new ExtensionFileFilter("url", "url", tr("URL Files") + " (*.url)");
051    
052        /**
053         * Create an open action. The name is "Open a file".
054         */
055        public OpenFileAction() {
056            super(tr("Open..."), "open", tr("Open a file."),
057                    Shortcut.registerShortcut("system:open", tr("File: {0}", tr("Open...")), KeyEvent.VK_O, Shortcut.CTRL));
058            putValue("help", ht("/Action/Open"));
059    
060        }
061    
062        public void actionPerformed(ActionEvent e) {
063            JFileChooser fc = createAndOpenFileChooser(true, true, null);
064            if (fc == null)
065                return;
066            File[] files = fc.getSelectedFiles();
067            OpenFileTask task = new OpenFileTask(Arrays.asList(files), fc.getFileFilter());
068            task.setRecordHistory(true);
069            Main.worker.submit(task);
070        }
071    
072        @Override
073        protected void updateEnabledState() {
074            setEnabled(! Main.applet);
075        }
076    
077        /**
078         * Open a list of files. The complete list will be passed to batch importers.
079         * @param fileList A list of files
080         */
081        static public void openFiles(List<File> fileList) {
082            openFiles(fileList, false);
083        }
084    
085        static public void openFiles(List<File> fileList, boolean recordHistory) {
086            OpenFileTask task = new OpenFileTask(fileList, null);
087            task.setRecordHistory(recordHistory);
088            Main.worker.submit(task);
089        }
090    
091        static public class OpenFileTask extends PleaseWaitRunnable {
092            private List<File> files;
093            private List<File> successfullyOpenedFiles = new ArrayList<File>();
094            private FileFilter fileFilter;
095            private boolean canceled;
096            private boolean recordHistory = false;
097    
098            public OpenFileTask(List<File> files, FileFilter fileFilter, String title) {
099                super(title, false /* don't ignore exception */);
100                this.files = new ArrayList<File>(files);
101                this.fileFilter = fileFilter;
102            }
103    
104            public OpenFileTask(List<File> files, FileFilter fileFilter) {
105                this(files, fileFilter, tr("Opening files"));
106            }
107    
108            /**
109             * save filename in history (for list of recently opened files)
110             * default: false
111             */
112            public void setRecordHistory(boolean recordHistory) {
113                this.recordHistory = recordHistory;
114            }
115    
116            public boolean isRecordHistory() {
117                return recordHistory;
118            }
119    
120            @Override
121            protected void cancel() {
122                this.canceled = true;
123            }
124    
125            @Override
126            protected void finish() {
127                // do nothing
128            }
129    
130            protected void alertFilesNotMatchingWithImporter(Collection<File> files, FileImporter importer) {
131                final StringBuffer msg = new StringBuffer();
132                msg.append("<html>");
133                msg.append(
134                        trn(
135                                "Cannot open {0} file with the file importer ''{1}''.",
136                                "Cannot open {0} files with the file importer ''{1}''.",
137                                files.size(),
138                                files.size(),
139                                importer.filter.getDescription()
140                        )
141                ).append("<br>");
142                msg.append("<ul>");
143                for (File f: files) {
144                    msg.append("<li>").append(f.getAbsolutePath()).append("</li>");
145                }
146                msg.append("</ul>");
147    
148                HelpAwareOptionPane.showMessageDialogInEDT(
149                        Main.parent,
150                        msg.toString(),
151                        tr("Warning"),
152                        JOptionPane.WARNING_MESSAGE,
153                        HelpUtil.ht("/Action/Open#ImporterCantImportFiles")
154                );
155            }
156    
157            protected void alertFilesWithUnknownImporter(Collection<File> files) {
158                final StringBuffer msg = new StringBuffer();
159                msg.append("<html>");
160                msg.append(
161                        trn(
162                                "Cannot open {0} file because file does not exist or no suitable file importer is available.",
163                                "Cannot open {0} files because files do not exist or no suitable file importer is available.",
164                                files.size(),
165                                files.size()
166                        )
167                ).append("<br>");
168                msg.append("<ul>");
169                for (File f: files) {
170                    msg.append("<li>");
171                    msg.append(f.getAbsolutePath());
172                    msg.append(" (<i>");
173                    msg.append(f.exists() ? tr("no importer") : tr("does not exist"));
174                    msg.append("</i>)</li>");
175                }
176                msg.append("</ul>");
177    
178                HelpAwareOptionPane.showMessageDialogInEDT(
179                        Main.parent,
180                        msg.toString(),
181                        tr("Warning"),
182                        JOptionPane.WARNING_MESSAGE,
183                        HelpUtil.ht("/Action/Open#MissingImporterForFiles")
184                );
185            }
186    
187            @Override
188            protected void realRun() throws SAXException, IOException, OsmTransferException {
189                if (files == null || files.isEmpty()) return;
190    
191                /**
192                 * Find the importer with the chosen file filter
193                 */
194                FileImporter chosenImporter = null;
195                for (FileImporter importer : ExtensionFileFilter.importers) {
196                    if (fileFilter == importer.filter) {
197                        chosenImporter = importer;
198                    }
199                }
200                /**
201                 * If the filter hasn't been changed in the dialog, chosenImporter is null now.
202                 * When the filter has been set explicitly to AllFormatsImporter, treat this the same.
203                 */
204                if (chosenImporter instanceof AllFormatsImporter) {
205                    chosenImporter = null;
206                }
207                getProgressMonitor().setTicksCount(files.size());
208    
209                if (chosenImporter != null) {
210                    // The importer was explicitly chosen, so use it.
211                    List<File> filesNotMatchingWithImporter = new LinkedList<File>();
212                    List<File> filesMatchingWithImporter = new LinkedList<File>();
213                    for (final File f : files) {
214                        if (!chosenImporter.acceptFile(f)) {
215                            if (f.isDirectory()) {
216                                SwingUtilities.invokeLater(new Runnable() {
217                                    public void run() {
218                                        JOptionPane.showMessageDialog(Main.parent, tr(
219                                                "<html>Cannot open directory ''{0}''.<br>Please select a file.</html>",
220                                                f.getAbsolutePath()), tr("Open file"), JOptionPane.ERROR_MESSAGE);
221                                    }
222                                });
223                                // TODO when changing to Java 6: Don't cancel the
224                                // task here but use different modality. (Currently 2 dialogs
225                                // would block each other.)
226                                return;
227                            } else {
228                                filesNotMatchingWithImporter.add(f);
229                            }
230                        } else {
231                            filesMatchingWithImporter.add(f);
232                        }
233                    }
234    
235                    if (!filesNotMatchingWithImporter.isEmpty()) {
236                        alertFilesNotMatchingWithImporter(filesNotMatchingWithImporter, chosenImporter);
237                    }
238                    if (!filesMatchingWithImporter.isEmpty()) {
239                        importData(chosenImporter, filesMatchingWithImporter);
240                    }
241                } else {
242                    // find appropriate importer
243                    MultiMap<FileImporter, File> importerMap = new MultiMap<FileImporter, File>();
244                    List<File> filesWithUnknownImporter = new LinkedList<File>();
245                    List<File> urlFiles = new LinkedList<File>();
246                    FILES: for (File f : files) {
247                        for (FileImporter importer : ExtensionFileFilter.importers) {
248                            if (importer.acceptFile(f)) {
249                                importerMap.put(importer, f);
250                                continue FILES;
251                            }
252                        }
253                        if (urlFileFilter.accept(f)) {
254                            urlFiles.add(f);
255                        } else {
256                            filesWithUnknownImporter.add(f);
257                        }
258                    }
259                    if (!filesWithUnknownImporter.isEmpty()) {
260                        alertFilesWithUnknownImporter(filesWithUnknownImporter);
261                    }
262                    List<FileImporter> importers = new ArrayList<FileImporter>(importerMap.keySet());
263                    Collections.sort(importers);
264                    Collections.reverse(importers);
265    
266                    Set<String> fileHistory = new LinkedHashSet<String>();
267                    Set<String> failedAll = new HashSet<String>();
268    
269                    for (FileImporter importer : importers) {
270                        List<File> files = new ArrayList<File>(importerMap.get(importer));
271                        importData(importer, files);
272                        // suppose all files will fail to load
273                        List<File> failedFiles = new ArrayList<File>(files);
274    
275                        if (recordHistory && !importer.isBatchImporter()) {
276                            // remove the files which didn't fail to load from the failed list
277                            failedFiles.removeAll(successfullyOpenedFiles);
278                            for (File f : successfullyOpenedFiles) {
279                                fileHistory.add(f.getCanonicalPath());
280                            }
281                            for (File f : failedFiles) {
282                                failedAll.add(f.getCanonicalPath());
283                            }
284                        }
285                    }
286                    
287                    for (File urlFile: urlFiles) {
288                        try {
289                            BufferedReader reader = new BufferedReader(new FileReader(urlFile));
290                            String line;
291                            while ((line = reader.readLine()) != null) {
292                                Matcher m = Pattern.compile(".*(http://.*)").matcher(line);
293                                if (m.matches()) {
294                                    String url = m.group(1);
295                                    Main.main.menu.openLocation.openUrl(false, url);
296                                }
297                            }
298                            reader.close();
299                        } catch (Exception e) {
300                            System.err.println(e.getMessage());
301                        }
302                    }
303    
304                    if (recordHistory) {
305                        Collection<String> oldFileHistory = Main.pref.getCollection("file-open.history");
306                        fileHistory.addAll(oldFileHistory);
307                        // remove the files which failed to load from the list
308                        fileHistory.removeAll(failedAll);
309                        int maxsize = Math.max(0, Main.pref.getInteger("file-open.history.max-size", 15));
310                        Main.pref.putCollectionBounded("file-open.history", maxsize, fileHistory);
311                    }
312                }
313            }
314    
315            public void importData(FileImporter importer, List<File> files) {
316                if (importer.isBatchImporter()) {
317                    if (canceled) return;
318                    String msg = trn("Opening {0} file...", "Opening {0} files...", files.size(), files.size());
319                    getProgressMonitor().setCustomText(msg);
320                    getProgressMonitor().indeterminateSubTask(msg);
321                    if (importer.importDataHandleExceptions(files, getProgressMonitor().createSubTaskMonitor(files.size(), false))) {
322                        successfullyOpenedFiles.addAll(files);
323                    }
324                } else {
325                    for (File f : files) {
326                        if (canceled) return;
327                        getProgressMonitor().indeterminateSubTask(tr("Opening file ''{0}'' ...", f.getAbsolutePath()));
328                        if (importer.importDataHandleExceptions(f, getProgressMonitor().createSubTaskMonitor(1, false))) {
329                            successfullyOpenedFiles.add(f);
330                        }
331                    }
332                }
333            }
334    
335            public List<File> getSuccessfullyOpenedFiles() {
336                return successfullyOpenedFiles;
337            }
338        }
339    }