/**
*
*/
package net.varkhan.base.containers.array;
import java.io.IOException;
/**
* <b>Static char arrays manipulation utilities.</b>
* <p/>
* Utilities for manipulating arrays of chars and character sequences.
* <p/>
*
* @author varkhan
* @date Mar 12, 2009
* @time 3:09:04 AM
*/
public class CharArrays {
/**
* Private empty constructor that forbids instantiation of this class.
*/
protected CharArrays() {
}
/*********************************************************************************
** Matching operations
**/
/**
* Indicates whether two char arrays are equal.
*
* @param array1 the first array
* @param array2 the second array
*
* @return {@code true} iff the arrays are either both {@code null} or
* have the same number of elements, and the values of elements in the
* same positions are equal
*/
public static boolean equals(char[] array1, char[] array2) {
if(array1==null) return array2==null;
if(array1.length!=array2.length) return false;
for(int i=0;i<array1.length;i++) {
if(array1[i]!=array2[i]) return false;
}
return true;
}
/**
* Indicates whether two CharSequence are equal.
*
* @param seq1 the first array
* @param seq2 the second array
*
* @return {@code true} iff the CharSequences are either both {@code null} or
* have the same number of characters, and the same characters in the
* same positions
*/
public static boolean equals(CharSequence seq1, CharSequence seq2) {
if(seq1==null) return seq2==null;
if(seq1.length()!=seq2.length()) return false;
for(int i=0;i<seq1.length();i++) {
if(seq1.charAt(i)!=seq2.charAt(i)) return false;
}
return true;
}
/**
* Returns the position of the first occurrence of a value inside an array.
*
* @param item the value to search for
* @param array the array to search
*
* @return {@literal -1} if {@code item} was not found in {@code array},
* the position of the first element of the array equal to {@code item}
*/
public static int indexOf(char item, char... array) {
if(array==null) return -1;
final int length = array.length;
for(int i=0;i<length;i++) if(item==array[i]) return i;
return -1;
}
/**
* Returns the position of the first occurrence of an array as a subsequence
* of an other array.
*
* @param sub the subsequence to find
* @param pos the starting point in the array
* @param array the array
*
* @return the smallest index {@code idx} greater than {@code pos} such that
* the elements of {@code array} starting at {@code idx} are exactly those of
* {@code sub} ({@code -1} is returned if {@code sub} is not a subsequence
* of {@code array})
*/
public static int indexOf(char[] sub, int pos, char[] array) {
if(array==null) return sub==null ? 0 : -1;
match:
while(pos<array.length) {
for(int i=0;i<sub.length;i++) {
if(pos+i>=array.length) return -1;
if(array[pos+i]!=sub[i]) {
pos++;
continue match;
}
}
return pos;
}
return -1;
}
/**
* Returns the position of the first occurrence of an array as a subsequence
* of an other array.
*
* @param sub the subsequence to find
* @param pos the starting point in the array
* @param array the array
*
* @return the smallest index {@code idx} greater than {@code pos} such that
* the elements of {@code array} starting at {@code idx} are exactly those of
* {@code sub} ({@code -1} is returned if {@code sub} is not a subsequence
* of {@code array})
*/
public static int indexOf(CharSequence sub, int pos, CharSequence array) {
if(array==null) return sub==null ? 0 : -1;
int al=array.length();
match:
while(pos<al) {
for(int i=0;i<sub.length();i++) {
if(pos+i>=al) return -1;
if(array.charAt(pos+i)!=sub.charAt(i)) {
pos++;
continue match;
}
}
return pos;
}
return -1;
}
/*********************************************************************************
** Sorting and search
**/
/**
* Sorts an array in place, in ascending order.
*
* @param ary the array to sort
* @return the number of swap operations required for the sorting
*/
public static int sortInc(char... ary) {
return sortInc(ary, 0, ary.length-1);
}
/**
* Sorts an array in place, in descending order.
*
* @param ary the array to sort
* @return the number of swap operations required for the sorting
*/
public static int sortDec(char... ary) {
return sortDec(ary, 0, ary.length-1);
}
/**
* Sorts an array segment in place, in ascending order.
*
* @param ary the array to sort
* @param inf the minimum index
* @param sup the maximum index
* @return the number of swap operations required for the sorting
*/
public static int sortInc(char[] ary, int inf, int sup) {
int cnt = 0;
int beg = ((inf+sup)>>1)+1; // inf + (sup-inf+1)/2 - 1 = (sup+inf)/2+1
while(beg>inf) {
beg --;
cnt += heapDownInc(ary,beg,sup);
}
int end = sup;
while(end>=inf) {
char v = ary[end];
ary[end] = ary[inf];
ary[inf] = v;
end --;
cnt += 1 + heapDownInc(ary,inf,end);
}
return cnt;
}
/**
* Sorts an array segment in place, in descending order.
*
* @param ary the array to sort
* @param inf the minimum index
* @param sup the maximum index
* @return the number of swap operations required for the sorting
*/
public static int sortDec(char[] ary, int inf, int sup) {
int cnt = 0;
int beg = ((inf+sup)>>1)+1; // inf + (sup-inf+1)/2 - 1 = (sup+inf)/2+1
while(beg>inf) {
beg --;
cnt += heapDownDec(ary,beg,sup);
}
int end = sup;
while(end>=inf) {
char v = ary[end];
ary[end] = ary[inf];
ary[inf] = v;
end --;
cnt += 1 + heapDownDec(ary,inf,end);
}
return cnt;
}
protected static int heapDownInc(char[] ary, int inf, int sup) {
int cnt = 0;
int pos = inf;
int cld = (pos<<1)+1;
while(cld<=sup) {
int swp = pos;
if(ary[swp]<ary[cld]) swp = cld;
cld ++;
if(cld<=sup) {
if(ary[swp]<ary[cld]) swp = cld;
}
if(swp==pos) return cnt;
char v = ary[pos];
ary[pos] = ary[swp];
ary[swp] = v;
cnt ++;
pos = swp;
cld = (pos<<1)+1;
}
return cnt;
}
protected static int heapDownDec(char[] ary, int inf, int sup) {
int cnt = 0;
int pos = inf;
int cld = (pos<<1)+1;
while(cld<=sup) {
int swp = pos;
if(ary[swp]>ary[cld]) swp = cld;
cld ++;
if(cld<=sup) {
if(ary[swp]>ary[cld]) swp = cld;
}
if(swp==pos) return cnt;
char v = ary[pos];
ary[pos] = ary[swp];
ary[swp] = v;
cnt ++;
pos = swp;
cld = (pos<<1)+1;
}
return cnt;
}
/**
* Finds an object in a sorted array, in ascending order.
*
* @param ary the sorted array
* @param inf the minimum index
* @param sup the maximum index
* @param key the value to search for
*
* @return the key position in the array, or {@code -(inspos+1)},
* where {@code inspos} is the index of the first value in the
* array bigger than {@code key}
*/
public static int searchInc(char[] ary, int inf, int sup, char key) {
int min=inf;
int max=sup-1;
while(min<=max) {
int med=(min+max)>>>1;
char medVal=ary[med];
if(medVal<key) min=med+1;
else if(medVal>key) max=med-1;
else return med; // key found
}
return -(min+1); // key not found.
}
/**
* Finds an object in a sorted array, in descending order.
*
* @param ary the sorted array
* @param inf the minimum index
* @param sup the maximum index
* @param key the value to search for
*
* @return the key position in the array, or {@code -(inspos+1)},
* where {@code inspos} is the index of the first value in the
* array bigger than {@code key}
*/
public static int searchDec(char[] ary, int inf, int sup, char key) {
int min=inf;
int max=sup-1;
while(min<=max) {
int med=(min+max)>>>1;
char medVal=ary[med];
if(medVal<key) min=med+1;
else if(medVal>key) max=med-1;
else return med; // key found
}
return -(min+1); // key not found.
}
/**
* Computes the union of segments in two sorted arrays, in ascending order.
*
* @param ary1 the first sorted array
* @param beg1 the start position of the first segment
* @param len1 the length of the first segment
* @param ary2 the second sorted array
* @param beg2 the start position of the second segment
* @param len2 the length of the second segment
* @return the union of the two segments, with duplicates removed
*/
public static char[] unionInc(char[] ary1, int beg1, int len1, char[] ary2, int beg2, int len2) {
int len = len1+len2;
char[] union = new char[len];
if(len==0) return union;
char last = 0;
len1+=beg1;
len2+=beg2;
int beg=0;
while(beg1<len1 && beg2<len2) {
char val1 = ary1[beg1];
char val2 = ary2[beg2];
if(val1<val2) {
if(beg==0||last<val1) last = union[beg++] = val1;
beg1++;
} else
if(val1>val2) {
if(beg==0||last<val2) last = union[beg++] = val2;
beg2++;
} else {
if(beg==0||last<val1) last = union[beg++] = val1;
beg1++; beg2++;
}
}
while(beg1<len1) {
char val1 = ary1[beg1];
if(beg==0||last<val1) last = union[beg++] = val1;
beg1++;
}
while(beg2<len2) {
char val2 = ary2[beg2];
if(beg==0||last<val2) last = union[beg++] = val2;
beg2++;
}
if(beg>=len) return union;
char[] copy = new char[beg];
System.arraycopy(union, 0, copy, 0, beg);
return copy;
}
/**
* Computes the union of segments in two sorted arrays, in descending order.
*
* @param ary1 the first sorted array
* @param beg1 the start position of the first segment
* @param len1 the length of the first segment
* @param ary2 the second sorted array
* @param beg2 the start position of the second segment
* @param len2 the length of the second segment
* @return the union of the two segments, with duplicates removed
*/
public static char[] unionDec(char[] ary1, int beg1, int len1, char[] ary2, int beg2, int len2) {
int len = len1+len2;
char[] union = new char[len];
if(len==0) return union;
char last = 0;
len1+=beg1;
len2+=beg2;
int beg=0;
while(beg1<len1 && beg2<len2) {
char val1 = ary1[beg1];
char val2 = ary2[beg2];
if(val1>val2) {
if(beg==0||last>val1) last = union[beg++] = val1;
beg1++;
} else
if(val1<val2) {
if(beg==0||last>val1) last = union[beg++] = val2;
beg2++;
} else {
if(beg==0||last>val1) last = union[beg++] = val1;
beg1++; beg2++;
}
}
while(beg1<len1) {
char val1 = ary1[beg1];
if(beg==0||last>val1) last = union[beg++] = val1;
beg1++;
}
while(beg2<len2) {
char val2 = ary2[beg2];
if(beg==0||last>val2) last = union[beg++] = val2;
beg2++;
}
if(beg>=len) return union;
char[] copy = new char[beg];
System.arraycopy(union, 0, copy, 0, beg);
return copy;
}
/**
* Computes the intersection of segments in two sorted arrays, in ascending order.
*
* @param ary1 the first sorted array
* @param beg1 the start position of the first segment
* @param len1 the length of the first segment
* @param ary2 the second sorted array
* @param beg2 the start position of the second segment
* @param len2 the length of the second segment
* @return the intersection of the two segments, with duplicates removed
*/
public static char[] interInc(char[] ary1, int beg1, int len1, char[] ary2, int beg2, int len2) {
int len = (len1>len2)?len1:len2;
char[] inter = new char[len];
if(len==0) return inter;
char last = 0;
len1+=beg1;
len2+=beg2;
int beg=0;
while(beg1<len1 && beg2<len2) {
char val1 = ary1[beg1];
char val2 = ary2[beg2];
if(val1<val2) { beg1++; } else
if(val1>val2) { beg2++; } else
{ if(beg==0||last<val1) last = inter[beg++] = val1; beg1++; beg2++; }
}
if(beg>=len) return inter;
char[] copy = new char[beg];
System.arraycopy(inter, 0, copy, 0, beg);
return copy;
}
/**
* Computes the intersection of segments in two sorted arrays, in descending order.
*
* @param ary1 the first sorted array
* @param beg1 the start position of the first segment
* @param len1 the length of the first segment
* @param ary2 the second sorted array
* @param beg2 the start position of the second segment
* @param len2 the length of the second segment
* @return the intersection of the two segments, with duplicates removed
*/
public static char[] interDec(char[] ary1, int beg1, int len1, char[] ary2, int beg2, int len2) {
int len = (len1>len2)?len1:len2;
char[] inter = new char[len];
if(len==0) return inter;
char last = 0;
len1+=beg1;
len2+=beg2;
int beg=0;
while(beg1<len1 && beg2<len2) {
char val1 = ary1[beg1];
char val2 = ary2[beg2];
if(val1>val2) { beg1++; } else
if(val1<val2) { beg2++; } else
{ if(beg==0||last>val1) last = inter[beg++] = val1; beg1++; beg2++; }
}
if(beg>=len) return inter;
char[] copy = new char[beg];
System.arraycopy(inter, 0, copy, 0, beg);
return copy;
}
/*********************************************************************************
** Copy and concatenation
**/
/**
* Append elements to an array.
*
* @param array the array to append to
* @param elems the elements to append
*
* @return an array of chars containing all the elements of {@code array} followed by all the elements in {@code elems}
*/
public static char[] append(char[] array, char... elems) {
char[] concat=new char[array.length+elems.length];
System.arraycopy(array, 0, concat, 0, array.length);
System.arraycopy(elems, 0, concat, array.length, elems.length);
return concat;
}
/**
* Prepend elements to an array.
*
* @param array the array to prepend to
* @param elems the elements to prepend
*
* @return an array of chars containing all the elements in {@code elems} followed by all the elements of {@code array}
*/
public static char[] prepend(char[] array, char... elems) {
char[] concat=new char[array.length+elems.length];
System.arraycopy(elems, 0, concat, 0, elems.length);
System.arraycopy(array, 0, concat, elems.length, array.length);
return concat;
}
/**
* Concatenates several arrays.
*
* @param array the first array to append to
* @param arrays the arrays to append
*
* @return an array of chars containing all the elements in the arrays, in order
*/
public static char[] concat(char[] array, char[]... arrays) {
int l=array.length;
for(char[] t : arrays) { l+=t.length; }
char[] concat=new char[l];
System.arraycopy(array, 0, concat, 0, array.length);
l=array.length;
for(char[] t : arrays) {
System.arraycopy(t, 0, concat, l, t.length);
l+=t.length;
}
return concat;
}
/**
* Returns a segment of an array.
*
* @param array the source array
* @param beg the begining position of the segment, inclusive
* @param end the ending position of the segment, exclusive
*
* @return an array of chars, of length {@code end-beg}, and containing all the
* elements between positions {@code beg}, inclusive, and {@code end}, exclusive,
* of the original array
*/
public static char[] subarray(char[] array, int beg, int end) {
int l=array.length;
if(beg<0 || beg>end || end>l) throw new ArrayIndexOutOfBoundsException("["+beg+":"+end+"] is not a valid range specifier");
char[] subary=new char[end-beg];
if(end>beg) System.arraycopy(array, beg, subary, 0, end-beg);
return subary;
}
/*********************************************************************************
** Character level manipulation
**/
/**
* Transliterates (replaces single characters in) a segment of a char array.
* <p/>
* Characters that are contained in the matching set are counted, and replaced
* by the character at the same position in the replacement set as the matched
* character in the matching set. Duplicate characters in the matching set are
* ignored, and only the first occurrence is taken into account. If the replacement
* set is shorter than the matching set, the remaining matched characters are
* counted, but remain unchanged. If the replacement set is longer, the remaining
* characters are ignored.
*
* @param str a char array containing the segment
* @param pos the position of the first character of the segment
* @param len the length of (number of characters in) the segment
* @param pat the matching character set (the set of characters to replace)
* @param rep the replacement character set (the characters to replace with)
*
* @return the number of characters that have been matched in the segment
*/
public static int tr(char[] str, int pos, int len, char[] pat, char[] rep) {
if(pos>=str.length) throw new ArrayIndexOutOfBoundsException(pos);
len+=pos;
if(len>str.length) throw new ArrayIndexOutOfBoundsException(pos);
int m=0;
for(;pos<len;pos++) {
char c=str[pos];
for(int j=0;j<pat.length;j++) {
if(pat[j]==c) {
m++;
if(rep!=null&&j<rep.length) str[pos]=rep[j];
break;
}
}
}
return m;
}
/**
* Transliterates (replaces single characters in) a char array.
* <p/>
* Characters that are contained in the matching set are counted, and replaced
* by the character at the same position in the replacement set as the matched
* character in the matching set. Duplicate characters in the matching set are
* ignored, and only the first occurrence is taken into account. If the replacement
* set is shorter than the matching set, the remaining matched characters are
* counted, but remain unchanged. If the replacement set is longer, the remaining
* characters are ignored.
*
* @param str a char array
* @param pat the matching character set (the set of characters to replace)
* @param rep the replacement character set (the characters to replace with)
*
* @return the number of characters that have been matched in the segment
*/
public static int tr(char[] str, char[] pat, char[] rep) {
int m=0;
for(int i=0;i<str.length;i++) {
char c=str[i];
for(int j=0;j<pat.length;j++) {
if(pat[j]==c) {
m++;
if(rep!=null&&j<rep.length) str[i]=rep[j];
break;
}
}
}
return m;
}
/**
* Transliterates (replaces single characters in) a CharSequence.
* <p/>
* Characters that are contained in the matching set are counted, and replaced
* by the character at the same position in the replacement set as the matched
* character in the matching set. Duplicate characters in the matching set are
* ignored, and only the first occurrence is taken into account. If the replacement
* set is shorter than the matching set, the remaining matched characters are
* counted, but remain unchanged. If the replacement set is longer, the remaining
* characters are ignored.
*
* @param str a CharSequence
* @param pat the matching character set (the set of characters to replace)
* @param rep the replacement character set (the characters to replace with)
*
* @return a new CharSequence with the replaced characters
*/
public static CharSequence tr(CharSequence str, char[] pat, char[] rep) {
char[] ss=new char[str.length()];
for(int i=0;i<str.length();i++) {
char c=str.charAt(i);
ss[i]=c;
for(int j=0;j<pat.length;j++) {
if(pat[j]==c) {
if(rep!=null&&j<rep.length) ss[i]=rep[j];
break;
}
}
}
return new String(ss);
}
/**
* Transliterates (replaces single characters in) a String.
* <p/>
* Characters that are contained in the matching set are counted, and replaced
* by the character at the same position in the replacement set as the matched
* character in the matching set. Duplicate characters in the matching set are
* ignored, and only the first occurrence is taken into account. If the replacement
* set is shorter than the matching set, the remaining matched characters are
* counted, but remain unchanged. If the replacement set is longer, the remaining
* characters are ignored.
*
* @param sep a String
* @param pat the matching character set (the set of characters to replace)
* @param rep the replacement character set (the characters to replace with)
*
* @return a new String with the replaced characters
*/
public static String tr(String sep, char[] pat, char[] rep) {
char[] ss=sep.toCharArray();
for(int i=0;i<ss.length;i++) {
char c=ss[i];
for(int j=0;j<pat.length;j++) {
if(pat[j]==c) {
if(rep!=null&&j<rep.length) ss[i]=rep[j];
break;
}
}
}
return new String(ss);
}
/*********************************************************************************
** Subsequence level manipulations
**/
/**
* Replace a character by a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the character to replace
* @param rep the CharSequence to replace matches with
*
* @return the original buffer, for chaining purposes
*/
public static StringBuilder repl(StringBuilder buf, CharSequence str, char pat, CharSequence rep) {
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;i++) {
char c=str.charAt(i);
// Look for a local match starting at i
if(c!=pat) {
// No match => add current char, restart match
buf.append(c);
continue find;
}
// Match => add replacement
buf.append(rep);
}
return buf;
}
/**
* Replace a character by a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the character to replace
* @param rep the CharSequence to replace matches with
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <A extends Appendable> A repl(A buf, CharSequence str, char pat, CharSequence rep) throws IOException {
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;i++) {
char c=str.charAt(i);
// Look for a local match starting at i
if(c!=pat) {
// No match => add current char, restart match
buf.append(c);
continue find;
}
// Match => add replacement
buf.append(rep);
}
return buf;
}
/**
* Replace a CharSequence by a character.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the CharSequence to replace
* @param rep the character to replace matches with
*
* @return the original buffer, for chaining purposes
*/
public static StringBuilder repl(StringBuilder buf, CharSequence str, CharSequence pat, char rep) {
final int lp=pat.length();
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=pat.charAt(j)) {
// No match => add current char, restart match
buf.append(str.charAt(i++));
continue find;
}
}
// Match => add replacement
buf.append(rep);
i+=lp;
}
return buf;
}
/**
* Replace a CharSequence by a character.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the CharSequence to replace
* @param rep the character to replace matches with
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <A extends Appendable> A repl(A buf, CharSequence str, CharSequence pat, char rep) throws IOException {
final int lp=pat.length();
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=pat.charAt(j)) {
// No match => add current char, restart match
buf.append(str.charAt(i++));
continue find;
}
}
// Match => add replacement
buf.append(rep);
i+=lp;
}
return buf;
}
/**
* Replace a subsequence by an other in a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the CharSequence to replace in the source
* @param rep the CharSequence to replace matches with
*
* @return the original buffer, for chaining purposes
*/
public static StringBuilder repl(StringBuilder buf, CharSequence str, CharSequence pat, CharSequence rep) {
final int lp=pat.length();
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=pat.charAt(j)) {
// No match => add current char, restart match
buf.append(str.charAt(i++));
continue find;
}
}
// Match => add replacement
if(rep!=null) buf.append(rep);
i+=lp;
}
return buf;
}
/**
* Replace a subsequence by an other in a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the CharSequence to replace in the source
* @param rep the CharSequence to replace matches with
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <A extends Appendable> A repl(A buf, CharSequence str, CharSequence pat, CharSequence rep) throws IOException {
final int lp=pat.length();
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=pat.charAt(j)) {
// No match => add current char, restart match
buf.append(str.charAt(i++));
continue find;
}
}
// Match => add replacement
if(rep!=null) buf.append(rep);
i+=lp;
}
return buf;
}
/**
* Replace a subsequence by an other in a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the subsequence to replace in the source
* @param rep the subsequence to replace matches with
*
* @return the original buffer, for chaining purposes
*/
public static StringBuilder repl(StringBuilder buf, CharSequence str, char[] pat, char[] rep) {
final int lp=pat.length;
final int lr=rep.length;
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=pat[j]) {
// No match => add current char, restart match
buf.append(str.charAt(i++));
continue find;
}
}
// Match => add replacement
if(rep!=null) for(int j=0;j<lr;j++) buf.append(rep[j]);
i+=lp;
}
return buf;
}
/**
* Replace a subsequence by an other in a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the subsequence to replace in the source
* @param rep the subsequence to replace matches with
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <A extends Appendable> A repl(A buf, CharSequence str, char[] pat, char[] rep) throws IOException {
final int lp=pat.length;
final int lr=rep.length;
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=pat[j]) {
// No match => add current char, restart match
buf.append(str.charAt(i++));
continue find;
}
}
// Match => add replacement
if(rep!=null) for(int j=0;j<lr;j++) buf.append(rep[j]);
i+=lp;
}
return buf;
}
/**
* Replace a subsequence by an other in a segment of a character array.
* <p/>
* Appends each character of the source segment to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source character array
* @param pos the position of the segment in the array
* @param len the length of the segment
* @param pat the subsequence to replace in the segment
* @param rep the subsequence to replace matches with
*
* @return the original buffer, for chaining purposes
*/
public static StringBuilder repl(StringBuilder buf, char[] str, int pos, int len, char[] pat, char[] rep) {
if(pos>=str.length) throw new ArrayIndexOutOfBoundsException(pos);
len+=pos;
if(len>str.length) throw new ArrayIndexOutOfBoundsException(pos);
final int lp=pat.length;
// Pattern finding loop
find:
for(;pos<len;) {
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(pos+j>=len||str[pos+j]!=pat[j]) {
// No match => add current char, restart match
buf.append(str[pos++]);
continue find;
}
}
// Match => add replacement
if(rep!=null) for(int j=0;j<rep.length;j++) buf.append(rep[j]);
pos+=lp;
}
return buf;
}
/**
* Replace a subsequence by an other in a segment of a character array.
* <p/>
* Appends each character of the source segment to the buffer,
* except for segments that match the pattern, for which the
* replacement is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source character array
* @param pos the position of the segment in the array
* @param len the length of the segment
* @param pat the subsequence to replace in the segment
* @param rep the subsequence to replace matches with
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <A extends Appendable> A repl(A buf, char[] str, int pos, int len, char[] pat, char[] rep) throws IOException {
if(pos>=str.length) throw new ArrayIndexOutOfBoundsException(pos);
len+=pos;
if(len>str.length) throw new ArrayIndexOutOfBoundsException(pos);
final int lp=pat.length;
// Pattern finding loop
find:
for(;pos<len;) {
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(pos+j>=len||str[pos+j]!=pat[j]) {
// No match => add current char, restart match
buf.append(str[pos++]);
continue find;
}
}
// Match => add replacement
if(rep!=null) for(int j=0;j<rep.length;j++) buf.append(rep[j]);
pos+=lp;
}
return buf;
}
/**
* Replace multiple subsequences in a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match one of the pattern, for which the
* sequence at the same position in the replacement array is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the array of CharSequence to replace in the source
* @param rep the array of CharSequence to replace corresponding matches with
*
* @return the original buffer, for chaining purposes
*/
public static StringBuilder repl(StringBuilder buf, CharSequence str, CharSequence[] pat, CharSequence[] rep) {
final int np=pat.length;
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
match:
for(int k=0;k<np;k++) {
CharSequence sp=pat[k];
final int lp=sp.length();
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=sp.charAt(j)) {
// No match for pattern => go to next pattern
continue match;
}
}
// Match => add replacement, skip pattern
if(rep!=null) buf.append(rep[k]);
i+=lp;
continue find;
}
// No match => add current char, restart match
buf.append(str.charAt(i++));
}
return buf;
}
/**
* Replace multiple subsequences in a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match one of the pattern, for which the
* sequence at the same position in the replacement array is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the array of CharSequence to replace in the source
* @param rep the array of CharSequence to replace corresponding matches with
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <A extends Appendable> A repl(A buf, CharSequence str, CharSequence[] pat, CharSequence[] rep) throws IOException {
final int np=pat.length;
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
match:
for(int k=0;k<np;k++) {
CharSequence sp=pat[k];
final int lp=sp.length();
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=sp.charAt(j)) {
// No match for pattern => go to next pattern
continue match;
}
}
// Match => add replacement, skip pattern
if(rep!=null) buf.append(rep[k]);
i+=lp;
continue find;
}
// No match => add current char, restart match
buf.append(str.charAt(i++));
}
return buf;
}
/**
* Replace multiple subsequences in a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match one of the pattern, for which the
* sequence at the same position in the replacement array is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the array of subsequences to replace in the source
* @param rep the array of subsequences to replace corresponding matches with
*
* @return the original buffer, for chaining purposes
*/
public static StringBuilder repl(StringBuilder buf, CharSequence str, char[][] pat, char[][] rep) {
final int np=pat.length;
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
match:
for(int k=0;k<np;k++) {
char[] sp=pat[k];
final int lp=sp.length;
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=sp[j]) {
// No match for pattern => go to next pattern
continue match;
}
}
// Match => add replacement (if any), skip pattern
if(rep!=null&&k<rep.length) {
char[] sr=rep[k];
final int lr=sr.length;
for(int j=0;j<lr;j++) buf.append(sr[j]);
}
i+=lp;
continue find;
}
// No match => add current char, restart match
buf.append(str.charAt(i++));
}
return buf;
}
/**
* Replace multiple subsequences in a CharSequence.
* <p/>
* Appends each character of the source sequence to the buffer,
* except for segments that match one of the pattern, for which the
* sequence at the same position in the replacement array is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source CharSequence
* @param pat the array of subsequences to replace in the source
* @param rep the array of subsequences to replace corresponding matches with
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <A extends Appendable> A repl(A buf, CharSequence str, char[][] pat, char[][] rep) throws IOException {
final int np=pat.length;
final int ls=str.length();
// Pattern finding loop
find:
for(int i=0;i<ls;) {
match:
for(int k=0;k<np;k++) {
char[] sp=pat[k];
final int lp=sp.length;
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(i+j>=ls||str.charAt(i+j)!=sp[j]) {
// No match for pattern => go to next pattern
continue match;
}
}
// Match => add replacement (if any), skip pattern
if(rep!=null&&k<rep.length) {
char[] sr=rep[k];
final int lr=sr.length;
for(int j=0;j<lr;j++) buf.append(sr[j]);
}
i+=lp;
continue find;
}
// No match => add current char, restart match
buf.append(str.charAt(i++));
}
return buf;
}
/**
* Replace multiple subsequences in a segment of a character array.
* <p/>
* Appends each character of the source segment to the buffer,
* except for segments that match one of the pattern, for which the
* sequence at the same position in the replacement array is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source character array
* @param pos the position of the segment in the array
* @param len the length of the segment
* @param pat the array of subsequences to replace in the source
* @param rep the array of subsequences to replace corresponding matches with
*
* @return the original buffer, for chaining purposes
*/
public static StringBuilder repl(StringBuilder buf, char[] str, int pos, int len, char[][] pat, char[][] rep) {
if(pos>=str.length) throw new ArrayIndexOutOfBoundsException(pos);
len+=pos;
if(len>str.length) throw new ArrayIndexOutOfBoundsException(pos);
final int np=pat.length;
// Pattern finding loop
find:
for(;pos<len;) {
match:
for(int k=0;k<np;k++) {
char[] sp=pat[k];
final int lp=sp.length;
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(pos+j>=len||str[pos+j]!=sp[j]) {
// No match for pattern => go to next pattern
continue match;
}
}
// Match => add replacement (if any), skip pattern
if(rep!=null&&k<rep.length) {
char[] sr=rep[k];
final int lr=sr.length;
for(int j=0;j<lr;j++) buf.append(sr[j]);
}
pos+=lp;
continue find;
}
// No match => add current char, restart match
buf.append(str[pos++]);
}
return buf;
}
/**
* Replace multiple subsequences in a segment of a character array.
* <p/>
* Appends each character of the source segment to the buffer,
* except for segments that match one of the pattern, for which the
* sequence at the same position in the replacement array is appended instead.
*
* @param buf the buffer to append the result to
* @param str the source character array
* @param pos the position of the segment in the array
* @param len the length of the segment
* @param pat the array of subsequences to replace in the source
* @param rep the array of subsequences to replace corresponding matches with
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <A extends Appendable> A repl(A buf, char[] str, int pos, int len, char[][] pat, char[][] rep) throws IOException {
if(pos>=str.length) throw new ArrayIndexOutOfBoundsException(pos);
len+=pos;
if(len>str.length) throw new ArrayIndexOutOfBoundsException(pos);
final int np=pat.length;
// Pattern finding loop
find:
for(;pos<len;) {
match:
for(int k=0;k<np;k++) {
char[] sp=pat[k];
final int lp=sp.length;
// Look for a local match starting at i
for(int j=0;j<lp;j++) {
if(pos+j>=len||str[pos+j]!=sp[j]) {
// No match for pattern => go to next pattern
continue match;
}
}
// Match => add replacement (if any), skip pattern
if(rep!=null&&k<rep.length) {
char[] sr=rep[k];
final int lr=sr.length;
for(int j=0;j<lr;j++) buf.append(sr[j]);
}
pos+=lp;
continue find;
}
// No match => add current char, restart match
buf.append(str[pos++]);
}
return buf;
}
public static String repl(String str, String pat, String rep) {
return repl(new StringBuilder(), str, pat, rep).toString();
}
public static String repl(String str, String[] pat, String[] rep) {
return repl(new StringBuilder(), str, pat, rep).toString();
}
/*********************************************************************************
** Field level manipulations
**/
/************************
** Single field extraction
**/
/**
* Cuts a segment of a char array at the first occurrence of a delimiter.
*
* @param s a char array containing the segment
* @param p the position in the array of the first character of the segment
* @param l the length of (number of characters in) the segment
* @param c the delimiter character
*
* @return a copy of the initial portion of the segment, ending at the first
* Occurrence of the delimiter, or a copy of the whole segment if the delimiter
* does not occur
*/
public static char[] chop(char[] s, int p, int l, char c) {
for(int i=0;i<l;i++) {
if(s[p+i]==c) {
char[] t=new char[i];
System.arraycopy(s, p, t, 0, i);
return t;
}
}
char[] t=new char[l];
System.arraycopy(s, p, t, 0, l);
return t;
}
/**
* Cuts a char array at the first occurrence of a delimiter.
*
* @param s a char array
* @param c the delimiter character
*
* @return a copy of the initial portion of the char array ending at the first
* Occurrence of the delimiter, or a copy of the whole array if the delimiter
* does not occur
*/
public static char[] chop(char[] s, char c) {
for(int i=0;i<s.length;i++) {
if(s[i]==c) {
char[] t=new char[i];
System.arraycopy(s, 0, t, 0, i);
return t;
}
}
char[] t=new char[s.length];
System.arraycopy(s, 0, t, 0, s.length);
return t;
}
/**
* Cuts a character sequence at the first occurrence of a delimiter.
*
* @param s a character sequence
* @param c the delimiter character
*
* @return the initial portion of the character sequence ending at the first
* Occurrence of the delimiter, or the whole character sequence if the delimiter
* does not occur
*/
public static CharSequence chop(CharSequence s, char c) {
for(int i=0;i<s.length();i++) {
if(s.charAt(i)==c) return s.subSequence(0, i);
}
return s;
}
/**
* Cuts a string at the first occurrence of a delimiter.
*
* @param s a string
* @param c the delimiter character
*
* @return the initial portion of the string ending at the first occurrence
* of the delimiter, or the whole string if the delimiter does not occur
*/
public static String chop(String s, char c) {
int p=s.indexOf(c);
if(p>=0) return s.substring(0, p);
return s;
}
/************************
** Field array extraction
**/
/**
* Splits a segment of a char array into fields, at occurrences of a delimiter.
*
* @param s a char array containing the segment
* @param p the position in the array of the first character of the segment
* @param l the length of (number of characters in) the segment
* @param c the delimiter character
* @param n the maximum number of fields to extract
*
* @return an array containing at most {@code n} elements, which are the portions of the
* segment between occurrences of the delimiter. If there are more than {@code n} of those,
* the last element of the array contains the remaining characters of the segment after the
* {@code n}-th occurrence of the delimiter.
*/
public static char[][] split(char[] s, final int p, final int l, char c, int n) {
if(n<0) return null;
if(p>=s.length) throw new ArrayIndexOutOfBoundsException(p);
if(p+l>s.length) throw new ArrayIndexOutOfBoundsException(p);
if(n==1) {
char[] x=new char[l];
System.arraycopy(s, p, x, 0, l);
return new char[][] { x };
}
int m=1;
for(int i=0;i<l;i++) if(s[p+i]==c) m++;
if(m<n) n=m;
char[][] b=new char[n][];
n--;
m=0;
int j=-1; // Start at -1 (a virtual first delimiter is before the first char)
for(int i=0;m<n&&i<l;i++)
if(s[p+i]==c) {
j++; // Skip last delimiter (or get to 0 from -1)
char[] x=new char[i-j];
System.arraycopy(s, p+j, x, 0, i-j);
b[m++]=x;
j=i;
}
if(j<l) {
j++; // Skip last delimiter (or get to 0 from -1)
char[] x=new char[l-j];
System.arraycopy(s, p+j, x, 0, l-j);
b[m]=x;
}
else b[m]=new char[0];
return b;
}
/**
* Splits a char array into fields, at occurrences of a delimiter.
*
* @param s a char array
* @param c the delimiter character
* @param n the maximum number of fields to extract
*
* @return an array containing at most {@code n} elements, which are the portions of the
* char array between occurrences of the delimiter. If there are more than {@code n} of those,
* the last element of the array contains the remaining characters of the array after the
* {@code n}-th occurrence of the delimiter.
*/
public static char[][] split(char[] s, char c, int n) {
if(n<0) return null;
final int l=s.length;
if(n==1) {
char[] x=new char[l];
System.arraycopy(s, 0, x, 0, l);
return new char[][] { x };
}
int m=1;
for(int i=0;i<l;i++) if(s[i]==c) m++;
if(m<n) n=m;
char[][] b=new char[n][];
n--;
m=0;
int j=-1; // Start at -1 (a virtual first delimiter is before the first char)
for(int i=0;m<n&&i<l;i++)
if(s[i]==c) {
j++; // Skip last delimiter (or get to 0 from -1)
char[] x=new char[i-j];
System.arraycopy(s, j, x, 0, i-j);
b[m++]=x;
j=i;
}
if(j<l) {
j++; // Skip last delimiter (or get to 0 from -1)
char[] x=new char[l-j];
System.arraycopy(s, j, x, 0, l-j);
b[m]=x;
}
else b[m]=new char[0];
return b;
}
/**
* Splits a character sequence into fields, at occurrences of a delimiter.
*
* @param s a character sequence
* @param c the delimiter character
* @param n the maximum number of fields to extract
*
* @return an array containing at most {@code n} elements, which are the portions of the
* argument character sequence between occurrences of the delimiter. If there are more than
* {@code n} of those, the last element of the array contains the remaining of the character
* sequence after the {@code n}-th occurrence of the delimiter.
*/
public static CharSequence[] split(CharSequence s, char c, int n) {
if(n<0) return null;
if(n==1) return new CharSequence[] { s };
int m=1;
int l=s.length();
for(int i=0;i<l;i++) if(s.charAt(i)==c) m++;
if(m<n) n=m;
CharSequence[] b=new CharSequence[n];
n--;
m=0;
int j=-1;
for(int i=0;m<n&&i<l;i++)
if(s.charAt(i)==c) {
b[m++]=s.subSequence(j+1, i);
j=i;
}
if(j<l) b[m]=s.subSequence(j+1, l);
else b[m]="";
return b;
}
/**
* Splits a string into fields, at occurrences of a delimiter.
*
* @param s a string
* @param c the delimiter character
* @param n the maximum number of fields to extract
*
* @return an array containing at most {@code n} elements, which are the portions of the
* argument string between occurrences of the delimiter. If there are more than {@code n}
* of those, the last element of the array contains the remaining of the string after
* the {@code n}-th occurrence of the delimiter.
*/
public static String[] split(String s, char c, int n) {
if(n<0) return null;
if(n==1) return new String[] { s };
int m=1;
for(int i=0;i<s.length();i++) if(s.charAt(i)==c) m++;
if(m<n) n=m;
String[] b=new String[n];
n--;
m=0;
int j=-1;
for(int i=0;m<n&&i<s.length();i++)
if(s.charAt(i)==c) {
b[m++]=s.substring(j+1, i);
j=i;
}
if(j<s.length()) b[m]=s.substring(j+1);
else b[m]="";
return b;
}
/************************
** Field array merging
**/
/**
* Appends as strings the elements of an array, separating them with a given string.
*
* @param buf the buffer to append the composed string to
* @param ldl the left delimiter for non-null elements
* @param rdl the right delimiter for non-null elements
* @param nil the representation to use for {@literal null} elements
* @param sep the separator between elements
* @param array the array to concatenate
*
* @param <T> the element type
*
* @return a concatenation of the elements of the array, as string, and the separator
*/
public static <T> StringBuilder join(StringBuilder buf, String ldl, String rdl, String nil, String sep, T... array) {
if(sep==null) for(int i=0;i<array.length;i++) {
T t=array[i];
if(t!=null) {
if(ldl!=null) buf.append(ldl);
buf.append(t.toString());
if(rdl!=null) buf.append(rdl);
}
else if(nil!=null) buf.append(nil);
}
else for(int i=0;i<array.length;i++) {
if(i>0) buf.append(sep);
T t=array[i];
if(t!=null) {
if(ldl!=null) buf.append(ldl);
buf.append(t.toString());
if(rdl!=null) buf.append(rdl);
}
else if(nil!=null) buf.append(nil);
}
return buf;
}
/**
* Appends as strings the elements of an array, separating them with a given string.
*
* @param buf the buffer to append the composed string to
* @param ldl the left delimiter for non-null elements
* @param rdl the right delimiter for non-null elements
* @param nil the representation to use for {@literal null} elements
* @param sep the separator between elements
* @param array the array to concatenate
*
* @param <T> the element type
* @param <A> the buffer type
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <T,A extends Appendable> A join(A buf, String ldl, String rdl, String nil, String sep, T... array) throws IOException {
if(sep==null) for(int i=0;i<array.length;i++) {
T t=array[i];
if(t!=null) {
if(ldl!=null) buf.append(ldl);
buf.append(t.toString());
if(rdl!=null) buf.append(rdl);
}
else if(nil!=null) buf.append(nil);
}
else for(int i=0;i<array.length;i++) {
if(i>0) buf.append(sep);
T t=array[i];
if(t!=null) {
if(ldl!=null) buf.append(ldl);
buf.append(t.toString());
if(rdl!=null) buf.append(rdl);
}
else if(nil!=null) buf.append(nil);
}
return buf;
}
/**
* Appends as strings the elements of an array, separating them with a given string.
*
* @param buf the buffer to append the composed string to
* @param sep the separator between elements
* @param array the array to concatenate
*
* @param <T> the element type
*
* @return a concatenation of the elements of the array, as string, and the separator
*/
public static <T> StringBuilder join(StringBuilder buf, String sep, T... array) {
if(sep==null) for(int i=0;i<array.length;i++) {
T t=array[i];
if(t!=null) buf.append(t.toString());
}
else for(int i=0;i<array.length;i++) {
if(i>0) buf.append(sep);
T t=array[i];
if(t!=null) buf.append(t.toString());
}
return buf;
}
/**
* Appends as strings the elements of an array, separating them with a given string.
*
* @param buf the buffer to append the composed string to
* @param sep the separator between elements
* @param array the array to concatenate
*
* @param <T> the element type
* @param <A> the buffer type
*
* @return the original buffer, for chaining purposes
*
* @throws IOException if the output buffer raises this exception on {@code append()}
*/
public static <T,A extends Appendable> A join(A buf, String sep, T... array) throws IOException {
if(sep==null) for(int i=0;i<array.length;i++) {
T t=array[i];
if(t!=null) buf.append(t.toString());
}
else for(int i=0;i<array.length;i++) {
if(i>0) buf.append(sep);
T t=array[i];
if(t!=null) buf.append(t.toString());
}
return buf;
}
/**
* Appends as strings the elements of an array, separating them with a given string.
*
* @param ldl the left delimiter for non-null elements
* @param rdl the right delimiter for non-null elements
* @param nil the representation to use for {@literal null} elements
* @param sep the separator between elements
* @param array the array to concatenate
*
* @param <T> the element type
*
* @return a human-readable string exposing the contents of the array
*/
public static <T> String join(String ldl, String rdl, String nil, String sep, T... array) {
return join(new StringBuilder(), ldl, rdl, nil, sep, array).toString();
}
/**
* Appends as strings the elements of an array, separating them with a given string.
*
* @param sep the separator between elements
* @param array the array to concatenate
*
* @param <T> the element type
*
* @return a human-readable string exposing the contents of the array
*/
public static <T> String join(String sep, T... array) {
return join(new StringBuilder(), sep, array).toString();
}
/************************
** Character sequence formatting
**/
/**
* All possible lower-case chars for representing a number as a String
*/
private final static char[] digits={
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
};
/**
* All possible upper-case chars for representing a number as a String
*/
private final static char[] DIGITS={
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z',
};
/**
* Formats a character sequence and a variadic array of arguments into a buffer.
* <p/>
* All characters from the format are added to the buffer, except those within an escape sequence.
* Escape sequences start with a '%' character, can specify several optional parameters, and end
* with a format type character. Each escape sequence consumes one argument from the array, and
* transforms that argument into a format item, appended to the buffer, according to the type and
* parameters of the escape sequence.
* <p/>
* Accepted format types are:
* <ul>
* <li/> <b>'%'</b>: outputs a literal '%' character, <em>without consuming an argument</em>
* <li/> <b>'c'</b>: formats the argument (which must be of type {@link java.lang.Character}) as a single character
* <li/> <b>'s'</b>: formats the argument (which must be of type {@link java.lang.CharSequence}) as a character sequence
* <li/> <b>'S'</b>: formats the argument (which must be {@literal null}, or of type {@link java.lang.CharSequence}) as a character sequence, outputting {@code "null"} if it is {@literal null}
* <li/> <b>'d'</b>: formats the argument (which must be of type {@link java.lang.Number}) as a signed decimal integer
* <li/> <b>'u'</b>: formats the argument (which must be of type {@link java.lang.Number}) as an unsigned decimal integer
* <li/> <b>'x'</b>: formats the argument (which must be of type {@link java.lang.Number}) as an hexadecimal integer, using lower case letters
* <li/> <b>'X'</b>: formats the argument (which must be of type {@link java.lang.Number}) as an hexadecimal integer, using upper case letters
* </ul>
* All other format types result in the argument being converted to a String using {@link java.lang.Object#toString()}, and the result being
* formatted as though an 'S' format had been specified.
* <p/>
* Parameters in order, any of the optional following:
* <ul>
* <li/> <b>'<'</b> or <b>'>'</b>: an orientation specifier, defining the alignment direction of the formatting: respectively left, or right (default: right)
* <li/> <b>'|'</b>: a flag indicating that if the formatting results in more characters than the specified size, it should be truncated (default: none)
* <li/> <b>'+'</b> or <b>'-'</b>: specifies that signed formatting of a positive number should respectively include or omit a leading '+' character (default: omit)
* <li/> a <b>decimal integer</b> indicating the size (number of characters) of the formatted result
* <li/> <b>'.'</b> (a dot) followed by a <b>decimal integer</b> indicating:
* <ul>
* <li/> for unsigned decimal integers: the radix (default: 10),
* <li/> for hexadecimal integers: the shift (the number of bits that each digit represents, i.e. the base-2 logarithm of the radix)
* </ul>
* </ul>
* <p/>
*
* @param buf the output buffer
* @param fmt the format defining how the arguments should be written to the buffer
* @param var the variadic array of arguments
*
* @throws IOException if appending to the buffer produced an I/O error
*/
public static <A extends Appendable> A format(A buf, CharSequence fmt, Object... var) throws IOException {
int a=0;
final int n=fmt.length();
char[] nbf=null;
// Iterate on single characters / escapes
for(int pos=0;pos<n;pos++) {
char chr=fmt.charAt(pos);
// If not escape, simply add char
if(chr!='%') {
buf.append(chr);
continue;
}
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
boolean sign=false; // Whether a plus sign should be appended to positive numeric items
boolean left=false; // Whether item should be aligned left instead of right
boolean trnc=false; // Whether oversized items should be truncated
if(chr=='<') {
left=true;
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
else if(chr=='>') {
left=false;
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
if(chr=='|') {
trnc=true;
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
if(chr=='+') {
sign=true;
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
else if(chr=='-') {
sign=false;
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
char pad='\0';
// Look for a pad char -> 'c'
if(((chr<='0')||(chr>'9'))
&&((chr<'a')||(chr>'z'))
&&((chr<'A')||(chr>'Z'))
&&(chr!='%')) {
pad=chr;
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
// Look for display width
int dsp=-1;
if(chr=='*') {
if(a>=var.length)
throw new IllegalArgumentException("Too few arguments for format * in '"+fmt+"' at char "+pos+", arg "+a);
Object o=var[a++];
if(!(o instanceof Number))
throw new IllegalArgumentException("Invalid argument for format * in '"+fmt+"' at char "+pos+", arg "+a);
dsp=((Number) o).intValue();
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
else if((chr>'0')&&(chr<='9')) {
dsp=0;
while((chr>='0')&&(chr<='9')) {
dsp*=10;
dsp+=chr-'0';
if(++pos>=n)
throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
}
// Look for display precision / radix / shift
int pre=0;
if(chr=='.') {
if(++pos>=n) throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
if(chr=='*') {
if(a>=var.length)
throw new IllegalArgumentException("Too few arguments for format * in '"+fmt+"' at char "+pos+", arg "+a);
Object o=var[a++];
if(!(o instanceof Number))
throw new IllegalArgumentException("Invalid argument for format * in '"+fmt+"' at char "+pos+", arg "+a);
dsp=((Number) o).intValue();
if(++pos>=n)
throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
else if((chr>'0')&&(chr<='9')) {
while((chr>='0')&&(chr<='9')) {
pre*=10;
pre+=chr-'0';
if(++pos>=n)
throw new IllegalArgumentException("Invalid format sequence in '"+fmt+"' at char "+pos);
chr=fmt.charAt(pos);
}
}
}
// Switch on format char
switch(chr) {
case '%': { // Literal %
buf.append('%');
}
break;
case 'c': { // Char format
if(a>=var.length)
throw new IllegalArgumentException("Too few arguments for format %c in '"+fmt+"' at char "+pos+", arg "+a);
Object o=var[a++];
// Ignore padding and orientation
if(o==null)
throw new IllegalArgumentException("Null argument for format %c in '"+fmt+"' at char "+pos+", arg "+a);
if(o instanceof Character) buf.append((Character) o);
else if(o instanceof Integer) buf.append((char)((Integer) o).intValue());
else throw new IllegalArgumentException("Invalid argument for format %c in '"+fmt+"' at char "+pos+", arg "+a);
}
break;
case 's':
case 'S': { // String format
if(a>=var.length)
throw new IllegalArgumentException("Too few arguments for format %s in '"+fmt+"' at char "+pos+", arg "+a);
Object o=var[a++];
if(o==null) {
if(chr!='S')
throw new IllegalArgumentException("Null argument for format %s in '"+fmt+"' at char "+pos+", arg "+a);
// else o="null";
}
else if(!(o instanceof CharSequence))
throw new IllegalArgumentException("Invalid argument for format %s in '"+fmt+"' at char "+pos+", arg "+a);
CharSequence s;
if(chr=='S') s = o==null?"null":("\""+o+"\"");
else s = (CharSequence) o;
// Pad with ' ' by default
if(pad=='\0') pad=' ';
if(dsp<0) {
buf.append(s);
}
else if(dsp>s.length()) {
if(left) {
dsp-=s.length();
buf.append(s);
while(dsp>0) {
buf.append(pad);
dsp--;
}
}
else {
dsp-=s.length();
while(dsp>0) {
buf.append(pad);
dsp--;
}
buf.append(s);
}
}
else if(trnc) {
if(left) buf.append(s, 0, dsp);
else buf.append(s, s.length()-dsp, s.length());
}
else buf.append(s);
}
break;
case 'd': { // Decimal signed integer
if(a>=var.length)
throw new IllegalArgumentException("Too few arguments for format %d in '"+fmt+"' at char "+pos+", arg "+a);
Object o=var[a++];
if(!(o instanceof Number))
throw new IllegalArgumentException("Invalid argument for format %d in '"+fmt+"' at char "+pos+", arg "+a);
long v=((Number) o).longValue();
long u; // Unsigned value
if(v<0) u=-v;
else u=v;
// Pad with ' ' by default
if(pad=='\0') pad=' ';
// Base is 10 by default
if(pre==0) pre=10;
if(nbf==null) nbf=new char[65];
int j=0;
while(j<65&&u!=0) {
nbf[j++]=(char) (u%pre+'0');
u/=pre;
}
if(dsp<0) {
// Append digits
while(j>0) buf.append(nbf[--j]);
}
else if(pad=='0') { // '0' implies right align
// Append sign
if(v<0) {
buf.append('-');
dsp--;
}
else if(v>0&&sign) {
buf.append('+');
dsp--;
}
// Append padding
while(dsp>j) {
buf.append(pad);
dsp--;
}
// Append digits
while(j>0) buf.append(nbf[--j]);
}
else if(left) {
// Append sign
if(v<0) {
buf.append('-');
dsp--;
}
else if(v>0&&sign) {
buf.append('+');
dsp--;
}
// Append digits
dsp-=j;
while(j>0) buf.append(nbf[--j]);
// Append padding
while(dsp>0) {
buf.append(pad);
dsp--;
}
}
else {
// Reserve space for sign
if(v<0) { dsp--; }
else if(v>0&&sign) { dsp--; }
// Append padding
while(dsp>j) {
buf.append(pad);
dsp--;
}
// Append sign
if(v<0) { buf.append('-'); }
else if(v>0&&sign) { buf.append('+'); }
// Append digits
while(j>0) buf.append(nbf[--j]);
}
}
break;
case 'u': { // Decimal unsigned integer
if(a>=var.length)
throw new IllegalArgumentException("Too few arguments for format %u in '"+fmt+"' at char "+pos+", arg "+a);
Object o=var[a++];
if(!(o instanceof Number))
throw new IllegalArgumentException("Invalid argument for format %u in '"+fmt+"' at char "+pos+", arg "+a);
long u=((Number) o).longValue();
if(u<0) u=-u;
// Pad with ' ' by default
if(pad=='\0') pad=' ';
// Base is 10 by default
if(pre==0) pre=10;
if(nbf==null) nbf=new char[65];
int j=0;
while(j<65&&u!=0) {
nbf[j++]=(char) (u%pre+'0');
u/=pre;
}
if(dsp<0) {
// Append digits
while(j>0) buf.append(nbf[--j]);
}
else if(left) {
// Append digits
dsp-=j;
while(j>0) buf.append(nbf[--j]);
// Append padding
while(dsp>0) {
buf.append(pad);
dsp--;
}
}
else {
// Append padding
while(dsp>j) {
buf.append(pad);
dsp--;
}
// Append digits
while(j>0) buf.append(nbf[--j]);
}
}
break;
case 'x':
case 'X': { // Hexadecimal unsigned integer
if(a>=var.length)
throw new IllegalArgumentException("Too few arguments for format %x in '"+fmt+"' at char "+pos+", arg "+a);
Object o=var[a++];
if(!(o instanceof Number))
throw new IllegalArgumentException("Invalid argument for format %x in '"+fmt+"' at char "+pos+", arg "+a);
long u=((Number) o).longValue();
if(u<0) u=-u;
// Pad with ' ' by default
if(pad=='\0') pad=' ';
// Shift is 4 by default
if(pre==0) pre=4;
if(pre>=6) pre=6;
if(nbf==null) nbf=new char[65];
int j=0;
int msk=(1<<pre)-1;
if(chr=='X') while(j<65&&u!=0) {
nbf[j++]=DIGITS[(int) (u&msk)];
u>>>=pre;
}
else while(j<65&&u!=0) {
nbf[j++]=digits[(int) (u&msk)];
u>>>=pre;
}
if(dsp<0) {
// Append digits
while(j>0) buf.append(nbf[--j]);
}
else if(left) {
// Append digits
while(j>0&&dsp>0) {
buf.append(nbf[--j]);
dsp--;
}
// If not truncating, write overflowing lower order digits
if(!trnc) while(j>0) buf.append(nbf[--j]);
// Append padding
while(dsp>0) {
buf.append(pad);
dsp--;
}
}
else {
// Append padding
while(dsp>j) {
buf.append(pad);
dsp--;
}
// If truncating and overflow, keep only lower order digits
if(trnc&&j>dsp) j=dsp;
// Append digits
while(j>0) buf.append(nbf[--j]);
}
}
break;
default: {
if(a>=var.length)
throw new IllegalArgumentException("Too few arguments for format %s in '"+fmt+"' at char "+pos+", arg "+a);
Object o=var[a++];
String s=o==null ? "null" : o.toString();
// Pad with ' ' by default
if(pad=='\0') pad=' ';
if(dsp<0) {
buf.append(s);
}
else if(dsp>s.length()) {
if(left) {
dsp-=s.length();
buf.append(s);
while(dsp>0) {
buf.append(pad);
dsp--;
}
}
else {
dsp-=s.length();
while(dsp>0) {
buf.append(pad);
dsp--;
}
buf.append(s);
}
}
else if(trnc) {
if(left) buf.append(s, 0, dsp);
else buf.append(s, s.length()-dsp, s.length());
}
else buf.append(s);
}
} // End format character switch
} // End item loop
return buf;
}
/**
* Formats character sequence and arguments into a buffer.
* <p/>
*
* @param buf the output buffer
* @param fmt the format defining how the arguments should be written to the buffer
* @param var the variadic array of arguments
*
* @see {@link #format(Appendable, CharSequence, Object...)} for a complete description of syntax of the format sequence
*/
public static StringBuilder format(StringBuilder buf, CharSequence fmt, Object... var) {
try {
format((Appendable) buf, fmt, var);
}
catch(IOException e) {
/* That exception is never thrown by StringBuilder */
}
return buf;
}
/**
* Formats character sequence and arguments into a string.
* <p/>
*
* @param fmt the format defining how the arguments should be written to the buffer
* @param var the variadic array of arguments
*
* @return the formatted string
*
* @see {@link #format(Appendable, CharSequence, Object...)} for a complete description of syntax of the format sequence
*/
public static String format(CharSequence fmt, Object... var) {
StringBuilder buf=new StringBuilder();
try {
format((Appendable) buf, fmt, var);
}
catch(IOException e) {
/* That exception is never thrown by StringBuilder */
}
return buf.toString();
}
}