package org.checkerframework.checker.formatter;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.Tree;
import java.lang.annotation.Annotation;
import java.util.IllegalFormatException;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.checker.formatter.qual.ConversionCategory;
import org.checkerframework.checker.formatter.qual.Format;
import org.checkerframework.checker.formatter.qual.FormatBottom;
import org.checkerframework.checker.formatter.qual.InvalidFormat;
import org.checkerframework.checker.formatter.qual.UnknownFormat;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
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;
/**
* Adds {@link Format} to the type of tree, if it is a {@code String} or {@code char} literal that
* represents a satisfiable format. The annotation's value is set to be a list of appropriate {@link
* ConversionCategory} values for every parameter of the format.
*
* @see ConversionCategory
* @author Konstantin Weitz
*/
public class FormatterAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
protected final AnnotationMirror UNKNOWNFORMAT;
protected final AnnotationMirror FORMAT;
protected final AnnotationMirror INVALIDFORMAT;
protected final AnnotationMirror FORMATBOTTOM;
protected final FormatterTreeUtil treeUtil;
public FormatterAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
UNKNOWNFORMAT = AnnotationUtils.fromClass(elements, UnknownFormat.class);
FORMAT = AnnotationUtils.fromClass(elements, Format.class);
INVALIDFORMAT = AnnotationUtils.fromClass(elements, InvalidFormat.class);
FORMATBOTTOM = AnnotationUtils.fromClass(elements, FormatBottom.class);
this.treeUtil = new FormatterTreeUtil(checker);
this.postInit();
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return getBundledTypeQualifiersWithoutPolyAll(UnknownFormat.class, FormatBottom.class);
}
@Override
public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) {
return new FormatterQualifierHierarchy(factory);
}
@Override
protected TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(super.createTreeAnnotator(), new FormatterTreeAnnotator(this));
}
private class FormatterTreeAnnotator extends TreeAnnotator {
public FormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
}
@Override
public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
if (!type.isAnnotatedInHierarchy(FORMAT)) {
String format = null;
if (tree.getKind() == Tree.Kind.STRING_LITERAL) {
format = (String) tree.getValue();
} else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) {
format = Character.toString((Character) tree.getValue());
}
if (format != null) {
AnnotationMirror anno;
try {
ConversionCategory[] cs = FormatUtil.formatParameterCategories(format);
anno =
FormatterAnnotatedTypeFactory.this.treeUtil
.categoriesToFormatAnnotation(cs);
} catch (IllegalFormatException e) {
anno =
FormatterAnnotatedTypeFactory.this.treeUtil
.exceptionToInvalidFormatAnnotation(e);
}
type.addAnnotation(anno);
}
}
return super.visitLiteral(tree, type);
}
}
class FormatterQualifierHierarchy extends GraphQualifierHierarchy {
public FormatterQualifierHierarchy(MultiGraphFactory f) {
super(f, FORMATBOTTOM);
}
@Override
public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) {
if (AnnotationUtils.areSameIgnoringValues(subAnno, FORMAT)
&& AnnotationUtils.areSameIgnoringValues(superAnno, FORMAT)) {
ConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno);
ConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno);
if (rhsArgTypes.length > lhsArgTypes.length) {
return false;
}
for (int i = 0; i < rhsArgTypes.length; ++i) {
if (!ConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) {
return false;
}
}
return true;
}
if (AnnotationUtils.areSameIgnoringValues(superAnno, FORMAT)) {
superAnno = FORMAT;
}
if (AnnotationUtils.areSameIgnoringValues(subAnno, FORMAT)) {
subAnno = FORMAT;
}
if (AnnotationUtils.areSameIgnoringValues(superAnno, INVALIDFORMAT)) {
superAnno = INVALIDFORMAT;
}
if (AnnotationUtils.areSameIgnoringValues(subAnno, INVALIDFORMAT)) {
subAnno = INVALIDFORMAT;
}
return super.isSubtype(subAnno, superAnno);
}
@Override
public AnnotationMirror leastUpperBound(AnnotationMirror anno1, AnnotationMirror anno2) {
if (AnnotationUtils.areSameIgnoringValues(anno1, FORMATBOTTOM)) {
return anno2;
}
if (AnnotationUtils.areSameIgnoringValues(anno2, FORMATBOTTOM)) {
return anno1;
}
if (AnnotationUtils.areSameIgnoringValues(anno1, FORMAT)
&& AnnotationUtils.areSameIgnoringValues(anno2, FORMAT)) {
ConversionCategory[] shorterArgTypesList =
treeUtil.formatAnnotationToCategories(anno1);
ConversionCategory[] longerArgTypesList =
treeUtil.formatAnnotationToCategories(anno2);
if (shorterArgTypesList.length > longerArgTypesList.length) {
ConversionCategory[] temp = longerArgTypesList;
longerArgTypesList = shorterArgTypesList;
shorterArgTypesList = temp;
}
// From the manual:
// It is legal to use a format string with fewer format specifiers
// than required, but a warning is issued.
ConversionCategory[] resultArgTypes =
new ConversionCategory[longerArgTypesList.length];
for (int i = 0; i < shorterArgTypesList.length; ++i) {
resultArgTypes[i] =
ConversionCategory.intersect(
shorterArgTypesList[i], longerArgTypesList[i]);
}
for (int i = shorterArgTypesList.length; i < longerArgTypesList.length; ++i) {
resultArgTypes[i] = longerArgTypesList[i];
}
return treeUtil.categoriesToFormatAnnotation(resultArgTypes);
}
if (AnnotationUtils.areSameIgnoringValues(anno1, INVALIDFORMAT)
&& AnnotationUtils.areSameIgnoringValues(anno2, INVALIDFORMAT)) {
assert !anno1.getElementValues().isEmpty();
assert !anno2.getElementValues().isEmpty();
if (AnnotationUtils.areSame(anno1, anno2)) {
return anno1;
}
return treeUtil.stringToInvalidFormatAnnotation(
"("
+ treeUtil.invalidFormatAnnotationToErrorMessage(anno1)
+ " or "
+ treeUtil.invalidFormatAnnotationToErrorMessage(anno2)
+ ")");
}
return UNKNOWNFORMAT;
}
@Override
public AnnotationMirror greatestLowerBound(AnnotationMirror anno1, AnnotationMirror anno2) {
if (AnnotationUtils.areSameIgnoringValues(anno1, UNKNOWNFORMAT)) {
return anno2;
}
if (AnnotationUtils.areSameIgnoringValues(anno2, UNKNOWNFORMAT)) {
return anno1;
}
if (AnnotationUtils.areSameIgnoringValues(anno1, FORMAT)
&& AnnotationUtils.areSameIgnoringValues(anno2, FORMAT)) {
ConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1);
ConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2);
// From the manual:
// It is legal to use a format string with fewer format specifiers
// than required, but a warning is issued.
int length = anno1ArgTypes.length;
if (anno2ArgTypes.length < length) {
length = anno2ArgTypes.length;
}
ConversionCategory[] anno3ArgTypes = new ConversionCategory[length];
for (int i = 0; i < length; ++i) {
anno3ArgTypes[i] = ConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]);
}
return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes);
}
if (AnnotationUtils.areSameIgnoringValues(anno1, INVALIDFORMAT)
&& AnnotationUtils.areSameIgnoringValues(anno2, INVALIDFORMAT)) {
assert !anno1.getElementValues().isEmpty();
assert !anno2.getElementValues().isEmpty();
if (AnnotationUtils.areSame(anno1, anno2)) {
return anno1;
}
return treeUtil.stringToInvalidFormatAnnotation(
"("
+ treeUtil.invalidFormatAnnotationToErrorMessage(anno1)
+ " and "
+ treeUtil.invalidFormatAnnotationToErrorMessage(anno2)
+ ")");
}
return FORMATBOTTOM;
}
}
}