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