Java
Binary

Javaでバイナリファイルを読む1

はじめに

少し前に思いついた方法です。ベストプラクティスではないです。オブジェクト指向が好きなんですのん( ˘ω˘)

基本

ファイルをByteBufferにする

まずInputStreamからbyte[]に取り出します。取り出し方はこちらが参考になります。
それをwrapしてByteBuffer(以下buf)を作ります。

byte[] bin = BinUtil.readAll(stream);
ByteBuffer buf = ByteBuffer.wrap(bin);

bufから4byteを符号ありとして読むには

(long)buf.getInt()

bufから4byteを符号なしとして読むには

Javaには符号の概念はないのです( ˘ω˘)。

(long)buf.getInt () & 0xffffffffL

定義する

  • データ型のinterfaceを定義します。

データ型インターフェース

bufからデータを取ったり入れたりできたりしないといけません。compileとdecompileを用意。
全体のデータサイズ長とオフセット(後述)も必要。

public interface DataTypeInterface<T> {
    Integer getOffset();

    void setOffset(Integer offset);

    Integer getSize();

    void setSize(Integer size);

    void decompile(ByteBuffer buf);

    void compile(ByteBuffer buf);

    /**
     * 子要素を取得する
     *
     * @return 子要素
     */
    T get();

    /**
     * 子要素をセットする
     *
     * @param item 子要素
     */
    void set(T item);
}

単純なデータ構造の場合

内部値がjavaのプリミティブ(StringとかLongとか)で表現できるタイプのもの。

public abstract class SingleDataType<T> implements DataTypeInterface<T>{

    /**
     * 内部値
     */
    private T value;

    /**
     * バイトストリーム内のオブジェクト開始位置
     */
    private Integer offset;

    /**
     * オブジェクトバイトサイズ
     */
    private Integer size;

    @Override
    final public Integer getOffset(){
        return this.offset;
    }

    @Override
    final public void setOffset(Integer offset){
        this.offset=  offset;
    }

    @Override
    final public void setSize(Integer size) {
        this.size = size;
    }

    @Override
    public Integer getSize() {
        return this.size;
    }

    public T get() {
        return this.value;
    }

    public void set(T value) {
        this.value = value;
    }
}

数値用の抽象クラスを作る

数値だけです。

abstract public class NumericDataType<T extends Number> extends SingleDataType<T> {

    @Override
    final public T get() {
        return super.get();
    }

    @Override
    public void set(T val) {
         super.set(val);
    }

    abstract public void setNumber(Number val);

}

LONG

基本でやったlong (゚∀゚)

public class LONG extends NumericDataType<Long> {

    @Override
    public void decompile(ByteBuffer buf) {
        this.setOffset(buf.position());
        this.set((long)buf.getInt());
    }

    @Override
    public void compile(ByteBuffer buf) {
        this.setOffset(buf.position());
        buf.putInt(this.get().intValue());
    }

    public LONG(ByteBuffer buf) {
        this.decompile(buf);
        this.setSize(4);
    }

    public LONG(Long value) {
        this.set(value);
        this.setSize(4);
    }

    /**
     *  値をバリデートしてセットする
     *
     * @param value 値
     * @throws IllegalArgumentException 値の範囲が-2147483648L未満か2147483647Lを超えていた場合
     */
    @Override
    public void set(Long value) throws IllegalArgumentException {
        /*1*/
        if (  value < -2147483648L ||  2147483647L < value) {
            throw new IllegalArgumentException("値の範囲が最大長、最小長を超えています");
        }
        /*2*/
        super.set(value);
    }

    @Override
    public void setNumber(Number val) {
        this.set(val.longValue());
    }
}

複雑なデータ構造の場合

Javaにはstructの概念はないのです( ;ω;)。
アノテーションとリフレクションでなんとかします

@Retention(RetentionPolicy.RUNTIME)
public @interface PROPERTY {
    int order();
}

フィールドをリフレクションで列挙して上記のアノテーションがついたもののみを抽出し、order順に並び替えて、その順番でdecompileします。

public abstract class CompositeDataType implements DataTypeInterface<DataTypeInterface>{

    protected Integer offset;

    @Override
    final public Integer getOffset(){
        return this.offset;
    }

    @Override
    final public void setOffset(Integer offset){
        this.offset=  offset;
    }

    @Override
    public Integer getSize(){
        return this.OrderedProperties()
                .entrySet()
                .stream()
                .mapToInt(item -> item.getValue().getSize())
                .sum();
    }

    @Override
    @Deprecated
    public void setSize(Integer size) {
        throw new RuntimeException("複合オブジェクトのサイズを設定することはできません。");
    }

    @Override
    public void decompile(ByteBuffer buf){
        this.setOffset(buf.position());
        for (Map.Entry<String, DataTypeInterface> item: this.OrderedProperties().entrySet()) {
            try {
                item.getValue().decompile(buf);
            }catch (IllegalArgumentException e){
                System.out.println("キー("+ item.getKey() +")の子要素はスキップされます");
                e.printStackTrace();
            }
        }
    }

    @Override
    public void compile(ByteBuffer buf) {
        this.setOffset(buf.position());
        for (Map.Entry<String, DataTypeInterface> item: this.OrderedProperties().entrySet()) {
            try {
                item.getValue().compile(buf);
            }catch (IllegalArgumentException e){
                System.out.println("キー("+ item.getKey() +")の子要素はスキップされます");
                e.printStackTrace();
            }
        }
    }

    protected Map<String,DataTypeInterface> OrderedProperties() {

        List<Field> fields=new ArrayList<>();
        Class c= this.getClass() ;
        while(c != null){
            fields.addAll(java.util.Arrays.asList(c.getDeclaredFields()));
            c = c.getSuperclass();
        }

        List<Field> paramfields = fields
                .stream()
                .filter(f -> f.getAnnotation(PROPERTY.class) != null)
                .collect(Collectors.toList());

        Collections.sort(paramfields, (Field o1, Field o2) -> {
                    PROPERTY or1 = o1.getAnnotation(PROPERTY.class);
                    PROPERTY or2 = o2.getAnnotation(PROPERTY.class);

                    if (or1 != null && or2 != null) {
                        return or1.order() - or2.order();
                    }
                    return o1.getName().compareTo(o2.getName());
                }
        );

        Map<String, DataTypeInterface> m = new LinkedHashMap<>();

        try {
            for (Field f : paramfields) {
                f.setAccessible(true);
                m.put(f.getName(), (DataTypeInterface) f.get(this));
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("フィールド("+ fields.toString() +"にアクセスできません");
        }

        return m;

    }

    @Override
    public DataTypeInterface get() {
        return null;
    }

    @Override
    public void set(DataTypeInterface item) {
    }
}

なにかのデータ構造があった場合、下記のように使います。

public class Version extends CompositeDataType {

    @PROPERTY(order = 1)
    public LONG version = new LONG();

    @PROPERTY(order = 2)
    public LONG subVersion = new LONG();
}

作ったものは再利用できます。

public class Data1 extends CompositeDataType {

    @PROPERTY(order = 1)
    public Version version = new Version();

    @PROPERTY(order = 2)
    public LONG data1 = new LONG();
}

ここまでが基礎的なもので、2は配列データ、オフセットデータをやります。