今書いてるプログラムでBase32エンコードされた文字列をデコードをする必要があったんですが、Java標準ライブラリにそういうクラスは無く、Apache Commonsで提供されているみたいですが、練習を兼ねて車輪の再発明をしました。
エンコードの仕組みについてはこちらのサイトを参考にさせて頂きました。
base32 についてメモ
ここで書かれていることの逆を実装していきます。
作ったクラスのコードはこちら
https://github.com/yoshi-self/Base32Decoder/blob/master/com/yoshi_self/Base32Decoder.java
以下解説
Base32エンコードは40bit(5byte)単位で8文字に変換される
なので8文字単位でデコードしていき最後につなげます。
// エンコード文字列を8で割ってを5を掛けたサイズのバッファを作る
int bufferSize = this.source.length / this.DECODE_UNIT * this.ENCODE_UNIT;
ByteBuffer resultBuffer = ByteBuffer.allocate(bufferSize);
// 8byteごとにデコードしてバッファに入れる
for(this.pos = 0; this.pos < this.source.length; this.pos += this.DECODE_UNIT) {
byte[] unit = decodeUnit();
resultBuffer.put(unit);
}
40bit(8byte)をデコード
40bitのBitSetを作成します
int bitSize = this.DECODE_UNIT * this.ENCODE_UNIT;
BitSet bitSet = new BitSet(bitSize);
ABCDEFGHIJKLMNOPQRSTUVWXYZ234567の配列を作成しておき、1バイトごとに対応する数値を取ります。
byte b = this.source[i];
int n = TABLE.indexOf(b);
この数値の下位5ビットをBitSetにセットします。
for(int j = 0; j < this.ENCODE_UNIT; ++j) {
if( ( (n >>> (this.ENCODE_UNIT - 1 - j)) & 1 ) != 0) {
bitSet.set(bitPos + j);
}
}
5byteのbyte[]を作り、BitSetと同じ並びにビットをセットします。(BitSet.toByteArray()を使うと順番がおかしくなります)
byte[] unitBytes = new byte[this.ENCODE_UNIT];
for(int i = 0; i < bitSize; ++i) {
int bit = bitSet.get(i) ? 1 : 0;
unitBytes[i/this.DECODE_UNIT] |= (bit << (this.DECODE_UNIT -1 - i % this.DECODE_UNIT));
}
return unitBytes;
結果を返す
最初charsetとか考えず5の倍数バイト全部返してて、それじゃダメだと気づいて0のバイト捨てるように直したんですが、更に考えてみると、オリジナルの文字コードが何バイトで意味をもつかわからないので結局charsetを指定させる実装にしました。
String result = new String(resultBuffer.array(), charset);
// find last index not null and return until it
int lastIdx = result.length() - 1;
for(int i = lastIdx; i >= 1; --i) {
if(result.charAt(i) == '\000') {
lastIdx = i - 1;
}
else {
break;
}
}
return result.substring(0, lastIdx + 1);
っていうかこのブログのコード表示ブロックの記号がエスケープされたままになってる…WordPressが自動エスケープしたあとpreとかで出てるからだろうか…そのうち直します。。
コメント