/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2003-2007 University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.ba.jsr305;
import java.lang.annotation.ElementType;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.DescriptorFactory;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.MissingClassException;
import edu.umd.cs.findbugs.classfile.analysis.AnnotationValue;
import edu.umd.cs.findbugs.classfile.analysis.EnumValue;
/**
* Resolve annotations into type qualifiers.
*
* @author William Pugh
*/
public class TypeQualifierResolver {
static final ClassDescriptor typeQualifier = DescriptorFactory.createClassDescriptor(javax.annotation.meta.TypeQualifier.class);
static final ClassDescriptor typeQualifierNickname = DescriptorFactory
.createClassDescriptor(javax.annotation.meta.TypeQualifierNickname.class);
static final ClassDescriptor typeQualifierDefault = DescriptorFactory
.createClassDescriptor(javax.annotation.meta.TypeQualifierDefault.class);
static final ClassDescriptor elementTypeDescriptor = DescriptorFactory
.createClassDescriptor(java.lang.annotation.ElementType.class);
static final ClassDescriptor googleNullable = DescriptorFactory.createClassDescriptor("com/google/common/base/Nullable");
static final ClassDescriptor intellijNullable = DescriptorFactory.createClassDescriptor("org/jetbrains/annotations/Nullable");
static final ClassDescriptor eclipseNullable = DescriptorFactory.createClassDescriptor("org/eclipse/jdt/annotation/Nullable");
static final ClassDescriptor eclipseNonNull = DescriptorFactory.createClassDescriptor("org/eclipse/jdt/annotation/NonNull");
// javax.annotations.ParametersAreNonnullByDefault ?
static final ClassDescriptor eclipseNonNullByDefault = DescriptorFactory.createClassDescriptor("org/eclipse/jdt/annotation/NonNullByDefault");
/**
* Resolve an AnnotationValue into a list of AnnotationValues representing
* type qualifier annotations.
*
* @param value
* AnnotationValue representing the use of an annotation
* @return Collection of AnnotationValues representing resolved
* TypeQualifier annotations
*/
public static Collection<AnnotationValue> resolveTypeQualifiers(AnnotationValue value) {
LinkedList<AnnotationValue> result = new LinkedList<AnnotationValue>();
resolveTypeQualifierNicknames(value, result, new LinkedList<ClassDescriptor>());
return result;
}
/**
* Resolve collection of AnnotationValues (which have been used to annotate
* an AnnotatedObject or method parameter) into collection of resolved type
* qualifier AnnotationValues.
*
* @param values
* Collection of AnnotationValues used to annotate an
* AnnotatedObject or method parameter
* @return Collection of resolved type qualifier AnnotationValues
*/
public static Collection<AnnotationValue> resolveTypeQualifierDefaults(Collection<AnnotationValue> values,
ElementType elementType) {
LinkedList<AnnotationValue> result = new LinkedList<AnnotationValue>();
for (AnnotationValue value : values) {
resolveTypeQualifierDefaults(value, elementType, result);
}
return result;
}
/**
* Resolve an annotation into AnnotationValues representing any type
* qualifier(s) the annotation resolves to. Detects annotations which are
* directly marked as TypeQualifier annotations, and also resolves the use
* of TypeQualifierNickname annotations.
*
* @param value
* AnnotationValue representing the use of an annotation
* @param result
* LinkedList containing resolved type qualifier AnnotationValues
* @param onStack
* stack of annotations being processed; used to detect cycles in
* type qualifier nicknames
*/
private static void resolveTypeQualifierNicknames(AnnotationValue value, LinkedList<AnnotationValue> result,
LinkedList<ClassDescriptor> onStack) {
ClassDescriptor annotationClass = value.getAnnotationClass();
if (onStack.contains(annotationClass)) {
AnalysisContext.logError("Cycle found in type nicknames: " + onStack);
return;
}
try {
onStack.add(annotationClass);
try {
if (annotationClass.equals(googleNullable)
|| annotationClass.equals(eclipseNullable)
|| annotationClass.equals(intellijNullable)) {
resolveTypeQualifierNicknames(new AnnotationValue(JSR305NullnessAnnotations.CHECK_FOR_NULL), result, onStack);
return;
}
if (annotationClass.equals(eclipseNonNull) || annotationClass.equals(eclipseNonNullByDefault)) {
resolveTypeQualifierNicknames(new AnnotationValue(JSR305NullnessAnnotations.NONNULL), result, onStack);
return;
}
XClass c = Global.getAnalysisCache().getClassAnalysis(XClass.class, annotationClass);
if (c.getAnnotationDescriptors().contains(typeQualifierNickname)) {
for (ClassDescriptor d : c.getAnnotationDescriptors()) {
if (!d.equals(typeQualifierNickname)) {
resolveTypeQualifierNicknames(c.getAnnotation(d), result, onStack);
}
}
} else if (c.getAnnotationDescriptors().contains(typeQualifier)) {
result.add(value);
}
} catch (MissingClassException e) {
logMissingAnnotationClass(e);
return;
} catch (CheckedAnalysisException e) {
AnalysisContext.logError("Error resolving " + annotationClass, e);
return;
}
} finally {
onStack.removeLast();
}
}
public static void logMissingAnnotationClass(MissingClassException e) {
ClassDescriptor c = e.getClassDescriptor();
if (c.getClassName().startsWith("javax.annotation")) {
AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(c);
}
}
/**
* Resolve collection of AnnotationValues (which have been used to annotate
* an AnnotatedObject or method parameter) into collection of resolved type
* qualifier AnnotationValues.
*
* @param values
* Collection of AnnotationValues used to annotate an
* AnnotatedObject or method parameter
* @return Collection of resolved type qualifier AnnotationValues
*/
public static Collection<AnnotationValue> resolveTypeQualifiers(Collection<AnnotationValue> values) {
if (values.isEmpty()) {
return Collections.emptyList();
}
LinkedList<AnnotationValue> result = new LinkedList<AnnotationValue>();
LinkedList<ClassDescriptor> onStack = new LinkedList<ClassDescriptor>();
for (AnnotationValue value : values) {
resolveTypeQualifierNicknames(value, result, onStack);
}
return result;
}
/**
* Resolve an annotation into AnnotationValues representing any type
* qualifier(s) the annotation resolves to. Detects annotations which are
* directly marked as TypeQualifier annotations, and also resolves the use
* of TypeQualifierNickname annotations.
*
* @param value
* AnnotationValue representing the use of an annotation
* @param result
* LinkedList containing resolved type qualifier AnnotationValues
* @param onStack
* stack of annotations being processed; used to detect cycles in
* type qualifier nicknames
*/
private static void resolveTypeQualifierDefaults(AnnotationValue value, ElementType defaultFor,
LinkedList<AnnotationValue> result) {
try {
XClass c = Global.getAnalysisCache().getClassAnalysis(XClass.class, value.getAnnotationClass());
AnnotationValue defaultAnnotation = c.getAnnotation(typeQualifierDefault);
if (defaultAnnotation == null) {
return;
}
for (Object o : (Object[]) defaultAnnotation.getValue("value")) {
if (o instanceof EnumValue) {
EnumValue e = (EnumValue) o;
if (e.desc.equals(elementTypeDescriptor) && e.value.equals(defaultFor.name())) {
for (ClassDescriptor d : c.getAnnotationDescriptors()) {
if (!d.equals(typeQualifierDefault)) {
resolveTypeQualifierNicknames(c.getAnnotation(d), result, new LinkedList<ClassDescriptor>());
}
}
break;
}
}
}
} catch (MissingClassException e) {
logMissingAnnotationClass(e);
} catch (CheckedAnalysisException e) {
AnalysisContext.logError("Error resolving " + value.getAnnotationClass(), e);
} catch (ClassCastException e) {
AnalysisContext.logError("ClassCastException " + value.getAnnotationClass(), e);
}
}
}