001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.io.importexport;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.io.File;
007import java.io.IOException;
008import java.util.List;
009
010import javax.swing.JOptionPane;
011
012import org.openstreetmap.josm.actions.ExtensionFileFilter;
013import org.openstreetmap.josm.gui.HelpAwareOptionPane;
014import org.openstreetmap.josm.gui.MainApplication;
015import org.openstreetmap.josm.gui.Notification;
016import org.openstreetmap.josm.gui.progress.ProgressMonitor;
017import org.openstreetmap.josm.gui.util.GuiHelper;
018import org.openstreetmap.josm.io.IllegalDataException;
019import org.openstreetmap.josm.io.ImportCancelException;
020import org.openstreetmap.josm.tools.Logging;
021import org.openstreetmap.josm.tools.Utils;
022import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
023
024/**
025 * Abstract file importer.
026 * @since 1637
027 * @since 10386 (signature)
028 */
029public abstract class FileImporter implements Comparable<FileImporter> {
030
031    /**
032     * The extension file filter used to accept files.
033     */
034    public final ExtensionFileFilter filter;
035
036    private boolean enabled;
037
038    /**
039     * Constructs a new {@code FileImporter} with the given extension file filter.
040     * @param filter The extension file filter
041     */
042    public FileImporter(ExtensionFileFilter filter) {
043        this.filter = filter;
044        this.enabled = true;
045    }
046
047    /**
048     * Determines if this file importer accepts the given file.
049     * @param pathname The file to test
050     * @return {@code true} if this file importer accepts the given file, {@code false} otherwise
051     */
052    public boolean acceptFile(File pathname) {
053        return filter.acceptName(pathname.getName());
054    }
055
056    /**
057     * A batch importer is a file importer that prefers to read multiple files at the same time.
058     * @return {@code true} if this importer is a batch importer
059     */
060    public boolean isBatchImporter() {
061        return false;
062    }
063
064    /**
065     * Needs to be implemented if isBatchImporter() returns false.
066     * @param file file to import
067     * @param progressMonitor progress monitor
068     * @throws IOException if any I/O error occurs
069     * @throws IllegalDataException if invalid data is read
070     */
071    public void importData(File file, ProgressMonitor progressMonitor) throws IOException, IllegalDataException {
072        throw new IOException(tr("Could not import ''{0}''.", file.getName()));
073    }
074
075    /**
076     * Needs to be implemented if isBatchImporter() returns true.
077     * @param files files to import
078     * @param progressMonitor progress monitor
079     * @throws IOException if any I/O error occurs
080     * @throws IllegalDataException if invalid data is read
081     */
082    public void importData(List<File> files, ProgressMonitor progressMonitor) throws IOException, IllegalDataException {
083        throw new IOException(tr("Could not import files."));
084    }
085
086    /**
087     * Wrapper to {@link #importData(File, ProgressMonitor)} to give meaningful output if things go wrong.
088     * @param f data file to import
089     * @param progressMonitor progress monitor
090     * @return true if data import was successful
091     */
092    public boolean importDataHandleExceptions(File f, ProgressMonitor progressMonitor) {
093        try {
094            Logging.info("Open file: " + f.getAbsolutePath() + " (" + f.length() + " bytes)");
095            importData(f, progressMonitor);
096            return true;
097        } catch (IllegalDataException | IllegalStateException e) {
098            Throwable cause = e.getCause();
099            if (cause instanceof ImportCancelException) {
100                displayCancel(cause);
101            } else {
102                displayError(f, e);
103            }
104            return false;
105        } catch (IOException e) {
106            displayError(f, e);
107            return false;
108        } catch (RuntimeException | LinkageError e) { // NOPMD
109            BugReportExceptionHandler.handleException(e);
110            return false;
111        }
112    }
113
114    private static void displayError(File f, Exception e) {
115        Logging.error(e);
116        HelpAwareOptionPane.showMessageDialogInEDT(
117                MainApplication.getMainFrame(),
118                tr("<html>Could not read file ''{0}''.<br>Error is:<br>{1}</html>",
119                        f.getName(), Utils.escapeReservedCharactersHTML(e.getMessage())),
120                tr("Error"),
121                JOptionPane.ERROR_MESSAGE, null
122        );
123    }
124
125    private static void displayCancel(final Throwable t) {
126        GuiHelper.runInEDTAndWait(() -> {
127            Notification note = new Notification(t.getMessage());
128            note.setIcon(JOptionPane.INFORMATION_MESSAGE);
129            note.setDuration(Notification.TIME_SHORT);
130            note.show();
131        });
132    }
133
134    /**
135     * Wrapper to {@link #importData(List, ProgressMonitor)} to give meaningful output if things go wrong.
136     * @param files data files to import
137     * @param progressMonitor progress monitor
138     * @return true if data import was successful
139     */
140    public boolean importDataHandleExceptions(List<File> files, ProgressMonitor progressMonitor) {
141        try {
142            Logging.info("Open "+files.size()+" files");
143            importData(files, progressMonitor);
144            return true;
145        } catch (IOException | IllegalDataException e) {
146            Logging.error(e);
147            HelpAwareOptionPane.showMessageDialogInEDT(
148                    MainApplication.getMainFrame(),
149                    tr("<html>Could not read files.<br>Error is:<br>{0}</html>", Utils.escapeReservedCharactersHTML(e.getMessage())),
150                    tr("Error"),
151                    JOptionPane.ERROR_MESSAGE, null
152            );
153            return false;
154        }
155    }
156
157    /**
158     * If multiple files (with multiple file formats) are selected,
159     * they are opened in the order of their priorities.
160     * Highest priority comes first.
161     * @return priority
162     */
163    public double getPriority() {
164        return 0;
165    }
166
167    @Override
168    public int compareTo(FileImporter other) {
169        return Double.compare(this.getPriority(), other.getPriority());
170    }
171
172    /**
173     * Returns the enabled state of this {@code FileImporter}. When enabled, it is listed and usable in "File-&gt;Open" dialog.
174     * @return true if this {@code FileImporter} is enabled
175     * @since 5459
176     */
177    public final boolean isEnabled() {
178        return enabled;
179    }
180
181    /**
182     * Sets the enabled state of the {@code FileImporter}. When enabled, it is listed and usable in "File-&gt;Open" dialog.
183     * @param enabled true to enable this {@code FileImporter}, false to disable it
184     * @since 5459
185     */
186    public final void setEnabled(boolean enabled) {
187        this.enabled = enabled;
188    }
189}