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