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