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}