<commit_before>package org.threadly.load;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import org.threadly.concurrent.future.FutureUtils;
import org.threadly.concurrent.future.ListenableFuture;
import org.threadly.concurrent.future.SettableListenableFuture;
import org.threadly.load.ExecutableScript.ExecutionItem;
import org.threadly.load.ExecutableScript.ExecutionItem.ChildItems;
import org.threadly.util.ArgumentVerifier;
import org.threadly.util.Clock;

/**
 * <p>Provides the shared implementation among all execution step builders.  This also defines the 
 * minimum API set that all builders must implement.</p>
 * 
 * @author jent - <NAME>
 */
public abstract class AbstractScriptBuilder {
  protected final ArrayList<ExecutionItem> stepRunners;
  private final AtomicBoolean finalized;
  private int neededThreadCount;
  private Exception replacementException = null;

  protected AbstractScriptBuilder(AbstractScriptBuilder sourceBuilder) {
    if (sourceBuilder == null) {
      stepRunners = new ArrayList<ExecutionItem>();
      neededThreadCount = 1;
    } else {
      sourceBuilder.replaced();
      this.stepRunners = sourceBuilder.stepRunners;
      this.neededThreadCount = sourceBuilder.neededThreadCount;
    }
    this.finalized = new AtomicBoolean(false);
  }
  
  /**
   * Adds a step in the current test position which will log out the percent of completion for the 
   * entire test script.  The provided future will complete once the test has reached this point 
   * in the script.  The resulting double provided by the future is the percent of completion at 
   * this moment.  You can easily log this by adding a 
   * {@link org.threadly.concurrent.future.FutureCallback} to the returned future.  
   * 
   * If there are steps running in parallel at the time of execution for this progress future it 
   * should be noted the number is a best guess, as no locking occurs during determining the 
   * current progress.  
   * 
   * If the script is stopped (likely due to an error or step failure), this returned future will 
   * complete in an error state (ie {@link ListenableFuture#get()} will throw a 
   * {@code ExecutionException}.
   * 
   * @return A future which provide a double representing the percent of how much of the script has completed
   */
  public ListenableFuture<Double> addProgressFuture() {
    SettableListenableFuture<Double> slf = new SettableListenableFuture<Double>(false);
    addStep(new ProgressScriptStep(slf));
    return slf;
  }
  
  /**
   * Sets a new limit for the script steps to be executed at.  This will only take place for steps 
   * which execute AFTER this point in the script.  This is effectively adding a task at this 
   * point in the script that will set an limit on how fast the steps can run.  If you want your 
   * entire script to adher to this limit it must be done before any steps are added.  
   * 
   * This allows you to control request rates, allowing you at run time to adjust the rate at 
   * which executions are occurring.  
   * 
   * Provide a value of zero to disable this and allow steps to execute as fast as possible.
   * 
   * @param stepsPerSecondLimit Steps per second allowed to execute
   */
  public void setMaxScriptStepRate(double stepsPerSecondLimit) {
    addStep(new RateAdjustmentStep(stepsPerSecondLimit));
  }

  /**
   * Get builder which will switch to parallel construction.  Steps added to the returned 
   * builder will execute in parallel.  This returned parallel builders steps will not be 
   * executed until ALL sequential steps from this builder are completed.
   * 
   * By requesting a sequential builder steps can no longer be added to THIS builder.  
   * Attempting to do so will result in an {@link IllegalStateException}.
   * 
   * @return Builder which you can add additional test steps to run in parallel
   */
  public abstract ParallelScriptBuilder inParallel();
  
  /**
   * Get builder which will switch to sequential construction.  Steps added to the returned 
   * builder will execute one after the other.  This returned sequential builders steps will not 
   * be executed until ALL parallel steps from this builder are completed.
   * 
   * By requesting a sequential builder steps can no longer be added to THIS builder.  
   * Attempting to do so will result in an {@link IllegalStateException}.
   * 
   * @return Builder which you can add additional test steps to run sequentially
   */
  public abstract SequentialScriptBuilder inSequence();

  /**
   * Add a step to this builder.  For more specific step addition descriptions please see: 
   * {@link SequentialScriptBuilder#addStep(ScriptStep)} and 
   * {@link ParallelScriptBuilder#addStep(ScriptStep)}.
   * 
   * @param step Test step to add to builder
   */
  public abstract void addStep(ScriptStep step);
  
  /**
   * Add a {@link ExecutionItem} to this builder.  This is a private API so that we can add test 
   * steps which need access to the executing {@link AbstractScriptBuilder}.
   * 
   * @param chainItem Item to add to current execution chain
   */
  protected abstract void addStep(ExecutionItem chainItem);

  /**
   * Add a sequential series of steps to this builder.  Since the behavior of this depends on the 
   * current step, please see a more specific step addition behavior descriptions here: 
   * {@link SequentialScriptBuilder#addSteps(SequentialScriptBuilder)} and 
   * {@link ParallelScriptBuilder#addSteps(SequentialScriptBuilder)}.
   * 
   * @param sequentialSteps Test steps to be added to this builder
   */
  public abstract void addSteps(SequentialScriptBuilder sequentialSteps);

  /**
   * Add a parallel series of steps to this builder.  Since the behavior of this depends on the 
   * current step, please see a more specific step addition behavior descriptions here: 
   * {@link SequentialScriptBuilder#addSteps(SequentialScriptBuilder)} and 
   * {@link ParallelScriptBuilder#addSteps(SequentialScriptBuilder)}.
   * 
   * @param parallelSteps Test steps to be added to this builder
   */
  public abstract void addSteps(ParallelScriptBuilder parallelSteps);
  
  /**
   * Call to check how many threads this script will need to execute at the current build point.  
   * This can give you an idea of how intensely parallel this script is.
   * 
   * @return Number of threads to run script at it's most parallel point
   */
  public int getNeededThreadCount() {
    return neededThreadCount;
  }
  
  /**
   * Make a copy of the script chain.  This is necessary if you want to add the chain multiple 
   * times to another chain.  The returned chain will execute the same script step instances (so 
   * if added to a parallel builder make sure the script steps are thread safe).
   * 
   * @return A copy builder
   */
  public abstract AbstractScriptBuilder makeCopy();
  
  // TODO - javadoc
  protected void maybeUpdatedMaximumThreads(int currentValue) {
    if (neededThreadCount < currentValue) {
      neededThreadCount = currentValue;
    }
  }
  <commit_msg>Handy helper function to make it easier when adding chains of steps<commit_after>package org.threadly.load;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import org.threadly.concurrent.future.FutureUtils;
import org.threadly.concurrent.future.ListenableFuture;
import org.threadly.concurrent.future.SettableListenableFuture;
import org.threadly.load.ExecutableScript.ExecutionItem;
import org.threadly.load.ExecutableScript.ExecutionItem.ChildItems;
import org.threadly.util.ArgumentVerifier;
import org.threadly.util.Clock;

/**
 * <p>Provides the shared implementation among all execution step builders.  This also defines the 
 * minimum API set that all builders must implement.</p>
 * 
 * @author jent - <NAME>
 */
public abstract class AbstractScriptBuilder {
  protected final ArrayList<ExecutionItem> stepRunners;
  private final AtomicBoolean finalized;
  private int neededThreadCount;
  private Exception replacementException = null;

  protected AbstractScriptBuilder(AbstractScriptBuilder sourceBuilder) {
    if (sourceBuilder == null) {
      stepRunners = new ArrayList<ExecutionItem>();
      neededThreadCount = 1;
    } else {
      sourceBuilder.replaced();
      this.stepRunners = sourceBuilder.stepRunners;
      this.neededThreadCount = sourceBuilder.neededThreadCount;
    }
    this.finalized = new AtomicBoolean(false);
  }
  
  /**
   * Adds a step in the current test position which will log out the percent of completion for the 
   * entire test script.  The provided future will complete once the test has reached this point 
   * in the script.  The resulting double provided by the future is the percent of completion at 
   * this moment.  You can easily log this by adding a 
   * {@link org.threadly.concurrent.future.FutureCallback} to the returned future.  
   * 
   * If there are steps running in parallel at the time of execution for this progress future it 
   * should be noted the number is a best guess, as no locking occurs during determining the 
   * current progress.  
   * 
   * If the script is stopped (likely due to an error or step failure), this returned future will 
   * complete in an error state (ie {@link ListenableFuture#get()} will throw a 
   * {@code ExecutionException}.
   * 
   * @return A future which provide a double representing the percent of how much of the script has completed
   */
  public ListenableFuture<Double> addProgressFuture() {
    SettableListenableFuture<Double> slf = new SettableListenableFuture<Double>(false);
    addStep(new ProgressScriptStep(slf));
    return slf;
  }
  
  /**
   * Sets a new limit for the script steps to be executed at.  This will only take place for steps 
   * which execute AFTER this point in the script.  This is effectively adding a task at this 
   * point in the script that will set an limit on how fast the steps can run.  If you want your 
   * entire script to adher to this limit it must be done before any steps are added.  
   * 
   * This allows you to control request rates, allowing you at run time to adjust the rate at 
   * which executions are occurring.  
   * 
   * Provide a value of zero to disable this and allow steps to execute as fast as possible.
   * 
   * @param stepsPerSecondLimit Steps per second allowed to execute
   */
  public void setMaxScriptStepRate(double stepsPerSecondLimit) {
    addStep(new RateAdjustmentStep(stepsPerSecondLimit));
  }

  /**
   * Get builder which will switch to parallel construction.  Steps added to the returned 
   * builder will execute in parallel.  This returned parallel builders steps will not be 
   * executed until ALL sequential steps from this builder are completed.
   * 
   * By requesting a sequential builder steps can no longer be added to THIS builder.  
   * Attempting to do so will result in an {@link IllegalStateException}.
   * 
   * @return Builder which you can add additional test steps to run in parallel
   */
  public abstract ParallelScriptBuilder inParallel();
  
  /**
   * Get builder which will switch to sequential construction.  Steps added to the returned 
   * builder will execute one after the other.  This returned sequential builders steps will not 
   * be executed until ALL parallel steps from this builder are completed.
   * 
   * By requesting a sequential builder steps can no longer be added to THIS builder.  
   * Attempting to do so will result in an {@link IllegalStateException}.
   * 
   * @return Builder which you can add additional test steps to run sequentially
   */
  public abstract SequentialScriptBuilder inSequence();

  /**
   * Add a step to this builder.  For more specific step addition descriptions please see: 
   * {@link SequentialScriptBuilder#addStep(ScriptStep)} and 
   * {@link ParallelScriptBuilder#addStep(ScriptStep)}.
   * 
   * @param step Test step to add to builder
   */
  public abstract void addStep(ScriptStep step);
  
  /**
   * Add a {@link ExecutionItem} to this builder.  This is a private API so that we can add test 
   * steps which need access to the executing {@link AbstractScriptBuilder}.
   * 
   * @param chainItem Item to add to current execution chain
   */
  protected abstract void addStep(ExecutionItem chainItem);

  /**
   * Add a sequential series of steps to this builder.  Since the behavior of this depends on the 
   * current step, please see a more specific step addition behavior descriptions here: 
   * {@link SequentialScriptBuilder#addSteps(SequentialScriptBuilder)} and 
   * {@link ParallelScriptBuilder#addSteps(SequentialScriptBuilder)}.
   * 
   * @param sequentialSteps Test steps to be added to this builder
   */
  public abstract void addSteps(SequentialScriptBuilder sequentialSteps);

  /**
   * Add a parallel series of steps to this builder.  Since the behavior of this depends on the 
   * current step, please see a more specific step addition behavior descriptions here: 
   * {@link SequentialScriptBuilder#addSteps(SequentialScriptBuilder)} and 
   * {@link ParallelScriptBuilder#addSteps(SequentialScriptBuilder)}.
   * 
   * @param parallelSteps Test steps to be added to this builder
   */
  public abstract void addSteps(ParallelScriptBuilder parallelSteps);
  
  /**
   * Adds steps for an unknown builder type.  This does instance of checks and defers to either 
   * {@link #addSteps(ParallelScriptBuilder)} or {@link #addSteps(SequentialScriptBuilder)}.
   * 
   * @param scriptBuilder Script builder to add steps from
   */
  public void addSteps(AbstractScriptBuilder scriptBuilder) {
    if (scriptBuilder == null) {
      return;
    } else if (scriptBuilder instanceof SequentialScriptBuilder) {
      addSteps((SequentialScriptBuilder)scriptBuilder);
    } else if (scriptBuilder instanceof ParallelScriptBuilder) {
      addSteps((ParallelScriptBuilder)scriptBuilder);
    } else {
      throw new UnsupportedOperationException("Provided an unknown builder type: " + 
                                                scriptBuilder.getClass());
    }
  }
  
  /**
   * Call to check how many threads this script will need to execute at the current build point.  
   * This can give you an idea of how intensely parallel this script is.
   * 
   * @return Number of threads to run script at it's most parallel point
   */
  public int getNeededThreadCount() {
    return neededThreadCount;
  }
  
  /**
   * Make a copy of the script chain.  This is necessary if you want to add the chain multiple 
   * times to another chain.  The returned chain will execute the same script step instances (so 
   * if added to a parallel builder make sure the script steps are thread safe).
   * 
   * @return A copy builder
   */
  public abstract AbstractScriptBuilder makeCopy();
  
  // TODO - javadoc
  protected void maybeUpdatedMaximumThreads(int currentValue) {
    if (neededThreadCount < currentValue) {
      neededThreadCount = currentValue;
    }
  }
  
{'fraction_non_alphanumeric': 0.06201091602551457, 'fraction_numerical': 0.00013151837969356216, 'mean_word_length': 3.5642256902761105, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '934583', 'n_tokens_mistral': 3964, 'n_tokens_neox': 3805, 'n_words': 1693}