001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package org.wpilib.commands3;
006
007import static edu.wpi.first.util.ErrorMessages.requireNonNullParam;
008
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.List;
012import java.util.function.BooleanSupplier;
013import java.util.stream.Collectors;
014import org.wpilib.annotation.NoDiscard;
015
016/**
017 * A builder class to configure and then create a {@link SequentialGroup}. Like {@link
018 * StagedCommandBuilder}, the final command is created by calling the terminal {@link
019 * #named(String)} method, or with an automatically generated name using {@link
020 * #withAutomaticName()}.
021 */
022@NoDiscard
023public class SequentialGroupBuilder {
024  private final List<Command> m_steps = new ArrayList<>();
025  private BooleanSupplier m_endCondition;
026
027  /**
028   * Creates new SequentialGroupBuilder. The builder will have no commands and have no preapplied
029   * configuration options. Use {@link #andThen(Command)} to add commands to the sequence.
030   */
031  public SequentialGroupBuilder() {}
032
033  /**
034   * Adds a command to the sequence.
035   *
036   * @param next The next command in the sequence
037   * @return The builder object, for chaining
038   */
039  public SequentialGroupBuilder andThen(Command next) {
040    requireNonNullParam(next, "next", "SequentialGroupBuilder.andThen");
041
042    m_steps.add(next);
043    return this;
044  }
045
046  /**
047   * Adds commands to the sequence. Commands will be added to the sequence in the order they are
048   * passed to this method.
049   *
050   * @param nextCommands The next commands in the sequence
051   * @return The builder object, for chaining
052   */
053  public SequentialGroupBuilder andThen(Command... nextCommands) {
054    requireNonNullParam(nextCommands, "nextCommands", "SequentialGroupBuilder.andThen");
055    for (int i = 0; i < nextCommands.length; i++) {
056      requireNonNullParam(
057          nextCommands[i], "nextCommands[" + i + "]", "SequentialGroupBuilder.andThen");
058    }
059
060    m_steps.addAll(Arrays.asList(nextCommands));
061    return this;
062  }
063
064  /**
065   * Adds an end condition to the command group. If this condition is met before all required
066   * commands have completed, the group will exit early. If multiple end conditions are added (e.g.
067   * {@code .until(() -> conditionA()).until(() -> conditionB())}), then the last end condition
068   * added will be used and any previously configured condition will be overridden.
069   *
070   * @param endCondition The end condition for the group
071   * @return The builder object, for chaining
072   */
073  public SequentialGroupBuilder until(BooleanSupplier endCondition) {
074    m_endCondition = endCondition;
075    return this;
076  }
077
078  /**
079   * Creates the sequence command, giving it the specified name.
080   *
081   * @param name The name of the sequence command
082   * @return The built command
083   */
084  public Command named(String name) {
085    var seq = new SequentialGroup(name, m_steps);
086    if (m_endCondition != null) {
087      // No custom end condition, return the group as is
088      return seq;
089    }
090
091    // We have a custom end condition, so we need to wrap the group in a race
092    return new ParallelGroupBuilder()
093        .optional(seq, Command.waitUntil(m_endCondition).named("Until Condition"))
094        .named(name);
095  }
096
097  /**
098   * Creates the sequence command, giving it an automatically generated name based on the commands
099   * in the sequence.
100   *
101   * @return The built command
102   */
103  public Command withAutomaticName() {
104    return named(m_steps.stream().map(Command::name).collect(Collectors.joining(" -> ")));
105  }
106}