blob: d6ab1f291739956481a4a4a7648f3021944d7faf [file] [log] [blame]
/*
* Copyright 2000-2014 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.annotator.checkers;
import com.intellij.codeInsight.daemon.JavaErrorMessages;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyBundle;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
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.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.annotation.GrAnnotationImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Max Medvedev
*/
public abstract class CustomAnnotationChecker {
public static final ExtensionPointName<CustomAnnotationChecker> EP_NAME = ExtensionPointName.create("org.intellij.groovy.customAnnotationChecker");
public boolean checkArgumentList(@NotNull AnnotationHolder holder, @NotNull GrAnnotation annotation) {return false;}
public boolean checkApplicability(@NotNull AnnotationHolder holder, @NotNull GrAnnotation annotation) {return false;}
@Nullable
public static String isAnnotationApplicable(@NotNull GrAnnotation annotation, @Nullable PsiAnnotationOwner owner) {
if (!(owner instanceof PsiElement)) return null;
PsiElement ownerToUse = owner instanceof PsiModifierList ? ((PsiElement)owner).getParent() : (PsiElement)owner;
PsiAnnotation.TargetType[] elementTypeFields = GrAnnotationImpl.getApplicableElementTypeFields(ownerToUse);
if (elementTypeFields.length != 0 && !GrAnnotationImpl.isAnnotationApplicableTo(annotation, elementTypeFields)) {
String annotationTargetText = JavaErrorMessages.message("annotation.target." + elementTypeFields[0]);
GrCodeReferenceElement ref = annotation.getClassReference();
return JavaErrorMessages.message("annotation.not.applicable", ref.getText(), annotationTargetText);
}
return null;
}
public static void checkAnnotationArguments(@NotNull AnnotationHolder holder,
@NotNull PsiClass annotation,
@NotNull GrCodeReferenceElement refToHighlight,
@NotNull GrAnnotationNameValuePair[] attributes,
boolean checkMissedAttributes) {
Set<String> usedAttrs = new HashSet<String>();
if (attributes.length > 0) {
final PsiElement identifier = attributes[0].getNameIdentifierGroovy();
if (attributes.length == 1 && identifier == null) {
checkAnnotationValue(annotation, attributes[0], "value", usedAttrs, attributes[0].getValue(), holder);
}
else {
for (GrAnnotationNameValuePair attribute : attributes) {
final String name = attribute.getName();
if (name != null) {
final PsiElement toHighlight = attribute.getNameIdentifierGroovy();
assert toHighlight != null;
checkAnnotationValue(annotation, toHighlight, name, usedAttrs, attribute.getValue(), holder);
}
}
}
}
List<String> missedAttrs = new ArrayList<String>();
final PsiMethod[] methods = annotation.getMethods();
for (PsiMethod method : methods) {
final String name = method.getName();
if (usedAttrs.contains(name) ||
method instanceof PsiAnnotationMethod && ((PsiAnnotationMethod)method).getDefaultValue() != null) {
continue;
}
missedAttrs.add(name);
}
if (checkMissedAttributes && !missedAttrs.isEmpty()) {
holder.createErrorAnnotation(refToHighlight, GroovyBundle.message("missed.attributes", StringUtil.join(missedAttrs, ", ")));
}
}
private static void checkAnnotationValue(@NotNull PsiClass annotation,
@NotNull PsiElement identifierToHighlight,
@NotNull String name,
@NotNull Set<String> usedAttrs,
@Nullable GrAnnotationMemberValue value,
@NotNull AnnotationHolder holder) {
if (usedAttrs.contains(name)) {
holder.createErrorAnnotation(identifierToHighlight, GroovyBundle.message("duplicate.attribute"));
}
usedAttrs.add(name);
final PsiMethod[] methods = annotation.findMethodsByName(name, false);
if (methods.length == 0) {
holder.createErrorAnnotation(identifierToHighlight,
GroovyBundle.message("at.interface.0.does.not.contain.attribute", annotation.getQualifiedName(), name));
}
else {
final PsiMethod method = methods[0];
final PsiType ltype = method.getReturnType();
if (ltype != null && value != null) {
checkAnnotationValueByType(holder, value, ltype, true);
}
}
}
public static void checkAnnotationValueByType(@NotNull AnnotationHolder holder,
@NotNull GrAnnotationMemberValue value,
@Nullable PsiType ltype,
boolean skipArrays) {
final GlobalSearchScope resolveScope = value.getResolveScope();
final PsiManager manager = value.getManager();
if (value instanceof GrExpression) {
final PsiType rtype;
if (value instanceof GrClosableBlock) {
rtype = PsiType.getJavaLangClass(manager, resolveScope);
}
else {
rtype = ((GrExpression)value).getType();
}
if (rtype != null && !checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) {
holder.createErrorAnnotation(value,
GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText()));
}
}
else if (value instanceof GrAnnotation) {
final PsiElement resolved = ((GrAnnotation)value).getClassReference().resolve();
if (resolved instanceof PsiClass) {
final PsiClassType rtype = JavaPsiFacade.getElementFactory(value.getProject()).createType((PsiClass)resolved, PsiSubstitutor.EMPTY);
if (!checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) {
holder.createErrorAnnotation(value, GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText()));
}
}
}
else if (value instanceof GrAnnotationArrayInitializer) {
if (ltype instanceof PsiArrayType) {
final PsiType componentType = ((PsiArrayType)ltype).getComponentType();
final GrAnnotationMemberValue[] initializers = ((GrAnnotationArrayInitializer)value).getInitializers();
for (GrAnnotationMemberValue initializer : initializers) {
checkAnnotationValueByType(holder, initializer, componentType, false);
}
}
else {
final PsiType rtype = TypesUtil.getTupleByAnnotationArrayInitializer((GrAnnotationArrayInitializer)value);
if (!checkAnnoTypeAssignable(ltype, rtype, value, skipArrays)) {
holder.createErrorAnnotation(value, GroovyBundle.message("cannot.assign", rtype.getPresentableText(), ltype.getPresentableText()));
}
}
}
}
private static boolean checkAnnoTypeAssignable(@Nullable PsiType type,
@Nullable PsiType rtype,
@NotNull GroovyPsiElement context,
boolean skipArrays) {
rtype = TypesUtil.unboxPrimitiveTypeWrapper(rtype);
if (TypesUtil.isAssignableByMethodCallConversion(type, rtype, context)) return true;
if (!(type instanceof PsiArrayType && skipArrays)) return false;
final PsiType componentType = ((PsiArrayType)type).getComponentType();
return checkAnnoTypeAssignable(componentType, rtype, context, skipArrays);
}
public static void highlightErrors(AnnotationHolder holder, Map<PsiElement, String> errors) {
for (Map.Entry<PsiElement, String> entry : errors.entrySet()) {
holder.createErrorAnnotation(entry.getKey(), entry.getValue());
}
}
}