package org.checkerframework.common.reflection;
import static org.checkerframework.common.reflection.ClassValAnnotatedTypeFactory.getClassNamesFromAnnotation;
import com.sun.source.tree.Tree;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeValidator;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.reflection.qual.ClassBound;
import org.checkerframework.common.reflection.qual.ClassVal;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
public class ClassValVisitor extends BaseTypeVisitor<ClassValAnnotatedTypeFactory> {
public ClassValVisitor(BaseTypeChecker checker) {
super(checker);
}
@Override
protected ClassValAnnotatedTypeFactory createTypeFactory() {
return new ClassValAnnotatedTypeFactory(checker);
}
@Override
protected BaseTypeValidator createTypeValidator() {
return new ClassNameValidator(checker, this, atypeFactory);
}
}
class ClassNameValidator extends BaseTypeValidator {
public ClassNameValidator(
BaseTypeChecker checker,
BaseTypeVisitor<?> visitor,
AnnotatedTypeFactory atypeFactory) {
super(checker, visitor, atypeFactory);
}
/**
* Reports an "illegal.classname" error if the type contains a classVal annotation with
* classNames that cannot possibly be valid class annotations.
*/
@Override
public boolean isValid(AnnotatedTypeMirror type, Tree tree) {
AnnotationMirror classVal = type.getAnnotation(ClassVal.class);
classVal = classVal == null ? type.getAnnotation(ClassBound.class) : classVal;
if (classVal != null) {
List<String> classNames = getClassNamesFromAnnotation(classVal);
for (String className : classNames) {
if (!isLegalClassName(className)) {
checker.report(Result.failure("illegal.classname", className, type), tree);
}
}
}
return super.isValid(type, tree);
}
/**
* A string is a legal binary name if it has the following form: ((Java identifier)\.)*(Java
* identifier)([])* https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.1
*
* @param className string to check
* @return true if className is a legal class name
*/
private boolean isLegalClassName(String className) {
int lastBracket = className.lastIndexOf("]");
if (lastBracket != -1 && lastBracket != className.length() - 1) {
return false;
}
className = className.replaceAll("\\[\\]", "");
String[] identifiers = className.split("(\\.)");
for (String identifier : identifiers) {
if (!isJavaIdentifier(identifier)) {
return false;
}
}
return true;
}
/**
* Whether the given string is a Java Identifier. (This method returns true if the Identifier is
* a keyword, boolean literal, null literal.
*/
private boolean isJavaIdentifier(String identifier) {
char[] identifierChars = identifier.toCharArray();
if (!(identifierChars.length > 0
&& (Character.isJavaIdentifierStart(identifierChars[0])))) {
return false;
}
for (int i = 1; i < identifierChars.length; i++) {
if (!Character.isJavaIdentifierPart(identifierChars[i])) {
return false;
}
}
return true;
}
}