Generating setters and getters using Java::Geci
In the article , we created very simple hello-world generators to introduce the framework and how to generate generators generally. In this article, we will look at the accessor generator, which is defined in the core module of Java::Geci and which is a commercial grade and not a demo-only generator. Even though the generator is commercial grade, using the services of the framework it has simple code so that it can be represented in an article.
What does an accessor generator
Accessors are setters and getters. When a class has many fields and we want to help encapsulation we declare these fields to be
private and create setters and getters, a pair for each field that can set the value for the field (the setter) and can get the value of the field (the getter). Note that contrary to what many juniors think creating setters and getters is not encapsulation by itself, but it may be a tool to do proper encapsulation. And the same time note that it also may NOT be a tool for proper encapsulation. You can read more about it in “Joshua Bloch: Effective Java 3rd Edition” Item 16.
Read it with a bit of caution though. The book says that it was updated for Java 9. That version of Java contains the module system. The chapter Item 16 does not mention it and even this edition still says to use private members with setters and getters for public classes, which in case of Java 9 may also mean classes in packages that the module does not export.
Many developers argue that setters and getters are inherently evil and a sign of bad design. Don’t make a mistake! They do not advocate to use the raw fields directly. That would even be worse. They argue that you should program with a more object-oriented mindset. In my opinion, they are right and still in my professional practice I have to use a lot of classes maintaining legacy applications using legacy frameworks containing setters, getters, which are needed by the programming tools around the application. Theory is one thing and real life is another. Different integrated development environments and many other tools like generate setters and getters for us unless we forget to execute them when a new field was added.
A setter is a method that has an argument of the same type as the field and returns
void. (A.k.a. does not return any value.) The name of the setter, by convention, is
set and the name of the field with the first letter capitalized. For the field
businessOwner the setter is usually
setBusinessOwner. The setter sets the value of the field to that of the argument of the setter.
The getter is also a method which does not have any argument but returns the argument value and hence it has the same return type as the type of the field. The name of the getter, by convention, is
get and again the name of the field capitalized. That way the getter will be
In case of
Boolean type fiels the getter may have the
is prefix, so
isBusinessOwner could also be a valid name in case the field is some boolean type.
An accessor generates setter and getter for all the fields it has to.
How to generate accessors
The accessor generator has to generate code for some of the fields of the class. This generator is the ideal candidate for a filtered field generator in Java::Geci. A filtered field generator extends the
AbstractFilteredFieldsGenerator class and its
process() method is invoked once for each filtered field. The method also gets the
Field as a third parameter in addition to the usual
CompoundParams parameter that we already saw in the article a few weeks ago.
AbstractFilteredFieldsGenerator uses the configuration parameter
filter to filter the fields. That way the selection of which field to take into account is the same for each generator that extends this class and the generators should not care about field filtering: it is done for them.
The major part of the code of the generator is the following:
The code at the place of the ellipsis contains some more methods, which we will look at later. The first call is to get the parameter
id. This is a special parameter and in case it is not defined then default
params.get("id") returns is the mnemonic of the generator. This is the only parameter that has such a global default value.
The call to
source.init(id) ensures that the segment will be treated as “touched” even if the generator does not write anything to that segment. It may happen in some cases and when writing a generator it never hurts calling
source.init(id) for any segment that the generator intends to write into.
The code looks at the actual field to check if the field is final. If the field is final then it has to get the value by the time the object is created and after that, no setter can modify it. In this case, only a getter will be created for the field.
The next thing the setter/getter generator needs is the name of the field and also the string representation of the type of the field. The static utility method
GeciReflectionTools.typeAsString() is a convenience tool in the framework that provides just that.
The optional configuration parameter
access will get into the variable of the same name and it will be used in case the access modifier of the setter and the getter needs to be different from
public. The default is
public and this is defined as the second argument to the method
params.get(). The method
check() is part of the generator. It checks that the modifier is correct and prevents in most cases generation of syntax errored code (e.g.: creating setters and getter with access modifier
pritected). We will look at that method in a while.
The next thing is the name of the getter and the setter. By default is
set/get+ capitalized name of the field, but it can also be defined by the configuration parameter
getter. That way you can have
isBusinessOwner if that is an absolute need.
The last configuration parameter is the key
only. If the code specifies
only='getter' then only the setter or only the getter will be generated.
The segment the generator wants to write into is opened in the head of the try-with-resources block and then calls local
writeGetter methods. There are two different methods to open a segment from a source object. One is calling
open(id), the other one if
safeOpen(id). The first method will try to open the segment and if the segment with the name is not defined in the class source file then the method will return
null. The generator can check the nullity and it has the possibility to use a different segment name if it is programmed so. On the other hand
safeOpen() throws a
GeciException if the segment cannot be opened. This is the safer version to avoid later null pointer exceptions in the generator. Not nice.
Note that the setter is only written if the field is not final and if the
only configuration key was NOT configured to be
Let’s have a look at these two methods. After all, these are the real core methods of the generators that do actually generate code.
The methods get the name of the field, the name of the accessor, the type of the field as a string, the access modifier string and the
Segment the code has to be written into. The code generators do not write directly into the source files. The segment object provided by the framework is used to send the generated code and the framework inserts the written lines into the source code if that is needed.
write_r() methods of the segment can be used to write code. They work very much like
String.format if there are more than one parameters, but they also care about the proper tabulating. When the code invokes
write_r() then the segment will remember that the lines following it have to be tabulated four spaces to the right more. When the code calls
write_l() then the segment knows that the tabulation has to be decreased by four characters (even for the actual written line). They also handle multi-line strings so that they all will be properly tabulated.
Generated code should also be readable.
The final non-trivial method is the access modifier check.
The purpose of this check is to protect the programmer from mistyping the access modifier. It checks that the access modifier is either
private (I do not see a real use case for this one though),
package. The last one is converted to an empty string, as the package protected access is the default for class methods. The same time using the empty string in the configuration to denote package private access is not really readable.
That way if the code is configured
pritected including a typo the code generator will throw an exception and refuses to generate code that is known to contain syntax error. On the other hand, the access modifier can also be more complex. In some rare cases, the program may need synchronized getters and setters. We do not try to figure out automatically anything like that checking if the field is volatile or such, because these are border cases. However, the generator provides a possibility to overcome the limited syntax checking and that way just to provide any string as access modifier. If the access modifier string ends with an exclamation mark then it means the programmer using the generator takes full responsibility for the correctness of the access modifier and the generator will use it as it is (without the exclamation mark of course).
What is left are the methods
mnemonic() is used by the framework to identify the sources that need the service of this generator and also to use it as a default value for the configuration parameter
id. All generators should provide this. The other one is
cap that capitalizes a string. I will not explain how it works.
The class is annotated with the
Geci annotation. The parameters is
accessor filter='private | protected' that defines the name of the generator to be used on this source file and configures the filter. It says that we need setters and getters for the fields that are private and protected. The logical expression should be read: “filter the field is it is private or protected”.
Some of the fields are also annotated.
birnen will get only a setter,
truth setter and getter will be package protected and the getter will be named
isTrue(). The field
not_this will not get a setter or getter because the filter expression is overridden in the field annotation and it says:
false that will never be
true, which is needed to be processed by the generator.
apple is not annotated and will be processed according to the class level configuration. It is private therefore it will get accessor and because it is
final it will get only a getter.
The code between the
will contain the generated code. (You have to run the code to see it, I did not copy it here.)
In this article, we looked at a generator, which is a real life, commercial grade generator in the Java::Geci framework. Walking through the code we discussed how the code works, but also some other, more general aspects of writing code generators. The next step is to start a project using Java::Geci as a test dependency, use the accessor generator instead of the IDE code generator (which lets you forget to re-execute the setter getter generation) and later, perhaps you can create your own generators for even more complex tasks than just setters and getters.
Published on Java Code Geeks with permission by Peter Verhas, partner at our JCG program. See the original article here: Generating setters and getters using Java::Geci
Opinions expressed by Java Code Geeks contributors are their own.
The author and/or editor should do a better job of proofreading. As for the content, why bother? A keystroke in a modern IDE will generate accessors and the bloatware in this article is avoided.
Java::Geci is a code generation framework. The accessor generator is an example how to create a generator. Evidently the example should be a simple one. Setters and getters are also supported by IDEs so long as long you forget to run them after a needing change. Currently I know no other framework that, for example, would generate fluent API from fluent grammar definition. An article about that would be way too complex for readers who even struggle to comprehend the framework structure and aim and who think it is simply another setter/getter generator. I appreciate your concerns. You should not… Read more »