Commit | Line | Data |
---|---|---|
49ccd711 FT |
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 | } |