[java] type annotations in class files

Step 2: apply top-level field/method/parameter annotations to types.
This commit is contained in:
Roman Shevchenko
2016-06-06 17:24:03 +03:00
parent b4c5a3cfae
commit a2497edcee
9 changed files with 195 additions and 123 deletions
@@ -17,6 +17,7 @@ package com.intellij.psi.impl.compiled;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiSubstitutorImpl;
import com.intellij.psi.impl.ResolveScopeManager;
@@ -37,9 +38,10 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Map;
public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements PsiJavaCodeReferenceElement {
public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements PsiAnnotatedJavaCodeReferenceElement {
private final PsiElement myParent;
private final String myCanonicalText;
private final String myQualifiedName;
@@ -89,6 +91,28 @@ public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements P
return myCanonicalText;
}
@NotNull
@Override
public String getCanonicalText(boolean annotated, @Nullable PsiAnnotation[] annotations) {
String text = getCanonicalText();
if (!annotated || annotations == null) return text;
StringBuilder sb = new StringBuilder();
String prefix = getOuterClassRef(text);
int tailStart = 0;
if (!StringUtil.isEmpty(prefix)) {
sb.append(prefix).append('.');
tailStart = prefix.length() + 1;
}
PsiNameHelper.appendAnnotations(sb, Arrays.asList(annotations), true);
sb.append(text, tailStart, text.length());
return sb.toString();
}
private static class Resolver implements ResolveCache.PolyVariantContextResolver<ClsJavaCodeReferenceElementImpl> {
public static final Resolver INSTANCE = new Resolver();
@@ -332,4 +356,4 @@ public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements P
public PsiElement getQualifier() {
return null;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import com.intellij.psi.impl.PsiJavaParserFacadeImpl;
import com.intellij.psi.impl.cache.TypeInfo;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.JavaSharedImplUtil;
import com.intellij.psi.impl.source.tree.TreeElement;
import org.jetbrains.annotations.NotNull;
@@ -148,6 +149,12 @@ public class ClsTypeElementImpl extends ClsElementImpl implements PsiTypeElement
@NotNull
private PsiType calculateType() {
PsiModifierList modifierList = myParent instanceof PsiModifierListOwner ? ((PsiModifierListOwner)myParent).getModifierList() : null;
return JavaSharedImplUtil.applyAnnotations(calculateBaseType(), modifierList);
}
@NotNull
private PsiType calculateBaseType() {
PsiType result = PsiJavaParserFacadeImpl.getPrimitiveType(myTypeText);
if (result != null) return result;
@@ -228,4 +235,4 @@ public class ClsTypeElementImpl extends ClsElementImpl implements PsiTypeElement
public String toString() {
return "PsiTypeElement:" + getText();
}
}
}
@@ -18,7 +18,6 @@ package com.intellij.psi.impl.source;
import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.navigation.ItemPresentationProviders;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
@@ -42,14 +41,9 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
public class PsiFieldImpl extends JavaStubPsiElement<PsiFieldStub> implements PsiField, PsiVariableEx, Queryable {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PsiFieldImpl");
private volatile SoftReference<PsiType> myCachedType;
private volatile Object myCachedInitializerValue; // PsiExpression on constant value for literal
@@ -125,26 +119,25 @@ public class PsiFieldImpl extends JavaStubPsiElement<PsiFieldStub> implements Ps
@Override
@NotNull
@SuppressWarnings("Duplicates")
public PsiType getType() {
final PsiFieldStub stub = getStub();
PsiFieldStub stub = getStub();
if (stub != null) {
PsiType type = SoftReference.dereference(myCachedType);
if (type != null) return type;
String typeText = TypeInfo.createTypeText(stub.getType(true));
try {
if (type == null) {
String typeText = TypeInfo.createTypeText(stub.getType(false));
assert typeText != null : stub;
type = JavaPsiFacade.getInstance(getProject()).getParserFacade().createTypeFromText(typeText, this);
type = JavaSharedImplUtil.applyAnnotations(type, getModifierList());
myCachedType = new SoftReference<PsiType>(type);
return type;
}
catch (IncorrectOperationException e) {
LOG.error(e);
return null;
}
return type;
}
myCachedType = null;
return JavaSharedImplUtil.getType(getTypeElement(), getNameIdentifier());
PsiTypeElement typeElement = getTypeElement();
assert typeElement != null : Arrays.toString(getChildren());
return JavaSharedImplUtil.getType(typeElement, getNameIdentifier());
}
@Override
@@ -396,7 +389,8 @@ public class PsiFieldImpl extends JavaStubPsiElement<PsiFieldStub> implements Ps
return true;
}
public String toString(){
@Override
public String toString() {
return "PsiField:" + getName();
}
@@ -442,5 +436,4 @@ public class PsiFieldImpl extends JavaStubPsiElement<PsiFieldStub> implements Ps
protected boolean isVisibilitySupported() {
return true;
}
}
}
@@ -19,7 +19,6 @@ import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.navigation.ItemPresentationProviders;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
@@ -50,8 +49,6 @@ import java.util.List;
import java.util.Map;
public class PsiMethodImpl extends JavaStubPsiElement<PsiMethodStub> implements PsiMethod, Queryable {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PsiMethodImpl");
private SoftReference<PsiType> myCachedType;
public PsiMethodImpl(final PsiMethodStub stub) {
@@ -190,30 +187,22 @@ public class PsiMethodImpl extends JavaStubPsiElement<PsiMethodStub> implements
public PsiType getReturnType() {
if (isConstructor()) return null;
final PsiMethodStub stub = getStub();
PsiMethodStub stub = getStub();
if (stub != null) {
PsiType type = SoftReference.dereference(myCachedType);
if (type != null) return type;
final String typeText = TypeInfo.createTypeText(stub.getReturnTypeText(true));
if (typeText == null) return null;
try {
type = JavaPsiFacade.getInstance(getProject()).getElementFactory().createTypeFromText(typeText, this);
if (type == null) {
String typeText = TypeInfo.createTypeText(stub.getReturnTypeText(false));
assert typeText != null : stub;
type = JavaPsiFacade.getInstance(getProject()).getParserFacade().createTypeFromText(typeText, this);
type = JavaSharedImplUtil.applyAnnotations(type, getModifierList());
myCachedType = new SoftReference<PsiType>(type);
return type;
}
catch (IncorrectOperationException e) {
LOG.error("stub: " + stub + "; method: " + getText(), e);
return null;
}
return type;
}
myCachedType = null;
PsiTypeElement typeElement = getReturnTypeElement();
if (typeElement == null) return null;
PsiParameterList parameterList = getParameterList();
return JavaSharedImplUtil.getType(typeElement, parameterList);
return typeElement != null ? JavaSharedImplUtil.getType(typeElement, getParameterList()) : null;
}
@Override
@@ -298,6 +287,7 @@ public class PsiMethodImpl extends JavaStubPsiElement<PsiMethodStub> implements
}
}
@Override
public String toString() {
return "PsiMethod:" + getName();
}
@@ -376,4 +366,4 @@ public class PsiMethodImpl extends JavaStubPsiElement<PsiMethodStub> implements
protected boolean isVisibilitySupported() {
return true;
}
}
}
@@ -141,23 +141,19 @@ public class PsiParameterImpl extends JavaStubPsiElement<PsiParameterStub> imple
@Override
@NotNull
@SuppressWarnings("Duplicates")
public PsiType getType() {
PsiParameterStub stub = getStub();
if (stub != null) {
PsiType type = SoftReference.dereference(myCachedType);
if (type != null) return type;
String typeText = TypeInfo.createTypeText(stub.getType(true));
assert typeText != null : stub;
try {
if (type == null) {
String typeText = TypeInfo.createTypeText(stub.getType(false));
assert typeText != null : stub;
type = JavaPsiFacade.getInstance(getProject()).getParserFacade().createTypeFromText(typeText, this);
type = JavaSharedImplUtil.applyAnnotations(type, getModifierList());
myCachedType = new SoftReference<PsiType>(type);
return type;
}
catch (IncorrectOperationException e) {
LOG.error(e);
return null;
}
return type;
}
myCachedType = null;
@@ -232,6 +228,7 @@ public class PsiParameterImpl extends JavaStubPsiElement<PsiParameterStub> imple
}
}
@Override
public String toString() {
return "PsiParameter:" + getName();
}
@@ -322,5 +319,4 @@ public class PsiParameterImpl extends JavaStubPsiElement<PsiParameterStub> imple
}
return this;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,15 +16,11 @@
package com.intellij.psi.impl.source;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.PsiJavaParserFacadeImpl;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
@@ -36,13 +32,11 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeElement {
private static final Class[] INTERMEDIATES = {PsiComment.class, PsiWhiteSpace.class, PsiAnnotation.class, PsiTypeParameterList.class};
import static com.intellij.util.containers.ContainerUtil.copyAndClear;
public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeElement {
@SuppressWarnings({"UnusedDeclaration"})
public PsiTypeElementImpl() {
this(JavaElementType.TYPE);
@@ -81,7 +75,7 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
}
PsiType type = null;
final SmartList<PsiAnnotation> annotations = new SmartList<PsiAnnotation>();
List<PsiAnnotation> annotations = new SmartList<PsiAnnotation>();
for (PsiElement child = getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof PsiComment || child instanceof PsiWhiteSpace) continue;
@@ -101,23 +95,20 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
}
else if (PsiUtil.isJavaToken(child, ElementType.PRIMITIVE_TYPE_BIT_SET)) {
assert type == null : this;
addTypeUseCandidateAnnotations(annotations);
String text = child.getText();
type = annotations.isEmpty() ? PsiJavaParserFacadeImpl.getPrimitiveType(text)
: new PsiPrimitiveType(text, filterTypeUse(annotations));
type = annotations.isEmpty() ? PsiJavaParserFacadeImpl.getPrimitiveType(text) : new PsiPrimitiveType(text, createProvider(annotations));
}
else if (child instanceof PsiJavaCodeReferenceElement) {
assert type == null : this;
addTypeUseCandidateAnnotations(annotations);
type = new PsiClassReferenceType((PsiJavaCodeReferenceElement)child, null, filterTypeUse(annotations));
type = new PsiClassReferenceType((PsiJavaCodeReferenceElement)child, null, createProvider(annotations));
}
else if (PsiUtil.isJavaToken(child, JavaTokenType.LBRACKET)) {
assert type != null : this;
type = new PsiArrayType(type, filterTypeUse(annotations));
type = new PsiArrayType(type, createProvider(annotations));
}
else if (PsiUtil.isJavaToken(child, JavaTokenType.ELLIPSIS)) {
assert type != null : this;
type = new PsiEllipsisType(type, filterTypeUse(annotations));
type = new PsiEllipsisType(type, createProvider(annotations));
}
if (PsiUtil.isJavaToken(child, JavaTokenType.QUEST) ||
@@ -134,7 +125,7 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
else {
type = PsiWildcardType.createUnbounded(getManager());
}
type = type.annotate(filterTypeUse(annotations));
type = type.annotate(createProvider(annotations));
break;
}
@@ -153,49 +144,18 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
}
}
return type == null ? PsiType.NULL : type;
}
if (type == null) return PsiType.NULL;
@NotNull
private static TypeAnnotationProvider filterTypeUse(List<PsiAnnotation> annotations) {
if (annotations.isEmpty()) return TypeAnnotationProvider.EMPTY;
final ArrayList<PsiAnnotation> copy = ContainerUtil.newArrayList(annotations);
annotations.clear();
return new TypeAnnotationProvider() {
private volatile PsiAnnotation[] myCached;
@NotNull
@Override
public PsiAnnotation[] getAnnotations() {
PsiAnnotation[] result = myCached;
if (result == null) {
List<PsiAnnotation> filtered = ContainerUtil.filter(copy, new Condition<PsiAnnotation>() {
@Override
public boolean value(PsiAnnotation annotation) {
return PsiImplUtil.isTypeAnnotation(annotation);
}
});
myCached = result = filtered.isEmpty() ? PsiAnnotation.EMPTY_ARRAY : filtered.toArray(new PsiAnnotation[filtered.size()]);
}
return result;
}
};
}
private void addTypeUseCandidateAnnotations(List<PsiAnnotation> annotations) {
PsiElement parent = this;
while (parent instanceof PsiTypeElement) {
PsiElement left = PsiTreeUtil.skipSiblingsBackward(parent, INTERMEDIATES);
if (left instanceof PsiModifierList) {
Collections.addAll(annotations, ((PsiModifierList)left).getAnnotations());
break;
}
if (left != null) break;
parent = parent.getParent();
PsiElement parent = getParent();
if (parent instanceof PsiModifierListOwner) {
type = JavaSharedImplUtil.applyAnnotations(type, ((PsiModifierListOwner)parent).getModifierList());
}
return type;
}
private static TypeAnnotationProvider createProvider(List<PsiAnnotation> annotations) {
return TypeAnnotationProvider.Static.create(copyAndClear(annotations, PsiAnnotation.ARRAY_FACTORY, true));
}
private List<PsiType> collectTypes() {
@@ -246,9 +206,7 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
@Override
@NotNull
public PsiAnnotation[] getApplicableAnnotations() {
List<PsiAnnotation> annotations = PsiTreeUtil.getChildrenOfTypeAsList(this, PsiAnnotation.class);
addTypeUseCandidateAnnotations(annotations);
return annotations.toArray(PsiAnnotation.ARRAY_FACTORY.create(annotations.size()));
return getType().getAnnotations();
}
@Override
@@ -275,4 +233,4 @@ public class PsiTypeElementImpl extends CompositePsiElement implements PsiTypeEl
public String toString() {
return "PsiTypeElement:" + getText();
}
}
}
@@ -15,9 +15,11 @@
*/
package com.intellij.psi.impl.source.tree;
import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.lang.ASTFactory;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.*;
import com.intellij.psi.impl.GeneratedMarkerVisitor;
import com.intellij.psi.impl.PsiImplUtil;
@@ -26,6 +28,7 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.util.CharTable;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -84,6 +87,40 @@ public class JavaSharedImplUtil {
return !found || stop ? null : annotations;
}
@NotNull
public static PsiType applyAnnotations(@NotNull PsiType type, @Nullable PsiModifierList modifierList) {
if (modifierList != null) {
PsiAnnotation[] annotations = modifierList.getAnnotations();
if (annotations.length > 0) {
TypeAnnotationProvider provider = new FilteringTypeAnnotationProvider(annotations);
if (type instanceof PsiArrayType) {
Stack<PsiArrayType> types = new Stack<PsiArrayType>();
do {
types.push((PsiArrayType)type);
type = ((PsiArrayType)type).getComponentType();
}
while (type instanceof PsiArrayType);
type = type.annotate(provider);
while (!types.isEmpty()) {
PsiArrayType t = types.pop();
type = t instanceof PsiEllipsisType ? new PsiEllipsisType(type, t.getAnnotations()) : new PsiArrayType(type, t.getAnnotations());
}
return type;
}
else if (type instanceof PsiDisjunctionType) {
List<PsiType> components = ContainerUtil.newArrayList(((PsiDisjunctionType)type).getDisjunctions());
components.set(0, components.get(0).annotate(provider));
return ((PsiDisjunctionType)type).newDisjunctionType(components);
}
else {
return type.annotate(provider);
}
}
}
return type;
}
public static void normalizeBrackets(@NotNull PsiVariable variable) {
CompositeElement variableElement = (CompositeElement)variable.getNode();
@@ -156,4 +193,29 @@ public class JavaSharedImplUtil {
}
variable.addAfter(initializer, eq.getPsi());
}
}
private static class FilteringTypeAnnotationProvider implements TypeAnnotationProvider {
private final PsiAnnotation[] myCandidates;
private volatile PsiAnnotation[] myCache;
private FilteringTypeAnnotationProvider(PsiAnnotation[] candidates) {
myCandidates = candidates;
}
@NotNull
@Override
public PsiAnnotation[] getAnnotations() {
PsiAnnotation[] result = myCache;
if (result == null) {
List<PsiAnnotation> filtered = ContainerUtil.filter(myCandidates, new Condition<PsiAnnotation>() {
@Override
public boolean value(PsiAnnotation annotation) {
return AnnotationTargetUtil.isTypeAnnotation(annotation);
}
});
myCache = result = filtered.isEmpty() ? PsiAnnotation.EMPTY_ARRAY : filtered.toArray(new PsiAnnotation[filtered.size()]);
}
return result;
}
}
}
@@ -27,6 +27,7 @@ import com.intellij.psi.impl.java.stubs.PsiMethodStub;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtil;
import com.intellij.testFramework.LightIdeaTestCase;
import com.intellij.util.ObjectUtils;
import com.intellij.util.indexing.FileBasedIndex;
import java.io.File;
@@ -390,13 +391,31 @@ public class ClsPsiTest extends LightIdeaTestCase {
assertEquals(42L, lng.computeConstantValue());
}
public void testAnnotationsOnTypes() {
PsiClass cls = getFile("../../mirror/pkg/TypeAnnotations").getClasses()[0];
PsiField f1 = cls.findFieldByName("f1", false);
assertNotNull(f1);
assertEquals("java.lang.@pkg.TypeAnnotations.TA(\"field type\") String", f1.getType().getCanonicalText(true));
PsiField f2 = cls.findFieldByName("f2", false);
assertNotNull(f2);
assertEquals("java.lang.@pkg.TypeAnnotations.MixA(\"field and type\") String", f2.getType().getCanonicalText(true));
PsiMethod m1 = cls.findMethodsByName("m1", false)[0];
assertEquals("@pkg.TypeAnnotations.TA(\"return type\") int", ObjectUtils.assertNotNull(m1.getReturnType()).getCanonicalText(true));
PsiParameter p1 = cls.findMethodsByName("m2", false)[0].getParameterList().getParameters()[0];
assertEquals("@pkg.TypeAnnotations.TA(\"parameter\") int", p1.getType().getCanonicalText(true));
}
private PsiJavaFile getFile() {
return getFile(getTestName(false));
}
private static PsiJavaFile getFile(String name) {
String path = PathManagerEx.getTestDataPath() + TEST_DATA_PATH + "/pack/" + name + ".class";
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path);
VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
assertNotNull(path, file);
PsiFile clsFile = PsiManager.getInstance(getProject()).findFile(file);
assertTrue(String.valueOf(clsFile), clsFile instanceof ClsFileImpl);
@@ -16,6 +16,7 @@
package com.intellij.psi
import com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer
import com.intellij.psi.impl.source.PsiClassImpl
import com.intellij.psi.impl.source.PsiFileImpl
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
@@ -54,4 +55,26 @@ class JavaStubsTest extends LightCodeInsightFixtureTestCase {
assert !file.contentsLoaded
}
public void "test applying type annotations"() {
def cls = myFixture.addClass("""
import java.lang.annotation.*;
class Foo {
@Target(ElementType.TYPE_USE)
@interface TA { String value(); }
private @TA String f1;
private static @TA int m1(@TA int p1) { return 0; }
}
""".stripIndent())
def f1 = cls.fields[0].type
def m1 = cls.methods[0].returnType
def p1 = cls.methods[0].parameterList.parameters[0].type
assert (cls as PsiClassImpl).stub
assert f1.getCanonicalText(true) == "java.lang.@Foo.TA String"
assert m1.getCanonicalText(true) == "@Foo.TA int"
assert p1.getCanonicalText(true) == "@Foo.TA int"
}
}