在 Preon 中解析可变记录长度

发布于 2024-08-24 05:56:13 字数 4064 浏览 4 评论 0原文

我正在尝试使用 Preon 来解析二进制文件,这些文件被构造为一系列可变长度记录。对于每条记录,都有一个数字指定记录长度(以字节为单位)。

这是我想要做的事情的简化版本:

package test.preon; 

import nl.flotsam.preon.annotation.BoundList; 
import nl.flotsam.preon.annotation.BoundNumber; 
import java.util.List; 

public class BinFile { 
    @BoundNumber(size="16") int numberOfRecords; 
    @BoundList(type=Record.class, size="numberOfRecords") List<Record> records; 

    public int getNumberOfRecords() { 
        return numberOfRecords;
    } 

    public List<Record> getRecords() { 
        return records;
    } 

    public class Record { 
        @BoundNumber(size="16") int recordLength; 
        @BoundList(size="recordLength") byte[] data; 

        public int getRecordLength() { 
            return recordLength; 
        } 

        public byte[] getData() { 
            return data; 
        } 
    } 
}

因此,numberOfRecords 指定文件中的记录数,recordLength 指定每个记录的长度。问题是 Preon 无法解析 Record 中的 recordLength,尽管 numberOfRecords 在 BinFile 中工作正常。

这是我得到的异常:

nl.flotsam.limbo.BindingException: Failed to resolve recordLength on class test.preon.BinFile
at nl.flotsam.preon.codec.BindingsContext$BindingsResolver.get(BindingsContext.java:412)
at nl.flotsam.preon.codec.BindingsContext$BindingReference.resolve(BindingsContext.java:247)
at nl.flotsam.preon.codec.BindingsContext$BindingReference.resolve(BindingsContext.java:189)
at nl.flotsam.limbo.ast.ReferenceNode.eval(ReferenceNode.java:57)
at nl.flotsam.limbo.ast.ArithmeticNode$Operator$5.eval(ArithmeticNode.java:109)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:250)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:33)
at nl.flotsam.limbo.ast.ArithmeticNode$Operator$3.eval(ArithmeticNode.java:83)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:250)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:33)
at nl.flotsam.limbo.ast.ArithmeticNode$Operator$5.eval(ArithmeticNode.java:109)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:250)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:33)
at nl.flotsam.preon.codec.ListCodecFactory$SwitchingListCodec.decode(ListCodecFactory.java:458)
at nl.flotsam.preon.codec.ListCodecFactory$SwitchingListCodec.decode(ListCodecFactory.java:443)
at nl.flotsam.preon.binding.StandardBindingFactory$FieldBinding.load(StandardBindingFactory.java:128)
at nl.flotsam.preon.codec.ObjectCodecFactory$ObjectCodec.decode(ObjectCodecFactory.java:251)
at nl.flotsam.preon.DefaultCodecFactory$DefaultCodec.decode(DefaultCodecFactory.java:173)
at nl.flotsam.preon.Codecs.decode(Codecs.java:218)
at nl.flotsam.preon.Codecs.decode(Codecs.java:199)
    ...

如果我将 size="recordLength" 更改为常量,例如 size="42",我不会得到异常(但当然,记录长度必须始终相同)。

我是否有其他方法可以使记录长度可变,或者我应该以不同的方式组织事物?

如果有人感兴趣,这是我使用过的 JUnit 测试:

package test.preon;

import org.junit.Test;
import static org.junit.Assert.*;
import nl.flotsam.preon.Codecs;
import nl.flotsam.preon.Codec;
import nl.flotsam.preon.DecodingException;
import test.preon.BinFile;
import test.preon.BinFile.Record;
import java.util.List;

public class BinFileTest {

    @Test
    public void parseBinFile() throws DecodingException {
        Codec<BinFile> codec = Codecs.create(BinFile.class);
        byte[] buffer = new byte[] {
                2, 0, 
                3, 0, 
                'a', 'b', 'c',
                4, 0,
                '1', '2', '3', '4'
        };
        BinFile b = Codecs.decode(codec, buffer);

        assertEquals(b.getNumberOfRecords(), 2);

        List<Record> rL = b.getRecords();

        assertEquals(rL.size(), 2);

        Record r0 = rL.get(0);
        assertEquals(r0.getRecordLength(), 3);
        assertEquals(new String(r0.getData()), "abc");

        Record r1 = rL.get(1);
        assertEquals(r1.getRecordLength(), 4);
        assertEquals(new String(r1.getData()), "1234");
    }
}

I'm trying to use Preon to parse binary files, which are structured as a sequence of variable length records. For each record, there's a number which specifies the record length (in bytes).

Here's a simplified version of what I'm trying to do:

package test.preon; 

import nl.flotsam.preon.annotation.BoundList; 
import nl.flotsam.preon.annotation.BoundNumber; 
import java.util.List; 

public class BinFile { 
    @BoundNumber(size="16") int numberOfRecords; 
    @BoundList(type=Record.class, size="numberOfRecords") List<Record> records; 

    public int getNumberOfRecords() { 
        return numberOfRecords;
    } 

    public List<Record> getRecords() { 
        return records;
    } 

    public class Record { 
        @BoundNumber(size="16") int recordLength; 
        @BoundList(size="recordLength") byte[] data; 

        public int getRecordLength() { 
            return recordLength; 
        } 

        public byte[] getData() { 
            return data; 
        } 
    } 
}

So, numberOfRecords specifies the number of records in the file and recordLength specifies the length of each record. The problem is that Preon isn't able to resolve recordLength in Record, although numberOfRecords works fine in BinFile.

Here's the exception I get:

nl.flotsam.limbo.BindingException: Failed to resolve recordLength on class test.preon.BinFile
at nl.flotsam.preon.codec.BindingsContext$BindingsResolver.get(BindingsContext.java:412)
at nl.flotsam.preon.codec.BindingsContext$BindingReference.resolve(BindingsContext.java:247)
at nl.flotsam.preon.codec.BindingsContext$BindingReference.resolve(BindingsContext.java:189)
at nl.flotsam.limbo.ast.ReferenceNode.eval(ReferenceNode.java:57)
at nl.flotsam.limbo.ast.ArithmeticNode$Operator$5.eval(ArithmeticNode.java:109)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:250)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:33)
at nl.flotsam.limbo.ast.ArithmeticNode$Operator$3.eval(ArithmeticNode.java:83)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:250)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:33)
at nl.flotsam.limbo.ast.ArithmeticNode$Operator$5.eval(ArithmeticNode.java:109)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:250)
at nl.flotsam.limbo.ast.ArithmeticNode.eval(ArithmeticNode.java:33)
at nl.flotsam.preon.codec.ListCodecFactory$SwitchingListCodec.decode(ListCodecFactory.java:458)
at nl.flotsam.preon.codec.ListCodecFactory$SwitchingListCodec.decode(ListCodecFactory.java:443)
at nl.flotsam.preon.binding.StandardBindingFactory$FieldBinding.load(StandardBindingFactory.java:128)
at nl.flotsam.preon.codec.ObjectCodecFactory$ObjectCodec.decode(ObjectCodecFactory.java:251)
at nl.flotsam.preon.DefaultCodecFactory$DefaultCodec.decode(DefaultCodecFactory.java:173)
at nl.flotsam.preon.Codecs.decode(Codecs.java:218)
at nl.flotsam.preon.Codecs.decode(Codecs.java:199)
    ...

If I change size="recordLength" to a constant, e.g. size="42", I don't get the exception (but of course, then the record length always has to be the same).

Is there some other way for me to make the record length variable, or should I have organised things differently?

If anybody's interested, here's the JUnit test I've used:

package test.preon;

import org.junit.Test;
import static org.junit.Assert.*;
import nl.flotsam.preon.Codecs;
import nl.flotsam.preon.Codec;
import nl.flotsam.preon.DecodingException;
import test.preon.BinFile;
import test.preon.BinFile.Record;
import java.util.List;

public class BinFileTest {

    @Test
    public void parseBinFile() throws DecodingException {
        Codec<BinFile> codec = Codecs.create(BinFile.class);
        byte[] buffer = new byte[] {
                2, 0, 
                3, 0, 
                'a', 'b', 'c',
                4, 0,
                '1', '2', '3', '4'
        };
        BinFile b = Codecs.decode(codec, buffer);

        assertEquals(b.getNumberOfRecords(), 2);

        List<Record> rL = b.getRecords();

        assertEquals(rL.size(), 2);

        Record r0 = rL.get(0);
        assertEquals(r0.getRecordLength(), 3);
        assertEquals(new String(r0.getData()), "abc");

        Record r1 = rL.get(1);
        assertEquals(r1.getRecordLength(), 4);
        assertEquals(new String(r1.getData()), "1234");
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

素罗衫 2024-08-31 05:56:13

事实证明你遇到了一个错误。 ListCodecFactory 有一个策略来决定在各种情况下生成哪种类型的编解码器,但事实证明在这种情况下它选择了错误的编解码器。我有这个补丁,如果你有兴趣的话我可以发给你。

It turns out you ran into a bug. The ListCodecFactory has a policy for deciding what type of Codec to generate in various circumstances, and it turns out it picks the wrong one in this case. I do have the patch for it, and I can send it to you if you're interested.

吃颗糖壮壮胆 2024-08-31 05:56:13

为其创建了两个错误报告:PREON-16PREON-17。第一个解决了上述问题。第二个解决了一个稍微相关的问题,这里值得一提:

在下面的代码中,名为“records”的列表中 Test2 元素的大小完全由 Test1 定义。 (Test2 的大小基本上是 Test2.value 中的字符数,由 Test1 的“nrCharacters”属性确定。)

因此,Preon 能够优化记录列表的读取。它不必一次读取所有记录;相反,它可以跳过它并仅在需要时读取这些元素。 (元素的起始位置基本上是元素索引的函数。)

然而,困难在于需要在读取 Test2 实例之前计算元素的大小。由于它包含基于 Test2 上下文的引用,因此需要重写这些引用。事实上,整个大小表达式(尽管在本例中是一个简单的表达式)需要重写。这个问题在 PREON-17 中得到了解决。

 public static class Test1 {

        @BoundNumber(size = "8")
        public int nrRecords;

        @BoundNumber(size = "8")
        public int nrCharacters;

        @BoundList(size = "nrRecords", type = Test2.class)
        public List<Test2> records;

        public static class Test2 {

            @BoundString(size = "outer.nrCharacters")
            public String value;

        }

    }

Created two bug reports for it: PREON-16 and PREON-17. The first one solves the problem outlined above. The second one solves a slightly related problem, which is worth mentioning here:

In the code below, the size of a Test2 element in the list called 'records' is defined by Test1 entirely. (The size of Test2 is basically the number of characters in Test2.value, which is determined by the 'nrCharacters' attribute of Test1.)

As a consequence of this, Preon is able to optimize reading the list of records. It doesn't have to read all records all at once; instead it is able to skip over it and read those elements only if needed. (The starting position of an element is basically a function of the elements index.)

The difficulty however is that the size of the element needs to be calculated before an instance of Test2 has been read. Since it contains references that are based on the context of Test2, those references need to be rewritten. In fact, the entire size expression (although in this case a simple one) needs to be rewritten. That is solved in PREON-17.

 public static class Test1 {

        @BoundNumber(size = "8")
        public int nrRecords;

        @BoundNumber(size = "8")
        public int nrCharacters;

        @BoundList(size = "nrRecords", type = Test2.class)
        public List<Test2> records;

        public static class Test2 {

            @BoundString(size = "outer.nrCharacters")
            public String value;

        }

    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文