Java における文字列とバイナリ列の相互変換についてと OAuth のパーセントエンコードの方法

Java で文字列を扱うのはあまり慣れておらず、文字列をパーセントエンコードするのにちょっとてこずったので軽くメモを。

文字列 (String オブジェクト) とバイナリ列 (byte 型配列) の相互変換

Java において、文字列を任意のエンコーディングエンコードしてバイナリ列を得るには、String#getBytes( Charset ) メソッド を使用します。

// "あいうえお" という文字列が UTF-8 エンコードされたバイナリ列
byte[] encodedStr = "あいうえお".getBytes( Charset.forName( "UTF-8" ) );

また、バイナリ列 (byte 型配列) を文字列に戻す場合には、String クラスのコンストラクタ String( byte[], Charset ) を使用します。

String str = new String( encodedStr, Charset.forName( "UTF-8" ) );

パーセントエンコード (The OAuth 1.0 Protocol 仕様)

The OAuth 1.0 Protocol (RFC5849) の仕様に合うように、文字列をパーセントエンコードします。 パーセントエンコードというのは、URL エンコードに用いられるように、エスケープすべき文字がある場合に、そのバイナリ列を 1 バイト毎に '%' + XX (XX は、バイト値を 16 進数表示したもの) の形式で表現するものです。

文字列をパーセントエンコードするには、まず文字列をエンコードしたバイナリ列を取得し、バイナリ列に対して処理を行います。 バイナリ列の取得は、上で述べた方法でできます。 また、バイナリ列に対して処理を行う際、byte 型の可変長配列が欲しいところですが、java.io.ByteArrayOutputStream クラス をその用途に用いることができます。

パーセントエンコードを行う機能を提供するクラスのサンプルコードを以下に示します。

package info.vividcode.oauth;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;

/**
 * The OAuth 1.0 Protocol の仕様に合う形で文字列をパーセントエンコードする
 * 機能を提供するクラス
 * @author nobuoka
 */
public class OAuthEncoder {
    
    /** "0123456789ABCDEF" の ASCII バイト列 */
    static final private byte[] BS = { 48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70 };
    /** 指定のバイトをパーセントエンコードする必要があるかどうかの真理値を格納した配列 
     * (インデックスがバイト値に対応. ただし最上位ビットが 1 のものは含まない) */
    static final private boolean[] NEED_ENCODE = new boolean[ 0x7F ];
    
    // NEED_ENCODING の初期化
    static {
        for( int i = 0; i < NEED_ENCODE.length; i++ ) {
            // a(97)-z(122), A(65)-Z(90), 0(48)-9(57), -(45), .(46), _(95), ~(126)
            if( ( 65 <= i && i <= 90 ) || ( 97 <= i && i <= 122) || 
                    ( 48 <= i && i <= 57 ) || i == 45 || i == 46 || i == 95 || i == 126 ) {
                NEED_ENCODE[i] = false;
            } else {
                NEED_ENCODE[i] = true;
            }
        }
    }
    
    /**
     * can't instantiate from this class
     */
    private OAuthEncoder() {}
    
    /** 
     * The OAuth 1.0 Protocol の仕様に合う形で文字列をパーセントエンコードする.
     * パーセントエンコードの対象になるのは 'A'-'Z', 'a'-'z', '0'-'9', '-', '.', '_', '~' を除く全ての文字である. 
     * @param str パーセントエンコードの対象文字列
     * @return str をパーセントエンコードした文字列
     */
    static public String encode( String str ) {
        String encodedStr = null;
        ByteArrayOutputStream os = null;
        try {
            os = new ByteArrayOutputStream();
            for( byte b : str.getBytes( Charset.forName( "UTF-8" ) ) ) {
                if( b < 0 || NEED_ENCODE[b] ) {
                    // "%"
                    os.write( 37 );
                    // 上の 4 ビット
                    os.write( BS[ ( b >> 4 ) & 0x0F ] );
                    // 下の 4 ビット
                    os.write( BS[ b & 0x0F ] );
                } else {
                    os.write( b );
                }
            }
            encodedStr = os.toString();
        } finally {
            try {
                // close する意味はないが, 一応
                if( os != null ) os.close();
            } catch( IOException err ) {
                err.printStackTrace();
            }
        }
        return encodedStr;
    }
    
}

次のように使うことができます。

String parcentEncodedStr = OAuthEncoder.encode( "あい" ); //=> "%E3%81%82%E3%81%84"