|
|
@@ -0,0 +1,704 @@
|
|
|
+/*
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
+ */
|
|
|
+
|
|
|
+package net.ranides.assira.text;
|
|
|
+
|
|
|
+import java.io.UnsupportedEncodingException;
|
|
|
+import java.net.URLDecoder;
|
|
|
+import java.nio.charset.Charset;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collection;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Locale;
|
|
|
+import java.util.StringTokenizer;
|
|
|
+import java.util.function.Function;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+import net.ranides.assira.collection.arrays.NativeArrayAllocator;
|
|
|
+import net.ranides.assira.lexer.CharStreamer;
|
|
|
+import net.ranides.assira.math.MathUtils;
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ */
|
|
|
+public final class StringUtils {
|
|
|
+
|
|
|
+ private StringUtils() {
|
|
|
+ /* utility class */
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String quote(String value) {
|
|
|
+ if (null == value || value.isEmpty()) {
|
|
|
+ return "\"\"";
|
|
|
+ }
|
|
|
+
|
|
|
+ int offset = 1, size = quoteCount(value);
|
|
|
+ char c, result[] = new char[size];
|
|
|
+
|
|
|
+ for(int i=0, n=value.length(); i<n; i++) {
|
|
|
+ c = value.charAt(i);
|
|
|
+ if( c=='\\' || c=='"') { result[offset++] = '\\'; }
|
|
|
+ result[offset++] = c;
|
|
|
+ }
|
|
|
+ result[0] = '"';
|
|
|
+ result[size-1] = '"';
|
|
|
+ return new String(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int quoteCount(String value) {
|
|
|
+ int size = value.length() + 2;
|
|
|
+ char c;
|
|
|
+ for(int i=0, n=value.length(); i<n; i++) {
|
|
|
+ c = value.charAt(i);
|
|
|
+ if( c=='\\' || c=='"') { size++; }
|
|
|
+ }
|
|
|
+ return size;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String unquote(String value) {
|
|
|
+ String test = unquoteCheck(value);
|
|
|
+ if(null != test) { return test; }
|
|
|
+
|
|
|
+ // tak, obie funkcje są "brzydkie" ale kod działa
|
|
|
+ // 5-10 razy szybciej od String#replaceAll
|
|
|
+ // 2 razy szybciej od apache.commons.StrBuilder#replaceAll
|
|
|
+ char[] result = new char[value.length()-2];
|
|
|
+ return new String(result, 0, removeQuotes(result, removeSlashes(result, value) ) );
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String unquoteCheck(String value) {
|
|
|
+ if (null == value || value.isEmpty() ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ if("\"".equals(value)) {
|
|
|
+ throw new IllegalArgumentException("string quotes must be opened and closed but only single quote found.");
|
|
|
+ }
|
|
|
+ boolean as = value.charAt(0)=='"';
|
|
|
+ boolean at = value.charAt(value.length()-1)=='"';
|
|
|
+ if( at!=as) {
|
|
|
+ throw new IllegalArgumentException("string quotes must be opened and closed.");
|
|
|
+ } else {
|
|
|
+ return !at ? value : null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int removeSlashes(char[] buffer, String source) {
|
|
|
+ int position = 0;
|
|
|
+ boolean skip = false;
|
|
|
+
|
|
|
+ for(int i=1, length=source.length()-1; i<length; i++) {
|
|
|
+ char recent = source.charAt(i);
|
|
|
+ if(recent=='\\') {
|
|
|
+ if(skip) {
|
|
|
+ skip = false;
|
|
|
+ } else {
|
|
|
+ buffer[position++] = recent;
|
|
|
+ skip = true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ buffer[position++] = recent;
|
|
|
+ skip = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return position;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int removeQuotes(char[] buffer, int length) {
|
|
|
+ int position = 0;
|
|
|
+ boolean skip = false;
|
|
|
+
|
|
|
+ for(int i=0; i<length; i++) {
|
|
|
+ char c = buffer[i];
|
|
|
+ if(c=='"') {
|
|
|
+ if(skip) {
|
|
|
+ skip = false;
|
|
|
+ buffer[position-1] = '"';
|
|
|
+ } else {
|
|
|
+ buffer[position++] = c;
|
|
|
+ skip = true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ buffer[position++] = c;
|
|
|
+ skip = (c=='\\');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return position;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Rozbija tekst jako separatora używając spacji. Fragmenty wzięte w " " traktuje jako całość.
|
|
|
+ * @param text
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public static List<String> split(String text) {
|
|
|
+ List<String> result = new ArrayList<>();
|
|
|
+ boolean inquote = false;
|
|
|
+ boolean escape = false;
|
|
|
+ int last = 0;
|
|
|
+ for(int i=0; i<text.length(); i++) {
|
|
|
+ if( !escape && '\\' == text.charAt(i) ) {
|
|
|
+ escape = true;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if(escape) {
|
|
|
+ escape = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if( '"' == text.charAt(i) ) {
|
|
|
+ inquote = !inquote;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if( ' ' == text.charAt(i) && !inquote ) {
|
|
|
+ if( last-i != 0 ) {
|
|
|
+ result.add( unquote(text.substring(last, i)) );
|
|
|
+ }
|
|
|
+ last = i+1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String tail = text.substring(last);
|
|
|
+ if(!tail.trim().isEmpty()) {
|
|
|
+ result.add( unquote(tail) );
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private enum CharsType { STR, OCT }
|
|
|
+
|
|
|
+ private static final char[] C_ESC_SEQ = new char[]{'\033','['};
|
|
|
+ private static final char[] C_ESC = new char[]{'\033'};
|
|
|
+ private static final char[] C_SLASH = new char[]{'\\'};
|
|
|
+ private static final char[] C_BKSPACE = new char[]{'\b'};
|
|
|
+ private static final char[] C_FORM = new char[]{'\f'};
|
|
|
+ private static final char[] C_TAB = new char[]{'\t'};
|
|
|
+ private static final char[] C_CR = new char[]{'\r'};
|
|
|
+ private static final char[] C_NL = new char[]{'\n'};
|
|
|
+
|
|
|
+ public static String compile(String input) {
|
|
|
+ StringBuilder builder = new StringBuilder();
|
|
|
+
|
|
|
+ CharStreamer<CharsType> stream = new CharStreamer<>((type, data)->{
|
|
|
+ switch(type) {
|
|
|
+ case STR:
|
|
|
+ builder.append(data);
|
|
|
+ break;
|
|
|
+ case OCT:
|
|
|
+ builder.append( (char)Integer.parseInt(new String(data),8) );
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new AssertionError("Unsupported type: " + type);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ stream.open(input.toCharArray());
|
|
|
+
|
|
|
+ while( !stream.end() ) {
|
|
|
+ char c = stream.peek();
|
|
|
+ if( c=='\\' ) {
|
|
|
+ stream.flush(CharsType.STR);
|
|
|
+ stream.skip();
|
|
|
+ processEsc(stream);
|
|
|
+ } else {
|
|
|
+ stream.skip();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ stream.flush(CharsType.STR);
|
|
|
+ stream.close();
|
|
|
+ return builder.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void processEsc(CharStreamer<CharsType> stream) {
|
|
|
+ if( stream.end() ) {
|
|
|
+ stream.close();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ char code = stream.get();
|
|
|
+
|
|
|
+ if('n'==code) {
|
|
|
+ stream.flush();
|
|
|
+ stream.send(CharsType.STR, C_NL);
|
|
|
+ }
|
|
|
+ else if('r'==code) {
|
|
|
+ stream.flush();
|
|
|
+ stream.send(CharsType.STR, C_CR);
|
|
|
+ }
|
|
|
+ else if('t'==code) {
|
|
|
+ stream.flush();
|
|
|
+ stream.send(CharsType.STR, C_TAB);
|
|
|
+ }
|
|
|
+ else if('f'==code) {
|
|
|
+ stream.flush();
|
|
|
+ stream.send(CharsType.STR, C_FORM);
|
|
|
+ }
|
|
|
+ else if('b'==code) {
|
|
|
+ stream.flush();
|
|
|
+ stream.send(CharsType.STR, C_BKSPACE);
|
|
|
+ }
|
|
|
+ else if('\\'==code) {
|
|
|
+ stream.flush();
|
|
|
+ stream.send(CharsType.STR, C_SLASH);
|
|
|
+ }
|
|
|
+ else if('e'==code) {
|
|
|
+ stream.flush();
|
|
|
+ stream.send(CharsType.STR, C_ESC);
|
|
|
+ }
|
|
|
+ else if('^'==code) {
|
|
|
+ stream.flush();
|
|
|
+ stream.send(CharsType.STR, C_ESC_SEQ);
|
|
|
+ }
|
|
|
+ else if(Character.isDigit(code)) {
|
|
|
+ processOct(stream);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private static boolean isOct(char ch) {
|
|
|
+ return (ch>='0' && ch<='7');
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void processOct(CharStreamer<CharsType> stream) {
|
|
|
+ if( isOct(stream.peek()) ) { stream.skip(); }
|
|
|
+ if( isOct(stream.peek()) ) { stream.skip(); }
|
|
|
+ stream.flush(CharsType.OCT, 1,0);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String decodeURL(String input) {
|
|
|
+ return decodeURL(input, Charsets.UTF8);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String decodeURL(String input, Charset charset) {
|
|
|
+ try {
|
|
|
+ return URLDecoder.decode(input, charset.name());
|
|
|
+ } catch(UnsupportedEncodingException cause) {
|
|
|
+ throw new AssertionError("Unsupported charset: " + charset, cause);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String remove(String text, String fragment) {
|
|
|
+ StringTokenizer tokenizer = new StringTokenizer(text, fragment, false);
|
|
|
+ StringBuilder result = new StringBuilder( text.length() );
|
|
|
+ while (tokenizer.hasMoreElements()) {
|
|
|
+ result.append( tokenizer.nextElement() );
|
|
|
+ }
|
|
|
+ return result.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String remove(String text, char value) {
|
|
|
+ final int size = text.length();
|
|
|
+ final char buffer[] = new char[size];
|
|
|
+ char c;
|
|
|
+ int position = 0;
|
|
|
+ for(int i=0; i<size; i++) {
|
|
|
+ c = text.charAt(i);
|
|
|
+ if(c!=value) { buffer[position++] = c; }
|
|
|
+ }
|
|
|
+ return new String(buffer, 0, position);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String removePrefix(String text, String prefix) {
|
|
|
+ if(text.startsWith(prefix)) {
|
|
|
+ return text.substring(prefix.length());
|
|
|
+ }
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String removeSuffix(String text, String suffix) {
|
|
|
+ if(text.endsWith(suffix)) {
|
|
|
+ return text.substring(0, text.length() - suffix.length());
|
|
|
+ }
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String condense(String text, char value) {
|
|
|
+ final int size = text.length();
|
|
|
+ final char buffer[] = new char[size];
|
|
|
+ char c;
|
|
|
+ boolean pc = false;
|
|
|
+ int position = 0;
|
|
|
+ for(int i=0; i<size; i++) {
|
|
|
+ c = text.charAt(i);
|
|
|
+ if(c == value) {
|
|
|
+ if(!pc) {
|
|
|
+ buffer[position++] = c;
|
|
|
+ pc = true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ buffer[position++] = c;
|
|
|
+ pc = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new String(buffer, 0, position);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String ltrim(String source) {
|
|
|
+ int n = source.length(), offset = 0;
|
|
|
+ while( offset<n && Character.isWhitespace(source.charAt(offset)) ) {
|
|
|
+ offset++;
|
|
|
+ }
|
|
|
+ return source.substring(offset);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String rtrim(String source) {
|
|
|
+ int offset = source.length() - 1;
|
|
|
+ while(offset >= 0 && Character.isWhitespace(source.charAt(offset)) ) {
|
|
|
+ offset--;
|
|
|
+ }
|
|
|
+ return source.substring(0, offset+1);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String trim(String source){
|
|
|
+ return source.trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String replace(String text, Pattern pattern, Function<Matcher, String> converter) {
|
|
|
+ Matcher matcher = pattern.matcher(text);
|
|
|
+ StringBuffer buffer = new StringBuffer();
|
|
|
+ while(matcher.find()) {
|
|
|
+ String str = converter.apply(matcher);
|
|
|
+ if( null!=str ) {
|
|
|
+ matcher.appendReplacement(buffer, "");
|
|
|
+ buffer.append(str);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ matcher.appendTail(buffer);
|
|
|
+ return buffer.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String replace(String text, char search, char newval) {
|
|
|
+ final char[] buffer = text.toCharArray();
|
|
|
+ for(int i=0; i<buffer.length; i++) {
|
|
|
+ if(buffer[i]==search) {
|
|
|
+ buffer[i] = newval;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new String(buffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String replace(String text, char search, String newval) {
|
|
|
+ if(text.isEmpty()) {
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+ int start = 0;
|
|
|
+ int end = text.indexOf(search, start);
|
|
|
+ if (end == -1) {
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+ int delta = (newval.length()-1);
|
|
|
+
|
|
|
+ StringBuilder buf = new StringBuilder(text.length() + 16*delta );
|
|
|
+ while (end != -1) {
|
|
|
+ buf.append(text.substring(start, end)).append(newval);
|
|
|
+ start = end + 1;
|
|
|
+ end = text.indexOf(search, start);
|
|
|
+ }
|
|
|
+ buf.append(text.substring(start));
|
|
|
+ return buf.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static <T> String join(Iterable<T> values, String separator) {
|
|
|
+ return join(values, separator, Object::toString);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static <T> String join(Iterable<? extends T> values, String separator, Function<T,String> function) {
|
|
|
+ Iterator<? extends T> itr = values.iterator();
|
|
|
+ if ( !itr.hasNext() ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ StringBuilder builder = new StringBuilder();
|
|
|
+ for (;;) {
|
|
|
+ T item = itr.next();
|
|
|
+ builder.append(function.apply(item));
|
|
|
+ if ( !itr.hasNext() ) {
|
|
|
+ return builder.toString();
|
|
|
+ }
|
|
|
+ builder.append(separator);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static <T> String join(T[] values, String separator) {
|
|
|
+ return join(values, separator, Object::toString);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static <T> String join(T[] values, String separator, Function<T,String> function) {
|
|
|
+ if ( 0==values.length ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ StringBuilder builder = new StringBuilder();
|
|
|
+ builder.append(function.apply(values[0]));
|
|
|
+ for (int i=1; i<values.length; i++) {
|
|
|
+ builder.append(separator).append(function.apply(values[i]));
|
|
|
+ }
|
|
|
+ return builder.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String capitalize(String value) {
|
|
|
+ return capitalize(value, Locale.ROOT);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String capitalize(String value, Locale locale) {
|
|
|
+ if( value.isEmpty() ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ if( 1==value.length() ) {
|
|
|
+ return value.toUpperCase(locale);
|
|
|
+ }
|
|
|
+ return new StringBuilder(value.length())
|
|
|
+ .append( value.substring(0, 1).toUpperCase(locale) )
|
|
|
+ .append(value.substring(1))
|
|
|
+ .toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String uncapitalize(String value) {
|
|
|
+ return uncapitalize(value, Locale.ROOT);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String uncapitalize(String value, Locale locale) {
|
|
|
+ if( value.isEmpty() ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ if( 1==value.length() ) {
|
|
|
+ return value.toLowerCase(locale);
|
|
|
+ }
|
|
|
+ return new StringBuilder(value.length())
|
|
|
+ .append( value.substring(0, 1).toLowerCase(locale) )
|
|
|
+ .append(value.substring(1))
|
|
|
+ .toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String camelize(String value) {
|
|
|
+ return camelize(value, Locale.ROOT);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String camelize(String value, Locale locale) {
|
|
|
+ String[] parts = value.split("_+");
|
|
|
+ if(0==parts.length) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ if(1==parts.length) {
|
|
|
+ return parts[0].toLowerCase(locale);
|
|
|
+ }
|
|
|
+
|
|
|
+ StringBuilder result = new StringBuilder(value.length()-parts.length);
|
|
|
+ int i=0;
|
|
|
+ for(;i<parts.length; i++) {
|
|
|
+ if(!parts[i].isEmpty()) { break; }
|
|
|
+ }
|
|
|
+ result.append(parts[i].toLowerCase(locale));
|
|
|
+
|
|
|
+ for(;i<parts.length; i++) {
|
|
|
+ result.append(parts[i].substring(0, 1).toUpperCase(locale));
|
|
|
+ result.append(parts[i].substring(1).toLowerCase(locale));
|
|
|
+ }
|
|
|
+ return result.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String uncamelize(String value) {
|
|
|
+ return uncamelize(value, Locale.ROOT);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String uncamelize(String value, Locale locale) {
|
|
|
+ return replace(value, Pattern.compile("(\\p{Lu})"), (hit) -> {
|
|
|
+ String v = hit.group(1).toLowerCase(locale);
|
|
|
+ return (0 == hit.start(1)) ? v : "_"+v;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String clip(String text, int size) {
|
|
|
+ return text.substring(0, MathUtils.clip(size, 0, text.length()));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String lpadd(String text, char padd, int size) {
|
|
|
+ int n = text.length();
|
|
|
+ int d = size - n;
|
|
|
+ if(d <= 0) {
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+ char[] buffer = new char[size];
|
|
|
+ text.getChars(0, n, buffer, 0);
|
|
|
+ for(int i=n; i<size; i++) { buffer[i] = padd; }
|
|
|
+ return new String(buffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String rpadd(String text, char padd, int size) {
|
|
|
+
|
|
|
+ Collection<String> c = null;
|
|
|
+ int cs = 44;
|
|
|
+ c.stream().map((s)->s.substring(cs));
|
|
|
+
|
|
|
+ int n = text.length();
|
|
|
+ int d = size - n;
|
|
|
+ if(d <= 0) {
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+ char[] buffer = new char[size];
|
|
|
+ text.getChars(0, n, buffer, d);
|
|
|
+ for(int i=0; i<d; i++) { buffer[i] = padd; }
|
|
|
+ return new String(buffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String commonPrefix(String a, String b) {
|
|
|
+ if( a.isEmpty() || b.isEmpty() ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ int n = Math.min(a.length(), b.length());
|
|
|
+ for(int i=0; i<n; i++) {
|
|
|
+ if( a.charAt(i)!=b.charAt(i) ) { return a.substring(0,i); }
|
|
|
+ }
|
|
|
+ return a.substring(0,n);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String commonPrefix(Collection<String> values) {
|
|
|
+ if(values.isEmpty()) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return values.stream().reduce(values.iterator().next(), StringUtils::commonPrefix);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String commonSuffix(String a, String b) {
|
|
|
+ if( a.isEmpty() || b.isEmpty() ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ int as = a.length();
|
|
|
+ int bs = b.length();
|
|
|
+ int n = Math.min(as, bs);
|
|
|
+ for(int i=0; i<n; i++) {
|
|
|
+ if( a.charAt(as-i)!=b.charAt(bs-i) ) { return a.substring(as-i,i); }
|
|
|
+ }
|
|
|
+ return a.substring(as-n,n);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String commonSuffix(Collection<String> values) {
|
|
|
+ if(values.isEmpty()) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return values.stream().reduce(values.iterator().next(), StringUtils::commonSuffix);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String reverse(String value) {
|
|
|
+ return value.isEmpty() ? value : new StringBuilder(value).reverse().toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String before(String text, String end) {
|
|
|
+ int iend = text.indexOf(end);
|
|
|
+ return (-1 == iend) ? "" : text.substring(0, iend);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String after(String text, String start) {
|
|
|
+ int istart = text.indexOf(start);
|
|
|
+ return (-1 == istart) ? "" : text.substring(istart + start.length());
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String between(String text, String start, String end) {
|
|
|
+ if(start == null) {
|
|
|
+ return before(text, end);
|
|
|
+ }
|
|
|
+ if(end == null) {
|
|
|
+ return after(text, start);
|
|
|
+ }
|
|
|
+ int istart = text.indexOf(start);
|
|
|
+ if( istart == -1 ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ istart += start.length();
|
|
|
+ int iend = text.indexOf(end, istart);
|
|
|
+ if( iend == -1 ) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return text.substring(istart, iend);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static CharSequence repeat(int n, String text) {
|
|
|
+ return new NSSequence(n, text);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static CharSequence repeat(int n, char chr) {
|
|
|
+ return new NCSequence(n, chr);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class NCSequence implements CharSequence {
|
|
|
+ private final int count;
|
|
|
+ private final char chr;
|
|
|
+
|
|
|
+ public NCSequence(int count, char chr) {
|
|
|
+ this.count = count;
|
|
|
+ this.chr = chr;
|
|
|
+ }
|
|
|
+ @Override
|
|
|
+ public char charAt(int index) {
|
|
|
+ if( index >= count ) {
|
|
|
+ throw new IndexOutOfBoundsException(index + " out of bound " + count);
|
|
|
+ }
|
|
|
+ return chr;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int length() {
|
|
|
+ return count;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CharSequence subSequence(int start, int end) {
|
|
|
+ NativeArrayAllocator.ensureFromTo(count, start, end);
|
|
|
+ return new NCSequence(end-start, chr);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ int n = length();
|
|
|
+ char[] data = new char[n];
|
|
|
+ Arrays.fill(data, chr);
|
|
|
+ return new String(data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class NSSequence implements CharSequence {
|
|
|
+ private final int start;
|
|
|
+ private final int end;
|
|
|
+ private final String text;
|
|
|
+
|
|
|
+ public NSSequence(int count, String text) {
|
|
|
+ this(text, 0, count * text.length());
|
|
|
+ }
|
|
|
+
|
|
|
+ public NSSequence(String text, int start, int end) {
|
|
|
+ this.start = start;
|
|
|
+ this.end = end;
|
|
|
+ this.text = text;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public char charAt(int index) {
|
|
|
+ if( index >= end ) {
|
|
|
+ throw new IndexOutOfBoundsException(index + " out of bound " + end);
|
|
|
+ }
|
|
|
+ return text.charAt( (index+start) % text.length() );
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int length() {
|
|
|
+ return end-start;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CharSequence subSequence(int start, int end) {
|
|
|
+ NativeArrayAllocator.ensureFromTo(length(), start, end);
|
|
|
+ return new NSSequence(text, start, end);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ int n = length();
|
|
|
+ char[] data = new char[n];
|
|
|
+ for(int i=0; i<n; i++) { data[i] = charAt(i); }
|
|
|
+ return new String(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|