Skip to content

Instantly share code, notes, and snippets.

@skrb
Last active December 24, 2023 14:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skrb/7f97debc096617d9424cb87ee2a8048e to your computer and use it in GitHub Desktop.
Save skrb/7f97debc096617d9424cb87ee2a8048e to your computer and use it in GitHub Desktop.
動的にString Templateのテンプレートを作成し、ヒープだけでコンパイル、クラスロードまで行う例
import java.lang.reflect.Method;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class DynamicTemplateBuilder {
public static Optional<StringTemplate> createTemplate(String template, String argName, Object variable)
throws IOException, ReflectiveOperationException {
// コンパイラの取得
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
return Optional.empty();
}
// 仮想ファイルマネージャの取得
try (StandardJavaFileManager fm
= compiler.getStandardFileManager(null, null, null);
var fileManager = new MemoryJavaFileManager(fm)) {
// コンパイルするファイルの準備
List<? extends JavaFileObject> fileobjs
= createJavaFileObjects(template, argName);
// コンパイルタスクの生成
JavaCompiler.CompilationTask task
= compiler.getTask(null,
fileManager,
null,
List.of("--release", "22", "--enable-preview"),
null,
fileobjs);
// コンパイル
if (task.call()) {
// クラスのロード
// ClassLoader loader = ClassLoader.getSystemClassLoader();
ClassLoader loader = new MemoryClassLoader(fileManager.getClassBytes());
Class<?> clss = loader.loadClass("Template");
// Method オブジェクトを取得し、リフレクションで実行する
Method method = clss.getMethod("process", Object.class);
StringTemplate dynamicTemplate
= (StringTemplate) method.invoke(null, new Object[]{variable});
return Optional.of(dynamicTemplate);
} else {
return Optional.empty();
}
}
}
private static List<? extends JavaFileObject> createJavaFileObjects(String template, String argName) {
// Java のソースとなる文字列
String templateSrc = STR."""
public class Template {
public static StringTemplate process(Object \{ argName }) {
return java.lang.StringTemplate.RAW.\"\"\"
\{ template }
\"\"\";
}
}
""" ;
// 文字列をソースとする StringJavaFileObject オブジェクトを
// 生成する
JavaFileObject fileobj
= new StringJavaFileObject("Template", templateSrc);
return List.of(fileobj);
}
}
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
public final class MemoryClassLoader extends URLClassLoader {
private final Map<String, byte[]> classBytes;
public MemoryClassLoader(Map<String, byte[]> classBytes) {
super(new URL[]{});
this.classBytes = classBytes;
}
@Override
protected Class findClass(String className) throws ClassNotFoundException {
byte[] buf = classBytes.get(className);
if (buf != null) {
classBytes.put(className, null);
return defineClass(className, buf, 0, buf.length);
} else {
return super.findClass(className);
}
}
}
import java.io.*;
import java.net.URI;
import java.util.Map;
import java.util.HashMap;
import javax.tools.*;
import javax.tools.JavaFileObject.Kind;
public final class MemoryJavaFileManager extends ForwardingJavaFileManager {
private final static String EXT = ".java";
private Map<String, byte[]> classBytes;
public MemoryJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
classBytes = new HashMap<>();
}
public Map<String, byte[]> getClassBytes() {
return classBytes;
}
@Override
public void close() throws IOException {
classBytes = new HashMap<>();
}
@Override
public void flush() throws IOException {
}
private class ClassOutputBuffer extends SimpleJavaFileObject {
private final String name;
ClassOutputBuffer(String name) {
super(toURI(name), Kind.CLASS);
this.name = name;
}
@Override
public OutputStream openOutputStream() {
return new FilterOutputStream(new ByteArrayOutputStream()) {
@Override
public void close() throws IOException {
out.close();
ByteArrayOutputStream bos = (ByteArrayOutputStream)out;
classBytes.put(name, bos.toByteArray());
}
};
}
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
String className,
Kind kind,
FileObject sibling) throws IOException {
if (kind == Kind.CLASS) {
return new ClassOutputBuffer(className);
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
static URI toURI(String name) {
File file = new File(name);
if (file.exists()) {
return file.toURI();
} else {
try {
final StringBuilder newUri = new StringBuilder();
newUri.append("mfm:///");
newUri.append(name.replace('.', '/'));
if(name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
return URI.create(newUri.toString());
} catch (Exception exp) {
return URI.create("mfm:///com/sun/script/java/java_source");
}
}
}
}
import java.net.URI;
import javax.tools.SimpleJavaFileObject;
public class StringJavaFileObject extends SimpleJavaFileObject {
private String content;
public StringJavaFileObject(String className, String content) {
super(URI.create("string:///"
+ className.replace('.', '/')
+ Kind.SOURCE.extension),
Kind.SOURCE);
this.content = content;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return content;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment