/*
* Copyright 2003-2016 JetBrains s.r.o.
*
* 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 jetbrains.mps.smodel;
import java.util.Arrays;
/**
* Variation of Base64 encoding, with 10 digits, lower- and uppercase latin characters, '$' and '_' characters, basically, regular ASCII chars
* with isJavaIdentifierPart == true, for the sake of use in generated code, e.g. method names.
* <p/>
* This class is not thread-safe, uses internal buffers to save memory on (de-)serialize, do not share it between threads.
*
* @author Artem Tikhomirov
* @since 3.5
*/
public final class JavaFriendlyBase64 {
// length shall be 2^^6 = 64 (10 digits + 2x26 letters + '$' and '_'. ASCII chars with isJavaIdentifierPart == true
// Important: charAt(0) shall be '0', we use this to strip leading zeros.
private final char[] myIndexChars = "0123456789abcdefghijklmnopqrstuvwxyz$_ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final char MIN_CHAR = '$';
private static final char MAX_CHAR = 'z';
private final int[] myCharToValue = new int[MAX_CHAR - MIN_CHAR + 1];
private final char[] myBufferLong = new char[11]; // ceil(sizeof(long) / sizeof(indexChars)) = ceil(64 bits / 6) = 11;
private final char[] myBufferInt = new char[6]; // ceil(32 bits / 6) = ceil(5.33) = 6;
public JavaFriendlyBase64() {
Arrays.fill(myCharToValue, -1);
for (int i = 0; i < myIndexChars.length; i++) {
int charValue = myIndexChars[i];
myCharToValue[charValue - MIN_CHAR] = i;
}
}
public String toString(long v) {
for (int i = myBufferLong.length - 1; i >= 0; i--) {
myBufferLong[i] = myIndexChars[((int) v & 0x3F)];
v = v >>> 6;
}
// strip leading zeros, up to last digit, which is kept anyway (if it's zero, fine)
for (int i = 0; i < myBufferLong.length - 1; i++) {
if (myBufferLong[i] != '0') {
return new String(myBufferLong, i, myBufferLong.length - i);
}
}
return new String(myBufferLong, myBufferLong.length - 1, 1);
}
public long parseLong(String text) throws IllegalArgumentException {
long result = 0;
for (int i = 0, x = text.length(), shift = 0; i < x; i++, shift = 6) {
result <<= shift;
char c = text.charAt(i);
if (c - MIN_CHAR < 0 || c - MIN_CHAR >= myCharToValue.length) {
throw new IllegalArgumentException(String.format("String \"%s\" cannot be parsed as long value: invalid character \"%c\" in position %d", text, c, i));
}
int value = myCharToValue[c - MIN_CHAR];
if (value < 0) {
throw new IllegalArgumentException(String.format("String \"%s\" cannot be parsed as long value: invalid character \"%c\" in position %d", text, c, i));
}
result |= value;
}
return result;
}
// at least 5, at most 6 character string encoding. Leading zero is removed only if it's sixth symbol.
public String indexValue(int v) {
myBufferInt[5] = myIndexChars[v & 0x3F];
v >>= 6;
myBufferInt[4] = myIndexChars[v & 0x3F];
v >>= 6;
myBufferInt[3] = myIndexChars[v & 0x3F];
v >>= 6;
myBufferInt[2] = myIndexChars[v & 0x3F];
v >>= 6;
myBufferInt[1] = myIndexChars[v & 0x3F];
v >>= 6;
// 5 times x 6 bits = we've got only 2 bits left of integer's total 32
v &= 0x3;
if (v != 0) {
myBufferInt[0] = myIndexChars[v];
return new String(myBufferInt);
}
return new String(myBufferInt, 1, 5);
}
public static void main(String[] args) {
JavaFriendlyBase64 x = new JavaFriendlyBase64();
final long[] test = {0, 1, 15, 63, 64, 65, 123, 9834503475L, Long.MAX_VALUE, Long.MIN_VALUE};
for (long l : test) {
final String s = x.toString(l);
System.out.printf("%d: toString: %s, fromString:%d\n", l, s, x.parseLong(s));
}
}
}