Last active
December 24, 2023 14:57
-
-
Save skrb/7f97debc096617d9424cb87ee2a8048e to your computer and use it in GitHub Desktop.
動的にString Templateのテンプレートを作成し、ヒープだけでコンパイル、クラスロードまで行う例
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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