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.FileNotFoundException;
008import java.io.IOException;
009import java.io.OutputStream;
010import java.io.OutputStreamWriter;
011import java.io.PrintWriter;
012import java.io.Writer;
013import java.nio.charset.StandardCharsets;
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.tools.Utils;
023
024/**
025 * Exports data to an .osm file.
026 * @since 1949
027 */
028public class OsmExporter extends FileExporter {
029
030    /**
031     * Constructs a new {@code OsmExporter}.
032     */
033    public OsmExporter() {
034        super(new ExtensionFileFilter(
035            "osm,xml", "osm", tr("OSM Server Files") + " (*.osm)"));
036    }
037
038    /**
039     * Constructs a new {@code OsmExporter}.
040     * @param filter The extension file filter
041     */
042    public OsmExporter(ExtensionFileFilter filter) {
043        super(filter);
044    }
045
046    @Override
047    public boolean acceptFile(File pathname, Layer layer) {
048        if (!(layer instanceof OsmDataLayer))
049            return false;
050        return super.acceptFile(pathname, layer);
051    }
052
053    @Override
054    public void exportData(File file, Layer layer) throws IOException {
055        exportData(file, layer, false);
056    }
057
058    /**
059     * Exports OSM data to the given file.
060     * @param file Output file
061     * @param layer Data layer. Must be an instance of {@link OsmDataLayer}.
062     * @param noBackup if {@code true}, the potential backup file created if the output file already exists will be deleted
063     *                 after a successful export
064     * @throws IllegalArgumentException if {@code layer} is not an instance of {@code OsmDataLayer}
065     */
066    public void exportData(File file, Layer layer, boolean noBackup) {
067        checkOsmDataLayer(layer);
068        save(file, (OsmDataLayer) layer, noBackup);
069    }
070
071    protected static void checkOsmDataLayer(Layer layer) {
072        if (!(layer instanceof OsmDataLayer)) {
073            throw new IllegalArgumentException(MessageFormat.format("Expected instance of OsmDataLayer. Got ''{0}''.", layer
074                    .getClass().getName()));
075        }
076    }
077
078    protected static OutputStream getOutputStream(File file) throws FileNotFoundException, 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
086            // process of writing the file, we might just end up with
087            // 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 || !Main.pref.getBoolean("save.keepbackup", false)) {
095                if (tmpFile != null) {
096                    Utils.deleteFile(tmpFile);
097                }
098            }
099            layer.onPostSaveToFile();
100        } catch (IOException e) {
101            Main.error(e);
102            JOptionPane.showMessageDialog(
103                    Main.parent,
104                    tr("<html>An error occurred while saving.<br>Error is:<br>{0}</html>", e.getMessage()),
105                    tr("Error"),
106                    JOptionPane.ERROR_MESSAGE
107            );
108
109            try {
110                // if the file save failed, then the tempfile will not
111                // 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 e2) {
116                Main.error(e2);
117                JOptionPane.showMessageDialog(
118                        Main.parent,
119                        tr("<html>An error occurred while restoring backup file.<br>Error is:<br>{0}</html>", e2.getMessage()),
120                        tr("Error"),
121                        JOptionPane.ERROR_MESSAGE
122                );
123            }
124        }
125    }
126
127    protected void doSave(File file, OsmDataLayer layer) throws IOException, FileNotFoundException {
128        // create outputstream and wrap it with gzip or bzip, if necessary
129        try (
130            OutputStream out = getOutputStream(file);
131            Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
132            OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, layer.data.getVersion());
133        ) {
134            layer.data.getReadLock().lock();
135            try {
136                w.writeLayer(layer);
137            } finally {
138                layer.data.getReadLock().unlock();
139            }
140        }
141    }
142}