41
41
@ SuppressWarnings ("StaticNonFinalField" )
42
42
public class CachedCompiler implements Closeable {
43
43
private static final Logger LOG = LoggerFactory .getLogger (CachedCompiler .class );
44
+ /** Writes to {@link System#err} */
44
45
private static final PrintWriter DEFAULT_WRITER = new PrintWriter (System .err );
45
46
private static final List <String > DEFAULT_OPTIONS = Arrays .asList ("-g" , "-nowarn" );
46
47
@@ -57,16 +58,28 @@ public class CachedCompiler implements Closeable {
57
58
58
59
private final ConcurrentMap <String , JavaFileObject > javaFileObjects = new ConcurrentHashMap <>();
59
60
61
+ /**
62
+ * Delegates to {@link #CachedCompiler(File, File, List)} with default {@code javac} compilation
63
+ * options {@code -g} (generate debug information) and {@code -nowarn}.
64
+ */
60
65
public CachedCompiler (@ Nullable File sourceDir , @ Nullable File classDir ) {
61
66
this (sourceDir , classDir , DEFAULT_OPTIONS );
62
67
}
63
68
69
+ /**
70
+ * @param sourceDir where to write {@code .java} source code files to be compiled; {@code null}
71
+ * to not write them to the file system
72
+ * @param classDir where to write compiled {@code .class} files; {@code null} to not write them
73
+ * to the file system
74
+ * @param options {@code javac} compilation options
75
+ */
64
76
public CachedCompiler (@ Nullable File sourceDir , @ Nullable File classDir , @ NotNull List <String > options ) {
65
77
this .sourceDir = sourceDir ;
66
78
this .classDir = classDir ;
67
79
this .options = options ;
68
80
}
69
81
82
+ @ Override
70
83
public void close () {
71
84
try {
72
85
for (MyJavaFileManager fileManager : fileManagerMap .values ()) {
@@ -77,67 +90,58 @@ public void close() {
77
90
}
78
91
}
79
92
93
+ /**
94
+ * Delegates to {@link #loadFromJava(ClassLoader, String, String, PrintWriter, DiagnosticListener)}.
95
+ * <ul>
96
+ * <li>The class loader of {@link CachedCompiler} is used for defining and loading the class
97
+ * <li>Only error diagnostics are collected, and are written to {@link System#err}
98
+ * </ul>
99
+ */
80
100
public Class <?> loadFromJava (@ NotNull String className , @ NotNull String javaCode ) throws ClassNotFoundException {
81
101
return loadFromJava (getClass ().getClassLoader (), className , javaCode , DEFAULT_WRITER );
82
102
}
83
103
104
+ /**
105
+ * Delegates to {@link #loadFromJava(ClassLoader, String, String, PrintWriter, DiagnosticListener)}.
106
+ * Only error diagnostics are collected, and are written to {@link System#err}.
107
+ */
84
108
public Class <?> loadFromJava (@ NotNull ClassLoader classLoader ,
85
109
@ NotNull String className ,
86
110
@ NotNull String javaCode ) throws ClassNotFoundException {
87
111
return loadFromJava (classLoader , className , javaCode , DEFAULT_WRITER );
88
112
}
89
113
90
- @ NotNull
91
- Map <String , byte []> compileFromJava (@ NotNull String className , @ NotNull String javaCode , MyJavaFileManager fileManager ) {
92
- return compileFromJava (className , javaCode , DEFAULT_WRITER , fileManager );
93
- }
94
-
95
- @ NotNull
96
- Map <String , byte []> compileFromJava (@ NotNull String className ,
97
- @ NotNull String javaCode ,
98
- final @ NotNull PrintWriter writer ,
99
- MyJavaFileManager fileManager ) {
100
- Iterable <? extends JavaFileObject > compilationUnits ;
101
- if (sourceDir != null ) {
102
- String filename = className .replaceAll ("\\ ." , '\\' + File .separator ) + ".java" ;
103
- File file = new File (sourceDir , filename );
104
- writeText (file , javaCode );
105
- if (s_standardJavaFileManager == null )
106
- s_standardJavaFileManager = s_compiler .getStandardFileManager (null , null , null );
107
- compilationUnits = s_standardJavaFileManager .getJavaFileObjects (file );
108
-
109
- } else {
110
- javaFileObjects .put (className , new JavaSourceFromString (className , javaCode ));
111
- compilationUnits = new ArrayList <>(javaFileObjects .values ()); // To prevent CME from compiler code
112
- }
113
- // reuse the same file manager to allow caching of jar files
114
- boolean ok = s_compiler .getTask (writer , fileManager , new DiagnosticListener <JavaFileObject >() {
115
- @ Override
116
- public void report (Diagnostic <? extends JavaFileObject > diagnostic ) {
117
- if (diagnostic .getKind () == Diagnostic .Kind .ERROR ) {
118
- writer .println (diagnostic );
119
- }
120
- }
121
- }, options , null , compilationUnits ).call ();
122
-
123
- if (!ok ) {
124
- // compilation error, so we want to exclude this file from future compilation passes
125
- if (sourceDir == null )
126
- javaFileObjects .remove (className );
127
-
128
- // nothing to return due to compiler error
129
- return Collections .emptyMap ();
130
- } else {
131
- Map <String , byte []> result = fileManager .getAllBuffers ();
132
-
133
- return result ;
134
- }
114
+ /**
115
+ * Delegates to {@link #loadFromJava(ClassLoader, String, String, PrintWriter, DiagnosticListener)}.
116
+ * Only error diagnostics are collected, and are written to {@code writer}.
117
+ */
118
+ public Class <?> loadFromJava (@ NotNull ClassLoader classLoader ,
119
+ @ NotNull String className ,
120
+ @ NotNull String javaCode ,
121
+ @ Nullable PrintWriter writer ) throws ClassNotFoundException {
122
+ return loadFromJava (classLoader , className , javaCode , writer , null );
135
123
}
136
124
125
+ /**
126
+ * Gets a previously compiled and loaded class, or compiles the given Java code and
127
+ * loads the class.
128
+ *
129
+ * @param classLoader class loader for defining and loading the class
130
+ * @param className binary name of the class to load, for example {@code com.example.MyClass$Nested}
131
+ * @param javaCode Java code to compile, in case the class had not been compiled and loaded before
132
+ * @param writer writer for compilation information and diagnostics (should be thread-safe);
133
+ * when {@code null} defaults to writing to {@link System#err}
134
+ * @param diagnosticListener listener for diagnostics emitted by the compiler (should be thread-safe);
135
+ * when {@code null}, error diagnostics are written to the {@code writer}, other diagnostics are ignored
136
+ * @return the loaded class
137
+ * @throws ClassNotFoundException if compiling or loading the class failed; inspect {@code writer} or
138
+ * {@code diagnosticListener} for additional details
139
+ */
137
140
public Class <?> loadFromJava (@ NotNull ClassLoader classLoader ,
138
141
@ NotNull String className ,
139
142
@ NotNull String javaCode ,
140
- @ Nullable PrintWriter writer ) throws ClassNotFoundException {
143
+ @ Nullable PrintWriter writer ,
144
+ @ Nullable DiagnosticListener <? super JavaFileObject > diagnosticListener ) throws ClassNotFoundException {
141
145
Class <?> clazz = null ;
142
146
Map <String , Class <?>> loadedClasses ;
143
147
synchronized (loadedClassesMap ) {
@@ -147,17 +151,29 @@ public Class<?> loadFromJava(@NotNull ClassLoader classLoader,
147
151
else
148
152
clazz = loadedClasses .get (className );
149
153
}
150
- PrintWriter printWriter = ( writer == null ? DEFAULT_WRITER : writer );
154
+
151
155
if (clazz != null )
152
156
return clazz ;
153
157
158
+ PrintWriter printWriter = writer == null ? DEFAULT_WRITER : writer ;
159
+ if (diagnosticListener == null ) {
160
+ diagnosticListener = new DiagnosticListener <JavaFileObject >() {
161
+ @ Override
162
+ public void report (Diagnostic <? extends JavaFileObject > diagnostic ) {
163
+ if (diagnostic .getKind () == Diagnostic .Kind .ERROR ) {
164
+ printWriter .println (diagnostic );
165
+ }
166
+ }
167
+ };
168
+ }
169
+
154
170
MyJavaFileManager fileManager = fileManagerMap .get (classLoader );
155
171
if (fileManager == null ) {
156
172
StandardJavaFileManager standardJavaFileManager = s_compiler .getStandardFileManager (null , null , null );
157
173
fileManager = getFileManager (standardJavaFileManager );
158
174
fileManagerMap .put (classLoader , fileManager );
159
175
}
160
- final Map <String , byte []> compiled = compileFromJava (className , javaCode , printWriter , fileManager );
176
+ final Map <String , byte []> compiled = compileFromJava (className , javaCode , printWriter , diagnosticListener , fileManager );
161
177
for (Map .Entry <String , byte []> entry : compiled .entrySet ()) {
162
178
String className2 = entry .getKey ();
163
179
synchronized (loadedClassesMap ) {
@@ -191,6 +207,42 @@ public Class<?> loadFromJava(@NotNull ClassLoader classLoader,
191
207
return clazz ;
192
208
}
193
209
210
+ @ NotNull
211
+ Map <String , byte []> compileFromJava (@ NotNull String className ,
212
+ @ NotNull String javaCode ,
213
+ @ NotNull PrintWriter writer ,
214
+ @ NotNull DiagnosticListener <? super JavaFileObject > diagnosticListener ,
215
+ MyJavaFileManager fileManager ) {
216
+ Iterable <? extends JavaFileObject > compilationUnits ;
217
+ if (sourceDir != null ) {
218
+ String filename = className .replaceAll ("\\ ." , '\\' + File .separator ) + ".java" ;
219
+ File file = new File (sourceDir , filename );
220
+ writeText (file , javaCode );
221
+ if (s_standardJavaFileManager == null )
222
+ s_standardJavaFileManager = s_compiler .getStandardFileManager (null , null , null );
223
+ compilationUnits = s_standardJavaFileManager .getJavaFileObjects (file );
224
+
225
+ } else {
226
+ javaFileObjects .put (className , new JavaSourceFromString (className , javaCode ));
227
+ compilationUnits = new ArrayList <>(javaFileObjects .values ()); // To prevent CME from compiler code
228
+ }
229
+ // reuse the same file manager to allow caching of jar files
230
+ boolean ok = s_compiler .getTask (writer , fileManager , diagnosticListener , options , null , compilationUnits ).call ();
231
+
232
+ if (!ok ) {
233
+ // compilation error, so we want to exclude this file from future compilation passes
234
+ if (sourceDir == null )
235
+ javaFileObjects .remove (className );
236
+
237
+ // nothing to return due to compiler error
238
+ return Collections .emptyMap ();
239
+ } else {
240
+ Map <String , byte []> result = fileManager .getAllBuffers ();
241
+
242
+ return result ;
243
+ }
244
+ }
245
+
194
246
private @ NotNull MyJavaFileManager getFileManager (StandardJavaFileManager fm ) {
195
247
return fileManagerOverride != null
196
248
? fileManagerOverride .apply (fm )
0 commit comments