IDEA-99589 Groovy 2.1 : resolve annotation aliases

This commit is contained in:
Max Medvedev
2013-01-27 09:34:51 +04:00
parent 7d0342fb18
commit cd49e06b72
10 changed files with 381 additions and 33 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 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.
@@ -48,4 +48,6 @@ public interface GrModifierList extends GroovyPsiElement, PsiModifierList {
@Override
void setModifierProperty(@GrModifier.GrModifierConstant @NotNull @NonNls String name, boolean value) throws IncorrectOperationException;
@NotNull
GrAnnotation[] getRawAnnotations();
}
@@ -0,0 +1,166 @@
/*
* 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 org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.modifiers;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArrayInitializer;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationMemberValue;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class GrAnnotationCollector {
public static GrAnnotation[] getResolvedAnnotations(@NotNull GrModifierList modifierList) {
final GrAnnotation[] rawAnnotations = modifierList.getRawAnnotations();
if (!hasAliases(rawAnnotations)) return rawAnnotations;
List<GrAnnotation> result = ContainerUtil.newArrayList();
for (GrAnnotation annotation : rawAnnotations) {
final PsiElement resolved = annotation.getClassReference().resolve();
final GrAnnotation annotationCollector = findAnnotationCollector(resolved);
if (annotationCollector != null) {
collectAnnotations(result, annotation, annotationCollector);
}
else {
result.add(annotation);
}
}
return result.toArray(new GrAnnotation[result.size()]);
}
private static boolean hasAliases(GrAnnotation[] rawAnnotations) {
for (GrAnnotation annotation : rawAnnotations) {
final PsiElement resolved = annotation.getClassReference().resolve();
final GrAnnotation annotationCollector = findAnnotationCollector(resolved);
if (annotationCollector != null) {
return true;
}
}
return false;
}
public static void collectAnnotations(@NotNull List<GrAnnotation> list,
@NotNull GrAnnotation alias,
@NotNull GrAnnotation annotationCollector) {
final GrModifierList modifierList = (GrModifierList)annotationCollector.getParent();
Map<String, Map<String, GrAnnotationNameValuePair>> annotations = ContainerUtil.newHashMap();
collectAliasedAnnotationsFromAnnotationCollectorValueAttribute(annotationCollector,
(HashMap<String, Map<String, GrAnnotationNameValuePair>>)annotations);
collectAliasedAnnotationsFromAnnotationCollectorAnnotations(modifierList,
(HashMap<String, Map<String, GrAnnotationNameValuePair>>)annotations);
final PsiManager manager = alias.getManager();
final GrAnnotationNameValuePair[] attributes = alias.getParameterList().getAttributes();
for (Map.Entry<String, Map<String, GrAnnotationNameValuePair>> entry : annotations.entrySet()) {
final String qname = entry.getKey();
final PsiClass resolved = JavaPsiFacade.getInstance(alias.getProject()).findClass(qname, alias.getResolveScope());
if (resolved == null) continue;
final GrLightAnnotation annotation = new GrLightAnnotation(manager, alias.getLanguage(), qname, modifierList);
Set<String> usedAttrs = ContainerUtil.newHashSet();
for (GrAnnotationNameValuePair attr : attributes) {
final String name = attr.getName() != null ? attr.getName() : "value";
if (resolved.findMethodsByName(name, false).length > 0) {
annotation.addAttribute(attr);
}
usedAttrs.add(name);
}
final Map<String, GrAnnotationNameValuePair> defaults = entry.getValue();
for (Map.Entry<String, GrAnnotationNameValuePair> defa : defaults.entrySet()) {
if (!usedAttrs.contains(defa.getKey())) {
annotation.addAttribute(defa.getValue());
}
}
list.add(annotation);
}
}
private static void collectAliasedAnnotationsFromAnnotationCollectorAnnotations(GrModifierList modifierList,
HashMap<String, Map<String, GrAnnotationNameValuePair>> annotations) {
for (GrAnnotation annotation : modifierList.getRawAnnotations()) {
final String qname = annotation.getQualifiedName();
if (qname == null || qname.equals(GroovyCommonClassNames.GROOVY_TRANSFORM_ANNOTATION_COLLECTOR)) continue;
for (GrAnnotationNameValuePair pair : annotation.getParameterList().getAttributes()) {
Map<String, GrAnnotationNameValuePair> map = annotations.get(qname);
if (map == null) {
map = ContainerUtil.newHashMap();
annotations.put(qname, map);
}
map.put(pair.getName() != null ? pair.getName() : "value", pair);
}
}
}
private static void collectAliasedAnnotationsFromAnnotationCollectorValueAttribute(GrAnnotation annotationCollector,
HashMap<String, Map<String, GrAnnotationNameValuePair>> annotations) {
final PsiAnnotationMemberValue annotationsFromValue = annotationCollector.findAttributeValue("value");
if (annotationsFromValue instanceof GrAnnotationArrayInitializer) {
for (GrAnnotationMemberValue member : ((GrAnnotationArrayInitializer)annotationsFromValue).getInitializers()) {
if (member instanceof GrReferenceExpression) {
final PsiElement resolved = ((GrReferenceExpression)member).resolve();
if (resolved instanceof PsiClass && ((PsiClass)resolved).isAnnotationType()) {
annotations.put(((PsiClass)resolved).getQualifiedName(), ContainerUtil.<String, GrAnnotationNameValuePair>newHashMap());
}
}
}
}
}
@Nullable
private static GrAnnotation findAnnotationCollector(PsiElement clazz) {
if (clazz instanceof GrTypeDefinition) {
final GrModifierList modifierList = ((GrTypeDefinition)clazz).getModifierList();
if (modifierList != null) {
for (GrAnnotation annotation : modifierList.getRawAnnotations()) {
if (GroovyCommonClassNames.GROOVY_TRANSFORM_ANNOTATION_COLLECTOR.equals(annotation.getQualifiedName())) {
return annotation;
}
}
}
}
return null;
}
}
@@ -19,8 +19,10 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.modifiers;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.ArrayFactory;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
@@ -254,6 +256,12 @@ public class GrModifierListImpl extends GrStubElementBase<GrModifierListStub> im
}
}
@NotNull
@Override
public GrAnnotation[] getRawAnnotations() {
return getStubOrPsiChildren(GroovyElementTypes.ANNOTATION, ARRAY_FACTORY);
}
private void setModifierPropertyInternal(String name, boolean doSet) {
if (doSet) {
if (isEmptyModifierList()) {
@@ -286,7 +294,7 @@ public class GrModifierListImpl extends GrStubElementBase<GrModifierListStub> im
}
private boolean isEmptyModifierList() {
return getTextLength() == 0 || getModifiers().length == 0 && getAnnotations().length == 0;
return getTextLength() == 0 || getModifiers().length == 0 && getRawAnnotations().length == 0;
}
@Nullable
@@ -311,31 +319,26 @@ public class GrModifierListImpl extends GrStubElementBase<GrModifierListStub> im
@NotNull
public GrAnnotation[] getAnnotations() {
return getStubOrPsiChildren(GroovyElementTypes.ANNOTATION, ARRAY_FACTORY);
return CachedValuesManager.getManager(getProject()).createCachedValue(new CachedValueProvider<GrAnnotation[]>() {
@Nullable
@Override
public Result<GrAnnotation[]> compute() {
return Result.create(GrAnnotationCollector.getResolvedAnnotations(GrModifierListImpl.this), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
}
}).getValue();
}
@NotNull
public PsiAnnotation[] getApplicableAnnotations() {
//todo[medvedev]
return getAnnotations();
}
@Nullable
public PsiAnnotation findAnnotation(@NotNull @NonNls String qualifiedName) {
final GrModifierListStub stub = getStub();
if (stub != null) {
for (StubElement stubElement : stub.getChildrenStubs()) {
final PsiElement child = stubElement.getPsi();
if (child instanceof PsiAnnotation && qualifiedName.equals(((PsiAnnotation)child).getQualifiedName())) {
return (PsiAnnotation)child;
}
}
} else {
PsiElement child = getFirstChild();
while (child != null) {
if (child instanceof PsiAnnotation && qualifiedName.equals(((PsiAnnotation)child).getQualifiedName())) {
return (PsiAnnotation)child;
}
child = child.getNextSibling();
for (GrAnnotation annotation : getAnnotations()) {
if (qualifiedName.equals(annotation.getQualifiedName())) {
return annotation;
}
}
return null;
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 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.
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.meta.PsiMetaData;
import com.intellij.util.containers.ContainerUtilRt;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,11 +33,13 @@ import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import java.util.List;
/**
* @author Max Medvedev
*/
public class GrLightAnnotation extends LightElement implements GrAnnotation {
private final GrEmptyAnnotationArgumentList myAnnotationArgList;
private final GrLightAnnotationArgumentList myAnnotationArgList;
private final String myQualifiedName;
private PsiAnnotationOwner myOwner;
@@ -47,7 +50,7 @@ public class GrLightAnnotation extends LightElement implements GrAnnotation {
myQualifiedName = qualifiedName;
myOwner = owner;
myAnnotationArgList = new GrEmptyAnnotationArgumentList(manager, language);
myAnnotationArgList = new GrLightAnnotationArgumentList(manager, language);
myRef = new GrLightClassReferenceElement(qualifiedName, qualifiedName, this);
}
@@ -119,15 +122,34 @@ public class GrLightAnnotation extends LightElement implements GrAnnotation {
return null;
}
private static class GrEmptyAnnotationArgumentList extends LightElement implements GrAnnotationArgumentList {
private GrEmptyAnnotationArgumentList(PsiManager manager, Language language) {
public void addAttribute(GrAnnotationNameValuePair attribute) {
myAnnotationArgList.addAttribute(attribute);
}
private static class GrLightAnnotationArgumentList extends LightElement implements GrAnnotationArgumentList {
private List<GrAnnotationNameValuePair> myAttributes = null;
private GrAnnotationNameValuePair[] myCachedAttributes = GrAnnotationNameValuePair.EMPTY_ARRAY;
private GrLightAnnotationArgumentList(PsiManager manager, Language language) {
super(manager, language);
}
@NotNull
@Override
public GrAnnotationNameValuePair[] getAttributes() {
return GrAnnotationNameValuePair.EMPTY_ARRAY;
if (myCachedAttributes == null) {
assert myAttributes != null;
myCachedAttributes = myAttributes.toArray(new GrAnnotationNameValuePair[myAttributes.size()]);
}
return myCachedAttributes;
}
public void addAttribute(@NotNull GrAnnotationNameValuePair attribute) {
if (myAttributes == null) myAttributes = ContainerUtilRt.newArrayList();
myAttributes.add(attribute);
myCachedAttributes = null;
}
@Override
@@ -137,12 +159,16 @@ public class GrLightAnnotation extends LightElement implements GrAnnotation {
@Override
public void acceptChildren(GroovyElementVisitor visitor) {
//todo
if (myAttributes != null) {
for (GrAnnotationNameValuePair attribute : myAttributes) {
attribute.accept(visitor);
}
}
}
@Override
public String toString() {
return "light empty annotation argument list";
return "light annotation argument list";
}
}
}
@@ -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.
@@ -89,6 +89,12 @@ public class GrLightModifierList extends LightElement implements GrModifierList
throw new IncorrectOperationException();
}
@NotNull
@Override
public GrAnnotation[] getRawAnnotations() {
return getAnnotations();
}
public void checkSetModifierProperty(@NotNull String name, boolean value) throws IncorrectOperationException{
throw new IncorrectOperationException();
}
@@ -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.
@@ -84,7 +84,7 @@ public class GrStubUtils {
List<String> annoNames = ContainerUtil.newArrayList();
final PsiModifierList modifierList = psi.getModifierList();
if (modifierList instanceof GrModifierList) {
for (GrAnnotation annotation : ((GrModifierList)modifierList).getAnnotations()) {
for (GrAnnotation annotation : ((GrModifierList)modifierList).getRawAnnotations()) {
final String name = annotation.getShortName();
if (StringUtil.isNotEmpty(name)) {
annoNames.add(name);
@@ -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.
@@ -59,6 +59,7 @@ public final class GroovyCommonClassNames {
public static final String GROOVY_LANG_IMMUTABLE = "groovy.lang.Immutable";
@NonNls public static final String GROOVY_LANG_META_CLASS = "groovy.lang.MetaClass";
@NonNls public static final String GROOVY_LANG_GROOVY_CALLABLE = "groovy.lang.GroovyCallable";
@NonNls public static final String GROOVY_TRANSFORM_ANNOTATION_COLLECTOR = "groovy.transform.AnnotationCollector";
private GroovyCommonClassNames() {
@@ -102,6 +102,20 @@ public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
}"""
}
protected final void addAnnotationCollector() {
myFixture.addClass '''\
package groovy.transform;
@java.lang.annotation.Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
public @interface AnnotationCollector {
String processor() default "org.codehaus.groovy.transform.AnnotationCollectorTransform";
Class[] value() default {};
}
'''
}
/*void addHashMap() {
myFixture.addClass('''\
package java.util;
@@ -0,0 +1,128 @@
/*
* 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 org.jetbrains.plugins.groovy.lang.aliasAnnotations
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.NotNull
import org.jetbrains.plugins.groovy.LightGroovyTestCase
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition
/**
* @author Max Medvedev
*/
class GrAnnotationCollectorTest extends LightGroovyTestCase {
final String basePath = null
void testAnnotatedAlias() {
doTest('''\
import groovy.transform.*
@AnnotationCollector([ToString, EqualsAndHashCode, Immutable])
@interface Alias {}
@Alias(excludes = ["a"])
class F<caret>oo {
Integer a, b
}
''', '''\
@ToString(excludes = ["a"])
@EqualsAndHashCode(excludes = ["a"])
@Immutable
''')
}
void testAliasWithProperties() {
doTest('''\
import groovy.transform.*
@AnnotationCollector([ToString, EqualsAndHashCode, Immutable])
@interface Alias {}
@Alias(excludes = ["a"])
class F<caret>oo {
Integer a, b
}
''', '''\
@ToString(excludes = ["a"])
@EqualsAndHashCode(excludes = ["a"])
@Immutable
''')
}
void testMixedProperties() {
doTest('''\
import groovy.transform.*
@ToString(excludes = ['a', 'b'])
@AnnotationCollector([EqualsAndHashCode, Immutable])
@interface Alias {}
@Alias(excludes = ["a"])
class F<caret>oo {
Integer a, b
}
''', '''\
@ToString(excludes = ["a"])
@EqualsAndHashCode(excludes = ["a"])
@Immutable
''')
}
void doTest(@NotNull String text, @NotNull String expectedAnnotations) {
addAnnotationCollector()
myFixture.configureByText('_a.groovy', text)
final atCaret = myFixture.file.findElementAt(myFixture.editor.caretModel.offset)
final clazz = PsiTreeUtil.getParentOfType(atCaret, GrTypeDefinition.class)
assertNotNull(clazz)
final actual = getActualAnnotations(clazz)
assertEquals(expectedAnnotations, actual)
}
@NotNull
@NonNls
static String getActualAnnotations(@NotNull GrTypeDefinition clazz) {
StringBuilder buffer = new StringBuilder()
for (GrAnnotation annotation : clazz.modifierList.annotations) {
buffer << '@' << annotation.shortName
final GrAnnotationNameValuePair[] attributes = annotation.parameterList.attributes
if (attributes.length > 0) {
buffer << '('
for (GrAnnotationNameValuePair pair : attributes) {
final name = pair.name
if (name != null) {
buffer << name << " = "
}
buffer << pair.value.text
}
buffer << ')'
}
buffer << '\n'
}
return buffer.toString()
}
}
@@ -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.
@@ -24,7 +24,9 @@ import org.jetbrains.plugins.groovy.util.TestUtils
* @author Max Medvedev
*/
abstract class GrHighlightingTestBase extends LightGroovyTestCase {
final String basePath = TestUtils.testDataPath + 'highlighting/'
String getBasePath() {
TestUtils.testDataPath + 'highlighting/'
}
InspectionProfileEntry[] getCustomInspections() {[]}