package org.graalvm.libgraal.jni.processor;
import static org.graalvm.libgraal.jni.processor.AbstractFromLibGraalProcessor.topDeclaringType;
import static org.graalvm.libgraal.jni.processor.AbstractFromLibGraalProcessor.createSourceFile;
import java.io.PrintWriter;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import org.graalvm.compiler.processor.AbstractProcessor;
import org.graalvm.libgraal.jni.annotation.FromLibGraalEntryPointsResolver;
import org.graalvm.libgraal.jni.annotation.FromLibGraalId;
@SupportedAnnotationTypes("org.graalvm.libgraal.jni.annotation.FromLibGraalEntryPointsResolver")
public final class FromLibGraalEntryPointsResolverProcessor extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public boolean doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return true;
}
TypeElement resolverElement = getTypeElement(FromLibGraalEntryPointsResolver.class.getName());
for (Element element : roundEnv.getElementsAnnotatedWith(resolverElement)) {
try {
processElement(element, (DeclaredType) resolverElement.asType());
} catch (ProcessingException e) {
processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage(), e.element);
}
}
return true;
}
private void processElement(Element annotatedElement, DeclaredType annotationType) throws ProcessingException {
AnnotationMirror annotation = getAnnotation(annotatedElement, annotationType);
String entryPointsClassName = getAnnotationValue(annotation, "entryPointsClassName", String.class);
TypeMirror idType = getAnnotationValue(annotation, "value", TypeMirror.class);
TypeMirror fromLibGraalId = getTypeElement(FromLibGraalId.class.getName()).asType();
if (!processingEnv.getTypeUtils().isSubtype(idType, fromLibGraalId)) {
throw new ProcessingException(annotatedElement, "The %s must implement %s.", ((DeclaredType) idType).asElement().getSimpleName(),
((DeclaredType) fromLibGraalId).asElement().getSimpleName());
}
boolean method = annotatedElement.getKind() == ElementKind.METHOD;
boolean requiresJNIEnv = false;
if (method) {
if (!entryPointsClassName.isEmpty()) {
throw new ProcessingException(annotatedElement, "The FromLibGraalEntryPointsResolver on method cannot have entryPointsClassName.");
}
ExecutableElement methodElement = (ExecutableElement) annotatedElement;
if (!methodElement.getModifiers().contains(Modifier.STATIC)) {
throw new ProcessingException(annotatedElement, "Method %s must be static.", methodElement.getSimpleName());
}
if (methodElement.getModifiers().contains(Modifier.PRIVATE)) {
throw new ProcessingException(annotatedElement, "Method %s cannot be private.", methodElement.getSimpleName());
}
if (!verifyParameters(methodElement)) {
throw new ProcessingException(annotatedElement, "Method %s can have either a single JNIEnv parameter or no parameters.", methodElement.getSimpleName());
}
TypeMirror jclass = getTypeElement("org.graalvm.libgraal.jni.JNI.JClass").asType();
if (!processingEnv.getTypeUtils().isSameType(methodElement.getReturnType(), jclass)) {
throw new ProcessingException(annotatedElement, "Method %s must return JClass.", methodElement.getSimpleName());
}
requiresJNIEnv = !methodElement.getParameters().isEmpty();
} else {
if (entryPointsClassName.isEmpty()) {
throw new ProcessingException(annotatedElement, "The FromLibGraalEntryPointsResolver on class or package must have entryPointsClassName.");
}
}
TypeElement topEnclosingElement = topDeclaringType(annotatedElement);
String pkg = ((PackageElement) topEnclosingElement.getEnclosingElement()).getQualifiedName().toString();
TypeElement idElement = (TypeElement) ((DeclaredType) idType).asElement();
TypeElement idTopEncloingElement = topDeclaringType(idElement);
String simpleName = String.format("%sCalls", idTopEncloingElement.getSimpleName());
try (PrintWriter out = createSourceFile(pkg, simpleName, processingEnv.getFiler(), annotatedElement)) {
out.println("// CheckStyle: stop header check");
out.println("// CheckStyle: stop line length check");
out.println("// GENERATED CONTENT - DO NOT EDIT");
out.printf("// Source: %s.java%n", topEnclosingElement.getQualifiedName());
out.printf("// Generated-by: %s%n", getClass().getName());
out.println("package " + pkg + ";");
out.println("");
if (!pkg.equals("org.graalvm.libgraal.jni")) {
out.println("import org.graalvm.libgraal.jni.FromLibGraalCalls;");
}
out.println("import org.graalvm.libgraal.jni.JNI.JClass;");
out.println("import org.graalvm.libgraal.jni.JNI.JNIEnv;");
out.printf("import %s;%n", idElement.getQualifiedName());
out.println("");
out.printf("class %s extends FromLibGraalCalls<%s> {%n", simpleName, idElement.getSimpleName());
out.println("");
out.printf(" static final %s INSTANCE = new %s();%n", simpleName, simpleName);
out.println("");
out.printf(" private %s() {%n", simpleName);
out.printf(" super(%s.class);%n", idElement.getSimpleName());
out.println(" }");
out.println("");
out.println(" @Override");
out.println(" protected JClass resolvePeer(JNIEnv env) {");
if (method) {
out.printf(" return %s.%s(%s);%n",
annotatedElement.getEnclosingElement().getSimpleName(),
annotatedElement.getSimpleName(),
requiresJNIEnv ? "env" : "");
} else {
out.printf(" return getJNIClass(env, \"%s\");%n", entryPointsClassName);
}
out.println(" }");
out.println("}");
}
}
private boolean verifyParameters(ExecutableElement method) {
List<? extends VariableElement> params = method.getParameters();
if (params.isEmpty()) {
return true;
}
if (params.size() == 1) {
VariableElement param = params.get(0);
TypeMirror expectedType = processingEnv.getElementUtils().getTypeElement("org.graalvm.libgraal.jni.JNI.JNIEnv").asType();
TypeMirror actualType = param.asType();
return processingEnv.getTypeUtils().isSameType(expectedType, actualType);
}
return false;
}
@SuppressWarnings("serial")
private static final class ProcessingException extends Exception {
final Element element;
ProcessingException(Element element, String message, Object... params) {
super(String.format(message, params));
this.element = element;
}
}
}