17

Due to the limitation on the client JVM, I can not use any of the popular serializers due to the fact that reflection is not supported. I am looking for a tool that perform byte-code manipulation to achieve serialization by injecting writer and reader methods into the already compiled class. I need byte-code manipulation java code to bind it with my code for building process.

I have been doing this by generating code and injecting it into the source code before compiling to use my custom serializer. I would like to avoid this approach as I don't want yo modify the source files in any way.

I am aware of Kryo and other XML and JSON serializers, but they don't match my needs.

Thanks.

DNA
  • 42,007
  • 12
  • 107
  • 146
Juan Garcia
  • 714
  • 6
  • 23
  • Can the downvoter please leave a comment? – Juan Garcia Apr 23 '15 at 11:08
  • 1
    What kind of client vm do you use? Is it GWT? – slartidan May 01 '15 at 06:46
  • I am moving from GWT to TeaVM. – Juan Garcia May 01 '15 at 06:47
  • If you are trying to write serializer for TeaVM, may be you should ask its author by email or at project's google group? – Alexey Andreev May 07 '15 at 17:09
  • @Alexey I hacked into the JavaScript output for the TeaVM compiler and I've got the fields for the object, however it used sort autogenerated names for them and after many compile and try I realized that the fields never followed a predictable pattern: they are neither sorted by the alphabetical order of the fields names nor sorted by the order they are declared or even by the order the getDeclaredField reflection method use. And even if I serialize them in TeaVM I will need to use another implementation for the server JVM. – Juan Garcia May 07 '15 at 17:35
  • @Alexey I am trying to make it portable, so it will work also for Dragome and Bck2Brwsr, Android, any standard JVM or Java ME, and by source code injection in GWT. – Juan Garcia May 07 '15 at 17:37

3 Answers3

8

Try javassist. It's probably the most straightforward bytecode generation library for your particular project.

That's because you'll be able to reuse part of your current code generator as javassist is able to parse some simple forms of java code.

For instance you can do:

CtClass clazz = ...;
CtMethod m = CtNewMethod.make(
    "public void serialize(java.io.DataOutput out) {  out.writeInt(x); }",
     clazz);

If you want to dive deep into bytecode generation you can try asm. Asm will allow you to write bytecode directly but it seems to be an overkill for you type of problem.

Implementation with javassist

Here is the base skeleton to do this with javassist:

Path inputDir = Paths.get("target/classes");
Path outputDir = Paths.get("target/classes2");

ClassPool classPool = new ClassPool(true);
classPool.appendClassPath(inputDir.toString());

// get all class names from a certain directory
String[] classNames = Files.walk(inputDir)
        .filter(p -> (!Files.isDirectory(p)) && p.toString().endsWith(".class"))
        .map(p -> inputDir.relativize(p).toString())
        .map(s -> s.substring(0, s.length() - 6).replace(File.separatorChar, '.'))
        .toArray(size -> new String[size]);

for (String className : classNames) {
    CtClass clazz = classPool.get(className);
    // add further filtering to select the classes you want.

    // ex: "public void serializer(java.io.DataOutput out) { out.writeInt(x); } }"
    String serializerBody = generateSerializer(clazz);
    clazz.addMethod(CtNewMethod.make(serializerBody, clazz));

    // ex: "public void deserializer(java.io.DataInput in) { x = in.readInt(); } }";
    String deserializerBody = generateDeserializer(clazz);
    clazz.addMethod(CtNewMethod.make(deserializerBody, clazz));

    // save the modified class
    clazz.setModifiers(clazz.getModifiers() & ~Modifier.ABSTRACT);
    byte[] bytes = clazz.toBytecode();
    Path outFile = outputDir.resolve(className.replace('.', '/') + ".class");
    Files.createDirectories(outFile.getParent());
    Files.write(outFile, bytes);
}

Dependency: org.javassist:javassist:3.19.0-GA

Daniel Sperry
  • 4,381
  • 4
  • 31
  • 41
  • That's what I have been doing, porting my serializer to Javassist. I am waiting to see if there is a project doing this already, if not this will be the accepted answer. – Juan Garcia May 03 '15 at 06:16
  • It would have been kind of you to mention that in the question. From what you said one can only infer that you were only generating source code. The two answers you've got so far are a testament to that. – Daniel Sperry May 03 '15 at 13:04
  • I didn't mention it because I just found out about Javassist after I have post this question. – Juan Garcia May 03 '15 at 13:29
  • Cool :) BTW, I think you might be into something new here. Can't find any references to someone doing what you're doing in java. I hope you opensource it. – Daniel Sperry May 05 '15 at 00:15
  • Sure I would like to release it at open source when it is complete, but I will not have the time to maintain the documentation. – Juan Garcia May 05 '15 at 08:34
  • If you want to know what my serializer does, it serializes object to an UTF-8 string in a very compact format. It sacrifices processing time for reduced size and also makes a lot of assumptions so it will not be able to deserialize if class change. On the other side, it is a format designed for fast client-server comunication. I am designing my own RPC protocol around it. – Juan Garcia May 05 '15 at 08:44
  • The resulting string is safe to embed on XML or JSON as it does not produce any character that needs to be escaped. It can be in many cases very small in size, as small long or it field are serialized in one single UTF-8 one byte character. This makes it fast cause reduce the network time even if processing takes longer than, say, writing and reading to-from streams. – Juan Garcia May 05 '15 at 08:51
  • I wouldn't have write my own serializer if something like that were already done. GWT did it automatically, but it only works with GTW. Mine can be platform independent and it is compiled statically. If you are interested to know more or you want to collaborate in maintain it as an open source project, please contact me. – Juan Garcia May 05 '15 at 08:56
  • Let's talk :) I couldn't find a way to contact you from your stackoverflow, profile. This is my [github profile](https://github.com/DanielSperry) and I'm reacheable on [gitter](http://gitter.im): – Daniel Sperry May 07 '15 at 22:21
5

You can use my library Byte Buddy for this purpose. Byte Buddy is a byte code manipulation library and it allows you to easily add methods to any existing class. Furthermore, it allows you inject redefined code into a jar file. This way, you could ask Byte Buddy to redefine classes to add the required methods. However, note that adding methods to a class might change its implicit serialization uuid.

If your classes must not be loaded until your application starts up, Byte Buddy allows you to redefine classes without loading them. For this, you can use Byte Buddys type pool.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Your library sounds great. However, I am looking for a serialization library. If I need to implement it myself, Javassist will be better in this case, as it easily let me reuse my own source code serializer and work with class files, which I could plug it to a builder for Eclise – Juan Garcia May 03 '15 at 06:33
1

Some possible alternatives:

  • If you can use scala, this project does compile time serializer generation: https://github.com/scala/pickling
  • This stackoverflow question points out to the possibility of using aspectJ and annotation processing: AspectJ / Generate methods using compile-time reflection
    BTW, your classes don't need to be annotated if your annotation processor claims to process "*" all annotations.
  • Seren (SERialization ENhancer) claims to "enhance your classes so that they are much quicker to serialize." However like many other tools it does that at load time... It might be possible to adapt it to do build time instrumentation, or suggest that to it's creator.
  • leshy, also does runtime instrumentation. Same thing: adapt it to compile time or suggest this to the author.
Community
  • 1
  • 1
Daniel Sperry
  • 4,381
  • 4
  • 31
  • 41
  • I had a look to all of them, sadly I couldn't use any of them. The JVM is an AOT compiler. But it was an usefull answer as now I started to realize that what I need has not been made yet, so that I will continue to work on my serializer. – Juan Garcia May 05 '15 at 08:31