CLOVER🍀

That was when it all began.

文字が特定のエンコーディングで変換可能かどうかを確認する

Java 7以前のJDKには、

  • sun.io.ByteToCharConverter
  • sun.io.CharToByteConverter

というクラスがあり、ここから以下のようにConverterを取得することで

        CharToByteConverter ascii = CharToByteConverter.getConverter("ASCII");
        CharToByteConverter jis0201 = CharToByteConverter.getConverter("JIS0201");
        CharToByteConverter jis0208 = CharToByteConverter.getConverter("JIS0208");
        CharToByteConverter ms932 = CharToByteConverter.getConverter("MS932");

charを特定の文字集合エンコーディングで変換可能かどうかを確認できていました。

ascii.canConvert('あ');  // false
jis0201.canConvert('あ');  // false
jis0208.canConvert('あ');  // true
ms932.canConvert('あ');  // true

とはいえ、これ普通にjava.nio.charset.CharsetDecoderでやればよかったですね。困ったらこちらを眺めていることが多かったので、そういえば使ったことありませんでした。

というわけで、試してみましょう。

使用できるエンコーディングは、こちらを参照。

http://docs.oracle.com/javase/jp/7/technotes/guides/intl/encoding.doc.html
http://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html

JIS X 0208とか、普通に指定できたんですね(Charsetの指定は「x-JIS0208」)。知らなかった…。

まずは、CharsetEncoder。

import static org.fest.assertions.api.Assertions.*;

import org.junit.Test;

// Java 8では、もう使用不能
//import sun.io.ByteToCharConverter;
//import sun.io.CharToByteConverter;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;

import java.nio.charset.CoderResult;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;


public class MultiByteConverterTest {
    // ここに、テストコードを書く!!
}

CharsetEncoder。

    @Test
    public void canEncodeTest() {
        CharsetEncoder asciiEncoder =
            StandardCharsets.US_ASCII.newEncoder();
        CharsetEncoder jisX0201Encoder =
            Charset.forName("JIS_X0201").newEncoder();
        CharsetEncoder jisX0208Encoder =
            Charset.forName("x-JIS0208").newEncoder();
        CharsetEncoder win31jEncoder =
            Charset.forName("Windows-31J").newEncoder();

        char alpha = 'A';

        assertThat(asciiEncoder.canEncode(alpha))
            .isEqualTo(true);
        assertThat(jisX0201Encoder.canEncode(alpha))
            .isEqualTo(true);
        assertThat(jisX0208Encoder.canEncode(alpha))
            .isEqualTo(false);
        assertThat(win31jEncoder.canEncode(alpha))
            .isEqualTo(true);


        char hiragana = 'あ';

        assertThat(asciiEncoder.canEncode(hiragana))
            .isEqualTo(false);
        assertThat(jisX0201Encoder.canEncode(hiragana))
            .isEqualTo(false);
        assertThat(jisX0208Encoder.canEncode(hiragana))
            .isEqualTo(true);
        assertThat(win31jEncoder.canEncode(hiragana))
            .isEqualTo(true);


        char gaiji = '①';

        assertThat(asciiEncoder.canEncode(gaiji))
            .isEqualTo(false);
        assertThat(jisX0201Encoder.canEncode(gaiji))
            .isEqualTo(false);
        assertThat(jisX0208Encoder.canEncode(gaiji))
            .isEqualTo(false);
        assertThat(win31jEncoder.canEncode(gaiji))
            .isEqualTo(true);
    }

CharsetEncoder#canEncodeで確認すればよい、と。

これで、文字が特定のエンコーディング文字集合に入っているかどうかは、確認することができますね、と。

CharsetDecoderは、ちょっと意に反した結果に?

    @Test
    public void canDecodeTest() {
        CharsetDecoder asciiDecoder =
            StandardCharsets.US_ASCII.newDecoder();
        CharsetDecoder jisX0201Decoder =
            Charset.forName("JIS_X0201").newDecoder();
        CharsetDecoder jisX0208Decoder =
            Charset.forName("x-JIS0208").newDecoder();
        CharsetDecoder win31jDecoder =
            Charset.forName("Windows-31J").newDecoder();

        byte[] alpha = {(byte)0x41};  // 'A'

        assertThat(asciiDecoder.decode(ByteBuffer.wrap(alpha),
                                       CharBuffer.allocate(1),
                                       true).isError())
            .isEqualTo(false);
        assertThat(jisX0201Decoder.decode(ByteBuffer.wrap(alpha),
                                          CharBuffer.allocate(1),
                                          true).isError())
            .isEqualTo(false);
        assertThat(jisX0208Decoder.decode(ByteBuffer.wrap(alpha),
                                          CharBuffer.allocate(1),
                                          true).isError())
            .isEqualTo(true);
        assertThat(win31jDecoder.decode(ByteBuffer.wrap(alpha),
                                        CharBuffer.allocate(1),
                                        true).isError())
            .isEqualTo(false);

        asciiDecoder.reset();
        jisX0201Decoder.reset();
        jisX0208Decoder.reset();
        win31jDecoder.reset();


        byte[] hiragana = {(byte)0x82, (byte)0xa0};  // 'あ'

        assertThat(asciiDecoder.decode(ByteBuffer.wrap(hiragana),
                                       CharBuffer.allocate(1),
                                       true).isError())
            .isEqualTo(true);
        assertThat(jisX0201Decoder.decode(ByteBuffer.wrap(hiragana),
                                          CharBuffer.allocate(1),
                                          true).isError())
            .isEqualTo(true);
        assertThat(jisX0208Decoder.decode(ByteBuffer.wrap(hiragana),
                                          CharBuffer.allocate(1),
                                          true).isError())
            .isEqualTo(true);
        assertThat(win31jDecoder.decode(ByteBuffer.wrap(hiragana),
                                        CharBuffer.allocate(1),
                                        true).isError())
            .isEqualTo(false);

        asciiDecoder.reset();
        jisX0201Decoder.reset();
        jisX0208Decoder.reset();
        win31jDecoder.reset();


        byte[] gaiji = {(byte)0x87, (byte)0x40};  // '①'

        assertThat(asciiDecoder.decode(ByteBuffer.wrap(gaiji),
                                       CharBuffer.allocate(1),
                                       true).isError())
            .isEqualTo(true);
        assertThat(jisX0201Decoder.decode(ByteBuffer.wrap(gaiji),
                                          CharBuffer.allocate(1),
                                          true).isError())
            .isEqualTo(true);
        assertThat(jisX0208Decoder.decode(ByteBuffer.wrap(gaiji),
                                          CharBuffer.allocate(1),
                                          true).isError())
            .isEqualTo(true);
        assertThat(win31jDecoder.decode(ByteBuffer.wrap(gaiji),
                                        CharBuffer.allocate(1),
                                        true).isError())
            .isEqualTo(false);
    }

Windows-31Jでないと、マルチバイト文字を理解できませんでした。JIS X シリーズは、エンコーディングというよりは、文字集合だからでしょうか…?

バイト配列を文字に変換できるか確認する時は、普通に「Windows-31J」とか「Shift_JIS」とか「UTF-8」などのエンコーディングを指定すべきなんでしょうね。