Initial commit.
[jagi.git] / src / jagi / fs / Compiler.java
1 package jagi.fs;
2
3 import java.util.*;
4 import java.util.regex.*;
5 import java.nio.file.*;
6 import java.nio.file.attribute.*;
7 import java.io.*;
8 import java.net.*;
9 import javax.tools.*;
10
11 public 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 }