/******************************************************************************* * Copyright 2019 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.talosvfx.talos.runtime.script; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.reflect.ClassReflection; import com.badlogic.gdx.utils.reflect.ReflectionException; import com.talosvfx.talos.runtime.scripts.SimpleReturnScript; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaCompiler; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.util.Arrays; public class ScriptCompiler { private static ScriptCompiler instance; public static ScriptCompiler instance () { if (instance == null) { instance = new ScriptCompiler(); } return instance; } private final JavaCompiler compiler; public ScriptCompiler () { compiler = ToolProvider.getSystemJavaCompiler(); } public SimpleReturnScript compile (String javaString) { JavaSourceFromString file = new JavaSourceFromString("SimpleRunIm", javaString); DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); DynamicClassesFileManager manager = new DynamicClassesFileManager(compiler.getStandardFileManager(null, null, null)); Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file); JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, null, null, compilationUnits); boolean success = task.call(); for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { System.err.print(String.format("Script compilation error: Line: %d - %s%n", diagnostic.getLineNumber(), diagnostic.getMessage(null))); } if (success) { try { System.out.println("Compiled"); Class clazz = manager.loader.findClass("SimpleRunIm"); return (SimpleReturnScript)ClassReflection.newInstance(clazz); } catch (ReflectionException | ClassNotFoundException e) { e.printStackTrace(); } } return null; } public static class ByteClassLoader extends ClassLoader { private ObjectMap<String, JavaSourceFromString> cache = new ObjectMap<>(); public ByteClassLoader () { super(ByteClassLoader.class.getClassLoader()); } public void put (String name, JavaSourceFromString obj) { cache.put(name, obj); } @Override protected Class<?> findClass (String name) throws ClassNotFoundException { if (cache.containsKey(name)) { final JavaSourceFromString javaSourceFromString = cache.get(name); final byte[] classBytes = javaSourceFromString.getClassBytes(); return defineClass(name, classBytes, 0, classBytes.length); } throw new GdxRuntimeException("Woopsy"); } } public static class JavaSourceFromString extends SimpleJavaFileObject { private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); String code; JavaSourceFromString(String name, String code) { super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE); this.code = code; } JavaSourceFromString(String name, Kind kind) { super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; } public byte[] getClassBytes () { return bos.toByteArray(); } @Override public OutputStream openOutputStream () throws IOException { return bos; } } public static class DynamicClassesFileManager<FileManager> extends ForwardingJavaFileManager<JavaFileManager> { private ByteClassLoader loader = null; protected DynamicClassesFileManager (StandardJavaFileManager fileManager) { super(fileManager); try { loader = new ByteClassLoader(); } catch (Exception e) { e.printStackTrace(); } } @Override public JavaFileObject getJavaFileForOutput (Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { JavaSourceFromString obj = new JavaSourceFromString(className, kind); loader.put(className, obj); return obj; } @Override public ClassLoader getClassLoader (Location location) { return loader; } } }