This tutorial shows you how you can analyse Java annotations using jQAssistant.
Note
|
This tutorial has been written for version 1.3.0 of jQAssistant. In further versions of jQAssistant some aspects might change. |
1. A Simple Example
In our first example we will use a Cypher query to find all classes
annotated with @Foo
and a second query to find all values
for the name
attribute of the annotation.
At first we will have a look at the source of the @Foo
annotation and its usage in the class Dings`
@Foo
Annotation@Target(ElementType.TYPE)
public @interface Foo {
String name();
}
Dings
class@Foo(name = "dingding")
public class Dings {
}
As we can see @Foo
is a quite simple annotation with only one attribute called
name
. Also Dings
is very straight forward.
1.1. Finding The Usage Of An Annotation
If you would like to find all usages of the annotation @Foo
in
your project you have to find any type
with an annotation of the type you.company.project.anno.Foo
.
The corresponding query in Cypher looks like
the query shown below.
@Foo
MATCH (element:Type:Java)
-[:ANNOTATED_BY]->(annotation:Java:Annotation)
-[:OF_TYPE]->(type:Java {fqn:'you.company.project.anno.Foo'})
RETURN element, annotation, type
The result set of the query will contain three nodes:
a node for the class Dings
,
a node for the annotation and
a node for the type of the annotation.
1.1.1. tl;dr - Why Three Nodes?
In the first moment it might be confusing that the Cypher query operates on three nodes and not two. What’s the reason?
The element
node represents the annotated class and
the type
node is the representation for the
annotation type @Foo
.
As @Foo
is a type it can not take values. So Java
must store the actual value (e.g. name = "dingsbums"
)
somewhere. Here is where the node annotation
comes into
play. This node represents the concrete annotation with
its concrete values found in the code. You can imagine
this nodes as 'instance' of an annotation type.
1.2. Getting The Attribute Values Of An Annotation
In case you would like to retrieve the attribute values of an annotation, you must keep in mind an attribute and its value belongs to the annotation of the annotated element and not to the type of the annotation. The type only defines the possible attributes but the concrete annotation carries the value of an annotation attribute.
For example if you would like to find all values for the
name
attribute of the @Foo
annotation you must extend
the query from the previous Cypher query. jQAssistant
stores each attribute of an annotation as single node
labeled with Value
and linked to the annotation representing
the concrete annotation with a relation labeled with HAS
.
Therefore we have to modify our Cypher query as shown
below.
@Foo
MATCH (element:Type:Java)
-[:ANNOTATED_BY]->(annotation:Java:Annotation)
-[:OF_TYPE]->(type:Java {fqn:'you.company.project.anno.Foo'}),
(annotation)-[:HAS]->(attribute:Value {name:'name'})
RETURN attribute.value
The difference between both queries is that we extended
the second with (annotation)-[:HAS]→(attribute:Value {name:'name'})
.
This expression takes any nodes represented by annotation
and checks if it has a HAS
relation to a node labeled with Value
.
The return
clause only returns the value
attribute
of each matched node.
1.3. Limitations
As jQAssistant scans .class
files and not Java source files
you can only write concepts and constraints for annotations
which are available in the generated byte code by
the Java Compiler.
Java has the @java.lang.annotation.Retention
annotation
which allows the author of an annotation to specify the policy
for retaining annotations.
At the moment exist three retention policies, provided by the
enum java.lang.annotation.RetentionPolicy
:
RUNTIME
|
Annotation is present in the class file and at runtime. Thus available to jQAssistant. |
CLASS
|
Annotation is present in the class file and thus available to jQAssistant. |
SOURCE
|
Annotation is only present in the source file and will be discarded by the compiler. Thus the annotation is not available to jQAssistant. |