org.openjdk.tools.javac.comp.Annotate Maven / Gradle / Ivy
Show all versions of javac-shaded Show documentation
/*
* 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 org.openjdk.tools.javac.comp;
import org.openjdk.tools.javac.code.*;
import org.openjdk.tools.javac.code.Attribute.Compound;
import org.openjdk.tools.javac.code.Attribute.TypeCompound;
import org.openjdk.tools.javac.code.Scope.WriteableScope;
import org.openjdk.tools.javac.code.Symbol.*;
import org.openjdk.tools.javac.code.TypeMetadata.Entry.Kind;
import org.openjdk.tools.javac.resources.CompilerProperties.Errors;
import org.openjdk.tools.javac.tree.JCTree;
import org.openjdk.tools.javac.tree.JCTree.*;
import org.openjdk.tools.javac.tree.TreeInfo;
import org.openjdk.tools.javac.tree.TreeMaker;
import org.openjdk.tools.javac.tree.TreeScanner;
import org.openjdk.tools.javac.util.*;
import org.openjdk.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import org.openjdk.tools.javac.util.List;
import javax.tools.JavaFileObject;
import java.util.*;
import static org.openjdk.tools.javac.code.Flags.SYNTHETIC;
import static org.openjdk.tools.javac.code.Kinds.Kind.MDL;
import static org.openjdk.tools.javac.code.Kinds.Kind.MTH;
import static org.openjdk.tools.javac.code.Kinds.Kind.PCK;
import static org.openjdk.tools.javac.code.Kinds.Kind.VAR;
import static org.openjdk.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
import static org.openjdk.tools.javac.code.TypeTag.ARRAY;
import static org.openjdk.tools.javac.code.TypeTag.CLASS;
import static org.openjdk.tools.javac.tree.JCTree.Tag.ANNOTATION;
import static org.openjdk.tools.javac.tree.JCTree.Tag.ASSIGN;
import static org.openjdk.tools.javac.tree.JCTree.Tag.IDENT;
import static org.openjdk.tools.javac.tree.JCTree.Tag.NEWARRAY;
import org.openjdk.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.
*/
public class Annotate {
protected static final Context.Key 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 */
private int blockCount = 0;
/** Called when annotations processing needs to be postponed. */
public void blockAnnotations() {
blockCount++;
}
/** 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 */
public void unblockAnnotationsNoFlush() {
blockCount--;
}
/** are we blocking annotation processing? */
public boolean annotationsBlocked() {return blockCount > 0; }
public void enterDone() {
unblockAnnotations();
}
public List fromAnnotations(List annotations) {
if (annotations.isEmpty()) {
return List.nil();
}
ListBuffer buf = new ListBuffer<>();
for (JCAnnotation anno : annotations) {
Assert.checkNonNull(anno.attribute);
buf.append((TypeCompound) anno.attribute);
}
return buf.toList();
}
/** Annotate (used for everything else) */
public void normal(Runnable r) {
q.append(r);
}
/** Validate, triggers after 'normal' */
public void validate(Runnable a) {
validateQ.append(a);
}
/** 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 q = new ListBuffer<>();
private ListBuffer validateQ = new ListBuffer<>();
private int flushCount = 0;
private boolean isFlushing() { return flushCount > 0; }
private void startFlushing() { flushCount++; }
private void doneFlushing() { flushCount--; }
ListBuffer typesQ = new ListBuffer<>();
ListBuffer 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.
*
* @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 annotations, Env 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. */
public void annotateDefaultValueLater(JCExpression defaultValue, Env 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. */
private void enterDefaultValue(JCExpression defaultValue,
Env 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.
*/
private void annotateNow(Symbol toAnnotate,
List withAnnotations, Env env, boolean typeAnnotations,
boolean isTypeParam)
{
Map> annotated = new LinkedHashMap<>();
Map pos = new HashMap<>();
for (List 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 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 buf = List.nil();
for (ListBuffer lb : annotated.values()) {
if (lb.size() == 1) {
buf = buf.prepend(lb.first());
} else {
AnnotationContext 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 attrs = (List)buf.reverse();
toAnnotate.appendUniqueTypeAttributes(attrs);
} else {
@SuppressWarnings("unchecked")
List attrs = (List)buf.reverse();
toAnnotate.resetAnnotations();
toAnnotate.setDeclarationAttributes(attrs);
}
}
/**
* 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 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> 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 {@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 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> 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. */
private List> attributeAnnotationValues(JCAnnotation a,
Type expected, Env 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 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> buf = new ListBuffer<>();
for (List tl = args; tl.nonEmpty(); tl = tl.tail) {
Pair 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 attributeAnnotationNameValuePair(JCExpression nameValuePair,
Type thisAnnotationType, boolean badAnnotation, Env 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 */
private Attribute attributeAnnotationValue(Type expectedElementType, JCExpression tree,
Env 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 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 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 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 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 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 buf = new ListBuffer<>();
for (List 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.
*/
private class AnnotationContext {
public final Env env;
public final Map> annotated;
public final Map pos;
public final boolean isTypeCompound;
public AnnotationContext(Env env,
Map> annotated,
Map 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 processRepeatedAnnotations(List annotations,
AnnotationContext ctx, Symbol on, boolean isTypeParam)
{
T firstOccurrence = annotations.head;
List 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 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 p =
new Pair(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.
*/
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. */
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 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 makeContainerAnnotation(List toBeReplaced,
AnnotationContext 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 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.
*/
public void enterTypeAnnotations(List annotations, Env 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.
*/
public void queueScanTreeAndTypeAnnotate(JCTree tree, Env env, Symbol sym,
DiagnosticPosition deferPos)
{
Assert.checkNonNull(sym);
normal(() -> tree.accept(new TypeAnnotate(env, sym, deferPos)));
}
/**
* Apply the annotations to the particular type.
*/
public void annotateTypeSecondStage(JCTree tree, List annotations, Type storeAt) {
typeAnnotation(() -> {
List 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.
*/
public void annotateTypeParameterSecondStage(JCTree tree, List annotations) {
typeAnnotation(() -> {
List 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.
*/
private class TypeAnnotate extends TreeScanner {
private final Env env;
private final Symbol sym;
private DiagnosticPosition deferPos;
public TypeAnnotate(Env 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 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 *
*********************/
private AnnotationTypeCompleter theSourceCompleter = new AnnotationTypeCompleter() {
@Override
public void complete(ClassSymbol sym) throws CompletionFailure {
Env 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 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.
*/
public class AnnotationTypeVisitor extends TreeScanner {
private Env 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 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.
*/
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 getAnnotationElements() {
init();
Set members = new LinkedHashSet<>();
WriteableScope s = metaDataFor.members();
Iterable 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 getAnnotationElementsWithDefault() {
init();
Set members = getAnnotationElements();
Set 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 getAnnotationElements() {
return new LinkedHashSet<>(0);
}
@Override
public Set 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;
}
}