/*
* 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.io.Writer;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.MessageResolution;
import com.oracle.truffle.dsl.processor.java.ElementUtils;
public final class ForeignAccessFactoryGenerator {
private final String receiverTypeClass;
private final String packageName;
private final String simpleClassName;
private final ProcessingEnvironment processingEnv;
protected final TypeElement element;
private final Map<Object, String> messageHandlers;
private String languageCheckFactoryInvokation;
public ForeignAccessFactoryGenerator(ProcessingEnvironment processingEnv, MessageResolution messageResolutionAnnotation, TypeElement element) {
this.processingEnv = processingEnv;
this.element = element;
this.packageName = ElementUtils.getPackageName(element);
this.simpleClassName = ElementUtils.getSimpleName(element) + "Foreign";
this.receiverTypeClass = Utils.getReceiverTypeFullClassName(messageResolutionAnnotation);
this.messageHandlers = new HashMap<>();
this.languageCheckFactoryInvokation = null;
}
public void addMessageHandler(Object message, String factoryMethodInvocation) {
messageHandlers.put(message, factoryMethodInvocation);
}
public void addLanguageCheckHandler(String invocation) {
this.languageCheckFactoryInvokation = invocation;
}
public String getFullClassName() {
return packageName + "." + simpleClassName;
}
public void generate() throws IOException {
JavaFileObject factoryFile = processingEnv.getFiler().createSourceFile(packageName + "." + simpleClassName, element);
Writer w = factoryFile.openWriter();
w.append("package ").append(packageName).append(";\n");
appendImports(w);
Utils.appendFactoryGeneratedFor(w, "", receiverTypeClass, ElementUtils.getQualifiedName(element));
Utils.appendVisibilityModifier(w, element);
w.append("final class ").append(simpleClassName);
w.append(" implements Factory26, Factory {\n");
appendSingletonAndGetter(w);
appendPrivateConstructor(w);
appendFactoryCanHandle(w);
appendFactoryAccessIsNull(w);
appendFactoryAccessIsExecutable(w);
appendFactoryAccessIsBoxed(w);
appendFactoryAccessHasSize(w);
appendFactoryAccessGetSize(w);
appendFactoryAccessUnbox(w);
appendFactoryAccessRead(w);
appendFactoryAccessWrite(w);
appendFactoryAccessExecute(w);
appendFactoryAccessInvoke(w);
appendFactoryAccessNew(w);
appendFactoryAccessKeyInfo(w);
appendFactoryAccessKeys(w);
appendFactoryAccessIsPointer(w);
appendFactoryAccessAsPointer(w);
appendFactoryAccessToNative(w);
appendFactoryAccessMessage(w);
w.append("}\n");
w.close();
}
private boolean hasLanguageCheckNode() {
return languageCheckFactoryInvokation != null;
}
private void appendImports(Writer w) throws IOException {
w.append("import com.oracle.truffle.api.interop.ForeignAccess.Factory26;").append("\n");
w.append("import com.oracle.truffle.api.interop.ForeignAccess.Factory;").append("\n");
w.append("import com.oracle.truffle.api.interop.Message;").append("\n");
w.append("import com.oracle.truffle.api.interop.ForeignAccess;").append("\n");
w.append("import com.oracle.truffle.api.interop.TruffleObject;").append("\n");
w.append("import com.oracle.truffle.api.CallTarget;").append("\n");
if (hasLanguageCheckNode()) {
w.append("import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;").append("\n");
}
w.append("import com.oracle.truffle.api.Truffle;").append("\n");
if (!(messageHandlers.containsKey(Message.IS_BOXED) &&
messageHandlers.containsKey(Message.IS_NULL) &&
messageHandlers.containsKey(Message.IS_EXECUTABLE) &&
messageHandlers.containsKey(Message.KEY_INFO) &&
messageHandlers.containsKey(Message.HAS_SIZE) &&
messageHandlers.containsKey(Message.IS_POINTER))) {
w.append("import com.oracle.truffle.api.nodes.RootNode;").append("\n");
}
}
private void appendSingletonAndGetter(Writer w) throws IOException {
String allocation;
if (hasLanguageCheckNode()) {
allocation = "ForeignAccess.create(new " + simpleClassName + "(), " + languageCheckFactoryInvokation + ");";
} else {
allocation = "ForeignAccess.create(new " + simpleClassName + "(), null);";
}
w.append(" public static final ForeignAccess ACCESS = ").append(allocation).append("\n");
w.append(" @Deprecated");
w.append(" public static ForeignAccess createAccess() { return ").append(allocation).append(" }\n");
w.append("\n");
}
private void appendPrivateConstructor(Writer w) throws IOException {
w.append(" private ").append(simpleClassName).append("() { }").append("\n");
w.append("\n");
}
private void appendFactoryCanHandle(Writer w) throws IOException {
w.append(" @Override").append("\n");
if (hasLanguageCheckNode()) {
w.append(" @TruffleBoundary").append("\n");
}
w.append(" public boolean canHandle(TruffleObject obj) {").append("\n");
if (hasLanguageCheckNode()) {
w.append(" return (boolean) Truffle.getRuntime().createCallTarget(").append(languageCheckFactoryInvokation).append(").call(obj);\n");
} else {
w.append(" return ").append(receiverTypeClass).append(".isInstance(obj);").append("\n");
}
w.append(" }").append("\n");
w.append("\n");
}
private void appendFactoryAccessIsNull(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessIsNull() {").append("\n");
appendOptionalDefaultHandlerBody(w, Message.IS_NULL);
w.append(" }").append("\n");
}
private void appendFactoryAccessIsExecutable(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessIsExecutable() {").append("\n");
appendOptionalDefaultHandlerBody(w, Message.IS_EXECUTABLE);
w.append(" }").append("\n");
}
private void appendFactoryAccessIsBoxed(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessIsBoxed() {").append("\n");
appendOptionalDefaultHandlerBody(w, Message.IS_BOXED);
w.append(" }").append("\n");
}
private void appendFactoryAccessHasSize(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessHasSize() {").append("\n");
appendOptionalDefaultHandlerBody(w, Message.HAS_SIZE);
w.append(" }").append("\n");
}
private void appendOptionalDefaultHandlerBody(Writer w, Message message) throws IOException {
appendOptionalDefaultHandlerBody(w, message, "false");
}
private void appendOptionalDefaultHandlerBody(Writer w, Message message, String defaultValue) throws IOException {
if (!messageHandlers.containsKey(message)) {
w.append(" return Truffle.getRuntime().createCallTarget(RootNode.createConstantNode(" + defaultValue + "));").append("\n");
} else {
w.append(" return Truffle.getRuntime().createCallTarget(").append(messageHandlers.get(message)).append(");").append("\n");
}
}
private void appendFactoryAccessGetSize(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessGetSize() {").append("\n");
appendOptionalHandlerBody(w, Message.GET_SIZE);
w.append(" }").append("\n");
}
private void appendFactoryAccessKeyInfo(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessKeyInfo() {").append("\n");
appendOptionalHandlerBody(w, Message.KEY_INFO);
w.append(" }").append("\n");
}
private void appendFactoryAccessKeys(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessKeys() {").append("\n");
appendOptionalHandlerBody(w, Message.KEYS);
w.append(" }").append("\n");
}
private void appendFactoryAccessIsPointer(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessIsPointer() {").append("\n");
appendOptionalDefaultHandlerBody(w, Message.IS_POINTER);
w.append(" }").append("\n");
}
private void appendFactoryAccessAsPointer(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessAsPointer() {").append("\n");
appendOptionalHandlerBody(w, Message.AS_POINTER);
w.append(" }").append("\n");
}
private void appendFactoryAccessToNative(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessToNative() {").append("\n");
appendOptionalHandlerBody(w, Message.TO_NATIVE);
w.append(" }").append("\n");
}
private void appendFactoryAccessUnbox(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessUnbox() {").append("\n");
appendOptionalHandlerBody(w, Message.UNBOX);
w.append(" }").append("\n");
}
private void appendFactoryAccessRead(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessRead() {").append("\n");
appendOptionalHandlerBody(w, Message.READ);
w.append(" }").append("\n");
}
private void appendFactoryAccessWrite(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessWrite() {").append("\n");
appendOptionalHandlerBody(w, Message.WRITE);
w.append(" }").append("\n");
}
private void appendFactoryAccessExecute(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessExecute(int argumentsLength) {").append("\n");
appendOptionalHandlerBody(w, Message.createExecute(0));
w.append(" }").append("\n");
}
private void appendFactoryAccessInvoke(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessInvoke(int argumentsLength) {").append("\n");
appendOptionalHandlerBody(w, Message.createInvoke(0));
w.append(" }").append("\n");
}
private void appendFactoryAccessNew(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessNew(int argumentsLength) {").append("\n");
appendOptionalHandlerBody(w, Message.createNew(0));
w.append(" }").append("\n");
}
private void appendOptionalHandlerBody(Writer w, Message message) throws IOException {
if (!messageHandlers.containsKey(message)) {
w.append(" return null;\n");
} else {
w.append(" return com.oracle.truffle.api.Truffle.getRuntime().createCallTarget(").append(messageHandlers.get(message)).append(");").append("\n");
}
}
private void appendFactoryAccessMessage(Writer w) throws IOException {
w.append(" @Override").append("\n");
w.append(" public CallTarget accessMessage(Message unknown) {").append("\n");
for (Object m : messageHandlers.keySet()) {
if (!InteropDSLProcessor.KNOWN_MESSAGES.contains(m)) {
String msg = m instanceof Message ? Message.toString((Message) m) : (String) m;
w.append(" if (unknown != null && unknown.getClass().getName().equals(\"").append(msg).append("\")) {").append("\n");
w.append(" return Truffle.getRuntime().createCallTarget(").append(messageHandlers.get(m)).append(");").append("\n");
w.append(" }").append("\n");
}
}
w.append(" return null;\n");
w.append(" }").append("\n");
}
@Override
public String toString() {
return "FactoryGenerator: " + simpleClassName;
}
}