5 import java.util.regex.*;
6 import java.nio.file.*;
7 import java.nio.file.attribute.*;
11 public class Compiler {
12 private final Map<Path, File> files = new HashMap<>();
13 private final Map<Path, ClassLoader> libs = new HashMap<>();
14 private final Collection<Path> searchpath = new ArrayList<>();
17 String syspath = System.getenv("PATH");
19 String sep = java.io.File.pathSeparator;
20 int p1 = 0, p2 = syspath.indexOf(sep);
24 el = syspath.substring(p1, p2);
25 p1 = p2 + sep.length();
26 p2 = syspath.indexOf(sep, p1);
28 el =syspath.substring(p1);
31 Path p = Paths.get(el);
32 if(p.getParent() != null)
33 searchpath.add(p.getParent().resolve("share").resolve("java"));
34 } catch(InvalidPathException e) {
39 String proppath = System.getProperty("jagi.search-path");
40 if(proppath != null) {
41 for(String el : proppath.split(":")) {
43 searchpath.add(Paths.get(el));
44 } catch(InvalidPathException e) {
51 public static class ClassOutput {
52 public final String name;
53 public final ByteArrayOutputStream buf = new ByteArrayOutputStream();
55 public ClassOutput(String name) {
60 public static class CompilationException extends RuntimeException {
61 public final Path file;
62 private final List<? extends Object> messages;
64 public CompilationException(Path file, List<? extends Object> messages) {
66 this.messages = messages;
69 public String getMessage() {
70 return(file + ": compilation failed");
73 public String messages() {
74 StringBuilder buf = new StringBuilder();
75 for(Object msg : messages)
76 buf.append(msg.toString() + "\n");
77 return(buf.toString());
80 public void printStackTrace(PrintStream out) {
81 out.print(messages());
82 super.printStackTrace(out);
86 public static class Compilation implements AutoCloseable {
87 public final Path dir, srcdir, outdir;
88 private final List<Path> infiles = new ArrayList<>();
89 private final List<Path> classpath = new ArrayList<>();
90 private List<String> output = null;
92 public Compilation() throws IOException {
93 dir = Files.createTempDirectory("javac");
94 srcdir = dir.resolve("src");
95 outdir = dir.resolve("out");
96 Files.createDirectory(srcdir);
97 Files.createDirectory(outdir);
100 public void add(Path p) {
104 public void classpath(Path p) {
108 public boolean compile() throws IOException {
109 List<String> args = new ArrayList<>();
112 args.add(outdir.toString());
113 if(!classpath.isEmpty()) {
114 StringBuilder buf = new StringBuilder();
115 for(Path cp : classpath) {
118 buf.append(cp.toString());
121 args.add(buf.toString());
123 for(Path p : infiles)
124 args.add(p.toString());
125 ProcessBuilder cmd = new ProcessBuilder(args);
126 cmd.redirectErrorStream(true);
127 cmd.redirectOutput(ProcessBuilder.Redirect.PIPE);
128 Process proc = cmd.start();
129 List<String> output = new ArrayList<>();
130 BufferedReader fp = new BufferedReader(new InputStreamReader(proc.getInputStream(), Utils.UTF8));
132 String ln = fp.readLine();
139 status = proc.waitFor();
140 } catch(InterruptedException e) {
141 Thread.currentThread().interrupt();
142 throw(new IOException("compilation interrupted"));
144 this.output = output;
148 public List<String> output() {
150 throw(new IllegalStateException());
154 public Collection<ClassOutput> classes() throws IOException {
155 Collection<ClassOutput> ret = new ArrayList<>();
156 for(Path p : (Iterable<Path>)Files.walk(outdir)::iterator) {
157 Path rel = outdir.relativize(p);
158 String fn = rel.getName(rel.getNameCount() - 1).toString();
159 if(!Files.isRegularFile(p) || !fn.endsWith(".class"))
161 StringBuilder clnm = new StringBuilder();
162 for(int i = 0; i < rel.getNameCount() - 1; i++) {
163 clnm.append(rel.getName(i));
166 clnm.append(fn.substring(0, fn.length() - 6));
167 ClassOutput cls = new ClassOutput(clnm.toString());
168 Files.copy(p, cls.buf);
174 private static void remove(Path p) throws IOException {
175 if(Files.isDirectory(p)) {
176 for(Path ent : (Iterable<Path>)Files.list(p)::iterator)
182 public void close() throws IOException {
187 public static class BufferedClassLoader extends ClassLoader {
188 public final Map<String, byte[]> contents;
190 public BufferedClassLoader(ClassLoader parent, Collection<ClassOutput> contents) {
192 this.contents = new HashMap<>();
193 for(ClassOutput clc : contents)
194 this.contents.put(clc.name, clc.buf.toByteArray());
197 public Class<?> findClass(String name) throws ClassNotFoundException {
198 byte[] c = contents.get(name);
200 throw(new ClassNotFoundException(name));
201 return(defineClass(name, c, 0, c.length));
205 public static class LibClassLoader extends ClassLoader {
206 private final ClassLoader[] classpath;
208 public LibClassLoader(ClassLoader parent, Collection<ClassLoader> classpath) {
210 this.classpath = classpath.toArray(new ClassLoader[0]);
213 public Class<?> findClass(String name) throws ClassNotFoundException {
214 for(ClassLoader lib : classpath) {
216 return(lib.loadClass(name));
217 } catch(ClassNotFoundException e) {}
219 throw(new ClassNotFoundException("Could not find " + name + " in any of " + Arrays.asList(classpath).toString()));
223 public ClassLoader libloader(Path p) {
225 ClassLoader ret = libs.get(p);
228 libs.put(p, ret = new URLClassLoader(new URL[] {p.toUri().toURL()}));
229 } catch(MalformedURLException e) {
230 throw(new RuntimeException(e));
237 private Path findlib(String nm) {
238 for(Path dir : searchpath) {
239 Path jar = dir.resolve(nm + ".jar");
240 if(Files.isRegularFile(jar))
246 private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
247 private static final Pattern libpat = Pattern.compile("\\$use\\s*:\\s*(\\S+)");
248 public class Module {
249 public final Path file;
250 public final ClassLoader code;
251 public final Collection<Path> classpath = new ArrayList<>();
253 public Module(Path file) throws IOException {
255 try(Compilation c = new Compilation()) {
257 for(Path cp : classpath)
260 throw(new CompilationException(file, c.output()));
261 ClassLoader parent = Compiler.class.getClassLoader();
262 if(!classpath.isEmpty()) {
263 Collection<ClassLoader> libs = new ArrayList<>();
264 for(Path cp : classpath)
265 libs.add(libloader(cp));
266 parent = new LibClassLoader(parent, libs);
268 code = new BufferedClassLoader(parent, c.classes());
272 public void split(Compilation c) throws IOException {
273 StringBuilder head = new StringBuilder();
274 BufferedWriter cur = null;
275 try(BufferedReader fp = Files.newBufferedReader(file)) {
276 for(String ln = fp.readLine(); ln != null; ln = fp.readLine()) {
277 Matcher m = libpat.matcher(ln);
279 Path lib = findlib(m.group(1));
281 throw(new CompilationException(file, Arrays.asList("no such library: " + m.group(1))));
284 m = classpat.matcher(ln);
286 String clnm = m.group(4);
287 Path sp = c.srcdir.resolve(clnm + ".java");
291 cur = Files.newBufferedWriter(sp);
295 cur.append(ln); cur.append('\n');
297 head.append(ln); head.append('\n');
308 public final Path name;
309 private FileTime mtime = null;
310 private Module mod = null;
312 private File(Path name) {
316 public void update() throws IOException {
318 FileTime mtime = Files.getLastModifiedTime(name);
319 if((this.mtime == null) || (this.mtime.compareTo(mtime) < 0)) {
320 mod = new Module(name);
326 public Module mod() {
328 throw(new RuntimeException("file has not yet been updated"));
333 public File file(Path name) {
334 synchronized(files) {
335 File ret = files.get(name);
337 files.put(name, ret = new File(name));
342 private static Compiler global = null;
343 public static Compiler get() {
345 synchronized(Compiler.class) {
347 global = new Compiler();