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->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->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}