com.questdb.misc.Base64 Maven / Gradle / Ivy
Show all versions of questdb-core Show documentation
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (C) 2014-2016 Appsicle
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
******************************************************************************/
package com.questdb.misc;
public class Base64 {
private static final byte PADDING = 127;
private static final byte[] decodeMap = initDecodeMap();
/**
* @param text base64Binary data is likely to be long, and decoding requires
* each character to be accessed twice (once for counting length, another
* for decoding.)
*
* A benchmark showed that taking {@link String} is faster, presumably
* because JIT can inline a lot of string access (with data of 1K chars, it was twice as fast)
* @return decoded bytes
*/
public static byte[] parseBase64Binary(String text) {
final int buflen = guessLength(text);
final byte[] out = new byte[buflen];
int o = 0;
final int len = text.length();
int i;
final byte[] quadruplet = new byte[4];
int q = 0;
// convert each quadruplet to three bytes.
for (i = 0; i < len; i++) {
char ch = text.charAt(i);
byte v = decodeMap[ch];
if (v != -1) {
quadruplet[q++] = v;
}
if (q == 4) {
// quadruplet is now filled.
out[o++] = (byte) ((quadruplet[0] << 2) | (quadruplet[1] >> 4));
if (quadruplet[2] != PADDING) {
out[o++] = (byte) ((quadruplet[1] << 4) | (quadruplet[2] >> 2));
}
if (quadruplet[3] != PADDING) {
out[o++] = (byte) ((quadruplet[2] << 6) | (quadruplet[3]));
}
q = 0;
}
}
if (buflen == o) // speculation worked out to be OK
{
return out;
}
// we overestimated, so need to create a new buffer
byte[] nb = new byte[o];
System.arraycopy(out, 0, nb, 0, o);
return nb;
}
/**
* computes the length of binary data speculatively.
*
*
* Our requirement is to create byte[] of the exact length to store the binary data.
* If we do this in a straight-forward way, it takes two passes over the data.
* Experiments show that this is a non-trivial overhead (35% or so is spent on
* the first pass in calculating the length.)
*
*
* So the approach here is that we compute the length speculatively, without looking
* at the whole contents. The obtained speculative value is never less than the
* actual length of the binary data, but it may be bigger. So if the speculation
* goes wrong, we'll pay the cost of reallocation and buffer copying.
*
*
* If the base64 text is tightly packed with no indentation nor illegal char
* (like what most web services produce), then the speculation of this method
* will be correct, so we get the performance benefit.
*/
private static int guessLength(String text) {
final int len = text.length();
// compute the tail '=' chars
int j = len - 1;
for (; j > -1; j--) {
byte code = decodeMap[text.charAt(j)];
if (code != PADDING) {
if (code == -1) // most likely this base64 text is indented. go with the upper bound
{
return text.length() / 4 * 3;
}
break;
}
}
j++; // text.charAt(j) is now at some base64 char, so +1 to make it the size
int padSize = len - j;
if (padSize > 2) // something is wrong with base64. be safe and go with the upper bound
{
return text.length() / 4 * 3;
}
// so far this base64 looks like it's unindented tightly packed base64.
// take a chance and create an array with the expected size
return text.length() / 4 * 3 - padSize;
}
private static byte[] initDecodeMap() {
byte[] map = new byte[128];
int i;
for (i = 0; i < 128; i++) {
map[i] = -1;
}
for (i = 'A'; i <= 'Z'; i++) {
map[i] = (byte) (i - 'A');
}
for (i = 'a'; i <= 'z'; i++) {
map[i] = (byte) (i - 'a' + 26);
}
for (i = '0'; i <= '9'; i++) {
map[i] = (byte) (i - '0' + 52);
}
map['+'] = 62;
map['/'] = 63;
map['='] = PADDING;
return map;
}
}