Monday, March 22, 2010

Parsing a Jar for annotated POJOs

How to parse a given jar file for Annotated POJOs without extracting the jar and without loading the classes?

After trying few open source solutions, I got a solution in eclipse newsgroup. See this thread.

Now let me explain step-by-step procedure to identify annotated pojos inside a Jar.

Have these imports in your class:

import org.apache.cxf.common.classloader.ClassLoaderUtils;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.util.IAnnotation;
import org.eclipse.jdt.core.util.IClassFileAttribute;
import org.eclipse.jdt.core.util.IClassFileReader;
import org.eclipse.jdt.core.util.IRuntimeInvisibleAnnotationsAttribute;
import org.eclipse.jdt.core.util.IRuntimeVisibleAnnotationsAttribute;
import org.eclipse.jdt.internal.core.util.ClassFileReader;


Step1: Get the local file system URL of the Jar:

java.util.jar.JarFile jarFile =
new java.util.jar.JarFile( < jarURL.getPath() > );


Step2: Have an ArrayList populated with the required annotations. Eg:

List reqAnnotations = new ArrayList();
reqAnnotations .add("javax.jws.WebService");
reqAnnotations .add("javax.jws.WebMethod");


Step3: Get the list of annotated POJOs inside the Jar




private List findAnnotatedPOJOsInJar (JarFile jarFile){
List annotatedPOJOsList = new ArrayList();
Enumeration jarEntries = jarFile.entries();
while (jarEntries .hasMoreElements()) {
JarEntry jarEntry = jarEntries .nextElement();
if (jarEntry.isDirectory())
continue;
String entryName = entry.getName();
// Ignore files other than
if (!jarEntryName.endsWith(".class"))
continue;

ClassFileReader classReader = (ClassFileReader)
ToolFactory.createDefaultClassFileReader(jarFile
.getName(), jarEntry.getName(), IClassFileReader.ALL);

IClassFileAttribute[] classAttrs = classReader.getAttributes();

IRuntimeVisibleAnnotationsAttribute visibleAnnotations = null;
IRuntimeInvisibleAnnotationsAttribute invisibleAnnotations = null;
List annotationsOfThisClass = new ArrayList();

for (int i = 0; i < classAttrs.length; i++) {
IClassFileAttribute attr = attributes[i];
if (attr instanceof IRuntimeVisibleAnnotationsAttribute) {
visibleAnnotations = (IRuntimeVisibleAnnotationsAttribute) attr;
IAnnotation[] annotations = visibleAnnotations.getAnnotations();
populateAnnotationsList(annotationsOfThisClass , annotations);
} else if (attr instanceof IRuntimeInvisibleAnnotationsAttribute) {
invisibleAnnotations = (IRuntimeInvisibleAnnotationsAttribute) attr;
IAnnotation[] annotations = invisibleAnnotations.getAnnotations();
populateAnnotationsList(annotationsOfThisClass , annotations);
}
}

if (annotationsOfThisClass.isEmpty())
continue;
if (hasRequiredAnnotation(annotationsOfThisClass))
annotatedPOJOsList.add(entry);
}
return annotatedPOJOsList;
}

private void populateAnnotationsList (List annotationsOfThisClass, IAnnotation[] annotations) {
for (int i = 0; i < annotations.length; i++) {
IAnnotation iAnnotation = annotations[i];
char[] typeName = iAnnotation.getTypeName();
String annotation = new String(typeName).substring(1, typeName.length - 1).replace('/', '.');
annotationsOfThisClass.add(annotation);
}
}

private boolean hasRequiredAnnotation (List annotationsOfThisClass) {
for (String string : annotationsOfThisClass) {
if (reqAnnotations.contains(string))
return true;
}
return false;
}


Step4: You now have the List of JarEntries which are the POJOs with annotations.
From this list, you need to extract the class names from each of the entry.
Each entry will be name like com/sample/Abc. But we need the fully qualified class names:


List annotatedClassNames = ArrayList();
for (JarEntry jarEntry : annotatedPOJOsList) {
String entryName = jarEntry.getName();
int lastIndexOfSlash = entryName.lastIndexOf('/');
int indexOfDot = entryName.indexOf('.');
// get the package name of this class file
String packageName = null;
try {
packageName = entryName.substring(0, lastIndexOfSlash).
replace('/', '.');
} catch (StringIndexOutOfBoundsException e) {
// No package
packageName = "";
}
// get the class name
String className = entryName.substring(
lastIndexOfSlash + 1, indexOfDot);

String completeClassName = packageName.equals("") ?
className : packageName + "." + className;
annotatedClassNames.add(completeClassName);
}


So, using JDT's ClassFileReader, we are able to find pojos with annotations without extracting the jar, without loading the classes inside the jar. It is nice right :-).

No comments:

Post a Comment