/**************************************************************************************
* Copyright (c) Jonas Bon�r, Alexandre Vasseur. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the LGPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.annotation;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import org.codehaus.aspectwerkz.annotation.instrumentation.AttributeEnhancer;
import org.codehaus.aspectwerkz.annotation.instrumentation.asm.AsmAttributeEnhancer;
import org.codehaus.aspectwerkz.exception.DefinitionException;
import java.io.File;
import java.io.FileInputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
/**
* <p/>Annotation compiler. <p/>Extracts the annotations from JavaDoc tags and inserts them into the bytecode of the
* class.
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r </a>
* @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur </a>
*/
public class AnnotationC {
public static final String ANNOTATION_ASPECT = "Aspect";
public static final String ANNOTATION_AROUND = "Around";
public static final String ANNOTATION_BEFORE = "Before";
public static final String ANNOTATION_AFTER = "After";
public static final String ANNOTATION_EXPRESSION = "Expression";
public static final String ANNOTATION_IMPLEMENTS = "Implements";
public static final String ANNOTATION_INTRODUCE = "Introduce";
private static final String COMMAND_LINE_OPTION_DASH = "-";
private static final String COMMAND_LINE_OPTION_VERBOSE = "-verbose";
private static final String COMMAND_LINE_OPTION_CUSTOM = "-custom";
private static final String COMMAND_LINE_OPTION_SRC = "-src";
private static final String COMMAND_LINE_OPTION_CLASSES = "-classes";
private static final String COMMAND_LINE_OPTION_DEST = "-dest";
static final String[] SYSTEM_ANNOTATIONS = new String[] {
ANNOTATION_ASPECT,
ANNOTATION_AROUND,
ANNOTATION_BEFORE,
ANNOTATION_AFTER,
ANNOTATION_EXPRESSION,
ANNOTATION_IMPLEMENTS,
ANNOTATION_INTRODUCE
};
/**
* The annotations properties file define by the user.
*/
public static final Properties ANNOTATION_DEFINITION = new Properties();
/**
* Verbose logging.
*/
private static boolean s_verbose = false;
/**
* The class loader.
*/
private static URLClassLoader s_loader;
/**
* The custom annotations.
*/
private static Map s_customAnnotations = new HashMap();
/**
* Runs the compiler from the command line.
*
* @param args
*/
public static void main(String[] args) {
if (args.length < 4) {
printUsage();
}
Map commandLineOptions = parseCommandLineOptions(args);
compile(
(String) commandLineOptions.get(COMMAND_LINE_OPTION_SRC),
(String) commandLineOptions.get(COMMAND_LINE_OPTION_CLASSES),
(String) commandLineOptions.get(COMMAND_LINE_OPTION_DEST),
(String) commandLineOptions.get(COMMAND_LINE_OPTION_CUSTOM));
}
/**
* Compiles attributes for the aspects.
*
* @TODO support multiple source dirs
* @param sourcePath the path to the sources to compile attributes for
* @param classPath the path to the compiled classes matching the source files
* @param destDir the path where to write the compiled aspects (can be NULL)
* @param annotationPropetiesFile the annotation properties file (for custom annotations) (can be NULL)
*/
public static void compile(
final String sourcePath,
final String classPath,
String destDir,
final String annotationPropetiesFile) {
if (sourcePath == null) {
throw new IllegalArgumentException("source path can not be null");
}
if (classPath == null) {
throw new IllegalArgumentException("class path can not be null");
}
if (destDir == null) {
destDir = classPath;
}
try {
s_loader = new URLClassLoader(new URL[] {
new File(classPath).toURL()
}, ClassLoader.getSystemClassLoader());
} catch (MalformedURLException e) {
String message = "URL [" + classPath + "] is not valid: " + e.toString();
logError(message);
throw new DefinitionException(message, e);
}
doCompile(annotationPropetiesFile, classPath, sourcePath, destDir);
}
/**
* Compiles the annotations.
*
* @param classPath
* @param destDir
*/
private static void doCompile(
final String annotationPropetiesFile,
final String classPath,
final String sourcePath,
final String destDir) {
logInfo("compiling annotations...");
logInfo("note: if no output is seen, then nothing is compiled");
// create the annotation manager
final AnnotationManager manager = new AnnotationManager();
manager.addSourceTrees(new String[] {
sourcePath
});
// register annotations
registerSystemAnnotations(manager);
registerUserDefinedAnnotations(manager, annotationPropetiesFile);
// get all the classes
JavaClass[] classes = manager.getAllClasses();
for (int i = 0; i < classes.length; i++) {
JavaClass clazz = classes[i];
logInfo("class [" + clazz.getFullyQualifiedName() + ']');
try {
AttributeEnhancer enhancer = new AsmAttributeEnhancer();
if (enhancer.initialize(clazz.getFullyQualifiedName(), classPath)) {
handleClassAnnotations(manager, enhancer, clazz);
handleInnerClassAnnotations(manager, enhancer, clazz, classPath, destDir);
JavaMethod[] methods = clazz.getMethods();
for (int j = 0; j < methods.length; j++) {
JavaMethod method = methods[j];
if (method.isConstructor()) {
handleConstructorAnnotations(manager, enhancer, method);
} else {
handleMethodAnnotations(manager, enhancer, method);
}
}
JavaField[] fields = clazz.getFields();
for (int j = 0; j < fields.length; j++) {
handleFieldAnnotations(manager, enhancer, fields[j]);
}
// write enhanced class to disk
enhancer.write(destDir);
}
} catch (Throwable e) {
e.printStackTrace();
logWarning("could not compile annotations for class ["
+ clazz.getFullyQualifiedName()
+ "] due to: "
+ e.toString());
}
}
logInfo("compiled classes written to " + destDir);
logInfo("compilation successful");
}
/**
* Handles the class annotations.
*
* @param enhancer
* @param clazz
*/
private static void handleClassAnnotations(
final AnnotationManager manager,
final AttributeEnhancer enhancer,
final JavaClass clazz) {
Annotation[] annotations = manager.getAnnotations(ANNOTATION_ASPECT, clazz);
for (int i = 0; i < annotations.length; i++) {
Annotation annotation = annotations[i];
if (annotation != null) {
AspectAnnotationProxy aspectProxy = (AspectAnnotationProxy) annotation;
enhancer.insertClassAttribute(new AnnotationInfo(ANNOTATION_ASPECT, aspectProxy));
logInfo("aspect [" + clazz.getFullyQualifiedName() + ']');
logInfo(" deployment model [" + aspectProxy.deploymentModel() + ']');
}
}
for (Iterator it = s_customAnnotations.keySet().iterator(); it.hasNext();) {
String annotationName = (String) it.next();
Annotation[] customAnnotations = manager.getAnnotations(annotationName, clazz);
for (int i = 0; i < customAnnotations.length; i++) {
Annotation customAnnotation = customAnnotations[i];
if (customAnnotation != null) {
enhancer.insertClassAttribute(new AnnotationInfo(annotationName, customAnnotation));
logInfo("custom class annotation [" + annotationName + " @ " + clazz.getFullyQualifiedName() + ']');
}
}
}
}
/**
* Handles the method annotations.
*
* @param enhancer
* @param method
*/
private static void handleMethodAnnotations(
final AnnotationManager manager,
final AttributeEnhancer enhancer,
final JavaMethod method) {
// Pointcut with signature
Annotation[] expressionAnnotations = manager.getAnnotations(ANNOTATION_EXPRESSION, method);
for (int i = 0; i < expressionAnnotations.length; i++) {
Annotation expressionAnnotation = expressionAnnotations[i];
if (expressionAnnotation != null) {
ExpressionAnnotationProxy expressionProxy = (ExpressionAnnotationProxy) expressionAnnotation;
AnnotationC.registerCallParameters(expressionProxy, method);
enhancer.insertMethodAttribute(method, new AnnotationInfo(ANNOTATION_EXPRESSION, expressionProxy));
logInfo(" pointcut ["
+ AnnotationC.getShortCallSignature(method)
+ " :: "
+ expressionProxy.expression()
+ ']');
}
}
Annotation[] aroundAnnotations = manager.getAnnotations(ANNOTATION_AROUND, method);
for (int i = 0; i < aroundAnnotations.length; i++) {
Annotation aroundAnnotation = aroundAnnotations[i];
if (aroundAnnotation != null) {
AroundAnnotationProxy aroundProxy = (AroundAnnotationProxy) aroundAnnotation;
AnnotationC.registerCallParameters(aroundProxy, method);
enhancer.insertMethodAttribute(method, new AnnotationInfo(ANNOTATION_AROUND, aroundProxy));
logInfo(" around advice ["
+ AnnotationC.getShortCallSignature(method)
+ " :: "
+ aroundProxy.pointcut()
+ ']');
}
}
Annotation[] beforeAnnotations = manager.getAnnotations(ANNOTATION_BEFORE, method);
for (int i = 0; i < beforeAnnotations.length; i++) {
Annotation beforeAnnotation = beforeAnnotations[i];
if (beforeAnnotation != null) {
BeforeAnnotationProxy beforeProxy = (BeforeAnnotationProxy) beforeAnnotation;
AnnotationC.registerCallParameters(beforeProxy, method);
enhancer.insertMethodAttribute(method, new AnnotationInfo(ANNOTATION_BEFORE, beforeProxy));
logInfo(" before ["
+ AnnotationC.getShortCallSignature(method)
+ " :: "
+ beforeProxy.pointcut()
+ ']');
}
}
Annotation[] afterAnnotations = manager.getAnnotations(ANNOTATION_AFTER, method);
for (int i = 0; i < afterAnnotations.length; i++) {
Annotation afterAnnotation = afterAnnotations[i];
if (afterAnnotation != null) {
AfterAnnotationProxy afterProxy = (AfterAnnotationProxy) afterAnnotation;
AnnotationC.registerCallParameters(afterProxy, method);
enhancer.insertMethodAttribute(method, new AnnotationInfo(ANNOTATION_AFTER, afterProxy));
logInfo(" after advice ["
+ AnnotationC.getShortCallSignature(method)
+ " :: "
+ afterProxy.pointcut()
+ ']');
}
}
for (Iterator it = s_customAnnotations.keySet().iterator(); it.hasNext();) {
String annotationName = (String) it.next();
Annotation[] customAnnotations = manager.getAnnotations(annotationName, method);
for (int i = 0; i < customAnnotations.length; i++) {
Annotation customAnnotation = customAnnotations[i];
if (customAnnotation != null) {
enhancer.insertMethodAttribute(method, new AnnotationInfo(annotationName, customAnnotation));
logInfo(" custom method annotation ["
+ annotationName
+ " @ "
+ method.getParentClass().getName()
+ '.'
+ method.getName()
+ ']');
}
}
}
}
/**
* Handles the constructor annotations.
*
* @param enhancer
* @param constructor
*/
private static void handleConstructorAnnotations(
final AnnotationManager manager,
final AttributeEnhancer enhancer,
final JavaMethod constructor) {
Annotation[] aroundAnnotations = manager.getAnnotations(ANNOTATION_AROUND, constructor);
for (int i = 0; i < aroundAnnotations.length; i++) {
Annotation aroundAnnotation = aroundAnnotations[i];
if (aroundAnnotation != null) {
AroundAnnotationProxy aroundProxy = (AroundAnnotationProxy) aroundAnnotation;
enhancer.insertConstructorAttribute(constructor, new AnnotationInfo(ANNOTATION_AROUND, aroundProxy));
logInfo(" around advice [" + constructor.getName() + " :: " + aroundProxy.pointcut() + ']');
}
}
Annotation[] beforeAnnotations = manager.getAnnotations(ANNOTATION_BEFORE, constructor);
for (int i = 0; i < beforeAnnotations.length; i++) {
Annotation beforeAnnotation = beforeAnnotations[i];
if (beforeAnnotation != null) {
BeforeAnnotationProxy beforeProxy = (BeforeAnnotationProxy) beforeAnnotation;
enhancer.insertConstructorAttribute(constructor, new AnnotationInfo(ANNOTATION_BEFORE, beforeProxy));
logInfo(" before [" + constructor.getName() + " :: " + beforeProxy.pointcut() + ']');
}
}
Annotation[] afterAnnotations = manager.getAnnotations(ANNOTATION_AFTER, constructor);
for (int i = 0; i < afterAnnotations.length; i++) {
Annotation afterAnnotation = afterAnnotations[i];
if (afterAnnotation != null) {
AfterAnnotationProxy afterProxy = (AfterAnnotationProxy) afterAnnotation;
enhancer.insertConstructorAttribute(constructor, new AnnotationInfo(ANNOTATION_AFTER, afterProxy));
logInfo(" after advice [" + constructor.getName() + " :: " + afterProxy.pointcut() + ']');
}
}
for (Iterator it = s_customAnnotations.keySet().iterator(); it.hasNext();) {
String annotationName = (String) it.next();
Annotation[] customAnnotations = manager.getAnnotations(annotationName, constructor);
for (int i = 0; i < customAnnotations.length; i++) {
Annotation customAnnotation = customAnnotations[i];
if (customAnnotation != null) {
enhancer.insertConstructorAttribute(constructor, new AnnotationInfo(
annotationName,
customAnnotation));
logInfo(" custom constructor annotation ["
+ annotationName
+ " @ "
+ constructor.getParentClass().getName()
+ '.'
+ constructor.getName()
+ ']');
}
}
}
}
/**
* Handles the field annotations.
*
* @param enhancer
* @param field
*/
private static void handleFieldAnnotations(
final AnnotationManager manager,
final AttributeEnhancer enhancer,
final JavaField field) {
Annotation[] expressionAnnotations = manager.getAnnotations(ANNOTATION_EXPRESSION, field);
for (int i = 0; i < expressionAnnotations.length; i++) {
Annotation expressionAnnotation = expressionAnnotations[i];
if (expressionAnnotation != null) {
ExpressionAnnotationProxy expressionProxy = (ExpressionAnnotationProxy) expressionAnnotation;
enhancer.insertFieldAttribute(field, new AnnotationInfo(ANNOTATION_EXPRESSION, expressionProxy));
logInfo(" pointcut [" + field.getName() + " :: " + expressionProxy.expression() + ']');
}
}
Annotation[] implementsAnnotations = manager.getAnnotations(ANNOTATION_IMPLEMENTS, field);
for (int i = 0; i < implementsAnnotations.length; i++) {
Annotation implementsAnnotation = implementsAnnotations[i];
if (implementsAnnotation != null) {
ImplementsAnnotationProxy implementsProxy = (ImplementsAnnotationProxy) implementsAnnotation;
enhancer.insertFieldAttribute(field, new AnnotationInfo(ANNOTATION_IMPLEMENTS, implementsProxy));
logInfo(" interface introduction [" + field.getName() + " :: " + implementsProxy.expression() + ']');
}
}
for (Iterator it = s_customAnnotations.keySet().iterator(); it.hasNext();) {
String annotationName = (String) it.next();
Annotation[] customAnnotations = manager.getAnnotations(annotationName, field);
for (int i = 0; i < customAnnotations.length; i++) {
Annotation customAnnotation = customAnnotations[i];
if (customAnnotation != null) {
enhancer.insertFieldAttribute(field, new AnnotationInfo(annotationName, customAnnotation));
logInfo(" custom field annotation [" + annotationName + " @ " + field.getName() + ']');
}
}
}
}
/**
* Handles the inner class annotations.
*
* @param enhancer
* @param clazz
* @param classPath
* @param destDir
*/
private static void handleInnerClassAnnotations(
final AnnotationManager manager,
final AttributeEnhancer enhancer,
final JavaClass clazz,
final String classPath,
final String destDir) {
JavaClass[] innerClasses = clazz.getInnerClasses();
for (int i = 0; i < innerClasses.length; i++) {
JavaClass innerClass = innerClasses[i];
String innerClassName = innerClass.getFullyQualifiedName();
Annotation[] introduceAnnotations = manager.getAnnotations(ANNOTATION_INTRODUCE, innerClass);
for (int k = 0; k < introduceAnnotations.length; k++) {
Annotation introduceAnnotation = introduceAnnotations[k];
if (introduceAnnotation != null) {
IntroduceAnnotationProxy introduceProxy = (IntroduceAnnotationProxy) introduceAnnotation;
if (introduceProxy != null) {
//directly implemented interfaces
JavaClass[] introducedInterfaceClasses = innerClass.getImplementedInterfaces();
String[] introducedInterfaceNames = new String[introducedInterfaceClasses.length];
for (int j = 0; j < introducedInterfaceClasses.length; j++) {
introducedInterfaceNames[j] = introducedInterfaceClasses[j].getFullyQualifiedName();
logInfo(" interface introduction [" + introducedInterfaceNames[j] + ']');
}
if (introducedInterfaceNames.length == 0) {
introducedInterfaceNames = enhancer.getNearestInterfacesInHierarchy(innerClassName);
if (introducedInterfaceNames.length == 0) {
throw new RuntimeException("no implicit interfaces found for " + innerClassName);
}
for (int j = 0; j < introducedInterfaceNames.length; j++) {
logInfo(" interface introduction [" + introducedInterfaceNames[j] + ']');
}
}
introduceProxy.setIntroducedInterfaces(introducedInterfaceNames);
introduceProxy.setInnerClassName(innerClassName);
logInfo(" mixin introduction ["
+ innerClass.getFullyQualifiedName()
+ " :: "
+ introduceProxy.expression()
+ "] deployment model ["
+ introduceProxy.deploymentModel()
+ ']');
enhancer.insertClassAttribute(new AnnotationInfo(ANNOTATION_INTRODUCE, introduceProxy));
}
}
}
// try {
// // TODO: we do not support parsing inner classes of inner classes
// AttributeEnhancer innerClassEnhancer = new AsmAttributeEnhancer();
// if (innerClassEnhancer.initialize(innerClass.getFullyQualifiedName(), classPath)) {
// handleClassAnnotations(manager, innerClassEnhancer, innerClass);
// JavaMethod[] methods = innerClass.getMethods();
// for (int k = 0; k < methods.length; k++) {
// handleMethodAnnotations(manager, innerClassEnhancer, methods[k]);
// }
// JavaField[] fields = innerClass.getFields();
// for (int k = 0; k < fields.length; k++) {
// handleFieldAnnotations(manager, innerClassEnhancer, fields[k]);
// }
//
// // write enhanced class to disk
// innerClassEnhancer.write(destDir);
// }
// } catch (Throwable e) {
// logWarning("could not compile annotations for class ["
// + innerClassName
// + "] due to: "
// + e.toString());
// }
}
}
/**
* Registers the system annotations.
*
* @param manager the annotations manager
*/
private static void registerSystemAnnotations(final AnnotationManager manager) {
manager.registerAnnotationProxy(AspectAnnotationProxy.class, ANNOTATION_ASPECT);
manager.registerAnnotationProxy(AroundAnnotationProxy.class, ANNOTATION_AROUND);
manager.registerAnnotationProxy(BeforeAnnotationProxy.class, ANNOTATION_BEFORE);
manager.registerAnnotationProxy(AfterAnnotationProxy.class, ANNOTATION_AFTER);
manager.registerAnnotationProxy(ExpressionAnnotationProxy.class, ANNOTATION_EXPRESSION);
manager.registerAnnotationProxy(ImplementsAnnotationProxy.class, ANNOTATION_IMPLEMENTS);
manager.registerAnnotationProxy(IntroduceAnnotationProxy.class, ANNOTATION_INTRODUCE);
}
/**
* Registers the user defined annotations.
*
* @param manager the annotations manager
*/
private static void registerUserDefinedAnnotations(final AnnotationManager manager, final String propertiesFile) {
if (propertiesFile == null) {
return;
}
try {
ANNOTATION_DEFINITION.load(new FileInputStream(propertiesFile));
} catch (Exception e) {
String message = "custom annotation properties can not be loaded: " + e.toString();
logWarning(message);
throw new DefinitionException(message);
}
for (Iterator it = ANNOTATION_DEFINITION.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
String name = ((String) entry.getKey()).trim();
String className = ((String) entry.getValue()).trim();
Class klass;
if (className.equals("")) {
// use default untyped annotation proxy
klass = UntypedAnnotationProxy.class;
className = klass.getName();
} else {
try {
klass = s_loader.loadClass(className);
} catch (ClassNotFoundException e) {
String message = className
+ " could not be found on system classpath or class path provided as argument to the compiler";
logError(message);
throw new DefinitionException(message);
}
}
logInfo("register custom annotation [" + name + " :: " + className + ']');
manager.registerAnnotationProxy(klass, name);
s_customAnnotations.put(name, className);
}
}
/**
* Prints the usage.
*/
private static void printUsage() {
System.out.println("AspectWerkz (c) 2002-2004 Jonas Bon�r, Alexandre Vasseur");
System.out
.println("usage: java [options...] org.codehaus.aspectwerkz.annotation.AnnotationC [-verbose] -src <path to src dir> -classes <path to classes dir> [-dest <path to destination dir>] [-custom <property file for custom annotations>]");
System.out
.println(" -dest <path to destination dir> is optional, if omitted the compiled classes will be written to the initial directory");
System.out
.println(" -custom <property file for cutom annotations> is optional, only needed if you have custom annotations you want to compile");
System.out.println(" -verbose activates compilation status information");
System.exit(0);
}
/**
* Parses the command line options.
*
* @param args the arguments
* @return a map with the options
*/
private static Map parseCommandLineOptions(final String[] args) {
final Map arguments = new HashMap();
try {
for (int i = 0; i < args.length; i++) {
if (args[i].equals(COMMAND_LINE_OPTION_VERBOSE)) {
s_verbose = true;
} else if (args[i].startsWith(COMMAND_LINE_OPTION_DASH)) {
String option = args[i++];
String value = args[i];
arguments.put(option, value);
}
}
} catch (Exception e) {
logError("options list to compiler is not valid");
System.exit(1);
}
return arguments;
}
/**
* Logs an INFO message.
*
* @param message the message
*/
private static void logInfo(final String message) {
if (s_verbose) {
System.out.println("AnnotationC::INFO - " + message);
}
}
/**
* Logs an ERROR message.
*
* @param message the message
*/
private static void logError(final String message) {
if (s_verbose) {
System.err.println("AnnotationC::ERROR - " + message);
}
}
/**
* Logs an WARNING message.
*
* @param message the message
*/
private static void logWarning(final String message) {
if (s_verbose) {
System.err.println("AnnotationC::WARNING - " + message);
}
}
private static String getShortCallSignature(final JavaMethod method) {
StringBuffer buffer = new StringBuffer(method.getName());
buffer.append("(");
for (int i = 0; i < method.getParameters().length; i++) {
JavaParameter javaParameter = method.getParameters()[i];
if (javaParameter.getType().toString().equals("org.codehaus.aspectwerkz.joinpoint.JoinPoint")) {
buffer.append("JoinPoint");
} else {
buffer.append(javaParameter.getType().toString());
buffer.append(" ");
buffer.append(javaParameter.getName());
}
if (i + 1 < method.getParameters().length) {
buffer.append(", ");
}
}
buffer.append(")");
return buffer.toString();
}
private static void registerCallParameters(final ParameterizedAnnotationProxy proxy, final JavaMethod method) {
for (int j = 0; j < method.getParameters().length; j++) {
JavaParameter javaParameter = method.getParameters()[j];
proxy.addArgument(javaParameter.getName(), javaParameter.getType().toString());
}
}
}