続・Cの構造体をJavaでうまく扱う

試作品を作った。jdk1.6.0_07で動作確認。

例によって元の構造体は下のやつ。

typedef struct {
  int x;
  int y;
} point;

で、Java側のソースコード。まず上記構造体。最初はStructを継承しなければならないような案だったが、POJOで済むようにした。

/**
 * Point.java
 */
package jp.ne.hatena.jappy;

public class Point {

  @Int
  private int x;

  @Int
  private int y;

  public int getX() {
    return x;
  }

  public int getY() {
    return y;
  }
}

次に、いきなり使い方を示すテストコード。

/**
 * StructTest.java
 */
package jp.ne.hatena.jappy;

import static org.junit.Assert.*;

import org.junit.Test;

public class StructTest {

  @Test
  public void testPointNormal() throws Exception {
    Point p = new Point();
    byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04 };
    Struct.load(p, data, 0, 0);
    assertEquals(0x0102, p.getX());
    assertEquals(0x0304, p.getY());
  }
}

Point.javaで使われている@Intが型のマッピングを行うアノテーション

/**
 * Point.java
 */
package jp.ne.hatena.jappy;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.ByteOrder;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Int {

  public static final ByteOrder ENDIAN = ByteOrder.LITTLE_ENDIAN;

  public static final int SIZE = 2;
}

ロード用のクラスは以下のようになった。読み込みや値のセットに失敗したときはInvalidStructExceptionをスローするようにした。この例外クラスは変わったことをしていないのでソース省略。

/**
 * Struct.java
 */
package jp.ne.hatena.jappy;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

public final class Struct {

  public static void load(Object obj, byte[] data, int off, int len)
      throws InvalidStructException {

    Field[] fields = obj.getClass().getDeclaredFields();
    for (Field field : fields) {
      Annotation[] annotations = field.getDeclaredAnnotations();
      for (Annotation annotation : annotations) {
        if (annotation.annotationType().equals(Int.class)) {
          int value = 0;

          DataInputStream bais = null;
          try {
            bais = new DataInputStream(new ByteArrayInputStream(data, off, data.length));
            value = bais.readShort();
          } catch (IOException e) {
            throw new InvalidStructException(e);
          } finally {
            if (bais != null) {
              try {
                bais.close();
              } catch (IOException ignore) {}
             }
          }

          off += Int.SIZE;

          try {
            field.setAccessible(true);
            field.set(obj, value);
          } catch (IllegalAccessException e) {
            throw new InvalidStructException(e);
          }
        }
      }
    }
  }

  public static void load(Object obj, InputStream in)
        throws InvalidStructException {
    throw new UnsupportedOperationException("not yet implemented.");
  }

  private Struct() {}
}

実装にあたって知ったこと。

とりあえずこいつをたたき台に改良していくことにする。
修正点

  • バイト列からの数値読み込みはByteBufferを使う。ついでにエンディアン処理対応。
  • パフォーマンス考慮のためのリファクタリング。上の例だと何度もStruct#load()が呼ばれた場合に毎回リフレクションでガリガリ処理することになってしまう。
  • ポインタの処理。ただアドレスを値として持たせるだけでいい気もするけど、下のようにポインタの指すオブジェクトもロードできるようにならないものか。
  // フィールド定義
  @Pointer
  Object hoge;

  // キャストすれば参照先のオブジェクトが取得できる
  Foo fuga = (Foo)hoge;

というわけで、続く。