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;
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
126    @Override
127    public int hashCode() {
128        final int prime = 31;
129        int result = super.hashCode();
130        result = prime * result + (continueOnError ? 1231 : 1237);
131        result = prime * result + ((name == null) ? 0 : name.hashCode());
132        result = prime * result + Arrays.hashCode(sequence);
133        result = prime * result + (sequenceComplete ? 1231 : 1237);
134        return result;
135    }
136
137    @Override
138    public boolean equals(Object obj) {
139        if (this == obj)
140            return true;
141        if (!super.equals(obj))
142            return false;
143        if (getClass() != obj.getClass())
144            return false;
145        SequenceCommand other = (SequenceCommand) obj;
146        if (continueOnError != other.continueOnError)
147            return false;
148        if (name == null) {
149            if (other.name != null)
150                return false;
151        } else if (!name.equals(other.name))
152            return false;
153        if (!Arrays.equals(sequence, other.sequence))
154            return false;
155        if (sequenceComplete != other.sequenceComplete)
156            return false;
157        return true;
158    }
159}