001 /*----------------------------------------------------------------------------*/ 002 /* Copyright (c) FIRST 2008-2012. All Rights Reserved. */ 003 /* Open Source Software - may be modified and shared by FRC teams. The code */ 004 /* must be accompanied by the FIRST BSD license file in the root directory of */ 005 /* the project. */ 006 /*----------------------------------------------------------------------------*/ 007 008 package edu.wpi.first.wpilibj.command; 009 010 import java.util.Enumeration; 011 import java.util.Vector; 012 013 /** 014 * A {@link CommandGroup} is a list of commands which are executed in sequence. 015 * 016 * <p>Commands in a {@link CommandGroup} are added using the {@link CommandGroup#addSequential(Command) addSequential(...)} method 017 * and are called sequentially. 018 * {@link CommandGroup CommandGroups} are themselves {@link Command commands} 019 * and can be given to other {@link CommandGroup CommandGroups}.</p> 020 * 021 * <p>{@link CommandGroup CommandGroups} will carry all of the requirements of their {@link Command subcommands}. Additional 022 * requirements can be specified by calling {@link CommandGroup#requires(Subsystem) requires(...)} 023 * normally in the constructor.</P> 024 * 025 * <p>CommandGroups can also execute commands in parallel, simply by adding them 026 * using {@link CommandGroup#addParallel(Command) addParallel(...)}.</p> 027 * 028 * @author Brad Miller 029 * @author Joe Grinstead 030 * @see Command 031 * @see Subsystem 032 * @see IllegalUseOfCommandException 033 */ 034 public class CommandGroup extends Command { 035 036 /** The commands in this group (stored in entries) */ 037 private Vector m_commands = new Vector(); 038 /** The active children in this group (stored in entries) */ 039 Vector m_children = new Vector(); 040 /** The current command, -1 signifies that none have been run */ 041 private int m_currentCommandIndex = -1; 042 043 /** 044 * Creates a new {@link CommandGroup CommandGroup}. 045 * The name of this command will be set to its class name. 046 */ 047 public CommandGroup() { 048 } 049 050 /** 051 * Creates a new {@link CommandGroup CommandGroup} with the given name. 052 * @param name the name for this command group 053 * @throws IllegalArgumentException if name is null 054 */ 055 public CommandGroup(String name) { 056 super(name); 057 } 058 059 /** 060 * Adds a new {@link Command Command} to the group. The {@link Command Command} will be started after 061 * all the previously added {@link Command Commands}. 062 * 063 * <p>Note that any requirements the given {@link Command Command} has will be added to the 064 * group. For this reason, a {@link Command Command's} requirements can not be changed after 065 * being added to a group.</p> 066 * 067 * <p>It is recommended that this method be called in the constructor.</p> 068 * 069 * @param command The {@link Command Command} to be added 070 * @throws IllegalUseOfCommandException if the group has been started before or been given to another group 071 * @throws IllegalArgumentException if command is null 072 */ 073 public synchronized final void addSequential(Command command) { 074 validate("Can not add new command to command group"); 075 if (command == null) { 076 throw new IllegalArgumentException("Given null command"); 077 } 078 079 command.setParent(this); 080 081 m_commands.addElement(new Entry(command, Entry.IN_SEQUENCE)); 082 for (Enumeration e = command.getRequirements(); e.hasMoreElements();) { 083 requires((Subsystem) e.nextElement()); 084 } 085 } 086 087 /** 088 * Adds a new {@link Command Command} to the group with a given timeout. 089 * The {@link Command Command} will be started after all the previously added commands. 090 * 091 * <p>Once the {@link Command Command} is started, it will be run until it finishes or the time 092 * expires, whichever is sooner. Note that the given {@link Command Command} will have no 093 * knowledge that it is on a timer.</p> 094 * 095 * <p>Note that any requirements the given {@link Command Command} has will be added to the 096 * group. For this reason, a {@link Command Command's} requirements can not be changed after 097 * being added to a group.</p> 098 * 099 * <p>It is recommended that this method be called in the constructor.</p> 100 * 101 * @param command The {@link Command Command} to be added 102 * @param timeout The timeout (in seconds) 103 * @throws IllegalUseOfCommandException if the group has been started before or been given to another group or 104 * if the {@link Command Command} has been started before or been given to another group 105 * @throws IllegalArgumentException if command is null or timeout is negative 106 */ 107 public synchronized final void addSequential(Command command, double timeout) { 108 validate("Can not add new command to command group"); 109 if (command == null) { 110 throw new IllegalArgumentException("Given null command"); 111 } 112 if (timeout < 0) { 113 throw new IllegalArgumentException("Can not be given a negative timeout"); 114 } 115 116 command.setParent(this); 117 118 m_commands.addElement(new Entry(command, Entry.IN_SEQUENCE, timeout)); 119 for (Enumeration e = command.getRequirements(); e.hasMoreElements();) { 120 requires((Subsystem) e.nextElement()); 121 } 122 } 123 124 /** 125 * Adds a new child {@link Command} to the group. The {@link Command} will be started after 126 * all the previously added {@link Command Commands}. 127 * 128 * <p>Instead of waiting for the child to finish, a {@link CommandGroup} will have it 129 * run at the same time as the subsequent {@link Command Commands}. The child will run until either 130 * it finishes, a new child with conflicting requirements is started, or 131 * the main sequence runs a {@link Command} with conflicting requirements. In the latter 132 * two cases, the child will be canceled even if it says it can't be 133 * interrupted.</p> 134 * 135 * <p>Note that any requirements the given {@link Command Command} has will be added to the 136 * group. For this reason, a {@link Command Command's} requirements can not be changed after 137 * being added to a group.</p> 138 * 139 * <p>It is recommended that this method be called in the constructor.</p> 140 * 141 * @param command The command to be added 142 * @throws IllegalUseOfCommandException if the group has been started before or been given to another command group 143 * @throws IllegalArgumentException if command is null 144 */ 145 public synchronized final void addParallel(Command command) { 146 validate("Can not add new command to command group"); 147 if (command == null) { 148 throw new NullPointerException("Given null command"); 149 } 150 151 command.setParent(this); 152 153 m_commands.addElement(new Entry(command, Entry.BRANCH_CHILD)); 154 for (Enumeration e = command.getRequirements(); e.hasMoreElements();) { 155 requires((Subsystem) e.nextElement()); 156 } 157 } 158 159 /** 160 * Adds a new child {@link Command} to the group with the given timeout. The {@link Command} will be started after 161 * all the previously added {@link Command Commands}. 162 * 163 * <p>Once the {@link Command Command} is started, it will run until it finishes, is interrupted, 164 * or the time expires, whichever is sooner. Note that the given {@link Command Command} will have no 165 * knowledge that it is on a timer.</p> 166 * 167 * <p>Instead of waiting for the child to finish, a {@link CommandGroup} will have it 168 * run at the same time as the subsequent {@link Command Commands}. The child will run until either 169 * it finishes, the timeout expires, a new child with conflicting requirements is started, or 170 * the main sequence runs a {@link Command} with conflicting requirements. In the latter 171 * two cases, the child will be canceled even if it says it can't be 172 * interrupted.</p> 173 * 174 * <p>Note that any requirements the given {@link Command Command} has will be added to the 175 * group. For this reason, a {@link Command Command's} requirements can not be changed after 176 * being added to a group.</p> 177 * 178 * <p>It is recommended that this method be called in the constructor.</p> 179 * 180 * @param command The command to be added 181 * @param timeout The timeout (in seconds) 182 * @throws IllegalUseOfCommandException if the group has been started before or been given to another command group 183 * @throws IllegalArgumentException if command is null 184 */ 185 public synchronized final void addParallel(Command command, double timeout) { 186 validate("Can not add new command to command group"); 187 if (command == null) { 188 throw new NullPointerException("Given null command"); 189 } 190 if (timeout < 0) { 191 throw new IllegalArgumentException("Can not be given a negative timeout"); 192 } 193 194 command.setParent(this); 195 196 m_commands.addElement(new Entry(command, Entry.BRANCH_CHILD, timeout)); 197 for (Enumeration e = command.getRequirements(); e.hasMoreElements();) { 198 requires((Subsystem) e.nextElement()); 199 } 200 } 201 202 void _initialize() { 203 m_currentCommandIndex = -1; 204 } 205 206 void _execute() { 207 Entry entry = null; 208 Command cmd = null; 209 boolean firstRun = false; 210 if (m_currentCommandIndex == -1) { 211 firstRun = true; 212 m_currentCommandIndex = 0; 213 } 214 215 while (m_currentCommandIndex < m_commands.size()) { 216 217 if (cmd != null) { 218 if (entry.isTimedOut()) { 219 cmd._cancel(); 220 } 221 if (cmd.run()) { 222 break; 223 } else { 224 cmd.removed(); 225 m_currentCommandIndex++; 226 firstRun = true; 227 cmd = null; 228 continue; 229 } 230 } 231 232 entry = (Entry) m_commands.elementAt(m_currentCommandIndex); 233 cmd = null; 234 235 switch (entry.state) { 236 case Entry.IN_SEQUENCE: 237 cmd = entry.command; 238 if (firstRun) { 239 cmd.startRunning(); 240 cancelConflicts(cmd); 241 } 242 firstRun = false; 243 break; 244 case Entry.BRANCH_PEER: 245 m_currentCommandIndex++; 246 entry.command.start(); 247 break; 248 case Entry.BRANCH_CHILD: 249 m_currentCommandIndex++; 250 cancelConflicts(entry.command); 251 entry.command.startRunning(); 252 m_children.addElement(entry); 253 break; 254 } 255 } 256 257 // Run Children 258 for (int i = 0; i < m_children.size(); i++) { 259 entry = (Entry) m_children.elementAt(i); 260 Command child = entry.command; 261 if (entry.isTimedOut()) { 262 child._cancel(); 263 } 264 if (!child.run()) { 265 child.removed(); 266 m_children.removeElementAt(i--); 267 } 268 } 269 } 270 271 void _end() { 272 // Theoretically, we don't have to check this, but we do if teams override the isFinished method 273 if (m_currentCommandIndex != -1 && m_currentCommandIndex < m_commands.size()) { 274 Command cmd = ((Entry) m_commands.elementAt(m_currentCommandIndex)).command; 275 cmd._cancel(); 276 cmd.removed(); 277 } 278 279 Enumeration children = m_children.elements(); 280 while (children.hasMoreElements()) { 281 Command cmd = ((Entry) children.nextElement()).command; 282 cmd._cancel(); 283 cmd.removed(); 284 } 285 m_children.removeAllElements(); 286 } 287 288 void _interrupted() { 289 _end(); 290 } 291 292 /** 293 * Returns true if all the {@link Command Commands} in this group 294 * have been started and have finished. 295 * 296 * <p>Teams may override this method, although they should probably 297 * reference super.isFinished() if they do.</p> 298 * 299 * @return whether this {@link CommandGroup} is finished 300 */ 301 protected boolean isFinished() { 302 return m_currentCommandIndex >= m_commands.size() && m_children.isEmpty(); 303 } 304 305 // Can be overwritten by teams 306 protected void initialize() { 307 } 308 309 // Can be overwritten by teams 310 protected void execute() { 311 } 312 313 // Can be overwritten by teams 314 protected void end() { 315 } 316 317 // Can be overwritten by teams 318 protected void interrupted() { 319 } 320 321 /** 322 * Returns whether or not this group is interruptible. 323 * A command group will be uninterruptible if {@link CommandGroup#setInterruptible(boolean) setInterruptable(false)} 324 * was called or if it is currently running an uninterruptible command 325 * or child. 326 * 327 * @return whether or not this {@link CommandGroup} is interruptible. 328 */ 329 public synchronized boolean isInterruptible() { 330 if (!super.isInterruptible()) { 331 return false; 332 } 333 334 if (m_currentCommandIndex != -1 && m_currentCommandIndex < m_commands.size()) { 335 Command cmd = ((Entry) m_commands.elementAt(m_currentCommandIndex)).command; 336 if (!cmd.isInterruptible()) { 337 return false; 338 } 339 } 340 341 for (int i = 0; i < m_children.size(); i++) { 342 if (!((Entry) m_children.elementAt(i)).command.isInterruptible()) { 343 return false; 344 } 345 } 346 347 return true; 348 } 349 350 private void cancelConflicts(Command command) { 351 for (int i = 0; i < m_children.size(); i++) { 352 Command child = ((Entry) m_children.elementAt(i)).command; 353 354 Enumeration requirements = command.getRequirements(); 355 356 while (requirements.hasMoreElements()) { 357 Object requirement = requirements.nextElement(); 358 if (child.doesRequire((Subsystem) requirement)) { 359 child._cancel(); 360 child.removed(); 361 m_children.removeElementAt(i--); 362 break; 363 } 364 } 365 } 366 } 367 368 private static class Entry { 369 370 private static final int IN_SEQUENCE = 0; 371 private static final int BRANCH_PEER = 1; 372 private static final int BRANCH_CHILD = 2; 373 Command command; 374 int state; 375 double timeout; 376 377 Entry(Command command, int state) { 378 this.command = command; 379 this.state = state; 380 this.timeout = -1; 381 } 382 383 Entry(Command command, int state, double timeout) { 384 this.command = command; 385 this.state = state; 386 this.timeout = timeout; 387 } 388 389 boolean isTimedOut() { 390 if (timeout == -1) { 391 return false; 392 } else { 393 double time = command.timeSinceInitialized(); 394 return time == 0 ? false : time >= timeout; 395 } 396 } 397 } 398 }