/*
* Copyright (c) 2003, 2017, 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.comp;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Attribute.Compound;
import com.sun.tools.javac.code.Attribute.TypeCompound;
import com.sun.tools.javac.code.Scope.WriteableScope;
import com.sun.tools.javac.code.Symbol.*;
import com.sun.tools.javac.code.TypeMetadata.Entry.Kind;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.List;
import javax.tools.JavaFileObject;
import java.util.*;
import static com.sun.tools.javac.code.Flags.SYNTHETIC;
import static com.sun.tools.javac.code.Kinds.Kind.MDL;
import static com.sun.tools.javac.code.Kinds.Kind.MTH;
import static com.sun.tools.javac.code.Kinds.Kind.PCK;
import static com.sun.tools.javac.code.Kinds.Kind.VAR;
import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
import static com.sun.tools.javac.code.TypeTag.ARRAY;
import static com.sun.tools.javac.code.TypeTag.CLASS;
import static com.sun.tools.javac.tree.JCTree.Tag.ANNOTATION;
import static com.sun.tools.javac.tree.JCTree.Tag.ASSIGN;
import static com.sun.tools.javac.tree.JCTree.Tag.IDENT;
import static com.sun.tools.javac.tree.JCTree.Tag.NEWARRAY;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
Enter annotations onto symbols and types (and trees).
This is also a pseudo stage in the compiler taking care of scheduling when annotations are
entered.
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.
/** Enter annotations onto symbols and types (and trees).
*
* This is also a pseudo stage in the compiler taking care of scheduling when annotations are
* entered.
*
* <p><b>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.</b>
*/
public class Annotate {
protected static final Context.Key<Annotate> annotateKey = new Context.Key<>();
public static Annotate instance(Context context) {
Annotate instance = context.get(annotateKey);
if (instance == null)
instance = new Annotate(context);
return instance;
}
private final Attr attr;
private final Check chk;
private final ConstFold cfolder;
private final DeferredLintHandler deferredLintHandler;
private final Enter enter;
private final Lint lint;
private final Log log;
private final Names names;
private final Resolve resolve;
private final TreeMaker make;
private final Symtab syms;
private final TypeEnvs typeEnvs;
private final Types types;
private final Attribute theUnfinishedDefaultValue;
private final boolean allowRepeatedAnnos;
private final String sourceName;
protected Annotate(Context context) {
context.put(annotateKey, this);
attr = Attr.instance(context);
chk = Check.instance(context);
cfolder = ConstFold.instance(context);
deferredLintHandler = DeferredLintHandler.instance(context);
enter = Enter.instance(context);
log = Log.instance(context);
lint = Lint.instance(context);
make = TreeMaker.instance(context);
names = Names.instance(context);
resolve = Resolve.instance(context);
syms = Symtab.instance(context);
typeEnvs = TypeEnvs.instance(context);
types = Types.instance(context);
theUnfinishedDefaultValue = new Attribute.Error(syms.errType);
Source source = Source.instance(context);
allowRepeatedAnnos = source.allowRepeatedAnnotations();
sourceName = source.name;
blockCount = 1;
}
Semaphore to delay annotation processing /** Semaphore to delay annotation processing */
private int blockCount = 0;
Called when annotations processing needs to be postponed. /** Called when annotations processing needs to be postponed. */
public void blockAnnotations() {
blockCount++;
}
Called when annotation processing can be resumed. /** Called when annotation processing can be resumed. */
public void unblockAnnotations() {
blockCount--;
if (blockCount == 0)
flush();
}
Variant which allows for a delayed flush of annotations.
Needed by ClassReader /** Variant which allows for a delayed flush of annotations.
* Needed by ClassReader */
public void unblockAnnotationsNoFlush() {
blockCount--;
}
are we blocking annotation processing? /** are we blocking annotation processing? */
public boolean annotationsBlocked() {return blockCount > 0; }
public void enterDone() {
unblockAnnotations();
}
public List<TypeCompound> fromAnnotations(List<JCAnnotation> annotations) {
if (annotations.isEmpty()) {
return List.nil();
}
ListBuffer<TypeCompound> buf = new ListBuffer<>();
for (JCAnnotation anno : annotations) {
Assert.checkNonNull(anno.attribute);
buf.append((TypeCompound) anno.attribute);
}
return buf.toList();
}
Annotate (used for everything else) /** Annotate (used for everything else) */
public void normal(Runnable r) {
q.append(r);
}
Validate, triggers after 'normal' /** Validate, triggers after 'normal' */
public void validate(Runnable a) {
validateQ.append(a);
}
Flush all annotation queues /** Flush all annotation queues */
public void flush() {
if (annotationsBlocked()) return;
if (isFlushing()) return;
startFlushing();
try {
while (q.nonEmpty()) {
q.next().run();
}
while (typesQ.nonEmpty()) {
typesQ.next().run();
}
while (afterTypesQ.nonEmpty()) {
afterTypesQ.next().run();
}
while (validateQ.nonEmpty()) {
validateQ.next().run();
}
} finally {
doneFlushing();
}
}
private ListBuffer<Runnable> q = new ListBuffer<>();
private ListBuffer<Runnable> validateQ = new ListBuffer<>();
private int flushCount = 0;
private boolean isFlushing() { return flushCount > 0; }
private void startFlushing() { flushCount++; }
private void doneFlushing() { flushCount--; }
ListBuffer<Runnable> typesQ = new ListBuffer<>();
ListBuffer<Runnable> afterTypesQ = new ListBuffer<>();
public void typeAnnotation(Runnable a) {
typesQ.append(a);
}
public void afterTypes(Runnable a) {
afterTypesQ.append(a);
}
Queue annotations for later attribution and entering. This is probably the method you are looking for.
Params: - annotations – the list of JCAnnotations to attribute and enter
- localEnv – the enclosing env
- s – ths Symbol on which to enter the annotations
- deferPos – report errors here
/**
* Queue annotations for later attribution and entering. This is probably the method you are looking for.
*
* @param annotations the list of JCAnnotations to attribute and enter
* @param localEnv the enclosing env
* @param s ths Symbol on which to enter the annotations
* @param deferPos report errors here
*/
public void annotateLater(List<JCAnnotation> annotations, Env<AttrContext> localEnv,
Symbol s, DiagnosticPosition deferPos)
{
if (annotations.isEmpty()) {
return;
}
s.resetAnnotations(); // mark Annotations as incomplete for now
normal(() -> {
// Packages are unusual, in that they are the only type of declaration that can legally appear
// more than once in a compilation, and in all cases refer to the same underlying symbol.
// This means they are the only kind of declaration that syntactically may have multiple sets
// of annotations, each on a different package declaration, even though that is ultimately
// forbidden by JLS 8 section 7.4.
// The corollary here is that all of the annotations on a package symbol may have already
// been handled, meaning that the set of annotations pending completion is now empty.
Assert.check(s.kind == PCK || s.annotationsPendingCompletion());
JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile);
DiagnosticPosition prevLintPos =
deferPos != null
? deferredLintHandler.setPos(deferPos)
: deferredLintHandler.immediate();
Lint prevLint = deferPos != null ? null : chk.setLint(lint);
try {
if (s.hasAnnotations() && annotations.nonEmpty())
log.error(annotations.head.pos, "already.annotated", Kinds.kindName(s), s);
Assert.checkNonNull(s, "Symbol argument to actualEnterAnnotations is null");
// false is passed as fifth parameter since annotateLater is
// never called for a type parameter
annotateNow(s, annotations, localEnv, false, false);
} finally {
if (prevLint != null)
chk.setLint(prevLint);
deferredLintHandler.setPos(prevLintPos);
log.useSource(prev);
}
});
validate(() -> { //validate annotations
JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile);
try {
chk.validateAnnotations(annotations, s);
} finally {
log.useSource(prev);
}
});
}
Queue processing of an attribute default value. /** Queue processing of an attribute default value. */
public void annotateDefaultValueLater(JCExpression defaultValue, Env<AttrContext> localEnv,
MethodSymbol m, DiagnosticPosition deferPos)
{
normal(() -> {
JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile);
DiagnosticPosition prevLintPos = deferredLintHandler.setPos(deferPos);
try {
enterDefaultValue(defaultValue, localEnv, m);
} finally {
deferredLintHandler.setPos(prevLintPos);
log.useSource(prev);
}
});
validate(() -> { //validate annotations
JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile);
try {
// if default value is an annotation, check it is a well-formed
// annotation value (e.g. no duplicate values, no missing values, etc.)
chk.validateAnnotationTree(defaultValue);
} finally {
log.useSource(prev);
}
});
}
Enter a default value for an annotation element. /** Enter a default value for an annotation element. */
private void enterDefaultValue(JCExpression defaultValue,
Env<AttrContext> localEnv, MethodSymbol m) {
m.defaultValue = attributeAnnotationValue(m.type.getReturnType(), defaultValue, localEnv);
}
Gather up annotations into a map from type symbols to lists of Compound attributes,
then continue on with repeating annotations processing.
/**
* Gather up annotations into a map from type symbols to lists of Compound attributes,
* then continue on with repeating annotations processing.
*/
private <T extends Attribute.Compound> void annotateNow(Symbol toAnnotate,
List<JCAnnotation> withAnnotations, Env<AttrContext> env, boolean typeAnnotations,
boolean isTypeParam)
{
Map<TypeSymbol, ListBuffer<T>> annotated = new LinkedHashMap<>();
Map<T, DiagnosticPosition> pos = new HashMap<>();
for (List<JCAnnotation> al = withAnnotations; !al.isEmpty(); al = al.tail) {
JCAnnotation a = al.head;
T c;
if (typeAnnotations) {
@SuppressWarnings("unchecked")
T tmp = (T)attributeTypeAnnotation(a, syms.annotationType, env);
c = tmp;
} else {
@SuppressWarnings("unchecked")
T tmp = (T)attributeAnnotation(a, syms.annotationType, env);
c = tmp;
}
Assert.checkNonNull(c, "Failed to create annotation");
if (annotated.containsKey(a.type.tsym)) {
if (!allowRepeatedAnnos) {
log.error(DiagnosticFlag.SOURCE_LEVEL, a.pos(), "repeatable.annotations.not.supported.in.source", sourceName);
}
ListBuffer<T> l = annotated.get(a.type.tsym);
l = l.append(c);
annotated.put(a.type.tsym, l);
pos.put(c, a.pos());
} else {
annotated.put(a.type.tsym, ListBuffer.of(c));
pos.put(c, a.pos());
}
// Note: @Deprecated has no effect on local variables and parameters
if (!c.type.isErroneous()
&& (toAnnotate.kind == MDL || toAnnotate.owner.kind != MTH)
&& types.isSameType(c.type, syms.deprecatedType)) {
toAnnotate.flags_field |= (Flags.DEPRECATED | Flags.DEPRECATED_ANNOTATION);
Attribute fr = c.member(names.forRemoval);
if (fr instanceof Attribute.Constant) {
Attribute.Constant v = (Attribute.Constant) fr;
if (v.type == syms.booleanType && ((Integer) v.value) != 0) {
toAnnotate.flags_field |= Flags.DEPRECATED_REMOVAL;
}
}
}
}
List<T> buf = List.nil();
for (ListBuffer<T> lb : annotated.values()) {
if (lb.size() == 1) {
buf = buf.prepend(lb.first());
} else {
AnnotationContext<T> ctx = new AnnotationContext<>(env, annotated, pos, typeAnnotations);
T res = makeContainerAnnotation(lb.toList(), ctx, toAnnotate, isTypeParam);
if (res != null)
buf = buf.prepend(res);
}
}
if (typeAnnotations) {
@SuppressWarnings("unchecked")
List<TypeCompound> attrs = (List<TypeCompound>)buf.reverse();
toAnnotate.appendUniqueTypeAttributes(attrs);
} else {
@SuppressWarnings("unchecked")
List<Attribute.Compound> attrs = (List<Attribute.Compound>)buf.reverse();
toAnnotate.resetAnnotations();
toAnnotate.setDeclarationAttributes(attrs);
}
}
Attribute and store a semantic representation of the annotation tree tree
into the tree.attribute field. Params: - tree – the tree representing an annotation
- expectedAnnotationType – the expected (super)type of the annotation
- env – the current env in where the annotation instance is found
/**
* Attribute and store a semantic representation of the annotation tree {@code tree} into the
* tree.attribute field.
*
* @param tree the tree representing an annotation
* @param expectedAnnotationType the expected (super)type of the annotation
* @param env the current env in where the annotation instance is found
*/
public Attribute.Compound attributeAnnotation(JCAnnotation tree, Type expectedAnnotationType,
Env<AttrContext> env)
{
// The attribute might have been entered if it is Target or Repetable
// Because TreeCopier does not copy type, redo this if type is null
if (tree.attribute != null && tree.type != null)
return tree.attribute;
List<Pair<MethodSymbol, Attribute>> elems = attributeAnnotationValues(tree, expectedAnnotationType, env);
Attribute.Compound ac = new Attribute.Compound(tree.type, elems);
return tree.attribute = ac;
}
Attribute and store a semantic representation of the type annotation tree tree
into the tree.attribute field. Params: - a – the tree representing an annotation
- expectedAnnotationType – the expected (super)type of the annotation
- env – the the current env in where the annotation instance is found
/** Attribute and store a semantic representation of the type annotation tree {@code tree} into
* the tree.attribute field.
*
* @param a the tree representing an annotation
* @param expectedAnnotationType the expected (super)type of the annotation
* @param env the the current env in where the annotation instance is found
*/
public Attribute.TypeCompound attributeTypeAnnotation(JCAnnotation a, Type expectedAnnotationType,
Env<AttrContext> env)
{
// The attribute might have been entered if it is Target or Repetable
// Because TreeCopier does not copy type, redo this if type is null
if (a.attribute == null || a.type == null || !(a.attribute instanceof Attribute.TypeCompound)) {
// Create a new TypeCompound
List<Pair<MethodSymbol,Attribute>> elems =
attributeAnnotationValues(a, expectedAnnotationType, env);
Attribute.TypeCompound tc =
new Attribute.TypeCompound(a.type, elems, TypeAnnotationPosition.unknown);
a.attribute = tc;
return tc;
} else {
// Use an existing TypeCompound
return (Attribute.TypeCompound)a.attribute;
}
}
Attribute annotation elements creating a list of pairs of the Symbol representing that
element and the value of that element as an Attribute. /**
* Attribute annotation elements creating a list of pairs of the Symbol representing that
* element and the value of that element as an Attribute. */
private List<Pair<MethodSymbol, Attribute>> attributeAnnotationValues(JCAnnotation a,
Type expected, Env<AttrContext> env)
{
// The annotation might have had its type attributed (but not
// checked) by attr.attribAnnotationTypes during MemberEnter,
// in which case we do not need to do it again.
Type at = (a.annotationType.type != null ?
a.annotationType.type : attr.attribType(a.annotationType, env));
a.type = chk.checkType(a.annotationType.pos(), at, expected);
boolean isError = a.type.isErroneous();
if (!a.type.tsym.isAnnotationType() && !isError) {
log.error(a.annotationType.pos(),
"not.annotation.type", a.type.toString());
isError = true;
}
// List of name=value pairs (or implicit "value=" if size 1)
List<JCExpression> args = a.args;
boolean elidedValue = false;
// special case: elided "value=" assumed
if (args.length() == 1 && !args.head.hasTag(ASSIGN)) {
args.head = make.at(args.head.pos).
Assign(make.Ident(names.value), args.head);
elidedValue = true;
}
ListBuffer<Pair<MethodSymbol,Attribute>> buf = new ListBuffer<>();
for (List<JCExpression> tl = args; tl.nonEmpty(); tl = tl.tail) {
Pair<MethodSymbol, Attribute> p = attributeAnnotationNameValuePair(tl.head, a.type, isError, env, elidedValue);
if (p != null && !p.fst.type.isErroneous())
buf.append(p);
}
return buf.toList();
}
// where
private Pair<MethodSymbol, Attribute> attributeAnnotationNameValuePair(JCExpression nameValuePair,
Type thisAnnotationType, boolean badAnnotation, Env<AttrContext> env, boolean elidedValue)
{
if (!nameValuePair.hasTag(ASSIGN)) {
log.error(nameValuePair.pos(), "annotation.value.must.be.name.value");
attributeAnnotationValue(nameValuePair.type = syms.errType, nameValuePair, env);
return null;
}
JCAssign assign = (JCAssign)nameValuePair;
if (!assign.lhs.hasTag(IDENT)) {
log.error(nameValuePair.pos(), "annotation.value.must.be.name.value");
attributeAnnotationValue(nameValuePair.type = syms.errType, nameValuePair, env);
return null;
}
// Resolve element to MethodSym
JCIdent left = (JCIdent)assign.lhs;
Symbol method = resolve.resolveQualifiedMethod(elidedValue ? assign.rhs.pos() : left.pos(),
env, thisAnnotationType,
left.name, List.nil(), null);
left.sym = method;
left.type = method.type;
if (method.owner != thisAnnotationType.tsym && !badAnnotation)
log.error(left.pos(), "no.annotation.member", left.name, thisAnnotationType);
Type resultType = method.type.getReturnType();
// Compute value part
Attribute value = attributeAnnotationValue(resultType, assign.rhs, env);
nameValuePair.type = resultType;
return method.type.isErroneous() ? null : new Pair<>((MethodSymbol)method, value);
}
Attribute an annotation element value /** Attribute an annotation element value */
private Attribute attributeAnnotationValue(Type expectedElementType, JCExpression tree,
Env<AttrContext> env)
{
//first, try completing the symbol for the annotation value - if acompletion
//error is thrown, we should recover gracefully, and display an
//ordinary resolution diagnostic.
try {
expectedElementType.tsym.complete();
} catch(CompletionFailure e) {
log.error(tree.pos(), "cant.resolve", Kinds.kindName(e.sym), e.sym);
expectedElementType = syms.errType;
}
if (expectedElementType.hasTag(ARRAY)) {
return getAnnotationArrayValue(expectedElementType, tree, env);
}
//error recovery
if (tree.hasTag(NEWARRAY)) {
if (!expectedElementType.isErroneous())
log.error(tree.pos(), "annotation.value.not.allowable.type");
JCNewArray na = (JCNewArray)tree;
if (na.elemtype != null) {
log.error(na.elemtype.pos(), "new.not.allowed.in.annotation");
}
for (List<JCExpression> l = na.elems; l.nonEmpty(); l=l.tail) {
attributeAnnotationValue(syms.errType,
l.head,
env);
}
return new Attribute.Error(syms.errType);
}
if (expectedElementType.tsym.isAnnotationType()) {
if (tree.hasTag(ANNOTATION)) {
return attributeAnnotation((JCAnnotation)tree, expectedElementType, env);
} else {
log.error(tree.pos(), "annotation.value.must.be.annotation");
expectedElementType = syms.errType;
}
}
//error recovery
if (tree.hasTag(ANNOTATION)) {
if (!expectedElementType.isErroneous())
log.error(tree.pos(), "annotation.not.valid.for.type", expectedElementType);
attributeAnnotation((JCAnnotation)tree, syms.errType, env);
return new Attribute.Error(((JCAnnotation)tree).annotationType.type);
}
if (expectedElementType.isPrimitive() ||
(types.isSameType(expectedElementType, syms.stringType) && !expectedElementType.hasTag(TypeTag.ERROR))) {
return getAnnotationPrimitiveValue(expectedElementType, tree, env);
}
if (expectedElementType.tsym == syms.classType.tsym) {
return getAnnotationClassValue(expectedElementType, tree, env);
}
if (expectedElementType.hasTag(CLASS) &&
(expectedElementType.tsym.flags() & Flags.ENUM) != 0) {
return getAnnotationEnumValue(expectedElementType, tree, env);
}
//error recovery:
if (!expectedElementType.isErroneous())
log.error(tree.pos(), "annotation.value.not.allowable.type");
return new Attribute.Error(attr.attribExpr(tree, env, expectedElementType));
}
private Attribute getAnnotationEnumValue(Type expectedElementType, JCExpression tree, Env<AttrContext> env) {
Type result = attr.attribExpr(tree, env, expectedElementType);
Symbol sym = TreeInfo.symbol(tree);
if (sym == null ||
TreeInfo.nonstaticSelect(tree) ||
sym.kind != VAR ||
(sym.flags() & Flags.ENUM) == 0) {
log.error(tree.pos(), "enum.annotation.must.be.enum.constant");
return new Attribute.Error(result.getOriginalType());
}
VarSymbol enumerator = (VarSymbol) sym;
return new Attribute.Enum(expectedElementType, enumerator);
}
private Attribute getAnnotationClassValue(Type expectedElementType, JCExpression tree, Env<AttrContext> env) {
Type result = attr.attribExpr(tree, env, expectedElementType);
if (result.isErroneous()) {
// Does it look like an unresolved class literal?
if (TreeInfo.name(tree) == names._class &&
((JCFieldAccess) tree).selected.type.isErroneous()) {
Name n = (((JCFieldAccess) tree).selected).type.tsym.flatName();
return new Attribute.UnresolvedClass(expectedElementType,
types.createErrorType(n,
syms.unknownSymbol, syms.classType));
} else {
return new Attribute.Error(result.getOriginalType());
}
}
// Class literals look like field accesses of a field named class
// at the tree level
if (TreeInfo.name(tree) != names._class) {
log.error(tree.pos(), "annotation.value.must.be.class.literal");
return new Attribute.Error(syms.errType);
}
return new Attribute.Class(types,
(((JCFieldAccess) tree).selected).type);
}
private Attribute getAnnotationPrimitiveValue(Type expectedElementType, JCExpression tree, Env<AttrContext> env) {
Type result = attr.attribExpr(tree, env, expectedElementType);
if (result.isErroneous())
return new Attribute.Error(result.getOriginalType());
if (result.constValue() == null) {
log.error(tree.pos(), "attribute.value.must.be.constant");
return new Attribute.Error(expectedElementType);
}
result = cfolder.coerce(result, expectedElementType);
return new Attribute.Constant(expectedElementType, result.constValue());
}
private Attribute getAnnotationArrayValue(Type expectedElementType, JCExpression tree, Env<AttrContext> env) {
// Special case, implicit array
if (!tree.hasTag(NEWARRAY)) {
tree = make.at(tree.pos).
NewArray(null, List.nil(), List.of(tree));
}
JCNewArray na = (JCNewArray)tree;
if (na.elemtype != null) {
log.error(na.elemtype.pos(), "new.not.allowed.in.annotation");
}
ListBuffer<Attribute> buf = new ListBuffer<>();
for (List<JCExpression> l = na.elems; l.nonEmpty(); l=l.tail) {
buf.append(attributeAnnotationValue(types.elemtype(expectedElementType),
l.head,
env));
}
na.type = expectedElementType;
return new Attribute.
Array(expectedElementType, buf.toArray(new Attribute[buf.length()]));
}
/* *********************************
* Support for repeating annotations
***********************************/
This context contains all the information needed to synthesize new
annotations trees for repeating annotations.
/**
* This context contains all the information needed to synthesize new
* annotations trees for repeating annotations.
*/
private class AnnotationContext<T extends Attribute.Compound> {
public final Env<AttrContext> env;
public final Map<Symbol.TypeSymbol, ListBuffer<T>> annotated;
public final Map<T, JCDiagnostic.DiagnosticPosition> pos;
public final boolean isTypeCompound;
public AnnotationContext(Env<AttrContext> env,
Map<Symbol.TypeSymbol, ListBuffer<T>> annotated,
Map<T, JCDiagnostic.DiagnosticPosition> pos,
boolean isTypeCompound) {
Assert.checkNonNull(env);
Assert.checkNonNull(annotated);
Assert.checkNonNull(pos);
this.env = env;
this.annotated = annotated;
this.pos = pos;
this.isTypeCompound = isTypeCompound;
}
}
/* Process repeated annotations. This method returns the
* synthesized container annotation or null IFF all repeating
* annotation are invalid. This method reports errors/warnings.
*/
private <T extends Attribute.Compound> T processRepeatedAnnotations(List<T> annotations,
AnnotationContext<T> ctx, Symbol on, boolean isTypeParam)
{
T firstOccurrence = annotations.head;
List<Attribute> repeated = List.nil();
Type origAnnoType = null;
Type arrayOfOrigAnnoType = null;
Type targetContainerType = null;
MethodSymbol containerValueSymbol = null;
Assert.check(!annotations.isEmpty() && !annotations.tail.isEmpty()); // i.e. size() > 1
int count = 0;
for (List<T> al = annotations; !al.isEmpty(); al = al.tail) {
count++;
// There must be more than a single anno in the annotation list
Assert.check(count > 1 || !al.tail.isEmpty());
T currentAnno = al.head;
origAnnoType = currentAnno.type;
if (arrayOfOrigAnnoType == null) {
arrayOfOrigAnnoType = types.makeArrayType(origAnnoType);
}
// Only report errors if this isn't the first occurrence I.E. count > 1
boolean reportError = count > 1;
Type currentContainerType = getContainingType(currentAnno, ctx.pos.get(currentAnno), reportError);
if (currentContainerType == null) {
continue;
}
// Assert that the target Container is == for all repeated
// annos of the same annotation type, the types should
// come from the same Symbol, i.e. be '=='
Assert.check(targetContainerType == null || currentContainerType == targetContainerType);
targetContainerType = currentContainerType;
containerValueSymbol = validateContainer(targetContainerType, origAnnoType, ctx.pos.get(currentAnno));
if (containerValueSymbol == null) { // Check of CA type failed
// errors are already reported
continue;
}
repeated = repeated.prepend(currentAnno);
}
if (!repeated.isEmpty() && targetContainerType == null) {
log.error(ctx.pos.get(annotations.head), "duplicate.annotation.invalid.repeated", origAnnoType);
return null;
}
if (!repeated.isEmpty()) {
repeated = repeated.reverse();
DiagnosticPosition pos = ctx.pos.get(firstOccurrence);
TreeMaker m = make.at(pos);
Pair<MethodSymbol, Attribute> p =
new Pair<MethodSymbol, Attribute>(containerValueSymbol,
new Attribute.Array(arrayOfOrigAnnoType, repeated));
if (ctx.isTypeCompound) {
/* TODO: the following code would be cleaner:
Attribute.TypeCompound at = new Attribute.TypeCompound(targetContainerType, List.of(p),
((Attribute.TypeCompound)annotations.head).position);
JCTypeAnnotation annoTree = m.TypeAnnotation(at);
at = attributeTypeAnnotation(annoTree, targetContainerType, ctx.env);
*/
// However, we directly construct the TypeCompound to keep the
// direct relation to the contained TypeCompounds.
Attribute.TypeCompound at = new Attribute.TypeCompound(targetContainerType, List.of(p),
((Attribute.TypeCompound)annotations.head).position);
JCAnnotation annoTree = m.TypeAnnotation(at);
if (!chk.validateAnnotationDeferErrors(annoTree))
log.error(annoTree.pos(), Errors.DuplicateAnnotationInvalidRepeated(origAnnoType));
if (!chk.isTypeAnnotation(annoTree, isTypeParam)) {
log.error(pos, isTypeParam ? Errors.InvalidRepeatableAnnotationNotApplicable(targetContainerType, on)
: Errors.InvalidRepeatableAnnotationNotApplicableInContext(targetContainerType));
}
at.setSynthesized(true);
@SuppressWarnings("unchecked")
T x = (T) at;
return x;
} else {
Attribute.Compound c = new Attribute.Compound(targetContainerType, List.of(p));
JCAnnotation annoTree = m.Annotation(c);
if (!chk.annotationApplicable(annoTree, on)) {
log.error(annoTree.pos(),
Errors.InvalidRepeatableAnnotationNotApplicable(targetContainerType, on));
}
if (!chk.validateAnnotationDeferErrors(annoTree))
log.error(annoTree.pos(), "duplicate.annotation.invalid.repeated", origAnnoType);
c = attributeAnnotation(annoTree, targetContainerType, ctx.env);
c.setSynthesized(true);
@SuppressWarnings("unchecked")
T x = (T) c;
return x;
}
} else {
return null; // errors should have been reported elsewhere
}
}
Fetches the actual Type that should be the containing annotation.
/**
* Fetches the actual Type that should be the containing annotation.
*/
private Type getContainingType(Attribute.Compound currentAnno,
DiagnosticPosition pos,
boolean reportError)
{
Type origAnnoType = currentAnno.type;
TypeSymbol origAnnoDecl = origAnnoType.tsym;
// Fetch the Repeatable annotation from the current
// annotation's declaration, or null if it has none
Attribute.Compound ca = origAnnoDecl.getAnnotationTypeMetadata().getRepeatable();
if (ca == null) { // has no Repeatable annotation
if (reportError)
log.error(pos, "duplicate.annotation.missing.container", origAnnoType, syms.repeatableType);
return null;
}
return filterSame(extractContainingType(ca, pos, origAnnoDecl),
origAnnoType);
}
// returns null if t is same as 's', returns 't' otherwise
private Type filterSame(Type t, Type s) {
if (t == null || s == null) {
return t;
}
return types.isSameType(t, s) ? null : t;
}
Extract the actual Type to be used for a containing annotation. /** Extract the actual Type to be used for a containing annotation. */
private Type extractContainingType(Attribute.Compound ca,
DiagnosticPosition pos,
TypeSymbol annoDecl)
{
// The next three checks check that the Repeatable annotation
// on the declaration of the annotation type that is repeating is
// valid.
// Repeatable must have at least one element
if (ca.values.isEmpty()) {
log.error(pos, "invalid.repeatable.annotation", annoDecl);
return null;
}
Pair<MethodSymbol,Attribute> p = ca.values.head;
Name name = p.fst.name;
if (name != names.value) { // should contain only one element, named "value"
log.error(pos, "invalid.repeatable.annotation", annoDecl);
return null;
}
if (!(p.snd instanceof Attribute.Class)) { // check that the value of "value" is an Attribute.Class
log.error(pos, "invalid.repeatable.annotation", annoDecl);
return null;
}
return ((Attribute.Class)p.snd).getValue();
}
/* Validate that the suggested targetContainerType Type is a valid
* container type for repeated instances of originalAnnoType
* annotations. Return null and report errors if this is not the
* case, return the MethodSymbol of the value element in
* targetContainerType if it is suitable (this is needed to
* synthesize the container). */
private MethodSymbol validateContainer(Type targetContainerType,
Type originalAnnoType,
DiagnosticPosition pos) {
MethodSymbol containerValueSymbol = null;
boolean fatalError = false;
// Validate that there is a (and only 1) value method
Scope scope = targetContainerType.tsym.members();
int nr_value_elems = 0;
boolean error = false;
for(Symbol elm : scope.getSymbolsByName(names.value)) {
nr_value_elems++;
if (nr_value_elems == 1 &&
elm.kind == MTH) {
containerValueSymbol = (MethodSymbol)elm;
} else {
error = true;
}
}
if (error) {
log.error(pos,
"invalid.repeatable.annotation.multiple.values",
targetContainerType,
nr_value_elems);
return null;
} else if (nr_value_elems == 0) {
log.error(pos,
"invalid.repeatable.annotation.no.value",
targetContainerType);
return null;
}
// validate that the 'value' element is a method
// probably "impossible" to fail this
if (containerValueSymbol.kind != MTH) {
log.error(pos,
"invalid.repeatable.annotation.invalid.value",
targetContainerType);
fatalError = true;
}
// validate that the 'value' element has the correct return type
// i.e. array of original anno
Type valueRetType = containerValueSymbol.type.getReturnType();
Type expectedType = types.makeArrayType(originalAnnoType);
if (!(types.isArray(valueRetType) &&
types.isSameType(expectedType, valueRetType))) {
log.error(pos,
"invalid.repeatable.annotation.value.return",
targetContainerType,
valueRetType,
expectedType);
fatalError = true;
}
return fatalError ? null : containerValueSymbol;
}
private <T extends Attribute.Compound> T makeContainerAnnotation(List<T> toBeReplaced,
AnnotationContext<T> ctx, Symbol sym, boolean isTypeParam)
{
// Process repeated annotations
T validRepeated =
processRepeatedAnnotations(toBeReplaced, ctx, sym, isTypeParam);
if (validRepeated != null) {
// Check that the container isn't manually
// present along with repeated instances of
// its contained annotation.
ListBuffer<T> manualContainer = ctx.annotated.get(validRepeated.type.tsym);
if (manualContainer != null) {
log.error(ctx.pos.get(manualContainer.first()),
"invalid.repeatable.annotation.repeated.and.container.present",
manualContainer.first().type.tsym);
}
}
// A null return will delete the Placeholder
return validRepeated;
}
/********************
* Type annotations *
********************/
Attribute the list of annotations and enter them onto s.
/**
* Attribute the list of annotations and enter them onto s.
*/
public void enterTypeAnnotations(List<JCAnnotation> annotations, Env<AttrContext> env,
Symbol s, DiagnosticPosition deferPos, boolean isTypeParam)
{
Assert.checkNonNull(s, "Symbol argument to actualEnterTypeAnnotations is nul/");
JavaFileObject prev = log.useSource(env.toplevel.sourcefile);
DiagnosticPosition prevLintPos = null;
if (deferPos != null) {
prevLintPos = deferredLintHandler.setPos(deferPos);
}
try {
annotateNow(s, annotations, env, true, isTypeParam);
} finally {
if (prevLintPos != null)
deferredLintHandler.setPos(prevLintPos);
log.useSource(prev);
}
}
Enqueue tree for scanning of type annotations, attaching to the Symbol sym.
/**
* Enqueue tree for scanning of type annotations, attaching to the Symbol sym.
*/
public void queueScanTreeAndTypeAnnotate(JCTree tree, Env<AttrContext> env, Symbol sym,
DiagnosticPosition deferPos)
{
Assert.checkNonNull(sym);
normal(() -> tree.accept(new TypeAnnotate(env, sym, deferPos)));
}
Apply the annotations to the particular type.
/**
* Apply the annotations to the particular type.
*/
public void annotateTypeSecondStage(JCTree tree, List<JCAnnotation> annotations, Type storeAt) {
typeAnnotation(() -> {
List<Attribute.TypeCompound> compounds = fromAnnotations(annotations);
Assert.check(annotations.size() == compounds.size());
storeAt.getMetadataOfKind(Kind.ANNOTATIONS).combine(new TypeMetadata.Annotations(compounds));
});
}
Apply the annotations to the particular type.
/**
* Apply the annotations to the particular type.
*/
public void annotateTypeParameterSecondStage(JCTree tree, List<JCAnnotation> annotations) {
typeAnnotation(() -> {
List<Attribute.TypeCompound> compounds = fromAnnotations(annotations);
Assert.check(annotations.size() == compounds.size());
});
}
We need to use a TreeScanner, because it is not enough to visit the top-level
annotations. We also need to visit type arguments, etc.
/**
* We need to use a TreeScanner, because it is not enough to visit the top-level
* annotations. We also need to visit type arguments, etc.
*/
private class TypeAnnotate extends TreeScanner {
private final Env<AttrContext> env;
private final Symbol sym;
private DiagnosticPosition deferPos;
public TypeAnnotate(Env<AttrContext> env, Symbol sym, DiagnosticPosition deferPos) {
this.env = env;
this.sym = sym;
this.deferPos = deferPos;
}
@Override
public void visitAnnotatedType(JCAnnotatedType tree) {
enterTypeAnnotations(tree.annotations, env, sym, deferPos, false);
scan(tree.underlyingType);
}
@Override
public void visitTypeParameter(JCTypeParameter tree) {
enterTypeAnnotations(tree.annotations, env, sym, deferPos, true);
scan(tree.bounds);
}
@Override
public void visitNewArray(JCNewArray tree) {
enterTypeAnnotations(tree.annotations, env, sym, deferPos, false);
for (List<JCAnnotation> dimAnnos : tree.dimAnnotations)
enterTypeAnnotations(dimAnnos, env, sym, deferPos, false);
scan(tree.elemtype);
scan(tree.elems);
}
@Override
public void visitMethodDef(JCMethodDecl tree) {
scan(tree.mods);
scan(tree.restype);
scan(tree.typarams);
scan(tree.recvparam);
scan(tree.params);
scan(tree.thrown);
scan(tree.defaultValue);
// Do not annotate the body, just the signature.
}
@Override
public void visitVarDef(JCVariableDecl tree) {
DiagnosticPosition prevPos = deferPos;
deferPos = tree.pos();
try {
if (sym != null && sym.kind == VAR) {
// Don't visit a parameter once when the sym is the method
// and once when the sym is the parameter.
scan(tree.mods);
scan(tree.vartype);
}
scan(tree.init);
} finally {
deferPos = prevPos;
}
}
@Override
public void visitClassDef(JCClassDecl tree) {
// We can only hit a classdef if it is declared within
// a method. Ignore it - the class will be visited
// separately later.
}
@Override
public void visitNewClass(JCNewClass tree) {
scan(tree.encl);
scan(tree.typeargs);
scan(tree.clazz);
scan(tree.args);
// the anonymous class instantiation if any will be visited separately.
}
}
Completer support *
/*********************
* Completer support *
*********************/
private AnnotationTypeCompleter theSourceCompleter = new AnnotationTypeCompleter() {
@Override
public void complete(ClassSymbol sym) throws CompletionFailure {
Env<AttrContext> context = typeEnvs.get(sym);
Annotate.this.attributeAnnotationType(context);
}
};
/* Last stage completer to enter just enough annotations to have a prototype annotation type.
* This currently means entering @Target and @Repetable.
*/
public AnnotationTypeCompleter annotationTypeSourceCompleter() {
return theSourceCompleter;
}
private void attributeAnnotationType(Env<AttrContext> env) {
Assert.check(((JCClassDecl)env.tree).sym.isAnnotationType(),
"Trying to annotation type complete a non-annotation type");
JavaFileObject prev = log.useSource(env.toplevel.sourcefile);
try {
JCClassDecl tree = (JCClassDecl)env.tree;
AnnotationTypeVisitor v = new AnnotationTypeVisitor(attr, chk, syms, typeEnvs);
v.scanAnnotationType(tree);
tree.sym.getAnnotationTypeMetadata().setRepeatable(v.repeatable);
tree.sym.getAnnotationTypeMetadata().setTarget(v.target);
} finally {
log.useSource(prev);
}
}
public Attribute unfinishedDefaultValue() {
return theUnfinishedDefaultValue;
}
public static interface AnnotationTypeCompleter {
void complete(ClassSymbol sym) throws CompletionFailure;
}
Visitor to determine a prototype annotation type for a class declaring an annotation type.
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.
/** Visitor to determine a prototype annotation type for a class declaring an annotation type.
*
* <p><b>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.</b>
*/
public class AnnotationTypeVisitor extends TreeScanner {
private Env<AttrContext> env;
private final Attr attr;
private final Check check;
private final Symtab tab;
private final TypeEnvs typeEnvs;
private Compound target;
private Compound repeatable;
public AnnotationTypeVisitor(Attr attr, Check check, Symtab tab, TypeEnvs typeEnvs) {
this.attr = attr;
this.check = check;
this.tab = tab;
this.typeEnvs = typeEnvs;
}
public Compound getRepeatable() {
return repeatable;
}
public Compound getTarget() {
return target;
}
public void scanAnnotationType(JCClassDecl decl) {
visitClassDef(decl);
}
@Override
public void visitClassDef(JCClassDecl tree) {
Env<AttrContext> prevEnv = env;
env = typeEnvs.get(tree.sym);
try {
scan(tree.mods); // look for repeatable and target
// don't descend into body
} finally {
env = prevEnv;
}
}
@Override
public void visitAnnotation(JCAnnotation tree) {
Type t = tree.annotationType.type;
if (t == null) {
t = attr.attribType(tree.annotationType, env);
tree.annotationType.type = t = check.checkType(tree.annotationType.pos(), t, tab.annotationType);
}
if (t == tab.annotationTargetType) {
target = Annotate.this.attributeAnnotation(tree, tab.annotationTargetType, env);
} else if (t == tab.repeatableType) {
repeatable = Annotate.this.attributeAnnotation(tree, tab.repeatableType, env);
}
}
}
Represents the semantics of an Annotation Type.
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.
/** Represents the semantics of an Annotation Type.
*
* <p><b>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.</b>
*/
public static class AnnotationTypeMetadata {
final ClassSymbol metaDataFor;
private Compound target;
private Compound repeatable;
private AnnotationTypeCompleter annotationTypeCompleter;
public AnnotationTypeMetadata(ClassSymbol metaDataFor, AnnotationTypeCompleter annotationTypeCompleter) {
this.metaDataFor = metaDataFor;
this.annotationTypeCompleter = annotationTypeCompleter;
}
private void init() {
// Make sure metaDataFor is member entered
while (!metaDataFor.isCompleted())
metaDataFor.complete();
if (annotationTypeCompleter != null) {
AnnotationTypeCompleter c = annotationTypeCompleter;
annotationTypeCompleter = null;
c.complete(metaDataFor);
}
}
public void complete() {
init();
}
public Compound getRepeatable() {
init();
return repeatable;
}
public void setRepeatable(Compound repeatable) {
Assert.checkNull(this.repeatable);
this.repeatable = repeatable;
}
public Compound getTarget() {
init();
return target;
}
public void setTarget(Compound target) {
Assert.checkNull(this.target);
this.target = target;
}
public Set<MethodSymbol> getAnnotationElements() {
init();
Set<MethodSymbol> members = new LinkedHashSet<>();
WriteableScope s = metaDataFor.members();
Iterable<Symbol> ss = s.getSymbols(NON_RECURSIVE);
for (Symbol sym : ss)
if (sym.kind == MTH &&
sym.name != sym.name.table.names.clinit &&
(sym.flags() & SYNTHETIC) == 0)
members.add((MethodSymbol)sym);
return members;
}
public Set<MethodSymbol> getAnnotationElementsWithDefault() {
init();
Set<MethodSymbol> members = getAnnotationElements();
Set<MethodSymbol> res = new LinkedHashSet<>();
for (MethodSymbol m : members)
if (m.defaultValue != null)
res.add(m);
return res;
}
@Override
public String toString() {
return "Annotation type for: " + metaDataFor;
}
public boolean isMetadataForAnnotationType() { return true; }
public static AnnotationTypeMetadata notAnAnnotationType() {
return NOT_AN_ANNOTATION_TYPE;
}
private static final AnnotationTypeMetadata NOT_AN_ANNOTATION_TYPE =
new AnnotationTypeMetadata(null, null) {
@Override
public void complete() {
} // do nothing
@Override
public String toString() {
return "Not an annotation type";
}
@Override
public Set<MethodSymbol> getAnnotationElements() {
return new LinkedHashSet<>(0);
}
@Override
public Set<MethodSymbol> getAnnotationElementsWithDefault() {
return new LinkedHashSet<>(0);
}
@Override
public boolean isMetadataForAnnotationType() {
return false;
}
@Override
public Compound getTarget() {
return null;
}
@Override
public Compound getRepeatable() {
return null;
}
};
}
public void newRound() {
blockCount = 1;
}
}