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}