|
<< Previous 1
2 3
4 5
Of course, we need to describe the structure, or specify the grammar for the Java class file in some language. Some might be tempted to use XML for this purpose.
That's not an appealing idea. There's a lot of interest in domain-specific XML files, which is great if your audience is a non-developer or you plan to write a graphical tool to edit the XML. This is not the case here. More importantly, we would have to write code to read the XML grammar and turn it into an object model and do something with that. If we used XML to specify the grammar, we would be defining a language and building a parser to parse the grammar in order to build a parser to parse class files. It seems like too much work for me.
This sounds like a chicken and egg problem. We need a language and a parser for that language. The usual solution is to either bootstrap a parser by hand coding a simpler version, or use an existing parser and leverage someone else's hard work. Fortunately, there's a free, high quality parser that we are all familiar with:
javac.
javac is the Java compiler which accepts the Java language as source code and emits binary class files as output. Now you're thinking we are back to where started. Not exactly. Instead of hand coding every low-level read, we could use a class model to drive the parse. In essence, the class model is the grammar. This approach has a couple of advantages:
- We specify the grammar using a familiar language (Java)
- There's a variety of developer tools for editing Java.
The Eclipse
project offers an excellent graphical tool exists for editing Java language source
code. Because Eclipse does a great job of validating the syntax and semantics of Java, development time is reduced.
We could do something similar with XML schema and
XML Spy, but that's more work.
How do we extract information from a class model? Simple: use the Java
Reflection API. With reflection, we can get all of the information we need.
Here's a simple example. Suppose we wanted to read the first few primitives from
a class file. We could define an array of Class objects representing the data
like this
Class[] header = {
Integer.TYPE, // magic
Short.TYPE, // minor_version
Short.TYPE // major_version
};
You will notice that the data in the class file is unsigned and the Java types are signed, but that's not a big deal, no bits are lost. We can deal with signs later. Here's a snippet of code that reads binary data from a stream given the array above as an argument:
Object parse(Class[] classes) {
Object[] array = new Object[classes.length];
for (int i=0; i<classes.length; i+=1) {
Class clazz = classes[i];
if (clazz == Byte.TYPE) {
array[i] = new Byte((byte) readBytes(1));
} else if (clazz == Short.TYPE) {
array[i] = new Short((short) readBytes(2));
} else if (clazz == Integer.TYPE) {
array[i] = new Integer((int) readBytes(4));
} else if (clazz == Long.TYPE) {
array[i] = new Long(readBytes(8));
} else if (clazz == Float.TYPE) {
array[i] = new Float(Float.intBitsToFloat((int) readBytes(4)));
} else if (clazz == Double.TYPE) {
array[i] = new Double(Double.longBitsToDouble(readBytes(8)));
}
throw new IllegalArgumentException("Unsupported primitive " + clazz);
}
return array;
}
Next: Processing primitives and
aggregates
1
2 3
4 5
Next>>
|