/* * Copyright (c) 2015, 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.api.test.vm; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.Reader; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.concurrent.Executors; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage.Env; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.interop.ForeignAccess; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.vm.PolyglotEngine; public class ImplicitExplicitExportTest { private static Thread mainThread; private PolyglotEngine vm; @Before public void initializeVM() { mainThread = Thread.currentThread(); vm = PolyglotEngine.newBuilder().executor(Executors.newSingleThreadExecutor()).build(); assertTrue("Found " + L1 + " language", vm.getLanguages().containsKey(L1)); assertTrue("Found " + L2 + " language", vm.getLanguages().containsKey(L2)); assertTrue("Found " + L3 + " language", vm.getLanguages().containsKey(L3)); } @After public void cleanThread() { mainThread = null; if (vm != null) { vm.dispose(); } } @Test public void explicitExportFound() { // @formatter:off vm.eval(Source.newBuilder("explicit.ahoj=42").name("Fourty two").mimeType(L1).build()); Object ret = vm.eval(Source.newBuilder("return=ahoj").name("Return").mimeType(L3).build() ).get(); // @formatter:on assertEquals("42", ret); } @Test public void implicitExportFound() { // @formatter:off vm.eval(Source.newBuilder("implicit.ahoj=42").name("Fourty two").mimeType(L1).build() ); Object ret = vm.eval(Source.newBuilder("return=ahoj").name("Return").mimeType(L3).build() ).get(); // @formatter:on assertEquals("42", ret); } @Test public void implicitExportFoundDirect() throws Exception { // @formatter:off vm.eval( Source.newBuilder("implicit.ahoj=42"). name("Fourty two"). mimeType(L1). build() ); Object ret = vm.findGlobalSymbol("ahoj").get(); // @formatter:on assertEquals("42", ret); } @Test public void explicitExportPreferred2() throws Exception { // @formatter:off vm.eval(Source.newBuilder("implicit.ahoj=42").name("Fourty two").mimeType(L1).build() ); vm.eval(Source.newBuilder("explicit.ahoj=43").name("Fourty three").mimeType(L2).build() ); Object ret = vm.eval(Source.newBuilder("return=ahoj").name("Return").mimeType(L3).build() ).get(); // @formatter:on assertEquals("Explicit import from L2 is used", "43", ret); assertEquals("Global symbol is also 43", "43", vm.findGlobalSymbol("ahoj").get()); } @Test public void explicitExportPreferredInIterator() throws Exception { vm.eval(Source.newBuilder("implicit.ahoj=42").name("Fourty two").mimeType(L1).build()); vm.eval(Source.newBuilder("explicit.ahoj=43").name("Fourty three").mimeType(L2).build()); Iterable<PolyglotEngine.Value> iterable = vm.findGlobalSymbols("ahoj"); assertExplicitOverImplicit(iterable); assertExplicitOverImplicit(iterable); assertExplicitOverImplicit(iterable); } @Test public void explicitExportPreferredInEnvIterator() throws Exception { vm.eval(Source.newBuilder("implicit.ahoj=42").name("Fourty two").mimeType(L1).build()); vm.eval(Source.newBuilder("explicit.ahoj=43").name("Fourty three").mimeType(L2).build()); Object ret = vm.eval(Source.newBuilder("returnall=ahoj").name("Return").mimeType(L3).build()).get(); assertEquals("Explicit import from L2 is used first, then L1 value", "4342", ret); } private static void assertExplicitOverImplicit(Iterable<PolyglotEngine.Value> iterable) { Iterator<PolyglotEngine.Value> it = iterable.iterator(); assertTrue("Has more", it.hasNext()); assertEquals("Explicit first", "43", it.next().get()); assertTrue("Has one more", it.hasNext()); assertEquals("Implicit next first", "42", it.next().get()); assertFalse("No more elements", it.hasNext()); } @Test public void explicitExportPreferredDirect() throws Exception { // @formatter:off vm.eval(Source.newBuilder("implicit.ahoj=42").name("Fourty two").mimeType(L1).build()); vm.eval(Source.newBuilder("explicit.ahoj=43").name("Fourty three").mimeType(L2).build()); Object ret = vm.findGlobalSymbol("ahoj").get(); // @formatter:on assertEquals("Explicit import from L2 is used", "43", ret); assertEquals("Global symbol is also 43", "43", vm.findGlobalSymbol("ahoj").get()); } @Test public void explicitExportPreferred1() throws Exception { // @formatter:off vm.eval(Source.newBuilder("explicit.ahoj=43").name("Fourty three").mimeType(L1).build() ); vm.eval(Source.newBuilder("implicit.ahoj=42").name("Fourty two").mimeType(L2).build() ); Object ret = vm.eval(Source.newBuilder("return=ahoj").name("Return").mimeType(L3).build() ).get(); // @formatter:on assertEquals("Explicit import from L2 is used", "43", ret); assertEquals("Global symbol is also 43", "43", vm.findGlobalSymbol("ahoj").execute().get()); } static final class Ctx implements TruffleObject { static final Set<Ctx> disposed = new HashSet<>(); final Map<String, String> explicit = new HashMap<>(); final Map<String, String> implicit = new HashMap<>(); final Env env; Ctx(Env env) { this.env = env; } void dispose() { assertFalse("No prior dispose", disposed.contains(this)); disposed.add(this); } @Override public ForeignAccess getForeignAccess() { return null; } } private abstract static class AbstractExportImportLanguage extends TruffleLanguage<Ctx> { @Override protected Ctx createContext(Env env) { if (mainThread != null) { assertNotEquals("Should run asynchronously", Thread.currentThread(), mainThread); } return new Ctx(env); } @Override protected void disposeContext(Ctx context) { context.dispose(); } @Override protected CallTarget parse(ParsingRequest request) throws Exception { Source code = request.getSource(); if (code.getCode().startsWith("parse=")) { throw new IOException(code.getCode().substring(6)); } return Truffle.getRuntime().createCallTarget(new ValueRootNode(code, this)); } @Override protected Object findExportedSymbol(Ctx context, String globalName, boolean onlyExplicit) { assertNotEquals("Should run asynchronously", Thread.currentThread(), mainThread); if (onlyExplicit && context.explicit.containsKey(globalName)) { return context.explicit.get(globalName); } if (!onlyExplicit && context.implicit.containsKey(globalName)) { return context.implicit.get(globalName); } return null; } @Override protected Object getLanguageGlobal(Ctx context) { return context; } @Override protected boolean isObjectOfLanguage(Object object) { return false; } @TruffleBoundary private Object importExport(Source code) { assertNotEquals("Should run asynchronously", Thread.currentThread(), mainThread); Ctx ctx = getContextReference().get(); Properties p = new Properties(); try (Reader r = code.getReader()) { p.load(r); } catch (IOException ex) { throw new IllegalStateException(ex); } Enumeration<Object> en = p.keys(); while (en.hasMoreElements()) { Object n = en.nextElement(); if (n instanceof String) { String k = (String) n; if (k.startsWith("explicit.")) { ctx.explicit.put(k.substring(9), p.getProperty(k)); } if (k.startsWith("implicit.")) { ctx.implicit.put(k.substring(9), p.getProperty(k)); } if (k.equals("return")) { return ctx.env.importSymbol(p.getProperty(k)); } if (k.equals("returnall")) { StringBuilder sb = new StringBuilder(); for (Object obj : ctx.env.importSymbols(p.getProperty(k))) { sb.append(obj); } return sb.toString(); } if (k.equals("throwInteropException")) { throw UnsupportedTypeException.raise(new Object[0]); } } } return null; } } private static final class ValueRootNode extends RootNode { private final Source code; private final AbstractExportImportLanguage language; private ValueRootNode(Source code, AbstractExportImportLanguage language) { super(language); this.code = code; this.language = language; } @Override public Object execute(VirtualFrame frame) { return language.importExport(code); } } public static final String L1 = "application/x-test-import-export-1"; public static final String L1_ALT = "application/alt-test-import-export-1"; static final String L2 = "application/x-test-import-export-2"; static final String L3 = "application/x-test-import-export-3"; @TruffleLanguage.Registration(mimeType = {L1, L1_ALT}, name = "ImportExport1", version = "0") public static final class ExportImportLanguage1 extends AbstractExportImportLanguage { public ExportImportLanguage1() { } @Override protected String toString(Ctx ctx, Object value) { if (value instanceof String) { try { int number = Integer.parseInt((String) value); return number + ": Int"; } catch (NumberFormatException ex) { // go on } } return Objects.toString(value); } } @TruffleLanguage.Registration(mimeType = L2, name = "ImportExport2", version = "0") public static final class ExportImportLanguage2 extends AbstractExportImportLanguage { public ExportImportLanguage2() { } @Override protected String toString(Ctx ctx, Object value) { if (value instanceof String) { try { double number = Double.parseDouble((String) value); return number + ": Double"; } catch (NumberFormatException ex) { // go on } } return Objects.toString(value); } } @TruffleLanguage.Registration(mimeType = {L3, L3 + "alt"}, name = "ImportExport3", version = "0") public static final class ExportImportLanguage3 extends AbstractExportImportLanguage { public ExportImportLanguage3() { } } }