/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javac.model;
import com.sun.tools.javac.util.*;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.lang.annotation.*;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import sun.reflect.annotation.*;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.MirroredTypesException;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Symbol.*;
import com.sun.tools.javac.code.Type.ArrayType;
/**
* A generator of dynamic proxy implementations of
* java.lang.annotation.Annotation.
*
*
The "dynamic proxy return form" of an annotation element value is
* the form used by sun.reflect.annotation.AnnotationInvocationHandler.
*
*
This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.
*/
public class AnnotationProxyMaker {
private final Attribute.Compound anno;
private final Class extends Annotation> annoType;
private AnnotationProxyMaker(Attribute.Compound anno,
Class extends Annotation> annoType) {
this.anno = anno;
this.annoType = annoType;
}
/**
* Returns a dynamic proxy for an annotation mirror.
*/
public static A generateAnnotation(
Attribute.Compound anno, Class annoType) {
AnnotationProxyMaker apm = new AnnotationProxyMaker(anno, annoType);
return annoType.cast(apm.generateAnnotation());
}
/**
* Returns a dynamic proxy for an annotation mirror.
*/
private Annotation generateAnnotation() {
return AnnotationParser.annotationForMap(annoType,
getAllReflectedValues());
}
/**
* Returns a map from element names to their values in "dynamic
* proxy return form". Includes all elements, whether explicit or
* defaulted.
*/
private Map getAllReflectedValues() {
Map res = new LinkedHashMap();
for (Map.Entry entry :
getAllValues().entrySet()) {
MethodSymbol meth = entry.getKey();
Object value = generateValue(meth, entry.getValue());
if (value != null) {
res.put(meth.name.toString(), value);
} else {
// Ignore this element. May (properly) lead to
// IncompleteAnnotationException somewhere down the line.
}
}
return res;
}
/**
* Returns a map from element symbols to their values.
* Includes all elements, whether explicit or defaulted.
*/
private Map getAllValues() {
Map res =
new LinkedHashMap();
// First find the default values.
ClassSymbol sym = (ClassSymbol) anno.type.tsym;
for (Scope.Entry e = sym.members().elems; e != null; e = e.sibling) {
if (e.sym.kind == Kinds.MTH) {
MethodSymbol m = (MethodSymbol) e.sym;
Attribute def = m.getDefaultValue();
if (def != null)
res.put(m, def);
}
}
// Next find the explicit values, possibly overriding defaults.
for (Pair p : anno.values)
res.put(p.fst, p.snd);
return res;
}
/**
* Converts an element value to its "dynamic proxy return form".
* Returns an exception proxy on some errors, but may return null if
* a useful exception cannot or should not be generated at this point.
*/
private Object generateValue(MethodSymbol meth, Attribute attr) {
ValueVisitor vv = new ValueVisitor(meth);
return vv.getValue(attr);
}
private class ValueVisitor implements Attribute.Visitor {
private MethodSymbol meth; // annotation element being visited
private Class> returnClass; // return type of annotation element
private Object value; // value in "dynamic proxy return form"
ValueVisitor(MethodSymbol meth) {
this.meth = meth;
}
Object getValue(Attribute attr) {
Method method; // runtime method of annotation element
try {
method = annoType.getMethod(meth.name.toString());
} catch (NoSuchMethodException e) {
return null;
}
returnClass = method.getReturnType();
attr.accept(this);
if (!(value instanceof ExceptionProxy) &&
!AnnotationType.invocationHandlerReturnType(returnClass)
.isInstance(value)) {
typeMismatch(method, attr);
}
return value;
}
public void visitConstant(Attribute.Constant c) {
value = c.getValue();
}
public void visitClass(Attribute.Class c) {
value = new MirroredTypeExceptionProxy(c.type);
}
public void visitArray(Attribute.Array a) {
Name elemName = ((ArrayType) a.type).elemtype.tsym.getQualifiedName();
if (elemName.equals(elemName.table.names.java_lang_Class)) { // Class[]
// Construct a proxy for a MirroredTypesException
ListBuffer elems = new ListBuffer();
for (Attribute value : a.values) {
Type elem = ((Attribute.Class) value).type;
elems.append(elem);
}
value = new MirroredTypesExceptionProxy(elems.toList());
} else {
int len = a.values.length;
Class> returnClassSaved = returnClass;
returnClass = returnClass.getComponentType();
try {
Object res = Array.newInstance(returnClass, len);
for (int i = 0; i < len; i++) {
a.values[i].accept(this);
if (value == null || value instanceof ExceptionProxy) {
return;
}
try {
Array.set(res, i, value);
} catch (IllegalArgumentException e) {
value = null; // indicates a type mismatch
return;
}
}
value = res;
} finally {
returnClass = returnClassSaved;
}
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
public void visitEnum(Attribute.Enum e) {
if (returnClass.isEnum()) {
String constName = e.value.toString();
try {
value = Enum.valueOf((Class)returnClass, constName);
} catch (IllegalArgumentException ex) {
value = new EnumConstantNotPresentExceptionProxy(
(Class>) returnClass, constName);
}
} else {
value = null; // indicates a type mismatch
}
}
public void visitCompound(Attribute.Compound c) {
try {
Class extends Annotation> nested =
returnClass.asSubclass(Annotation.class);
value = generateAnnotation(c, nested);
} catch (ClassCastException ex) {
value = null; // indicates a type mismatch
}
}
public void visitError(Attribute.Error e) {
value = null; // indicates a type mismatch
}
/**
* Sets "value" to an ExceptionProxy indicating a type mismatch.
*/
private void typeMismatch(Method method, final Attribute attr) {
class AnnotationTypeMismatchExceptionProxy extends ExceptionProxy {
static final long serialVersionUID = 269;
transient final Method method;
AnnotationTypeMismatchExceptionProxy(Method method) {
this.method = method;
}
public String toString() {
return ""; // eg: @Anno(value=)
}
protected RuntimeException generateException() {
return new AnnotationTypeMismatchException(method,
attr.type.toString());
}
}
value = new AnnotationTypeMismatchExceptionProxy(method);
}
}
/**
* ExceptionProxy for MirroredTypeException.
* The toString, hashCode, and equals methods foward to the underlying
* type.
*/
private static final class MirroredTypeExceptionProxy extends ExceptionProxy {
static final long serialVersionUID = 269;
private transient TypeMirror type;
private final String typeString;
MirroredTypeExceptionProxy(TypeMirror t) {
type = t;
typeString = t.toString();
}
public String toString() {
return typeString;
}
public int hashCode() {
return (type != null ? type : typeString).hashCode();
}
public boolean equals(Object obj) {
return type != null &&
obj instanceof MirroredTypeExceptionProxy &&
type.equals(((MirroredTypeExceptionProxy) obj).type);
}
protected RuntimeException generateException() {
return new MirroredTypeException(type);
}
// Explicitly set all transient fields.
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
type = null;
}
}
/**
* ExceptionProxy for MirroredTypesException.
* The toString, hashCode, and equals methods foward to the underlying
* types.
*/
private static final class MirroredTypesExceptionProxy extends ExceptionProxy {
static final long serialVersionUID = 269;
private transient List types;
private final String typeStrings;
MirroredTypesExceptionProxy(List ts) {
types = ts;
typeStrings = ts.toString();
}
public String toString() {
return typeStrings;
}
public int hashCode() {
return (types != null ? types : typeStrings).hashCode();
}
public boolean equals(Object obj) {
return types != null &&
obj instanceof MirroredTypesExceptionProxy &&
types.equals(
((MirroredTypesExceptionProxy) obj).types);
}
protected RuntimeException generateException() {
return new MirroredTypesException(types);
}
// Explicitly set all transient fields.
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
types = null;
}
}
}