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