001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.command;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.Arrays;
007import java.util.Collection;
008import java.util.HashSet;
009
010import javax.swing.Icon;
011
012import org.openstreetmap.josm.data.osm.OsmPrimitive;
013import org.openstreetmap.josm.tools.ImageProvider;
014import org.openstreetmap.josm.tools.Utils;
015
016/**
017 * A command consisting of a sequence of other commands. Executes the other commands
018 * and undo them in reverse order.
019 * @author imi
020 * @since 31
021 */
022public class SequenceCommand extends Command {
023
024    /** The command sequence to be executed. */
025    private Command[] sequence;
026    private boolean sequenceComplete;
027    private final String name;
028    /** Determines if the sequence execution should continue after one of its commands fails. */
029    public boolean continueOnError = false;
030
031    /**
032     * Create the command by specifying the list of commands to execute.
033     * @param name The description text
034     * @param sequenz The sequence that should be executed.
035     */
036    public SequenceCommand(String name, Collection<Command> sequenz) {
037        super();
038        this.name = name;
039        this.sequence = sequenz.toArray(new Command[sequenz.size()]);
040    }
041
042    /**
043     * Convenient constructor, if the commands are known at compile time.
044     * @param name The description text
045     * @param sequenz The sequence that should be executed.
046     */
047    public SequenceCommand(String name, Command... sequenz) {
048        this(name, Arrays.asList(sequenz));
049    }
050
051    @Override public boolean executeCommand() {
052        for (int i=0; i < sequence.length; i++) {
053            boolean result = sequence[i].executeCommand();
054            if (!result && !continueOnError) {
055                undoCommands(i-1);
056                return false;
057            }
058        }
059        sequenceComplete = true;
060        return true;
061    }
062
063    /**
064     * Returns the last command.
065     * @return The last command, or {@code null} if the sequence is empty.
066     */
067    public Command getLastCommand() {
068        if (sequence.length == 0)
069            return null;
070        return sequence[sequence.length-1];
071    }
072
073    protected final void undoCommands(int start) {
074        // We probably aborted this halfway though the
075        // execution sequence because of a sub-command
076        // error.  We already undid the sub-commands.
077        if (!sequenceComplete)
078            return;
079        for (int i = start; i >= 0; --i) {
080            sequence[i].undoCommand();
081        }
082    }
083
084    @Override public void undoCommand() {
085        undoCommands(sequence.length-1);
086    }
087
088    @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
089        for (Command c : sequence) {
090            c.fillModifiedData(modified, deleted, added);
091        }
092    }
093
094    @Override
095    public String getDescriptionText() {
096        return tr("Sequence: {0}", name);
097    }
098
099    @Override
100    public Icon getDescriptionIcon() {
101        return ImageProvider.get("data", "sequence");
102    }
103
104    @Override
105    public Collection<PseudoCommand> getChildren() {
106        return Arrays.<PseudoCommand>asList(sequence);
107    }
108
109    @Override
110    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
111        Collection<OsmPrimitive> prims = new HashSet<>();
112        for (Command c : sequence) {
113            prims.addAll(c.getParticipatingPrimitives());
114        }
115        return prims;
116    }
117
118    protected final void setSequence(Command[] sequence) {
119        this.sequence = Utils.copyArray(sequence);
120    }
121
122    protected final void setSequenceComplete(boolean sequenceComplete) {
123        this.sequenceComplete = sequenceComplete;
124    }
125}