Fudge-Proto Java

The Java code generator creates a .java file for each message definition, enumeration and taxonomy it finds in the .proto files - each map to a class. The namespaces map directly to Java packages, and the output files are created in an appropriate directory structure. Anything declared outside of a namespace corresponds to the Java default package. Messages and enumerations defined within an outer message are written as static inner classes.

Java command line

Please refer to the Fudge Proto Command Line for full details on launching the command line .proto compiler from the command line. Java specific options are:

Option Description
-lJava Selects the Java code generator
-Xequals Generate equals methods for value equality of message fields (default is not to)
-XfudgeContext=expression Generates serialization methods that do not require context as a parameter and will use a given global expression specific to the application
-XgitIgnore Write a .gitignore in the target folder(s) listing the generated .java files
-XhashCode Generate hashCode methods hashing the message field values (default is not to)
-XtoString Generate toString methods using the commons-lang ToStringBuilder (default is not to)

The method options apply to all output and can be overridden on a per-message basis using the methods directive.

Ant task

The Java code generator supports Ant based build systems. The Ant task is defined in org.fudgemsg.proto.AntTask, will scan a source folder for .proto files and generate .java files for any that have changed or that do not have a corresponding .java file.

 <taskdef name="fudgeproto" classname="org.fudgemsg.proto.AntTask" classpathref="lib.path.id" />

 <target name="fudge-proto">
  <fudgeproto />
 </target>

Attributes available for the task are:

Attribute Description Default value
destdir Output folder for .java files src
equals Write equals methods in messages true
fieldsMutable Make fields mutable if neither the mutable or readonly modifier is given true
fieldsRequired Make fields required if neither the required or optional modifier is given false
fudgeContext Create serialization methods that do not take a context parameter using this default instead  
gitIgnore Write a .gitignore file with the generated code false
hashCode Write hashCode methods in messages true
rebuildAll Ignore timestamps and always process .proto files false
searchdir Additional paths for .proto files to be read but not generate .java for  
srcdir Source folder for .proto files src
toString Write toString methods in messages false
verbose Write debugging output to stdout false

Typically the .proto files will be in your source tree with other .java files, so the generated ones can be written to the same location. The main javac task in the Ant script can then declare the Fudge proto task a dependency.

Java specific message definitions

Java specific definitions allow custom code to be written to the generated .java files or override global behaviours from the command line or Ant settings. Supported options are:

  • body
  • delegate
  • fudgeContext
  • implements
  • imports
  • methods

Body

Embeds a source code fragment in the body of a .java file. This can be used to include custom attributes or methods, for example:

 message Foo {
   required string name;
   binding Java {
     body <<<CODE
       public int getNameLength () {
         return getName ().length ();
       }
 CODE;
   }
 }

Note that the code will be written within the body of the class definition, so any class names must be fully qualified, or imported using the imports option.

Delegate

Allows the builder design pattern to return a sub-class of the message definition class. The nominated delegate class must be a sub-class of the message definition class that has a visible constructor (from the perspective of the corresponding Builder class) that takes a single parameter of the Builder. For example, this might be to implement more rigorous business logic constraints:

 /* Foo.proto */
 message Foo {
   optional string name;
   binding Java {
     delegate "FooImpl";
   }
 }
 /* FooImpl.java */
 class FooImpl extends Foo {
   /* package */ FooImpl (Foo.Builder builder) {
     super (builder);
     ... attribute checking statements to validate the construction
   }
 }

FudgeContext

Normally the toFudgeMsg method will require a FudgeMessageFactory (or FudgeSerializationContext if it has external references) parameter. A fromFudgeMsg method may also require a FudgeDeserializationContext if the message has external references. Specifying a default context globally (using the command line) or on a per-message basis with this option will produce additional methods that do not take this parameter and use this default instead.

Note: this should be used with caution. Relying on a global context may work well for smaller projects but may cause issues when scaling to larger environments where multiple contexts might be required (e.g. where a node has to communicate with two others, each with different type or taxonomy dictionaries). Care must also be taken to avoid the risk of infinite recursion as the tools for detecting and handling cyclic message or object graphs is contained within the FudgeSerializationContext.

Implements

Adds additional implements interfaces to the generated class declaration. All messages by default will implement java.io.Serializable. A generated message definition class cannot be abstract, so any methods from additional interfaces will need to be defined in a body option if they will not result from the field definitions. It takes a comma separated list. For example:

 message Foo {
   required string name;
   binding Java {
     implements "Comparable<Foo>";
     body <<<CODE
       public int compareTo (Foo o) {
         return getName ().compareTo (o.getName ());
       }
 CODE;
   }
}

Imports

Specifies one or more import directives to be placed at the top of the .java file. This is necessary if classes are referenced in a body section of code. It takes a comma separated list of classes (or packages with the wildcard import). For example:

 message Foo {
   required int n;
   binding Java {
     imports "java.io.InputStream, java.io.IOException";
     body <<<CODE
       public static Foo createFromStream (InputStream is) throws IOException {
         ... method body
       }
 CODE;
   }
 }

Methods

Overrides the settings for message methods equals, hashCode and toString. It is a comma separated list of directives, for example:

 message Foo {
   binding Java {
     methods "no-equals, toString";
   }
 }

The directives available are:

Directive Meaning
equals Generate equals method for this message
no-equals Do not generate equals method for this message
hashCode Generate hashCode method for this message
no-hashCode Do not generate hashCode method for this message
toString Generate toString method for this message
no-toString Do not generate toString method for this message

Code generator patterns

For a field foo within the message, the following elements may be included within the class:

Element Name Notes
field _foo Private internal value of the field. If not declared as mutable in the .proto file, will also be final.
mutator setFoo(foo) For mutable fields only.
accessor getFoo() Returns the value of the field. If the field is repeated, returns an unmodifiable list.

If there are immutable, but optional fields (e.g. a default value), a builder pattern will be used. Otherwise a public constructor will be created which will take each of the required fields in the order they are listed in the .proto file.

When the builder pattern is used, an inner Builder class is defined with a constructor that takes each of the required fields in the order they are listed into the .proto file. Any optional fields can be specified by calling a method on the builder.

For example:

 /* Foo.proto */
 message Foo {
   required string name;
   required string email;
 }

Will not require the builder pattern and create the following (some items omitted for clarity):

 /* Foo.java */
 public class Foo {
   private final String _name;
   private final String _email;
   public Foo (final String name, final String email) {
     _name = name;
     _email = email;
   }
   public String getName () {
     return _name;
   }
   public String getEmail () {
     return _name;
   }
 }

Whereas, if a fields is made optional the builder pattern will be used - for example:

 /* Foo.proto */
 message Foo {
   required string name;
   optional string email;
 }

Will generate (some artifacts have been omitted for clarity):

 /* Foo.java */
public class Foo {
  public static class Builder {
    private final String _name;
    private String _email;
    public Builder (final String name) {
      _name = name;
    }
    public Builder email (final String value) {
      _email = value;
      return this;
    }
    public Foo build () {
      return new Foo (this);
    }
  }
  private final String _name;
  private final String _email;
  protected Foo (final Builder builder) {
    _name = builder._name;
    _email = builder._email;
  }
  public String getName () {
    return _name;
  }
  public String getEmail () {
    return _email;
  }
}

Construction of a Foo object using the builder could look like:

 final Foo.Builder fooBuilder = new Foo.Builder ("my name");
 fooBuilder.email ("my e-mail address");
 final Foo foo = fooBuilder.build ();

Note that the methods on the Builder class to set optional values return a reference to the builder so that they can be chained in a manner similar to the java.lang.StringBuilder class.

Additional public elements in the classes may include:

Element Notes
equals method Only if requested
hashCode method Only if requested
toString method Only if requested
copy constructor Only public if the message has mutable fields so that safe assignments can be made
toFudgeMsg method Converts the object to a FudgeFieldContainer
fromFudgeMsg static method Constructs an object from a FudgeFieldContainer

Further protected elements may exist to support the inheritance mechanism. These should not be called directly.

External classes

An externally defined enumeration or message can be defined using the extern keyword. For example, to declare classes in package com.mycompany.utils as external:

 namespace com.mycompany.utils {
   extern message Foo;
   extern enum Bar;
 }

 message Example {
   required com.mycompany.utils.Foo foo;
   required com.mycompany.utils.Bar bar;
 }

External messages must implement a couple of methods to work correctly with the Fudge Proto generated code.

Enumerations

An enumeration should be declared as Java’s enum type. It will be string encoded in a Fudge field using the value returned by the .name() method. An alternative declaration of an enumeration is any Java class that implements a public Object name() method that returns the value to be placed in a Fudge field.

An encoded field will be returned to a Java object using the deserialization framework. The default decoding behaviour is to call Enum.valueOf with the class and string value if can be converted to a primitive string, or if it is a sub-message use the string value of a field with ordinal 1 to support the form generated by the serialization framework. If the implementing class is not a Java enum it must be registered with the TypeDictionary as a secondary type to define the decoding strategy from the encoded type returned by its name method.

Messages

An external message must be implemented as a class or interface. Conversion to/from Fudge messages will be implemented through the Fudge serialization framework when messages are used as field types. Refer to the documentation for the org.fudgemsg.mapping package for full details.

If an external message will be used as the base for inheritance it must implement a method for serialization:

 protected void toFudgeMsg (FudgeSerializationContext fudgeContext, MutableFudgeFieldContainer message) {
   ... adds any fields from this object to the message
 }

It must also implement constructors for copying and for deserialization:

 protected Foo (Foo source) {
   ... copy any data from source to this object
 }
 protected Foo (FudgeDeserializationContext fudgeContext, FudgeFieldContainer message) {
   ... construct this object from the message
 }

It is likely that base classes will not have these methods if they are salvaged from other projects or part of a supplied package. For example:

 /* OriginalBaseClass.proto */
 class OriginalBaseClass {
   private int _foo;
   public int getFoo () {
     return _foo;
   }
   public void setFoo (int foo) {
     _foo = foo;
   }
 }

Instead of inheriting directly from the above class, creating an intermediate one which implements the required methods and uses the methods and constructor available in the super-class. For example:

 /* IntermediateClass.proto */
 class IntermediateClass extends OriginalBaseClass {
   protected IntermediateClass (IntermediateClass source) {
     super ();
     setFoo (source.getFoo ());
   }
   protected IntermediateClass (FudgeDeserializationContext fudgeContext, FudgeFieldContainer message) {
     super ();
     setFoo (source.getInt ("foo"));
   }
   protected void toFudgeMsg (FudgeSerializationContext fudgeContext, MutableFudgeFieldContainer message) {
     message.add ("foo", getFoo ());
   }
 }

Which then allows:

 /* ExtendsBaseClass.proto */
 message ExtendsBaseClass extends IntermediateClass {
   required int bar;
 }

Note that in this example the serialization contexts are not used but always passed into the constructor and toFudgeMsg methods, but may not be used if the full serialization framework is not needed to encode or decode a Fudge message. This is because the .proto compiler does not currently know enough of the external classes at code generation time to identify the most efficient call. A future version may address this and call versions which just take the message parameter if available.

Return to the home page