checker.src.org.checkerframework.checker.propkey.PropertyKeyAnnotatedTypeFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
The Checker Framework enhances Java’s type system to
make it more powerful and useful. This lets software developers
detect and prevent errors in their Java programs.
The Checker Framework includes compiler plug-ins ("checkers")
that find bugs or verify their absence. It also permits you to
write your own compiler plug-ins.
package org.checkerframework.checker.propkey;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.*;
import javax.lang.model.element.AnnotationMirror;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.checker.propkey.qual.PropertyKey;
import org.checkerframework.checker.propkey.qual.PropertyKeyBottom;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.type.*;
import org.checkerframework.framework.type.treeannotator.ImplicitsTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.util.GraphQualifierHierarchy;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.Tree;
/**
* This AnnotatedTypeFactory adds PropertyKey annotations to String literals
* that contain values from lookupKeys.
*
* @author wmdietl
*/
public class PropertyKeyAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
private final Set lookupKeys;
protected AnnotationMirror PROPKEY_BOTTOM;
public PropertyKeyAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
this.lookupKeys = Collections.unmodifiableSet(buildLookupKeys());
// Reuse the framework Bottom annotation and make it the default for the
// null literal.
PROPKEY_BOTTOM = AnnotationUtils.fromClass(elements, PropertyKeyBottom.class);
this.postInit();
}
@Override
public TreeAnnotator createTreeAnnotator() {
ImplicitsTreeAnnotator implicitsTreeAnnotator = new ImplicitsTreeAnnotator(this);
implicitsTreeAnnotator.addTreeKind(Tree.Kind.NULL_LITERAL, PROPKEY_BOTTOM);
return new ListTreeAnnotator(
new PropagationTreeAnnotator(this),
implicitsTreeAnnotator,
new KeyLookupTreeAnnotator(this, PropertyKey.class));
}
/**
* This TreeAnnotator checks for every String literal whether it is included in the lookup
* keys. If it is, the given annotation is added to the literal; otherwise, nothing happens.
* Subclasses of this AnnotatedTypeFactory can directly reuse this class and use a different
* annotation as parameter.
*/
protected class KeyLookupTreeAnnotator extends TreeAnnotator {
AnnotationMirror theAnnot;
public KeyLookupTreeAnnotator(BaseAnnotatedTypeFactory atf, Class annot) {
super(atf);
theAnnot = AnnotationUtils.fromClass(elements, annot);
}
@Override
public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
if (!type.isAnnotatedInHierarchy(theAnnot)
&& tree.getKind() == Tree.Kind.STRING_LITERAL
&& strContains(lookupKeys, tree.getValue().toString())) {
type.addAnnotation(theAnnot);
}
// A possible extension is to record all the keys that have been used and
// in the end output a list of keys that were not used in the program,
// possibly pointing to the opposite problem, keys that were supposed to
// be used somewhere, but have not been, maybe because of copy-and-paste errors.
return super.visitLiteral(tree, type);
}
// Result of binary op might not be a property key.
@Override
public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
type.removeAnnotation(theAnnot);
return null; // super.visitBinary(node, type);
}
// Result of unary op might not be a property key.
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
type.removeAnnotation(theAnnot);
return null; // super.visitCompoundAssignment(node, type);
}
}
/**
* Instead of a precise comparison, we incrementally remove leading dot-separated
* strings until we find a match.
* For example if messages contains "y.z" and we look for "x.y.z" we find a match
* after removing the first "x.".
*
* Compare to SourceChecker.fullMessageOf.
*/
private static boolean strContains(Set messages, String messageKey) {
String key = messageKey;
do {
if (messages.contains(key)) {
return true;
}
int dot = key.indexOf('.');
if (dot < 0) return false;
key = key.substring(dot + 1);
} while (true);
}
/**
* Returns a set of the valid keys that can be used.
*/
public Set getLookupKeys() {
return this.lookupKeys;
}
private Set buildLookupKeys() {
Set result = new HashSet();
if (checker.hasOption("propfiles")) {
result.addAll( keysOfPropertyFiles(checker.getOption("propfiles")) );
}
if (checker.hasOption("bundlenames")) {
result.addAll( keysOfResourceBundle(checker.getOption("bundlenames")) );
}
return result;
}
private Set keysOfPropertyFiles(String names) {
String[] namesArr = names.split(":");
if (namesArr == null) {
checker.message(Kind.WARNING, "Couldn't parse the properties files: <" + names + ">");
return Collections.emptySet();
}
Set result = new HashSet();
for (String name : namesArr) {
try {
Properties prop = new Properties();
InputStream in = null;
ClassLoader cl = this.getClass().getClassLoader();
if (cl == null) {
// the class loader is null if the system class loader was
// used
cl = ClassLoader.getSystemClassLoader();
}
in = cl.getResourceAsStream(name);
if (in == null) {
// if the classloader didn't manage to load the file, try
// whether a FileInputStream works. For absolute paths this
// might help.
try {
in = new FileInputStream(name);
} catch (FileNotFoundException e) {
// ignore
}
}
if (in == null) {
checker.message(Kind.WARNING, "Couldn't find the properties file: " + name);
// report(Result.failure("propertykeychecker.filenotfound",
// name), null);
// return Collections.emptySet();
continue;
}
prop.load(in);
result.addAll(prop.stringPropertyNames());
} catch (Exception e) {
// TODO: is there a nicer way to report messages, that are not
// connected to an AST node?
// One cannot use report, because it needs a node.
checker.message(Kind.WARNING, "Exception in PropertyKeyChecker.keysOfPropertyFile: " + e);
e.printStackTrace();
}
}
return result;
}
private Set keysOfResourceBundle(String bundleNames) {
String[] namesArr = bundleNames.split(":");
if (namesArr == null) {
checker.message(Kind.WARNING, "Couldn't parse the resource bundles: <" + bundleNames + ">");
return Collections.emptySet();
}
Set result = new HashSet();
for (String bundleName : namesArr) {
ResourceBundle bundle = ResourceBundle.getBundle(bundleName);
if (bundle == null) {
checker.message(Kind.WARNING, "Couldn't find the resource bundle: <" + bundleName
+ "> for locale <" + Locale.getDefault() + ">");
continue;
}
result.addAll(bundle.keySet());
}
return result;
}
@Override
public GraphQualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) {
return new GraphQualifierHierarchy(factory, PROPKEY_BOTTOM);
}
}