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