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                "org.openstreetmap.josm.io.NoteExporter"
101        };
102
103        for (String classname : exporterNames) {
104            try {
105                FileExporter exporter = (FileExporter)Class.forName(classname).newInstance();
106                exporters.add(exporter);
107                MapView.addLayerChangeListener(exporter);
108            } catch (Exception e) {
109                if (Main.isDebugEnabled()) {
110                    Main.debug(e.getMessage());
111                }
112            } catch (ServiceConfigurationError e) {
113                // see above in importers initialization
114                Main.error(e);
115            }
116        }
117    }
118
119    private final String extensions;
120    private final String description;
121    private final String defaultExtension;
122
123    protected static void sort(List<ExtensionFileFilter> filters) {
124        Collections.sort(
125                filters,
126                new Comparator<ExtensionFileFilter>() {
127                    private AllFormatsImporter all = new AllFormatsImporter();
128                    @Override
129                    public int compare(ExtensionFileFilter o1, ExtensionFileFilter o2) {
130                        if (o1.getDescription().equals(all.filter.getDescription())) return 1;
131                        if (o2.getDescription().equals(all.filter.getDescription())) return -1;
132                        return o1.getDescription().compareTo(o2.getDescription());
133                    }
134                }
135        );
136    }
137
138    /**
139     * Updates the {@link AllFormatsImporter} that is contained in the importers list. If
140     * you do not use the importers variable directly, you don’t need to call this.
141     * <p>
142     * Updating the AllFormatsImporter is required when plugins add new importers that
143     * support new file extensions. The old AllFormatsImporter doesn’t include the new
144     * extensions and thus will not display these files.
145     *
146     * @since 5131
147     */
148    public static void updateAllFormatsImporter() {
149        for(int i=0; i < importers.size(); i++) {
150            if(importers.get(i) instanceof AllFormatsImporter) {
151                importers.set(i, new AllFormatsImporter());
152            }
153        }
154    }
155
156    /**
157     * Replies an ordered list of {@link ExtensionFileFilter}s for importing.
158     * The list is ordered according to their description, an {@link AllFormatsImporter}
159     * is append at the end.
160     *
161     * @return an ordered list of {@link ExtensionFileFilter}s for importing.
162     * @since 2029
163     */
164    public static List<ExtensionFileFilter> getImportExtensionFileFilters() {
165        updateAllFormatsImporter();
166        LinkedList<ExtensionFileFilter> filters = new LinkedList<>();
167        for (FileImporter importer : importers) {
168            filters.add(importer.filter);
169        }
170        sort(filters);
171        return filters;
172    }
173
174    /**
175     * Replies an ordered list of enabled {@link ExtensionFileFilter}s for exporting.
176     * The list is ordered according to their description, an {@link AllFormatsImporter}
177     * is append at the end.
178     *
179     * @return an ordered list of enabled {@link ExtensionFileFilter}s for exporting.
180     * @since 2029
181     */
182    public static List<ExtensionFileFilter> getExportExtensionFileFilters() {
183        LinkedList<ExtensionFileFilter> filters = new LinkedList<>();
184        for (FileExporter exporter : exporters) {
185            if (filters.contains(exporter.filter) || !exporter.isEnabled()) {
186                continue;
187            }
188            filters.add(exporter.filter);
189        }
190        sort(filters);
191        return filters;
192    }
193
194    /**
195     * Replies the default {@link ExtensionFileFilter} for a given extension
196     *
197     * @param extension the extension
198     * @return the default {@link ExtensionFileFilter} for a given extension
199     * @since 2029
200     */
201    public static ExtensionFileFilter getDefaultImportExtensionFileFilter(String extension) {
202        if (extension == null) return new AllFormatsImporter().filter;
203        for (FileImporter importer : importers) {
204            if (extension.equals(importer.filter.getDefaultExtension()))
205                return importer.filter;
206        }
207        return new AllFormatsImporter().filter;
208    }
209
210    /**
211     * Replies the default {@link ExtensionFileFilter} for a given extension
212     *
213     * @param extension the extension
214     * @return the default {@link ExtensionFileFilter} for a given extension
215     * @since 2029
216     */
217    public static ExtensionFileFilter getDefaultExportExtensionFileFilter(String extension) {
218        if (extension == null) return new AllFormatsImporter().filter;
219        for (FileExporter exporter : exporters) {
220            if (extension.equals(exporter.filter.getDefaultExtension()))
221                return exporter.filter;
222        }
223        return new AllFormatsImporter().filter;
224    }
225
226    /**
227     * Applies the choosable {@link FileFilter} to a {@link AbstractFileChooser} before using the
228     * file chooser for selecting a file for reading.
229     *
230     * @param fileChooser the file chooser
231     * @param extension the default extension
232     * @param allTypes If true, all the files types known by JOSM will be proposed in the "file type" combobox.
233     *                 If false, only the file filters that include {@code extension} will be proposed
234     * @since 5438
235     */
236    public static void applyChoosableImportFileFilters(AbstractFileChooser fileChooser, String extension, boolean allTypes) {
237        for (ExtensionFileFilter filter: getImportExtensionFileFilters()) {
238            if (allTypes || filter.acceptName("file."+extension)) {
239                fileChooser.addChoosableFileFilter(filter);
240            }
241        }
242        fileChooser.setFileFilter(getDefaultImportExtensionFileFilter(extension));
243    }
244
245    /**
246     * Applies the choosable {@link FileFilter} to a {@link AbstractFileChooser} before using the
247     * file chooser for selecting a file for writing.
248     *
249     * @param fileChooser the file chooser
250     * @param extension the default extension
251     * @param allTypes If true, all the files types known by JOSM will be proposed in the "file type" combobox.
252     *                 If false, only the file filters that include {@code extension} will be proposed
253     * @since 5438
254     */
255    public static void applyChoosableExportFileFilters(AbstractFileChooser fileChooser, String extension, boolean allTypes) {
256        for (ExtensionFileFilter filter: getExportExtensionFileFilters()) {
257            if (allTypes || filter.acceptName("file."+extension)) {
258                fileChooser.addChoosableFileFilter(filter);
259            }
260        }
261        fileChooser.setFileFilter(getDefaultExportExtensionFileFilter(extension));
262    }
263
264    /**
265     * Construct an extension file filter by giving the extension to check after.
266     * @param extension The comma-separated list of file extensions
267     * @param defaultExtension The default extension
268     * @param description A short textual description of the file type
269     * @since 1169
270     */
271    public ExtensionFileFilter(String extension, String defaultExtension, String description) {
272        this.extensions = extension;
273        this.defaultExtension = defaultExtension;
274        this.description = description;
275    }
276
277    /**
278     * Returns true if this file filter accepts the given filename.
279     * @param filename The filename to check after
280     * @return true if this file filter accepts the given filename (i.e if this filename ends with one of the extensions)
281     * @since 1169
282     */
283    public boolean acceptName(String filename) {
284        String name = filename.toLowerCase();
285        for (String ext : extensions.split(","))
286            if (name.endsWith("."+ext))
287                return true;
288        return false;
289    }
290
291    @Override
292    public boolean accept(File pathname) {
293        if (pathname.isDirectory())
294            return true;
295        return acceptName(pathname.getName());
296    }
297
298    @Override
299    public String getDescription() {
300        return description;
301    }
302
303    /**
304     * Replies the comma-separated list of file extensions of this file filter.
305     * @return the comma-separated list of file extensions of this file filter, as a String
306     * @since 5131
307     */
308    public String getExtensions() {
309        return extensions;
310    }
311
312    /**
313     * Replies the default file extension of this file filter.
314     * @return the default file extension of this file filter
315     * @since 2029
316     */
317    public String getDefaultExtension() {
318        return defaultExtension;
319    }
320
321    @Override
322    public int hashCode() {
323        final int prime = 31;
324        int result = 1;
325        result = prime * result + ((defaultExtension == null) ? 0 : defaultExtension.hashCode());
326        result = prime * result + ((description == null) ? 0 : description.hashCode());
327        result = prime * result + ((extensions == null) ? 0 : extensions.hashCode());
328        return result;
329    }
330
331    @Override
332    public boolean equals(Object obj) {
333        if (this == obj)
334            return true;
335        if (obj == null)
336            return false;
337        if (getClass() != obj.getClass())
338            return false;
339        ExtensionFileFilter other = (ExtensionFileFilter) obj;
340        if (defaultExtension == null) {
341            if (other.defaultExtension != null)
342                return false;
343        } else if (!defaultExtension.equals(other.defaultExtension))
344            return false;
345        if (description == null) {
346            if (other.description != null)
347                return false;
348        } else if (!description.equals(other.description))
349            return false;
350        if (extensions == null) {
351            if (other.extensions != null)
352                return false;
353        } else if (!extensions.equals(other.extensions))
354            return false;
355        return true;
356    }
357}