package org.jboss.seam.remoting; import java.io.StringReader; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.enterprise.inject.Any; import javax.enterprise.inject.Default; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.util.AnnotationLiteral; import org.jboss.seam.remoting.annotationparser.AnnotationParser; import org.jboss.seam.remoting.annotationparser.ParseException; import org.jboss.seam.remoting.annotationparser.syntaxtree.AnnotationsUnit; import org.jboss.seam.remoting.annotationparser.syntaxtree.BooleanLiteral; import org.jboss.seam.remoting.annotationparser.syntaxtree.ClassOrInterfaceType; import org.jboss.seam.remoting.annotationparser.syntaxtree.Literal; import org.jboss.seam.remoting.annotationparser.syntaxtree.MarkerAnnotation; import org.jboss.seam.remoting.annotationparser.syntaxtree.MemberValue; import org.jboss.seam.remoting.annotationparser.syntaxtree.MemberValuePair; import org.jboss.seam.remoting.annotationparser.syntaxtree.MemberValuePairs; import org.jboss.seam.remoting.annotationparser.syntaxtree.Name; import org.jboss.seam.remoting.annotationparser.syntaxtree.Node; import org.jboss.seam.remoting.annotationparser.syntaxtree.NodeListOptional; import org.jboss.seam.remoting.annotationparser.syntaxtree.NodeOptional; import org.jboss.seam.remoting.annotationparser.syntaxtree.NodeSequence; import org.jboss.seam.remoting.annotationparser.syntaxtree.NodeToken; import org.jboss.seam.remoting.annotationparser.syntaxtree.NormalAnnotation; import org.jboss.seam.remoting.annotationparser.syntaxtree.SingleMemberAnnotation; import org.jboss.seam.remoting.annotationparser.visitor.DepthFirstVisitor; /** * Parses a comma-separated list of annotation expressions and produces an * array of Annotation instances. * * @author Shane Bryzak */ public class AnnotationsParser extends DepthFirstVisitor { protected class AnnotationMetadata { private Class<? extends Annotation> annotationType; private Map<String, Object> memberValues = new HashMap<String, Object>(); public AnnotationMetadata(String name) { this.annotationType = determineAnnotationType(name, beanType); } public void addMemberValue(String name, Object value) { memberValues.put(name, value); } public Map<String, Object> getMemberValues() { return memberValues; } public Class<? extends Annotation> getAnnotationType() { return annotationType; } } @SuppressWarnings("all") private class AnyQualifier extends AnnotationLiteral<Any> implements Any { } ; private Class<?> beanType; private BeanManager beanManager; private List<AnnotationMetadata> meta = new ArrayList<AnnotationMetadata>(); private Annotation[] annotations; public AnnotationsParser(Class<?> beanType, String declaration, BeanManager beanManager) { this.beanType = beanType; this.beanManager = beanManager; // TODO cache the results somewhere AnnotationParser parser = new AnnotationParser(new StringReader(declaration)); try { Node root = parser.AnnotationsUnit(); root.accept(this); } catch (ParseException e) { throw new IllegalArgumentException( "Error while parsing annotation declaration: " + declaration, e); } annotations = new Annotation[meta.size()]; for (int i = 0; i < meta.size(); i++) { AnnotationMetadata ann = meta.get(i); InvocationHandler handler = new AnnotationInvocationHandler( (Class<? extends Annotation>) ann.getAnnotationType(), ann.getMemberValues()); annotations[i] = (Annotation) Proxy.newProxyInstance( ann.getAnnotationType().getClassLoader(), new Class[]{ann.getAnnotationType()}, handler); } meta = null; } public Annotation[] getAnnotations() { return annotations; } @SuppressWarnings("unchecked") private Class<? extends Annotation> determineAnnotationType(String name, Class<?> beanType) { try { return (Class<? extends Annotation>) Class.forName(name); } catch (ClassNotFoundException e) { // Iterate through the annotations on the bean type and look for a simple name match for (Annotation beanAnnotation : beanType.getAnnotations()) { if (name.equals(beanAnnotation.annotationType().getSimpleName())) { return beanAnnotation.annotationType(); } } // Couldn't find the annotation on the bean type itself - let's look at all beans // with the same type Set<Bean<?>> beans = beanManager.getBeans(beanType, new AnyQualifier()); for (Bean<?> bean : beans) { for (Annotation beanAnnotation : bean.getBeanClass().getAnnotations()) { if (name.equals(beanAnnotation.annotationType().getSimpleName())) { return beanAnnotation.annotationType(); } } } if ("Default".equals(name)) { return Default.class; } else if ("Any".equals(name)) { return Any.class; } return null; } } @Override public void visit(AnnotationsUnit node) { List<org.jboss.seam.remoting.annotationparser.syntaxtree.Annotation> annotations = new ArrayList<org.jboss.seam.remoting.annotationparser.syntaxtree.Annotation>(); // TODO messy! turn this into a recursive function NodeOptional n = (NodeOptional) node.f0; if (n.present()) { if (n.node instanceof NodeSequence) { NodeSequence ns = (NodeSequence) n.node; { for (Node nsNode : ns.nodes) { if (nsNode instanceof org.jboss.seam.remoting.annotationparser.syntaxtree.Annotation) { annotations.add((org.jboss.seam.remoting.annotationparser.syntaxtree.Annotation) nsNode); } else if (nsNode instanceof NodeListOptional) { NodeListOptional nlo = (NodeListOptional) nsNode; if (nlo.present()) { for (Node nloNode : nlo.nodes) { if (nloNode instanceof NodeSequence) { for (Node cn : ((NodeSequence) nloNode).nodes) { if (cn instanceof org.jboss.seam.remoting.annotationparser.syntaxtree.Annotation) { annotations.add((org.jboss.seam.remoting.annotationparser.syntaxtree.Annotation) cn); } } } } } } } } } } for (org.jboss.seam.remoting.annotationparser.syntaxtree.Annotation a : annotations) { processAnnotation(a); } super.visit(node); } private void processAnnotation(org.jboss.seam.remoting.annotationparser.syntaxtree.Annotation node) { if (node.f0.choice instanceof MarkerAnnotation) { meta.add(new AnnotationMetadata(extractName(((MarkerAnnotation) node.f0.choice).f1))); } else if (node.f0.choice instanceof NormalAnnotation) { NormalAnnotation ann = (NormalAnnotation) node.f0.choice; AnnotationMetadata metadata = new AnnotationMetadata(extractName(ann.f1)); if (ann.f3.present() && ann.f3.node instanceof MemberValuePairs) { MemberValuePairs mvp = (MemberValuePairs) ann.f3.node; extractMemberValue(metadata, mvp.f0.f0.tokenImage, mvp.f0.f2); if (mvp.f1.present()) { for (Node n : mvp.f1.nodes) { if (n instanceof NodeSequence) { for (Node nsn : ((NodeSequence) n).nodes) { if (nsn instanceof MemberValuePair) { MemberValuePair p = (MemberValuePair) nsn; extractMemberValue(metadata, p.f0.tokenImage, p.f2); } } } } } } meta.add(metadata); } else if (node.f0.choice instanceof SingleMemberAnnotation) { AnnotationMetadata metadata = new AnnotationMetadata( extractName(((SingleMemberAnnotation) node.f0.choice).f1)); extractMemberValue(metadata, "value", ((SingleMemberAnnotation) node.f0.choice).f3); meta.add(metadata); } } private void extractMemberValue(AnnotationMetadata metadata, String memberName, MemberValue memberValue) { Class<?> memberType = null; for (Method m : metadata.getAnnotationType().getMethods()) { if (memberName.equals(m.getName())) { memberType = m.getReturnType(); break; } } if (memberType == null) { throw new RuntimeException("Annotation member " + memberName + " not found on annotation type " + metadata.getAnnotationType().getName()); } Object value = null; switch (memberValue.f0.which) { // TODO add the missing conversions case 0: // Annotation break; case 1: // MemberValueArray // not supported - array member values are non-binding break; case 2: // Literal value = convertLiteral((Literal) memberValue.f0.choice); break; case 3: // ClassOrInterfaceType value = convertClassOrInterfaceType( (ClassOrInterfaceType) memberValue.f0.choice, memberType); break; } metadata.addMemberValue(memberName, value); } private Object convertLiteral(Literal literal) { switch (literal.f0.which) { case 0: // <INTEGER_LITERAL> return Integer.parseInt(((NodeToken) literal.f0.choice).tokenImage); case 1: // <FLOATING_POINT_LITERAL> return Float.parseFloat(((NodeToken) literal.f0.choice).tokenImage); case 2: // <CHARACTER_LITERAL> return ((NodeToken) literal.f0.choice).tokenImage.charAt(1); // ignore the single quotes case 3: // <STRING_LITERAL> String stringVal = ((NodeToken) literal.f0.choice).tokenImage; return stringVal.substring(1, stringVal.length() - 1); // strip the double quotes case 4: // BooleanLiteral() return "true".equals(((NodeToken) ((BooleanLiteral) literal.f0.choice).f0.choice).tokenImage); case 5: // NullLiteral() return null; } return null; } private Object convertClassOrInterfaceType(ClassOrInterfaceType node, Class<?> memberType) { StringBuilder sb = new StringBuilder(); sb.append(node.f0.tokenImage); if (node.f1.present()) { for (Node n : node.f1.nodes) { if (n instanceof NodeSequence) { for (Node nsn : ((NodeSequence) n).nodes) { if (nsn instanceof NodeToken) { sb.append(((NodeToken) nsn).tokenImage); } } } } } String className = sb.toString(); if (memberType.isEnum()) { for (Object e : memberType.getEnumConstants()) { if (className.equals(((Enum<?>) e).name())) return e; } throw new IllegalArgumentException( "Invalid enum specified for annotation member value: " + className); } else { try { return Class.forName(className); } catch (ClassNotFoundException e) { if (!className.startsWith("java.lang.")) { // try finding the class in the java.lang package try { return Class.forName("java.lang." + className); } catch (ClassNotFoundException e1) { } } throw new IllegalArgumentException( "Invalid class name specified for annotation member value: " + className); } } } private String extractName(Name name) { StringBuilder sb = new StringBuilder(); sb.append(name.f0.tokenImage); NodeListOptional nodeList = ((NodeListOptional) name.f1); if (nodeList.present()) { for (Node node : nodeList.nodes) { if (node instanceof NodeSequence) { for (Node n : ((NodeSequence) node).nodes) { if (n instanceof NodeToken) { sb.append(((NodeToken) n).tokenImage); } } } } } return sb.toString(); } }