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}