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 noBackup if {@code true}, the potential backup file created if the output file already exists will be deleted 069 * after a successful export 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 noBackup) 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, noBackup); 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 noBackup) 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 ((noBackup || !Config.getPref().getBoolean("save.keepbackup", false)) && tmpFile != null) { 102 Utils.deleteFile(tmpFile); 103 } 104 layer.onPostSaveToFile(); 105 } catch (IOException | InvalidPathException e) { 106 Logging.error(e); 107 108 try { 109 // if the file save failed, then the tempfile will not be deleted. So, restore the backup if we made one. 110 if (tmpFile != null && tmpFile.exists()) { 111 Utils.copyFile(tmpFile, file); 112 } 113 } catch (IOException | InvalidPathException e2) { 114 Logging.error(e2); 115 JOptionPane.showMessageDialog( 116 MainApplication.getMainFrame(), 117 tr("<html>An error occurred while restoring backup file.<br>Error is:<br>{0}</html>", 118 Utils.escapeReservedCharactersHTML(e2.getMessage())), 119 tr("Error"), 120 JOptionPane.ERROR_MESSAGE 121 ); 122 } 123 // re-throw original error 124 throw e; 125 } 126 } 127 128 protected void doSave(File file, OsmDataLayer layer) throws IOException { 129 // create outputstream and wrap it with gzip, xz or bzip, if necessary 130 try ( 131 OutputStream out = getOutputStream(file); 132 Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); 133 OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, layer.data.getVersion()) 134 ) { 135 layer.data.getReadLock().lock(); 136 try { 137 w.write(layer.data); 138 } finally { 139 layer.data.getReadLock().unlock(); 140 } 141 } 142 } 143}