001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import java.io.File;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.Comparator;
008import java.util.LinkedList;
009import java.util.List;
010import java.util.ServiceConfigurationError;
011
012import javax.swing.filechooser.FileFilter;
013
014import org.openstreetmap.josm.Main;
015import org.openstreetmap.josm.gui.MapView;
016import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
017import org.openstreetmap.josm.io.AllFormatsImporter;
018import org.openstreetmap.josm.io.FileExporter;
019import org.openstreetmap.josm.io.FileImporter;
020
021/**
022 * A file filter that filters after the extension. Also includes a list of file
023 * filters used in JOSM.
024 * @since 32
025 */
026public class ExtensionFileFilter extends FileFilter implements java.io.FileFilter {
027
028    /**
029     * List of supported formats for import.
030     * @since 4869
031     */
032    public static final ArrayList<FileImporter> importers;
033
034    /**
035     * List of supported formats for export.
036     * @since 4869
037     */
038    public static final ArrayList<FileExporter> exporters;
039
040    // add some file types only if the relevant classes are there.
041    // this gives us the option to painlessly drop them from the .jar
042    // and build JOSM versions without support for these formats
043
044    static {
045
046        importers = new ArrayList<>();
047
048        String[] importerNames = {
049                "org.openstreetmap.josm.io.OsmImporter",
050                "org.openstreetmap.josm.io.OsmGzipImporter",
051                "org.openstreetmap.josm.io.OsmZipImporter",
052                "org.openstreetmap.josm.io.OsmChangeImporter",
053                "org.openstreetmap.josm.io.GpxImporter",
054                "org.openstreetmap.josm.io.NMEAImporter",
055                "org.openstreetmap.josm.io.NoteImporter",
056                "org.openstreetmap.josm.io.OsmBzip2Importer",
057                "org.openstreetmap.josm.io.JpgImporter",
058                "org.openstreetmap.josm.io.WMSLayerImporter",
059                "org.openstreetmap.josm.io.AllFormatsImporter",
060                "org.openstreetmap.josm.io.session.SessionImporter"
061        };
062
063        for (String classname : importerNames) {
064            try {
065                FileImporter importer = (FileImporter) Class.forName(classname).newInstance();
066                importers.add(importer);
067                MapView.addLayerChangeListener(importer);
068            } catch (Exception e) {
069                if (Main.isDebugEnabled()) {
070                    Main.debug(e.getMessage());
071                }
072            } catch (ServiceConfigurationError e) {
073                // error seen while initializing WMSLayerImporter in plugin unit tests:
074                // -
075                // ServiceConfigurationError: javax.imageio.spi.ImageWriterSpi:
076                // Provider com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageWriterSpi could not be instantiated
077                // Caused by: java.lang.IllegalArgumentException: vendorName == null!
078                //      at javax.imageio.spi.IIOServiceProvider.<init>(IIOServiceProvider.java:76)
079                //      at javax.imageio.spi.ImageReaderWriterSpi.<init>(ImageReaderWriterSpi.java:231)
080                //      at javax.imageio.spi.ImageWriterSpi.<init>(ImageWriterSpi.java:213)
081                //      at com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageWriterSpi.<init>(CLibJPEGImageWriterSpi.java:84)
082                // -
083                // This is a very strange behaviour of JAI:
084                // http://thierrywasyl.wordpress.com/2009/07/24/jai-how-to-solve-vendorname-null-exception/
085                // -
086                // that can lead to various problems, see #8583 comments
087                Main.error(e);
088            }
089        }
090
091        exporters = new ArrayList<>();
092
093        String[] exporterNames = {
094                "org.openstreetmap.josm.io.GpxExporter",
095                "org.openstreetmap.josm.io.OsmExporter",
096                "org.openstreetmap.josm.io.OsmGzipExporter",
097                "org.openstreetmap.josm.io.OsmBzip2Exporter",
098                "org.openstreetmap.josm.io.GeoJSONExporter",
099                "org.openstreetmap.josm.io.WMSLayerExporter"
100        };
101
102        for (String classname : exporterNames) {
103            try {
104                FileExporter exporter = (FileExporter)Class.forName(classname).newInstance();
105                exporters.add(exporter);
106                MapView.addLayerChangeListener(exporter);
107            } catch (Exception e) {
108                if (Main.isDebugEnabled()) {
109                    Main.debug(e.getMessage());
110                }
111            } catch (ServiceConfigurationError e) {
112                // see above in importers initialization
113                Main.error(e);
114            }
115        }
116    }
117
118    private final String extensions;
119    private final String description;
120    private final String defaultExtension;
121
122    protected static void sort(List<ExtensionFileFilter> filters) {
123        Collections.sort(
124                filters,
125                new Comparator<ExtensionFileFilter>() {
126                    private AllFormatsImporter all = new AllFormatsImporter();
127                    @Override
128                    public int compare(ExtensionFileFilter o1, ExtensionFileFilter o2) {
129                        if (o1.getDescription().equals(all.filter.getDescription())) return 1;
130                        if (o2.getDescription().equals(all.filter.getDescription())) return -1;
131                        return o1.getDescription().compareTo(o2.getDescription());
132                    }
133                }
134        );
135    }
136
137    /**
138     * Updates the {@link AllFormatsImporter} that is contained in the importers list. If
139     * you do not use the importers variable directly, you don’t need to call this.
140     * <p>
141     * Updating the AllFormatsImporter is required when plugins add new importers that
142     * support new file extensions. The old AllFormatsImporter doesn’t include the new
143     * extensions and thus will not display these files.
144     *
145     * @since 5131
146     */
147    public static void updateAllFormatsImporter() {
148        for(int i=0; i < importers.size(); i++) {
149            if(importers.get(i) instanceof AllFormatsImporter) {
150                importers.set(i, new AllFormatsImporter());
151            }
152        }
153    }
154
155    /**
156     * Replies an ordered list of {@link ExtensionFileFilter}s for importing.
157     * The list is ordered according to their description, an {@link AllFormatsImporter}
158     * is append at the end.
159     *
160     * @return an ordered list of {@link ExtensionFileFilter}s for importing.
161     * @since 2029
162     */
163    public static List<ExtensionFileFilter> getImportExtensionFileFilters() {
164        updateAllFormatsImporter();
165        LinkedList<ExtensionFileFilter> filters = new LinkedList<>();
166        for (FileImporter importer : importers) {
167            filters.add(importer.filter);
168        }
169        sort(filters);
170        return filters;
171    }
172
173    /**
174     * Replies an ordered list of enabled {@link ExtensionFileFilter}s for exporting.
175     * The list is ordered according to their description, an {@link AllFormatsImporter}
176     * is append at the end.
177     *
178     * @return an ordered list of enabled {@link ExtensionFileFilter}s for exporting.
179     * @since 2029
180     */
181    public static List<ExtensionFileFilter> getExportExtensionFileFilters() {
182        LinkedList<ExtensionFileFilter> filters = new LinkedList<>();
183        for (FileExporter exporter : exporters) {
184            if (filters.contains(exporter.filter) || !exporter.isEnabled()) {
185                continue;
186            }
187            filters.add(exporter.filter);
188        }
189        sort(filters);
190        return filters;
191    }
192
193    /**
194     * Replies the default {@link ExtensionFileFilter} for a given extension
195     *
196     * @param extension the extension
197     * @return the default {@link ExtensionFileFilter} for a given extension
198     * @since 2029
199     */
200    public static ExtensionFileFilter getDefaultImportExtensionFileFilter(String extension) {
201        if (extension == null) return new AllFormatsImporter().filter;
202        for (FileImporter importer : importers) {
203            if (extension.equals(importer.filter.getDefaultExtension()))
204                return importer.filter;
205        }
206        return new AllFormatsImporter().filter;
207    }
208
209    /**
210     * Replies the default {@link ExtensionFileFilter} for a given extension
211     *
212     * @param extension the extension
213     * @return the default {@link ExtensionFileFilter} for a given extension
214     * @since 2029
215     */
216    public static ExtensionFileFilter getDefaultExportExtensionFileFilter(String extension) {
217        if (extension == null) return new AllFormatsImporter().filter;
218        for (FileExporter exporter : exporters) {
219            if (extension.equals(exporter.filter.getDefaultExtension()))
220                return exporter.filter;
221        }
222        return new AllFormatsImporter().filter;
223    }
224
225    /**
226     * Applies the choosable {@link FileFilter} to a {@link AbstractFileChooser} before using the
227     * file chooser for selecting a file for reading.
228     *
229     * @param fileChooser the file chooser
230     * @param extension the default extension
231     * @param allTypes If true, all the files types known by JOSM will be proposed in the "file type" combobox.
232     *                 If false, only the file filters that include {@code extension} will be proposed
233     * @since 5438
234     */
235    public static void applyChoosableImportFileFilters(AbstractFileChooser fileChooser, String extension, boolean allTypes) {
236        for (ExtensionFileFilter filter: getImportExtensionFileFilters()) {
237            if (allTypes || filter.acceptName("file."+extension)) {
238                fileChooser.addChoosableFileFilter(filter);
239            }
240        }
241        fileChooser.setFileFilter(getDefaultImportExtensionFileFilter(extension));
242    }
243
244    /**
245     * Applies the choosable {@link FileFilter} to a {@link AbstractFileChooser} before using the
246     * file chooser for selecting a file for writing.
247     *
248     * @param fileChooser the file chooser
249     * @param extension the default extension
250     * @param allTypes If true, all the files types known by JOSM will be proposed in the "file type" combobox.
251     *                 If false, only the file filters that include {@code extension} will be proposed
252     * @since 5438
253     */
254    public static void applyChoosableExportFileFilters(AbstractFileChooser fileChooser, String extension, boolean allTypes) {
255        for (ExtensionFileFilter filter: getExportExtensionFileFilters()) {
256            if (allTypes || filter.acceptName("file."+extension)) {
257                fileChooser.addChoosableFileFilter(filter);
258            }
259        }
260        fileChooser.setFileFilter(getDefaultExportExtensionFileFilter(extension));
261    }
262
263    /**
264     * Construct an extension file filter by giving the extension to check after.
265     * @param extension The comma-separated list of file extensions
266     * @param defaultExtension The default extension
267     * @param description A short textual description of the file type
268     * @since 1169
269     */
270    public ExtensionFileFilter(String extension, String defaultExtension, String description) {
271        this.extensions = extension;
272        this.defaultExtension = defaultExtension;
273        this.description = description;
274    }
275
276    /**
277     * Returns true if this file filter accepts the given filename.
278     * @param filename The filename to check after
279     * @return true if this file filter accepts the given filename (i.e if this filename ends with one of the extensions)
280     * @since 1169
281     */
282    public boolean acceptName(String filename) {
283        String name = filename.toLowerCase();
284        for (String ext : extensions.split(","))
285            if (name.endsWith("."+ext))
286                return true;
287        return false;
288    }
289
290    @Override
291    public boolean accept(File pathname) {
292        if (pathname.isDirectory())
293            return true;
294        return acceptName(pathname.getName());
295    }
296
297    @Override
298    public String getDescription() {
299        return description;
300    }
301
302    /**
303     * Replies the comma-separated list of file extensions of this file filter.
304     * @return the comma-separated list of file extensions of this file filter, as a String
305     * @since 5131
306     */
307    public String getExtensions() {
308        return extensions;
309    }
310
311    /**
312     * Replies the default file extension of this file filter.
313     * @return the default file extension of this file filter
314     * @since 2029
315     */
316    public String getDefaultExtension() {
317        return defaultExtension;
318    }
319
320    @Override
321    public int hashCode() {
322        final int prime = 31;
323        int result = 1;
324        result = prime * result + ((defaultExtension == null) ? 0 : defaultExtension.hashCode());
325        result = prime * result + ((description == null) ? 0 : description.hashCode());
326        result = prime * result + ((extensions == null) ? 0 : extensions.hashCode());
327        return result;
328    }
329
330    @Override
331    public boolean equals(Object obj) {
332        if (this == obj)
333            return true;
334        if (obj == null)
335            return false;
336        if (getClass() != obj.getClass())
337            return false;
338        ExtensionFileFilter other = (ExtensionFileFilter) obj;
339        if (defaultExtension == null) {
340            if (other.defaultExtension != null)
341                return false;
342        } else if (!defaultExtension.equals(other.defaultExtension))
343            return false;
344        if (description == null) {
345            if (other.description != null)
346                return false;
347        } else if (!description.equals(other.description))
348            return false;
349        if (extensions == null) {
350            if (other.extensions != null)
351                return false;
352        } else if (!extensions.equals(other.extensions))
353            return false;
354        return true;
355    }
356}