Reorganize compiler for more flexibility.
[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 {
e8892ea0 11 private final Map<Path, File> files = new HashMap<>();
49ccd711 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
06577e0b
FT
65 public boolean compile() throws IOException {
66 List<String> args = new ArrayList<>();
67 args.add("javac");
68 args.add("-d");
69 args.add(outdir.toString());
70 for(Path p : infiles)
71 args.add(p.toString());
72 ProcessBuilder cmd = new ProcessBuilder(args);
73 cmd.redirectErrorStream(true);
74 cmd.redirectOutput(ProcessBuilder.Redirect.PIPE);
75 Process proc = cmd.start();
76 List<String> output = new ArrayList<>();
77 BufferedReader fp = new BufferedReader(new InputStreamReader(proc.getInputStream(), Utils.UTF8));
78 while(true) {
79 String ln = fp.readLine();
80 if(ln == null)
81 break;
82 output.add(ln);
83 }
84 int status;
85 try {
86 status = proc.waitFor();
87 } catch(InterruptedException e) {
88 Thread.currentThread().interrupt();
89 throw(new IOException("compilation interrupted"));
90 }
91 this.output = output;
92 return(status == 0);
49ccd711
FT
93 }
94
06577e0b
FT
95 public List<String> output() {
96 if(output == null)
97 throw(new IllegalStateException());
98 return(output);
49ccd711 99 }
49ccd711 100
06577e0b
FT
101 public Collection<ClassOutput> classes() throws IOException {
102 Collection<ClassOutput> ret = new ArrayList<>();
103 for(Path p : (Iterable<Path>)Files.walk(outdir)::iterator) {
104 Path rel = outdir.relativize(p);
105 String fn = rel.getName(rel.getNameCount() - 1).toString();
106 if(!Files.isRegularFile(p) || !fn.endsWith(".class"))
107 continue;
108 StringBuilder clnm = new StringBuilder();
109 for(int i = 0; i < rel.getNameCount() - 1; i++) {
110 clnm.append(rel.getName(i));
111 clnm.append('.');
112 }
113 clnm.append(fn.substring(0, fn.length() - 6));
114 ClassOutput cls = new ClassOutput(clnm.toString());
115 Files.copy(p, cls.buf);
116 ret.add(cls);
117 }
118 return(ret);
49ccd711
FT
119 }
120
06577e0b
FT
121 private static void remove(Path p) throws IOException {
122 if(Files.isDirectory(p)) {
123 for(Path ent : (Iterable<Path>)Files.list(p)::iterator)
124 remove(ent);
125 }
126 Files.delete(p);
49ccd711
FT
127 }
128
06577e0b
FT
129 public void close() throws IOException {
130 remove(dir);
49ccd711
FT
131 }
132 }
133
49ccd711
FT
134 public static class BufferedClassLoader extends ClassLoader {
135 public final Map<String, byte[]> contents;
136
137 public BufferedClassLoader(Collection<ClassOutput> contents) {
138 this.contents = new HashMap<>();
139 for(ClassOutput clc : contents)
140 this.contents.put(clc.name, clc.buf.toByteArray());
141 }
142
143 public Class<?> findClass(String name) throws ClassNotFoundException {
144 byte[] c = contents.get(name);
145 if(c == null)
146 throw(new ClassNotFoundException(name));
147 return(defineClass(name, c, 0, c.length));
148 }
149 }
150
151 public static class Module {
152 public final Path file;
e8892ea0 153 public final ClassLoader code;
49ccd711 154
e8892ea0 155 public Module(Path file) throws IOException {
49ccd711 156 this.file = file;
e8892ea0
FT
157 try(Compilation c = new Compilation()) {
158 split(c);
159 if(!c.compile())
160 throw(new CompilationException(file, c.output()));
161 code = new BufferedClassLoader(c.classes());
162 }
163 }
164
165 private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
166 public void split(Compilation c) throws IOException {
167 StringBuilder head = new StringBuilder();
168 BufferedWriter cur = null;
169 try(BufferedReader fp = Files.newBufferedReader(file)) {
170 for(String ln = fp.readLine(); ln != null; ln = fp.readLine()) {
171 Matcher m = classpat.matcher(ln);
172 if(m.find()) {
173 String clnm = m.group(4);
174 Path sp = c.srcdir.resolve(clnm + ".java");
175 c.add(sp);
176 if(cur != null)
177 cur.close();
178 cur = Files.newBufferedWriter(sp);
179 cur.append(head);
180 }
181 if(cur != null) {
182 cur.append(ln); cur.append('\n');
183 } else {
184 head.append(ln); head.append('\n');
185 }
186 }
187 } finally {
188 if(cur != null)
189 cur.close();
190 }
191 }
192 }
193
194 public static class File {
195 public final Path name;
196 private FileTime mtime = null;
197 private Module mod = null;
198
199 private File(Path name) {
200 this.name = name;
49ccd711
FT
201 }
202
203 public void update() throws IOException {
204 synchronized(this) {
e8892ea0 205 FileTime mtime = Files.getLastModifiedTime(name);
49ccd711 206 if((this.mtime == null) || (this.mtime.compareTo(mtime) < 0)) {
e8892ea0 207 mod = new Module(name);
49ccd711
FT
208 this.mtime = mtime;
209 }
210 }
211 }
212
e8892ea0
FT
213 public Module mod() {
214 if(mod == null)
215 throw(new RuntimeException("file has not yet been updated"));
216 return(mod);
49ccd711
FT
217 }
218 }
219
e8892ea0
FT
220 public File file(Path name) {
221 synchronized(files) {
222 File ret = files.get(name);
49ccd711 223 if(ret == null)
e8892ea0 224 files.put(name, ret = new File(name));
49ccd711
FT
225 return(ret);
226 }
227 }
228
229 private static Compiler global = null;
230 public static Compiler get() {
231 if(global == null) {
232 synchronized(Compiler.class) {
233 if(global == null)
234 global = new Compiler();
235 }
236 }
237 return(global);
238 }
239}