/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.dsl.processor.interop;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.FilerException;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
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.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.tools.Diagnostic.Kind;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.CanResolve;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.MessageResolution;
import com.oracle.truffle.api.interop.Resolve;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.dsl.processor.ExpectError;
import com.oracle.truffle.dsl.processor.java.ElementUtils;
/**
* THIS IS NOT PUBLIC API.
*/
public final class InteropDSLProcessor extends AbstractProcessor {
static final List<Message> KNOWN_MESSAGES = Arrays.asList(new Message[]{Message.READ, Message.WRITE, Message.IS_NULL, Message.IS_EXECUTABLE,
Message.IS_BOXED, Message.HAS_SIZE, Message.GET_SIZE, Message.KEY_INFO, Message.KEYS, Message.UNBOX, Message.IS_POINTER,
Message.AS_POINTER, Message.TO_NATIVE,
Message.createExecute(0), Message.createInvoke(0), Message.createNew(0)});
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new HashSet<>();
annotations.add("com.oracle.truffle.api.interop.MessageResolution");
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}
process0(roundEnv);
return true;
}
private void process0(RoundEnvironment roundEnv) {
for (Element e : roundEnv.getElementsAnnotatedWith(MessageResolution.class)) {
try {
processElement(e);
} catch (Throwable ex) {
ex.printStackTrace();
String message = "Uncaught error in " + this.getClass();
processingEnv.getMessager().printMessage(Kind.ERROR, message + ": " + ElementUtils.printException(ex), e);
}
}
}
private void processElement(Element e) throws IOException {
if (e.getKind() != ElementKind.CLASS) {
return;
}
MessageResolution messageImplementations = e.getAnnotation(MessageResolution.class);
if (messageImplementations == null) {
return;
}
// Check the receiver
final String receiverTypeFullClassName = Utils.getReceiverTypeFullClassName(messageImplementations);
if (isReceiverNonStaticInner(messageImplementations)) {
emitError(receiverTypeFullClassName + " cannot be used as a receiver as it is not a static inner class.", e);
return;
}
if (e.getModifiers().contains(Modifier.PRIVATE) || e.getModifiers().contains(Modifier.PROTECTED)) {
emitError("Class must be public or package protected", e);
return;
}
// check if there is a @LanguageCheck class
Element curr = e;
List<TypeElement> receiverChecks = new ArrayList<>();
for (Element innerClass : curr.getEnclosedElements()) {
if (innerClass.getKind() != ElementKind.CLASS) {
continue;
}
if (innerClass.getAnnotation(CanResolve.class) != null) {
receiverChecks.add((TypeElement) innerClass);
}
}
if (receiverChecks.size() == 0 && isInstanceMissing(receiverTypeFullClassName)) {
emitError("Missing isInstance method in class " + receiverTypeFullClassName, e);
return;
}
if (receiverChecks.size() == 0 && isInstanceHasWrongSignature(receiverTypeFullClassName)) {
emitError("Method isInstance in class " + receiverTypeFullClassName + " has an invalid signature: expected signature (object: TruffleObject).", e);
return;
}
if (receiverChecks.size() > 1) {
emitError("Only one @LanguageCheck element allowed", e);
return;
}
// Collect all inner classes with an @Resolve annotation
curr = e;
List<TypeElement> elements = new ArrayList<>();
for (Element innerClass : curr.getEnclosedElements()) {
if (innerClass.getKind() != ElementKind.CLASS) {
continue;
}
if (innerClass.getAnnotation(Resolve.class) != null) {
elements.add((TypeElement) innerClass);
}
}
ForeignAccessFactoryGenerator factoryGenerator = new ForeignAccessFactoryGenerator(processingEnv, messageImplementations, (TypeElement) e);
// Process inner classes with an @Resolve annotation
boolean generationSuccessfull = true;
for (TypeElement elem : elements) {
generationSuccessfull &= processResolveClass(elem.getAnnotation(Resolve.class), messageImplementations, elem, factoryGenerator);
}
if (!generationSuccessfull) {
return;
}
if (!receiverChecks.isEmpty()) {
generationSuccessfull &= processLanguageCheck(messageImplementations, receiverChecks.get(0), factoryGenerator);
}
if (!generationSuccessfull) {
return;
}
try {
factoryGenerator.generate();
} catch (FilerException ex) {
emitError("Foreign factory class with same name already exists", e);
return;
}
}
private boolean processLanguageCheck(MessageResolution messageResolutionAnnotation, TypeElement element, ForeignAccessFactoryGenerator factoryGenerator)
throws IOException {
LanguageCheckGenerator generator = new LanguageCheckGenerator(processingEnv, messageResolutionAnnotation, element, factoryGenerator);
if (!ElementUtils.typeEquals(element.getSuperclass(), Utils.getTypeMirror(processingEnv, com.oracle.truffle.api.nodes.Node.class))) {
emitError(ElementUtils.getQualifiedName(element) + " must extend com.oracle.truffle.api.nodes.Node.", element);
return false;
}
if (!element.getModifiers().contains(Modifier.ABSTRACT)) {
emitError("Class must be abstract", element);
return false;
}
if (!element.getModifiers().contains(Modifier.STATIC)) {
emitError("Class must be static", element);
return false;
}
if (element.getModifiers().contains(Modifier.PRIVATE) || element.getModifiers().contains(Modifier.PROTECTED)) {
emitError("Class must be public or package protected", element);
return false;
}
List<ExecutableElement> methods = generator.getTestMethods();
if (methods.isEmpty() || methods.size() > 1) {
emitError("There needs to be exactly one test method.", element);
return false;
}
ExecutableElement m = methods.get(0);
String errorMessage = generator.checkSignature(m);
if (errorMessage != null) {
emitError(errorMessage, m);
return false;
}
try {
generator.generate();
} catch (FilerException ex) {
emitError("Language check class with same name already exists", element);
return false;
}
factoryGenerator.addLanguageCheckHandler(generator.getRootNodeFactoryInvokation());
return true;
}
private boolean processResolveClass(Resolve resolveAnnotation, MessageResolution messageResolutionAnnotation, TypeElement element, ForeignAccessFactoryGenerator factoryGenerator)
throws IOException {
MessageGenerator currentGenerator = MessageGenerator.getGenerator(processingEnv, resolveAnnotation, messageResolutionAnnotation, element, factoryGenerator);
if (currentGenerator == null) {
emitError("Unknown message type: " + resolveAnnotation.message(), element);
return false;
}
if (!ElementUtils.typeEquals(element.getSuperclass(), Utils.getTypeMirror(processingEnv, com.oracle.truffle.api.nodes.Node.class))) {
emitError(ElementUtils.getQualifiedName(element) + " must extend com.oracle.truffle.api.nodes.Node.", element);
return false;
}
if (!element.getModifiers().contains(Modifier.ABSTRACT)) {
emitError("Class must be abstract", element);
return false;
}
if (!element.getModifiers().contains(Modifier.STATIC)) {
emitError("Class must be static", element);
return false;
}
if (element.getModifiers().contains(Modifier.PRIVATE) || element.getModifiers().contains(Modifier.PROTECTED)) {
emitError("Class must be public or package protected", element);
return false;
}
List<ExecutableElement> methods = currentGenerator.getAccessMethods();
if (methods.isEmpty()) {
emitError("There needs to be at least one access method.", element);
return false;
}
List<? extends VariableElement> params = methods.get(0).getParameters();
int argumentSize = params.size();
if (params.size() > 0 && ElementUtils.typeEquals(params.get(0).asType(), Utils.getTypeMirror(processingEnv, VirtualFrame.class))) {
argumentSize -= 1;
}
for (ExecutableElement m : methods) {
params = m.getParameters();
int paramsSize = params.size();
if (params.size() > 0 && ElementUtils.typeEquals(params.get(0).asType(), Utils.getTypeMirror(processingEnv, VirtualFrame.class))) {
paramsSize -= 1;
}
if (argumentSize != paramsSize) {
emitError("Inconsistent argument length.", element);
return false;
}
}
for (ExecutableElement m : methods) {
String errorMessage = currentGenerator.checkSignature(m);
if (errorMessage != null) {
emitError(errorMessage, m);
return false;
}
}
try {
currentGenerator.generate();
} catch (FilerException ex) {
emitError("Message resolution class with same name already exists", element);
return false;
}
Object currentMessage = Utils.getMessage(processingEnv, resolveAnnotation.message());
factoryGenerator.addMessageHandler(currentMessage, currentGenerator.getRootNodeFactoryInvokation());
return true;
}
private static boolean isReceiverNonStaticInner(MessageResolution message) {
try {
message.receiverType();
throw new AssertionError();
} catch (MirroredTypeException mte) {
// This exception is always thrown: use the mirrors to inspect the class
DeclaredType type = (DeclaredType) mte.getTypeMirror();
TypeElement element = (TypeElement) type.asElement();
if (element.getNestingKind() == NestingKind.MEMBER || element.getNestingKind() == NestingKind.LOCAL) {
for (Modifier modifier : element.getModifiers()) {
if (modifier.compareTo(Modifier.STATIC) == 0) {
return false;
}
}
return true;
} else {
return false;
}
}
}
private boolean isInstanceMissing(String receiverTypeFullClassName) {
for (Element elem : this.processingEnv.getElementUtils().getTypeElement(receiverTypeFullClassName).getEnclosedElements()) {
if (elem.getKind().equals(ElementKind.METHOD)) {
ExecutableElement method = (ExecutableElement) elem;
if (method.getSimpleName().toString().equals("isInstance")) {
return false;
}
}
}
return true;
}
private boolean isInstanceHasWrongSignature(String receiverTypeFullClassName) {
for (Element elem : this.processingEnv.getElementUtils().getTypeElement(receiverTypeFullClassName).getEnclosedElements()) {
if (elem.getKind().equals(ElementKind.METHOD)) {
ExecutableElement method = (ExecutableElement) elem;
if (method.getSimpleName().toString().equals("isInstance") && method.getParameters().size() == 1 &&
ElementUtils.typeEquals(method.getParameters().get(0).asType(), Utils.getTypeMirror(processingEnv, TruffleObject.class))) {
return false;
}
}
}
return true;
}
private void emitError(String msg, Element e) {
if (ExpectError.isExpectedError(processingEnv, e, msg)) {
return;
}
processingEnv.getMessager().printMessage(Kind.ERROR, msg, e);
}
}