blob: 82edb68a2ab5e7406c7371f5ae788493ae4601f1 [file] [log] [blame]
package org.checkerframework.framework.ajava;
import com.github.javaparser.ParseProblemException;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
import com.github.javaparser.ast.expr.BooleanLiteralExpr;
import com.github.javaparser.ast.expr.CharLiteralExpr;
import com.github.javaparser.ast.expr.ClassExpr;
import com.github.javaparser.ast.expr.DoubleLiteralExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.IntegerLiteralExpr;
import com.github.javaparser.ast.expr.LongLiteralExpr;
import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
import com.github.javaparser.ast.expr.MemberValuePair;
import com.github.javaparser.ast.expr.Name;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.expr.UnaryExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TypesUtils;
/**
* Methods for converting a {@code AnnotationMirror} into a JavaParser {@code AnnotationExpr},
* namely {@code annotationMirrorToAnnotationExpr}.
*/
public class AnnotationMirrorToAnnotationExprConversion {
/**
* Converts an AnnotationMirror into a JavaParser {@code AnnotationExpr}.
*
* @param annotation the annotation to convert
* @return a JavaParser {@code AnnotationExpr} representing the same annotation with the same
* element values. The converted annotation will contain the annotation's fully qualified
* name.
*/
public static AnnotationExpr annotationMirrorToAnnotationExpr(AnnotationMirror annotation) {
Map<? extends ExecutableElement, ? extends AnnotationValue> values =
annotation.getElementValues();
Name name = createQualifiedName(AnnotationUtils.annotationName(annotation));
if (values.isEmpty()) {
return new MarkerAnnotationExpr(name);
}
NodeList<MemberValuePair> convertedValues = convertAnnotationValues(values);
if (convertedValues.size() == 1
&& convertedValues.get(0).getName().asString().equals("value")) {
return new SingleMemberAnnotationExpr(name, convertedValues.get(0).getValue());
}
return new NormalAnnotationExpr(name, convertedValues);
}
/**
* Converts a mapping of (annotation element &rarr; value) into a list of key-value pairs
* containing the JavaParser representations of the same values.
*
* @param values mapping of element values from an {@code AnnotationMirror}
* @return a list of the key-value pairs in {@code values} converted to their JavaParser
* representations
*/
private static NodeList<MemberValuePair> convertAnnotationValues(
Map<? extends ExecutableElement, ? extends AnnotationValue> values) {
NodeList<MemberValuePair> convertedValues = new NodeList<>();
AnnotationValueConverterVisitor converter = new AnnotationValueConverterVisitor();
for (ExecutableElement valueName : values.keySet()) {
AnnotationValue value = values.get(valueName);
convertedValues.add(
new MemberValuePair(valueName.getSimpleName().toString(), value.accept(converter, null)));
}
return convertedValues;
}
/**
* Given a fully qualified name, creates a JavaParser {@code Name} structure representing the same
* name.
*
* @param name the fully qualified name to convert
* @return a JavaParser {@code Name} holding {@code name}
*/
private static Name createQualifiedName(String name) {
String[] components = name.split("\\.");
Name result = new Name(components[0]);
for (int i = 1; i < components.length; i++) {
result = new Name(result, components[i]);
}
return result;
}
/**
* A visitor that converts an annotation value from an {@code AnnotationMirror} to a JavaParser
* node that can appear in an {@code AnnotationExpr}.
*/
private static class AnnotationValueConverterVisitor
implements AnnotationValueVisitor<Expression, Void> {
@Override
public Expression visit(AnnotationValue value, Void p) {
// This is called only if the value couldn't be dispatched to any known type, which
// should never happen.
throw new BugInCF("Unknown annotation value type: " + value);
}
@Override
public Expression visitAnnotation(AnnotationMirror value, Void p) {
return AnnotationMirrorToAnnotationExprConversion.annotationMirrorToAnnotationExpr(value);
}
@Override
public Expression visitArray(List<? extends AnnotationValue> value, Void p) {
NodeList<Expression> valueExpressions = new NodeList<>();
for (AnnotationValue arrayValue : value) {
valueExpressions.add(arrayValue.accept(this, null));
}
return new ArrayInitializerExpr(valueExpressions);
}
@Override
public Expression visitBoolean(boolean value, Void p) {
return new BooleanLiteralExpr(value);
}
@Override
public Expression visitByte(byte value, Void p) {
// Annotation byte values are automatically cast to the correct type, so using an
// integer literal here works.
return toIntegerLiteralExpr(value);
}
@Override
public Expression visitChar(char value, Void p) {
return new CharLiteralExpr(value);
}
@Override
public Expression visitDouble(double value, Void p) {
return new DoubleLiteralExpr(value);
}
@Override
public Expression visitEnumConstant(VariableElement value, Void p) {
// The enclosing element of an enum constant is the enum type itself.
TypeElement enumElt = (TypeElement) value.getEnclosingElement();
String[] components = enumElt.getQualifiedName().toString().split("\\.");
Expression enumName = new NameExpr(components[0]);
for (int i = 1; i < components.length; i++) {
enumName = new FieldAccessExpr(enumName, components[i]);
}
return new FieldAccessExpr(enumName, value.getSimpleName().toString());
}
@Override
public Expression visitFloat(float value, Void p) {
return new DoubleLiteralExpr(value + "f");
}
@Override
public Expression visitInt(int value, Void p) {
return toIntegerLiteralExpr(value);
}
@Override
public Expression visitLong(long value, Void p) {
if (value < 0) {
return new UnaryExpr(new LongLiteralExpr(Long.toString(-value)), UnaryExpr.Operator.MINUS);
}
return new LongLiteralExpr(Long.toString(value));
}
@Override
public Expression visitShort(short value, Void p) {
// Annotation short values are automatically cast to the correct type, so using an
// integer literal here works.
return toIntegerLiteralExpr(value);
}
@Override
public Expression visitString(String value, Void p) {
return new StringLiteralExpr(value);
}
@Override
public Expression visitType(TypeMirror value, Void p) {
if (value.getKind() != TypeKind.DECLARED) {
throw new BugInCF("Unexpected type for class expression: " + value);
}
DeclaredType type = (DeclaredType) value;
ClassOrInterfaceType parsedType;
try {
parsedType = StaticJavaParser.parseClassOrInterfaceType(TypesUtils.getQualifiedName(type));
} catch (ParseProblemException e) {
throw new BugInCF("Invalid class or interface name: " + value, e);
}
return new ClassExpr(parsedType);
}
@Override
public @Nullable Expression visitUnknown(AnnotationValue value, Void p) {
return null;
}
/**
* Creates a JavaParser expression node representing a literal with the given value.
*
* <p>JavaParser represents a negative literal with a {@code UnaryExpr} containing a {@code
* IntegerLiteralExpr}, so this method won't necessarily return an {@code IntegerLiteralExpr}.
*
* @param value the value for the literal
* @return a JavaParser expression representing {@code value}
*/
private Expression toIntegerLiteralExpr(int value) {
if (value < 0) {
return new UnaryExpr(
new IntegerLiteralExpr(Integer.toString(-value)), UnaryExpr.Operator.MINUS);
}
return new IntegerLiteralExpr(Integer.toString(value));
}
}
}