Ticket #12726: 12726_alpha1.diff

File 12726_alpha1.diff, 35.4 KB (added by Don-vip, 7 years ago)
  • src/org/openstreetmap/josm/io/session/CommandSessionExporters.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.session;
     3
     4import java.util.Collection;
     5
     6import org.openstreetmap.josm.command.AddCommand;
     7import org.openstreetmap.josm.command.ChangeCommand;
     8import org.openstreetmap.josm.command.Command;
     9import org.openstreetmap.josm.command.PseudoCommand;
     10import org.openstreetmap.josm.command.SequenceCommand;
     11import org.openstreetmap.josm.data.osm.OsmPrimitive;
     12import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
     13import org.openstreetmap.josm.tools.Logging;
     14import org.w3c.dom.Element;
     15
     16/**
     17 * Command session exporters.
     18 * @since xxx
     19 */
     20public final class CommandSessionExporters {
     21
     22    private CommandSessionExporters() {
     23        // Hide default constructor
     24    }
     25
     26    abstract static class AbstractCommandSessionExporter<T extends Command> implements SessionCommandExporter {
     27        protected final T command;
     28
     29        AbstractCommandSessionExporter(T command) {
     30            this.command = command;
     31        }
     32
     33        @Override
     34        public final Element export(ExportSupport support) {
     35            Element el = support.createElement("command");
     36            el.setAttribute("class", command.getClass().getName());
     37            el.setAttribute("layer", Integer.toString(support.getLayerIndex()));
     38            doExport(el, support);
     39            if (includeParticipatingOsmPrimitives()) {
     40                for (OsmPrimitive osm : command.getParticipatingPrimitives()) {
     41                    Element osmEl = support.createElement(osm.getType().getAPIName());
     42                    osmEl.setAttribute("id", Long.toString(osm.getUniqueId()));
     43                    el.appendChild(osmEl);
     44                }
     45            }
     46            return el;
     47        }
     48
     49        protected void doExport(Element el, ExportSupport support) {
     50            // To be overriden if needed
     51        }
     52    }
     53
     54    /**
     55     * A special command exporter used when no exporter can be found for a given command.
     56     * This allows us to export as many commands as possible but:<ul>
     57     * <li>log errors directly in the session file, for troubleshooting</li>
     58     * <li>include an error flag preventing session import action to load commands to ensure data integrity</li>
     59     * </ul>
     60     */
     61    public static class ErrorCommandExporter extends AbstractCommandSessionExporter<Command> {
     62
     63        /**
     64         * Constructs a new {@code ErrorCommandExporter}
     65         * @param command unsupported command
     66         */
     67        public ErrorCommandExporter(Command command) {
     68            super(command);
     69        }
     70
     71        @Override
     72        protected void doExport(Element el, ExportSupport support) {
     73            String message = "No exporter found for " + command;
     74            Logging.error(message);
     75            el.setAttribute("error", message);
     76        }
     77    }
     78
     79    /**
     80     * Command exporter for {@link AddCommand}.
     81     */
     82    public static class AddCommandExporter extends AbstractCommandSessionExporter<AddCommand> {
     83
     84        /**
     85         * Constructs a new {@code AddCommandExporter}
     86         * @param command add command
     87         */
     88        public AddCommandExporter(AddCommand command) {
     89            super(command);
     90        }
     91    }
     92
     93    /**
     94     * Command exporter for {@link ChangeCommand}.
     95     */
     96    public static class ChangeCommandExporter extends AbstractCommandSessionExporter<ChangeCommand> {
     97
     98        /**
     99         * Constructs a new {@code ChangeCommandExporter}
     100         * @param command change command
     101         */
     102        public ChangeCommandExporter(ChangeCommand command) {
     103            super(command);
     104        }
     105
     106        @Override
     107        protected void doExport(Element el, ExportSupport support) {
     108            // TODO Auto-generated method stub
     109        }
     110    }
     111
     112    /**
     113     * Command exporter for {@link SequenceCommand}.
     114     */
     115    public static class SequenceCommandExporter extends AbstractCommandSessionExporter<SequenceCommand> {
     116
     117        /**
     118         * Constructs a new {@code SequenceCommandExporter}
     119         * @param command sequence command
     120         */
     121        public SequenceCommandExporter(SequenceCommand command) {
     122            super(command);
     123        }
     124
     125        @Override
     126        public boolean includeParticipatingOsmPrimitives() {
     127            return false;
     128        }
     129
     130        @Override
     131        protected void doExport(Element el, ExportSupport support) {
     132            el.setAttribute("name", command.getName());
     133            for (PseudoCommand pc : command.getChildren()) {
     134                if (pc instanceof Command) {
     135                    SessionWriter.addCommand(el, (Command) pc, support);
     136                } else {
     137                    // Include a fake command to issue an error in the session file (PseudoCommand not supported)
     138                    SessionWriter.addCommand(el, new Command(command.getAffectedDataSet()) {
     139
     140                        @Override
     141                        public String getDescriptionText() {
     142                            return null;
     143                        }
     144
     145                        @Override
     146                        public void fillModifiedData(Collection<OsmPrimitive> modified,
     147                                Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
     148                            // Do nothing
     149                        }
     150
     151                        @Override
     152                        public String toString() {
     153                            return "No exporter found for " + pc;
     154                        }
     155                    }, support);
     156                }
     157            }
     158        }
     159    }
     160}
  • src/org/openstreetmap/josm/io/session/CommandSessionImporters.java

    Property changes on: src\org\openstreetmap\josm\io\session\CommandSessionExporters.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.session;
     3
     4import java.util.ArrayList;
     5import java.util.Collections;
     6import java.util.List;
     7
     8import org.openstreetmap.josm.command.AddCommand;
     9import org.openstreetmap.josm.command.ChangeCommand;
     10import org.openstreetmap.josm.command.Command;
     11import org.openstreetmap.josm.command.SequenceCommand;
     12import org.openstreetmap.josm.data.osm.DataSet;
     13import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     14import org.openstreetmap.josm.gui.layer.Layer;
     15import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     16import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     17import org.openstreetmap.josm.io.IllegalDataException;
     18import org.openstreetmap.josm.tools.XmlUtils;
     19import org.w3c.dom.Element;
     20
     21/**
     22 * Command session importers.
     23 * @since xxx
     24 */
     25public final class CommandSessionImporters {
     26
     27    private CommandSessionImporters() {
     28        // Hide default constructor
     29    }
     30
     31    abstract static class AbstractCommandSessionImporter<T extends Command> implements SessionCommandImporter {
     32
     33        @Override
     34        public Command load(Element elem, OsmDataLayer dataLayer, ProgressMonitor progressMonitor) throws IllegalDataException {
     35            return doImport(elem, dataLayer, elem.getAttribute("name"));
     36        }
     37
     38        /**
     39         * @param el Command element
     40         * @param dataLayer data layer
     41         * @param name command name
     42         * @return the resulting command
     43         * @throws IllegalDataException in case of illegal data
     44         */
     45        protected abstract Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException;
     46    }
     47
     48    /**
     49     * A special command importer used when no exporter was found for a given command.
     50     * This allows us to export as many commands as possible but:<ul>
     51     * <li>log errors directly in the session file, for troubleshooting</li>
     52     * <li>include an error flag preventing session import action to load commands to ensure data integrity</li>
     53     * </ul>
     54     */
     55    public static class ErrorCommandImporter extends AbstractCommandSessionImporter<Command> {
     56
     57        @Override
     58        protected Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException {
     59            throw new IllegalDataException(name + ": " + el.getAttribute("error"));
     60        }
     61    }
     62
     63    /**
     64     * Command importer for {@link AddCommand}.
     65     */
     66    public static class AddCommandImporter extends AbstractCommandSessionImporter<AddCommand> {
     67
     68        @Override
     69        protected Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException {
     70            DataSet dataSet = dataLayer.data;
     71            Element osm = XmlUtils.getFirstChildElement(el);
     72            return new AddCommand(dataSet, dataSet.getPrimitiveById(
     73                    Long.parseLong(osm.getAttribute("id")),
     74                    OsmPrimitiveType.from(osm.getLocalName())));
     75        }
     76    }
     77
     78    /**
     79     * Command importer for {@link ChangeCommand}.
     80     */
     81    public static class ChangeCommandImporter extends AbstractCommandSessionImporter<ChangeCommand> {
     82
     83        @Override
     84        protected Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException {
     85            // TODO Auto-generated method stub
     86            return null;
     87        }
     88    }
     89
     90    /**
     91     * Command importer for {@link SequenceCommand}.
     92     */
     93    public static class SequenceCommandImporter extends AbstractCommandSessionImporter<SequenceCommand> {
     94
     95        @Override
     96        protected Command doImport(Element el, OsmDataLayer dataLayer, String name) throws IllegalDataException {
     97            try {
     98                int layerIndex = Integer.parseInt(el.getAttribute("layer"));
     99                List<Layer> layers = Collections.nCopies(layerIndex, dataLayer);
     100                return new SequenceCommand(name, SessionReader.readCommands(el, new ArrayList<>(), layers));
     101            } catch (ClassNotFoundException e) {
     102                throw new IllegalDataException(e);
     103            }
     104        }
     105    }
     106}
  • src/org/openstreetmap/josm/io/session/GenericSessionExporter.java

    Property changes on: src\org\openstreetmap\josm\io\session\CommandSessionImporters.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
     
    3535import org.w3c.dom.Element;
    3636
    3737/**
    38  * Generic superclass of {@link OsmDataSessionExporter} and {@link GpxTracksSessionExporter} layer exporters.
     38 * Generic superclass of {@link OsmDataSessionExporter}, {@link GpxTracksSessionExporter} and {@code NoteSessionExporter} layer exporters.
    3939 * @param <T> Type of exported layer
    4040 * @since 9470
    4141 */
  • src/org/openstreetmap/josm/io/session/SessionCommandExporter.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.session;
     3
     4import org.openstreetmap.josm.command.Command;
     5import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
     6import org.w3c.dom.Element;
     7
     8/**
     9 * Session command exporter.
     10 * @since xxx
     11 */
     12public interface SessionCommandExporter {
     13
     14    /**
     15     * Save meta data to the .jos file. Return a command XML element.
     16     * Use <code>support</code> to save files in the zip archive as needed.
     17     * @param support support class providing export utilities
     18     * @return the resulting XML element
     19     */
     20    Element export(ExportSupport support);
     21
     22    /**
     23     * Whether to include participating OSM primitives as inner child elements.
     24     * @return {@code true} to include participating OSM primitives
     25     * @see Command#getParticipatingPrimitives
     26     */
     27    default boolean includeParticipatingOsmPrimitives() {
     28        return true;
     29    }
     30}
  • src/org/openstreetmap/josm/io/session/SessionCommandImporter.java

    Property changes on: src\org\openstreetmap\josm\io\session\SessionCommandExporter.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.session;
     3
     4import org.openstreetmap.josm.command.Command;
     5import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     6import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     7import org.openstreetmap.josm.io.IllegalDataException;
     8import org.w3c.dom.Element;
     9
     10/**
     11 * Session command importer.
     12 * @since xxx
     13 */
     14@FunctionalInterface
     15public interface SessionCommandImporter {
     16
     17    /**
     18     * Load the command from xml meta-data.
     19     * @param elem XML element
     20     * @param dataLayer data layer
     21     * @param progressMonitor progress monitor
     22     * @return the resulting command
     23     * @throws IllegalDataException in case of illegal data
     24     */
     25    Command load(Element elem, OsmDataLayer dataLayer, ProgressMonitor progressMonitor) throws IllegalDataException;
     26}
  • src/org/openstreetmap/josm/io/session/SessionReader.java

    Property changes on: src\org\openstreetmap\josm\io\session\SessionCommandImporter.java
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
     
    3131import javax.swing.SwingUtilities;
    3232import javax.xml.parsers.ParserConfigurationException;
    3333
     34import org.openstreetmap.josm.command.AddCommand;
     35import org.openstreetmap.josm.command.AddPrimitivesCommand;
     36import org.openstreetmap.josm.command.ChangeCommand;
     37import org.openstreetmap.josm.command.ChangeNodesCommand;
     38import org.openstreetmap.josm.command.ChangePropertyCommand;
     39import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
     40import org.openstreetmap.josm.command.ChangeRelationMemberRoleCommand;
     41import org.openstreetmap.josm.command.Command;
     42import org.openstreetmap.josm.command.DeleteCommand;
     43import org.openstreetmap.josm.command.MoveCommand;
     44import org.openstreetmap.josm.command.RemoveNodesCommand;
     45import org.openstreetmap.josm.command.RotateCommand;
     46import org.openstreetmap.josm.command.ScaleCommand;
     47import org.openstreetmap.josm.command.SelectCommand;
     48import org.openstreetmap.josm.command.SequenceCommand;
     49import org.openstreetmap.josm.command.SplitWayCommand;
     50import org.openstreetmap.josm.command.TransformNodesCommand;
    3451import org.openstreetmap.josm.data.ViewportData;
    3552import org.openstreetmap.josm.data.coor.EastNorth;
    3653import org.openstreetmap.josm.data.coor.LatLon;
     
    3855import org.openstreetmap.josm.gui.ExtendedDialog;
    3956import org.openstreetmap.josm.gui.MainApplication;
    4057import org.openstreetmap.josm.gui.layer.Layer;
     58import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    4159import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    4260import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    4361import org.openstreetmap.josm.io.Compression;
    4462import org.openstreetmap.josm.io.IllegalDataException;
     63import org.openstreetmap.josm.io.session.CommandSessionImporters.AddCommandImporter;
     64import org.openstreetmap.josm.io.session.CommandSessionImporters.ChangeCommandImporter;
     65import org.openstreetmap.josm.io.session.CommandSessionImporters.SequenceCommandImporter;
    4566import org.openstreetmap.josm.tools.CheckParameterUtil;
    4667import org.openstreetmap.josm.tools.JosmRuntimeException;
    4768import org.openstreetmap.josm.tools.Logging;
     
    148169        }
    149170    }
    150171
    151     private static final Map<String, Class<? extends SessionLayerImporter>> sessionLayerImporters = new HashMap<>();
     172    /**
     173     * Data class for commands saved in the session file.
     174     * @since xxx
     175     */
     176    public static class CommandStackData {
     177        private final List<Command> undo = new ArrayList<>();
     178        private final List<Command> redo = new ArrayList<>();
     179
     180        /**
     181         * Returns the undo commands.
     182         * @return the undo commands
     183         */
     184        public final List<Command> getUndo() {
     185            return undo;
     186        }
     187
     188        /**
     189         * Returns the redo commands.
     190         * @return the redo commands
     191         */
     192        public final List<Command> getRedo() {
     193            return redo;
     194        }
     195    }
     196
     197    private static final Map<String, Class<? extends SessionLayerImporter>> sessionLayerImporters
     198        = new HashMap<>();
     199    private static final Map<Class<? extends Command>, Class<? extends SessionCommandImporter>> sessionCommandImporters
     200        = new HashMap<>();
    152201
    153202    private URI sessionFileURI;
    154203    private boolean zip; // true, if session file is a .joz file; false if it is a .jos file
     
    156205    private List<Layer> layers = new ArrayList<>();
    157206    private int active = -1;
    158207    private final List<Runnable> postLoadTasks = new ArrayList<>();
     208    //private final Map<Integer, ImportSupport> supports = new HashMap<>();
    159209    private SessionViewportData viewport;
    160210    private SessionProjectionChoiceData projectionChoice;
     211    private CommandStackData commandStack;
    161212
    162213    static {
     214        // Layers
    163215        registerSessionLayerImporter("osm-data", OsmDataSessionImporter.class);
    164216        registerSessionLayerImporter("imagery", ImagerySessionImporter.class);
    165217        registerSessionLayerImporter("tracks", GpxTracksSessionImporter.class);
    166218        registerSessionLayerImporter("geoimage", GeoImageSessionImporter.class);
    167219        registerSessionLayerImporter("markers", MarkerSessionImporter.class);
    168220        registerSessionLayerImporter("osm-notes", NoteSessionImporter.class);
     221        // Commands TODO
     222        registerSessionCommandImporter(AddCommand.class, AddCommandImporter.class);
     223        registerSessionCommandImporter(AddPrimitivesCommand.class, null);
     224        registerSessionCommandImporter(ChangeCommand.class, ChangeCommandImporter.class);
     225        registerSessionCommandImporter(ChangeNodesCommand.class, null);
     226        registerSessionCommandImporter(ChangePropertyCommand.class, null);
     227        registerSessionCommandImporter(ChangePropertyKeyCommand.class, null);
     228        registerSessionCommandImporter(ChangeRelationMemberRoleCommand.class, null);
     229        registerSessionCommandImporter(DeleteCommand.class, null);
     230        registerSessionCommandImporter(MoveCommand.class, null);
     231        registerSessionCommandImporter(RemoveNodesCommand.class, null);
     232        registerSessionCommandImporter(RotateCommand.class, null);
     233        registerSessionCommandImporter(ScaleCommand.class, null);
     234        registerSessionCommandImporter(SelectCommand.class, null);
     235        registerSessionCommandImporter(SequenceCommand.class, SequenceCommandImporter.class);
     236        registerSessionCommandImporter(SplitWayCommand.class, null);
     237        registerSessionCommandImporter(TransformNodesCommand.class, null);
    169238    }
    170239
    171240    /**
     
    179248    }
    180249
    181250    /**
     251     * Register a session command importer.
     252     *
     253     * @param commandClass command class
     254     * @param importer importer for this command class
     255     * @since xxx
     256     */
     257    public static void registerSessionCommandImporter(Class<? extends Command> commandClass, Class<? extends SessionCommandImporter> importer) {
     258        sessionCommandImporters.put(commandClass, importer);
     259    }
     260
     261    /**
    182262     * Returns the session layer importer for the given layer type.
    183263     * @param layerType layer type to import
    184264     * @return session layer importer for the given layer
    185265     */
    186266    public static SessionLayerImporter getSessionLayerImporter(String layerType) {
    187         Class<? extends SessionLayerImporter> importerClass = sessionLayerImporters.get(layerType);
     267        return getSessionImporter(sessionLayerImporters, layerType);
     268    }
     269
     270    /**
     271     * Returns the session command importer for the given command.
     272     * @param commandClass command class to import
     273     * @return session command importer for the given command, or null
     274     * @since xxx
     275     */
     276    public static SessionCommandImporter getSessionCommandImporter(Class<? extends Command> commandClass) {
     277        return getSessionImporter(sessionCommandImporters, commandClass);
     278    }
     279
     280    private static <E, T> E getSessionImporter(Map<T, Class<? extends E>> importers, T key) {
     281        Class<? extends E> importerClass = importers.get(key);
    188282        if (importerClass == null)
    189283            return null;
    190         SessionLayerImporter importer = null;
    191284        try {
    192             importer = importerClass.getConstructor().newInstance();
     285            return importerClass.getConstructor().newInstance();
    193286        } catch (ReflectiveOperationException e) {
    194             throw new JosmRuntimeException(e);
     287            Logging.error(e);
     288            return null;
    195289        }
    196         return importer;
    197290    }
    198291
    199292    /**
     
    236329    }
    237330
    238331    /**
     332     * Return the command stack data.
     333     * @return the command stack; can be null when no command is found in the file
     334     * @since xxx
     335     */
     336    public CommandStackData getCommandStack() {
     337        return commandStack;
     338    }
     339
     340    /**
    239341     * A class that provides some context for the individual {@link SessionLayerImporter}
    240342     * when doing the import.
    241343     */
     
    400502        }
    401503    }
    402504
     505    /**
     506     * Layer dependency.
     507     */
    403508    public static class LayerDependency {
    404509        private final Integer index;
    405510        private final Layer layer;
    406511        private final SessionLayerImporter importer;
    407512
     513        /**
     514         * Constructs a new {@code LayerDependency}.
     515         * @param index layer index
     516         * @param layer layer
     517         * @param importer layer importer
     518         */
    408519        public LayerDependency(Integer index, Layer layer, SessionLayerImporter importer) {
    409520            this.index = index;
    410521            this.layer = layer;
    411522            this.importer = importer;
    412523        }
    413524
     525        /**
     526         * Returns the layer importer.
     527         * @return the layer importer
     528         */
    414529        public SessionLayerImporter getImporter() {
    415530            return importer;
    416531        }
    417532
     533        /**
     534         * Returns the layer index.
     535         * @return the layer index
     536         */
    418537        public Integer getIndex() {
    419538            return index;
    420539        }
    421540
     541        /**
     542         * Returns the layer.
     543         * @return the layer
     544         */
    422545        public Layer getLayer() {
    423546            return layer;
    424547        }
     
    557680                    depsImp.add(new LayerDependency(d, layersMap.get(d), dImp));
    558681                }
    559682                ImportSupport support = new ImportSupport(name, idx, depsImp);
     683                //supports.put(idx, support);
    560684                Layer layer = null;
    561685                Exception exception = null;
    562686                try {
     
    614738            layer.setName(names.get(entry.getKey()));
    615739            layers.add(layer);
    616740        }
     741        commandStack = readCommandStackData(root);
     742        //supports.clear();
     743    }
     744
     745    private CommandStackData readCommandStackData(Element root) {
     746        Element commandsEl = getElementByTagName(root, "commands");
     747        if (commandsEl == null) return null;
     748        CommandStackData result = new CommandStackData();
     749        try {
     750            readCommands(commandsEl, result.undo, "undo");
     751            readCommands(commandsEl, result.redo, "redo");
     752        } catch (ClassNotFoundException | IllegalDataException | RuntimeException e) {
     753            Logging.error(e);
     754            return null;
     755        }
     756        return result;
     757    }
     758
     759    private void readCommands(Element commandsEl, List<Command> result, String type)
     760            throws ClassNotFoundException, IllegalDataException {
     761        Element typeEl = getElementByTagName(commandsEl, type);
     762        if (typeEl != null) {
     763            readCommands(typeEl, result/*, supports*/, layers);
     764        }
     765    }
     766
     767    static List<Command> readCommands(Element parentEl, List<Command> result/*, Map<Integer, ImportSupport> supports*/, List<Layer> layers)
     768            throws ClassNotFoundException, IllegalDataException {
     769        NodeList commandNl = parentEl.getElementsByTagName("command");
     770        int length = commandNl.getLength();
     771        for (int i = 0; i < length; i++) {
     772            Element commandEl = (Element) commandNl.item(i);
     773            String klass = commandEl.getAttribute("class");
     774            @SuppressWarnings("unchecked")
     775            SessionCommandImporter importer = getSessionCommandImporter((Class<? extends Command>) Class.forName(klass));
     776            if (importer == null)
     777                throw new IllegalStateException("No importer found for " + klass);
     778            int layerIndex = Integer.parseInt(commandEl.getAttribute("layer"));
     779            OsmDataLayer dataLayer = ((OsmDataLayer) layers.get(layerIndex - 1));
     780            result.add(importer.load(commandEl, dataLayer, /*supports.get(layerIndex),*/ NullProgressMonitor.INSTANCE));
     781        }
     782        return result;
    617783    }
    618784
    619785    private static SessionViewportData readViewportData(Element root) {
  • src/org/openstreetmap/josm/io/session/SessionWriter.java

     
    2525import javax.xml.transform.dom.DOMSource;
    2626import javax.xml.transform.stream.StreamResult;
    2727
     28import org.openstreetmap.josm.command.AddCommand;
     29import org.openstreetmap.josm.command.AddPrimitivesCommand;
     30import org.openstreetmap.josm.command.ChangeCommand;
     31import org.openstreetmap.josm.command.ChangeNodesCommand;
     32import org.openstreetmap.josm.command.ChangePropertyCommand;
     33import org.openstreetmap.josm.command.ChangePropertyKeyCommand;
     34import org.openstreetmap.josm.command.ChangeRelationMemberRoleCommand;
     35import org.openstreetmap.josm.command.Command;
     36import org.openstreetmap.josm.command.DeleteCommand;
     37import org.openstreetmap.josm.command.MoveCommand;
     38import org.openstreetmap.josm.command.RemoveNodesCommand;
     39import org.openstreetmap.josm.command.RotateCommand;
     40import org.openstreetmap.josm.command.ScaleCommand;
     41import org.openstreetmap.josm.command.SelectCommand;
     42import org.openstreetmap.josm.command.SequenceCommand;
     43import org.openstreetmap.josm.command.SplitWayCommand;
     44import org.openstreetmap.josm.command.TransformNodesCommand;
     45import org.openstreetmap.josm.data.UndoRedoHandler;
    2846import org.openstreetmap.josm.data.coor.EastNorth;
    2947import org.openstreetmap.josm.data.coor.LatLon;
     48import org.openstreetmap.josm.data.osm.DataSet;
    3049import org.openstreetmap.josm.data.projection.ProjectionRegistry;
    3150import org.openstreetmap.josm.gui.MainApplication;
    3251import org.openstreetmap.josm.gui.MapView;
     
    4059import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
    4160import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
    4261import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
     62import org.openstreetmap.josm.io.session.CommandSessionExporters.AddCommandExporter;
     63import org.openstreetmap.josm.io.session.CommandSessionExporters.ChangeCommandExporter;
     64import org.openstreetmap.josm.io.session.CommandSessionExporters.ErrorCommandExporter;
     65import org.openstreetmap.josm.io.session.CommandSessionExporters.SequenceCommandExporter;
    4366import org.openstreetmap.josm.tools.JosmRuntimeException;
    4467import org.openstreetmap.josm.tools.Logging;
    4568import org.openstreetmap.josm.tools.MultiMap;
     
    4770import org.openstreetmap.josm.tools.XmlUtils;
    4871import org.w3c.dom.Document;
    4972import org.w3c.dom.Element;
     73import org.w3c.dom.Node;
    5074import org.w3c.dom.Text;
    5175
    5276/**
     
    5579 */
    5680public class SessionWriter {
    5781
    58     private static Map<Class<? extends Layer>, Class<? extends SessionLayerExporter>> sessionLayerExporters = new HashMap<>();
     82    private static final Map<Class<? extends Layer>, Class<? extends SessionLayerExporter>> sessionLayerExporters
     83        = new HashMap<>();
     84    private static final Map<Class<? extends Command>, Class<? extends SessionCommandExporter>> sessionCommandExporters
     85        = new HashMap<>();
    5986
    6087    private final List<Layer> layers;
    6188    private final int active;
     
    6693    private ZipOutputStream zipOut;
    6794
    6895    static {
     96        // Layers
    6997        registerSessionLayerExporter(OsmDataLayer.class, OsmDataSessionExporter.class);
    7098        registerSessionLayerExporter(TMSLayer.class, ImagerySessionExporter.class);
    7199        registerSessionLayerExporter(WMSLayer.class, ImagerySessionExporter.class);
     
    74102        registerSessionLayerExporter(GeoImageLayer.class, GeoImageSessionExporter.class);
    75103        registerSessionLayerExporter(MarkerLayer.class, MarkerSessionExporter.class);
    76104        registerSessionLayerExporter(NoteLayer.class, NoteSessionExporter.class);
     105        // Commands TODO
     106        registerSessionCommandExporter(AddCommand.class, AddCommandExporter.class);
     107        registerSessionCommandExporter(AddPrimitivesCommand.class, null);
     108        registerSessionCommandExporter(ChangeCommand.class, ChangeCommandExporter.class);
     109        registerSessionCommandExporter(ChangeNodesCommand.class, null);
     110        registerSessionCommandExporter(ChangePropertyCommand.class, null);
     111        registerSessionCommandExporter(ChangePropertyKeyCommand.class, null);
     112        registerSessionCommandExporter(ChangeRelationMemberRoleCommand.class, null);
     113        registerSessionCommandExporter(DeleteCommand.class, null);
     114        registerSessionCommandExporter(MoveCommand.class, null);
     115        registerSessionCommandExporter(RemoveNodesCommand.class, null);
     116        registerSessionCommandExporter(RotateCommand.class, null);
     117        registerSessionCommandExporter(ScaleCommand.class, null);
     118        registerSessionCommandExporter(SelectCommand.class, null);
     119        registerSessionCommandExporter(SequenceCommand.class, SequenceCommandExporter.class);
     120        registerSessionCommandExporter(SplitWayCommand.class, null);
     121        registerSessionCommandExporter(TransformNodesCommand.class, null);
    77122    }
    78123
    79124    /**
     
    88133    }
    89134
    90135    /**
     136     * Register a session layer exporter.
     137     *
     138     * The exporter class must have a one-argument constructor with commandClass as formal parameter type.
     139     * @param commandClass command class
     140     * @param exporter exporter for this command class
     141     * @since xxx
     142     */
     143    public static void registerSessionCommandExporter(Class<? extends Command> commandClass, Class<? extends SessionCommandExporter> exporter) {
     144        sessionCommandExporters.put(commandClass, exporter);
     145    }
     146
     147    /**
    91148     * Returns the session layer exporter for the given layer.
    92149     * @param layer layer to export
    93      * @return session layer exporter for the given layer
     150     * @return session layer exporter for the given layer, or null
    94151     */
    95152    public static SessionLayerExporter getSessionLayerExporter(Layer layer) {
    96         Class<? extends Layer> layerClass = layer.getClass();
    97         Class<? extends SessionLayerExporter> exporterClass = sessionLayerExporters.get(layerClass);
     153        return getSessionExporter(sessionLayerExporters, layer.getClass(), layer);
     154    }
     155
     156    /**
     157     * Returns the session command exporter for the given command.
     158     * @param command command to export
     159     * @return session command exporter for the given command, or null
     160     * @since xxx
     161     */
     162    public static SessionCommandExporter getSessionCommandExporter(Command command) {
     163        return getSessionExporter(sessionCommandExporters, command.getClass(), command);
     164    }
     165
     166    private static <E, T> E getSessionExporter(Map<Class<? extends T>, Class<? extends E>> exporters, Class<? extends T> klass, T obj) {
     167        Class<? extends E> exporterClass = exporters.get(klass);
    98168        if (exporterClass == null)
    99169            return null;
    100170        try {
    101             return exporterClass.getConstructor(layerClass).newInstance(layer);
     171            return exporterClass.getConstructor(klass).newInstance(obj);
    102172        } catch (ReflectiveOperationException e) {
    103             throw new JosmRuntimeException(e);
     173            Logging.error(e);
     174            return null;
    104175        }
    105176    }
    106177
     
    252323            }
    253324            layersEl.appendChild(el);
    254325        }
     326        writeCommandStack(root);
    255327        return doc;
    256328    }
    257329
     330    private void writeCommandStack(Element root) {
     331        Document doc = root.getOwnerDocument();
     332        Element commandsEl = doc.createElement("commands");
     333        root.appendChild(commandsEl);
     334        addCommands(doc, commandsEl.appendChild(doc.createElement("undo")), UndoRedoHandler.getInstance().getUndoCommands());
     335        addCommands(doc, commandsEl.appendChild(doc.createElement("redo")), UndoRedoHandler.getInstance().getRedoCommands());
     336    }
     337
     338    private void addCommands(Document doc, Node commandsEl, List<Command> commands) {
     339        Map<DataSet, Integer> layerIndexMap = new HashMap<>();
     340        for (Command c : commands) {
     341            DataSet ds = c.getAffectedDataSet();
     342            Integer layerIndex = layerIndexMap.computeIfAbsent(ds, x -> {
     343                for (int i = 0; i < layers.size(); i++) {
     344                    Layer l = layers.get(i);
     345                    if (l instanceof OsmDataLayer && ((OsmDataLayer) l).getDataSet() == ds) {
     346                        return i + 1;
     347                    }
     348                }
     349                return -1;
     350            });
     351            addCommand(commandsEl, c, new ExportSupport(doc, layerIndex));
     352        }
     353    }
     354
     355    static Node addCommand(Node commandsEl, Command c, ExportSupport support) {
     356        SessionCommandExporter exporter = getSessionCommandExporter(c);
     357        if (exporter == null) {
     358            exporter = new ErrorCommandExporter(c);
     359        }
     360        return commandsEl.appendChild(exporter.export(support));
     361    }
     362
    258363    private static void writeViewPort(Element root) {
    259364        Document doc = root.getOwnerDocument();
    260365        Element viewportEl = doc.createElement("viewport");