001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.core.util;
007
008import java.util.function.Function;
009import java.util.regex.Matcher;
010import java.util.regex.Pattern;
011
012import edu.umd.cs.findbugs.annotations.NonNull;
013
014/**
015 * Provides a collection of utilities for checking and managing strings.
016 * <p>
017 * This utility class provides methods for string validation and manipulation,
018 * with strict null-safety guarantees. All methods in this class are thread-safe
019 * and throw appropriate exceptions for invalid inputs.
020 */
021public final class StringUtils {
022  private StringUtils() {
023    // disable construction
024  }
025
026  /**
027   * Require a non-empty string value.
028   *
029   * @param string
030   *          the object reference to check for emptiness
031   * @return {@code string} if not {@code null} or empty
032   * @throws NullPointerException
033   *           if {@code string} is {@code null}
034   * @throws IllegalArgumentException
035   *           if {@code string} is empty
036   */
037  @NonNull
038  public static String requireNonEmpty(@NonNull String string) {
039    return requireNonEmpty(string, "String is empty.");
040  }
041
042  /**
043   * Require a non-empty string value.
044   *
045   * @param string
046   *          the object reference to check for emptiness
047   * @param message
048   *          detail message to be used in the event that an {@code
049   *                IllegalArgumentException} is thrown
050   * @return {@code string} if not {@code null} or empty
051   * @throws NullPointerException
052   *           if {@code string} is {@code null}
053   * @throws IllegalArgumentException
054   *           if {@code string} is empty
055   */
056  @NonNull
057  public static String requireNonEmpty(@NonNull String string, @NonNull String message) {
058    if (string.isEmpty()) {
059      throw new IllegalArgumentException(message);
060    }
061    return string;
062  }
063
064  /**
065   * Searches for instances of {@code pattern} in {@code text}. Replace each
066   * matching occurrence using the {@code replacementFunction}.
067   * <p>
068   * This method builds a new string by efficiently copying unmatched segments and
069   * applying the replacement function only to matched portions.
070   *
071   * @param text
072   *          the text to search
073   * @param pattern
074   *          the pattern to search for
075   * @param replacementFunction
076   *          a function that will provided the replacement text
077   * @return the resulting text after replacing matching occurrences in
078   *         {@code text}
079   */
080  public static CharSequence replaceTokens(
081      @NonNull CharSequence text,
082      @NonNull Pattern pattern,
083      @NonNull Function<Matcher, CharSequence> replacementFunction) {
084    int lastIndex = 0;
085    StringBuilder retval = new StringBuilder();
086    Matcher matcher = pattern.matcher(text);
087    while (matcher.find()) {
088      retval.append(text, lastIndex, matcher.start())
089          .append(replacementFunction.apply(matcher));
090
091      lastIndex = matcher.end();
092    }
093    if (lastIndex < text.length()) {
094      retval.append(text, lastIndex, text.length());
095    }
096    return retval;
097  }
098
099}