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();
}
}