package squidpony;
import squidpony.squidmath.CrossHash;
import squidpony.squidmath.NumberTools;
import java.util.Collection;
import java.util.Iterator;
/**
* Various utility functions for dealing with Strings, CharSequences, and char[]s; mostly converting numbers.
* Created by Tommy Ettinger on 3/21/2016.
*/
public class StringKit {
/**
* Searches text for the exact contents of the char array search; returns true if text contains search.
* @param text a CharSequence, such as a String or StringBuilder, that might contain search
* @param search a char array to try to find in text
* @return true if search was found
*/
public static boolean contains(CharSequence text, char[] search) {
return !(text == null || text.length() == 0 || search == null || search.length <= 0)
&& containsPart(text, search, "", "") == search.length;
}
public static int containsPart(CharSequence text, char[] search)
{
return containsPart(text, search, "", "");
}
public static int containsPart(CharSequence text, char[] search, CharSequence prefix, CharSequence suffix)
{
if(prefix == null) prefix = "";
if(suffix == null) suffix = "";
int bl = prefix.length(), el = suffix.length();
if(text == null || text.length() == 0 || search == null || (search.length + bl + el <= 0))
return 0;
int sl = bl + search.length + el, tl = text.length() - sl, f = 0, sl2 = sl - el;
char s = (bl <= 0) ? (search.length <= 0 ? suffix.charAt(0) : search[0]) : prefix.charAt(0);
PRIMARY:
for (int i = 0; i <= tl; i++) {
if(text.charAt(i) == s)
{
for (int j = i+1, x = 1; x < sl; j++, x++) {
if(x < bl)
{
if (text.charAt(j) != prefix.charAt(x)) {
f = Math.max(f, x);
continue PRIMARY;
}
}
else if(x < sl2)
{
if (text.charAt(j) != search[x-bl]) {
f = Math.max(f, x);
continue PRIMARY;
}
}
else
{
if (text.charAt(j) != suffix.charAt(x - sl2)) {
f = Math.max(f, x);
continue PRIMARY;
}
}
}
return sl;
}
}
return f;
}
public static String join(CharSequence delimiter, CharSequence... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, Collection<? extends CharSequence> elements) {
if (elements == null || elements.isEmpty()) return "";
StringBuilder sb = new StringBuilder(64);
Iterator<? extends CharSequence> it = elements.iterator();
sb.append(it.next());
while(it.hasNext()) {
sb.append(delimiter).append(it.next());
}
return sb.toString();
}
public static String joinArrays(CharSequence delimiter, char[]... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, long... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, double... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, int... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, float... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, short... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, char... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, byte... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
public static String join(CharSequence delimiter, boolean... elements) {
if (elements == null || elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
sb.append(elements[0]);
for (int i = 1; i < elements.length; i++) {
sb.append(delimiter).append(elements[i]);
}
return sb.toString();
}
/**
* Joins the boolean array {@code elements} without delimiters into a String, using "1" for true and "0" for false.
* @param elements
* @return
*/
public static String joinAlt(boolean... elements) {
if (elements == null) return "N";
if(elements.length == 0) return "";
StringBuilder sb = new StringBuilder(64);
for (int i = 0; i < elements.length; i++) {
sb.append(elements[i] ? '1' : '0');
}
return sb.toString();
}
/**
* Scans repeatedly in {@code source} for the String {@code search}, not scanning the same char twice except as part
* of a larger String, and returns the number of instances of search that were found, or 0 if source is null or if
* search is null or empty.
* @param source a String to look through
* @param search a String to look for
* @return the number of times search was found in source
*/
public static int count(final String source, final String search)
{
if(source == null || search == null || source.isEmpty() || search.isEmpty())
return 0;
int amount = 0, idx = -1;
while ((idx = source.indexOf(search, idx+1)) >= 0)
++amount;
return amount;
}
/**
* Scans repeatedly in {@code source} for the codepoint {@code search} (which is usually a char literal), not
* scanning the same section twice, and returns the number of instances of search that were found, or 0 if source is
* null.
* @param source a String to look through
* @param search a codepoint or char to look for
* @return the number of times search was found in source
*/
public static int count(final String source, final int search)
{
if(source == null || source.isEmpty())
return 0;
int amount = 0, idx = -1;
while ((idx = source.indexOf(search, idx+1)) >= 0)
++amount;
return amount;
}
/**
* Scans repeatedly in {@code source} (only using the area from startIndex, inclusive, to endIndex, exclusive) for
* the String {@code search}, not scanning the same char twice except as part of a larger String, and returns the
* number of instances of search that were found, or 0 if source or search is null or if the searched area is empty.
* If endIndex is negative, this will search from startIndex until the end of the source.
* @param source a String to look through
* @param search a String to look for
* @param startIndex the first index to search through, inclusive
* @param endIndex the last index to search through, exclusive; if negative this will search the rest of source
* @return the number of times search was found in source
*/
public static int count(final String source, final String search, final int startIndex, int endIndex)
{
if(endIndex < 0) endIndex = 0x7fffffff;
if(source == null || search == null || source.isEmpty() || search.isEmpty()
|| startIndex < 0 || startIndex >= endIndex)
return 0;
int amount = 0, idx = startIndex-1;
while ((idx = source.indexOf(search, idx+1)) >= 0 && idx < endIndex)
++amount;
return amount;
}
/**
* Scans repeatedly in {@code source} (only using the area from startIndex, inclusive, to endIndex, exclusive) for
* the codepoint {@code search} (which is usually a char literal), not scanning the same section twice, and returns
* the number of instances of search that were found, or 0 if source is null or if the searched area is empty.
* If endIndex is negative, this will search from startIndex until the end of the source.
* @param source a String to look through
* @param search a codepoint or char to look for
* @param startIndex the first index to search through, inclusive
* @param endIndex the last index to search through, exclusive; if negative this will search the rest of source
* @return the number of times search was found in source
*/
public static int count(final String source, final int search, final int startIndex, int endIndex)
{
if(endIndex < 0) endIndex = 0x7fffffff;
if(source == null || source.isEmpty() || startIndex < 0 || startIndex >= endIndex)
return 0;
int amount = 0, idx = startIndex-1;
while ((idx = source.indexOf(search, idx+1)) >= 0 && idx < endIndex)
++amount;
return amount;
}
/**
* Like {@link String#substring(int, int)} but returns "" instead of throwing any sort of Exception.
* @param source the String to get a substring from
* @param beginIndex the first index, inclusive; will be treated as 0 if negative
* @param endIndex the index after the last character (i.e. length, so exclusive); if negative this returns ""
* @return the substring of source between beginIndex and endIndex, or "" if any parameters are null/invalid
*/
public static String safeSubstring(String source, int beginIndex, int endIndex)
{
if(endIndex < 0 || source == null || source.isEmpty()) return "";
if(beginIndex < 0) beginIndex = 0;
if(endIndex > source.length()) endIndex = source.length();
if(beginIndex > endIndex) return "";
return source.substring(beginIndex, endIndex);
}
/**
* Like {@link String#split(String)} but doesn't use any regex for splitting (delimiter is a literal String).
* @param source the String to get split-up substrings from
* @param delimiter the literal String to split on (not a regex); will not be included in the returned String array
* @return a String array consisting of at least one String (all of Source if nothing was split)
*/
public static String[] split(String source, String delimiter) {
int amount = count(source, delimiter);
if (amount <= 0) return new String[]{source};
String[] splat = new String[amount+1];
int dl = delimiter.length(), idx = -dl, idx2;
for (int i = 0; i < amount; i++) {
splat[i] = safeSubstring(source, idx+dl, idx = source.indexOf(delimiter, idx+dl));
}
if((idx2 = source.indexOf(delimiter, idx+dl)) < 0)
{
splat[amount] = safeSubstring(source, idx+dl, source.length());
}
else
{
splat[amount] = safeSubstring(source, idx+dl, idx2);
}
return splat;
}
public static final String mask64 = "0000000000000000000000000000000000000000000000000000000000000000",
mask32 = "00000000000000000000000000000000",
mask16 = "0000000000000000",
mask8 = "00000000";
private static final char[] keyBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(),
valBase64 = new char[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};
public static String hex(long number) {
String h = Long.toHexString(number);
return mask16.substring(0, 16 - h.length()) + h;
}
public static String hex(int number) {
String h = Integer.toHexString(number);
return mask8.substring(0, 8 - h.length()) + h;
}
public static String hex(short number) {
String h = Integer.toHexString(number & 0xffff);
return mask8.substring(4, 8 - h.length()) + h;
}
public static String hex(char number) {
String h = Integer.toHexString(number & 0xffff);
return mask8.substring(4, 8 - h.length()) + h;
}
public static String hex(byte number) {
String h = Integer.toHexString(number & 0xff);
return mask8.substring(6, 8 - h.length()) + h;
}
public static String hex(long[] numbers) {
int len;
if (numbers == null || (len = numbers.length) <= 0) return "";
StringBuilder sb = new StringBuilder(numbers.length << 4);
for (int i = 0; i < len; i++) {
sb.append(hex(numbers[i]));
}
return sb.toString();
}
public static String hex(int[] numbers) {
int len;
if (numbers == null || (len = numbers.length) <= 0) return "";
StringBuilder sb = new StringBuilder(numbers.length << 3);
for (int i = 0; i < len; i++) {
sb.append(hex(numbers[i]));
}
return sb.toString();
}
public static String hex(short[] numbers) {
int len;
if (numbers == null || (len = numbers.length) <= 0) return "";
StringBuilder sb = new StringBuilder(numbers.length << 2);
for (int i = 0; i < len; i++) {
sb.append(hex(numbers[i]));
}
return sb.toString();
}
public static String hex(char[] numbers) {
int len;
if (numbers == null || (len = numbers.length) <= 0) return "";
StringBuilder sb = new StringBuilder(numbers.length << 2);
for (int i = 0; i < len; i++) {
sb.append(hex(numbers[i]));
}
return sb.toString();
}
public static String hex(byte[] numbers) {
int len;
if (numbers == null || (len = numbers.length) <= 0) return "";
StringBuilder sb = new StringBuilder(numbers.length << 1);
for (int i = 0; i < len; i++) {
sb.append(hex(numbers[i]));
}
return sb.toString();
}
public static String bin(long number) {
String h = Long.toBinaryString(number);
return mask64.substring(0, 64 - h.length()) + h;
}
public static String bin(int number) {
String h = Integer.toBinaryString(number);
return mask32.substring(0, 32 - h.length()) + h;
}
public static String bin(short number) {
String h = Integer.toBinaryString(number & 0xffff);
return mask16.substring(0, 16 - h.length()) + h;
}
public static String bin(char number) {
String h = Integer.toBinaryString(number & 0xffff);
return mask16.substring(0, 16 - h.length()) + h;
}
public static String bin(byte number) {
String h = Integer.toBinaryString(number & 0xff);
return mask8.substring(0, 8 - h.length()) + h;
}
private static final int[] hexCodes = new int[]
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,10,11,12,13,14,15};
/**
* Reads in a CharSequence containing only hex digits (only 0-9, a-f, and A-F) with an optional sign at the start
* and returns the long they represent, reading at most 16 characters (17 if there is a sign) and returning the
* result if valid, or 0 if nothing could be read. The leading sign can be '+' or '-' if present. This can also
* represent negative numbers as they are printed by such methods as String.format given a %x in the formatting
* string, or this class' {@link #hex(long)} method; that is, if the first char of a 16-char (or longer)
* CharSequence is a hex digit 8 or higher, then the whole number represents a negative number, using two's
* complement and so on. This means "FFFFFFFFFFFFFFFF" would return the long -1 when passed to this, though you
* could also simply use "-1 ".
* <br>
* Should be fairly close to Java 8's Long.parseUnsignedLong method, which is an odd omission from earlier JDKs.
* This doesn't throw on invalid input, though, instead returning 0 if the first char is not a hex digit, or
* stopping the parse process early if a non-hex-digit char is read before the end of cs is reached. If the parse is
* stopped early, this behaves as you would expect for a number with less digits, and simply doesn't fill the larger
* places.
* @param cs a CharSequence, such as a String, containing only hex digits with an optional sign (no 0x at the start)
* @return the long that cs represents
*/
public static long longFromHex(final CharSequence cs) {
return longFromHex(cs, 0, cs.length());
}
/**
* Reads in a CharSequence containing only hex digits (only 0-9, a-f, and A-F) with an optional sign at the start
* and returns the long they represent, reading at most 16 characters (17 if there is a sign) and returning the
* result if valid, or 0 if nothing could be read. The leading sign can be '+' or '-' if present. This can also
* represent negative numbers as they are printed by such methods as String.format given a %x in the formatting
* string, or this class' {@link #hex(long)} method; that is, if the first char of a 16-char (or longer)
* CharSequence is a hex digit 8 or higher, then the whole number represents a negative number, using two's
* complement and so on. This means "FFFFFFFFFFFFFFFF" would return the long -1 when passed to this, though you
* could also simply use "-1 ". If you use both '-' at the start and have the most significant digit as 8 or higher,
* such as with "-FFFFFFFFFFFFFFFF", then both indicate a negative number, but the digits will be processed first
* (producing -1) and then the whole thing will be multiplied by -1 to flip the sign again (returning 1).
* <br>
* Should be fairly close to Java 8's Long.parseUnsignedLong method, which is an odd omission from earlier JDKs.
* This doesn't throw on invalid input, though, instead returning 0 if the first char is not a hex digit, or
* stopping the parse process early if a non-hex-digit char is read before end is reached. If the parse is stopped
* early, this behaves as you would expect for a number with less digits, and simply doesn't fill the larger places.
* @param cs a CharSequence, such as a String, containing only hex digits with an optional sign (no 0x at the start)
* @param start the (inclusive) first character position in cs to read
* @param end the (exclusive) last character position in cs to read (this stops after 16 characters if end is too large)
* @return the long that cs represents
*/
public static long longFromHex(final CharSequence cs, final int start, int end) {
int len, h, lim = 16;
if (cs == null || start < 0 || end <= 0 || end - start <= 0
|| (len = cs.length()) - start <= 0 || end > len)
return 0;
char c = cs.charAt(start);
if (c == '-') {
len = -1;
h = 0;
++end;
lim = 17;
} else if (c == '+') {
len = 1;
h = 0;
++end;
lim = 17;
} else if (c > 102 || (h = hexCodes[c]) < 0)
return 0;
else {
len = 1;
}
long data = h;
for (int i = start; i < end && i < start + lim; i++) {
if ((c = cs.charAt(i)) > 102 || (h = hexCodes[c]) < 0)
return data * len;
data <<= 4;
data |= h;
}
return data * len;
}
/**
* Reads in a CharSequence containing only hex digits (only 0-9, a-f, and A-F) with an optional sign at the start
* and returns the int they represent, reading at most 8 characters (9 if there is a sign) and returning the result
* if valid, or 0 if nothing could be read. The leading sign can be '+' or '-' if present. This can also represent
* negative numbers as they are printed by such methods as String.format given a %x in the formatting string, or
* this class' {@link #hex(int)} method; that is, if the first digit of an 8-char (or longer) CharSequence is a hex
* digit 8 or higher, then the whole number represents a negative number, using two's complement and so on. This
* means "FFFFFFFF" would return the int -1 when passed to this, though you could also simply use "-1 ". If you use
* both '-' at the start and have the most significant digit as 8 or higher, such as with "-FFFFFFFF", then both
* indicate a negative number, but the digits will be processed first (producing -1) and then the whole thing will
* be multiplied by -1 to flip the sign again (returning 1).
* <br>
* Should be fairly close to Java 8's Integer.parseUnsignedInt method, which is an odd omission from earlier JDKs.
* This doesn't throw on invalid input, though, instead returning 0 if the first char is not a hex digit, or
* stopping the parse process early if a non-hex-digit char is read before the end of cs is reached. If the parse is
* stopped early, this behaves as you would expect for a number with less digits, and simply doesn't fill the larger
* places.
* @param cs a CharSequence, such as a String, containing only hex digits with an optional sign (no 0x at the start)
* @return the int that cs represents
*/
public static int intFromHex(final CharSequence cs) {
return intFromHex(cs, 0, cs.length());
}
/**
* Reads in a CharSequence containing only hex digits (only 0-9, a-f, and A-F) with an optional sign at the start
* and returns the int they represent, reading at most 8 characters (9 if there is a sign) and returning the result
* if valid, or 0 if nothing could be read. The leading sign can be '+' or '-' if present. This can also represent
* negative numbers as they are printed by such methods as String.format given a %x in the formatting string, or
* this class' {@link #hex(int)} method; that is, if the first digit of an 8-char (or longer) CharSequence is a hex
* digit 8 or higher, then the whole number represents a negative number, using two's complement and so on. This
* means "FFFFFFFF" would return the int -1 when passed to this, though you could also simply use "-1 ". If you use
* both '-' at the start and have the most significant digit as 8 or higher, such as with "-FFFFFFFF", then both
* indicate a negative number, but the digits will be processed first (producing -1) and then the whole thing will
* be multiplied by -1 to flip the sign again (returning 1).
* <br>
* Should be fairly close to Java 8's Integer.parseUnsignedInt method, which is an odd omission from earlier JDKs.
* This doesn't throw on invalid input, though, instead returning 0 if the first char is not a hex digit, or
* stopping the parse process early if a non-hex-digit char is read before end is reached. If the parse is stopped
* early, this behaves as you would expect for a number with less digits, and simply doesn't fill the larger places.
* @param cs a CharSequence, such as a String, containing only hex digits with an optional sign (no 0x at the start)
* @param start the (inclusive) first character position in cs to read
* @param end the (exclusive) last character position in cs to read (this stops after 8 or 9 characters if end is too large, depending on sign)
* @return the int that cs represents
*/
public static int intFromHex(final CharSequence cs, final int start, int end)
{
int len, h, lim = 8;
if(cs == null || start < 0 || end <=0 || end - start <= 0
|| (len = cs.length()) - start <= 0 || end > len)
return 0;
char c = cs.charAt(start);
if(c == '-')
{
len = -1;
h = 0;
++end;
lim = 9;
}
else if(c == '+')
{
len = 1;
h = 0;
++end;
lim = 9;
}
else if(c > 102 || (h = hexCodes[c]) < 0)
return 0;
else
{
len = 1;
}
int data = h;
for (int i = start; i < end && i < start + lim; i++) {
if((c = cs.charAt(i)) > 102 || (h = hexCodes[c]) < 0)
return data * len;
data <<= 4;
data |= h;
}
return data * len;
}
/**
* Reads in a char[] containing only hex digits (only 0-9, a-f, and A-F) with an optional sign at the start
* and returns the int they represent, reading at most 8 characters (9 if there is a sign) and returning the result
* if valid, or 0 if nothing could be read. The leading sign can be '+' or '-' if present. This can also represent
* negative numbers as they are printed by such methods as String.format given a %x in the formatting string, or
* this class' {@link #hex(int)} method; that is, if the first digit of an 8-char (or longer) char[] is a hex
* digit 8 or higher, then the whole number represents a negative number, using two's complement and so on. This
* means "FFFFFFFF" would return the int -1 when passed to this, though you could also simply use "-1 ". If you use
* both '-' at the start and have the most significant digit as 8 or higher, such as with "-FFFFFFFF", then both
* indicate a negative number, but the digits will be processed first (producing -1) and then the whole thing will
* be multiplied by -1 to flip the sign again (returning 1).
* <br>
* Should be fairly close to Java 8's Integer.parseUnsignedInt method, which is an odd omission from earlier JDKs.
* This doesn't throw on invalid input, though, instead returning 0 if the first char is not a hex digit, or
* stopping the parse process early if a non-hex-digit char is read before end is reached. If the parse is stopped
* early, this behaves as you would expect for a number with less digits, and simply doesn't fill the larger places.
* @param cs a char array containing only hex digits with an optional sign (no 0x at the start)
* @param start the (inclusive) first character position in cs to read
* @param end the (exclusive) last character position in cs to read (this stops after 8 or 9 characters if end is too large, depending on sign)
* @return the int that cs represents
*/
public static int intFromHex(final char[] cs, final int start, int end)
{
int len, h, lim = 8;
if(cs == null || start < 0 || end <=0 || end - start <= 0
|| (len = cs.length) - start <= 0 || end > len)
return 0;
char c = cs[start];
if(c == '-')
{
len = -1;
h = 0;
++end;
lim = 9;
}
else if(c == '+')
{
len = 1;
h = 0;
++end;
lim = 9;
}
else if(c > 102 || (h = hexCodes[c]) < 0)
return 0;
else
{
len = 1;
}
int data = h;
for (int i = start; i < end && i < start + lim; i++) {
if((c = cs[i]) > 102 || (h = hexCodes[c]) < 0)
return data * len;
data <<= 4;
data |= h;
}
return data * len;
}
/**
* Reads in a CharSequence containing only decimal digits (0-9) with an optional sign at the start and returns the
* int they represent, reading at most 10 characters (11 if there is a sign) and returning the result if valid, or 0
* if nothing could be read. The leading sign can be '+' or '-' if present. This can technically be used to handle
* unsigned integers in decimal format, but it isn't the intended purpose. If you do use it for handling unsigned
* ints, 2147483647 is normally the highest positive int and -2147483648 the lowest negative one, but if you give
* this a number between 2147483647 and {@code 2147483647 + 2147483648}, it will interpret it as a negative number
* that fits in bounds using the normal rules for converting between signed and unsigned numbers.
* <br>
* Should be fairly close to the JDK's Integer.parseInt method, but this also supports CharSequence data instead of
* just String data, and ignores chars after the number. This doesn't throw on invalid input, either, instead
* returning 0 if the first char is not a decimal digit, or stopping the parse process early if a non-decimal-digit
* char is read before the end of cs is reached. If the parse is stopped early, this behaves as you would expect for
* a number with less digits, and simply doesn't fill the larger places.
* @param cs a CharSequence, such as a String, containing only digits 0-9 with an optional sign
* @return the int that cs represents
*/
public static int intFromDec(final CharSequence cs) {
return intFromDec(cs, 0, cs.length());
}
/**
* Reads in a CharSequence containing only decimal digits (0-9) with an optional sign at the start and returns the
* int they represent, reading at most 10 characters (11 if there is a sign) and returning the result if valid, or 0
* if nothing could be read. The leading sign can be '+' or '-' if present. This can technically be used to handle
* unsigned integers in decimal format, but it isn't the intended purpose. If you do use it for handling unsigned
* ints, 2147483647 is normally the highest positive int and -2147483648 the lowest negative one, but if you give
* this a number between 2147483647 and {@code 2147483647 + 2147483648}, it will interpret it as a negative number
* that fits in bounds using the normal rules for converting between signed and unsigned numbers.
* <br>
* Should be fairly close to the JDK's Integer.parseInt method, but this also supports CharSequence data instead of
* just String data, and allows specifying a start and end. This doesn't throw on invalid input, either, instead
* returning 0 if the first char is not a decimal digit, or stopping the parse process early if a non-decimal-digit
* char is read before end is reached. If the parse is stopped early, this behaves as you would expect for a number
* with less digits, and simply doesn't fill the larger places.
* @param cs a CharSequence, such as a String, containing only digits 0-9 with an optional sign
* @param start the (inclusive) first character position in cs to read
* @param end the (exclusive) last character position in cs to read (this stops after 10 or 11 characters if end is too large, depending on sign)
* @return the int that cs represents
*/
public static int intFromDec(final CharSequence cs, final int start, int end)
{
int len, h, lim = 10;
if(cs == null || start < 0 || end <=0 || end - start <= 0
|| (len = cs.length()) - start <= 0 || end > len)
return 0;
char c = cs.charAt(start);
if(c == '-')
{
len = -1;
++end;
lim = 11;
}
else if(c == '+')
{
len = 1;
++end;
lim = 11;
}
else if(c > 102 || (h = hexCodes[c]) < 0 || h > 9)
return 0;
else
{
len = 1;
}
int data = 0;
for (int i = start; i < end && i < start + lim; i++) {
if((c = cs.charAt(i)) > 102 || (h = hexCodes[c]) < 0 || h > 9)
return data * len;
data = data * 10 + h;
}
return data * len;
}
/**
* Reads in a CharSequence containing only binary digits (only 0 and 1) and returns the long they represent,
* reading at most 64 characters and returning the result if valid or 0 otherwise. The first digit is considered
* the sign bit iff cs is 64 chars long.
* <br>
* Should be fairly close to Java 8's Long.parseUnsignedLong method, which is a bizarre omission from earlier JDKs.
* This doesn't throw on invalid input, though, instead returning 0.
* @param cs a CharSequence, such as a String, containing only binary digits (nothing at the start)
* @return the long that cs represents
*/
public static long longFromBin(CharSequence cs)
{
return longFromBin(cs, 0, cs.length());
}
/**
* Reads in a CharSequence containing only binary digits (only 0 and 1) and returns the long they represent,
* reading at most 64 characters and returning the result if valid or 0 otherwise. The first digit is considered
* the sign bit iff cs is 64 chars long.
* <br>
* Should be fairly close to Java 8's Long.parseUnsignedLong method, which is a bizarre omission from earlier JDKs.
* This doesn't throw on invalid input, though, instead returning 0.
* @param cs a CharSequence, such as a String, containing only binary digits (nothing at the start)
* @param start the first character position in cs to read from
* @param end the last character position in cs to read from (this stops after 64 characters if end is too large)
* @return the long that cs represents
*/
public static long longFromBin(CharSequence cs, final int start, final int end)
{
int len;
if(cs == null || start < 0 || end <=0 || end - start <= 0
|| (len = cs.length()) - start <= 0 || end > len)
return 0;
char c = cs.charAt(start);
if(c < '0' || c > '1')
return 0;
long data = hexCodes[c];
for (int i = start+1; i < end && i < 64; i++) {
if((c = cs.charAt(i)) < '0' || c > '1')
return 0;
data <<= 1;
data |= c - '0';
}
return data;
}
/**
* Base-64 encodes number and stores that string representation in buf starting at offset; uses 11 chars.
*
* @param number the long to encode
* @param offset the first position to set in buf
* @param buf a char array that should be non-null and have length of at least offset + 11
* @return buf, after modifying it in-place
*/
public static char[] b64Encode(long number, int offset, char[] buf) {
if (buf != null && buf.length >= 11 - offset) {
buf[offset] = keyBase64[(int) (number >>> 60)];
buf[offset + 1] = keyBase64[(int) (0x3f & number >>> 54)];
buf[offset + 2] = keyBase64[(int) (0x3f & number >>> 48)];
buf[offset + 3] = keyBase64[(int) (0x3f & number >>> 42)];
buf[offset + 4] = keyBase64[(int) (0x3f & number >>> 36)];
buf[offset + 5] = keyBase64[(int) (0x3f & number >>> 30)];
buf[offset + 6] = keyBase64[(int) (0x3f & number >>> 24)];
buf[offset + 7] = keyBase64[(int) (0x3f & number >>> 18)];
buf[offset + 8] = keyBase64[(int) (0x3f & number >>> 12)];
buf[offset + 9] = keyBase64[(int) (0x3f & number >>> 6)];
buf[offset + 10] = keyBase64[(int) (0x3f & number)];
}
return buf;
}
/**
* Base-64 encodes number and stores that string representation in buf starting at offset; uses 11 chars.
*
* @param number the double to encode
* @param offset the first position to set in buf
* @param buf a char array that should be non-null and have length of at least offset + 11
* @return buf, after modifying it in-place
*/
public static char[] b64Encode(double number, int offset, char[] buf) {
return b64Encode(NumberTools.doubleToLongBits(number), offset, buf);
}
/**
* Base-64 encodes number and stores that string representation in buf starting at offset; uses 6 chars.
*
* @param number the int to encode
* @param offset the first position to set in buf
* @param buf a char array that should be non-null and have length of at least offset + 6
* @return buf, after modifying it in-place
*/
public static char[] b64Encode(int number, int offset, char[] buf) {
if (buf != null && buf.length >= 6 - offset) {
buf[offset] = keyBase64[number >>> 30];
buf[offset + 1] = keyBase64[0x3f & number >>> 24];
buf[offset + 2] = keyBase64[0x3f & number >>> 18];
buf[offset + 3] = keyBase64[0x3f & number >>> 12];
buf[offset + 4] = keyBase64[0x3f & number >>> 6];
buf[offset + 5] = keyBase64[0x3f & number];
}
return buf;
}
/**
* Base-64 encodes number and stores that string representation in buf starting at offset; uses 6 chars.
*
* @param number the float to encode
* @param offset the first position to set in buf
* @param buf a char array that should be non-null and have length of at least offset + 6
* @return buf, after modifying it in-place
*/
public static char[] b64Encode(float number, int offset, char[] buf) {
return b64Encode(NumberTools.floatToIntBits(number), offset, buf);
}
/**
* Base-64 encodes number and stores that string representation in buf starting at offset; uses 3 chars.
*
* @param number the int to encode
* @param offset the first position to set in buf
* @param buf a char array that should be non-null and have length of at least offset + 3
* @return buf, after modifying it in-place
*/
public static char[] b64Encode(short number, int offset, char[] buf) {
if (buf != null && buf.length >= 3 - offset) {
buf[offset] = keyBase64[number >>> 12];
buf[offset + 1] = keyBase64[0x3f & number >>> 6];
buf[offset + 2] = keyBase64[0x3f & number];
}
return buf;
}
/**
* Base-64 encodes glyph and stores that string representation in buf starting at offset; uses 3 chars.
*
* @param glyph the char to encode
* @param offset the first position to set in buf
* @param buf a char array that should be non-null and have length of at least offset + 3
* @return buf, after modifying it in-place
*/
public static char[] b64Encode(char glyph, int offset, char[] buf) {
if (buf != null && buf.length >= 4 - offset) {
buf[offset] = keyBase64[glyph >>> 12];
buf[offset + 1] = keyBase64[0x3f & glyph >>> 6];
buf[offset + 2] = keyBase64[0x3f & glyph];
}
return buf;
}
/**
* Base-64 encodes number and stores that string representation in buf starting at offset; uses 2 chars.
*
* @param number the byte to encode
* @param offset the first position to set in buf
* @param buf a char array that should be non-null and have length of at least offset + 2
* @return buf, after modifying it in-place
*/
public static char[] b64Encode(byte number, int offset, char[] buf) {
if (buf != null && buf.length >= 2 - offset) {
buf[offset] = keyBase64[number >>> 6];
buf[offset + 1] = keyBase64[0x3f & number];
}
return buf;
}
/**
* Decodes 11 characters from data starting from offset to get a long encoded as base-64.
* @param data a char array that should be have length of at least offset + 11
* @param offset where in data to start reading from
* @return the decoded long
*/
public static long b64DecodeLong(char[] data, int offset) {
return (data == null || data.length < 11 + offset) ? 0L :
(((long)data[offset]) << 60)
| ((0x3fL & data[offset + 1]) << 54)
| ((0x3fL & data[offset + 2]) << 48)
| ((0x3fL & data[offset + 3]) << 42)
| ((0x3fL & data[offset + 4]) << 36)
| ((0x3fL & data[offset + 5]) << 30)
| ((0x3fL & data[offset + 6]) << 24)
| ((0x3fL & data[offset + 7]) << 18)
| ((0x3fL & data[offset + 8]) << 12)
| ((0x3fL & data[offset + 9]) << 6)
| (0x3fL & data[offset + 10]);
}
/**
* Decodes 11 characters from data starting from offset to get a double encoded as base-64.
* @param data a char array that should be have length of at least offset + 11
* @param offset where in data to start reading from
* @return the decoded double
*/
public static double b64DecodeDouble(char[] data, int offset) {
return (data == null || data.length < 11 + offset) ? 0.0 :
NumberTools.longBitsToDouble((((long) data[offset]) << 60)
| ((0x3fL & data[offset + 1]) << 54)
| ((0x3fL & data[offset + 2]) << 48)
| ((0x3fL & data[offset + 3]) << 42)
| ((0x3fL & data[offset + 4]) << 36)
| ((0x3fL & data[offset + 5]) << 30)
| ((0x3fL & data[offset + 6]) << 24)
| ((0x3fL & data[offset + 7]) << 18)
| ((0x3fL & data[offset + 8]) << 12)
| ((0x3fL & data[offset + 9]) << 6)
| (0x3fL & data[offset + 10]));
}
/**
* Decodes 6 characters from data starting from offset to get an int encoded as base-64.
* @param data a char array that should be have length of at least offset + 6
* @param offset where in data to start reading from
* @return the decoded int
*/
public static int b64DecodeInt(char[] data, int offset) {
return (data == null || data.length < 6 + offset) ? 0 :
((data[offset]) << 30)
| ((0x3f & data[offset + 1]) << 24)
| ((0x3f & data[offset + 2]) << 18)
| ((0x3f & data[offset + 3]) << 12)
| ((0x3f & data[offset + 4]) << 6)
| (0x3f & data[offset + 5]);
}
/**
* Decodes 6 characters from data starting from offset to get a float encoded as base-64.
* @param data a char array that should be have length of at least offset + 6
* @param offset where in data to start reading from
* @return the decoded float
*/
public static float b64DecodeFloat(char[] data, int offset) {
return (data == null || data.length < 6 + offset) ? 0f :
NumberTools.intBitsToFloat(((data[offset]) << 30)
| ((0x3f & data[offset + 1]) << 24)
| ((0x3f & data[offset + 2]) << 18)
| ((0x3f & data[offset + 3]) << 12)
| ((0x3f & data[offset + 4]) << 6)
| (0x3f & data[offset + 5]));
}
/**
* Decodes 3 characters from data starting from offset to get a short encoded as base-64.
* @param data a char array that should be have length of at least offset + 3
* @param offset where in data to start reading from
* @return the decoded short
*/
public static short b64DecodeShort(char[] data, int offset) {
return (short) ((data == null || data.length < 3 + offset) ? 0 :
((data[offset]) << 12)
| ((0x3f & data[offset + 1]) << 6)
| (0x3f & data[offset + 2]));
}
/**
* Decodes 3 characters from data starting from offset to get a char encoded as base-64.
* @param data a char array that should be have length of at least offset + 3
* @param offset where in data to start reading from
* @return the decoded char
*/
public static char b64DecodeChar(char[] data, int offset) {
return (char) ((data == null || data.length < 3 + offset) ? 0 :
((data[offset]) << 12)
| ((0x3f & data[offset + 1]) << 6)
| (0x3f & data[offset + 2]));
}
/**
* Decodes 2 characters from data starting from offset to get a byte encoded as base-64.
* @param data a char array that should be have length of at least offset + 2
* @param offset where in data to start reading from
* @return the decoded byte
*/
public static byte b64DecodeByte(char[] data, int offset) {
return (byte) ((data == null || data.length < 2 + offset) ? 0 :
((data[offset]) << 6)
| (0x3f & data[offset + 1]));
}
public static String hexHash(boolean... array) {
return hex(CrossHash.hash64(array));
}
public static String hexHash(byte... array) {
return hex(CrossHash.hash64(array));
}
public static String hexHash(short... array) {
return hex(CrossHash.hash64(array));
}
public static String hexHash(char... array) {
return hex(CrossHash.hash64(array));
}
public static String hexHash(int... array) {
return hex(CrossHash.hash64(array));
}
public static String hexHash(long... array) {
return hex(CrossHash.hash64(array));
}
}