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

de.carne.util.Strings Maven / Gradle / Ivy

There is a newer version: 10.2.0
Show newest version
/*
 * Copyright (c) 2016-2018 Holger de Carne and contributors, All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License
 * along with this program.  If not, see .
 */
package de.carne.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import de.carne.boot.check.Nullable;

/**
 * Utility class providing {@linkplain String} related functions.
 */
public final class Strings {

	private Strings() {
		// Prevent instantiation
	}

	/**
	 * Ellipsis string.
	 */
	public static final String ELLIPSIS = SystemProperties.value(Strings.class, ".ellipsis", "\\u2026");

	/**
	 * Checks whether a {@linkplain String} is empty.
	 * 

* A {@linkplain String} is considered empty if it is either {@code null} or of length {@code 0}. * * @param s the {@linkplain String} to check. * @return {@code true} if the string is empty. */ public static boolean isEmpty(@Nullable String s) { return s == null || s.length() == 0; } /** * Checks whether a {@linkplain String} is not empty. *

* A {@linkplain String} is considered empty if it is either {@code null} or of length {@code 0}. * * @param s the {@linkplain String} to check. * @return {@code true} if the string is not empty. */ public static boolean notEmpty(@Nullable String s) { return s != null && s.length() > 0; } /** * Makes sure a {@linkplain String} is not {@code null}. * * @param s the {@linkplain String} to check. * @return the submitted {@linkplain String} or {@code ""} if {@code null} was submitted. */ public static String safe(@Nullable String s) { return (s != null ? s : ""); } /** * Makes sure a {@linkplain String} is not {@code null} and trimmed. * * @param s the {@linkplain String} to trim. * @return the submitted {@linkplain String} in trimmed form or {@code ""} if {@code null} was submitted. */ public static String safeTrim(@Nullable String s) { return (s != null ? s.trim() : ""); } /** * Makes sure a {@linkplain String} is either {@code null} or trimmed. * * @param s the {@linkplain String} to trim. * @return the submitted {@linkplain String} in trimmed form or {@code null} if {@code null} was submitted. */ @Nullable public static String trim(@Nullable String s) { return (s != null ? s.trim() : s); } /** * Splits a {@linkplain String} according to a given delimiter. * * @param s the {@linkplain String} to split. * @param delim the delimiter character to split at. * @param all whether to split at all occurrences of the delimiter character ({@code true}) or only at the first one * ({@code false}). * @return the splitted sub-strings. The actual number of sub-strings depends on the actual occurrences of the * delimiter character as well as the {@code all} flag. */ public static String[] split(String s, char delim, boolean all) { List splits = new ArrayList<>(); int splitIndex = -1; while (true) { int nextSplitIndex = (splitIndex == -1 || all ? s.indexOf(delim, splitIndex + 1) : -1); if (nextSplitIndex < 0) { splits.add(s.substring(splitIndex + 1)); break; } splits.add(s.substring(splitIndex + 1, nextSplitIndex)); splitIndex = nextSplitIndex; } return splits.toArray(new String[splits.size()]); } /** * Joins the {@linkplain String} representation of multiple {@linkplain Object}s into a single {@linkplain String}. *

* The {@linkplain Object#toString()} is used to retrieve the Objects' {@linkplain String} representation. * * @param objects the {@linkplain Objects}s to join. * @param delim the delimiter to use for joining. * @return the joined {@linkplain String}. */ public static String join(Iterable objects, String delim) { return join(objects, delim, Integer.MAX_VALUE, ELLIPSIS); } /** * Joins the {@linkplain String} representation of multiple {@linkplain Object}s into a single {@linkplain String}. *

* The {@linkplain Object#toString()} is used to retrieve the Objects' {@linkplain String} representation. * * @param objects the {@linkplain String}s to join. * @param delim the delimiter to use for joining. * @param limit the maximum length of the joined {@linkplain String}. * @return the joined {@linkplain String}. */ public static String join(Iterable objects, String delim, int limit) { return join(objects, delim, limit, ELLIPSIS); } /** * Joins the {@linkplain String} representation of multiple {@linkplain Object}s into a single {@linkplain String}. *

* The {@linkplain Object#toString()} is used to retrieve the Objects' {@linkplain String} representation. * * @param objects the {@linkplain Objects}s to join. * @param delim the delimiter to use for joining. * @param limit the maximum length of the joined {@linkplain String}. * @param ellipsis the ellipsis to place at the end of the joined string in case {@code limit} is reached. * @return the joined {@linkplain String}. */ public static String join(Iterable objects, String delim, int limit, String ellipsis) { StringBuilder joined = new StringBuilder(); boolean limitReached = false; for (Object object : objects) { if (joined.length() > 0) { limitReached = joinLimit(joined, delim, limit); } limitReached = limitReached || joinLimit(joined, object.toString(), limit); if (limitReached) { break; } } if (limitReached) { int joinedLength = joined.length(); int replaceLength = Math.min(ellipsis.length(), joinedLength); int replaceStart = joinedLength - replaceLength; for (int replaceIndex = 0; replaceIndex < replaceLength; replaceIndex++) { joined.setCharAt(replaceStart + replaceIndex, ellipsis.charAt(replaceIndex)); } } return joined.toString(); } private static boolean joinLimit(StringBuilder joined, String s, int limit) { int maxAppend = limit - joined.length(); boolean limitReached = s.length() > maxAppend; joined.append(!limitReached ? s : s.substring(0, maxAppend)); return limitReached; } private static char[] hexCharsUpper = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /** * Encodes a {@linkplain CharSequence} to a pure ASCII representation by quoting non printable characters. * * @param chars the {@linkplain CharSequence} to encode. * @return the encoded characters. */ public static String encode(CharSequence chars) { StringBuilder buffer = new StringBuilder(); chars.chars().forEach(c -> { if (c == '\\') { buffer.append("\\\\"); } else if (32 <= c && c <= 126) { buffer.append((char) c); } else { switch (c) { case 0: buffer.append("\\0"); break; case 8: buffer.append("\\b"); break; case 9: buffer.append("\\t"); break; case 10: buffer.append("\\n"); break; case 12: buffer.append("\\f"); break; case 13: buffer.append("\\r"); break; default: buffer.append("\\u").append(hexCharsUpper[(c >> 12) & 0xf]).append(hexCharsUpper[(c >> 8) & 0xf]) .append(hexCharsUpper[(c >> 4) & 0xf]).append(hexCharsUpper[c & 0xf]); } } }); return buffer.toString(); } /** * Decodes a {@linkplain CharSequence} previously encoded via {@linkplain #encode(CharSequence)}. * * @param chars the {@linkplain CharSequence} to decode. * @return the decoded characters. */ public static String decode(CharSequence chars) { CharDecoder decoder = new CharDecoder(); chars.chars().forEach(decoder::decode); return decoder.getDecoded(); } private static class CharDecoder { private final StringBuilder buffer = new StringBuilder(); private boolean quoted = false; private int encodeIndex = 0; private int decodedC = 0; CharDecoder() { // To make it accessible to the outer class } public void decode(int c) { if (!this.quoted) { decodeUnquoted(c); } else if (this.encodeIndex == 0) { decodeQuoted(c); } else { decodeQuotedEncoded(c); } } private void decodeUnquoted(int c) { if (c != '\\') { this.buffer.append((char) c); } else { this.quoted = true; } } private void decodeQuoted(int c) { if (c != 'u') { this.quoted = false; switch (c) { case '\\': this.buffer.append('\\'); break; case '0': this.buffer.append('\0'); break; case 'b': this.buffer.append('\b'); break; case 't': this.buffer.append('\t'); break; case 'n': this.buffer.append('\n'); break; case 'f': this.buffer.append('\f'); break; case 'r': this.buffer.append('\r'); break; default: throw new IllegalArgumentException("Unexpected quoted character: " + ((char) c)); } } else { this.encodeIndex = 1; this.decodedC = 0; } } private void decodeQuotedEncoded(int c) { this.encodeIndex = (this.encodeIndex + 1) % 5; this.quoted = this.encodeIndex != 0; this.decodedC <<= 4; if ('0' <= c && c <= '9') { this.decodedC |= c - '0'; } else if ('a' <= c && c <= 'f') { this.decodedC |= c - 'a' + 10; } else if ('A' <= c && c <= 'F') { this.decodedC |= c - 'A' + 10; } else { throw new IllegalArgumentException("Unexpected encoded character: " + ((char) c)); } if (!this.quoted) { this.buffer.append((char) this.decodedC); } } public String getDecoded() { return this.buffer.toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy