NoCheatPlus/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/StringUtil.java

438 lines
13 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.utilities;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
* String utility methods (working with or returning strings).
* @author asofold
*
*/
public class StringUtil {
/** Decimal format for "#.###" */
public static final DecimalFormat fdec3 = new DecimalFormat();
/** Decimal format for "#.#" */
public static final DecimalFormat fdec1 = new DecimalFormat();
static {
// 3 digits.
DecimalFormatSymbols sym = fdec3.getDecimalFormatSymbols();
sym.setDecimalSeparator('.');
fdec3.setDecimalFormatSymbols(sym);
fdec3.setMaximumFractionDigits(3);
fdec3.setMinimumIntegerDigits(1);
// 1 digit.
sym = fdec1.getDecimalFormatSymbols();
sym.setDecimalSeparator('.');
fdec1.setDecimalFormatSymbols(sym);
fdec1.setMaximumFractionDigits(1);
fdec1.setMinimumIntegerDigits(1);
}
/**
* List by boxing.
* @param chars
* @return
*/
public static List<Character> characterList(char...chars) {
final List<Character> res = new ArrayList<Character>(chars.length);
for (int i = 0; i < chars.length; i++) {
res.add(chars[i]);
}
return res;
}
/**
* Join parts with link, starting from startIndex.
* @param input
* @param startIndex
* @param link
* @return
*/
public static <O extends Object> String join(O[] input, int startIndex, String link) {
return join(Arrays.copyOfRange(input, startIndex, input.length), link);
}
/**
* Join parts with link.
*
* @param input
* @param link
* @return
*/
public static <O extends Object> String join(O[] input, String link) {
return join(Arrays.asList(input), link);
}
public static String join(Iterator<? extends Object> iterator, String link) {
final StringBuilder builder = new StringBuilder(1024);
boolean first = true;
while (iterator.hasNext()) {
final Object obj = iterator.next();
if (!first) {
builder.append(link);
}
builder.append(obj.toString());
first = false;
}
return builder.toString();
}
/**
* Join parts with link.
*
* @param input
* @param link
* @return
*/
public static String join(final Collection<? extends Object> input, final String link)
{
return join(input, link, new StringBuilder(Math.max(300, input.size() * 10))).toString();
}
/**
* Add joined parts with link.
*
* @param input
* @param link
* @return The given StringBuilder for chaining.
*/
public static StringBuilder join(final Collection<? extends Object> input, final String link,
final StringBuilder builder)
{
boolean first = true;
for (final Object obj : input) {
if (!first) {
builder.append(link);
}
builder.append(obj.toString());
first = false;
}
return builder;
}
/**
* Split input by all characters given (convenience method).
*
* @param input
* @param chars
* @return An (Array)List with the results.
*/
public static List<String> split(String input, Character... chars){
return split(input, Arrays.asList(chars));
}
/**
* Split input by all characters given (convenience method).
*
* @param input
* @param chars
* @return An (Array)List with the results.
*/
public static List<String> splitChars(String input, char... chars){
return split(input, characterList(chars));
}
/**
* Split input by all characters given (convenience method).
*
* @param input
* @param chars
* @return An (Array)List with the results.
*/
public static List<String> split(String input, Collection<Character> chars){
// TODO: Construct one regular expression to do the entire job!?
List<String> out = new ArrayList<String>();
out.add(input);
List<String> queue = new ArrayList<String>();
for (final char c : chars){
String hex = Integer.toHexString((int) c);
switch (hex.length()){
case 1:
hex = "000" + hex;
break;
case 2:
hex = "00" + hex;
break;
case 3:
hex = "0" + hex;
}
for (final String s : out){
final String[] split = s.split("\\u" + hex);
for (final String _s : split){
queue.add(_s);
}
}
List<String> temp = out;
out = queue;
queue = temp;
queue.clear();
}
return out;
}
/**
* Add non empty strings to an output (Array)List.
*
* @param input
* @param trim
* @return
*/
public static List<String> getNonEmpty(final List<String> input, boolean trim) {
final List<String> output = new ArrayList<String>();
for (String x : input) {
if (trim) {
x = x.trim();
}
if (!x.isEmpty()) {
output.add(x);
}
}
return output;
}
/**
* Return if the two Strings are similar based on the given threshold.
*
* @param s
* the first String, must not be null
* @param t
* the second String, must not be null
* @param threshold
* the minimum value of the correlation coefficient
* @return result true if the two Strings are similar, false otherwise
*/
public static boolean isSimilar(final String s, final String t, final float threshold)
{
return 1.0f - (float) levenshteinDistance(s, t) / Math.max(1.0, Math.max(s.length(), t.length())) > threshold;
}
/**
* Find the Levenshtein distance between two Strings.
*
* This is the number of changes needed to change one String into another,
* where each change is a single character modification (deletion, insertion or substitution).
*
* @param s
* the first String, must not be null
* @param t
* the second String, must not be null
* @return result distance
*/
public static int levenshteinDistance(CharSequence s, CharSequence t) {
if (s == null || t == null) throw new IllegalArgumentException("Strings must not be null");
int n = s.length();
int m = t.length();
if (n == 0) return m;
else if (m == 0) return n;
if (n > m) {
final CharSequence tmp = s;
s = t;
t = tmp;
n = m;
m = t.length();
}
int p[] = new int[n + 1];
int d[] = new int[n + 1];
int _d[];
int i;
int j;
char t_j;
int cost;
for (i = 0; i <= n; i++)
p[i] = i;
for (j = 1; j <= m; j++) {
t_j = t.charAt(j - 1);
d[0] = j;
for (i = 1; i <= n; i++) {
cost = s.charAt(i - 1) == t_j ? 0 : 1;
d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
}
_d = p;
p = d;
d = _d;
}
return p[n];
}
/**
* Just return the stack trace as new-line-separated string.
* @param t
* @param header Add a header about the exception itself, if set to true.
* @param trim If to make the output more compact in case of repetitions.
* @return
*/
public static final String stackTraceToString(final Throwable t, final boolean header, final boolean trim) {
// TODO: Consider to use System.getProperty("line.separator").
// TODO: Consider to add a trimDepth argument, for repetition of a sequence of elements.
final StringBuilder b = new StringBuilder(325);
if (header) {
b.append(t.toString()); // TODO: Check.
b.append("\n");
}
final StackTraceElement[] elements = t.getStackTrace();
StackTraceElement last = null; // Assume this is faster than operating on the strings.
int repetition = 0;
for (int i = 0; i < elements.length; i++) {
final StackTraceElement element = elements[i];
if (trim) {
if (element.equals(last)) {
repetition += 1;
continue;
} else {
if (repetition > 0) {
if (header) {
b.append("\t");
}
b.append("(... repeated " + repetition + " times.)\n");
repetition = 0;
}
last = element;
}
}
if (header) {
b.append("\t");
}
b.append(element);
b.append("\n");
}
if (repetition > 0) {
if (header) {
b.append("\t");
}
b.append("(... repeated " + repetition + " times.)\n");
}
return b.toString();
}
/**
* Convenience method for stackTraceToString(t).
* @param t
* @return
*/
public static final String throwableToString(final Throwable t) {
return stackTraceToString(t, true, true);
}
/**
* Count number of needles left in a dart board.
* @param dartBoard
* @param needles
* @return
*/
public static final int count(final String dartBoard, final char needles) {
int n = 0;
int index = 0;
while (index != -1) {
index = dartBoard.indexOf(needles, index);
if (index != -1) {
n ++;
index ++;
}
}
return n;
}
/**
* Get a version of a String with all leading whitespace removed.
*
* @param input
* @return String with leading whitespace removed. Returns the original
* reference, if there is no leading whitespace.
*/
public static final String leftTrim(final String input) {
if (input == null) {
return null;
}
final int len = input.length();
int beginIndex = 0;
for (int i = 0; i < len; i++) {
if (Character.isWhitespace(input.charAt(i))) {
++beginIndex;
}
else {
break;
}
}
if (beginIndex > 0) {
if (beginIndex >= len) {
return "";
} else {
return input.substring(beginIndex);
}
}
else {
return input;
}
}
/**
* Format to maximally 3 digits after the comma, always show the sign,
* unless equal.
*
* @param current
* @param previous
* @return
*/
public static String formatDiff(final double current, final double previous) {
return current == previous ? "0" : ((current > previous ? "+" : "-") + fdec3.format(Math.abs(current - previous)));
}
public static boolean startsWithAnyOf(final String input, final String... startsWith) {
for (int i = 0; i < startsWith.length; i++) {
if (input.startsWith(startsWith[i])) {
return true;
}
}
return false;
}
public static boolean endsWithAnyOf(final String input, final String... endsWith) {
for (int i = 0; i < endsWith.length; i++) {
if (input.endsWith(endsWith[i])) {
return true;
}
}
return false;
}
public static String reverse(final String input) {
return new StringBuilder(input).reverse().toString();
}
}