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}