4

I'm using protobuf and I'm generating JAVA classes from the following proto file.

syntax = "proto3";
enum Greeting {
    NONE = 0;
    MR = 1;
    MRS = 2;
    MISS = 3;
}

message Hello {
    Greeting greeting = 1;
    string name = 2;
}

message Bye {
    string name = 1;
}

option java_multiple_files = true;

Now I need to add some code to the generated files and I found that is possible using a custom plugin (https://developers.google.com/protocol-buffers/docs/reference/java-generated#plugins). I'm trying to generate that plugin in Java, something like this.

public class Test {
   PluginProtos.CodeGeneratorResponse.getDefaultInstance();
   /* Code to get generated files from java_out and use the insertion points */
   codeGeneratorResponse.writeTo(System.out);
}

And then I run

protoc --java_out=./classes --plugin=protoc-gen-demo=my-plugin --demo_out=. example.proto

The problem is that on my Test.java main method I don't know how to get access to the files created by the option --java_out so that I can use their insertion points. Currently the CodeGeneratorResponse for the default instance is empty (no files).

Does anybody know how can I get the CodeGeneratorResponse from the --java_out so that I can add more code to the generated classes?

Thanks in advance.

Alberto
  • 63
  • 1
  • 8

3 Answers3

8

I recently struggled with this as well and wasn't able to find a good answer. I finally figured it out after staring at the comments within the CodeGeneratorResponse message for a while.

What threw me off at first was that I was thinking of plugins as a pipeline, where the output from one feeds into the next. However, each plugin gets the exact same input (the parsed .proto files expressed via CodeGeneratorRequest messages), and all the generated code from the plugins (including the built-in ones) gets combined into the output file. However, plugins may modify the output from the previous plugins, which is what insertion points are designed for.

Specifically to your question, you would add a file to the response with the name field getting set to the name of the generated Java file, the insertion_point field getting set to the name of the insertion point at which you want to add code, and the content field getting set to the code you want inserted at that point.

I found this article helpful in creating a simple plugin (in this case in python). As a simple test, I modified the generate_code function from that article to look like this:

def generate_code(request, response):
    for proto_file in request.proto_file:
        f = response.file.add()
        f.name = "Test.java"
        f.insertion_point = "outer_class_scope"
        f.content = "// Inserting a comment as a test"

Then I ran protoc with the plugin:

$ cat test.proto
syntax = "proto3";
message MyMsg {
    int32 num = 1;
}
$ protoc --plugin=protoc-gen-sample=sample_proto_gen.py --java_out=. --sample_out=. test.proto
$ tail -n3 Test.java
  // Inserting a comment as a test
  // @@protoc_insertion_point(outer_class_scope)
}

Your plugin just needs to be some executable which reads a CodeGeneratorRequest message from stdin and writes a CodeGeneratorResponse message to stdout, so could certainly be written in Java instead. I just chose python as I'm generally more comfortable with it and found this simple example.

As a reference, here's a plugin I wrote for generating code based on custom protobuf options.

bgomberg
  • 1,153
  • 1
  • 9
  • 9
  • 2
    Wow. This is the hard-to-find information. I thought the plugin chains working as pipeline as well. Thx! – B.Z. Sep 01 '20 at 15:43
  • This was a missing piece for me trying to understand how all this plugin stuff works. Props to you. – NoodleCollie Feb 02 '22 at 15:00
0

I have made a custom python plugin. To run my plugin i use the command below:

protoc --plugin=protoc-gen-custom=my_plugin_executable_file --custom_out=./build test.proto

So i think that, you have to generate an executable file from your .java file and use it in your command.

Hoai Nguyen
  • 51
  • 1
  • 2
0

Not clear what type of java code you want to add,
but according to the protobuf documentation , messages are only immutable data holders,

If you want to add richer behavior to a generated class, the best way to do this is to wrap the generated protocol buffer class in an application-specific class.

That is, create a decorator class with an instance of the message, and add there your own java code, using the MyMessage.Builder eventually to create a new instance of a modified message.

public class MyMessageDecorator {

    private MyMessage message;
    
    public MyMessageDecorator(MyMessage message) {
        super();
        this.mmessage=messsage;
    }

    //access to the message
    public MyMessage getMessage() {
        return message;
    }

    //add behaviour
    public <result> addBehaviour(<params>) {
        //my java code here
        ...
    }

}

Bruno F
  • 31
  • 3