// ======================================================================== // Copyright (c) 2009 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.annotations; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.webapp.JarScanner; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.commons.EmptyVisitor; /** * AnnotationParser * * Use asm to scan classes for annotations. A SAX-style parsing is done, with * a handler being able to be registered to handle each annotation type. */ public class AnnotationParser { private static final Logger LOG = Log.getLogger(AnnotationParser.class); protected List<String> _parsedClassNames = new ArrayList<String>(); protected Map<String, List<DiscoverableAnnotationHandler>> _annotationHandlers = new HashMap<String, List<DiscoverableAnnotationHandler>>(); protected List<ClassHandler> _classHandlers = new ArrayList<ClassHandler>(); protected List<MethodHandler> _methodHandlers = new ArrayList<MethodHandler>(); protected List<FieldHandler> _fieldHandlers = new ArrayList<FieldHandler>(); public static String normalize (String name) { if (name==null) return null; if (name.startsWith("L") && name.endsWith(";")) name = name.substring(1, name.length()-1); if (name.endsWith(".class")) name = name.substring(0, name.length()-".class".length()); return name.replace('/', '.'); } public abstract class Value { String _name; public Value (String name) { _name = name; } public String getName() { return _name; } public abstract Object getValue(); } public class SimpleValue extends Value { Object _val; public SimpleValue(String name) { super(name); } public void setValue(Object val) { _val=val; } public Object getValue() { return _val; } public String toString() { return "("+getName()+":"+_val+")"; } } public class ListValue extends Value { List<Value> _val; public ListValue (String name) { super(name); _val = new ArrayList<Value>(); } public Object getValue() { return _val; } public List<Value> getList() { return _val; } public void addValue (Value v) { _val.add(v); } public int size () { return _val.size(); } public String toString() { StringBuffer buff = new StringBuffer(); buff.append("("); buff.append(getName()); buff.append(":"); for (Value n: _val) { buff.append(" "+n.toString()); } buff.append(")"); return buff.toString(); } } public interface DiscoverableAnnotationHandler { public void handleClass (String className, int version, int access, String signature, String superName, String[] interfaces, String annotation, List<Value>values); public void handleMethod (String className, String methodName, int access, String desc, String signature,String[] exceptions, String annotation, List<Value>values); public void handleField (String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, List<Value>values); } public interface ClassHandler { public void handle (String className, int version, int access, String signature, String superName, String[] interfaces); } public interface MethodHandler { public void handle (String className, String methodName, int access, String desc, String signature,String[] exceptions); } public interface FieldHandler { public void handle (String className, String fieldName, int access, String fieldType, String signature, Object value); } public class MyAnnotationVisitor implements AnnotationVisitor { List<Value> _annotationValues; String _annotationName; public MyAnnotationVisitor (String annotationName, List<Value> values) { _annotationValues = values; _annotationName = annotationName; } public List<Value> getAnnotationValues() { return _annotationValues; } /** * Visit a single-valued (name,value) pair for this annotation * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object) */ public void visit(String aname, Object avalue) { SimpleValue v = new SimpleValue(aname); v.setValue(avalue); _annotationValues.add(v); } /** * Visit a (name,value) pair whose value is another Annotation * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String) */ public AnnotationVisitor visitAnnotation(String name, String desc) { String s = normalize(desc); ListValue v = new ListValue(s); _annotationValues.add(v); MyAnnotationVisitor visitor = new MyAnnotationVisitor(s, v.getList()); return visitor; } /** * Visit an array valued (name, value) pair for this annotation * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String) */ public AnnotationVisitor visitArray(String name) { ListValue v = new ListValue(name); _annotationValues.add(v); MyAnnotationVisitor visitor = new MyAnnotationVisitor(null, v.getList()); return visitor; } /** * Visit a enum-valued (name,value) pair for this annotation * @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String) */ public void visitEnum(String name, String desc, String value) { //TODO } public void visitEnd() { } } /** * MyClassVisitor * * ASM visitor for a class. */ public class MyClassVisitor extends EmptyVisitor { String _className; int _access; String _signature; String _superName; String[] _interfaces; int _version; public void visit (int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { _className = normalize(name); _access = access; _signature = signature; _superName = superName; _interfaces = interfaces; _version = version; _parsedClassNames.add(_className); //call all registered ClassHandlers String[] normalizedInterfaces = null; if (interfaces!= null) { normalizedInterfaces = new String[interfaces.length]; int i=0; for (String s : interfaces) normalizedInterfaces[i++] = normalize(s); } for (ClassHandler h : AnnotationParser.this._classHandlers) { h.handle(_className, _version, _access, _signature, normalize(_superName), normalizedInterfaces); } } public AnnotationVisitor visitAnnotation (String desc, boolean visible) { MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>()) { public void visitEnd() { super.visitEnd(); //call all AnnotationHandlers with classname, annotation name + values List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName); if (handlers != null) { for (DiscoverableAnnotationHandler h:handlers) { h.handleClass(_className, _version, _access, _signature, _superName, _interfaces, _annotationName, _annotationValues); } } } }; return visitor; } public MethodVisitor visitMethod (final int access, final String name, final String methodDesc, final String signature, final String[] exceptions) { return new EmptyVisitor () { public AnnotationVisitor visitAnnotation(String desc, boolean visible) { MyAnnotationVisitor visitor = new MyAnnotationVisitor (normalize(desc), new ArrayList<Value>()) { public void visitEnd() { super.visitEnd(); //call all AnnotationHandlers with classname, method, annotation name + values List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName); if (handlers != null) { for (DiscoverableAnnotationHandler h:handlers) { h.handleMethod(_className, name, access, methodDesc, signature, exceptions, _annotationName, _annotationValues); } } } }; return visitor; } }; } public FieldVisitor visitField (final int access, final String fieldName, final String fieldType, final String signature, final Object value) { return new EmptyVisitor () { public AnnotationVisitor visitAnnotation(String desc, boolean visible) { MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>()) { public void visitEnd() { super.visitEnd(); List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName); if (handlers != null) { for (DiscoverableAnnotationHandler h:handlers) { h.handleField(_className, fieldName, access, fieldType, signature, value, _annotationName, _annotationValues); } } } }; return visitor; } }; } } /** * Register a handler that will be called back when the named annotation is * encountered on a class. * * @param annotationName * @param handler */ public void registerAnnotationHandler (String annotationName, DiscoverableAnnotationHandler handler) { List<DiscoverableAnnotationHandler> handlers = _annotationHandlers.get(annotationName); if (handlers == null) { handlers = new ArrayList<DiscoverableAnnotationHandler>(); _annotationHandlers.put(annotationName, handlers); } handlers.add(handler); } public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName) { List<DiscoverableAnnotationHandler> handlers = _annotationHandlers.get(annotationName); if (handlers == null) return Collections.emptyList(); return new ArrayList<DiscoverableAnnotationHandler>(); } public List<DiscoverableAnnotationHandler> getAnnotationHandlers() { List<DiscoverableAnnotationHandler> allHandlers = new ArrayList<DiscoverableAnnotationHandler>(); for (List<DiscoverableAnnotationHandler> list:_annotationHandlers.values()) allHandlers.addAll(list); return allHandlers; } public void registerClassHandler (ClassHandler handler) { _classHandlers.add(handler); } public boolean isParsed (String className) { return _parsedClassNames.contains(className); } public void parse (String className, ClassNameResolver resolver) throws Exception { if (className == null) return; if (!resolver.isExcluded(className)) { if (!isParsed(className) || resolver.shouldOverride(className)) { className = className.replace('.', '/')+".class"; URL resource = Loader.getResource(this.getClass(), className, false); if (resource!= null) { Resource r = Resource.newResource(resource); scanClass(r.getInputStream()); } } } } public void parse (Class clazz, ClassNameResolver resolver, boolean visitSuperClasses) throws Exception { Class cz = clazz; while (cz != null) { if (!resolver.isExcluded(cz.getName())) { if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName())) { String nameAsResource = cz.getName().replace('.', '/')+".class"; URL resource = Loader.getResource(this.getClass(), nameAsResource, false); if (resource!= null) { Resource r = Resource.newResource(resource); scanClass(r.getInputStream()); } } } if (visitSuperClasses) cz = cz.getSuperclass(); else cz = null; } } public void parse (String[] classNames, ClassNameResolver resolver) throws Exception { if (classNames == null) return; parse(Arrays.asList(classNames), resolver); } public void parse (List<String> classNames, ClassNameResolver resolver) throws Exception { for (String s:classNames) { if ((resolver == null) || (!resolver.isExcluded(s) && (!isParsed(s) || resolver.shouldOverride(s)))) { s = s.replace('.', '/')+".class"; URL resource = Loader.getResource(this.getClass(), s, false); if (resource!= null) { Resource r = Resource.newResource(resource); scanClass(r.getInputStream()); } } } } public void parse (Resource dir, ClassNameResolver resolver) throws Exception { if (!dir.isDirectory() || !dir.exists()) return; String[] files=dir.list(); for (int f=0;files!=null && f<files.length;f++) { try { Resource res = dir.addPath(files[f]); if (res.isDirectory()) parse(res, resolver); String name = res.getName(); if (name.endsWith(".class")) { if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name)))) { Resource r = Resource.newResource(res.getURL()); scanClass(r.getInputStream()); } } } catch (Exception ex) { LOG.warn(Log.EXCEPTION,ex); } } } /** * Find annotations on classes in the supplied classloader. * Only class files in jar files will be scanned. * @param loader * @param visitParents * @param nullInclusive * @param resolver * @throws Exception */ public void parse (ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver) throws Exception { if (loader==null) return; if (!(loader instanceof URLClassLoader)) return; //can't extract classes? JarScanner scanner = new JarScanner() { public void processEntry(URI jarUri, JarEntry entry) { try { String name = entry.getName(); if (name.toLowerCase().endsWith(".class")) { String shortName = name.replace('/', '.').substring(0,name.length()-6); if ((resolver == null) || (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName)))) { Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name); scanClass(clazz.getInputStream()); } } } catch (Exception e) { LOG.warn("Problem processing jar entry "+entry, e); } } }; scanner.scan(null, loader, nullInclusive, visitParents); } /** * Find annotations in classes in the supplied url of jar files. * @param uris * @param resolver * @throws Exception */ public void parse (URI[] uris, final ClassNameResolver resolver) throws Exception { if (uris==null) return; JarScanner scanner = new JarScanner() { public void processEntry(URI jarUri, JarEntry entry) { try { String name = entry.getName(); if (name.toLowerCase().endsWith(".class")) { String shortName = name.replace('/', '.').substring(0,name.length()-6); if ((resolver == null) || (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName)))) { Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name); scanClass(clazz.getInputStream()); } } } catch (Exception e) { LOG.warn("Problem processing jar entry "+entry, e); } } }; scanner.scan(null, uris, true); } public void parse (URI uri, final ClassNameResolver resolver) throws Exception { if (uri == null) return; URI[] uris = {uri}; parse(uris, resolver); } private void scanClass (InputStream is) throws IOException { ClassReader reader = new ClassReader(is); reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES); } }