package jagi.fs;
+import jagi.*;
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<>();
+ private final Map<Path, File> files = 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 static class ClassOutput {
public final String name;
- private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ public 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;
+ private final List<? extends Object> messages;
- public CompilationException(Path file, List<Diagnostic<? extends JavaFileObject>> messages) {
+ public CompilationException(Path file, List<? extends Object> messages) {
this.file = file;
this.messages = messages;
}
public String messages() {
StringBuilder buf = new StringBuilder();
- for(Diagnostic msg : messages)
+ for(Object msg : messages)
buf.append(msg.toString() + "\n");
return(buf.toString());
}
}
}
- 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 Compilation implements AutoCloseable {
+ private final Path dir, srcdir, outdir;
+ private final List<Path> infiles = new ArrayList<>();
+ private List<String> output = null;
+
+ public Compilation() throws IOException {
+ dir = Files.createTempDirectory("javac");
+ srcdir = dir.resolve("src");
+ outdir = dir.resolve("out");
+ Files.createDirectory(srcdir);
+ Files.createDirectory(outdir);
+ }
+
+ public void add(Path p) {
+ infiles.add(p);
+ }
+
+ public boolean compile() throws IOException {
+ List<String> args = new ArrayList<>();
+ args.add("javac");
+ args.add("-d");
+ args.add(outdir.toString());
+ for(Path p : infiles)
+ args.add(p.toString());
+ ProcessBuilder cmd = new ProcessBuilder(args);
+ cmd.redirectErrorStream(true);
+ cmd.redirectOutput(ProcessBuilder.Redirect.PIPE);
+ Process proc = cmd.start();
+ List<String> output = new ArrayList<>();
+ BufferedReader fp = new BufferedReader(new InputStreamReader(proc.getInputStream(), Utils.UTF8));
+ while(true) {
+ String ln = fp.readLine();
+ if(ln == null)
+ break;
+ output.add(ln);
+ }
+ int status;
+ try {
+ status = proc.waitFor();
+ } catch(InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw(new IOException("compilation interrupted"));
+ }
+ this.output = output;
+ return(status == 0);
+ }
+
+ public List<String> output() {
+ if(output == null)
+ throw(new IllegalStateException());
+ return(output);
+ }
+
+ public Collection<ClassOutput> classes() throws IOException {
+ Collection<ClassOutput> ret = new ArrayList<>();
+ for(Path p : (Iterable<Path>)Files.walk(outdir)::iterator) {
+ Path rel = outdir.relativize(p);
+ String fn = rel.getName(rel.getNameCount() - 1).toString();
+ if(!Files.isRegularFile(p) || !fn.endsWith(".class"))
+ continue;
+ StringBuilder clnm = new StringBuilder();
+ for(int i = 0; i < rel.getNameCount() - 1; i++) {
+ clnm.append(rel.getName(i));
+ clnm.append('.');
+ }
+ clnm.append(fn.substring(0, fn.length() - 6));
+ ClassOutput cls = new ClassOutput(clnm.toString());
+ Files.copy(p, cls.buf);
+ ret.add(cls);
+ }
+ return(ret);
+ }
+
+ private static void remove(Path p) throws IOException {
+ if(Files.isDirectory(p)) {
+ for(Path ent : (Iterable<Path>)Files.list(p)::iterator)
+ remove(ent);
+ }
+ Files.delete(p);
+ }
+
+ public void close() throws IOException {
+ remove(dir);
+ }
}
public static class BufferedClassLoader extends ClassLoader {
public static class Module {
public final Path file;
- private FileTime mtime = null;
- private ClassLoader code = null;
+ public final ClassLoader code;
- private Module(Path file) {
+ public Module(Path file) throws IOException {
this.file = file;
+ try(Compilation c = new Compilation()) {
+ split(c);
+ if(!c.compile())
+ throw(new CompilationException(file, c.output()));
+ code = new BufferedClassLoader(c.classes());
+ }
+ }
+
+ private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
+ public void split(Compilation c) throws IOException {
+ StringBuilder head = new StringBuilder();
+ BufferedWriter cur = 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()) {
+ String clnm = m.group(4);
+ Path sp = c.srcdir.resolve(clnm + ".java");
+ c.add(sp);
+ if(cur != null)
+ cur.close();
+ cur = Files.newBufferedWriter(sp);
+ cur.append(head);
+ }
+ if(cur != null) {
+ cur.append(ln); cur.append('\n');
+ } else {
+ head.append(ln); head.append('\n');
+ }
+ }
+ } finally {
+ if(cur != null)
+ cur.close();
+ }
+ }
+ }
+
+ public static class File {
+ public final Path name;
+ private FileTime mtime = null;
+ private Module mod = null;
+
+ private File(Path name) {
+ this.name = name;
}
public void update() throws IOException {
synchronized(this) {
- FileTime mtime = Files.getLastModifiedTime(file);
+ FileTime mtime = Files.getLastModifiedTime(name);
if((this.mtime == null) || (this.mtime.compareTo(mtime) < 0)) {
- code = new BufferedClassLoader(compile(file));
+ mod = new Module(name);
this.mtime = mtime;
}
}
}
- public ClassLoader code() {
- if(code == null)
- throw(new RuntimeException("module has not yet been updated"));
- return(code);
+ public Module mod() {
+ if(mod == null)
+ throw(new RuntimeException("file has not yet been updated"));
+ return(mod);
}
}
- public Module module(Path file) {
- synchronized(modules) {
- Module ret = modules.get(file);
+ public File file(Path name) {
+ synchronized(files) {
+ File ret = files.get(name);
if(ret == null)
- modules.put(file, ret = new Module(file));
+ files.put(name, ret = new File(name));
return(ret);
}
}