/*
* Copyright (c) 2009, 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.code;
import static com.sun.tools.javac.code.Flags.ANNOTATION;
import static com.sun.tools.javac.code.Flags.PARAMETER;
import static com.sun.tools.javac.code.Kinds.*;
import static com.sun.tools.javac.code.TypeTags.VOID;
import javax.lang.model.element.ElementKind;
import com.sun.tools.javac.code.Attribute.Compound;
import com.sun.tools.javac.code.Attribute.TypeCompound;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type.ArrayType;
import com.sun.tools.javac.comp.Annotate.Annotator;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;
/**
* Contains operations specific to processing type annotations
*/
public class TypeAnnotations {
private static final Context.Key<TypeAnnotations> key
= new Context.Key<TypeAnnotations>();
public static TypeAnnotations instance(Context context) {
TypeAnnotations instance = context.get(key);
if (instance == null)
instance = new TypeAnnotations(context);
return instance;
}
private final Symtab syms;
private final Names names;
protected TypeAnnotations(Context context) {
context.put(key, this);
syms = Symtab.instance(context);
names = Names.instance(context);
}
public Annotator annotator(final JCClassDecl tree) {
return new Annotator() {
@Override
public void enterAnnotation() {
taFillAndLift(tree, false);
}
};
}
public void taFillAndLift(List<JCCompilationUnit> trees, boolean visitBodies) {
for (JCCompilationUnit tree : trees) {
for (JCTree def : tree.defs) {
if (def.getTag() == JCTree.CLASSDEF)
taFillAndLift((JCClassDecl)def, visitBodies);
}
}
}
public void taFillAndLift(JCClassDecl tree, boolean visitBodies) {
new AnnotationsKindSeparator(visitBodies).scan(tree);
new TypeAnnotationPositions(visitBodies).scan(tree);
new TypeAnnotationLift(visitBodies).scan(tree);
}
enum AnnotationType { DECLARATION, TYPE, BOTH };
/**
* Separates type annotations from declaration annotations
*/
private class AnnotationsKindSeparator extends TreeScanner {
private final boolean visitBodies;
public AnnotationsKindSeparator(boolean visitBodies) {
this.visitBodies = visitBodies;
}
// each class (including enclosed inner classes) should be visited
// separately through MemberEnter.complete(Symbol)
// this flag is used to prevent from visiting inner classes.
private boolean isInner = false;
@Override
public void visitClassDef(final JCClassDecl tree) {
if (isInner)
return;
isInner = true;
super.visitClassDef(tree);
}
@Override
public void visitMethodDef(final JCMethodDecl tree) {
// clear all annotations
if (!visitBodies) {
if (!areAllDecl(tree.sym))
separateAnnotationsKinds(tree.sym, tree.sym.type.getReturnType(),
new TypeAnnotationPosition(TargetType.METHOD_RETURN));
int i = 0;
for (JCVariableDecl param : tree.params) {
if (!areAllDecl(param.sym)) {
TypeAnnotationPosition pos =
new TypeAnnotationPosition(TargetType.METHOD_PARAMETER);
pos.parameter_index = i;
separateAnnotationsKinds(param.sym, param.sym.type, pos);
}
++i;
}
}
super.visitMethodDef(tree);
}
@Override
public void visitVarDef(final JCVariableDecl tree) {
if (!visitBodies && !areAllDecl(tree.sym)) {
if (tree.sym.getKind() == ElementKind.FIELD) {
separateAnnotationsKinds(tree.sym, tree.sym.type,
new TypeAnnotationPosition(TargetType.FIELD));
} else if (tree.sym.getKind() == ElementKind.LOCAL_VARIABLE) {
TypeAnnotationPosition pos = new TypeAnnotationPosition(TargetType.LOCAL_VARIABLE);
pos.pos = tree.pos;
separateAnnotationsKinds(tree.sym, tree.sym.type, pos);
}
}
super.visitVarDef(tree);
}
@Override
public void visitBlock(final JCBlock tree) {
if (visitBodies)
super.visitBlock(tree);
}
}
private static class TypeAnnotationPositions extends TreeScanner {
private final boolean visitBodies;
TypeAnnotationPositions(boolean visitBodies) {
this.visitBodies = visitBodies;
}
private ListBuffer<JCTree> frames = ListBuffer.lb();
private void push(JCTree t) { frames = frames.prepend(t); }
private JCTree pop() { return frames.next(); }
private JCTree peek2() { return frames.toList().tail.head; }
@Override
public void scan(JCTree tree) {
push(tree);
super.scan(tree);
pop();
}
// each class (including enclosed inner classes) should be visited
// separately through MemberEnter.complete(Symbol)
// this flag is used to prevent from visiting inner classes.
private boolean isInner = false;
@Override
public void visitClassDef(final JCClassDecl tree) {
if (isInner)
return;
isInner = true;
super.visitClassDef(tree);
}
private TypeAnnotationPosition resolveFrame(JCTree tree, JCTree frame,
List<JCTree> path, TypeAnnotationPosition p) {
switch (frame.getKind()) {
case TYPE_CAST:
p.type = TargetType.TYPECAST;
p.pos = frame.pos;
return p;
case INSTANCE_OF:
p.type = TargetType.INSTANCEOF;
p.pos = frame.pos;
return p;
case NEW_CLASS:
JCNewClass frameNewClass = (JCNewClass)frame;
if (frameNewClass.typeargs.contains(tree)) {
p.type = TargetType.NEW_TYPE_ARGUMENT;
p.type_index = frameNewClass.typeargs.indexOf(tree);
} else {
p.type = TargetType.NEW;
}
p.pos = frame.pos;
return p;
case NEW_ARRAY:
p.type = TargetType.NEW;
p.pos = frame.pos;
return p;
case CLASS:
p.pos = frame.pos;
if (((JCClassDecl)frame).extending == tree) {
p.type = TargetType.CLASS_EXTENDS;
p.type_index = -1;
} else if (((JCClassDecl)frame).implementing.contains(tree)) {
p.type = TargetType.CLASS_EXTENDS;
p.type_index = ((JCClassDecl)frame).implementing.indexOf(tree);
} else if (((JCClassDecl)frame).typarams.contains(tree)) {
p.type = TargetType.CLASS_TYPE_PARAMETER;
p.parameter_index = ((JCClassDecl)frame).typarams.indexOf(tree);
} else
throw new AssertionError();
return p;
case METHOD: {
JCMethodDecl frameMethod = (JCMethodDecl)frame;
p.pos = frame.pos;
if (frameMethod.receiverAnnotations.contains(tree))
p.type = TargetType.METHOD_RECEIVER;
else if (frameMethod.thrown.contains(tree)) {
p.type = TargetType.THROWS;
p.type_index = frameMethod.thrown.indexOf(tree);
} else if (((JCMethodDecl)frame).restype == tree) {
p.type = TargetType.METHOD_RETURN;
} else if (frameMethod.typarams.contains(tree)) {
p.type = TargetType.METHOD_TYPE_PARAMETER;
p.parameter_index = frameMethod.typarams.indexOf(tree);
} else
throw new AssertionError();
return p;
}
case MEMBER_SELECT: {
JCFieldAccess fieldFrame = (JCFieldAccess)frame;
if ("class".contentEquals(fieldFrame.name)) {
p.type = TargetType.CLASS_LITERAL;
p.pos = TreeInfo.innermostType(fieldFrame.selected).pos;
} else
throw new AssertionError();
return p;
}
case PARAMETERIZED_TYPE: {
if (((JCTypeApply)frame).clazz == tree)
{ } // generic: RAW; noop
else if (((JCTypeApply)frame).arguments.contains(tree))
p.location = p.location.prepend(
((JCTypeApply)frame).arguments.indexOf(tree));
else
throw new AssertionError();
List<JCTree> newPath = path.tail;
return resolveFrame(newPath.head, newPath.tail.head, newPath, p);
}
case ARRAY_TYPE: {
int index = 0;
List<JCTree> newPath = path.tail;
while (true) {
JCTree npHead = newPath.tail.head;
if (npHead.getTag() == JCTree.TYPEARRAY) {
newPath = newPath.tail;
index++;
} else if (npHead.getTag() == JCTree.ANNOTATED_TYPE) {
newPath = newPath.tail;
} else {
break;
}
}
p.location = p.location.prepend(index);
return resolveFrame(newPath.head, newPath.tail.head, newPath, p);
}
case TYPE_PARAMETER:
if (path.tail.tail.head.getTag() == JCTree.CLASSDEF) {
JCClassDecl clazz = (JCClassDecl)path.tail.tail.head;
p.type = TargetType.CLASS_TYPE_PARAMETER_BOUND;
p.parameter_index = clazz.typarams.indexOf(path.tail.head);
p.bound_index = ((JCTypeParameter)frame).bounds.indexOf(tree);
} else if (path.tail.tail.head.getTag() == JCTree.METHODDEF) {
JCMethodDecl method = (JCMethodDecl)path.tail.tail.head;
p.type = TargetType.METHOD_TYPE_PARAMETER_BOUND;
p.parameter_index = method.typarams.indexOf(path.tail.head);
p.bound_index = ((JCTypeParameter)frame).bounds.indexOf(tree);
} else
throw new AssertionError();
p.pos = frame.pos;
return p;
case VARIABLE:
VarSymbol v = ((JCVariableDecl)frame).sym;
p.pos = frame.pos;
switch (v.getKind()) {
case LOCAL_VARIABLE:
p.type = TargetType.LOCAL_VARIABLE; break;
case FIELD:
p.type = TargetType.FIELD; break;
case PARAMETER:
p.type = TargetType.METHOD_PARAMETER;
p.parameter_index = methodParamIndex(path, frame);
break;
default: throw new AssertionError();
}
return p;
case ANNOTATED_TYPE: {
List<JCTree> newPath = path.tail;
return resolveFrame(newPath.head, newPath.tail.head,
newPath, p);
}
case METHOD_INVOCATION: {
JCMethodInvocation invocation = (JCMethodInvocation)frame;
if (!invocation.typeargs.contains(tree))
throw new AssertionError("{" + tree + "} is not an argument in the invocation: " + invocation);
p.type = TargetType.METHOD_TYPE_ARGUMENT;
p.pos = invocation.pos;
p.type_index = invocation.typeargs.indexOf(tree);
return p;
}
case EXTENDS_WILDCARD:
case SUPER_WILDCARD: {
p.type = TargetType.WILDCARD_BOUND;
List<JCTree> newPath = path.tail;
TypeAnnotationPosition wildcard =
resolveFrame(newPath.head, newPath.tail.head, newPath,
new TypeAnnotationPosition());
if (!wildcard.location.isEmpty())
wildcard.type = wildcard.type.getGenericComplement();
p.wildcard_position = wildcard;
p.pos = frame.pos;
return p;
}
}
return p;
}
private void setTypeAnnotationPos(List<JCTypeAnnotation> annotations, TypeAnnotationPosition position) {
for (JCTypeAnnotation anno : annotations) {
anno.annotation_position = position;
anno.attribute_field.position = position;
}
}
@Override
public void visitNewArray(JCNewArray tree) {
findPosition(tree, tree, tree.annotations);
int dimAnnosCount = tree.dimAnnotations.size();
// handle annotations associated with dimentions
for (int i = 0; i < dimAnnosCount; ++i) {
TypeAnnotationPosition p = new TypeAnnotationPosition();
p.pos = tree.pos;
if (i == 0) {
p.type = TargetType.NEW;
} else {
p.type = TargetType.NEW_GENERIC_OR_ARRAY;
p.location = p.location.append(i - 1);
}
setTypeAnnotationPos(tree.dimAnnotations.get(i), p);
}
// handle "free" annotations
int i = dimAnnosCount == 0 ? 0 : dimAnnosCount - 1;
JCExpression elemType = tree.elemtype;
while (elemType != null) {
if (elemType.getTag() == JCTree.ANNOTATED_TYPE) {
JCAnnotatedType at = (JCAnnotatedType)elemType;
TypeAnnotationPosition p = new TypeAnnotationPosition();
p.type = TargetType.NEW_GENERIC_OR_ARRAY;
p.pos = tree.pos;
p.location = p.location.append(i);
setTypeAnnotationPos(at.annotations, p);
elemType = at.underlyingType;
} else if (elemType.getTag() == JCTree.TYPEARRAY) {
++i;
elemType = ((JCArrayTypeTree)elemType).elemtype;
} else
break;
}
// TODO: Is this needed?
scan(tree.elems);
}
@Override
public void visitAnnotatedType(JCAnnotatedType tree) {
findPosition(tree, peek2(), tree.annotations);
super.visitAnnotatedType(tree);
}
@Override
public void visitMethodDef(JCMethodDecl tree) {
super.visitMethodDef(tree);
TypeAnnotationPosition p = new TypeAnnotationPosition();
p.type = TargetType.METHOD_RECEIVER;
setTypeAnnotationPos(tree.receiverAnnotations, p);
}
@Override
public void visitBlock(JCBlock tree) {
if (visitBodies)
super.visitBlock(tree);
}
@Override
public void visitTypeParameter(JCTypeParameter tree) {
findPosition(tree, peek2(), tree.annotations);
super.visitTypeParameter(tree);
}
void findPosition(JCTree tree, JCTree frame, List<JCTypeAnnotation> annotations) {
if (!annotations.isEmpty()) {
TypeAnnotationPosition p =
resolveFrame(tree, frame, frames.toList(),
new TypeAnnotationPosition());
if (!p.location.isEmpty())
p.type = p.type.getGenericComplement();
setTypeAnnotationPos(annotations, p);
}
}
private int methodParamIndex(List<JCTree> path, JCTree param) {
List<JCTree> curr = path;
if (curr.head != param)
curr = path.tail;
JCMethodDecl method = (JCMethodDecl)curr.tail.head;
return method.params.indexOf(param);
}
}
private static class TypeAnnotationLift extends TreeScanner {
List<Attribute.TypeCompound> recordedTypeAnnotations = List.nil();
// TODO: Find a better of handling this
// Handle cases where the symbol typeAnnotation is filled multiple times
private static <T> List<T> appendUnique(List<T> l1, List<T> l2) {
if (l1.isEmpty() || l2.isEmpty())
return l1.appendList(l2);
ListBuffer<T> buf = ListBuffer.lb();
buf.appendList(l1);
for (T i : l2) {
if (!l1.contains(i))
buf.append(i);
}
return buf.toList();
}
private final boolean visitBodies;
TypeAnnotationLift(boolean visitBodies) {
this.visitBodies = visitBodies;
}
boolean isInner = false;
@Override
public void visitClassDef(JCClassDecl tree) {
if (isInner) {
// tree is an inner class tree. stop now.
// TransTypes.visitClassDef makes an invocation for each class
// separately.
return;
}
isInner = true;
List<Attribute.TypeCompound> prevTAs = recordedTypeAnnotations;
recordedTypeAnnotations = List.nil();
try {
super.visitClassDef(tree);
} finally {
tree.sym.typeAnnotations = appendUnique(tree.sym.typeAnnotations, recordedTypeAnnotations);
recordedTypeAnnotations = prevTAs;
}
}
@Override
public void visitMethodDef(JCMethodDecl tree) {
List<Attribute.TypeCompound> prevTAs = recordedTypeAnnotations;
recordedTypeAnnotations = List.nil();
try {
super.visitMethodDef(tree);
} finally {
tree.sym.typeAnnotations = appendUnique(tree.sym.typeAnnotations, recordedTypeAnnotations);
recordedTypeAnnotations = prevTAs;
}
}
@Override
public void visitBlock(JCBlock tree) {
if (visitBodies)
super.visitBlock(tree);
}
private boolean isCatchParameter = false;
@Override
public void visitCatch(JCCatch tree) {
isCatchParameter = true;
scan(tree.param);
isCatchParameter = false;
scan(tree.body);
}
@Override
public void visitVarDef(JCVariableDecl tree) {
List<Attribute.TypeCompound> prevTAs = recordedTypeAnnotations;
recordedTypeAnnotations = List.nil();
ElementKind kind = tree.sym.getKind();
if (tree.mods.annotations.nonEmpty()
&& (kind == ElementKind.LOCAL_VARIABLE || isCatchParameter)) {
// need to lift the annotations
TypeAnnotationPosition position = new TypeAnnotationPosition();
position.pos = tree.pos;
position.type = TargetType.LOCAL_VARIABLE;
for (Attribute.Compound attribute : tree.sym.attributes_field) {
Attribute.TypeCompound tc =
new Attribute.TypeCompound(attribute.type, attribute.values, position);
recordedTypeAnnotations = recordedTypeAnnotations.append(tc);
}
}
try {
// copied from super.visitVarDef. need to skip tree.init
scan(tree.mods);
scan(tree.vartype);
if (visitBodies)
scan(tree.init);
} finally {
if (kind.isField() || kind == ElementKind.LOCAL_VARIABLE || isCatchParameter)
tree.sym.typeAnnotations = appendUnique(tree.sym.typeAnnotations, recordedTypeAnnotations);
recordedTypeAnnotations = kind.isField() ? prevTAs : prevTAs.appendList(recordedTypeAnnotations);
}
}
@Override
public void visitApply(JCMethodInvocation tree) {
scan(tree.meth);
scan(tree.typeargs);
scan(tree.args);
}
public void visitAnnotation(JCAnnotation tree) {
if (tree instanceof JCTypeAnnotation)
recordedTypeAnnotations = recordedTypeAnnotations.append(((JCTypeAnnotation)tree).attribute_field);
super.visitAnnotation(tree);
}
}
private void separateAnnotationsKinds(Symbol sym, Type type, TypeAnnotationPosition pos) {
List<Compound> annotations = sym.attributes_field;
ListBuffer<Compound> declAnnos = new ListBuffer<Compound>();
ListBuffer<TypeCompound> typeAnnos = new ListBuffer<TypeCompound>();
for (Compound a : annotations) {
switch (annotationType(a, sym)) {
case DECLARATION:
declAnnos.append(a);
break;
case BOTH: {
declAnnos.append(a);
TypeCompound ta = toTypeCompound(a, pos);
typeAnnos.append(ta);
break;
}
case TYPE: {
TypeCompound ta = toTypeCompound(a, pos);
typeAnnos.append(ta);
break;
}
}
}
sym.attributes_field = declAnnos.toList();
List<TypeCompound> typeAnnotations = typeAnnos.toList();
Type atype = typeWithAnnotations(type, typeAnnotations);
if (sym.getKind() == ElementKind.METHOD) {
sym.type.asMethodType().restype = atype;
} else
sym.type = atype;
sym.typeAnnotations = sym.typeAnnotations.appendList(typeAnnotations);
if (sym.getKind() == ElementKind.PARAMETER
|| sym.getKind() == ElementKind.LOCAL_VARIABLE)
sym.owner.typeAnnotations = sym.owner.typeAnnotations.appendList(typeAnnotations);
}
private Type typeWithAnnotations(Type type, List<TypeCompound> annotations) {
if (type.tag != TypeTags.ARRAY) {
Type atype = (Type)type.clone();
atype.typeAnnotations = List.convert(Compound.class, annotations);
return atype;
} else {
ArrayType arType = (ArrayType)type;
int depth = 0;
while (arType.elemtype.tag == TypeTags.ARRAY) {
arType = (ArrayType)arType.elemtype;
depth++;
}
arType.elemtype = typeWithAnnotations(arType.elemtype, annotations);
for (TypeCompound a : annotations) {
TypeAnnotationPosition p = a.position;
p.location = p.location.append(depth);
p.type = p.type.getGenericComplement();
}
}
return type;
}
private TypeCompound toTypeCompound(Compound a, TypeAnnotationPosition p) {
return new TypeCompound(a, p.clone());
}
private boolean areAllDecl(Symbol s) {
for (Compound a : s.attributes_field) {
if (annotationType(a, s) != AnnotationType.DECLARATION)
return false;
}
return true;
}
AnnotationType annotationType(Compound a, Symbol s) {
Attribute.Compound atTarget =
a.type.tsym.attribute(syms.annotationTargetType.tsym);
if (atTarget == null) return inferTargetMetaInfo(a, s);
Attribute atValue = atTarget.member(names.value);
if (!(atValue instanceof Attribute.Array)) return AnnotationType.DECLARATION; // error recovery
Attribute.Array arr = (Attribute.Array) atValue;
boolean isDecl = false, isType = false;
for (Attribute app : arr.values) {
if (!(app instanceof Attribute.Enum)) return AnnotationType.DECLARATION; // recovery
Attribute.Enum e = (Attribute.Enum) app;
if (e.value.name == names.TYPE)
{ if (s.kind == TYP) isDecl = true; }
else if (e.value.name == names.FIELD)
{ if (s.kind == VAR && s.owner.kind != MTH) isDecl = true; }
else if (e.value.name == names.METHOD)
{ if (s.kind == MTH && !s.isConstructor()) isDecl = true; }
else if (e.value.name == names.PARAMETER)
{ if (s.kind == VAR &&
s.owner.kind == MTH &&
(s.flags() & PARAMETER) != 0)
isDecl = true;
}
else if (e.value.name == names.CONSTRUCTOR)
{ if (s.kind == MTH && s.isConstructor()) isDecl = true; }
else if (e.value.name == names.LOCAL_VARIABLE)
{ if (s.kind == VAR && s.owner.kind == MTH &&
(s.flags() & PARAMETER) == 0)
isDecl = true;
}
else if (e.value.name == names.ANNOTATION_TYPE)
{ if (s.kind == TYP && (s.flags() & ANNOTATION) != 0)
isDecl = true;
}
else if (e.value.name == names.PACKAGE)
{ if (s.kind == PCK) isDecl = true; }
else if (e.value.name == names.TYPE_USE)
{ if (s.kind == TYP ||
s.kind == VAR ||
(s.kind == MTH && !s.isConstructor() &&
s.type.getReturnType().tag != VOID))
isType = true;
}
else
return AnnotationType.DECLARATION; // recovery
}
if (isDecl && isType)
return AnnotationType.BOTH;
else
return isType ? AnnotationType.TYPE : AnnotationType.DECLARATION;
}
private AnnotationType inferTargetMetaInfo(Compound a, Symbol s) {
if (s.kind == TYP || s.kind == VAR
|| (s.kind == MTH && !s.isConstructor() &&
s.type.getReturnType().tag != VOID))
return AnnotationType.BOTH;
else
return AnnotationType.DECLARATION;
}
}