class ThreadLocalErrorCollector implements AssertionErrorCollector {

    InheritableThreadLocal<AssertionErrorCollector> threadLocal = new InheritableThreadLocal<>();

    @Override
    public Optional<AssertionErrorCollector> getDelegate() {
      return Optional.of(threadLocal.get());
    }

    @Override
    public void setDelegate(AssertionErrorCollector assertionErrorCollector) {
      threadLocal.set(assertionErrorCollector);
    }

    public void reset() {
      threadLocal.remove();
    }

    @Override
    public void collectAssertionError(AssertionError assertionError) {
      threadLocal.get().collectAssertionError(assertionError);
    }

    @Override
    public List<AssertionError> assertionErrorsCollected() {
      return threadLocal.get().assertionErrorsCollected();
    }

    @Override
    public void succeeded() {
      threadLocal.get().succeeded();
    }

    @Override
    public boolean wasSuccess() {
      return threadLocal.get().wasSuccess();
    }
  }

  static boolean isPerClass(ExtensionContext context) {
    return context.getTestInstanceLifecycle().map(x -> x == Lifecycle.PER_CLASS).orElse(false);
  }

  static boolean isAnnotatedConcurrent(ExtensionContext context) {
    return findAnnotation(context.getRequiredTestClass(), Execution.class).map(Execution::value)
                                                                          .map(x -> x == ExecutionMode.CONCURRENT)
                                                                          .orElse(false);
  }

  static boolean isPerClassConcurrent(ExtensionContext context) {
    return isPerClass(context) && isAnnotatedConcurrent(context);
  }

  @Override
  public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
    // find SoftAssertions fields in the test class hierarchy
    Collection<Field> softAssertionsFields = findFields(testInstance.getClass(),
                                                        field -> isAnnotated(field, InjectSoftAssertions.class),
                                                        HierarchyTraversalMode.BOTTOM_UP);
    for (Field softAssertionsField : softAssertionsFields) {
      checkIsNotStaticOrFinal(softAssertionsField);
      Class<? extends SoftAssertionsProvider> softAssertionsProviderClass = asSoftAssertionsProviderClass(softAssertionsField,
                                                                                                          softAssertionsField.getType());
      checkIsNotAbstract(softAssertionsField, softAssertionsProviderClass);
      checkHasDefaultConstructor(softAssertionsField, softAssertionsProviderClass);
      SoftAssertionsProvider softAssertions = getSoftAssertionsProvider(context, softAssertionsProviderClass);
      setTestInstanceSoftAssertionsField(testInstance, softAssertionsField, softAssertions);
    }
  }

  @Override
  public void beforeEach(ExtensionContext context) throws Exception {
    AssertionErrorCollector collector = getAssertionErrorCollector(context);

    if (isPerClassConcurrent(context)) {
      // If the current context is "per class+concurrent", then getSoftAssertionsProvider() will have already set the delegate
      // for all the soft assertions provider to the thread-local error collector, so all we need to do is set the tlec's value
      // for the current thread.
      ThreadLocalErrorCollector tlec = getThreadLocalCollector(context);
      tlec.setDelegate(collector);
    } else {
      // Make sure that all of the soft assertion provider instances have their delegate initialised to the assertion error
      // collector for the current context. Also check enclosing contexts (in the case of nested tests).
      while (initialiseDelegate(context, collector) && context.getParent().isPresent()) {
        context = context.getParent().get();
      }
    }
  }

  private static boolean initialiseDelegate(ExtensionContext context, AssertionErrorCollector collector) {
    Collection<SoftAssertionsProvider> providers = getSoftAssertionsProviders(context);
    if (providers == null) return false;
    providers.forEach(x -> x.setDelegate(collector));
    return context.getParent().isPresent();
  }

  @Override
  public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
    // Abort if parameter type is unsupported.
    if (isUnsupportedParameterType(parameterContext.getParameter())) return false;

    Executable executable = parameterContext.getDeclaringExecutable();
    // @Testable is used as a meta-annotation on @Test, @TestFactory, @TestTemplate, etc.
    boolean isTestableMethod = executable instanceof Method && isAnnotated(executable, Testable.class);
    if (!isTestableMethod) {
      throw new ParameterResolutionException(format("Configuration error: cannot resolve SoftAssertionsProvider instances for [%s]. Only test methods are supported.",
                                                    executable));
    }
    Class<?> parameterType = parameterContext.getParameter().getType();
    if (isAbstract(parameterType.getModifiers())) {
      throw new ParameterResolutionException(format("Configuration error: the resolved SoftAssertionsProvider implementation [%s] is abstract and cannot be instantiated.",
                                                    executable));
    }
    try {
      parameterType.getDeclaredConstructor();
    } catch (@SuppressWarnings("unused") Exception e) {
      throw new ParameterResolutionException(format("Configuration error: the resolved SoftAssertionsProvider implementation [%s] has no default constructor and cannot be instantiated.",
                                                    executable));
    }
    return true;
  }

  @Override
  public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {<FILL_FUNCTION_BODY>

    // The parameter type is guaranteed to be an instance of SoftAssertionsProvider
    @SuppressWarnings("unchecked")
    Class<? extends SoftAssertionsProvider> concreteSoftAssertionsProviderType = (Class<? extends SoftAssertionsProvider>) parameterContext.getParameter()
                                                                                                                                           .getType();
    SoftAssertionsProvider provider = ReflectionSupport.newInstance(concreteSoftAssertionsProviderType);
    provider.setDelegate(getAssertionErrorCollector(extensionContext));
    return provider;
  
<no_super_class>