import jagi.*;
import java.util.*;
import java.util.regex.*;
+import java.util.logging.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.*;
+import java.net.*;
public class Compiler {
- private final Map<Path, Module> modules = new HashMap<>();
+ private static final Logger log = Logger.getLogger("jagi-fs");
+ private static final Path cwd = Paths.get("").toAbsolutePath();
+ private final Map<Path, File> files = new HashMap<>();
+ private final Map<Path, ClassLoader> libs = new HashMap<>();
+ private final Collection<Path> searchpath = new ArrayList<>();
+
+ public Compiler() {
+ String syspath = System.getenv("PATH");
+ if(syspath != null) {
+ String sep = java.io.File.pathSeparator;
+ int p1 = 0, p2 = syspath.indexOf(sep);
+ do {
+ String el;
+ if(p2 >= 0) {
+ el = syspath.substring(p1, p2);
+ p1 = p2 + sep.length();
+ p2 = syspath.indexOf(sep, p1);
+ } else {
+ el =syspath.substring(p1);
+ }
+ try {
+ Path p = Paths.get(el);
+ if(p.getParent() != null)
+ searchpath.add(p.getParent().resolve("share").resolve("java"));
+ } catch(InvalidPathException e) {
+ continue;
+ }
+ } while(p2 >= 0);
+ }
+ String proppath = System.getProperty("jagi.search-path");
+ if(proppath != null) {
+ for(String el : proppath.split(":")) {
+ try {
+ searchpath.add(Paths.get(el));
+ } catch(InvalidPathException e) {
+ continue;
+ }
+ }
+ }
+ }
public static class ClassOutput {
public final String name;
}
public static class Compilation implements AutoCloseable {
- private final Path dir, srcdir, outdir;
+ public final Path dir, srcdir, outdir, libdir;
private final List<Path> infiles = new ArrayList<>();
+ private final List<Path> classpath = new ArrayList<>();
private List<String> output = null;
+ private boolean haslib;
public Compilation() throws IOException {
dir = Files.createTempDirectory("javac");
srcdir = dir.resolve("src");
outdir = dir.resolve("out");
+ libdir = dir.resolve("lib");
Files.createDirectory(srcdir);
Files.createDirectory(outdir);
}
infiles.add(p);
}
- private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
- public void split(Path infile) throws IOException {
- StringBuilder head = new StringBuilder();
- BufferedWriter cur = null;
- try(BufferedReader fp = Files.newBufferedReader(infile)) {
- 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 = srcdir.resolve(clnm + ".java");
- 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 void classpath(Path p) {
+ classpath.add(p);
+ }
+
+ public void addlib(String clnm, byte[] contents) throws IOException {
+ if(!haslib) {
+ Files.createDirectory(libdir);
+ classpath(libdir);
+ haslib = true;
+ }
+ Path p = libdir;
+ int p1 = 0;
+ while(true) {
+ int p2 = clnm.indexOf('.', p1);
+ if(p2 < 0)
+ break;
+ p = p.resolve(clnm.substring(p1, p2));
+ if(!Files.isDirectory(p))
+ Files.createDirectory(p);
+ p1 = p2 + 1;
}
+ p = p.resolve(clnm.substring(p1) + ".class");
+ Files.write(p, contents);
+ }
+
+ public void addlib(Map<String, byte[]> classes) throws IOException {
+ for(Map.Entry<String, byte[]> ent : classes.entrySet())
+ addlib(ent.getKey(), ent.getValue());
}
public boolean compile() throws IOException {
args.add("javac");
args.add("-d");
args.add(outdir.toString());
+ if(!classpath.isEmpty()) {
+ StringBuilder buf = new StringBuilder();
+ for(Path cp : classpath) {
+ if(buf.length() > 0)
+ buf.append(":");
+ buf.append(cp.toString());
+ }
+ args.add("-cp");
+ args.add(buf.toString());
+ }
for(Path p : infiles)
args.add(p.toString());
ProcessBuilder cmd = new ProcessBuilder(args);
}
}
- public static Collection<ClassOutput> compile(Path file) throws IOException {
- try(Compilation c = new Compilation()) {
- c.split(file);
- if(!c.compile())
- throw(new CompilationException(file, c.output()));
- return(c.classes());
- }
- }
-
public static class BufferedClassLoader extends ClassLoader {
public final Map<String, byte[]> contents;
- public BufferedClassLoader(Collection<ClassOutput> contents) {
+ public BufferedClassLoader(ClassLoader parent, Collection<ClassOutput> contents) {
+ super(parent);
this.contents = new HashMap<>();
for(ClassOutput clc : contents)
this.contents.put(clc.name, clc.buf.toByteArray());
}
}
- public static class Module {
+ public static class LibClassLoader extends ClassLoader {
+ private final ClassLoader[] classpath;
+
+ public LibClassLoader(ClassLoader parent, Collection<ClassLoader> classpath) {
+ super(parent);
+ this.classpath = classpath.toArray(new ClassLoader[0]);
+ }
+
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ for(ClassLoader lib : classpath) {
+ try {
+ return(lib.loadClass(name));
+ } catch(ClassNotFoundException e) {}
+ }
+ throw(new ClassNotFoundException("Could not find " + name + " in any of " + Arrays.asList(classpath).toString()));
+ }
+ }
+
+ public ClassLoader libloader(Path p) {
+ synchronized(libs) {
+ ClassLoader ret = libs.get(p);
+ if(ret == null) {
+ try {
+ libs.put(p, ret = new URLClassLoader(new URL[] {p.toUri().toURL()}));
+ } catch(MalformedURLException e) {
+ throw(new RuntimeException(e));
+ }
+ }
+ return(ret);
+ }
+ }
+
+ private Path findlib(String nm) {
+ try {
+ Path p = Paths.get(nm);
+ if(Files.isRegularFile(p))
+ return(p);
+ } catch(InvalidPathException e) {
+ }
+ for(Path dir : searchpath) {
+ Path jar = dir.resolve(nm + ".jar");
+ if(Files.isRegularFile(jar))
+ return(jar);
+ }
+ return(null);
+ }
+
+ private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
+ private static final Pattern libpat = Pattern.compile("\\$use\\s*:\\s*(\\S+)");
+ private static final Pattern incpat = Pattern.compile("\\$include\\s*:\\s*(\\S+)");
+ public class Module {
public final Path file;
- private FileTime mtime = null;
- private ClassLoader code = null;
+ public final BufferedClassLoader code;
+ public final Collection<Path> classpath = new ArrayList<>();
+ public final Collection<Module> include = new ArrayList<>();
- private Module(Path file) {
+ public Module(Path file) throws IOException {
this.file = file;
+ try(Compilation c = new Compilation()) {
+ split(c);
+ for(Path cp : classpath)
+ c.classpath(cp);
+ if(!c.compile())
+ throw(new CompilationException(file, c.output()));
+ ClassLoader parent = Compiler.class.getClassLoader();
+ if(!classpath.isEmpty() || !include.isEmpty()) {
+ Collection<ClassLoader> libs = new ArrayList<>();
+ for(Path cp : classpath)
+ libs.add(libloader(cp));
+ for(Module mod : include)
+ libs.add(mod.code);
+ parent = new LibClassLoader(parent, libs);
+ }
+ code = new BufferedClassLoader(parent, c.classes());
+ }
+ }
+
+ 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 = libpat.matcher(ln);
+ if(m.find()) {
+ Path lib = findlib(m.group(1));
+ if(lib == null)
+ throw(new CompilationException(file, Arrays.asList("no such library: " + m.group(1))));
+ classpath.add(lib);
+ }
+ m = incpat.matcher(ln);
+ if(m.find()) {
+ String nm = m.group(1);
+ File f= null;
+ if(f == null) {
+ Path p = file.resolveSibling(nm);
+ if(Files.isRegularFile(p))
+ f = file(p);
+ }
+ if(f == null) {
+ Path p = cwd.resolve(nm);
+ if(Files.isRegularFile(p))
+ f = file(p);
+ }
+ if(f == null)
+ throw(new CompilationException(file, Arrays.asList("no such file to include: " + nm)));
+ f.update();
+ c.addlib(f.mod().code.contents);
+ include.add(f.mod());
+ }
+ 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 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));
+ Module pmod = this.mod;
+ this.mod = new Module(name);
this.mtime = mtime;
+ if(pmod instanceof AutoCloseable) {
+ try {
+ ((AutoCloseable)pmod).close();
+ } catch(Exception e) {
+ log.log(Level.WARNING, String.format("Error when disposing updated module %s", pmod.file), e);
+ }
+ }
}
}
}
- 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);
}
}