/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2003-2008 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 edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.MissingClassException;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.ba.XMethod;
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.util.DualKeyHashMap;
import edu.umd.cs.findbugs.util.Util;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
/**
* A TypeQualifierValue is a pair specifying a type qualifier annotation
* and a value. Each TypeQualifierValue is effectively a different
* type qualifier. For example, if Foo is a type qualifier annotation
* having an int value, then Foo(0), Foo(1), etc. are all
* different type qualifiers which must be checked separately.
*
* @author William Pugh
*/
public class TypeQualifierValue {
public static final boolean DEBUG = SystemProperties.getBoolean("tqv.debug");
private static final ClassDescriptor EXCLUSIVE_ANNOTATION =
DescriptorFactory.instance().getClassDescriptor("javax/annotation/meta/Exclusive");
private static final ClassDescriptor EXHAUSTIVE_ANNOTATION =
DescriptorFactory.instance().getClassDescriptor("javax/annotation/meta/Exhaustive");
public final ClassDescriptor typeQualifier;
public final @CheckForNull Object value;
private boolean isStrict;
private boolean isExclusive;
private boolean isExhaustive;
private TypeQualifierValue(ClassDescriptor typeQualifier, @CheckForNull Object value) {
this.typeQualifier = typeQualifier;
this.value = value;
this.isStrict = false; // will be set to true if this is a strict type qualifier value
this.isExclusive = false; // will be set to true if this is an exclusive type qualifier value
this.isExhaustive = false; // will be set to true if this is an exhaustive type qualifier value
}
static class Data {
/**
* Cache in which constructed TypeQualifierValues are interned.
*/
DualKeyHashMap <ClassDescriptor, Object, TypeQualifierValue> typeQualifierMap =
new DualKeyHashMap <ClassDescriptor, Object, TypeQualifierValue>();
/**
* Set of all known TypeQualifierValues.
*/
Set<TypeQualifierValue> allKnownTypeQualifiers =
new HashSet<TypeQualifierValue>();
}
private static ThreadLocal<Data> instance = new ThreadLocal<Data>() {
@Override
protected Data initialValue() {
return new Data();
}
};
public static void clearInstance() {
instance.remove();
}
/**
* Given a ClassDescriptor/value pair, return the
* interned TypeQualifierValue representing that pair.
*
* @param desc a ClassDescriptor denoting a type qualifier annotation
* @param value a value
* @return an interned TypeQualifierValue object
*/
public static @NonNull TypeQualifierValue getValue(ClassDescriptor desc, Object value) {
DualKeyHashMap<ClassDescriptor, Object, TypeQualifierValue> map = instance.get().typeQualifierMap;
TypeQualifierValue result = map.get(desc, value);
if (result != null) return result;
result = new TypeQualifierValue(desc, value);
determineIfQualifierIsStrict(desc, result);
determineIfQualifierIsExclusiveOrExhaustive(desc, result);
map.put(desc, value, result);
instance.get().allKnownTypeQualifiers.add(result);
return result;
}
/**
* Get Collection of all known TypeQualifierValues.
*
* @return Collection of all known TypeQualifierValues
*/
public static Collection<TypeQualifierValue> getAllKnownTypeQualifiers() {
return Collections.unmodifiableSet(instance.get().allKnownTypeQualifiers);
}
/**
* Get the "complementary" TypeQualifierValues for given exclusive type qualifier.
*
* @param tqv a type qualifier (which must be exclusive)
* @return Collection of complementary exclusive type qualifiers
*/
public static Collection<TypeQualifierValue> getComplementaryExclusiveTypeQualifierValue(TypeQualifierValue tqv) {
assert tqv.isExclusiveQualifier();
LinkedList<TypeQualifierValue> result = new LinkedList<TypeQualifierValue>();
for (TypeQualifierValue t : instance.get().allKnownTypeQualifiers) {
//
// Any TypeQualifierValue with the same
// annotation class but a different value is a complementary
// type qualifier.
//
if (t.typeQualifier.equals(tqv.typeQualifier) && !Util.nullSafeEquals(t.value, tqv.value)) {
result.add(t);
}
}
return result;
}
/**
* Determine whether or not given TypeQualifierValue has multiple variants.
* I.e., if Color is a type qualifier having values RED, GREEN, and BLUE,
* then there are 3 variants, Color(RED), Color(GREEN), and COLOR(BLUE).
*
* @param tqv a TypeQualifierValue
* @return true if there are multiple variants of this type qualifier, false otherwise
*/
public static boolean hasMultipleVariants(TypeQualifierValue tqv) {
int count = 0;
for (TypeQualifierValue t : instance.get().allKnownTypeQualifiers) {
if (t.typeQualifier.equals(tqv.typeQualifier)) {
count++;
}
}
return count > 1;
}
private static void determineIfQualifierIsStrict(ClassDescriptor desc, TypeQualifierValue result) {
if (DEBUG) {
System.out.print("Checking to see if " + desc + " requires strict checking...");
}
// Check to see if the type qualifier should be checked strictly
try {
XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, desc);
// Annotation elements appear as abstract methods in the annotation class (interface).
// So, if the type qualifier annotation has specified a default When value,
// it will appear as an abstract method called "when".
XMethod whenMethod = xclass.findMethod("when", "()Ljavax/annotation/meta/When;", false);
if (whenMethod == null) {
result.setIsStrict();
}
} catch (MissingClassException e) {
AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e.getClassNotFoundException());
} catch (CheckedAnalysisException e) {
AnalysisContext.logError("Error looking up annotation class " + desc.toDottedClassName(), e);
}
if (DEBUG) {
System.out.println(result.isStrictQualifier() ? "yes" : "no");
}
}
private static void determineIfQualifierIsExclusiveOrExhaustive(ClassDescriptor desc, TypeQualifierValue result) {
if (DEBUG) {
System.out.print("Checking to see if " + desc + " is exclusive or exhaustive...");
}
boolean isExclusive = false;
boolean isExhaustive = false;
try {
XClass xclass = Global.getAnalysisCache().getClassAnalysis(XClass.class, desc);
// If the value() method is annotated as @Exhaustive, the type qualifier is exhaustive and exclusive.
// If the value() method is annotated as @Exclusive, the type qualifier is exclusive.
for (XMethod xmethod : xclass.getXMethods()) {
if (xmethod.getName().equals("value") && xmethod.getSignature().startsWith("()")) {
isExhaustive = xmethod.getAnnotation(EXHAUSTIVE_ANNOTATION) != null;
if (isExhaustive) {
// exhaustive qualifiers are automatically exclusive
isExclusive = true;
} else {
// see if there is an explicit @Exclusive annotation
isExclusive = xmethod.getAnnotation(EXCLUSIVE_ANNOTATION) != null;
}
break;
}
}
} catch (MissingClassException e) {
AnalysisContext.currentAnalysisContext().getLookupFailureCallback().reportMissingClass(e.getClassNotFoundException());
} catch (CheckedAnalysisException e) {
AnalysisContext.logError("Error looking up annotation class " + desc.toDottedClassName(), e);
}
if (isExclusive) {
result.setIsExclusive();
}
if (isExhaustive) {
result.setIsExhaustive();
}
if (DEBUG) {
//System.out.println(result.isExclusiveQualifier() ? "yes" : "no");
if (isExhaustive) {
System.out.println("exhaustive,exclusive");
} else if (isExclusive) {
System.out.println("exclusive");
} else {
System.out.println("neither");
}
}
}
/**
* Get the ClassDescriptor which specifies the type qualifier annotation.
*
* @return ClassDescriptor which specifies the type qualifier annotation
*/
public ClassDescriptor getTypeQualifierClassDescriptor() {
return typeQualifier;
}
/**
* Mark this as a type qualifier value that should
* be checked strictly.
*/
private void setIsStrict() {
this.isStrict = true;
}
/**
* Return whether or not this TypeQualifierValue denotes
* a strict qualifier.
*
* @return true if type qualifier is strict, false otherwise
*/
public boolean isStrictQualifier() {
return isStrict;
}
private void setIsExclusive() {
this.isExclusive = true;
}
/**
* Return whether or not this TypeQualifierValue denotes
* an exclusive qualifier.
*
* @return true if type qualifier is exclusive, false otherwise
*/
public boolean isExclusiveQualifier() {
return isExclusive;
}
private void setIsExhaustive() {
this.isExhaustive = true;
}
/**
* Return whether or not this TypeQualifierValue denotes
* an exhaustive qualifier.
*
* @return true if type qualifier is exhaustive, false otherwise
*/
public boolean isExhaustiveQualifier() {
return isExhaustive;
}
@Override
public int hashCode() {
int result = typeQualifier.hashCode();
if (value != null) result += 37*value.hashCode();
return result;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof TypeQualifierValue)) return false;
TypeQualifierValue other = (TypeQualifierValue) o;
return typeQualifier.equals(other.typeQualifier) && Util.nullSafeEquals(value, other.value);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(typeQualifier.toString());
if (value != null) {
buf.append(':');
buf.append(value.toString());
}
return buf.toString();
}
}