/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.addthis.basis.util;
import java.util.Random;
import java.util.UUID;
import java.math.BigInteger;
import com.google.common.hash.Hashing;
import com.google.common.hash.HashFunction;
public class LessNumbers {
private static final HashFunction fallbackHashFunc = Hashing.murmur3_128();
private static final long SIX_BIT_MASK = 63;
/**
* takes a number in a human readable format (5BM, 6GB, 102K) and returns a long.
*
* @param num a human readable format (5BM, 6GB, 102K)
* @return a long number equivalent to the string representation
*/
public static long parseHumanReadable(String num) {
num = num.toUpperCase();
if (num.endsWith("M") || num.endsWith("MB")) {
return Long.parseLong(num.substring(0, num.indexOf("M"))) * 1024 * 1024;
} else if (num.endsWith("MIB")) {
return Long.parseLong(num.substring(0, num.indexOf("M"))) * 1000 * 1000;
} else if (num.endsWith("K") || num.endsWith("KB")) {
return Long.parseLong(num.substring(0, num.indexOf("K"))) * 1024;
} else if (num.endsWith("KIB")) {
return Long.parseLong(num.substring(0, num.indexOf("K"))) * 1000;
} else if (num.endsWith("G") || num.endsWith("GB")) {
return Long.parseLong(num.substring(0, num.indexOf("G"))) * 1024 * 1024 * 1024;
} else if (num.endsWith("GIB")) {
return Long.parseLong(num.substring(0, num.indexOf("G"))) * 1000 * 1000 * 1000;
} else {
return Long.parseLong(num);
}
}
/**
* integer
*/
public static int parseInt(String val, int def, int radix) {
try {
return Integer.parseInt(val, radix);
} catch (Exception ex) {
return def;
}
}
/**
* long
*/
public static long parseLong(String val, long def, int radix) {
try {
return Long.parseLong(val, radix);
} catch (Exception ex) {
return def;
}
}
/**
* float
*/
public static float parseFloat(String val, float def) {
try {
return Float.parseFloat(val);
} catch (Exception ex) {
return def;
}
}
/**
* double
*/
public static double parseDouble(String val, double def) {
try {
return Double.parseDouble(val);
} catch (Exception ex) {
return def;
}
}
/**
* Converts a base-encoded string to a long.
* Does not handle negative numbers;
*/
public static long longFromBase(String val, int base) {
return longFromBase(val, base, false, false);
}
public static long longFromBase(CharSequence val, int base, boolean fallback, boolean toLower) {
if (base > basechars.length) {
if (fallback) {
return fallbackHashFunc.hashUnencodedChars(val).asLong();
}
throw new RuntimeException(base + " outside base range of 2-" + basechars.length);
}
long rv = 0;
int length = val.length();
for (int i = 0; i < length; i++) {
char aCv = val.charAt(i);
if (toLower) {
aCv = Character.toLowerCase(aCv);
}
rv *= base;
int av;
if (aCv >= '0' && aCv <= '9') {
av = aCv - '0';
} else if (aCv >= 'a' && aCv <= 'z') {
av = aCv - 'a' + 10;
} else if (aCv >= 'A' && aCv <= 'Z') {
av = aCv - 'A' + 36;
} else {
if (fallback) {
return fallbackHashFunc.hashUnencodedChars(val).asLong();
}
throw new RuntimeException("invalid base encoding: " + val);
}
if (av >= base) {
if (fallback) {
return fallbackHashFunc.hashUnencodedChars(val).asLong();
}
throw new RuntimeException("chars outside of base range: " + val);
}
rv += av;
}
return rv;
}
/**
* Converts a base-encoded string to an integer.
* Does not handle negative numbers;
*/
public static int intFromBase(CharSequence val, int base) {
return intFromBase(val, base, false);
}
private static int intFromBase(CharSequence val, int base, boolean toLower) {
if (base > basechars.length) {
throw new RuntimeException(base + " outside base range of 2-" + basechars.length);
}
int rv = 0;
int length = val.length();
for (int i = 0; i < length; i++) {
char aCv = val.charAt(i);
if (toLower) {
aCv = Character.toLowerCase(aCv);
}
rv *= base;
rv += charToDigit(aCv, val);
}
return rv;
}
/**
* Array of chars for generating numbers in alternate bases.
*/
static final char[] basechars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_".toCharArray();
static final BigInteger[] bigIntegerChars = new BigInteger[basechars.length];
static {
int offset = 0;
for(char i = '0'; i <= '9'; i++) {
bigIntegerChars[offset++] = BigInteger.valueOf(i - '0');
}
for(char i = 'a'; i <= 'z'; i++) {
bigIntegerChars[offset++] = BigInteger.valueOf(i - 'a' + 10);
}
for(char i = 'A'; i <= 'Z'; i++) {
bigIntegerChars[offset++] = BigInteger.valueOf(i - 'A' + 36);
}
bigIntegerChars[offset++] = BigInteger.valueOf(62);
bigIntegerChars[offset++] = BigInteger.valueOf(63);
}
public static final int MAX_BASE = basechars.length;
/**
* String containing zeros for pre-padding base-36 longs.
*/
private static final String[] pads = new String[MAX_BASE];
/**
* Public pre-generated Random for what ailes you.
*/
public static final Random random = new Random(System.currentTimeMillis());
/**
* Converts a long to a base-encoded string. Does not handle negative numbers;
*/
public static String toBase(long val, int base) {
return toBase(val, base, -1);
}
public static String toBase(long val, int base, int minlen) {
if (base > basechars.length) {
throw new RuntimeException(base + " outside base range of 2-" + basechars.length);
}
char[] out = new char[128];
for (int i = out.length - 1;; i--) {
out[i] = basechars[(int) Math.abs(val % base)];
val = val / base;
if (--minlen <= 0 && (val == 0 || i == 0)) {
return new String(out, i, out.length - i);
}
}
}
/**
* Generates a random long in specified pre-padded with 0s.
*
* @return long in specified base pre-padded with 0s
*/
static String nextLong(int base) {
synchronized (pads) {
String pad = pads[base - 1];
if (pad == null) {
pad = LessBytes.clear(toBase(Long.MAX_VALUE, base), '0');
pads[base - 1] = pad;
}
String nv = toBase(random.nextLong(), base);
return pad.substring(nv.length()) + nv;
}
}
/**
* Array of bytes for generating hex strings.
*/
public static final byte[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static final byte[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
private static final boolean verboseHex = System.getProperty("xhex", "0").equals("1");
public static String toHexString(byte[] b) {
StringBuilder sb = new StringBuilder();
for (byte c : b) {
if (sb.length() > 0) {
sb.append(' ');
}
char c1 = (char) hex[(c >> 4) & 0xf];
char c2 = (char) hex[(c & 0xf)];
sb.append(c1);
sb.append(c2);
if (verboseHex) {
sb.append(' ');
if (c >= 32 && c <= 126) {
sb.append((char) c);
} else {
sb.append('.');
}
}
}
return sb.toString();
}
/**
* Parse integer with given radix and default value on errors
*/
public static int parseInt(int radix, String val, int def) {
try {
return Integer.parseInt(val, radix);
} catch (Exception ex) {
return def;
}
}
/**
* Parse integer with given radix and default value on errors
*/
public static long parseLong(int radix, String val, long def) {
try {
return Long.parseLong(val, radix);
} catch (Exception ex) {
return def;
}
}
public static long longFromBase36(CharSequence val) {
return longFromBase(val, 36, false, true);
}
public static long longFromBase64(CharSequence val) {
return longFromBase(val, 64, false, false);
}
public static String toBase36(long val) {
return toBase(val, 36).toUpperCase();
}
public static int intFromBase36(CharSequence val) {
return intFromBase(val, 36, true);
}
public static String toBase64(long val) {
return toBase(val, 64);
}
public static int intFromBase64(CharSequence val) {
return intFromBase(val, 64, false);
}
static int charToDigit(char character, CharSequence val) {
if (character >= '0' && character <= '9') {
return character - '0';
} else if (character >= 'a' && character <= 'z') {
return character - 'a' + 10;
} else if (character >= 'A' && character <= 'Z') {
return character - 'A' + 36;
} else if (character == '-') {
return 62;
} else if (character == '_') {
return 63;
} else {
throw new RuntimeException("invalid base encoding: " + val);
}
}
public static BigInteger bigIntegerFromBase(String val, int base) {
if (base > MAX_BASE) {
throw new RuntimeException(base + " outside base range of 2-" + MAX_BASE);
}
BigInteger rv = BigInteger.ZERO;
BigInteger biBase = BigInteger.valueOf(base);
int length = val.length();
for (int i = 0; i < length; i++) {
rv = rv.multiply(biBase);
char cvv = val.charAt(i);
if (cvv >= '0' && cvv <= '9') {
rv = rv.add(bigIntegerChars[cvv - '0']);
} else if (cvv >= 'a' && cvv <= 'z') {
rv = rv.add(bigIntegerChars[cvv - 'a' + 10]);
} else if (cvv >= 'A' && cvv <= 'Z') {
rv = rv.add(bigIntegerChars[cvv - 'A' + 36]);
} else if (cvv == '-') {
rv = rv.add(bigIntegerChars[62]);
} else if (cvv == '_') {
rv = rv.add(bigIntegerChars[63]);
} else {
throw new RuntimeException("invalid base encoding: " + val);
}
}
return rv;
}
public static UUID UUIDFromBase64(String val) {
long hiBits = 0;
int pos = 0;
for(int i = 0; i < 11; i++) {
hiBits <<= 6;
hiBits += charToDigit(val.charAt(pos++), val);
}
int mixBits = charToDigit(val.charAt(pos++), val);
hiBits <<= 2;
hiBits += (mixBits >>> 4);
/**
* loBits only needs to low 4 bits out of the 6 bits
* in mixBits but the high 2 bits are going to
* fall off anyway as a result of the left shift operations.
*/
long loBits = mixBits;
for(int i = 0; i < 10; i++) {
loBits <<= 6;
loBits += charToDigit(val.charAt(pos++), val);
}
return new UUID(hiBits, loBits);
}
public static String toBase(BigInteger val, int base) {
return toBase(val, base, -1);
}
public static String toBase(BigInteger val, int base, int minlen) {
if (base > MAX_BASE) {
throw new RuntimeException(base + " outside base range of 2-" + MAX_BASE);
}
char[] out = new char[128];
BigInteger biBase = BigInteger.valueOf(base);
for (int i = out.length - 1;; i--) {
BigInteger[] divAndRem = val.divideAndRemainder(biBase);
out[i] = basechars[Math.abs(divAndRem[1].intValue())];
val = divAndRem[0];
if (--minlen <= 0 && (val.equals(BigInteger.ZERO) || i == 0)) {
return new String(out, i, out.length - i);
}
}
}
public static String toBase64(UUID val) {
long hiBits = val.getMostSignificantBits();
long loBits = val.getLeastSignificantBits();
char[] out = new char[22];
int pos = 21;
for(int i = 0; i < 10; i++) {
out[pos--] = basechars[(int) (loBits & SIX_BIT_MASK)];
loBits >>>= 6;
}
int mixBits = (((int) (hiBits & 3)) << 4) | ((int) loBits);
out[pos--] = basechars[mixBits];
hiBits >>>= 2;
for(int i = 0; i < 11; i++) {
out[pos--] = basechars[(int) (hiBits & SIX_BIT_MASK)];
hiBits >>>= 6;
}
return new String(out);
}
public static String toBase36(BigInteger val) {
return toBase(val, 36).toUpperCase();
}
public static BigInteger bigIntegerFromBase36(String val) {
return bigIntegerFromBase(val.toLowerCase(), 36);
}
public static String toBase64(BigInteger val) {
return toBase(val, 64);
}
public static BigInteger bigIntegerFromBase64(String val) {
return bigIntegerFromBase(val, 64);
}
}