class ErrorMessageCollector implements Collector<String, StringJoiner, Optional<String>> {

    private final String prefix;
    private final String emptyString;
    private final boolean returnNullWhenEmpty;

    /**
     * Creates a collector with given prefix and
     * returns optional empty for empty collection.
     * @param prefix prefix before the joined string
     */
    public ErrorMessageCollector(@NotNull String prefix) {
        Objects.requireNonNull(prefix);
        this.prefix = prefix;
        this.emptyString = "";
        this.returnNullWhenEmpty = true;
    }

    /**
     * Creates a string joiner with given prefix and empty string.
     * @param prefix prefix before the joined string
     * @param emptyString string to display if collection is empty
     */
    public ErrorMessageCollector(@NotNull String prefix, @NotNull String emptyString) {
        Objects.requireNonNull(prefix);
        Objects.requireNonNull(emptyString);
        this.prefix = prefix;
        this.emptyString = emptyString;
        this.returnNullWhenEmpty = false;

    }
    /**
     * A function that creates and returns a new mutable result container.
     *
     * @return a function which returns a new, mutable result container
     */
    @Override
    public Supplier<StringJoiner> supplier() {
        return () -> {
            var joiner = new StringJoiner(", ", prefix, "");
            joiner.setEmptyValue(emptyString);
            return joiner;
        };
    }

    /**
     * A function that folds a value into a mutable result container.
     *
     * @return a function which folds a value into a mutable result container
     */
    @Override
    public BiConsumer<StringJoiner, String> accumulator() {
        return StringJoiner::add;
    }

    /**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     *
     * @return a function which combines two partial results into a combined
     * result
     */
    @Override
    public BinaryOperator<StringJoiner> combiner() {
        return StringJoiner::merge;
    }

    /**
     * Perform the final transformation from the intermediate accumulation type
     * {@code A} to the final result type {@code R}.
     *
     * <p>If the characteristic {@code IDENTITY_FINISH} is
     * set, this function may be presumed to be an identity transform with an
     * unchecked cast from {@code A} to {@code R}.
     *
     * @return a function which transforms the intermediate result to the final
     * result
     */
    @Override
    public Function<StringJoiner, Optional<String>> finisher() {<FILL_FUNCTION_BODY>}

    /**
     * Returns a {@code Set} of {@code Collector.Characteristics} indicating
     * the characteristics of this Collector.  This set should be immutable.
     *
     * @return an immutable set of collector characteristics
     */
    @Override
    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
    }
}

        return stringJoiner ->
            Optional.of(stringJoiner.toString())
                    .filter(msg -> !(msg.isEmpty() && returnNullWhenEmpty));

    
<no_super_class>