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