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.io.OutputStream; 009import java.io.OutputStreamWriter; 010import java.io.PrintWriter; 011import java.io.Writer; 012import java.nio.charset.StandardCharsets; 013import java.nio.file.AccessDeniedException; 014import java.nio.file.InvalidPathException; 015import java.text.MessageFormat; 016 017import javax.swing.JOptionPane; 018 019import org.openstreetmap.josm.actions.ExtensionFileFilter; 020import org.openstreetmap.josm.gui.MainApplication; 021import org.openstreetmap.josm.gui.layer.Layer; 022import org.openstreetmap.josm.gui.layer.OsmDataLayer; 023import org.openstreetmap.josm.io.Compression; 024import org.openstreetmap.josm.io.OsmWriter; 025import org.openstreetmap.josm.io.OsmWriterFactory; 026import org.openstreetmap.josm.spi.preferences.Config; 027import org.openstreetmap.josm.tools.Logging; 028import org.openstreetmap.josm.tools.Utils; 029 030/** 031 * Exports data to an .osm file. 032 * @since 1949 033 */ 034public class OsmExporter extends FileExporter { 035 036 /** 037 * Constructs a new {@code OsmExporter}. 038 */ 039 public OsmExporter() { 040 super(new ExtensionFileFilter( 041 "osm,xml", "osm", tr("OSM Server Files") + " (*.osm)")); 042 } 043 044 /** 045 * Constructs a new {@code OsmExporter}. 046 * @param filter The extension file filter 047 */ 048 public OsmExporter(ExtensionFileFilter filter) { 049 super(filter); 050 } 051 052 @Override 053 public boolean acceptFile(File pathname, Layer layer) { 054 if (!(layer instanceof OsmDataLayer)) 055 return false; 056 return super.acceptFile(pathname, layer); 057 } 058 059 @Override 060 public void exportData(File file, Layer layer) throws IOException { 061 exportData(file, layer, false); 062 } 063 064 /** 065 * Exports OSM data to the given file. 066 * @param file Output file 067 * @param layer Data layer. Must be an instance of {@link OsmDataLayer}. 068 * @param isAutosave if {@code true}, the potential backup file created if the output file already exists will be deleted 069 * after a successful export and post-save events won't be fired 070 * @throws IOException in case of IO errors 071 * @throws InvalidPathException when file name cannot be converted into a Path 072 * @throws IllegalArgumentException if {@code layer} is not an instance of {@code OsmDataLayer} 073 */ 074 public void exportData(File file, Layer layer, boolean isAutosave) throws IOException { 075 if (!(layer instanceof OsmDataLayer)) { 076 throw new IllegalArgumentException( 077 MessageFormat.format("Expected instance of OsmDataLayer. Got ''{0}''.", layer.getClass().getName())); 078 } 079 save(file, (OsmDataLayer) layer, isAutosave); 080 } 081 082 protected static OutputStream getOutputStream(File file) throws IOException { 083 return Compression.getCompressedFileOutputStream(file); 084 } 085 086 private void save(File file, OsmDataLayer layer, boolean isAutosave) throws IOException { 087 File tmpFile = null; 088 try { 089 if (file.exists() && !file.canWrite()) { 090 throw new AccessDeniedException(file.toString()); 091 } 092 093 // use a tmp file because if something errors out in the process of writing the file, 094 // we might just end up with a truncated file. That can destroy lots of work. 095 if (file.exists()) { 096 tmpFile = new File(file.getPath() + '~'); 097 Utils.copyFile(file, tmpFile); 098 } 099 100 doSave(file, layer); 101 if ((isAutosave || !Config.getPref().getBoolean("save.keepbackup", false)) && tmpFile != null) { 102 Utils.deleteFile(tmpFile); 103 } 104 if (!isAutosave) { 105 layer.onPostSaveToFile(); 106 } 107 } catch (IOException | InvalidPathException e) { 108 Logging.error(e); 109 110 try { 111 // if the file save failed, then the tempfile will not be deleted. So, restore the backup if we made one. 112 if (tmpFile != null && tmpFile.exists()) { 113 Utils.copyFile(tmpFile, file); 114 } 115 } catch (IOException | InvalidPathException e2) { 116 Logging.error(e2); 117 JOptionPane.showMessageDialog( 118 MainApplication.getMainFrame(), 119 tr("<html>An error occurred while restoring backup file.<br>Error is:<br>{0}</html>", 120 Utils.escapeReservedCharactersHTML(e2.getMessage())), 121 tr("Error"), 122 JOptionPane.ERROR_MESSAGE 123 ); 124 } 125 // re-throw original error 126 throw e; 127 } 128 } 129 130 protected void doSave(File file, OsmDataLayer layer) throws IOException { 131 // create outputstream and wrap it with gzip, xz or bzip, if necessary 132 try ( 133 OutputStream out = getOutputStream(file); 134 Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); 135 OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, layer.data.getVersion()) 136 ) { 137 layer.data.getReadLock().lock(); 138 try { 139 w.write(layer.data); 140 } finally { 141 layer.data.getReadLock().unlock(); 142 } 143 } 144 } 145}