Просмотр исходного кода

FIX: StringUtils#replace - string returned by callback should be used as literal

Ranides Atterwim 10 лет назад
Родитель
Сommit
45fa839843
1 измененных файлов с 489 добавлено и 489 удалено
  1. 489 489
      assira1/src/main/java/net/ranides/assira/text/StringUtils.java

+ 489 - 489
assira1/src/main/java/net/ranides/assira/text/StringUtils.java

@@ -1,489 +1,489 @@
-/*
- * @author Ranides Atterwim <ranides@gmail.com>
- * @copyright Ranides Atterwim
- * @license WTFPL
- * @url http://ranides.net/projects/assira
- */
-package net.ranides.assira.text;
-
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import net.ranides.assira.generic.Function;
-import net.ranides.assira.math.MathUtils;
-
-/**
- *
- * @author ranides
- */
-public final class StringUtils {
-
-    public static final String ENDL = String.format("%n");
-
-    private static final Pattern  REGEX_SPECIAL = Pattern.compile("([\\\\*+\\[\\](){}\\$.?\\^|])");
-
-    private StringUtils() { /* utility class */ }
-
-    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( Strings.isEmpty(text)) {
-            return "";
-        }
-        if(text.startsWith(prefix)) {
-            return text.substring(prefix.length());
-        }
-        return text;
-    }
-
-    public static String removeSuffix(String text, String suffix) {
-        if( Strings.isEmpty(text)) {
-            return "";
-        }
-        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<String,Matcher> converter) {
-        Matcher matcher = pattern.matcher(text);
-        StringBuffer buffer = new StringBuffer();
-        while(matcher.find()) {
-            String str = converter.apply(matcher);
-            if( null!=str ) { matcher.appendReplacement(buffer, 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(null == text) {
-            return "";
-        }
-        if(text.isEmpty()) {
-            return text;
-        }
-        int start = 0;
-        int end = text.indexOf(search, start);
-        if (end == -1) {
-            return text;
-        }
-        final 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 extends CharSequence> String join(Iterable<T> values, String separator) {
-        if (values instanceof Collection) {
-            Collection<?> collection = (Collection<?>)values;
-            return join( collection.toArray(new CharSequence[collection.size()]), separator );
-        }
-        StringBuilder builder = new StringBuilder();
-        boolean first = true;
-        for(Object value : values) {
-            if(!first) { builder.append(separator); }
-            if(null != value) { builder.append(value.toString()); }
-            first = false;
-        }
-        return builder.toString();
-    }
-
-    public static <T extends CharSequence> String join(Iterable<T> values) {
-        if (values instanceof Collection) {
-            Collection<?> collection = (Collection<?>)values;
-            return join( collection.toArray(new CharSequence[collection.size()]));
-        }
-        StringBuilder builder = new StringBuilder();
-        for(Object value : values) {
-            builder.append( value.toString() );
-        }
-        return builder.toString();
-    }
-
-    public static <T extends CharSequence> String join(T[] list, String separator) {
-        if(list.length == 0) {
-            return "";
-        }
-        if(list.length == 1) {
-            return list[0].toString();
-        }
-
-        int size = 0;
-        for(int i=0; i<list.length; i++) {
-            size+=list[i].length();
-        }
-        size += list.length * separator.length();
-
-        StringBuilder result = new StringBuilder(size);
-        result.append(list[0]);
-        for(int i=1; i<list.length; i++) {
-            result.append(separator).append(list[i]);
-        }
-        return result.toString();
-    }
-
-    public static <T extends CharSequence> String join(T[] list) {
-        if(list.length == 0) {
-            return "";
-        }
-        if(list.length == 1) {
-            return list[0].toString();
-        }
-
-        int size = 0;
-        for(int i=0; i<list.length; i++) {
-            size+=list[i].length();
-        }
-
-        StringBuilder result = new StringBuilder(size);
-        for(int i=0; i<list.length; i++) {
-            result.append(list[i]);
-        }
-        return result.toString();
-    }
-
-    /**
-     * Zamienia pierwszą literę w tekście na dużą. Nie uwzględnia lokalizacji.
-     * @param value
-     * @return
-     */
-    public static String capitalize(String value) {
-        return capitalize(value, Locale.ROOT);
-    }
-
-    public static String uncapitalize(String value) {
-        return uncapitalize(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, 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) {
-        String[] parts = value.split("_+");
-        if(0==parts.length) {
-            return "";
-        }
-        if(1==parts.length) {
-            return parts[0].toLowerCase(Locale.ROOT);
-        }
-
-        StringBuilder result = new StringBuilder(value.length()-parts.length);
-        boolean first = true;
-        for (String part : parts) {
-            if(part.isEmpty()) {
-                continue;
-            }
-            if (first) {
-                result.append(part.toLowerCase(Locale.ROOT));
-                first = false;
-            } else {
-                result.append(part.substring(0, 1).toUpperCase(Locale.ROOT));
-                result.append(part.substring(1).toLowerCase(Locale.ROOT));
-            }
-        }
-        return result.toString();
-    }
-
-    public static String uncamelize(String value) {
-        return replace(value, Pattern.compile("(\\p{Lu})"), new Function<String,Matcher>() {
-            @Override
-            public String apply(Matcher source) {
-                if(0 == source.start(1)) {
-                    return source.group(1).toLowerCase(Locale.ROOT);
-                }
-                return "_" + source.group(1).toLowerCase(Locale.ROOT);
-            }
-        });
-    }
-
-    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) {
-        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 lcommon(String value1, String value2) {
-        if( Strings.isEmpty(value1) || Strings.isEmpty(value2) ) { 
-            return ""; 
-        }
-        int n = Math.min(value1.length(), value2.length());
-        for(int i=0; i<n; i++) {
-            if( value1.charAt(i)!=value2.charAt(i) ) { return value1.substring(0,i); }
-        }
-        return value1.substring(0,n);
-    }
-
-    public static String lcommon(List<String> values) {
-        if(values.isEmpty() ) { 
-            return ""; 
-        }
-        String text = values.get(0);
-        for(int i=1, n=values.size(); i<n; i++) {
-            text = lcommon(text, values.get(i));
-        }
-        return text;
-    }
-
-    public static List<String> lcommonTrim(List<String> values) {
-        int n = lcommon(values).length();
-        List<String> result = new ArrayList<>(values.size());
-        for(String text : values) {
-            result.add(text.substring(n));
-        }
-        return result;
-    }
-
-    public static String reverse(String value) {
-        return Strings.isEmpty(value) ? value : new StringBuilder(value).reverse().toString();
-    }
-
-    public static String find(String text, Pattern pattern) {
-        Matcher hit = pattern.matcher(text);
-        if( hit.find() ) {
-            return hit.groupCount()>0 ? hit.group(1) : hit.group();
-        } else {
-            return null;
-        }
-    }
-
-    public static String find(String text, String pattern) {
-        return find(text, Pattern.compile(pattern));
-    }
-
-    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 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 CharSequence nCopies(int n, String text) {
-        return new NCopies(n, text);
-    }
-
-    public static CharSequence nCopies(int n, char chr) {
-        return new NCCopies(n, chr);
-    }
-
-    private static class NCCopies implements CharSequence {
-        private final int count;
-        private final char chr;
-
-        public NCCopies(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) {
-            return new NCCopies(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 NCopies implements CharSequence {
-        private final int start;
-        private final int end;
-        private final int mod;
-        private final String text;
-
-        public NCopies(int count, String text) {
-            this(text, 0, count * text.length());
-        }
-
-        public NCopies(String text, int start, int end) {
-            this.start  = start;
-            this.end    = end;
-            this.mod    = text.length();
-            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) % mod );
-        }
-
-        @Override
-        public int length() {
-            return end-start;
-        }
-
-        @Override
-        public CharSequence subSequence(int start, int end) {
-            return new NCopies(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);
-        }
-
-    }
-
-    public static String escapeRegex(String text) {
-        return REGEX_SPECIAL.matcher(text).replaceAll("\\\\$1");
-    }
-    
-}
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.text;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.ranides.assira.generic.Function;
+import net.ranides.assira.math.MathUtils;
+
+/**
+ *
+ * @author ranides
+ */
+public final class StringUtils {
+
+    public static final String ENDL = String.format("%n");
+
+    private static final Pattern  REGEX_SPECIAL = Pattern.compile("([\\\\*+\\[\\](){}\\$.?\\^|])");
+
+    private StringUtils() { /* utility class */ }
+
+    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( Strings.isEmpty(text)) {
+            return "";
+        }
+        if(text.startsWith(prefix)) {
+            return text.substring(prefix.length());
+        }
+        return text;
+    }
+
+    public static String removeSuffix(String text, String suffix) {
+        if( Strings.isEmpty(text)) {
+            return "";
+        }
+        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<String,Matcher> converter) {
+        Matcher matcher = pattern.matcher(text);
+        StringBuffer buffer = new StringBuffer();
+        while(matcher.find()) {
+            String str = converter.apply(matcher);
+            if( null!=str ) { matcher.appendReplacement(buffer, Matcher.quoteReplacement(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(null == text) {
+            return "";
+        }
+        if(text.isEmpty()) {
+            return text;
+        }
+        int start = 0;
+        int end = text.indexOf(search, start);
+        if (end == -1) {
+            return text;
+        }
+        final 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 extends CharSequence> String join(Iterable<T> values, String separator) {
+        if (values instanceof Collection) {
+            Collection<?> collection = (Collection<?>)values;
+            return join( collection.toArray(new CharSequence[collection.size()]), separator );
+        }
+        StringBuilder builder = new StringBuilder();
+        boolean first = true;
+        for(Object value : values) {
+            if(!first) { builder.append(separator); }
+            if(null != value) { builder.append(value.toString()); }
+            first = false;
+        }
+        return builder.toString();
+    }
+
+    public static <T extends CharSequence> String join(Iterable<T> values) {
+        if (values instanceof Collection) {
+            Collection<?> collection = (Collection<?>)values;
+            return join( collection.toArray(new CharSequence[collection.size()]));
+        }
+        StringBuilder builder = new StringBuilder();
+        for(Object value : values) {
+            builder.append( value.toString() );
+        }
+        return builder.toString();
+    }
+
+    public static <T extends CharSequence> String join(T[] list, String separator) {
+        if(list.length == 0) {
+            return "";
+        }
+        if(list.length == 1) {
+            return list[0].toString();
+        }
+
+        int size = 0;
+        for(int i=0; i<list.length; i++) {
+            size+=list[i].length();
+        }
+        size += list.length * separator.length();
+
+        StringBuilder result = new StringBuilder(size);
+        result.append(list[0]);
+        for(int i=1; i<list.length; i++) {
+            result.append(separator).append(list[i]);
+        }
+        return result.toString();
+    }
+
+    public static <T extends CharSequence> String join(T[] list) {
+        if(list.length == 0) {
+            return "";
+        }
+        if(list.length == 1) {
+            return list[0].toString();
+        }
+
+        int size = 0;
+        for(int i=0; i<list.length; i++) {
+            size+=list[i].length();
+        }
+
+        StringBuilder result = new StringBuilder(size);
+        for(int i=0; i<list.length; i++) {
+            result.append(list[i]);
+        }
+        return result.toString();
+    }
+
+    /**
+     * Zamienia pierwszą literę w tekście na dużą. Nie uwzględnia lokalizacji.
+     * @param value
+     * @return
+     */
+    public static String capitalize(String value) {
+        return capitalize(value, Locale.ROOT);
+    }
+
+    public static String uncapitalize(String value) {
+        return uncapitalize(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, 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) {
+        String[] parts = value.split("_+");
+        if(0==parts.length) {
+            return "";
+        }
+        if(1==parts.length) {
+            return parts[0].toLowerCase(Locale.ROOT);
+        }
+
+        StringBuilder result = new StringBuilder(value.length()-parts.length);
+        boolean first = true;
+        for (String part : parts) {
+            if(part.isEmpty()) {
+                continue;
+            }
+            if (first) {
+                result.append(part.toLowerCase(Locale.ROOT));
+                first = false;
+            } else {
+                result.append(part.substring(0, 1).toUpperCase(Locale.ROOT));
+                result.append(part.substring(1).toLowerCase(Locale.ROOT));
+            }
+        }
+        return result.toString();
+    }
+
+    public static String uncamelize(String value) {
+        return replace(value, Pattern.compile("(\\p{Lu})"), new Function<String,Matcher>() {
+            @Override
+            public String apply(Matcher source) {
+                if(0 == source.start(1)) {
+                    return source.group(1).toLowerCase(Locale.ROOT);
+                }
+                return "_" + source.group(1).toLowerCase(Locale.ROOT);
+            }
+        });
+    }
+
+    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) {
+        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 lcommon(String value1, String value2) {
+        if( Strings.isEmpty(value1) || Strings.isEmpty(value2) ) { 
+            return ""; 
+        }
+        int n = Math.min(value1.length(), value2.length());
+        for(int i=0; i<n; i++) {
+            if( value1.charAt(i)!=value2.charAt(i) ) { return value1.substring(0,i); }
+        }
+        return value1.substring(0,n);
+    }
+
+    public static String lcommon(List<String> values) {
+        if(values.isEmpty() ) { 
+            return ""; 
+        }
+        String text = values.get(0);
+        for(int i=1, n=values.size(); i<n; i++) {
+            text = lcommon(text, values.get(i));
+        }
+        return text;
+    }
+
+    public static List<String> lcommonTrim(List<String> values) {
+        int n = lcommon(values).length();
+        List<String> result = new ArrayList<>(values.size());
+        for(String text : values) {
+            result.add(text.substring(n));
+        }
+        return result;
+    }
+
+    public static String reverse(String value) {
+        return Strings.isEmpty(value) ? value : new StringBuilder(value).reverse().toString();
+    }
+
+    public static String find(String text, Pattern pattern) {
+        Matcher hit = pattern.matcher(text);
+        if( hit.find() ) {
+            return hit.groupCount()>0 ? hit.group(1) : hit.group();
+        } else {
+            return null;
+        }
+    }
+
+    public static String find(String text, String pattern) {
+        return find(text, Pattern.compile(pattern));
+    }
+
+    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 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 CharSequence nCopies(int n, String text) {
+        return new NCopies(n, text);
+    }
+
+    public static CharSequence nCopies(int n, char chr) {
+        return new NCCopies(n, chr);
+    }
+
+    private static class NCCopies implements CharSequence {
+        private final int count;
+        private final char chr;
+
+        public NCCopies(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) {
+            return new NCCopies(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 NCopies implements CharSequence {
+        private final int start;
+        private final int end;
+        private final int mod;
+        private final String text;
+
+        public NCopies(int count, String text) {
+            this(text, 0, count * text.length());
+        }
+
+        public NCopies(String text, int start, int end) {
+            this.start  = start;
+            this.end    = end;
+            this.mod    = text.length();
+            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) % mod );
+        }
+
+        @Override
+        public int length() {
+            return end-start;
+        }
+
+        @Override
+        public CharSequence subSequence(int start, int end) {
+            return new NCopies(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);
+        }
+
+    }
+
+    public static String escapeRegex(String text) {
+        return REGEX_SPECIAL.matcher(text).replaceAll("\\\\$1");
+    }
+    
+}