Skip to content

Commit 2344131

Browse files
committed
Initial commit of the Compiler class
1 parent 62db52c commit 2344131

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed

src/javaxt/orm/Compiler.java

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package javaxt.orm;
2+
3+
import java.io.*;
4+
import java.net.*;
5+
import java.util.*;
6+
import javax.tools.*;
7+
import java.nio.charset.Charset;
8+
9+
import static javaxt.utils.Console.console;
10+
11+
//******************************************************************************
12+
//** Compiler
13+
//******************************************************************************
14+
/**
15+
* Used to compile ORM models into Java classes. The classes are ephemeral
16+
* meaning they are not saved anywhere on disk (no class files or jar files).
17+
* Instead the classes are stored in memory and are tied to the lifecycle of
18+
* the JVM.
19+
*
20+
******************************************************************************/
21+
22+
public class Compiler {
23+
24+
private Class[] classes;
25+
private HashMap<String, SimpleJavaFileObject> outputFiles;
26+
private URLClassLoader urlClassLoader;
27+
private DiagnosticListener<JavaFileObject> listener = null;
28+
private Locale locale = null;
29+
private Charset charset = Charset.defaultCharset();
30+
31+
32+
//**************************************************************************
33+
//** Constructor
34+
//**************************************************************************
35+
public Compiler(Model[] models) throws Exception {
36+
37+
if (models==null || models.length==0){
38+
classes = new Class[0];
39+
}
40+
else{
41+
42+
outputFiles = new HashMap<>();
43+
urlClassLoader = getClassLoader();
44+
45+
46+
47+
//Convert models into an ArrayList
48+
ArrayList<Model> arr = new ArrayList<>();
49+
for (Model model : models){
50+
arr.add(model);
51+
}
52+
53+
54+
//Compile classes
55+
ArrayList<Class> classes = new ArrayList<>();
56+
int numErrors = 0;
57+
while (classes.size()<models.length){
58+
try{
59+
Model model = arr.get(0);
60+
classes.add(compile(model));
61+
arr.remove(0);
62+
}
63+
catch(Exception e){
64+
numErrors++;
65+
66+
//The following logic assumes that the compile error is due
67+
//to an ordering issue where one class depends on another
68+
//but the dependency hasn't been compiled yet. So we'll
69+
//shuffle the list of models and try again.
70+
71+
if (arr.size()==1) throw e;
72+
String firstClass = getClassName(arr.get(0));
73+
while (true){
74+
Collections.shuffle(arr);
75+
String className = getClassName(arr.get(0));
76+
if (!className.equals(firstClass)) break;
77+
}
78+
}
79+
80+
81+
//Safety switch
82+
if (numErrors>(models.length*models.length)){
83+
throw new Exception("Failed to compile " + getClassName(arr.get(0)));
84+
}
85+
}
86+
87+
88+
//Convert the class list into an array. Sorts the classes to match
89+
//the order of the input models.
90+
this.classes = new Class[classes.size()];
91+
for (int i=0; i<models.length; i++){
92+
String className = getClassName(models[i]);
93+
for (Class c : classes){
94+
String name = c.getPackageName() + "." + c.getSimpleName();
95+
if (name.equals(className)){
96+
this.classes[i] = c;
97+
break;
98+
}
99+
}
100+
}
101+
}
102+
}
103+
104+
105+
//**************************************************************************
106+
//** getClasses
107+
//**************************************************************************
108+
/** Returns classes generated by the compiler for each model
109+
*/
110+
public Class[] getClasses(){
111+
return classes;
112+
}
113+
114+
115+
116+
//**************************************************************************
117+
//** compile
118+
//**************************************************************************
119+
/** Used to compile a model and return a class
120+
*/
121+
private Class compile(Model model) throws Exception {
122+
123+
JavaCompiler c = ToolProvider.getSystemJavaCompiler();
124+
String className = getClassName(model);
125+
126+
127+
//Create input file object
128+
SimpleJavaFileObject src = new SimpleJavaFileObject(
129+
URI.create("string:///" + model.getName() + ".java"),
130+
JavaFileObject.Kind.SOURCE) {
131+
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
132+
return model.getJavaCode();
133+
}
134+
};
135+
136+
137+
138+
//Create output file object
139+
SimpleJavaFileObject cls = new SimpleJavaFileObject(
140+
URI.create("runtime:///" + model.getName() + ".class"),
141+
JavaFileObject.Kind.CLASS) {
142+
private ByteArrayOutputStream out = new ByteArrayOutputStream();
143+
public OutputStream openOutputStream() throws IOException {
144+
//console.log("openOutputStream");
145+
return out;
146+
}
147+
public InputStream openInputStream() throws IOException {
148+
//console.log("openInputStream");
149+
return new ByteArrayInputStream(out.toByteArray());
150+
}
151+
};
152+
outputFiles.put(className, cls);
153+
154+
155+
156+
//Create in-memory file manager
157+
StandardJavaFileManager fm = c.getStandardFileManager(listener, locale, charset);
158+
JavaFileManager fileManager = new ForwardingJavaFileManager(fm) {
159+
public JavaFileObject getJavaFileForOutput(
160+
JavaFileManager.Location location, String className,
161+
JavaFileObject.Kind kind, FileObject sibling) throws IOException {
162+
return outputFiles.get(className);
163+
}
164+
165+
166+
public Iterable list(JavaFileManager.Location location,
167+
String packageName, Set kinds, boolean recurse) throws IOException {
168+
169+
if (location==StandardLocation.CLASS_PATH){
170+
ArrayList<SimpleJavaFileObject> arr = new ArrayList<>();
171+
Iterator<String> it = outputFiles.keySet().iterator();
172+
while (it.hasNext()){
173+
String name = it.next();
174+
if (name.startsWith(packageName + ".")){
175+
if (!name.equals(className)){ //don't include the current file since it hasn't been compiled yet
176+
arr.add(outputFiles.get(name));
177+
}
178+
}
179+
}
180+
if (!arr.isEmpty()) return arr;
181+
}
182+
183+
return super.list(location, packageName, kinds, recurse);
184+
}
185+
186+
187+
188+
public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) {
189+
190+
if (location==StandardLocation.CLASS_PATH){
191+
Iterator<String> it = outputFiles.keySet().iterator();
192+
while (it.hasNext()){
193+
String name = it.next();
194+
SimpleJavaFileObject f = outputFiles.get(name);
195+
if (file==f) return name;
196+
}
197+
}
198+
199+
return super.inferBinaryName(location, file);
200+
}
201+
};
202+
203+
204+
205+
206+
//Compile class
207+
JavaCompiler.CompilationTask task = c.getTask(
208+
null,
209+
fileManager,
210+
listener,
211+
Collections.emptySet(),
212+
Collections.emptySet(),
213+
Collections.singleton(src)
214+
);
215+
216+
if (task.call()) {
217+
return urlClassLoader.loadClass(className);
218+
}
219+
else{
220+
throw new Exception("Failed to compile " + className);
221+
}
222+
}
223+
224+
225+
//**************************************************************************
226+
//** getClassLoader
227+
//**************************************************************************
228+
/** Returns a custom class loader used to find classes created by this class
229+
*/
230+
private URLClassLoader getClassLoader(){
231+
return new URLClassLoader(new URL[0]){
232+
protected Class<?> findClass(final String name) throws ClassNotFoundException {
233+
234+
235+
SimpleJavaFileObject f = outputFiles.get(name);
236+
if (f!=null){
237+
try (InputStream is = f.openInputStream()) {
238+
239+
240+
ByteArrayOutputStream out = new ByteArrayOutputStream();
241+
242+
int x;
243+
byte[] buffer = new byte[256];
244+
while ((x = is.read(buffer, 0, buffer.length)) != -1) {
245+
out.write(buffer, 0, x);
246+
}
247+
248+
out.flush();
249+
byte[] classBytes = out.toByteArray();
250+
return defineClass(name, classBytes, 0, classBytes.length);
251+
252+
}
253+
catch(Exception e){
254+
e.printStackTrace();
255+
}
256+
}
257+
258+
return super.findClass(name);
259+
}
260+
};
261+
}
262+
263+
264+
//**************************************************************************
265+
//** getClassName
266+
//**************************************************************************
267+
private String getClassName(Model model){
268+
return model.getPackageName() + "." + model.getName();
269+
}
270+
271+
272+
}

0 commit comments

Comments
 (0)