package de.axone.tools;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
import de.axone.refactor.NotTested;
public class Str {
public static final Joiner<Object> TO_STRING_KOMMA =
new Joiner<Object>() {
@Override
public String toString( Object object, int index ) {
return object.toString();
}
};
public static final Joiner<String> IS_STRING_KOMMA =
new Joiner<String>() {
@Override
public String toString( String object, int index ) {
return object;
}
};
// --- J o i n ------------------------------------------------------
public static String join( String joinWith, boolean [] ints ){
return join( joinWith, A.objects( ints ) ).toString();
}
public static String join( String joinWith, char [] ints ){
return join( joinWith, A.objects( ints ) ).toString();
}
public static String join( String joinWith, short [] ints ){
return join( joinWith, A.objects( ints ) ).toString();
}
public static String join( String joinWith, int [] ints ){
return join( joinWith, A.objects( ints ) ).toString();
}
public static String join( String joinWith, long [] ints ){
return join( joinWith, A.objects( ints ) ).toString();
}
public static String join( String joinWith, float [] ints ){
return join( joinWith, A.objects( ints ) ).toString();
}
public static String join( String joinWith, double [] ints ){
return join( joinWith, A.objects( ints ) ).toString();
}
@SafeVarargs
public static <T> String join( String joinWith, T ... objects ){
return joinB( joinWith, Arrays.asList( objects ) ).toString();
}
public static <T> String join( String joinWith, Iterable<T> objects ){
return joinB( joinWith, objects ).toString();
}
public static <T> String join( String joinWith, Stream<T> objects ){
return joinB( joinWith, objects ).toString();
}
@SafeVarargs
public static <T> String joinIgnoreEmpty( String joinWith, T ... objects ){
return joinIgnoreEmptyB( joinWith, Arrays.asList( objects ) ).toString();
}
public static <T> String joinIgnoreEmpty( String joinWith, Iterable<T> objects ){
return joinIgnoreEmptyB( joinWith, objects ).toString();
}
public static <T> String joinIgnoreEmpty( String joinWith, Stream<T> objects ){
return joinIgnoreEmptyB( joinWith, objects ).toString();
}
@SafeVarargs
public static <T, U extends T> String join( Joiner<T> joiner, U ... objects ){
return joinB( joiner, Arrays.asList( objects ) ).toString();
}
public static <T, U extends T> String join( Joiner<T> joiner, Iterable<U> objects ){
return joinB( joiner, objects ).toString();
}
public static <T, U extends T> String join( StreamJoiner<T> joiner, Stream<U> objects ){
return joinB( joiner, objects ).toString();
}
@SafeVarargs
public static <T, U extends T> String joinIgnoreEmpty( Joiner<T> joiner, U ... objects ){
return joinIgnoreEmptyB( joiner, Arrays.asList( objects ) ).toString();
}
public static <T, U extends T> String joinIgnoreEmpty( Joiner<T> joiner, Iterable<U> objects ){
return joinIgnoreEmptyB( joiner, objects ).toString();
}
public static <T, U extends T> String joinIgnoreEmpty( StreamJoiner<T> joiner, Stream<U> objects ){
return joinIgnoreEmptyB( joiner, objects ).toString();
}
// joinB
public static <T> StringBuilder joinB( String joinWith, Iterable<T> objects ){
return joinBB( new StringBuilder(), joinWith, false, objects );
}
public static <T> StringBuilder joinB( String joinWith, Stream<T> objects ){
return joinBB( new StringBuilder(), joinWith, false, objects );
}
public static <T, U extends T> StringBuilder joinB( Joiner<T> joiner, Iterable<U> objects ){
return joinBB( new StringBuilder(), joiner, false, objects );
}
public static <T, U extends T> StringBuilder joinB( StreamJoiner<T> joiner, Stream<U> objects ){
return joinBB( new StringBuilder(), joiner, false, objects );
}
public static <M,N> StringBuilder joinB( MapJoiner<M,N> joiner, Map<M,N> objects ){
return joinBB( new StringBuilder(), joiner, false, objects );
}
public static <T, U extends T> StringBuilder joinIgnoreEmptyB( String joinWith, Iterable<U> objects ){
return joinBB( new StringBuilder(), joinWith, true, objects );
}
public static <T, U extends T> StringBuilder joinIgnoreEmptyB( String joinWith, Stream<U> objects ){
return joinBB( new StringBuilder(), joinWith, true, objects );
}
public static <T, U extends T> StringBuilder joinIgnoreEmptyB( Joiner<T> joiner, Iterable<U> objects ){
return joinBB( new StringBuilder(), joiner, true, objects );
}
public static <T, U extends T> StringBuilder joinIgnoreEmptyB( StreamJoiner<T> joiner, Stream<U> objects ){
return joinBB( new StringBuilder(), joiner, true, objects );
}
public static <M,N> StringBuilder joinIgnoreEmptyB( MapJoiner<M,N> joiner, Map<M,N> objects ){
return joinBB( new StringBuilder(), joiner, true, objects );
}
// joinBB
@SuppressWarnings( "unchecked" )
public static <T, U extends T> StringBuilder joinBB( StringBuilder result, String joinWith, boolean ignoreEmpty, U ... objects ){
return joinBB( result, new SimpleJoiner<T>( joinWith ), ignoreEmpty, Arrays.asList( objects ) );
}
public static <T, U extends T> StringBuilder joinBB( StringBuilder result, String joinWith, boolean ignoreEmpty, Iterable<U> objects ){
return joinBB( result, new SimpleJoiner<T>( joinWith ), ignoreEmpty, objects );
}
public static <T, U extends T> StringBuilder joinBB( StringBuilder result, String joinWith, boolean ignoreEmpty, Stream<U> objects ){
return joinBB( result, new SimpleStreamJoiner<T>( joinWith ), ignoreEmpty, objects );
}
public static <T, U extends T> StringBuilder joinBB( StringBuilder result, Joiner<T> joiner, boolean ignoreEmpty, Iterable<U> objects ){
int index=0;
if( objects != null ) for( T object : objects ){
if( ignoreEmpty && ( object == null || "".equals( object ) ) ) continue;
if( index > 0 ) result.append( joiner.getSeparator() );
result.append( joiner.toString( object, index ) );
index++;
}
return result;
}
public static <T, U extends T> StringBuilder joinBB( StringBuilder result, StreamJoiner<T> joiner, boolean ignoreEmpty, Stream<U> objects ){
return result.append( objects
.collect( new StringCollector<T>( joiner ) )
) ;
}
private static class StringCollector<T> implements Collector<T,StringJoiner,String>{
private final StreamJoiner<T> joiner;
StringCollector( StreamJoiner<T> joiner ){
this.joiner = joiner;
}
@Override
public Supplier<StringJoiner> supplier() {
return () -> new StringJoiner( joiner.getSeparator(), "", "" );
}
@Override
public BiConsumer<StringJoiner, T> accumulator() {
return (sj, item) -> sj.add( joiner.toString( item ) );
}
@Override
public BinaryOperator<StringJoiner> combiner() {
return StringJoiner::merge;
}
@Override
public Function<StringJoiner, String> finisher() {
return StringJoiner::toString;
}
@Override
public Set<java.util.stream.Collector.Characteristics> characteristics() {
return Collections.emptySet();
}
};
// Map
public static <M,N> String join( MapJoiner<M,N> joiner, Map<M,N> objects ){
return joinB( joiner, objects ).toString();
}
public static <K,V> String join( String rs, String fs, Map<K,V> map ){
return joinB( rs, fs, map ).toString();
}
public static <K,V> StringBuilder joinB( String rs, String fs, Map<K,V> map ){
return joinBB( new StringBuilder(), rs, fs, false, map );
}
public static <M,N> String joinIgnoreEmpty( MapJoiner<M,N> joiner, Map<M,N> objects ){
return joinIgnoreEmptyB( joiner, objects ).toString();
}
public static <K,V> String joinIgnoreEmpty( String rs, String fs, Map<K,V> map ){
return joinIgnoreEmptyB( rs, fs, map ).toString();
}
public static <K,V> StringBuilder joinIgnoreEmptyB( String rs, String fs, Map<K,V> map ){
return joinBB( new StringBuilder(), rs, fs, true, map );
}
public static <K,V> StringBuilder joinBB( StringBuilder result, String rs, String fs, boolean ignoreEmpty, Map<K,V> map ){
return joinBB( result, new SimpleMapJoiner<K,V>( rs, fs ), ignoreEmpty, map );
}
public static <K,V> StringBuilder joinBB( StringBuilder result, MapJoiner<K,V> joiner, boolean ignoreEmpty, Map<K,V> map ){
int index = 0;
if( map != null ) for( Map.Entry<K,V> entry : map.entrySet() ){
K key = entry.getKey();
V value = entry.getValue();
if( value == null && ! ignoreEmpty ) continue;
if( index > 0 ) result.append( joiner.getRecordSeparator() );
result
.append( joiner.keyToString( key, index ) )
.append( joiner.getFieldSeparator() )
.append( joiner.valueToString( value, index ) )
;
index++;
} else {
result.append( "- null -" );
}
return result;
}
// --- J o i n e r --------------------------------------------------
@FunctionalInterface
public interface Joiner<T> {
public String toString( T object, int index );
public default String getSeparator(){ return ", "; }
}
@FunctionalInterface
public interface StreamJoiner<T> {
public String toString( T object ); //<- We can't use indexes here
public default String getSeparator(){ return ", "; }
}
public static class SimpleJoiner<T> implements Joiner<T> {
private String separator;
public SimpleJoiner( String separator ){
this.separator = separator;
}
@Override
public String getSeparator(){
return separator;
}
@Override
public String toString( T object, int index ){
if( object == null ) return null;
return object.toString();
}
}
public static class SimpleStreamJoiner<T> implements StreamJoiner<T> {
private String separator;
public SimpleStreamJoiner( String separator ){
this.separator = separator;
}
@Override
public String getSeparator(){
return separator;
}
@Override
public String toString( T object ){
if( object == null ) return null;
return object.toString();
}
}
public static final Joiner<String> CSVJOINER = new Joiner<String>() {
@Override
public String getSeparator() {
return ", ";
}
@Override
public String toString( String object, int index ) {
return '"' + object.replace( "\"", "\"\"" ) + '"';
}
};
public interface MapJoiner<K,V> {
public String getRecordSeparator();
public String getFieldSeparator();
public String keyToString( K nameField, int index );
public String valueToString( V valueField, int index );
}
public static class SimpleMapJoiner<M,N> implements MapJoiner<M,N> {
private String recordSeparator;
private String fieldSeparator;
SimpleMapJoiner( String recordSeparator, String fieldSeparator ){
this.recordSeparator = recordSeparator;
this.fieldSeparator = fieldSeparator;
}
@Override
public String getRecordSeparator() {
return recordSeparator;
}
@Override
public String getFieldSeparator() {
return fieldSeparator;
}
@Override
public String keyToString( M nameField, int index ) {
return nameField.toString();
}
@Override
public String valueToString( N valueField, int index ) {
return valueField != null ? valueField.toString() : "-null-";
}
}
// --- T r i m ------------------------------------------------------
public static String trimAtWordBoundary( String text, int len ){
return trimAtWordBoundary( text, len, null );
}
public static String trimAtWordBoundary( String text, int len, String appendix ){
assert text != null;
assert len > 0;
text = text.trim(); // Do some pretrimming
if( len >= text.length() ) return text;
int i;
for( i=len-1; i>0; i-- ){
if( Character.isWhitespace( text.charAt( i ) ) ) break;
}
if( i > 0 ){
return appendix != null ? text.substring( 0, i ) + appendix : text.substring( 0, i );
} else {
return appendix != null ? text.substring( 0, len ) + appendix : text.substring( 0, len );
}
}
public static String[] splitAtWordBoundaryNear( String text, int len ) {
assert text != null;
assert len >= 0;
if( len >= text.length() )
return new String[]{ text, null };
int i;
for( i=len-1; i>0; i-- ){
if( Character.isWhitespace( text.charAt( i ) ) ) break;
}
if( i > 0 ) {
return new String[]{ text.substring( 0, i ), text.substring( i+1 ) };
} else {
return new String[]{ null, text };
}
}
// --- S p l i t ----------------------------------------------------
public static String splitAt( int position, String text ){
StringBuilder result = new StringBuilder( text.length() + text.length()/position +1 );
for( int i=0; i < text.length(); i++ ){
result.append( text.charAt( i ) );
if( i%position ==0 && i < text.length()-1 ) result.append( '\n' );
}
return result.toString();
}
public static String [] splitFast( String s, char fs ){
int n = count( s, fs ) + 1;
if( n == 1 ) return new String[]{ s };
return splitFastLimited( s, fs, n );
}
public static String [] splitFastAndTrim( String s, char fs ){
String [] result = splitFast( s, fs );
for( int i=0; i < result.length; i++ ){
result [ i ] = result[ i ].trim();
}
return result;
}
/**
* StipedDonwVersionOfSplit resticted to n results
*
* Note that the size of N has a significant impact on performance
* even if the amount of found parts is smaller than n
*
* @param s
* @param n the limit. This is needed to prealocate the result array.
* @param split
* @return the splitted values in a string array
*/
public static String [] splitFastLimited( String s, char split, int n ){
final String [] result = new String[ n ];
final int len = s.length();
int num=0;
int i, last=0;
for( i=0; i<len; i++ ){
if( split == s.charAt( i ) ){
result[ num ] = s.substring( last, i );
last = i+1;
num++;
if( num == n-1 ) break;
};
}
if( len>=last ){
result[ num ] = s.substring( last );
}
return Arrays.copyOf( result, num+1 );
}
public static List<String> splitFastToList( String s, char split ){
List<String> result = new ArrayList<>( 8 );
final int len = s.length();
int i, last=0;
for( i=0; i<len; i++ ){
if( split == s.charAt( i ) ){
result.add( s.substring( last, i ) );
last = i+1;
};
}
if( len>=last ){
result.add( s.substring( last ) );
}
return result;
}
public static List<String> splitFastToListAndProcess( String s, char split, Function<String,String> processor ){
List<String> result = new ArrayList<>( 8 );
final int len = s.length();
int i, last=0;
for( i=0; i<len; i++ ){
if( split == s.charAt( i ) ){
result.add( processor.apply( s.substring( last, i ) ) );
last = i+1;
};
}
if( len>=last ){
result.add( processor.apply( s.substring( last ) ) );
}
return result;
}
public static Set<String> splitFastToSet( String s, char split ){
Set<String> result = new HashSet<>( 8 );
final int len = s.length();
int i, last=0;
for( i=0; i<len; i++ ){
if( split == s.charAt( i ) ){
result.add( s.substring( last, i ) );
last = i+1;
};
}
if( len>=last ){
result.add( s.substring( last ) );
}
return result;
}
public static Set<String> splitFastToSetAndProcess( String s, char split, Function<String,String> processor ){
Set<String> result = new HashSet<>( 8 );
final int len = s.length();
int i, last=0;
for( i=0; i<len; i++ ){
if( split == s.charAt( i ) ){
result.add( processor.apply( s.substring( last, i ) ) );
last = i+1;
};
}
if( len>=last ){
result.add( processor.apply( s.substring( last ) ) );
}
return result;
}
public static List<String> splitFastAtSpacesToList( String s ){
List<String> result = new ArrayList<>( 8 );
final int len = s.length();
int i, last=0;
for( i=0; i<len; i++ ){
if( Character.isWhitespace( s.charAt( i ) ) ){
if( last < i ){
result.add( s.substring( last, i ) );
}
last = i+1;
};
}
if( len>last ){
result.add( s.substring( last ) );
}
return result;
}
/**
* Split s one time
*
* @param s
* @param split
* @return the split values in a string array
*/
public static String [] splitFastOnce( String s, char split ){
int pos = s.indexOf( split );
if( pos >= 0 ){
return new String[]{ s.substring( 0, pos ), s.substring( pos+1 ) };
} else {
return new String[]{ s };
}
}
/**
* Split s one time starting at index
*
* @param s
* @param split
* @param startAt
* @return the split values in a string array
*/
public static String [] splitFastOnce( String s, char split, int startAt ){
int pos = s.indexOf( split, startAt );
if( pos >= 0 ){
return new String[]{ s.substring( 0, pos ), s.substring( pos+1 ) };
} else {
return new String[]{ s };
}
}
/**
* Split s one time
*
* @param s
* @param split
* @return the split values in a string array
*/
public static String [] splitFastOnce( String s, String split ){
int pos = s.indexOf( split );
if( pos >= 0 ){
return new String[]{ s.substring( 0, pos ), s.substring( pos+split.length() ) };
} else {
return new String[]{ s };
}
}
// --- R e m o v e --------------------------------------------------
public static String removeChars( String s, char ... chars ){
Arrays.sort( chars );
StringBuilder result = new StringBuilder();
for( int i=0; i<s.length(); i++ ){
char current = s.charAt( i );
if( Arrays.binarySearch( chars, current ) < 0 ){
result.append( current );
}
}
return result.toString();
}
public static String collapseWhitespace( String s ) {
StringBuilder result = new StringBuilder( s.length() );
boolean startOfString = true;
boolean lastWasWs = true;
for( int i=0; i<s.length(); i++ ){
char c = s.charAt( i );
if( Character.isWhitespace( c ) ){
if( lastWasWs ) continue;
lastWasWs = true;
} else {
if( lastWasWs && ! startOfString ) result.append( ' ' );
lastWasWs = false;
startOfString = false;
result.append( c );
}
}
return result.toString();
}
@NotTested
public static List<String> collapseEmpty( List<String> list ){
List<String> result = new ArrayList<>( list.size() );
for( String value : list ){
if( value != null && value.length() > 0 ) result.add( value );
}
return result;
}
@NotTested
public static List<String> collapseEmptyInPlace( List<String> list ){
for( Iterator<String> it = list.iterator(); it.hasNext(); ){
String value = it.next();
if( value == null || value.length() == 0 ) it.remove();;
}
return list;
}
@NotTested
public static List<String> trim( List<String> list ){
List<String> result = new ArrayList<>( list.size() );
for( String value : list ){
if( value == null ) result.add( null );
else result.add( value.trim() );
}
return result;
}
@NotTested
public static List<String> trimInPlace( List<String> list ){
for( int i=0; i<list.size(); i++ ){
String value = list.get( i );
if( value == null ) continue;
String trimmed = value.trim();
if( value != trimmed ) list.set( i, trimmed );
}
return list;
}
@NotTested
public static List<String> trimEmpty( List<String> list ){
List<String> result = new ArrayList<>( list.size() );
for( String value : list ){
if( value != null && value.length() > 0 && value.trim().length() > 0 ) result.add( value );
}
return result;
}
@NotTested
public static List<String> trimEmptyInPlace( List<String> list ){
for( Iterator<String> it = list.iterator(); it.hasNext(); ){
String value = it.next();
if( value == null || value.length() == 0 || value.trim().length() == 0 ) it.remove();;
}
return list;
}
public static String trimToNull( String string ) {
string = string.trim();
if( string.length() == 0 ) return null;
return string;
}
public static final String translate( String value, char from, char to ){
if( value == null ) return null;
int i, len = value.length();
// First look if there is 'from' char anyway
for( i=0; i<len; i++ ) {
if( value.charAt( i ) == from ) break;
}
// Only do something of so
if( i < len ) {
char [] asArray = value.toCharArray();
// start with old i value because we know there is nothing before that
for( ; i<asArray.length; i++ ) {
char c = asArray[ i ];
if( c == from ){
asArray[ i ] = to;
}
}
return new String( asArray );
} else {
return value;
}
}
public static final String translate( String value, char from, String to ){
if( value == null ) return null;
StringBuilder result = new StringBuilder( value.length() + value.length()/10 );
for( int i=0; i<value.length(); i++ ){
char c = value.charAt( i );
if( c == from ){
result.append( to );
} else {
result.append( c );
}
}
return result.toString();
}
public static final String translate( String value, char [] from, String [] to ){
if( value == null ) return null;
StringBuilder result = new StringBuilder( value.length() + value.length()/10 );
for( int i=0; i<value.length(); i++ ){
char c = value.charAt( i );
boolean found = false;
for( int j = 0; j<from.length; j++ ){
if( c == from[ j ] ){
result.append( to[ j ] );
found = true;
}
}
if( ! found ) result.append( c );
}
return result.toString();
}
public static final boolean contains( String value, char ch ){
return value.indexOf( ch ) >= 0;
}
public static final boolean contains( String haystack, String needle ) {
return haystack.indexOf( needle ) >= 0;
}
public static int count( String text, char rs ) {
int result = 0;
for( int i=0; i<text.length(); i++ ){
if( text.charAt( i ) == rs ) result++;
}
return result;
}
public static final boolean containsOneOf( String value, char [] chars ){
value.indexOf( 'c' );
for( int i=0; i<value.length(); i++ ){
char c = value.charAt( i );
for( char ct : chars ){
if( c == ct ) return true;
}
}
return false;
}
public static String replaceFast( String value, String replace, String with ) {
if( ! value.contains( replace ) ) return value;
// TODO: Make fast
return value.replace( replace, with );
}
@FunctionalInterface
public interface ReplacementProvider {
public String getReplacementFor( String key );
}
public static String replaceFast( final String text, final String prefix, final String suffix, ReplacementProvider provider ){
int pLength = prefix.length(),
sLength = suffix.length();
int idxStart = 0, idxEnd = -pLength;
StringBuilder result = new StringBuilder();
// Go through looking for "--("
while( (idxStart = text.indexOf( prefix, idxEnd )) > -1 ){
result.append( text.substring( idxEnd+sLength, idxStart ) );
idxEnd = text.indexOf( suffix, idxStart );
if( idxEnd < 0 ) break; // <-- Error is silently ignored here
String val = text.substring( idxStart+pLength, idxEnd ).trim();
if( val.length() > 0 ){
val = val.toLowerCase();
String txt = provider.getReplacementFor( val );
if( txt == null ) txt = prefix + "missing: " + val + suffix;
result.append( txt );
} else {
// Keep empty --()-- because they are processed later on and
// mean "inherit text from parent/child"
result.append( prefix + suffix );
}
idxStart = idxEnd;
};
if( idxEnd < 0 ){
// Nothing to replace or error happend
return text;
} else{
result.append( text.substring( idxEnd+pLength ) );
return result.toString();
}
}
public static String clean( String string ) {
StringBuilder result = new StringBuilder();
char [] chars = string.toCharArray();
for( char c : chars ) {
if( c >= 'a' && c <= 'z' || c >='A' && c <= 'Z' || c >= '0' && c <= '9' ) result.append( c );
else if( c == ' ' || c == '_' ) result.append( '_' );
}
return result.toString();
}
public static String cleanToLowerCase( String string ) {
StringBuilder result = new StringBuilder();
char [] chars = string.toCharArray();
for( char c : chars ) {
if( c >= 'a' && c <= 'z' || c >= '0' && c <= '9' ) result.append( c );
else if( c >='A' && c <= 'Z' ) result.append( (char)(c + ('a'-'A')) ); //(char) is needed to select the correct method because char + char is casted to int otherwise
else if( c == ' ' || c == '_' ) result.append( '_' );
}
return result.toString();
}
public static final class Chain<T> implements Function<T,T> {
private final Function<T,T>[] chain;
@SafeVarargs
public Chain( Function<T,T> ... chain ){
this.chain = chain;
}
@SafeVarargs
public static <X> Chain<X> of( Function<X,X> ... chain ) {
return new Chain<>( chain );
}
@Override
public T apply( T value ) {
T out = chain[ 0 ].apply( value );
for( int i=1; i<chain.length; i++ ) {
out = chain[ i ].apply( out );
}
return out;
}
}
}