All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sentrysoftware.metricshub.engine.common.helpers.StringHelper Maven / Gradle / Ivy

The newest version!
package org.sentrysoftware.metricshub.engine.common.helpers;

/*-
 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
 * MetricsHub Engine
 * ჻჻჻჻჻჻
 * Copyright 2023 - 2024 Sentry Software
 * ჻჻჻჻჻჻
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
 */

import static org.sentrysoftware.metricshub.engine.common.helpers.MetricsHubConstants.COMMA;
import static org.sentrysoftware.metricshub.engine.common.helpers.MetricsHubConstants.EMPTY;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.sentrysoftware.metricshub.engine.common.exception.InvalidConfigurationException;

/**
 * Helper class for working with strings.
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class StringHelper {

	/**
	 * Execute the given callable to get the resulting Object as String value.
	 *
	 * @param call         Callable providing a value.
	 * @param defaultValue The default value to return if the callable returns null or empty.
	 * @return String value.
	 */
	public static String getValue(final Callable call, final String defaultValue) {
		final Object result = callIfPossible(call);
		final String value = (result != null) ? result.toString() : null;
		if (value == null || value.isBlank()) {
			return defaultValue;
		}
		return value;
	}

	/**
	 * Call the callable and return the result. Return null if an exception occurs
	 *
	 * @param call callback to run
	 * @return Object value
	 */
	private static Object callIfPossible(final Callable call) {
		try {
			return call.call();
		} catch (Exception ex) {
			return null;
		}
	}

	/**
	 * Iterates over all the throwable causes and extract all the messages.
	 *
	 * @param throwable The {@link Throwable} instance we wish to process
	 * @return String value
	 */
	public static String getStackMessages(final Throwable throwable) {
		if (throwable == null) {
			return EMPTY;
		}

		return new StringBuilder(throwable.getClass().getSimpleName())
			.append(": ")
			.append(throwable.getMessage())
			.append("\n")
			.append(
				Stream
					.iterate(throwable, Objects::nonNull, Throwable::getCause)
					.filter(th -> th != throwable)
					.map(th -> String.format("Caused by %s: %s", th.getClass().getSimpleName(), th.getMessage()))
					.collect(Collectors.joining("\n"))
			)
			.toString();
	}

	/**
	 * Format the given HTTP headers map as the following example:
* * Content-Type: application/json
* Connection: keep-alive *
* * @param headers Key-Value collection * @return String value */ public static String prettyHttpHeaders(final Map headers) { if (headers == null) { return EMPTY; } return headers .entrySet() .stream() .map(entry -> String.format("%s: %s", entry.getKey(), entry.getValue())) .sorted() .collect(Collectors.joining("\n")); } /** * Add the given prefix and value to the {@link StringJoiner} instance. null value is not added. * * @param the type of the value to be added * @param stringJoiner {@link StringJoiner} instance used to append the prefix and the value. * @param prefix The value prefix. * @param value The value to add. */ public static void addNonNull( @NonNull final StringJoiner stringJoiner, @NonNull final String prefix, final T value ) { if (value != null) { stringJoiner.add(new StringBuilder(prefix).append(value)); } } /** * Replace each substring of the template that matches the literal * macro sequence with the value specified by the replacementSupplier. * * @param macro The sequence of char values to be replaced * @param replacementSupplier The supplier of the replacement sequence * @param template The template to replace * @return String value */ public static String replace( @NonNull final String macro, @NonNull final Supplier replacementSupplier, @NonNull final String template ) { if (template.contains(macro)) { return template.replace(macro, getValue(replacementSupplier::get, macro)); } return template; } /** * Replace each substring of the template that matches the * literal macro sequence with the specified literal * replacement sequence. * * @param macro The sequence of char values to be replaced * @param replacement The replacement sequence * @param template The template to replace * @return String value */ public static String replace(@NonNull final String macro, final String replacement, @NonNull final String template) { if (template.contains(macro) && replacement != null) { return template.replace(macro, replacement); } return template; } /** * Convert the given value to a string representation. If the value is a collection or an array, * it is transformed into a CSV (Comma-Separated Values) string. * * @param value The value to be converted to a string. * @return The string representation of the value, or a CSV string if the value is a collection or an array. */ public static String stringify(final Object value) { if (value == null) { // Handle null input return EMPTY; } else if (value instanceof Collection collection) { // If the input is a List, convert it to a CSV string return collection.stream().map(item -> item != null ? item.toString() : EMPTY).collect(Collectors.joining(COMMA)); } else if (value.getClass().isArray()) { // If the input is an array, convert it to a CSV string Object[] array = (Object[]) value; return Arrays .stream(array) .map(item -> item != null ? item.toString() : EMPTY) .collect(Collectors.joining(COMMA)); } else { // For any other type of value, simply convert it to a string return value.toString(); } } /** * Checks if the specified string is non-null and non-blank. * * @param value The string to be checked. * @return {@code true} if the string is non-null and contains at least one non-whitespace character, {@code false} otherwise. */ public static boolean nonNullNonBlank(final String value) { return value != null && !value.isBlank(); } /** * Validate the attribute against a predetermined test * * @param attribute Value getting compared * @param errorChecker Logic test comparing our value * @param messageSupplier error message supplier * @throws InvalidConfigurationException in case the validation fail */ public static void validateConfigurationAttribute( final T attribute, final Predicate errorChecker, final Supplier messageSupplier ) throws InvalidConfigurationException { if (errorChecker.test(attribute)) { log.error(messageSupplier.get()); throw new InvalidConfigurationException(messageSupplier.get()); } } }