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