Core Java

Handling repeated code automatically

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.

Introduction

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.

The problem

The class org.apache.commons.lang3.Functions in the Apache Commons Lang library defines an inner interface FailableFunction. This is a generic interface defined as

01
02
03
04
05
06
07
08
09
10
@FunctionalInterface
    public interface FailableFunction<I, O, T extends Throwable> {
        /**
         * Apply the function.
         * @param pInput the input for the function
         * @return the result of the function
         * @throws T if the function fails
         */
        O apply(I pInput) throws T;
    }

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 T.

The new need is to have

1
public interface Failable<I>Function<O, T extends Throwable>

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@FunctionalInterface
    public interface FailableCharFunction<O, T extends Throwable> {
        O apply(char pInput) throws T;
    }
    @FunctionalInterface
    public interface FailableByteFunction<O, T extends Throwable> {
        O apply(byte pInput) throws T;
    }
    @FunctionalInterface
    public interface FailableShortFunction<O, T extends Throwable> {
        O apply(short pInput) throws T;
    }
    @FunctionalInterface
    public interface FailableIntFunction<O, T extends Throwable> {
        O apply(int pInput) throws T;
    }
... and so on ...

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:

1
2
3
4
5
6
<dependency>
      <groupId>com.javax1.geci</groupId>
      <artifactId>javageci-core</artifactId>
      <version>1.2.0</version>
      <scope>test</scope>
    </dependency>

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

so I will not repeat here the overall architecture and how its workflow goes.

The unit tests will be the following:

01
02
03
04
05
06
07
08
09
10
11
12
13
@Test
    void generatePrimitiveFailables() throws Exception {
        final Geci geci = new Geci();
        Assertions.assertFalse(geci.source(Source.maven().mainSource())
                .only("Functions")
                .register(Repeated.builder()
                    .values("char,byte,short,int,long,float,double,boolean")
                    .selector("repeated")
                    .define((ctx, s) -> ctx.segment().param("Value", CaseTools.ucase(s)))
                    .build())
                .generate(),
            geci.failed());
    }

The calls source(), register() and 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 @Repeated or @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

01
02
03
04
05
06
07
08
09
10
value       Value
 
char    --> Char
byte    --> Byte
short   --> Short
int     --> Int
long    --> Long
float   --> Float
double  --> Double
boolean --> Boolean

Source Code

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:

1
2
//<editor-fold id="repeated">
    //</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:

1
2
3
4
5
6
/* TEMPLATE repeated
    @FunctionalInterface
    public interface Failable{{Value}}Function<O, T extends Throwable> {
        O apply({{value}} pInput) throws T;
    }
    */

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.

Published on Java Code Geeks with permission by Peter Verhas, partner at our JCG program. See the original article here: Handling repeated code automatically

Opinions expressed by Java Code Geeks contributors are their own.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button