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