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