From 218fd54bf22779c67629c4b6068d3b330948d844 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 4 Apr 2013 20:36:33 +0200 Subject: [PATCH] Type annotation in wildcard and class reference types; more tests --- .../src/com/intellij/psi/PsiNameHelper.java | 54 ++++++++++- .../src/com/intellij/psi/PsiType.java | 7 +- .../src/com/intellij/psi/PsiWildcardType.java | 29 +----- .../ClsJavaCodeReferenceElementImpl.java | 65 ++++--------- .../ClsReferenceParameterListImpl.java | 94 +++++++++++++++++++ .../addTypeCast/afterTypeAnnoValued.java | 12 +++ .../addTypeCast/afterTypeAnnoWildcard.java | 2 +- .../addTypeCast/beforeTypeAnnoValued.java | 12 +++ .../codeInsight/psi/AnnotatedTypeTest.groovy | 38 +++++--- 9 files changed, 222 insertions(+), 91 deletions(-) create mode 100644 java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClsReferenceParameterListImpl.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/afterTypeAnnoValued.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/beforeTypeAnnoValued.java diff --git a/java/java-psi-api/src/com/intellij/psi/PsiNameHelper.java b/java/java-psi-api/src/com/intellij/psi/PsiNameHelper.java index e82221e7a887..42331d13f68b 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiNameHelper.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiNameHelper.java @@ -16,12 +16,15 @@ package com.intellij.psi; import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.regex.Pattern; +import static com.intellij.util.ObjectUtils.notNull; + /** * Service for validating and parsing Java identifiers. * @@ -103,13 +106,54 @@ public abstract class PsiNameHelper { return sub.length() == referenceText.length() ? sub : new String(sub); } - public static String getPresentableText(PsiJavaCodeReferenceElement ref) { - final String referenceName = ref.getReferenceName(); - PsiType[] typeParameters = ref.getTypeParameters(); - return getPresentableText(referenceName, typeParameters); + @NotNull + public static String getPresentableText(@NotNull PsiJavaCodeReferenceElement ref) { + String name = ref.getReferenceName(); + + PsiAnnotation[] children = PsiTreeUtil.getChildrenOfType(ref, PsiAnnotation.class); + PsiAnnotation[] annotations = notNull(children, PsiAnnotation.EMPTY_ARRAY); + + PsiReferenceParameterList parameterList = ref.getParameterList(); + PsiTypeElement[] typeElements = parameterList != null ? parameterList.getTypeParameterElements() : PsiTypeElement.EMPTY_ARRAY; + + return getPresentableText(name, annotations, typeElements); } - public static String getPresentableText(final String referenceName, final PsiType[] typeParameters) { + @NotNull + public static String getPresentableText(@Nullable String referenceName, + @NotNull PsiAnnotation[] annotations, + @NotNull PsiTypeElement[] typeElements) { + if (typeElements.length == 0 && annotations.length == 0) { + return referenceName != null ? referenceName : ""; + } + + StringBuilder buffer = new StringBuilder(); + + if (annotations.length > 0) { + for (PsiAnnotation annotation : annotations) { + buffer.append(annotation.getText()).append(' '); + } + } + + buffer.append(referenceName); + + if (typeElements.length > 0) { + buffer.append("<"); + for (int i = 0; i < typeElements.length; i++) { + PsiType type = typeElements[i].getType(); + if (!(type instanceof PsiDiamondType)) { + buffer.append(type.getPresentableText()); + if (i < typeElements.length - 1) buffer.append(", "); + } + } + buffer.append(">"); + } + + return buffer.toString(); + } + + /** deprecated use {@link #getPresentableText(String, PsiAnnotation[], PsiTypeElement[])} (to remove in IDEA 13) */ + public static String getPresentableText(@Nullable String referenceName, @NotNull PsiType[] typeParameters) { if (typeParameters.length > 0) { StringBuilder buffer = new StringBuilder(); buffer.append(referenceName); diff --git a/java/java-psi-api/src/com/intellij/psi/PsiType.java b/java/java-psi-api/src/com/intellij/psi/PsiType.java index cc78466a7e4a..4a3285090bf6 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiType.java @@ -244,7 +244,12 @@ public abstract class PsiType implements PsiAnnotationOwner { @Override public PsiAnnotation findAnnotation(@NotNull @NonNls String qualifiedName) { - return null; //todo + for (PsiAnnotation annotation : myAnnotations) { + if (qualifiedName.equals(annotation.getQualifiedName())) { + return annotation; + } + } + return null; } @Override diff --git a/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java b/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java index 5d5a9f9671bb..7040d4b43fe0 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiWildcardType.java @@ -96,38 +96,19 @@ public class PsiWildcardType extends PsiType { @Override public String getPresentableText() { - if (myBound == null) return "?"; - if (myIsExtending) { - return EXTENDS_PREFIX + myBound.getPresentableText(); - } - else { - //noinspection HardCodedStringLiteral - return "? super " + myBound.getPresentableText(); - } + return getAnnotationsTextPrefix(false, false, true) + + (myBound == null ? "?" : (myIsExtending ? EXTENDS_PREFIX : SUPER_PREFIX) + myBound.getPresentableText()); } @Override public String getCanonicalText() { - if (myBound == null) return "?"; - if (myIsExtending) { - return EXTENDS_PREFIX + myBound.getCanonicalText(); - } - else { - //noinspection HardCodedStringLiteral - return "? super " + myBound.getCanonicalText(); - } + return (myBound == null ? "?" : (myIsExtending ? EXTENDS_PREFIX : SUPER_PREFIX) + myBound.getCanonicalText()); } @Override public String getInternalCanonicalText() { - if (myBound == null) return "?"; - if (myIsExtending) { - return EXTENDS_PREFIX + myBound.getInternalCanonicalText(); - } - else { - //noinspection HardCodedStringLiteral - return "? super " + myBound.getInternalCanonicalText(); - } + return getAnnotationsTextPrefix(true, false, true) + + (myBound == null ? "?" : (myIsExtending ? EXTENDS_PREFIX : SUPER_PREFIX) + myBound.getInternalCanonicalText()); } @Override diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClsJavaCodeReferenceElementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClsJavaCodeReferenceElementImpl.java index 068197d60a26..c69391dab891 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClsJavaCodeReferenceElementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClsJavaCodeReferenceElementImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -17,7 +17,6 @@ package com.intellij.psi.impl.compiled; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.PsiSubstitutorImpl; @@ -38,43 +37,19 @@ import org.jetbrains.annotations.Nullable; import java.util.Map; public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements PsiJavaCodeReferenceElement { - private static final ClsTypeElementImpl[] EMPTY_ARRAY = new ClsTypeElementImpl[0]; - @NonNls private static final String EXTENDS_PREFIX = "?extends"; - @NonNls private static final String SUPER_PREFIX = "?super"; - private final PsiElement myParent; private final String myCanonicalText; private final String myQualifiedName; - private final ClsTypeElementImpl[] myTypeParameters; // in right-to-left order - private volatile PsiType[] myTypeParametersCachedTypes = null; // in left-to-right-order + private final PsiReferenceParameterList myRefParameterList; public ClsJavaCodeReferenceElementImpl(PsiElement parent, String canonicalText) { myParent = parent; myCanonicalText = canonicalText; - final String[] classParametersText = PsiNameHelper.getClassParametersText(canonicalText); - int length = classParametersText.length; - myTypeParameters = length == 0 ? EMPTY_ARRAY : new ClsTypeElementImpl[length]; - for (int i = 0; i < length; i++) { - String s = classParametersText[length - i - 1]; - char variance = ClsTypeElementImpl.VARIANCE_NONE; - if (s.startsWith(EXTENDS_PREFIX)) { - variance = ClsTypeElementImpl.VARIANCE_EXTENDS; - s = s.substring(EXTENDS_PREFIX.length()); - } - else if (s.startsWith(SUPER_PREFIX)) { - variance = ClsTypeElementImpl.VARIANCE_SUPER; - s = s.substring(SUPER_PREFIX.length()); - } - else if (StringUtil.startsWithChar(s, '?')) { - variance = ClsTypeElementImpl.VARIANCE_INVARIANT; - s = s.substring(1); - } - - myTypeParameters[i] = new ClsTypeElementImpl(this, s, variance); - } - myQualifiedName = PsiNameHelper.getQualifiedClassName(myCanonicalText, false); + + String[] classParameters = PsiNameHelper.getClassParametersText(canonicalText); + myRefParameterList = classParameters.length == 0 ? null : new ClsReferenceParameterListImpl(this, classParameters); } @Override @@ -121,19 +96,23 @@ public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements P } private JavaResolveResult advancedResolveImpl() { - final PsiElement resolve = resolveElement(); + PsiTypeElement[] typeElements = myRefParameterList == null ? PsiTypeElement.EMPTY_ARRAY : myRefParameterList.getTypeParameterElements(); + PsiElement resolve = resolveElement(); + if (resolve instanceof PsiClass) { - final Map substitutionMap = new HashMap(); + Map substitutionMap = new HashMap(); int index = 0; for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable((PsiClass)resolve)) { - if (index >= myTypeParameters.length) { - final PsiTypeParameterListOwner parameterOwner = parameter.getOwner(); + if (index >= typeElements.length) { + PsiTypeParameterListOwner parameterOwner = parameter.getOwner(); if (parameterOwner == resolve) { substitutionMap.put(parameter, null); - } else if (parameterOwner instanceof PsiClass) { + } + else if (parameterOwner instanceof PsiClass) { PsiElement containingClass = myParent; while ((containingClass = PsiTreeUtil.getParentOfType(containingClass, PsiClass.class, true)) != null) { - final PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getClassSubstitutor((PsiClass)parameterOwner, (PsiClass)containingClass, PsiSubstitutor.EMPTY); + PsiSubstitutor superClassSubstitutor = + TypeConversionUtil.getClassSubstitutor((PsiClass)parameterOwner, (PsiClass)containingClass, PsiSubstitutor.EMPTY); if (superClassSubstitutor != null) { substitutionMap.put(parameter, superClassSubstitutor.substitute(parameter)); break; @@ -142,7 +121,7 @@ public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements P } } else { - substitutionMap.put(parameter, myTypeParameters[index].getType()); + substitutionMap.put(parameter, typeElements[index].getType()); } index++; } @@ -228,7 +207,7 @@ public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements P @Override public PsiReferenceParameterList getParameterList() { - return null; + return myRefParameterList; } @Override @@ -307,15 +286,7 @@ public class ClsJavaCodeReferenceElementImpl extends ClsElementImpl implements P @Override @NotNull public PsiType[] getTypeParameters() { - PsiType[] cachedTypes = myTypeParametersCachedTypes; - if (cachedTypes == null) { - cachedTypes = myTypeParameters.length == 0 ? PsiType.EMPTY_ARRAY : new PsiType[myTypeParameters.length]; - for (int i = 0; i < cachedTypes.length; i++) { - cachedTypes[cachedTypes.length - i - 1] = myTypeParameters[i].getType(); - } - myTypeParametersCachedTypes = cachedTypes; - } - return cachedTypes; + return myRefParameterList == null ? PsiType.EMPTY_ARRAY : myRefParameterList.getTypeArguments(); } @Override diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClsReferenceParameterListImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClsReferenceParameterListImpl.java new file mode 100644 index 000000000000..1606e7644aef --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/compiled/ClsReferenceParameterListImpl.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.psi.impl.compiled; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.TreeElement; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public class ClsReferenceParameterListImpl extends ClsElementImpl implements PsiReferenceParameterList { + @NonNls private static final String EXTENDS_PREFIX = "?extends"; + @NonNls private static final String SUPER_PREFIX = "?super"; + + private final PsiElement myParent; + private final ClsTypeElementImpl[] myTypeParameters; + private volatile PsiType[] myTypeParametersCachedTypes = null; + + public ClsReferenceParameterListImpl(PsiElement parent, String[] classParameters) { + myParent = parent; + + int length = classParameters.length; + myTypeParameters = new ClsTypeElementImpl[length]; + + for (int i = 0; i < length; i++) { + String s = classParameters[length - i - 1]; + char variance = ClsTypeElementImpl.VARIANCE_NONE; + if (s.startsWith(EXTENDS_PREFIX)) { + variance = ClsTypeElementImpl.VARIANCE_EXTENDS; + s = s.substring(EXTENDS_PREFIX.length()); + } + else if (s.startsWith(SUPER_PREFIX)) { + variance = ClsTypeElementImpl.VARIANCE_SUPER; + s = s.substring(SUPER_PREFIX.length()); + } + else if (StringUtil.startsWithChar(s, '?')) { + variance = ClsTypeElementImpl.VARIANCE_INVARIANT; + s = s.substring(1); + } + + myTypeParameters[i] = new ClsTypeElementImpl(this, s, variance); + } + } + + @Override + public void appendMirrorText(int indentLevel, @NotNull StringBuilder buffer) { } + + @Override + public void setMirror(@NotNull TreeElement element) throws InvalidMirrorException { } + + @NotNull + @Override + public PsiTypeElement[] getTypeParameterElements() { + return myTypeParameters; + } + + @NotNull + @Override + public PsiType[] getTypeArguments() { + PsiType[] cachedTypes = myTypeParametersCachedTypes; + if (cachedTypes == null) { + cachedTypes = myTypeParameters.length == 0 ? PsiType.EMPTY_ARRAY : new PsiType[myTypeParameters.length]; + for (int i = 0; i < cachedTypes.length; i++) { + cachedTypes[cachedTypes.length - i - 1] = myTypeParameters[i].getType(); + } + myTypeParametersCachedTypes = cachedTypes; + } + return cachedTypes; + } + + @NotNull + @Override + public PsiElement[] getChildren() { + return myTypeParameters; + } + + @Override + public PsiElement getParent() { + return myParent; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/afterTypeAnnoValued.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/afterTypeAnnoValued.java new file mode 100644 index 000000000000..bfedf89bc35f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/afterTypeAnnoValued.java @@ -0,0 +1,12 @@ +// "Cast to 'int'" "true" +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; + +@Target({TYPE_USE}) @interface TA { String value() default ""; } + +class C { + { + Object o = null; + @TA("wtf") int i = (@TA("wtf") int) o; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/afterTypeAnnoWildcard.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/afterTypeAnnoWildcard.java index f13704e3c443..a114746c145d 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/afterTypeAnnoWildcard.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/afterTypeAnnoWildcard.java @@ -8,6 +8,6 @@ import static java.lang.annotation.ElementType.*; class C { { Object o = null; - @TA List<@TA ? extends @TA String> l = (@TA List) o; + @TA List<@TA ? extends @TA String> l = (@TA List<@TA ? extends @TA String>) o; } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/beforeTypeAnnoValued.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/beforeTypeAnnoValued.java new file mode 100644 index 000000000000..726856c931b7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addTypeCast/beforeTypeAnnoValued.java @@ -0,0 +1,12 @@ +// "Cast to 'int'" "true" +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; + +@Target({TYPE_USE}) @interface TA { String value() default ""; } + +class C { + { + Object o = null; + @TA("wtf") int i = o; + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/psi/AnnotatedTypeTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/psi/AnnotatedTypeTest.groovy index 076b12346804..0d01869a0bf8 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/psi/AnnotatedTypeTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/psi/AnnotatedTypeTest.groovy @@ -15,34 +15,46 @@ */ package com.intellij.codeInsight.psi -import com.intellij.psi.* +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile import com.intellij.testFramework.LightIdeaTestCase +@SuppressWarnings(["GrUnresolvedAccess"]) class AnnotatedTypeTest extends LightIdeaTestCase { - @SuppressWarnings("GrUnresolvedAccess") + public void testTypeComposition() { - def context = createFile("typeCompositionTest.java", """ + PsiFile context = createFile("typeCompositionTest.java", """ import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; @interface A { } -@Target({TYPE_USE}) @interface TA { } +@Target({TYPE_USE}) @interface TA { int value() default 42; } class E1 extends Exception { } class E2 extends Exception { } """) - def factory = JavaPsiFacade.getInstance(getProject()).getElementFactory(), psi + PsiElement psi - psi = factory.createStatementFromText("@TA int @TA [] a = null", context) - assertEquals("@TA int @TA []", psi.declaredElements[0].type.presentableText) + psi = javaFacade.elementFactory.createStatementFromText("@A @TA(1) int @TA(2) [] a", context) + assertEquals("@TA(1) int @TA(2) []", psi.declaredElements[0].type.presentableText) - psi = factory.createStatementFromText("@A int @TA [] a = null", context) - assertEquals("int @TA []", psi.declaredElements[0].type.presentableText) + psi = javaFacade.elementFactory.createStatementFromText("try { } catch (@A @TA(1) E1 | @TA(2) E2 e) { }", context) + assertEquals("@TA(1) E1 | @TA(2) E2", psi.catchBlockParameters[0].type.presentableText) - psi = factory.createStatementFromText("try { } catch (@TA E1 | @TA E2 e) { }", context) - assertEquals("@TA E1 | @TA E2", psi.catchBlockParameters[0].type.presentableText) + psi = javaFacade.elementFactory.createStatementFromText("@A @TA(1) String @TA(2) [] f @TA(3) []", context) + assertEquals("@TA(1) String @TA(2) [] @TA(3) []", psi.declaredElements[0].type.presentableText) - psi = factory.createFieldFromText("@TA String @TA [] f @TA []", context) - assertEquals("@TA String @TA [] @TA []", psi.type.presentableText) + psi = javaFacade.elementFactory.createStatementFromText("Class<@TA(1) ?> c", context) + assertEquals("Class<@TA(1) ?>", psi.declaredElements[0].type.presentableText) + + psi = javaFacade.elementFactory.createStatementFromText("Class cs = new Class<>()", context) + assertEquals("Class<>", psi.declaredElements[0].initializer.type.presentableText) + + psi = javaFacade.elementFactory.createStatementFromText("@A @TA(1) String s", context) + assertEquals("@TA(1) String", psi.declaredElements[0].type.presentableText) + + psi = javaFacade.elementFactory.createStatementFromText("@A java.lang.@TA(1) String s", context) + assertEquals("@TA(1) String", psi.declaredElements[0].type.presentableText) } + }