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