Initial commit.
[jagi.git] / src / jagi / fs / Compiler.java
CommitLineData
49ccd711
FT
1package jagi.fs;
2
3import java.util.*;
4import java.util.regex.*;
5import java.nio.file.*;
6import java.nio.file.attribute.*;
7import java.io.*;
8import java.net.*;
9import javax.tools.*;
10
11public class Compiler {
12 private final Map<Path, Module> modules = new HashMap<>();
13
14 public static class FilePart extends SimpleJavaFileObject {
15 public final Path file;
16 public final String clnm;
17 public final CharSequence src;
18
19 private static URI dummyuri(Path file, String clnm) {
20 String clp = clnm.replace('.', '/') + Kind.SOURCE.extension;
21 return(URI.create(file.toUri().toString() + "!/" + clp));
22 }
23
24 public FilePart(Path file, String clnm, CharSequence src) {
25 super(dummyuri(file, clnm), Kind.SOURCE);
26 this.file = file;
27 this.clnm = clnm;
28 this.src = src;
29 }
30
31 public CharSequence getCharContent(boolean ice) {
32 return(src);
33 }
34
35 private static final Pattern classpat = Pattern.compile("^((public|abstract)\\s+)*(class|interface)\\s+(\\S+)");
36 public static Collection<FilePart> split(Path file) throws IOException {
37 Collection<FilePart> ret = new ArrayList<>();
38 StringBuilder head = new StringBuilder();
39 StringBuilder cur = null;
40 String clnm = null;
41 try(BufferedReader fp = Files.newBufferedReader(file)) {
42 for(String ln = fp.readLine(); ln != null; ln = fp.readLine()) {
43 Matcher m = classpat.matcher(ln);
44 if(m.find()) {
45 if(cur != null)
46 ret.add(new FilePart(file, clnm, cur));
47 clnm = m.group(4);
48 cur = new StringBuilder();
49 cur.append(head);
50 }
51 if(cur != null) {
52 cur.append(ln); cur.append('\n');
53 } else {
54 head.append(ln); head.append('\n');
55 }
56 }
57 if(cur != null)
58 ret.add(new FilePart(file, clnm, cur));
59 }
60 return(ret);
61 }
62 }
63
64 public static class ClassOutput extends SimpleJavaFileObject {
65 public final String name;
66 private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
67
68 public ClassOutput(String name) {
69 super(URI.create("mem://" + name), Kind.CLASS);
70 this.name = name;
71 }
72
73 public OutputStream openOutputStream() {
74 return(buf);
75 }
76 }
77
78 public static class FileContext extends ForwardingJavaFileManager<JavaFileManager> {
79 public final Collection<ClassOutput> output = new ArrayList<>();
80
81 public FileContext(JavaCompiler javac) {
82 super(javac.getStandardFileManager(null, null, null));
83 }
84
85 public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) {
86 ClassOutput cl = new ClassOutput(name);
87 output.add(cl);
88 return(cl);
89 }
90 }
91
92 public static class CompilationException extends RuntimeException {
93 public final Path file;
94 private final List<Diagnostic<? extends JavaFileObject>> messages;
95
96 public CompilationException(Path file, List<Diagnostic<? extends JavaFileObject>> messages) {
97 this.file = file;
98 this.messages = messages;
99 }
100
101 public String getMessage() {
102 return(file + ": compilation failed");
103 }
104
105 public String messages() {
106 StringBuilder buf = new StringBuilder();
107 for(Diagnostic msg : messages)
108 buf.append(msg.toString() + "\n");
109 return(buf.toString());
110 }
111
112 public void printStackTrace(PrintStream out) {
113 out.print(messages());
114 super.printStackTrace(out);
115 }
116 }
117
118 public static Collection<ClassOutput> compile(Path file) throws IOException {
119 List<String> opt = Arrays.asList();
120 JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
121 if(javac == null)
122 throw(new RuntimeException("no javac present"));
123 Collection<FilePart> files;
124 files = FilePart.split(file);
125 DiagnosticCollector<JavaFileObject> out = new DiagnosticCollector<>();
126 FileContext fs = new FileContext(javac);
127 JavaCompiler.CompilationTask job = javac.getTask(null, fs, out, opt, null, files);
128 if(!job.call())
129 throw(new CompilationException(file, out.getDiagnostics()));
130 return(fs.output);
131 }
132
133 public static class BufferedClassLoader extends ClassLoader {
134 public final Map<String, byte[]> contents;
135
136 public BufferedClassLoader(Collection<ClassOutput> contents) {
137 this.contents = new HashMap<>();
138 for(ClassOutput clc : contents)
139 this.contents.put(clc.name, clc.buf.toByteArray());
140 }
141
142 public Class<?> findClass(String name) throws ClassNotFoundException {
143 byte[] c = contents.get(name);
144 if(c == null)
145 throw(new ClassNotFoundException(name));
146 return(defineClass(name, c, 0, c.length));
147 }
148 }
149
150 public static class Module {
151 public final Path file;
152 private FileTime mtime = null;
153 private ClassLoader code = null;
154
155 private Module(Path file) {
156 this.file = file;
157 }
158
159 public void update() throws IOException {
160 synchronized(this) {
161 FileTime mtime = Files.getLastModifiedTime(file);
162 if((this.mtime == null) || (this.mtime.compareTo(mtime) < 0)) {
163 code = new BufferedClassLoader(compile(file));
164 this.mtime = mtime;
165 }
166 }
167 }
168
169 public ClassLoader code() {
170 if(code == null)
171 throw(new RuntimeException("module has not yet been updated"));
172 return(code);
173 }
174 }
175
176 public Module module(Path file) {
177 synchronized(modules) {
178 Module ret = modules.get(file);
179 if(ret == null)
180 modules.put(file, ret = new Module(file));
181 return(ret);
182 }
183 }
184
185 private static Compiler global = null;
186 public static Compiler get() {
187 if(global == null) {
188 synchronized(Compiler.class) {
189 if(global == null)
190 global = new Compiler();
191 }
192 }
193 return(global);
194 }
195}