package com.oracle.truffle.dsl.processor;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Predicate;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
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.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import com.oracle.truffle.dsl.processor.java.ElementUtils;
import com.oracle.truffle.dsl.processor.java.model.CodeExecutableElement;
import com.oracle.truffle.dsl.processor.java.model.CodeTreeBuilder;
@SupportedAnnotationTypes(TruffleTypes.TruffleInstrument_Registration_Name)
public final class InstrumentRegistrationProcessor extends AbstractRegistrationProcessor {
private static final int NUMBER_OF_PROPERTIES_PER_ENTRY = 4;
@Override
boolean validateRegistration(Element annotatedElement, AnnotationMirror registrationMirror) {
if (!annotatedElement.getModifiers().contains(Modifier.PUBLIC)) {
emitError("Registered instrument class must be public", annotatedElement);
return false;
}
if (annotatedElement.getEnclosingElement().getKind() != ElementKind.PACKAGE && !annotatedElement.getModifiers().contains(Modifier.STATIC)) {
emitError("Registered instrument inner-class must be static", annotatedElement);
return false;
}
TruffleTypes types = ProcessorContext.getInstance().getTypes();
TypeMirror truffleInstrument = types.TruffleInstrument;
TypeMirror truffleInstrumentProvider = types.TruffleInstrument_Provider;
boolean processingTruffleInstrument;
if (processingEnv.getTypeUtils().isAssignable(annotatedElement.asType(), truffleInstrument)) {
processingTruffleInstrument = true;
} else if (processingEnv.getTypeUtils().isAssignable(annotatedElement.asType(), truffleInstrumentProvider)) {
processingTruffleInstrument = false;
} else {
emitError("Registered instrument class must subclass TruffleInstrument", annotatedElement);
return false;
}
assertNoErrorExpected(annotatedElement);
return processingTruffleInstrument;
}
@Override
DeclaredType getProviderClass() {
TruffleTypes types = ProcessorContext.getInstance().getTypes();
return types.TruffleInstrument_Provider;
}
@Override
Iterable<AnnotationMirror> getProviderAnnotations(TypeElement annotatedElement) {
TruffleTypes types = ProcessorContext.getInstance().getTypes();
DeclaredType registrationType = types.TruffleInstrument_Registration;
AnnotationMirror registration = copyAnnotations(ElementUtils.findAnnotationMirror(annotatedElement.getAnnotationMirrors(), registrationType),
new Predicate<ExecutableElement>() {
@Override
public boolean test(ExecutableElement t) {
return !"services".contentEquals(t.getSimpleName());
}
});
return Collections.singleton(registration);
}
@Override
void implementMethod(TypeElement annotatedElement, CodeExecutableElement methodToImplement) {
CodeTreeBuilder builder = methodToImplement.createBuilder();
switch (methodToImplement.getSimpleName().toString()) {
case "create":
builder.startReturn().startNew(annotatedElement.asType()).end().end();
break;
case "getInstrumentClassName": {
ProcessorContext context = ProcessorContext.getInstance();
Elements elements = context.getEnvironment().getElementUtils();
builder.startReturn().doubleQuote(elements.getBinaryName(annotatedElement).toString()).end();
break;
}
case "getServicesClassNames": {
ProcessorContext context = ProcessorContext.getInstance();
AnnotationMirror registration = ElementUtils.findAnnotationMirror(annotatedElement.getAnnotationMirrors(),
context.getTypes().TruffleInstrument_Registration);
List<TypeMirror> services = ElementUtils.getAnnotationValueList(TypeMirror.class, registration, "services");
if (services.isEmpty()) {
builder.startReturn().startStaticCall(context.getType(Collections.class), "emptySet").end().end();
} else {
builder.startReturn();
builder.startStaticCall(context.getType(Arrays.class), "asList");
for (TypeMirror service : services) {
Elements elements = context.getEnvironment().getElementUtils();
Types types = context.getEnvironment().getTypeUtils();
builder.startGroup().doubleQuote(elements.getBinaryName((TypeElement) ((DeclaredType) types.erasure(service)).asElement()).toString()).end();
}
builder.end(2);
}
break;
}
default:
throw new IllegalStateException("Unsupported method: " + methodToImplement.getSimpleName());
}
}
@Override
String getRegistrationFileName() {
return "META-INF/truffle/instrument";
}
@Override
void storeRegistrations(Properties into, Iterable<? extends TypeElement> instruments) {
TruffleTypes types = ProcessorContext.getInstance().getTypes();
int numInstruments = loadIfFileAlreadyExists(getRegistrationFileName(), into);
for (TypeElement l : instruments) {
AnnotationMirror annotation = ElementUtils.findAnnotationMirror(l, types.TruffleInstrument_Registration);
if (annotation == null) {
continue;
}
String id = ElementUtils.getAnnotationValue(String.class, annotation, "id");
int instNum = findInstrument(id, into);
if (instNum == 0) {
numInstruments += 1;
instNum = numInstruments;
}
String prefix = "instrument" + instNum + ".";
String className = processingEnv.getElementUtils().getBinaryName(l).toString();
into.setProperty(prefix + "id", id);
into.setProperty(prefix + "name", ElementUtils.getAnnotationValue(String.class, annotation, "name"));
into.setProperty(prefix + "version", ElementUtils.getAnnotationValue(String.class, annotation, "version"));
into.setProperty(prefix + "className", className);
into.setProperty(prefix + "internal", Boolean.toString(ElementUtils.getAnnotationValue(Boolean.class, annotation, "internal")));
int serviceCounter = 0;
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) {
final Name attrName = entry.getKey().getSimpleName();
if (attrName.contentEquals("services")) {
AnnotationValue attrValue = entry.getValue();
List<?> classes = (List<?>) attrValue.getValue();
for (Object clazz : classes) {
AnnotationValue clazzValue = (AnnotationValue) clazz;
into.setProperty(prefix + "service" + serviceCounter++, clazzValue.getValue().toString());
}
}
}
}
}
private static int findInstrument(String id, Properties p) {
int cnt = 1;
String val;
while ((val = p.getProperty("instrument" + cnt + ".id")) != null) {
if (id.equals(val)) {
return cnt;
}
cnt += 1;
}
return 0;
}
private int loadIfFileAlreadyExists(String filename, Properties p) {
try {
FileObject file = processingEnv.getFiler().getResource(
StandardLocation.CLASS_OUTPUT, "", filename);
p.load(file.openInputStream());
return p.keySet().size() / NUMBER_OF_PROPERTIES_PER_ENTRY;
} catch (IOException e) {
return 0;
}
}
}