Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Why typed properties?

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

Using typed properties in code

In code you use typed properties at all locations where you would usually use  a string containing some property path. For example:

...

Summary

Typed Properties in DomUI are classes that are generated automatically, using an Annotations Processor, much like the JPA Static Metamodel - but for any Java class that exposes getters and setters.

Once configured you can address properties in code not by a String, like "definition.code", but as a fully typed and compile-time checked path like Fact_.definition().code(). You can create typed properties for any class, not just for database classes. And because the properties are fully typed the code using Typed Properties alsoknows what is the type of a property - allowing the compiler yet again to check more at compile time.

Most DomUI code that works/accepts properties accepts both the "old" String variants but also the typed properties. And there is an IntelliJ Idea plugin which automatically replaces String references to properties to the equivalent typed properties.

Why typed properties?

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

Using typed properties in code

In code you use typed properties at all locations where you would usually use  a string containing some property path. For example:

Code Block
List<Album> query = dc().query(QCriteria.create(Album.class).eq(Album_.artist().name(), "AC/DC"));
System.out.println("Got " + query.size() + " results");

The stanza Album_.artist().name() is a typed property reference. The Album_ class is generated automatically by an annotations processor, for all classes annotated by either @GenerateProperties or @Entity. The result of a property reference is QField<I, V>, where I stands for the class that the property reference comes from (Album in the above case) and V stands for the type of the property itself (which would be String in the above example because the "name" property of the Artist class is of type String.

...

Code Block
<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>
        </plugin>
    <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.

Warning
One warning: if you use the Eclipse compiler to compile your code
</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.

You also need to use at least the following versions of the build plugins specified in your parent/top pom:

PluginVersion
maven-compiler-plugin3.7.0
plexus-compiler-eclipse2.8.4Only when you build your code in Maven using the Eclipse plugin, see this stackoverflow article for details



Warning

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..8.4 of the plexus-compiler-eclipse plugin (to be released) because any version before that does not support annotation processors.

Extra configuration when using IntelliJ

IntelliJ does not really see the Annotations Processor as a "dependency" of the project. So if you are using DomUI as as submodule (so as source code) then IntelliJ will complain that the annotation processor could not be found. This is because no project "depends" on the annotation processor as far as IntelliJ is concerned - so it does not build it.

To fix this just add the annotation processor as a direct dependency of all of the modules that need the thing inside the project that needs it by adding the following fragment to the dependencies section:

Code Block
                    <dependency>
                        <groupId>to.etc.domui</groupId>
                        <artifactId>property-annotations-processor</artifactId>
                        <version>1.2-SNAPSHOT</version>
                    </dependency>



Using the annotations processor with IntelliJ and Maven

...

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

...