Rewrite jagidir compiler to call external javac.
[jagi.git] / src / jagi / fs / Compiler.java
CommitLineData
49ccd711
FT
1package jagi.fs;
2
06577e0b 3import jagi.*;
49ccd711
FT
4import java.util.*;
5import java.util.regex.*;
6import java.nio.file.*;
7import java.nio.file.attribute.*;
8import java.io.*;
49ccd711
FT
9
10public class Compiler {
11 private final Map<Path, Module> modules = new HashMap<>();
12
06577e0b
FT
13 public static class ClassOutput {
14 public final String name;
15 public final ByteArrayOutputStream buf = new ByteArrayOutputStream();
49ccd711 16
06577e0b
FT
17 public ClassOutput(String name) {
18 this.name = name;
49ccd711 19 }
06577e0b
FT
20 }
21
22 public static class CompilationException extends RuntimeException {
23 public final Path file;
24 private final List<? extends Object> messages;
49ccd711 25
06577e0b 26 public CompilationException(Path file, List<? extends Object> messages) {
49ccd711 27 this.file = file;
06577e0b
FT
28 this.messages = messages;
29 }
30
31 public String getMessage() {
32 return(file + ": compilation failed");
33 }
34
35 public String messages() {
36 StringBuilder buf = new StringBuilder();
37 for(Object msg : messages)
38 buf.append(msg.toString() + "\n");
39 return(buf.toString());
40 }
41
42 public void printStackTrace(PrintStream out) {
43 out.print(messages());
44 super.printStackTrace(out);
45 }
46 }
47
48 public static class Compilation implements AutoCloseable {
49 private final Path dir, srcdir, outdir;
50 private final List<Path> infiles = new ArrayList<>();
51 private List<String> output = null;
52
53 public Compilation() throws IOException {
54 dir = Files.createTempDirectory("javac");
55 srcdir = dir.resolve("src");
56 outdir = dir.resolve("out");
57 Files.createDirectory(srcdir);
58 Files.createDirectory(outdir);
49ccd711
FT
59 }
60
06577e0b
FT
61 public void add(Path p) {
62 infiles.add(p);
49ccd711
FT
63 }
64
65 private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
06577e0b 66 public void split(Path infile) throws IOException {
49ccd711 67 StringBuilder head = new StringBuilder();
06577e0b
FT
68 BufferedWriter cur = null;
69 try(BufferedReader fp = Files.newBufferedReader(infile)) {
49ccd711
FT
70 for(String ln = fp.readLine(); ln != null; ln = fp.readLine()) {
71 Matcher m = classpat.matcher(ln);
72 if(m.find()) {
06577e0b
FT
73 String clnm = m.group(4);
74 Path sp = srcdir.resolve(clnm + ".java");
75 add(sp);
49ccd711 76 if(cur != null)
06577e0b
FT
77 cur.close();
78 cur = Files.newBufferedWriter(sp);
49ccd711
FT
79 cur.append(head);
80 }
81 if(cur != null) {
82 cur.append(ln); cur.append('\n');
83 } else {
84 head.append(ln); head.append('\n');
85 }
86 }
06577e0b 87 } finally {
49ccd711 88 if(cur != null)
06577e0b 89 cur.close();
49ccd711 90 }
49ccd711 91 }
49ccd711 92
06577e0b
FT
93 public boolean compile() throws IOException {
94 List<String> args = new ArrayList<>();
95 args.add("javac");
96 args.add("-d");
97 args.add(outdir.toString());
98 for(Path p : infiles)
99 args.add(p.toString());
100 ProcessBuilder cmd = new ProcessBuilder(args);
101 cmd.redirectErrorStream(true);
102 cmd.redirectOutput(ProcessBuilder.Redirect.PIPE);
103 Process proc = cmd.start();
104 List<String> output = new ArrayList<>();
105 BufferedReader fp = new BufferedReader(new InputStreamReader(proc.getInputStream(), Utils.UTF8));
106 while(true) {
107 String ln = fp.readLine();
108 if(ln == null)
109 break;
110 output.add(ln);
111 }
112 int status;
113 try {
114 status = proc.waitFor();
115 } catch(InterruptedException e) {
116 Thread.currentThread().interrupt();
117 throw(new IOException("compilation interrupted"));
118 }
119 this.output = output;
120 return(status == 0);
49ccd711
FT
121 }
122
06577e0b
FT
123 public List<String> output() {
124 if(output == null)
125 throw(new IllegalStateException());
126 return(output);
49ccd711 127 }
49ccd711 128
06577e0b
FT
129 public Collection<ClassOutput> classes() throws IOException {
130 Collection<ClassOutput> ret = new ArrayList<>();
131 for(Path p : (Iterable<Path>)Files.walk(outdir)::iterator) {
132 Path rel = outdir.relativize(p);
133 String fn = rel.getName(rel.getNameCount() - 1).toString();
134 if(!Files.isRegularFile(p) || !fn.endsWith(".class"))
135 continue;
136 StringBuilder clnm = new StringBuilder();
137 for(int i = 0; i < rel.getNameCount() - 1; i++) {
138 clnm.append(rel.getName(i));
139 clnm.append('.');
140 }
141 clnm.append(fn.substring(0, fn.length() - 6));
142 ClassOutput cls = new ClassOutput(clnm.toString());
143 Files.copy(p, cls.buf);
144 ret.add(cls);
145 }
146 return(ret);
49ccd711
FT
147 }
148
06577e0b
FT
149 private static void remove(Path p) throws IOException {
150 if(Files.isDirectory(p)) {
151 for(Path ent : (Iterable<Path>)Files.list(p)::iterator)
152 remove(ent);
153 }
154 Files.delete(p);
49ccd711
FT
155 }
156
06577e0b
FT
157 public void close() throws IOException {
158 remove(dir);
49ccd711
FT
159 }
160 }
161
162 public static Collection<ClassOutput> compile(Path file) throws IOException {
06577e0b
FT
163 try(Compilation c = new Compilation()) {
164 c.split(file);
165 if(!c.compile())
166 throw(new CompilationException(file, c.output()));
167 return(c.classes());
168 }
49ccd711
FT
169 }
170
171 public static class BufferedClassLoader extends ClassLoader {
172 public final Map<String, byte[]> contents;
173
174 public BufferedClassLoader(Collection<ClassOutput> contents) {
175 this.contents = new HashMap<>();
176 for(ClassOutput clc : contents)
177 this.contents.put(clc.name, clc.buf.toByteArray());
178 }
179
180 public Class<?> findClass(String name) throws ClassNotFoundException {
181 byte[] c = contents.get(name);
182 if(c == null)
183 throw(new ClassNotFoundException(name));
184 return(defineClass(name, c, 0, c.length));
185 }
186 }
187
188 public static class Module {
189 public final Path file;
190 private FileTime mtime = null;
191 private ClassLoader code = null;
192
193 private Module(Path file) {
194 this.file = file;
195 }
196
197 public void update() throws IOException {
198 synchronized(this) {
199 FileTime mtime = Files.getLastModifiedTime(file);
200 if((this.mtime == null) || (this.mtime.compareTo(mtime) < 0)) {
201 code = new BufferedClassLoader(compile(file));
202 this.mtime = mtime;
203 }
204 }
205 }
206
207 public ClassLoader code() {
208 if(code == null)
209 throw(new RuntimeException("module has not yet been updated"));
210 return(code);
211 }
212 }
213
214 public Module module(Path file) {
215 synchronized(modules) {
216 Module ret = modules.get(file);
217 if(ret == null)
218 modules.put(file, ret = new Module(file));
219 return(ret);
220 }
221 }
222
223 private static Compiler global = null;
224 public static Compiler get() {
225 if(global == null) {
226 synchronized(Compiler.class) {
227 if(global == null)
228 global = new Compiler();
229 }
230 }
231 return(global);
232 }
233}