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 106 public void undoCommand() { 107 // We probably aborted this halfway though the execution sequence because of a sub-command error. 108 // We already undid the sub-commands. 109 if (!sequenceComplete) 110 return; 111 undoCommands(sequence.length-1); 112 } 113 114 @Override 115 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { 116 for (Command c : sequence) { 117 c.fillModifiedData(modified, deleted, added); 118 } 119 } 120 121 @Override 122 public String getDescriptionText() { 123 return tr("Sequence: {0}", name); 124 } 125 126 /** 127 * Returns the command name used in description text. 128 * @return the command name 129 * @since 14283 130 */ 131 public final String getName() { 132 return name; 133 } 134 135 @Override 136 public Icon getDescriptionIcon() { 137 return ImageProvider.get("data", "sequence"); 138 } 139 140 @Override 141 public Collection<PseudoCommand> getChildren() { 142 return Arrays.<PseudoCommand>asList(sequence); 143 } 144 145 @Override 146 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() { 147 Collection<OsmPrimitive> prims = new HashSet<>(); 148 for (Command c : sequence) { 149 prims.addAll(c.getParticipatingPrimitives()); 150 } 151 return prims; 152 } 153 154 protected final void setSequence(Command... sequence) { 155 this.sequence = Utils.copyArray(sequence); 156 } 157 158 protected final void setSequenceComplete(boolean sequenceComplete) { 159 this.sequenceComplete = sequenceComplete; 160 } 161 162 @Override 163 public int hashCode() { 164 return Objects.hash(super.hashCode(), Arrays.hashCode(sequence), sequenceComplete, name, continueOnError); 165 } 166 167 @Override 168 public boolean equals(Object obj) { 169 if (this == obj) return true; 170 if (obj == null || getClass() != obj.getClass()) return false; 171 if (!super.equals(obj)) return false; 172 SequenceCommand that = (SequenceCommand) obj; 173 return sequenceComplete == that.sequenceComplete && 174 continueOnError == that.continueOnError && 175 Arrays.equals(sequence, that.sequence) && 176 Objects.equals(name, that.name); 177 } 178}