StringConverters.java

// Generated by delombok at Mon Nov 18 07:27:48 UTC 2024
package de.larssh.utils.text;

import static java.util.stream.Collectors.joining;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collection;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * This class contains helper methods for decoding and encoding strings.
 */
public final class StringConverters {
	/**
	 * Decodes {@code value} as Base64 string.
	 *
	 * <p>
	 * This method is equivalent to using {@link Base64#getDecoder()} with the char
	 * set {@link Strings#DEFAULT_CHARSET}.
	 *
	 * @param value the value to be decoded
	 * @return the decoded value
	 */
	public static String decodeBase64(final String value) {
		return new String(Base64.getDecoder().decode(value), Strings.DEFAULT_CHARSET);
	}

	/**
	 * Decodes {@code value} using the Base64 MIME type decoding scheme.
	 *
	 * <p>
	 * This method is equivalent to using {@link Base64#getMimeDecoder()} with the
	 * char set {@link Strings#DEFAULT_CHARSET}.
	 *
	 * @param value the value to be decoded
	 * @return the decoded value
	 */
	public static String decodeBase64Mime(final String value) {
		return new String(Base64.getMimeDecoder().decode(value), Strings.DEFAULT_CHARSET);
	}

	/**
	 * Decodes {@code value} as Base64 URL and filename-safe string.
	 *
	 * <p>
	 * This method is equivalent to using {@link Base64#getUrlDecoder()} with the
	 * char set {@link Strings#DEFAULT_CHARSET}.
	 *
	 * @param value the value to be decoded
	 * @return the decoded value
	 */
	public static String decodeBase64Url(final String value) {
		return new String(Base64.getUrlDecoder().decode(value), Strings.DEFAULT_CHARSET);
	}

	/**
	 * Parses the CSV data given by {@code data}.
	 *
	 * <p>
	 * To process {@link Reader} input, refer to
	 * {@link Csv#parse(Reader, char, char)}.
	 *
	 * @param data      the CSV input data
	 * @param separator the CSV separator character
	 * @param escaper   the CSV escaping character
	 * @return an object representing the parsed CSV data
	 * @throws IllegalArgumentException on illegal {@code separator} or
	 *                                  {@code escaper} value
	 */
	@SuppressFBWarnings(value = "EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS", justification = "converting checked to unchecked exception, that should never be thrown at all")
	public static Csv decodeCsv(final String data, final char separator, final char escaper) {
		try (Reader reader = new StringReader(data)) {
			return Csv.parse(reader, separator, escaper);
		} catch (final IOException e) {
			throw new UncheckedIOException(e);
		}
	}

	/**
	 * Decodes an {@code application/x-www-form-urlencoded} string.
	 *
	 * <p>
	 * This method is equivalent to using {@link URLDecoder#decode(String)} with the
	 * char set {@link StandardCharsets#UTF_8} as the
	 * <a href= "https://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
	 * World Wide Web Consortium Recommendation</a> states that UTF-8 should be
	 * used.
	 *
	 * @param value the value to be decoded
	 * @return the decoded value
	 * @throws StringParseException on illegal or incomplete hex characters
	 */
	@SuppressFBWarnings(value = "EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS", justification = "converting checked to unchecked exception, that should never be thrown at all")
	public static String decodeUrl(final String value) throws StringParseException {
		try {
			return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
		} catch (final IllegalArgumentException e) {
			throw new StringParseException(e, "Failed decoding URL.");
		} catch (final UnsupportedEncodingException e) {
			throw new UncheckedIOException(e);
		}
	}

	/**
	 * Translates {@code value} into Base64 format.
	 *
	 * <p>
	 * This method is equivalent to using {@link Base64#getEncoder()} with the char
	 * set {@link Strings#DEFAULT_CHARSET}.
	 *
	 * @param value the value to be encoded
	 * @return the encoded value
	 */
	public static String encodeBase64(final String value) {
		return new String(Base64.getEncoder().encode(value.getBytes(Strings.DEFAULT_CHARSET)), Strings.DEFAULT_CHARSET);
	}

	/**
	 * Translates {@code value} into Base64 MIME type encoding scheme.
	 *
	 * <p>
	 * This method is equivalent to using {@link Base64#getMimeEncoder()} with the
	 * char set {@link Strings#DEFAULT_CHARSET}.
	 *
	 * @param value the value to be encoded
	 * @return the encoded value
	 */
	public static String encodeBase64Mime(final String value) {
		return new String(Base64.getMimeEncoder().encode(value.getBytes(Strings.DEFAULT_CHARSET)), Strings.DEFAULT_CHARSET);
	}

	/**
	 * Translates {@code value} into Base64 URL and filename-safe format.
	 *
	 * <p>
	 * This method is equivalent to using {@link Base64#getUrlEncoder()} with the
	 * charset {@link Strings#DEFAULT_CHARSET}.
	 *
	 * @param value the value to be encoded
	 * @return the encoded value
	 */
	public static String encodeBase64Url(final String value) {
		return new String(Base64.getUrlEncoder().encode(value.getBytes(Strings.DEFAULT_CHARSET)), Strings.DEFAULT_CHARSET);
	}

	/**
	 * Encodes {@code data} as CSV document. Occurrences of special characters are
	 * encoded using {@code escaper} and values are separated using
	 * {@code separator}.
	 *
	 * <p>
	 * For more information on single value escaping see
	 * {@link #encodeCsvValue(String, char, char)}.
	 *
	 * @param data      the data to be encoded
	 * @param separator the CSV separator character
	 * @param escaper   the CSV escaping character
	 * @return the encoded CSV document
	 */
	public static String encodeCsv(final Collection<? extends Collection<String>> data, final char separator, final char escaper) {
		return  //
		data.stream().map(row -> encodeCsvRow(row, separator, escaper)).collect(joining(Strings.NEW_LINE));
	}

	/**
	 * Encodes {@code values} as one CSV row. Occurrences of special characters are
	 * encoded using {@code escaper} and values are separated using
	 * {@code separator}.
	 *
	 * <p>
	 * For more information on single value escaping see
	 * {@link #encodeCsvValue(String, char, char)}.
	 *
	 * @param values    the values to be encoded
	 * @param separator the CSV separator character
	 * @param escaper   the CSV escaping character
	 * @return the encoded CSV row
	 */
	public static String encodeCsvRow(final Collection<? extends String> values, final char separator, final char escaper) {
		return values.stream().map(value -> encodeCsvValue(value, separator, escaper)).collect(joining(Character.toString(separator)));
	}

	/**
	 * Encodes {@code value} into a single CSV value. All occurrences of
	 * {@code escaper} are escaped (doubled). If {@code value} contains
	 * inappropriate characters, it is surrounded by {@code escaper}.
	 *
	 * <p>
	 * Inappropriate characters are {@code '\r'}, {@code '\n'}, {@code escaper} and
	 * {@code separator}. The latter is used for the list of inappropriate
	 * characters only.
	 *
	 * <p>
	 * <b>Examples:</b>
	 * <table>
	 * <caption>Examples</caption>
	 * <tr>
	 * <th>Parameter</th>
	 * <th>Return Value</th>
	 * </tr>
	 * <tr>
	 * <td>EMPTY</td>
	 * <td>EMPTY</td>
	 * </tr>
	 * <tr>
	 * <td>abc</td>
	 * <td>abc</td>
	 * </tr>
	 * <tr>
	 * <td>abc"xyz</td>
	 * <td>"abc""xyz"</td>
	 * </tr>
	 * <tr>
	 * <td>"</td>
	 * <td>""""</td>
	 * </tr>
	 * </table>
	 *
	 * @param value     the value to be encoded
	 * @param separator the CSV separator character
	 * @param escaper   the CSV escaping character
	 * @return the encoded value
	 */
	@SuppressWarnings("PMD.CyclomaticComplexity")
	public static String encodeCsvValue(final String value, final char separator, final char escaper) {
		CsvParser.assertCsvInput(separator, escaper);
		final int length = value.length();
		int index = 0;
		boolean needsEscaping = false;
		for (; index < length && !needsEscaping; index += 1) {
			final char character = value.charAt(index);
			if (character == escaper) {
				needsEscaping = true;
				index -= 1;
			} else if (character == separator || character == '\r' || character == '\n') {
				needsEscaping = true;
			}
		}
		if (!needsEscaping) {
			return value;
		}
		final StringBuilder builder = new StringBuilder(length + 2);
		builder.append(escaper);
		if (index > 0) {
			builder.append(value, 0, index);
		}
		for (; index < length; index += 1) {
			final char character = value.charAt(index);
			if (character == escaper) {
				builder.append(escaper);
			}
			builder.append(character);
		}
		return builder.append(escaper).toString();
	}

	/**
	 * Translates a string into {@code application/x-www-form-urlencoded} format
	 * with the char set {@link StandardCharsets#UTF_8} as the
	 * <a href= "https://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
	 * World Wide Web Consortium Recommendation</a> states that UTF-8 should be
	 * used.
	 *
	 * @param value the value to be encoded
	 * @return the encoded value
	 */
	@SuppressFBWarnings(value = "EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS", justification = "converting checked to unchecked exception, that should never be thrown at all")
	public static String encodeUrl(final String value) {
		try {
			return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
		} catch (final UnsupportedEncodingException e) {
			throw new UncheckedIOException(e);
		}
	}

	@java.lang.SuppressWarnings("all")
	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
	@lombok.Generated
	private StringConverters() {
		throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated");
	}
}