+package jagi.fs;
+
+import java.util.*;
+import java.util.regex.*;
+import java.nio.file.*;
+import java.nio.file.attribute.*;
+import java.io.*;
+import java.net.*;
+import javax.tools.*;
+
+public class Compiler {
+ private final Map<Path, Module> modules = new HashMap<>();
+
+ public static class FilePart extends SimpleJavaFileObject {
+ public final Path file;
+ public final String clnm;
+ public final CharSequence src;
+
+ private static URI dummyuri(Path file, String clnm) {
+ String clp = clnm.replace('.', '/') + Kind.SOURCE.extension;
+ return(URI.create(file.toUri().toString() + "!/" + clp));
+ }
+
+ public FilePart(Path file, String clnm, CharSequence src) {
+ super(dummyuri(file, clnm), Kind.SOURCE);
+ this.file = file;
+ this.clnm = clnm;
+ this.src = src;
+ }
+
+ public CharSequence getCharContent(boolean ice) {
+ return(src);
+ }
+
+ private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
+ public static Collection<FilePart> split(Path file) throws IOException {
+ Collection<FilePart> ret = new ArrayList<>();
+ StringBuilder head = new StringBuilder();
+ StringBuilder cur = null;
+ String clnm = null;
+ try(BufferedReader fp = Files.newBufferedReader(file)) {
+ for(String ln = fp.readLine(); ln != null; ln = fp.readLine()) {
+ Matcher m = classpat.matcher(ln);
+ if(m.find()) {
+ if(cur != null)
+ ret.add(new FilePart(file, clnm, cur));
+ clnm = m.group(4);
+ cur = new StringBuilder();
+ cur.append(head);
+ }
+ if(cur != null) {
+ cur.append(ln); cur.append('\n');
+ } else {
+ head.append(ln); head.append('\n');
+ }
+ }
+ if(cur != null)
+ ret.add(new FilePart(file, clnm, cur));
+ }
+ return(ret);
+ }
+ }
+
+ public static class ClassOutput extends SimpleJavaFileObject {
+ public final String name;
+ private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ public ClassOutput(String name) {
+ super(URI.create("mem://" + name), Kind.CLASS);
+ this.name = name;
+ }
+
+ public OutputStream openOutputStream() {
+ return(buf);
+ }
+ }
+
+ public static class FileContext extends ForwardingJavaFileManager<JavaFileManager> {
+ public final Collection<ClassOutput> output = new ArrayList<>();
+
+ public FileContext(JavaCompiler javac) {
+ super(javac.getStandardFileManager(null, null, null));
+ }
+
+ public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) {
+ ClassOutput cl = new ClassOutput(name);
+ output.add(cl);
+ return(cl);
+ }
+ }
+
+ public static class CompilationException extends RuntimeException {
+ public final Path file;
+ private final List<Diagnostic<? extends JavaFileObject>> messages;
+
+ public CompilationException(Path file, List<Diagnostic<? extends JavaFileObject>> messages) {
+ this.file = file;
+ this.messages = messages;
+ }
+
+ public String getMessage() {
+ return(file + ": compilation failed");
+ }
+
+ public String messages() {
+ StringBuilder buf = new StringBuilder();
+ for(Diagnostic msg : messages)
+ buf.append(msg.toString() + "\n");
+ return(buf.toString());
+ }
+
+ public void printStackTrace(PrintStream out) {
+ out.print(messages());
+ super.printStackTrace(out);
+ }
+ }
+
+ public static Collection<ClassOutput> compile(Path file) throws IOException {
+ List<String> opt = Arrays.asList();
+ JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
+ if(javac == null)
+ throw(new RuntimeException("no javac present"));
+ Collection<FilePart> files;
+ files = FilePart.split(file);
+ DiagnosticCollector<JavaFileObject> out = new DiagnosticCollector<>();
+ FileContext fs = new FileContext(javac);
+ JavaCompiler.CompilationTask job = javac.getTask(null, fs, out, opt, null, files);
+ if(!job.call())
+ throw(new CompilationException(file, out.getDiagnostics()));
+ return(fs.output);
+ }
+
+ public static class BufferedClassLoader extends ClassLoader {
+ public final Map<String, byte[]> contents;
+
+ public BufferedClassLoader(Collection<ClassOutput> contents) {
+ this.contents = new HashMap<>();
+ for(ClassOutput clc : contents)
+ this.contents.put(clc.name, clc.buf.toByteArray());
+ }
+
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ byte[] c = contents.get(name);
+ if(c == null)
+ throw(new ClassNotFoundException(name));
+ return(defineClass(name, c, 0, c.length));
+ }
+ }
+
+ public static class Module {
+ public final Path file;
+ private FileTime mtime = null;
+ private ClassLoader code = null;
+
+ private Module(Path file) {
+ this.file = file;
+ }
+
+ public void update() throws IOException {
+ synchronized(this) {
+ FileTime mtime = Files.getLastModifiedTime(file);
+ if((this.mtime == null) || (this.mtime.compareTo(mtime) < 0)) {
+ code = new BufferedClassLoader(compile(file));
+ this.mtime = mtime;
+ }
+ }
+ }
+
+ public ClassLoader code() {
+ if(code == null)
+ throw(new RuntimeException("module has not yet been updated"));
+ return(code);
+ }
+ }
+
+ public Module module(Path file) {
+ synchronized(modules) {
+ Module ret = modules.get(file);
+ if(ret == null)
+ modules.put(file, ret = new Module(file));
+ return(ret);
+ }
+ }
+
+ private static Compiler global = null;
+ public static Compiler get() {
+ if(global == null) {
+ synchronized(Compiler.class) {
+ if(global == null)
+ global = new Compiler();
+ }
+ }
+ return(global);
+ }
+}