Tuesday, April 20, 2010

Generating documentation with APT and Maven

Purpose:-
Display the usage of APT to generate traceability matrix for requirement and test case.

This is not a fully implemented feature rather it displays the concepts and the setup.
This assumes basic understanding of APT and maven.

Steps:-
1. Setup project 'TestAnnotation' for annotation processor - change pom, write annotation and process classes
2. Update project 'Test' for annotation processing
3. execute apt

Step 1:

1.1 Create a new maven project 'TestAnnotation' and update the pom dependency to include tools.jar.

<dependency>
<groupid>com.sun</groupid>
<artifactid>tools</artifactid>
<version>1.4.2</version>
<scope>system</scope>
<systempath>${java.home}/../lib/tools.jar</systempath>
</dependency>


1.2 Write the annotation class. This class needs to be shared with the Test project. This can be done by copying the source unto the project or having a common dependency between the projects.

package com.sash;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE,
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.PACKAGE,
ElementType.FIELD})
public @interface TestCaseDetails {
String code();
String description();
String useCase();
}
1.3 Create Annotation Processing Factory

The factory is responsible for creating processors for one or more annotation types. The factory is said to support these types.
package com.sash;

import java.util.Collection;
import java.util.Collections;
import java.util.Set;

import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.AnnotationProcessorFactory;
import com.sun.mirror.apt.AnnotationProcessors;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;

public class TestCaseDetailsProcessorFactory implements
AnnotationProcessorFactory {

public AnnotationProcessor getProcessorFor(
Set <> declarations,
AnnotationProcessorEnvironment env) {
AnnotationProcessor result;
if (declarations.isEmpty()) {
result = AnnotationProcessors.NO_OP;
} else {
result = new TestCaseDetailsProcessor(env);
}
return result;

}

public Collection <> supportedAnnotationTypes() {
return Collections.singletonList(TestCaseDetails.class.getName());
}

public Collection <> supportedOptions() {
return Collections.emptyList();
}
}

1.4 Create the annotation processor

package com.sash;

import java.util.Collection;
import java.util.Map;

import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.AnnotationMirror;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.AnnotationTypeElementDeclaration;
import com.sun.mirror.declaration.AnnotationValue;
import com.sun.mirror.declaration.Declaration;
import com.sun.mirror.util.SourcePosition;

public class TestCaseDetailsProcessor implements AnnotationProcessor {

private AnnotationProcessorEnvironment environment;

private AnnotationTypeDeclaration annotationTypeDeclaration;

public TestCaseDetailsProcessor(AnnotationProcessorEnvironment env) {
System.out.println("TestCaseDetailsProcessor.TestCaseDetailsProcessor()");
environment = env;
annotationTypeDeclaration = (AnnotationTypeDeclaration) environment
.getTypeDeclaration(TestCaseDetails.class.getName());
}

public void process() {
System.out.println("TestCaseDetailsProcessor.process()");
Collection declarations = environment
.getDeclarationsAnnotatedWith(annotationTypeDeclaration);
for (Declaration declaration : declarations) {
processNoteAnnotations(declaration);
}
}

private void processNoteAnnotations(Declaration declaration) {
System.out.println("TestCaseDetailsProcessor.processNoteAnnotations()");
Collection annotations = declaration
.getAnnotationMirrors();
for (AnnotationMirror mirror : annotations) {
if(mirror.getAnnotationType().getDeclaration().equals(
annotationTypeDeclaration)) {

SourcePosition position = mirror.getPosition();
Map values = mirror
.getElementValues();

System.out.println("Declaration: " + declaration.toString());
System.out.println("Position: " + position);
System.out.println("Values:");
for (Map.Entry entry : values
.entrySet()) {
AnnotationTypeElementDeclaration elemDecl = entry.getKey();
AnnotationValue value = entry.getValue();
System.out.println(" " + elemDecl + "=" + value);
}
}
}
}

}


2. Update the pom of project 'Test' for annotation processing to add apt maven plugin.


<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.0-alpha-3</version>
</plugin>


Update the test case to use the annotations.

package com.test; import com.sash.TestCaseDetails;
public class Testing1 {
@Test
@TestCaseDetails(code="1", description="2", useCase="3")
public void testing() {
//actual code goes here
}
}


3. Execute the maven command 'mvn apt:test-process' to execute the command. The following output can be seen:-


Values:
code()="1"
description()="2"
useCase()="3"


The example only shows a print into the console. This should be written into a file with the expected format.

The apt command can also be executed without maven using the following command.


apt \TestAnnotation-1.jar -factory com.sash.TestCaseDetailsProcessorFactory \Testing1.java


Note: The code provided has been written by using articles and documentation available. This is only an aggregation of information available online.

Reference and further reading:
http://www.oracle.com/technology/pub/articles/marx-jse6.html
http://www.javalobby.org/java/forums/t17876.html
http://mojo.codehaus.org/apt-maven-plugin/examples/configuring-a-factory.html