Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Version History

« Previous Version 2 Next »

One of the banes of Java is the abomination we call properties: the sad convention that gives us getters, setters and strings to access them. Using strings to access properties is a disaster because with one fell sweep all that work that the compiler does to check everything is useless: using a string means all errors will occur at runtime. And forget about refactoring: renaming properties is an exercise in frustration as even with the high level of support for refactoring in IDE's property references in strings will still bring your code down.

Languages like Kotlin do have properties and -more important- they have property references. These are typeful entities that are fully checked during compilation, and these are fully visible to the IDE too, so renames will work without issues. But all is not yet well: Kotlin property support still lacks some important things:

  • Kotlin property references only work for classes made in Kotlin. A class defined in Java has no property references. This means that in this respect here is no interoperability at all: to use property references the classes used must be written in Kotlin.
  • You can reference a single property but there is no nice way to reference a property path: something like Invoice::debtor::address::city or similar. This makes it hard to have path like structures. There are a few workarounds but most of them suck.

Since our Java "architects" are still busy with botching the things C# did well years ago we can probably forget about ever getting something reasonable, so we need a workaround.

Many frameworks have the above problem, so there are many solutions. The JPA 2.0 Criteria API has a specification for a domain class processor where an annotation processor is used to generate typed properties for all Entity classes in the JPA model. Libraries like queryDSL have a similar approach where a processor or a Maven plugin generates special classes for your entities, allowing you to write SQL in a reasonably intuitive and typesafe way. The problem with all of these is that they service a very specific area of the problem: they are defined for database entities. But any Java class that is part of a model can need typed properties, not just database related classes. So we need something more "generic".

Enter the typed properties annotation processor.

Because having just one wheel is not that useful: let's invent yet another one. The DomUI property-annotations-processor is an annotation processor which will create special classes for each class that we use in a model. It generates only classes with typeful properties; it does not add "natural query" ability or whatever.

To use the annotations processor in Maven you need to add the annotations processor to your pom, and you need to add it to each project that has classes that you want to have typeful properties for. Add the following to the project's POM if you have a single project:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler-plugin.version}</version>
            <configuration>
                <annotationProcessors>
                    <annotationProcessor>db.annotationprocessing.PropertyAnnotationProcessor</annotationProcessor>
                </annotationProcessors>
                <annotationProcessorPaths>
                    <dependency>
                        <groupId>to.etc.domui</groupId>
                        <artifactId>property-annotations-processor</artifactId>
                        <version>1.2-SNAPSHOT</version>
                    </dependency>
                </annotationProcessorPaths>
            </configuration>

            <dependencies>
                <dependency>
                    <groupId>to.etc.domui</groupId>
                    <artifactId>property-annotations-processor</artifactId>
                    <version>1.2-SNAPSHOT</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

This assumes that you already have a parent/upper POM which defines the rest of the maven-compiler-plugin's configuration.

If you have a project that consists of multiple modules then you should add the above configuration to the existing maven-compiler-plugin definition inside your parent/root POM, so that all projects get the configuration automatically. In such a case the config might also be present in a pluginManagement section.

One warning: if you use the Eclipse compiler to compile your code in Maven then you need to make sure that you use at least version 2.8.4 of the plexus-compiler-eclipse plugin (to be released) because any version before that does not support annotation processors.


Using the annotations processor with IntelliJ and Maven

The above configuration is usually enough for IntelliJ to pick up what it needs to support annotation processing. But if you have trouble with the processor not being found or classes not being generated you can try to add the processor as a dependency to the projects that need it. This ensures that the processor is on the classpath when IntelliJ needs it. You should also check IntelliJ's annotation processor settings which can be found at:

Settings → Build, Execution, Deployment → Compiler → Annotation Processors

IntelliJ should be able to properly read the configuration from Maven's POMs but if not use the screen to properly define how it is to run the processors. An example (for DomUI's demo application) looks as follows:

With this configuration every build should rebuild any annotated classes where needed.

How to generate properties for classes

The annotation processor will by default generate typeful properties for all classes that:

  • Are annotated with @javax.persistence.Entity
  • Are annotated with @to.etc.annotations.GenerateProperties

The latter annotation can be found in the DomUI project:

<dependency>
  <groupId>to.etc</groupId>
  <artifactId>common</artifactId>
  <version>1.2-SNAPSHOT</version>
</dependency>

For any class annotated like this the processor will generate a set of classes that are used to make typeful properties and typeful property paths. All classes are generated in the special location where annotation processors generate code; when using Maven this is usually target/annotations/xxxx, where xxxx represents the same package that the original class has. So for a class to.etc.domui.derbydata.db.Artist the processor will generate the class to.etc.domui.derbydata.db.Artist_ as the class holding the property references for the Artist class. This class-with-underscore is the only class that is directly used. There are more classes generated but these are used to be able to properly link classes and properties for paths.

What is generated

For each class that is annotated the generator will first detect all properties. It then depends on the type of the property whether it is generated:

  • If the type is a simple type (all primitives and primitive wrappers, BigDecimal, BigInteger, String, all enums, java.util.Date) the property is always generated
  • If the type is a class that is itself also annotated with either Entity or GenerateProperties then the property is generated.
  • If the type is a Collection type where the contained type is annotated with Entity or GenerateProperties the property is generated.

These rules ensure that not the whole world gets generated as that would be a tad slow and useless.

To prevent a property from being generated you can add the @IgnoreGeneration annotation to its getter method. Do not add it to the field: modelwise properties have no fields, so adding things there is an abomination and causes yet another heap of horrible problems.

Using typeful properties in code

You should generate typed properties for all classes that have properties that are used in your code with either QCriteria queries or DomUI data binding. This means that at least the following classes should be generated:

  • All Entity classes used by JPA/Hibernate
  • All model classes used in (DomUI) views


The properties generator is a recent addition, and updating DomUI's code base to use them is a work in progress.

Typed properties can be used on every location where you would normally use a string containing a property path. For instance the following code:

private RowRenderer<Line> createRowRenderer() {
   RowRenderer<Line> rr = new RowRenderer<>(Line.class);
   rr.column().label("Period from").renderer(createMonthRenderer("from"));
   rr.column().label("Period till").renderer(createMonthRenderer("till"));
   rr.column("amountType").editable().factory(createAmountTypeControlFactory());
   rr.column("percentage").editable().factory(createPercentageControlFactory());
   rr.column("amount").editable().factory(createAmountControlFactory());
   rr.column().label("Divide").renderer(createDivideRenderer());
   if(!model().isReadOnly()) {
      rr.column().renderer(createRemoveRenderer()).width("1%").nowrap();
   }
   return rr;
}

uses strings as property names. It is easily replaced by the equivalent code with typed properties:

private RowRenderer<Line> createRowRenderer() {
   RowRenderer<Line> rr = new RowRenderer<>(Line.class);
   rr.column().label("Period from").renderer(createMonthRenderer(Line_.from()));
   rr.column().label("Period till").renderer(createMonthRenderer(Line_.till()));
   rr.column(Line_.amountType()).editable().factory(createAmountTypeControlFactory());
   rr.column(Line_.percentage()).editable().factory(createPercentageControlFactory());
   rr.column(Line_.amount()).editable().factory(createAmountControlFactory());
   rr.column().label("Divide").renderer(createDivideRenderer());
   if(!model().isReadOnly()) {
      rr.column().renderer(createRemoveRenderer()).width("1%").nowrap();
   }
   return rr;
}

The advantages are plenty: since the properties are typed all parts of the RowRenderer are now typeful. So for instance adding a renderer now shows the proper type instead of ?:

This is invaluable in for instance bindings with conversion, where it is important to be able to know what is converted to what else:

TBD

It is also very useful for refactoring: the moment either the type or the name of a property changes the compiler will immediately signal problems. It is also easy to do impact analysis: just search for occurrences of a property method to see what code would be impacted by a change.




  • No labels