blob: 44d7ece184c35e8224440e99fe0c704d7c23ef27 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.dev.jjs.ast;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.util.StringInterner;
import com.google.gwt.dev.util.collect.Lists;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
/**
* Represents a java annotation.
*/
public class JAnnotation extends JNode implements JAnnotationArgument {
/**
* Represents a value contained within an annotation. Single-valued and
* array-valued properties are both represented by this type.
*
* @param <T> the type of JAnnotationValue node that the Property
* encapsulates.
* @see {@link #of}
*/
public static class Property extends JNode {
private final String name;
private List<JAnnotationArgument> values;
public Property(SourceInfo sourceInfo, String name,
List<JAnnotationArgument> values) {
super(sourceInfo);
this.name = StringInterner.get().intern(name);
this.values = Lists.normalize(values);
}
public Property(SourceInfo sourceInfo, String name,
JAnnotationArgument value) {
this(sourceInfo, name, Lists.create(value));
}
public void addValue(JAnnotationArgument value) {
values = Lists.add(values, value);
}
public String getName() {
return name;
}
public JAnnotationArgument getSingleValue() {
if (values.size() != 1) {
throw new IllegalStateException(
"Expecting single-valued property, found " + values.size()
+ " values");
}
return values.get(0);
}
public List<JAnnotationArgument> getValues() {
return Lists.normalizeUnmodifiable(values);
}
public List<JNode> getValuesAsNodes() {
// Lists.normalizeUnmodifiable would have allocated a new list anyway
List<JNode> toReturn = Lists.create();
for (JAnnotationArgument value : values) {
toReturn = Lists.add(toReturn, value.annotationNode());
}
return toReturn;
}
@SuppressWarnings("unchecked")
public void traverse(JVisitor visitor, Context ctx) {
if (visitor.visit(this, ctx)) {
// This is a shady cast to a raw list
List nodes = visitor.acceptImmutable((List) values);
// JNode and JAnnotationArgument types have disjoint hierarchies
if (JAnnotation.class.desiredAssertionStatus()) {
for (int i = 0, j = nodes.size(); i < j; i++) {
assert nodes.get(i) instanceof JAnnotationArgument : "Expecting a "
+ "JAnnotationArgument at index " + i + " found a "
+ nodes.get(i).getClass().getCanonicalName();
}
}
// This is a shady assignment
values = nodes;
}
visitor.endVisit(this, ctx);
}
}
/**
* This runtime exception is thrown when calling a Class-valued annotation
* method when the referenced class is not available to the JVM.
*/
public static class SourceOnlyClassException extends RuntimeException {
private final JClassLiteral literal;
public SourceOnlyClassException(JClassLiteral literal) {
super("The type " + literal.getRefType().getName()
+ " is available only in the module source");
this.literal = literal;
}
public JClassLiteral getLiteral() {
return literal;
}
}
/**
* This handles reflective dispatch for Proxy instances onto the data stored
* in the AST.
*
* @param <T> the type of Annotation
*/
private static class AnnotationInvocationHandler<T> implements
InvocationHandler {
private final Class<T> clazz;
private final JAnnotation annotation;
private AnnotationInvocationHandler(Class<T> clazz, JAnnotation annotation) {
this.clazz = clazz;
this.annotation = annotation;
}
/**
* Handles method invocations.
*/
public Object invoke(Object instance, Method method, Object[] arguments)
throws Throwable {
// Use the proxy object to handle trivial stuff
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, arguments);
}
Property prop = annotation.getProperty(method.getName());
if (prop == null) {
// This works because we're working with real Methods
return method.getDefaultValue();
}
if (method.getReturnType().isArray()) {
List<JAnnotationArgument> values = prop.getValues();
Object toReturn = Array.newInstance(
method.getReturnType().getComponentType(), values.size());
for (int i = 0, j = values.size(); i < j; i++) {
Object value = evaluate(values.get(i));
Array.set(toReturn, i, value);
}
return toReturn;
}
return evaluate(prop.getSingleValue());
}
/**
* Convert a JLiteral value into an Object the caller can work with.
*/
private Object evaluate(JAnnotationArgument value)
throws ClassNotFoundException {
if (value instanceof JValueLiteral) {
// Primitives
return ((JValueLiteral) value).getValueObj();
} else if (value instanceof JClassLiteral) {
String clazzName = ((JClassLiteral) value).getRefType().getName();
try {
return Class.forName(clazzName, true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw new SourceOnlyClassException((JClassLiteral) value);
}
} else if (value instanceof JAnnotation) {
// Determine the synthetic annotation's type
String clazzName = ((JAnnotation) value).getType().getName();
// Load the annotation class
Class<? extends Annotation> annotationType = Class.forName(clazzName,
true, clazz.getClassLoader()).asSubclass(Annotation.class);
// Creating the backing annotation
return createAnnotation(annotationType, (JAnnotation) value);
}
// Unhandled type
throw new RuntimeException("Cannot convert "
+ value.getClass().getCanonicalName() + " into an Object");
}
}
/**
* Create a synthetic Annotation instance, based on the data in a JAnnatation.
*
* @param clazz the type of Annotation
* @param annotation the backing data
*
* @param <T> the type of Annotation
* @return an instance of <code>clazz</code>
*/
public static <T extends Annotation> T createAnnotation(Class<T> clazz,
JAnnotation annotation) {
// Create the annotation as a reflective Proxy instance
Object o = Proxy.newProxyInstance(clazz.getClassLoader(),
new Class<?>[] {clazz}, new AnnotationInvocationHandler<T>(clazz,
annotation));
return clazz.cast(o);
}
/**
* A utility method to retrieve an annotation of the named type. This method
* will not search supertypes or superinterfaces if <code>x</code> is a
* JDeclaredType.
*
* @return the annotation of the requested type, or <code>null</code>
*/
public static JAnnotation findAnnotation(HasAnnotations x,
String annotationTypeName) {
for (JAnnotation a : x.getAnnotations()) {
if (a.getType().getName().equals(annotationTypeName)) {
return a;
}
}
return null;
}
private final JInterfaceType type;
private List<Property> properties = Lists.create();
public JAnnotation(SourceInfo sourceInfo, JInterfaceType type) {
super(sourceInfo);
this.type = type;
}
public void addValue(Property value) {
properties = Lists.add(properties, value);
}
public JNode annotationNode() {
return this;
}
public List<Property> getProperties() {
return Lists.normalizeUnmodifiable(properties);
}
/**
* Returns the named property or <code>null</code> if it does not exist.
*/
public Property getProperty(String name) {
for (Property p : properties) {
if (p.getName().equals(name)) {
return p;
}
}
return null;
}
public JInterfaceType getType() {
return type;
}
public void traverse(JVisitor visitor, Context ctx) {
if (visitor.visit(this, ctx)) {
properties = visitor.acceptImmutable(properties);
}
visitor.endVisit(this, ctx);
}
}