| /* |
| * Copyright (c) 2010-2020 BSI Business Systems Integration AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * BSI Business Systems Integration AG - initial API and implementation |
| */ |
| package org.eclipse.scout.sdk.core.util; |
| |
| import static java.lang.System.lineSeparator; |
| import static org.eclipse.scout.sdk.core.util.Ensure.newFail; |
| |
| import java.beans.Introspector; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.PrintWriter; |
| import java.io.Reader; |
| import java.io.StringWriter; |
| import java.nio.ByteBuffer; |
| import java.nio.CharBuffer; |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.Objects; |
| import java.util.Optional; |
| |
| /** |
| * <h3>{@link Strings}</h3> Static utility methods to work with character sequences like {@link String}, |
| * {@link CharSequence}, {@link StringBuilder} or {@code char[]}. |
| * |
| * @since 6.1.0 |
| */ |
| public final class Strings { |
| |
| private static final int INDEX_NOT_FOUND = -1; |
| |
| private Strings() { |
| } |
| |
| /** |
| * Checks if the two arrays have the same content comparing the character case sensitive. |
| * |
| * <pre> |
| * first=null & second=null -> true |
| * first=a & second=a -> true |
| * first=abc & second=def -> false |
| * first=null & second=a -> false |
| * </pre> |
| * |
| * @param first |
| * The first array |
| * @param second |
| * The second array |
| * @return {@code true} if both have equal content or both are {@code null}. |
| */ |
| public static boolean equals(char[] first, char[] second) { |
| //noinspection ArrayEquality |
| if (first == second) { |
| return true; |
| } |
| if (first == null || second == null) { |
| return false; |
| } |
| if (first.length != second.length) { |
| return false; |
| } |
| for (int i = first.length; --i >= 0;) { |
| if (first[i] != second[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Checks if the two arrays have the same content comparing the character using the case sensitivity given. See |
| * {@link #equals(char[], char[])} for more details. |
| * |
| * @param first |
| * The first array |
| * @param second |
| * The second array |
| * @param isCaseSensitive |
| * specifies whether or not the equality should be case sensitive |
| * @return {@code true} if the two arrays are identical character by character according to the value of |
| * isCaseSensitive or if both are {@code null}. |
| */ |
| public static boolean equals(char[] first, char[] second, boolean isCaseSensitive) { |
| if (isCaseSensitive) { |
| return equals(first, second); |
| } |
| //noinspection ArrayEquality |
| if (first == second) { |
| return true; |
| } |
| if (first == null || second == null) { |
| return false; |
| } |
| if (first.length != second.length) { |
| return false; |
| } |
| for (int i = first.length; --i >= 0;) { |
| if (Character.toLowerCase(first[i]) != Character.toLowerCase(second[i])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Checks if the two {@link CharSequence}s have the same content comparing the character case sensitive. |
| * |
| * <pre> |
| * first=null & second=null -> true |
| * first=a & second=a -> true |
| * first=abc & second=def -> false |
| * first=null & second=a -> false |
| * </pre> |
| * |
| * @param first |
| * The first {@link CharSequence} |
| * @param second |
| * The second {@link CharSequence} |
| * @return {@code true} if both have equal content or both are {@code null}. |
| */ |
| public static boolean equals(CharSequence first, CharSequence second) { |
| if (first == second) { |
| return true; |
| } |
| if (first == null || second == null) { |
| return false; |
| } |
| if (first.length() != second.length()) { |
| return false; |
| } |
| for (int i = first.length(); --i >= 0;) { |
| if (first.charAt(i) != second.charAt(i)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Checks if the two {@link CharSequence}s have the same content comparing the character using the case sensitivity |
| * given. See {@link #equals(CharSequence, CharSequence)} for more details. |
| * |
| * @param first |
| * The first {@link CharSequence} |
| * @param second |
| * The second {@link CharSequence} |
| * @param isCaseSensitive |
| * specifies whether or not the equality should be case sensitive |
| * @return {@code true} if the two sequences are identical character by character according to the value of |
| * isCaseSensitive or if both are {@code null}. |
| */ |
| public static boolean equals(CharSequence first, CharSequence second, boolean isCaseSensitive) { |
| if (isCaseSensitive) { |
| return equals(first, second); |
| } |
| |
| if (first == second) { |
| return true; |
| } |
| if (first == null || second == null) { |
| return false; |
| } |
| if (first.length() != second.length()) { |
| return false; |
| } |
| for (int i = first.length(); --i >= 0;) { |
| if (Character.toLowerCase(first.charAt(i)) != Character.toLowerCase(second.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Gets the first index having the character given. |
| * |
| * @param toBeFound |
| * The character to search |
| * @param searchIn |
| * The array to search in. Must not be {@code null}. |
| * @return The first zero based index having the character given. |
| * @throws NullPointerException |
| * if the array is {@code null}. |
| */ |
| public static int indexOf(char toBeFound, char[] searchIn) { |
| return indexOf(toBeFound, searchIn, 0); |
| } |
| |
| /** |
| * Gets the first index having the character given. It starts searching at index start (inclusive) and searches to the |
| * end of the array. |
| * |
| * @param toBeFound |
| * The character to search |
| * @param searchIn |
| * The array to search in. Must not be {@code null}. |
| * @param start |
| * The first index to consider. |
| * @return The first zero based index between start and the end of the array. |
| * @throws NullPointerException |
| * if the array is {@code null}. |
| */ |
| public static int indexOf(char toBeFound, char[] searchIn, int start) { |
| return indexOf(toBeFound, searchIn, start, searchIn.length); |
| } |
| |
| /** |
| * Gets the first index having the character given. It starts searching at index start (inclusive) and stops before |
| * index end (exclusive). |
| * |
| * @param toBeFound |
| * The character to search |
| * @param searchIn |
| * The array to search in. Must not be {@code null}. |
| * @param start |
| * The first index to consider. |
| * @param end |
| * Where to stop searching (exclusive) |
| * @return The first zero based index between start and end having the character given. |
| * @throws NullPointerException |
| * if the array is {@code null}. |
| */ |
| public static int indexOf(char toBeFound, char[] searchIn, int start, int end) { |
| int limit = Math.min(end, searchIn.length); |
| for (int i = start; i < limit; ++i) { |
| if (toBeFound == searchIn[i]) { |
| return i; |
| } |
| } |
| return INDEX_NOT_FOUND; |
| } |
| |
| /** |
| * Gets the first index having the character given. |
| * |
| * @param toBeFound |
| * The character to search |
| * @param searchIn |
| * The {@link CharSequence} to search in. Must not be {@code null}. |
| * @return The first zero based index having the character given. |
| * @throws NullPointerException |
| * if the {@link CharSequence} is {@code null}. |
| */ |
| public static int indexOf(char toBeFound, CharSequence searchIn) { |
| return indexOf(toBeFound, searchIn, 0); |
| } |
| |
| /** |
| * Gets the first index having the character given. It starts searching at index start (inclusive) and searches to the |
| * end of the array. |
| * |
| * @param toBeFound |
| * The character to search |
| * @param searchIn |
| * The {@link CharSequence} to search in. Must not be {@code null}. |
| * @param start |
| * The first index to consider. |
| * @return The first zero based index between start and the end of the array. |
| * @throws NullPointerException |
| * if the {@link CharSequence} is {@code null}. |
| */ |
| public static int indexOf(char toBeFound, CharSequence searchIn, int start) { |
| return indexOf(toBeFound, searchIn, start, searchIn.length()); |
| } |
| |
| /** |
| * Gets the first index having the character given. It starts searching at index start (inclusive) and stops before |
| * index end (exclusive). |
| * |
| * @param toBeFound |
| * The character to search |
| * @param searchIn |
| * The {@link CharSequence} to search in. Must not be {@code null}. |
| * @param start |
| * The first index to consider. |
| * @param end |
| * Where to stop searching (exclusive) |
| * @return The first zero based index between start and end having the character given. |
| * @throws NullPointerException |
| * if the {@link CharSequence} is {@code null}. |
| */ |
| public static int indexOf(char toBeFound, CharSequence searchIn, int start, int end) { |
| int limit = Math.max(Math.min(end, searchIn.length()), 0); |
| for (int i = start; i < limit; i++) { |
| if (toBeFound == searchIn.charAt(i)) { |
| return i; |
| } |
| } |
| return INDEX_NOT_FOUND; |
| } |
| |
| /** |
| * Like {@link #indexOf(char[], char[])}. |
| */ |
| public static int indexOf(CharSequence toBeFound, CharSequence searchIn) { |
| return indexOf(toBeFound, searchIn, 0); |
| } |
| |
| /** |
| * Like {@link #indexOf(char[], char[], int)}. |
| */ |
| public static int indexOf(CharSequence toBeFound, CharSequence searchIn, int start) { |
| return indexOf(toBeFound, searchIn, start, searchIn.length()); |
| } |
| |
| /** |
| * Like {@link #indexOf(char[], char[], int, int)}. |
| */ |
| public static int indexOf(CharSequence toBeFound, CharSequence searchIn, int start, int end) { |
| int toBeFoundLength = toBeFound.length(); |
| if (toBeFoundLength > end || start < 0) { |
| return INDEX_NOT_FOUND; |
| } |
| if (toBeFoundLength == 0) { |
| return 0; |
| } |
| arrayLoop: for (int i = start, max = end - toBeFoundLength + 1; i < max; i++) { |
| if (searchIn.charAt(i) == toBeFound.charAt(0)) { |
| for (int j = 1; j < toBeFoundLength; j++) { |
| if (searchIn.charAt(i + j) != toBeFound.charAt(j)) { |
| continue arrayLoop; |
| } |
| } |
| return i; |
| } |
| } |
| return INDEX_NOT_FOUND; |
| } |
| |
| /** |
| * Like {@link #indexOf(char[], char[], int, int, boolean)} but performs a case sensitive search in the full array. |
| */ |
| public static int indexOf(char[] toBeFound, char[] searchIn) { |
| return indexOf(toBeFound, searchIn, 0); |
| } |
| |
| /** |
| * Like {@link #indexOf(char[], char[], int, int, boolean)} but performs a case sensitive search from the given start |
| * (inclusive) to the end of the array. |
| */ |
| public static int indexOf(char[] toBeFound, char[] searchIn, int start) { |
| return indexOf(toBeFound, searchIn, start, searchIn.length); |
| } |
| |
| /** |
| * Like {@link #indexOf(char[], char[], int, int, boolean)} but performs a case sensitive search. |
| */ |
| public static int indexOf(char[] toBeFound, char[] searchIn, int start, int end) { |
| return indexOf(toBeFound, searchIn, start, end, true); |
| } |
| |
| /** |
| * Answers the first index in searchIn for which toBeFound is a matching followup array. Answers -1 if no match is |
| * found.<br> |
| * Examples: |
| * <ol> |
| * <li> |
| * |
| * <pre> |
| * toBeFound = { 'c' } |
| * searchIn = { ' a', 'b', 'c', 'd' } |
| * result => 2 |
| * </pre> |
| * |
| * </li> |
| * <li> |
| * |
| * <pre> |
| * toBeFound = { 'e' } |
| * searchIn = { ' a', 'b', 'c', 'd' } |
| * result => -1 |
| * </pre> |
| * |
| * </li> |
| * <li> |
| * |
| * <pre> |
| * toBeFound = { 'b', 'c' } |
| * searchIn = { ' a', 'b', 'c', 'd' } |
| * result => 1 |
| * </pre> |
| * |
| * </li> |
| * </ol> |
| * |
| * @param toBeFound |
| * the subarray to search. Must not be {@code null}. |
| * @param searchIn |
| * the array to be searched in. Must not be {@code null}. |
| * @param start |
| * the starting index (inclusive) describing where in searchIn to begin searching. |
| * @param end |
| * the end index (exclusive) describing where in searchIn to stop searching. |
| * @param isCaseSensitive |
| * describes if the comparation should be case sensitive or not. |
| * @return the first index in searchIn for which the toBeFound array is a matching followup array or -1 if it cannot |
| * be found. |
| * @throws NullPointerException |
| * if searchIn is {@code null} or toBeFound is {@code null} |
| */ |
| public static int indexOf(char[] toBeFound, char[] searchIn, int start, int end, boolean isCaseSensitive) { |
| int toBeFoundLength = toBeFound.length; |
| if (toBeFoundLength > end || start < 0) { |
| return INDEX_NOT_FOUND; |
| } |
| if (toBeFoundLength == 0) { |
| return 0; |
| } |
| if (isCaseSensitive) { |
| arrayLoop: for (int i = start, max = end - toBeFoundLength + 1; i < max; i++) { |
| if (searchIn[i] == toBeFound[0]) { |
| for (int j = 1; j < toBeFoundLength; j++) { |
| if (searchIn[i + j] != toBeFound[j]) { |
| continue arrayLoop; |
| } |
| } |
| return i; |
| } |
| } |
| } |
| else { |
| arrayLoop: for (int i = start, max = end - toBeFoundLength + 1; i < max; i++) { |
| if (Character.toLowerCase(searchIn[i]) == Character.toLowerCase(toBeFound[0])) { |
| for (int j = 1; j < toBeFoundLength; j++) { |
| if (Character.toLowerCase(searchIn[i + j]) != Character.toLowerCase(toBeFound[j])) { |
| continue arrayLoop; |
| } |
| } |
| return i; |
| } |
| } |
| } |
| return INDEX_NOT_FOUND; |
| } |
| |
| /** |
| * Searches for the last index in the {@link CharSequence} which has the character given. |
| * |
| * @param toBeFound |
| * The character to find |
| * @param searchIn |
| * The {@link CharSequence} to search in. |
| * @return The last zero based index or -1 if it could not be found. |
| * @throws NullPointerException |
| * if the sequence is {@code null}. |
| */ |
| public static int lastIndexOf(char toBeFound, CharSequence searchIn) { |
| return lastIndexOf(toBeFound, searchIn, 0); |
| } |
| |
| /** |
| * Searches for the last index after the given startIndex which has the character specified. |
| * |
| * @param toBeFound |
| * The character to find. |
| * @param searchIn |
| * The {@link CharSequence} to search in. |
| * @param startIndex |
| * The index to start. |
| * @return The last zero based index after the startIndex or -1 if it could not be found. |
| * @throws NullPointerException |
| * if the sequence is {@code null}. |
| */ |
| public static int lastIndexOf(char toBeFound, CharSequence searchIn, int startIndex) { |
| return lastIndexOf(toBeFound, searchIn, startIndex, searchIn.length()); |
| } |
| |
| /** |
| * Searches for the last index between the startIndex and the endIndex having the given character. |
| * |
| * @param toBeFound |
| * The character to find. |
| * @param searchIn |
| * The {@link CharSequence} to search in. |
| * @param startIndex |
| * The index where to start the search. |
| * @param endIndex |
| * The index where to end the search. |
| * @returnThe last zero based index between the startIndex and the endIndex or -1 if it could not be found in this |
| * section. |
| * @throws NullPointerException |
| * if the sequence is {@code null}. |
| */ |
| @SuppressWarnings("squid:S881") |
| public static int lastIndexOf(char toBeFound, CharSequence searchIn, int startIndex, int endIndex) { |
| for (int i = endIndex; --i >= startIndex;) { |
| if (toBeFound == searchIn.charAt(i)) { |
| return i; |
| } |
| } |
| return INDEX_NOT_FOUND; |
| } |
| |
| /** |
| * Gets the next index after the given offset at which the current line ends. If invoked for the last line, the array |
| * length (end) is returned. |
| * |
| * @param searchIn |
| * The array to search in. |
| * @param offset |
| * The offset within the array where to start the search. |
| * @return The next line end character after the given offset. If no one can be found the array length is returned. |
| */ |
| @SuppressWarnings("HardcodedLineSeparator") |
| public static int nextLineEnd(char[] searchIn, int offset) { |
| int nlPos = indexOf('\n', searchIn, offset); |
| if (nlPos < 0) { |
| return searchIn.length; // no more newline found: search to the end of searchIn |
| } |
| if (nlPos > 0 && searchIn[nlPos - 1] == '\r') { |
| nlPos--; |
| } |
| return nlPos; |
| } |
| |
| /** |
| * Replaces all occurrences of a character in the specified {@link CharSequence} with another character. |
| * |
| * @param text |
| * The text in which the characters should be replaced. |
| * @param search |
| * The character to be replaced. |
| * @param replacement |
| * The new character to insert instead. |
| * @return A {@link CharSequence} with the new content or {@code null} if the input text is {@code null}. |
| */ |
| public static CharSequence replace(CharSequence text, char search, char replacement) { |
| if (text == null) { |
| return null; |
| } |
| if (text.length() < 1) { |
| return ""; |
| } |
| |
| StringBuilder result = new StringBuilder(text.length()); |
| for (int i = 0; i < text.length(); i++) { |
| char c = text.charAt(i); |
| if (c == search) { |
| result.append(replacement); |
| } |
| else { |
| result.append(c); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Converts the {@link StringBuilder} specified into a {@code char[]}. |
| * |
| * @param s |
| * The {@link StringBuilder} to convert. Must not be {@code null}. |
| * @return The full contents as {@code char[]}. |
| * @throws IllegalArgumentException |
| * if the {@link StringBuilder} is {@code null}. |
| */ |
| public static char[] toCharArray(StringBuilder s) { |
| Ensure.notNull(s); |
| char[] buf = new char[s.length()]; |
| s.getChars(0, buf.length, buf, 0); |
| return buf; |
| } |
| |
| /** |
| * <p> |
| * Repeat a {@link CharSequence} n times to form a new {@link CharSequence}. |
| * </p> |
| * <b>Examples:</b> |
| * |
| * <pre> |
| * repeat(null, 2) = null |
| * repeat("", 0) = "" |
| * repeat("", 2) = "" |
| * repeat("a", 3) = "aaa" |
| * repeat("ab", 2) = "abab" |
| * repeat("a", -2) = "" |
| * </pre> |
| * |
| * @param str |
| * the {@link CharSequence} to repeat, may be {@code null}. |
| * @param n |
| * number of times to repeat, negative treated as zero. |
| * @return a new {@link CharSequence} consisting of the original CharSequence repeated |
| */ |
| public static CharSequence repeat(CharSequence str, int n) { |
| if (str == null) { |
| return null; |
| } |
| if (n < 1 || str.length() < 1) { |
| return ""; |
| } |
| StringBuilder b = new StringBuilder(str.length() * n); |
| for (int i = 0; i < n; i++) { |
| b.append(str); |
| } |
| return b; |
| } |
| |
| /** |
| * <p> |
| * Replaces a {@link CharSequence} with another {@link CharSequence} inside a larger {@link CharSequence} |
| * </p> |
| * <p> |
| * A {@code null} reference passed to this method is a no-op. |
| * </p> |
| * <p> |
| * The difference of this method to {@link String#replace(CharSequence, CharSequence)} is that this implementation |
| * does not make use of a regular-expression-pattern and is {@code null} safe. |
| * </p> |
| * <b>Examples:</b> |
| * |
| * <pre> |
| * replace(null, *, *) = null |
| * replace("", *, *) = "" |
| * replace("any", null, *) = "any" |
| * replace("any", *, null) = "any" |
| * replace("any", "", *) = "any" |
| * replace("abaa", "a", null) = "abaa" |
| * replace("abaa", "a", "") = "b" |
| * replace("abaa", "a", "z") = "abaa" |
| * </pre> |
| * |
| * @param text |
| * text to search and replace in, may be {@code null}. |
| * @param searchString |
| * the {@link CharSequence} to search for, may be {@code null}. |
| * @param replacement |
| * the {@link CharSequence} to replace it with, may be {@code null}. |
| * @return the text with any replacements processed, {@code null} if {@code null} input. |
| */ |
| public static CharSequence replace(CharSequence text, CharSequence searchString, CharSequence replacement) { |
| if (isEmpty(text) || isEmpty(searchString) || replacement == null || Objects.equals(searchString, replacement)) { |
| return text; |
| } |
| int start = 0; |
| int end = indexOf(searchString, text, start); |
| if (end == INDEX_NOT_FOUND) { |
| return text; |
| } |
| int replLength = searchString.length(); |
| int increase = replacement.length() - replLength; |
| increase = Math.max(increase, 0); |
| increase *= 16; |
| StringBuilder buf = new StringBuilder(text.length() + increase); |
| while (end != INDEX_NOT_FOUND) { |
| buf.append(text, start, end).append(replacement); |
| start = end + replLength; |
| end = indexOf(searchString, text, start); |
| } |
| buf.append(text.subSequence(start, text.length())); |
| return buf; |
| } |
| |
| /** |
| * <p> |
| * Counts how many times the substring appears in the larger {@link CharSequence}. |
| * </p> |
| * <p> |
| * A {@code null} or empty ("") {@link CharSequence} input returns {@code 0}. |
| * </p> |
| * <p> |
| * <p> |
| * <p> |
| * |
| * <pre> |
| * countMatches(null, *) = 0 |
| * countMatches("", *) = 0 |
| * countMatches("abba", null) = 0 |
| * countMatches("abba", "") = 0 |
| * countMatches("abba", "a") = 2 |
| * countMatches("abba", "ab") = 1 |
| * countMatches("abba", "xxx") = 0 |
| * </pre> |
| * |
| * @param str |
| * the {@link CharSequence} to check, may be {@code null}. |
| * @param sub |
| * the substring to count, may be {@code null}. |
| * @return the number of occurrences, 0 if either {@link CharSequence} is {@code null}. |
| */ |
| public static int countMatches(CharSequence str, CharSequence sub) { |
| if (isEmpty(str) || isEmpty(sub)) { |
| return 0; |
| } |
| int count = 0; |
| int idx = 0; |
| while ((idx = indexOf(sub, str, idx)) != INDEX_NOT_FOUND) { |
| count++; |
| idx += sub.length(); |
| } |
| return count; |
| } |
| |
| /** |
| * <p> |
| * Checks if a CharSequence is empty ("") or {@code null}. |
| * </p> |
| * <p> |
| * <p> |
| * <p> |
| * |
| * <pre> |
| * Strings.isEmpty(null) = true |
| * Strings.isEmpty("") = true |
| * Strings.isEmpty(" ") = false |
| * Strings.isEmpty("bob") = false |
| * Strings.isEmpty(" bob ") = false |
| * </pre> |
| * |
| * @param cs |
| * the CharSequence to check, may be {@code null} |
| * @return {@code true} if the CharSequence is empty or {@code null} |
| */ |
| public static boolean isEmpty(CharSequence cs) { |
| return cs == null || cs.length() == 0; |
| } |
| |
| /** |
| * Reads all bytes from the given {@link InputStream} and converts them into a {@link StringBuilder} using the given |
| * charset name.<br> |
| * |
| * @param is |
| * The data source. Must not be {@code null}. |
| * @param charsetName |
| * The name of the {@link Charset} to use. Must be supported by the platform. |
| * @return A {@link StringBuilder} holding the contents. |
| * @throws IOException |
| * While reading data from the stream or if the given charsetName does not exist on this platform. |
| * @see Charset#isSupported(String) |
| */ |
| public static StringBuilder fromInputStream(InputStream is, String charsetName) throws IOException { |
| if (!Charset.isSupported(charsetName)) { |
| throw new IOException("Charset '" + charsetName + "' is not supported."); |
| } |
| return fromInputStream(is, Charset.forName(charsetName)); |
| } |
| |
| /** |
| * Creates a {@link String} holding the content of the file specified. |
| * |
| * @param file |
| * The file to load. Must not be {@code null}. |
| * @param charset |
| * The {@link Charset} to use to transform the bytes in the file into characters. Consider using one of the |
| * {@link StandardCharsets} constants. Must not be {@code null}. |
| * @return A {@link String} holding the content. |
| * @throws IOException |
| * If {@link Path} does not point to a readable file or there was an error during read. |
| */ |
| public static String fromFileAsString(Path file, Charset charset) throws IOException { |
| Ensure.notNull(charset); |
| return new String(fileRawBytes(file), charset); |
| } |
| |
| /** |
| * Creates a char array holding the content of the file specified. |
| * |
| * @param file |
| * The file to load. Must not be {@code null}. |
| * @param charset |
| * The {@link Charset} to use to transform the bytes in the file into characters. Consider using one of the |
| * {@link StandardCharsets} constants. Must not be {@code null}. |
| * @return The chars of the file. |
| * @throws IOException |
| * If {@link Path} does not point to a readable file or there was an error during read. |
| */ |
| public static char[] fromFileAsChars(Path file, Charset charset) throws IOException { |
| Ensure.notNull(charset); |
| return charset.decode(ByteBuffer.wrap(fileRawBytes(file))).array(); |
| } |
| |
| /** |
| * Creates a {@link CharSequence} holding the content of the file specified. |
| * |
| * @param file |
| * The file to load. Must not be {@code null}. |
| * @param charset |
| * The {@link Charset} to use to transform the bytes in the file into characters. Consider using one of the |
| * {@link StandardCharsets} constants. Must not be {@code null}. |
| * @return A {@link CharSequence} holding the content. |
| * @throws IOException |
| * If {@link Path} does not point to a readable file or there was an error during read. |
| */ |
| public static CharSequence fromFileAsCharSequence(Path file, Charset charset) throws IOException { |
| return CharBuffer.wrap(fromFileAsChars(file, charset)); |
| } |
| |
| private static byte[] fileRawBytes(Path file) throws IOException { |
| Ensure.notNull(file); |
| if (!Files.isRegularFile(file) || !Files.isReadable(file)) { |
| throw new IOException(file + " cannot be read."); |
| } |
| return Files.readAllBytes(file); |
| } |
| |
| /** |
| * Reads all bytes from the given {@link InputStream} and converts them into a {@link StringBuilder} using the given |
| * {@link Charset}.<br> |
| * The specified {@link InputStream} is not closed! |
| * |
| * @param is |
| * The data source. Must not be {@code null}. |
| * @param charset |
| * The {@link Charset} to use for the byte-to-char conversion. |
| * @return A {@link StringBuilder} holding the contents. |
| * @throws IOException |
| * While reading data from the stream. |
| */ |
| public static StringBuilder fromInputStream(InputStream is, Charset charset) throws IOException { |
| char[] buffer = new char[8192]; |
| StringBuilder out = new StringBuilder(buffer.length); |
| int length; |
| //noinspection resource,IOResourceOpenedButNotSafelyClosed |
| Reader in = new InputStreamReader(is, charset); |
| while ((length = in.read(buffer)) != INDEX_NOT_FOUND) { |
| out.append(buffer, 0, length); |
| } |
| return out; |
| } |
| |
| /** |
| * Converts the stack trace of the given {@link Throwable} into a {@link String}. |
| * <p> |
| * The resulting {@link String} contains no leading or trailing line separators. |
| * |
| * @param t |
| * The {@link Throwable}. Must not be {@code null}. |
| * @return The {@link String} describing the given {@link Throwable}. |
| */ |
| @SuppressWarnings({"squid:S1148", "squid:S1166"}) |
| public static String fromThrowable(Throwable t) { |
| try (StringWriter w = new StringWriter(); PrintWriter p = new PrintWriter(w)) { |
| t.printStackTrace(p); |
| StringBuffer buffer = w.getBuffer(); |
| buffer.delete(buffer.length() - lineSeparator().length(), buffer.length()); |
| return w.toString(); |
| } |
| catch (IOException e) { |
| return '[' + e.toString() + ']' + t; |
| } |
| } |
| |
| /** |
| * Converts the given input string literal into the representing original string.<br> |
| * This is the inverse function of {@link #toStringLiteral(CharSequence)}. |
| * |
| * @param s |
| * The literal with leading and ending double-quotes |
| * @return the original (un-escaped) string. if it is no valid literal string, {@code null} is returned. |
| */ |
| public static CharSequence fromStringLiteral(CharSequence s) { |
| if (s == null) { |
| return null; |
| } |
| return replaceLiterals(withoutQuotes(s), true); |
| } |
| |
| private static CharSequence replaceLiterals(CharSequence result, boolean fromLiteral) { |
| //noinspection HardcodedLineSeparator |
| CharSequence[] a = {"\b", "\t", "\n", "\f", "\r", "\"", "\\", "\0", "\1", "\2", "\3", "\4", "\5", "\6", "\7"}; |
| //noinspection HardcodedLineSeparator |
| CharSequence[] b = {"\\b", "\\t", "\\n", "\\f", "\\r", "\\\"", "\\\\", "\\0", "\\1", "\\2", "\\3", "\\4", "\\5", "\\6", "\\7"}; |
| |
| if (fromLiteral) { |
| return replaceEach(result, b, a); |
| } |
| return replaceEach(result, a, b); |
| } |
| |
| /** |
| * Converts the given {@link CharSequence} into a string literal with leading and ending double-quotes including |
| * escaping of the given string.<br> |
| * This is the inverse function of {@link #fromStringLiteral(CharSequence)}. |
| * |
| * @param s |
| * the string to convert. |
| * @return the literal string ready to be directly inserted into java source or null if the input string is null. |
| */ |
| public static CharSequence toStringLiteral(CharSequence s) { |
| if (s == null) { |
| return null; |
| } |
| |
| StringBuilder b = new StringBuilder(s.length() * 2); |
| b.append('"'); // opening delimiter |
| b.append(replaceLiterals(s, false)); |
| b.append('"'); // closing delimiter |
| return b; |
| } |
| |
| /** |
| * Removes leading or trailing double quotes ("), single quotes (') or back ticks (`) from the input. |
| * |
| * @param literal |
| * The literal from which the quotes should be removed. |
| * @return The input with removed quotes. |
| */ |
| public static CharSequence withoutQuotes(CharSequence literal) { |
| return withoutQuotes(literal, true, true, true); |
| } |
| |
| /** |
| * Removes leading and trailing quotes (if existing) from the literal given.<br> |
| * Only the first quotes are removed. If there are nested quotes, the are part of the result. |
| * |
| * @param literal |
| * The literal from which the quotes should be removed. |
| * @param removeDouble |
| * {@code true} if double quotes (") should be removed if found. |
| * @param removeSingle |
| * {@code true} if single quotes (') should be removed if found. |
| * @param removeBackTick |
| * {@code true} if back ticks (`) should be removed if found. |
| * @return The input with removed leading and trailing quotes respecting the enabled quote types. |
| */ |
| public static CharSequence withoutQuotes(CharSequence literal, boolean removeDouble, boolean removeSingle, boolean removeBackTick) { |
| if (literal == null || literal.length() < 2 || (!removeDouble && !removeSingle && !removeBackTick)) { |
| return literal; |
| } |
| |
| boolean[] enabled = new boolean[]{removeDouble, removeSingle, removeBackTick}; |
| char[] toRemove = new char[]{'"', '\'', '`'}; |
| for (int i = 0; i < toRemove.length; i++) { |
| if (enabled[i] && literal.charAt(0) == toRemove[i] && literal.charAt(literal.length() - 1) == toRemove[i]) { |
| return literal.subSequence(1, literal.length() - 1); |
| } |
| } |
| return literal; |
| } |
| |
| /** |
| * Ensures the given name starts with an upper case character.<br> |
| * <br> |
| * <b>Note:</b><br> |
| * To ensure the first char starts with a lower case letter use {@link Introspector#decapitalize(String)} |
| * |
| * @param name |
| * The name to handle. |
| * @return null if the input is null, an empty string if the given string is empty or only contains white spaces. |
| * Otherwise the input string is returned with the first character modified to upper case. |
| */ |
| public static CharSequence ensureStartWithUpperCase(CharSequence name) { |
| if (isEmpty(name) || Character.isUpperCase(name.charAt(0))) { |
| return name; |
| } |
| return new StringBuilder(name.length()) |
| .append(Character.toUpperCase(name.charAt(0))) |
| .append(name, 1, name.length()); |
| } |
| |
| /** |
| * Returns the given input HTML with all necessary characters escaped. |
| * |
| * @param html |
| * The input HTML. |
| * @return The escaped version. |
| */ |
| public static CharSequence escapeHtml(CharSequence html) { |
| return replaceEach(html, |
| new CharSequence[]{"\"", "&", "<", ">", "'", "/"}, |
| new CharSequence[]{"\", "&", "<", ">", "'", "/"}); |
| } |
| |
| /** |
| * <p> |
| * Replaces all occurrences of strings within another string. |
| * </p> |
| * <p> |
| * A {@code null} reference passed to this method is a no-op, or if any "search string" or "string to replace" is |
| * {@code null}, that replace will be ignored. |
| * </p> |
| * <p> |
| * <p> |
| * <p> |
| * |
| * <pre> |
| * replaceEach(null, *, *) = null |
| * replaceEach("", *, *) = "" |
| * replaceEach("aba", null, null) = "aba" |
| * replaceEach("aba", new String[0], null) = "aba" |
| * replaceEach("aba", null, new String[0]) = "aba" |
| * replaceEach("aba", new String[]{"a"}, null) = "aba" |
| * replaceEach("aba", new String[]{"a"}, new String[]{""}) = "b" |
| * replaceEach("aba", new String[]{null}, new String[]{"a"}) = "aba" |
| * replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte" |
| * </pre> |
| * |
| * @param text |
| * text to search and replace in, no-op if {@code null}. |
| * @param searchList |
| * the strings to search for, no-op if {@code null}. |
| * @param replacementList |
| * the strings to replace them with, no-op if {@code null}. |
| * @return the text with any replacements processed, {@code null} if {@code null} input. |
| * @throws IllegalArgumentException |
| * if the lengths of the arrays are not the same ({@code null} is ok, and/or size 0) |
| */ |
| @SuppressWarnings("pmd:NPathComplexity") |
| public static CharSequence replaceEach(CharSequence text, CharSequence[] searchList, CharSequence[] replacementList) { |
| if (text == null || text.length() == 0) { |
| return text; |
| } |
| if (searchList == null || searchList.length == 0) { |
| return text; |
| } |
| if (replacementList == null || replacementList.length == 0) { |
| return text; |
| } |
| |
| int searchLength = searchList.length; |
| int replacementLength = replacementList.length; |
| if (searchLength != replacementLength) { // make sure lengths are ok, these need to be equal |
| throw newFail("Search and Replace array lengths don't match: {} vs {}", searchLength, replacementLength); |
| } |
| |
| // keep track of which still have matches |
| boolean[] noMoreMatchesForReplIndex = new boolean[searchLength]; |
| |
| // index on index that the match was found |
| int textIndex = INDEX_NOT_FOUND; |
| int replaceIndex = INDEX_NOT_FOUND; |
| int tempIndex; |
| |
| // index of replace array that will replace the search string found |
| for (int i = 0; i < searchLength; i++) { |
| if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].length() == 0 || replacementList[i] == null) { |
| continue; |
| } |
| tempIndex = indexOf(searchList[i], text); |
| |
| // see if we need to keep searching for this |
| if (tempIndex == INDEX_NOT_FOUND) { |
| noMoreMatchesForReplIndex[i] = true; |
| } |
| else { |
| if (textIndex == INDEX_NOT_FOUND || tempIndex < textIndex) { |
| textIndex = tempIndex; |
| replaceIndex = i; |
| } |
| } |
| } |
| |
| // no search strings found, we are done |
| if (textIndex == INDEX_NOT_FOUND) { |
| return text; |
| } |
| |
| int start = 0; |
| |
| // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit |
| int increase = getLengthIncreaseGuess(text, searchList, replacementList); |
| StringBuilder result = new StringBuilder(text.length() + increase); |
| while (textIndex != INDEX_NOT_FOUND) { |
| |
| for (int i = start; i < textIndex; i++) { |
| result.append(text.charAt(i)); |
| } |
| result.append(replacementList[replaceIndex]); |
| |
| start = textIndex + searchList[replaceIndex].length(); |
| |
| textIndex = INDEX_NOT_FOUND; |
| replaceIndex = INDEX_NOT_FOUND; |
| // find the next earliest match |
| for (int i = 0; i < searchLength; i++) { |
| if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].length() == 0 || replacementList[i] == null) { |
| continue; |
| } |
| tempIndex = indexOf(searchList[i], text, start); |
| |
| // see if we need to keep searching for this |
| if (tempIndex == INDEX_NOT_FOUND) { |
| noMoreMatchesForReplIndex[i] = true; |
| } |
| else { |
| if (textIndex == INDEX_NOT_FOUND || tempIndex < textIndex) { |
| textIndex = tempIndex; |
| replaceIndex = i; |
| } |
| } |
| } |
| } |
| int textLength = text.length(); |
| for (int i = start; i < textLength; i++) { |
| result.append(text.charAt(i)); |
| } |
| return result; |
| } |
| |
| private static int getLengthIncreaseGuess(CharSequence text, CharSequence[] searchList, CharSequence[] replacementList) { |
| int increase = 0; |
| // count the replacement text elements that are larger than their corresponding text being replaced |
| for (int i = 0; i < searchList.length; i++) { |
| if (searchList[i] == null || replacementList[i] == null) { |
| continue; |
| } |
| int longer = replacementList[i].length() - searchList[i].length(); |
| if (longer > 0) { |
| increase += 3 * longer; // assume 3 matches |
| } |
| } |
| // have upper-bound at 20% increase, then let Java take over |
| return Math.min(increase, text.length() / 5); |
| } |
| |
| /** |
| * Checks if a {@link CharSequence} contains only invisible characters. |
| * <p> |
| * |
| * <pre> |
| * isBlank(null) = true |
| * isBlank("") = true |
| * isBlank(" ") = true |
| * isBlank("bob") = false |
| * isBlank(" bob ") = false |
| * </pre> |
| * |
| * @param cs |
| * the CharSequence to check, may be null |
| * @return {@code true} if the CharSequence is null, empty or whitespace |
| * @see #hasText(CharSequence) |
| */ |
| public static boolean isBlank(CharSequence cs) { |
| int strLen; |
| if (cs == null || (strLen = cs.length()) == 0) { |
| return true; |
| } |
| for (int i = 0; i < strLen; i++) { |
| if (!Character.isWhitespace(cs.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Tests if the string specified ends with the specified suffix. |
| * |
| * @param string |
| * The {@link CharSequence} to test |
| * @param suffix |
| * The suffix to search |
| * @return {@code true} if the string ends with the specified suffix or the suffix has length 0. If the string or |
| * suffix are {@code null} or the string does not end with the suffix specified, {@code false} is returned. |
| */ |
| public static boolean endsWith(CharSequence string, CharSequence suffix) { |
| if (string == null || suffix == null) { |
| return false; |
| } |
| if (suffix.length() == 0) { |
| return true; |
| } |
| if (string.length() < suffix.length()) { |
| return false; |
| } |
| for (int i = string.length() - 1, j = suffix.length() - 1; j >= 0; i--, j--) { |
| if (string.charAt(i) != suffix.charAt(j)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Checks if a {@link CharSequence} contains visible characters. |
| * <p> |
| * |
| * <pre> |
| * hasText(null) = false |
| * hasText("") = false |
| * hasText(" ") = false |
| * hasText("bob") = true |
| * hasText(" bob ") = true |
| * </pre> |
| * |
| * @param cs |
| * the CharSequence to check, may be null |
| * @return {@code true} if the CharSequence is null, empty or whitespace |
| * @see #isBlank(CharSequence) |
| */ |
| public static boolean hasText(CharSequence cs) { |
| return !isBlank(cs); |
| } |
| |
| /** |
| * Wraps a {@link CharSequence} into an {@link Optional} holding a value if the given sequence is not empty. |
| * |
| * @param value |
| * The {@link CharSequence} to wrap |
| * @return If the given {@link CharSequence} is neither {@code null} nor of length zero, an {@link Optional} holding |
| * the value. Otherwise an empty {@link Optional} is returned. |
| * @see #isEmpty(CharSequence) |
| */ |
| public static <T extends CharSequence> Optional<T> notEmpty(T value) { |
| if (isEmpty(value)) { |
| return Optional.empty(); |
| } |
| return Optional.of(value); |
| } |
| |
| /** |
| * Wraps a {@link CharSequence} into an {@link Optional} holding a value if the given sequence is not blank. |
| * |
| * @param value |
| * The {@link CharSequence} to wrap |
| * @return An {@link Optional} holding the value if the given {@link CharSequence} contains visible characters. |
| * Otherwise an empty {@link Optional} is returned. |
| * @see #isBlank(CharSequence) |
| */ |
| public static <T extends CharSequence> Optional<T> notBlank(T value) { |
| if (isBlank(value)) { |
| return Optional.empty(); |
| } |
| return Optional.of(value); |
| } |
| |
| /** |
| * Compares two {@link CharSequence}s lexicographically. |
| * |
| * @param a |
| * The first {@link CharSequence}. Must not be {@code null}. |
| * @param b |
| * The second {@link CharSequence}. Must not be {@code null}. |
| * @return the value {@code 0} if the two {@link CharSequence}s are equal on every character they contain. A value |
| * less than {@code 0} if the first {@link CharSequence} is lexicographically less than the second. A value |
| * greater than {@code 0} if the first {@link CharSequence} is lexicographically greater than the second. |
| * @see String#compareTo(String) |
| */ |
| public static int compareTo(CharSequence a, CharSequence b) { |
| if (a == null && b == null) { |
| return 0; |
| } |
| if (a == null) { |
| return INDEX_NOT_FOUND; |
| } |
| if (b == null) { |
| return 1; |
| } |
| int limit = Math.min(a.length(), b.length()); |
| for (int i = 0; i < limit; i++) { |
| char x = a.charAt(i); |
| char y = b.charAt(i); |
| //noinspection CharUsedInArithmeticContext |
| int diff = x - y; |
| if (diff != 0) { |
| return diff; |
| } |
| } |
| return a.length() - b.length(); |
| } |
| } |