/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.tools.debug.core.sourcemaps;
import com.google.common.primitives.Ints;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A class to convert to and from base64 vlq encoded strings.
* <p>
* <code>"AAgBC" <==> {0, 0, 16, 1}</code>
*/
public class VlqDecoder {
// A Base64 VLQ digit can represent 5 bits, so it is base-32.
private static final int VLQ_BASE_SHIFT = 5;
private static final int VLQ_BASE = 1 << VLQ_BASE_SHIFT;
// A mask of bits for a VLQ digit (11111), 31 decimal.
private static final int VLQ_BASE_MASK = VLQ_BASE - 1;
// The continuation bit is the 6th bit.
private static final int VLQ_CONTINUATION_BIT = VLQ_BASE;
/**
* A map used to convert integer values in the range 0-63 to their base64 values.
*/
private static final String BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz" + "0123456789+/";
/**
* A map used to convert base64 character into integer values.
*/
private static final int[] BASE64_DECODE_MAP = new int[256];
static {
Arrays.fill(BASE64_DECODE_MAP, -1);
for (int i = 0; i < BASE64_MAP.length(); i++) {
BASE64_DECODE_MAP[BASE64_MAP.charAt(i)] = i;
}
}
/**
* Convert from a Base64 VLQ string sequence to the corresponding sequence of ints.
*
* @param str
* @return
*/
public static int[] decode(String str) {
List<Integer> results = new ArrayList<Integer>();
int i = 0;
int strLen = str.length();
while (i < strLen) {
int result = 0;
boolean continuation;
int shift = 0;
do {
char c = str.charAt(i++);
int digit = fromBase64(c);
continuation = (digit & VLQ_CONTINUATION_BIT) != 0;
digit &= VLQ_BASE_MASK;
result = result + (digit << shift);
shift = shift + VLQ_BASE_SHIFT;
} while (continuation);
results.add(fromVLQSigned(result));
}
return Ints.toArray(results);
}
/**
* Encode the given sequence of ints to a Base64 VLQ encoded string.
*
* @param values
* @return
*/
public static String encode(int[] values) {
StringBuilder builder = new StringBuilder();
for (int value : values) {
value = toVLQSigned(value);
do {
int digit = value & VLQ_BASE_MASK;
value >>>= VLQ_BASE_SHIFT;
if (value > 0) {
digit |= VLQ_CONTINUATION_BIT;
}
builder.append(toBase64(digit));
} while (value > 0);
}
return builder.toString();
}
private static int fromBase64(char c) {
return BASE64_DECODE_MAP[c];
}
/**
* Converts to a two-complement value from a value where the sign bit is is placed in the least
* significant bit. For example, as decimals: 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 4
* (100 binary) becomes 2, 5 (101 binary) becomes -2
*/
private static int fromVLQSigned(int value) {
boolean negate = (value & 1) == 1;
value = value >> 1;
return negate ? -value : value;
}
private static char toBase64(int value) {
return BASE64_MAP.charAt(value);
}
/**
* Converts from a two-complement value to a value where the sign bit is is placed in the least
* significant bit. For example, as decimals: 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) 2
* becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
private static int toVLQSigned(int value) {
if (value < 0) {
return ((-value) << 1) + 1;
} else {
return (value << 1) + 0;
}
}
private VlqDecoder() {
}
}