In this article I will describe how you can use Java::Geci generator
Repeated to overcome the Java language shortage that generics cannot be primitive. The example is a suggested extension of the Apache Commons Lang library.
When you copy-paste code you do something wrong. At least that is the perception. You have to create your code structure more generalized so that you can use different parameters instead of similar code many times.
This is not always the case. Sometimes you have to repeat some code because the language you use does not (yet) support the functionality that would be required for the problem.
This is too abstract. Let’s have a look at a specific example and how we can manage it using the
Repeated source generator, which runs inside the Java::Geci framework.
org.apache.commons.lang3.Functions in the Apache Commons Lang library defines an inner interface
FailableFunction. This is a generic interface defined as
This is essentially the same as
Function<I,O>, which converts an
I to an
O but since the interface is failable, it can also throw an exception of type
The new need is to have
itnerfaces for each
<I> primitive values. The problem is that the generics cannot be primitive (yet) in Java, and thus we should separate interfaces for each primitive types, as
This is a lot of very similar methods that could easily be described by a template and then been generated by some code generation tool.
Template handling using Java::Geci
The Java::Geci framework comes with many off-the-shelf generators. One of them is the powerful
Repeated generator, which is exactly for this purpose. If there is a code that has to be repeated with possible parameters then you can define a template, the values and
Repeated will generate the code resolving the template parameters.
Adding dependency to the POM
The first thing we have to do is to add the Java::Geci dependencies to the
pom.xml file. Since Apache Commons Language is still Java 8 based we have to use the Java 8 backport of Java::Geci 1.2.0:
Note that the scope of the dependency is
test. The generator
Repeated can conveniently be used without any Geci annotations that remain in the byte code and thus are compile-time dependencies. As a matter of fact, all of the generators can be used without annotations thus without any compile dependencies that would be an extra dependency for the production. In the case of
Repeated this is even easy to do.
Unit test to run the generator
The second thing we have to do is to create a unit test that will execute the generator. Java::Geci generators run during the unit test phase, so they can access the already compiled code using reflection as well as the actual source code. In case there is any code generated that is different from what was already there in the source file the test will fail and the build process should be executed again. Since generators are (should be) idempotent the test should not fail the second time.
As I experience, this workflow has an effect on the developer behavior, unfortunately. Run the test/ fails, run again! It is a bad cycle. Sometimes I happen to catch myself re-executing the unit tests when it was not a code generator that failed. However, this is how Java::Geci works.
There are articles about the Java::Geci workflow
- Your code is redundant, live with it!
- Keep JavaDoc up-to-date
- Converting Objects to Map and Back
- Reflection Selector Expression
- Generating Getters and Setters using Java::Geci
- Creating a Java::Geci generator
- How to generate Source Code
so I will not repeat here the overall architecture and how its workflow goes.
The unit tests will be the following:
only() configure the framework. This configuration tells the framework to use the source files that are in the main Java
src directory of the project and to use only the file names
"Functions". The call to
register() registers the
Repeated generator instance right before we call
generate() that starts the code generation.
The generator instance itself is created using the built-in builder that lets us configure the generator. In this case, the call to
values() defines the comma-separated list of values with which we want to repeat the template (defined later in the code in a comment). The call to
selector() defines the identifier for this code repeated code. A single source file may contain several templates. Each template can be processed with a different list of values and the result will be inserted into different output segments into the source file. In this case there is only one such code generation template, still, it has to be identified with a name and this name has also to be used in the
editor-fold section which is the placeholder for the generated code.
The actual use of the name of the generator has two effects. One is that it identifies the editor fold segment and the template. The other one is that the framework will see the editor-fold segment with this identifier and it will recognize that this source file needs the attention of this generator. The other possibility would be to add the
@Geci("repeated") annotation to the class.
If the identifier were something else and not
repeated then the source code would not be touched by the generator
Repeated or we would need another segment identified as
repeated, which would not actually be used other than trigger the generator.
The call to
define() defines a
BiConsumer that gets a context reference and an actual value. In this case, the
BiConsumer calculates the capitalized value and puts it into the actual segment parameter set associated with the name
Value. The actual value is associated with the name
value by default and the
BiConsumer passed to the method
define() can define and register other parameters. In this case, it will add new values as
The third thing is to prepare the template and the output segment in the source file.
The output segment preparation is extremely simple. It is only an editor fold:
The generated code will automatically be inserted between the two lines and the editors (Eclipse, IntelliJ or NetBeans) will allow you to close the fold. You do not want to edit this code: it is generated.
The template will look like the following:
The code generator finds the start of the template looking for lines that match the
/* TEMPLATE name format and collect the consecutive lines till the end of the comment.
The template uses the mustache template placeholder format, namely the name of the values enclosed between double braces. Double braces are rare in Java.
When we run the unit test it will generate the code that I already listed at the start of the article. (And after that it will fail of course: source code was modified, compile it again.)
Summary and Takeaway
The most important takeaway and WARNING: source code generation is a tool that aims to amend shortages of the programming language. Do not use code generations to amend a shortage that is not of the language but rather your experience, skill or knowledge about the language. The easy way to code generation is not an excuse to generate unnecessarily redundant code.
Another takeaway is that it is extremely easy to use this generator in Java. The functionality is comparable to the C preprocessor that Java does not have and for good. Use it when it is needed. Even though the setup of the dependencies and the unit test may be a small overhead later the maintainability usually pays this cost back.
Opinions expressed by Java Code Geeks contributors are their own.