3/23/2012

How to Instantiate Annotation Type and Qualifier

Java annotations are usually used to annotate a language element, to inject dependency, or to provide metadata. They are interface types and cannot be directly instantiated. But sometimes we find it necessary to have instances of annotation types. This post demonstrates how to instantiate annotation types and use qualifier in simple search.

In short, you will need to write an abstract class that implements your annotation type T, and extends javax.enterprise.util.AnnotationLiteral<T> (see ContainsQualifier class below). The client code then can instantiate this abstract class by using anonymous inner class that supplies appropriate values to T (see AnnotationLiteralTest#findBy below).

A somewhat contrived use case: there are bags and baskets that contain items like gold or fruit. So bag and basket are annotated with what they contain. In a warehouse, the staff needs to find all packgages containing gold, all packages containing fruit, etc, based on one or more qualifiers.

This is implemented in the following maven project (project source in github):


pom.xml:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.blogspot.javahowto</groupId>
<artifactId>annotation-literal</artifactId>
<version>1.0</version>

<build>
<defaultGoal>install</defaultGoal>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

<dependencies>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.0-SP4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
The annotation type qualifier: Contains

package com.blogspot.javahowto;

import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Qualifier
@Documented
@Retention(value= RetentionPolicy.RUNTIME)
public @interface Contains {
String value() default "";
}
Bag type containing gold:

package com.blogspot.javahowto;

@Contains("Gold")
public class Bag {
}
Basket type containing fruit:

package com.blogspot.javahowto;

@Contains("Fruit")
public class Basket {
}
Abstract class that implements the qualifier Contains:

package com.blogspot.javahowto;

import javax.enterprise.util.AnnotationLiteral;

public abstract class ContainsQualifier extends AnnotationLiteral<Contains>
implements Contains {}
The qualifier-based search is implemented in CollectionManager:

package com.blogspot.javahowto;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.LinkedList;

public class CollectionManager {
private Collection collection;

public CollectionManager(Collection collection) {
this.collection = collection;
}

/**
* Finds all objects matching all criteria represented by annotations
* @param annotationsToMatch one or multiple search qualifiers
* @return a collection of objects that match all the search qualifiers
*/
public Collection findBy(Annotation... annotationsToMatch) {
Collection result = new LinkedList();
for(Object obj : collection) {
boolean matched = false;
Annotation[] classAnnotations = obj.getClass().getAnnotations();
for(Annotation an : annotationsToMatch) {
if(contains(classAnnotations, an)) {
matched = true;
} else {
matched = false;
break;
}
}
if(matched)
result.add(obj);
}
return result;
}

/**
* Checks if an array of Annotations contains an individual Annotation
* @param annotations an array of annotations
* @param ann an individual annotation
* @return true if ann is equal to at least one of the element in
* annotations array; false otherwise
*/
private boolean contains(Annotation[] annotations, Annotation ann) {
for(Annotation a : annotations) {
if(a.equals(ann)) {
return true;
}
}
return false;
}
}
The JUnit test class:

package com.blogspot.javahowto.test;

import com.blogspot.javahowto.*;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

public class AnnotationLiteralTest {
private static List mixed = new LinkedList();
private static CollectionManager collectionManager;

@BeforeClass
public static void setUp() throws Exception {
for (int i = 0; i < 2; i++) {
mixed.add(new Bag());
mixed.add(new Basket());
}
collectionManager = new CollectionManager(mixed);
}

@Test
public void testContainsGold() throws Exception {
System.out.printf("contains Gold: %s%n%n", findBy("Gold"));
}

@Test
public void testContainsFruit() throws Exception {
System.out.printf("contains Fruit: %s%n%n", findBy("Fruit"));
}

@Test
public void testContainsFruitGold() throws Exception {
System.out.printf("contains Gold and Fruit: %s%n%n", findBy("Gold", "Fruit"));
}

/**
* Finds all objects matching all criteria
* @param criteria one or multiple search qualifiers
* @return a collection of objects that match all the search qualifiers
*/
private Collection findBy(final String... criteria) {
Contains[] qualifiers = new Contains[criteria.length];
for (int i = 0; i < criteria.length; i++) {
final String s = criteria[i];
Contains containsQualifier = new ContainsQualifier() {
@Override
public String value() {
return s;
}
};
qualifiers[i] = containsQualifier;
}
return collectionManager.findBy(qualifiers);
}
}
To build the project and run the JUnit tests:

$ mvn install

Running com.blogspot.javahowto.test.AnnotationLiteralTest
contains Gold: [com.blogspot.javahowto.Bag@7b112783, com.blogspot.javahowto.Bag@23394894]

contains Fruit: [com.blogspot.javahowto.Basket@69198891, com.blogspot.javahowto.Basket@b551d7f]

contains Gold and Fruit: []

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.047 sec
Some points for discussion:

The subclass of ContainsQualifier (the anonymous inner class) represents a strategy in strategy pattern. Traditionally the strategy is an implementation of a common interface. Now this seems another area that annotation can replace the traditional interface.

In this project the search criteria are based on string values, and wrap them as annotation may be overkill. But in more complex cases, annotation offers a very flexible and generic interface for specifying conditions and qualifiers.