4141@ SuppressWarnings ("StaticNonFinalField" )
4242public class CachedCompiler implements Closeable {
4343 private static final Logger LOG = LoggerFactory .getLogger (CachedCompiler .class );
44+ /** Writes to {@link System#err} */
4445 private static final PrintWriter DEFAULT_WRITER = new PrintWriter (System .err );
4546 private static final List <String > DEFAULT_OPTIONS = Arrays .asList ("-g" , "-nowarn" );
4647
@@ -57,16 +58,28 @@ public class CachedCompiler implements Closeable {
5758
5859 private final ConcurrentMap <String , JavaFileObject > javaFileObjects = new ConcurrentHashMap <>();
5960
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+ */
6065 public CachedCompiler (@ Nullable File sourceDir , @ Nullable File classDir ) {
6166 this (sourceDir , classDir , DEFAULT_OPTIONS );
6267 }
6368
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+ */
6476 public CachedCompiler (@ Nullable File sourceDir , @ Nullable File classDir , @ NotNull List <String > options ) {
6577 this .sourceDir = sourceDir ;
6678 this .classDir = classDir ;
6779 this .options = options ;
6880 }
6981
82+ @ Override
7083 public void close () {
7184 try {
7285 for (MyJavaFileManager fileManager : fileManagerMap .values ()) {
@@ -77,67 +90,58 @@ public void close() {
7790 }
7891 }
7992
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+ */
80100 public Class <?> loadFromJava (@ NotNull String className , @ NotNull String javaCode ) throws ClassNotFoundException {
81101 return loadFromJava (getClass ().getClassLoader (), className , javaCode , DEFAULT_WRITER );
82102 }
83103
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+ */
84108 public Class <?> loadFromJava (@ NotNull ClassLoader classLoader ,
85109 @ NotNull String className ,
86110 @ NotNull String javaCode ) throws ClassNotFoundException {
87111 return loadFromJava (classLoader , className , javaCode , DEFAULT_WRITER );
88112 }
89113
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 );
135123 }
136124
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+ */
137140 public Class <?> loadFromJava (@ NotNull ClassLoader classLoader ,
138141 @ NotNull String className ,
139142 @ NotNull String javaCode ,
140- @ Nullable PrintWriter writer ) throws ClassNotFoundException {
143+ @ Nullable PrintWriter writer ,
144+ @ Nullable DiagnosticListener <? super JavaFileObject > diagnosticListener ) throws ClassNotFoundException {
141145 Class <?> clazz = null ;
142146 Map <String , Class <?>> loadedClasses ;
143147 synchronized (loadedClassesMap ) {
@@ -147,17 +151,29 @@ public Class<?> loadFromJava(@NotNull ClassLoader classLoader,
147151 else
148152 clazz = loadedClasses .get (className );
149153 }
150- PrintWriter printWriter = ( writer == null ? DEFAULT_WRITER : writer );
154+
151155 if (clazz != null )
152156 return clazz ;
153157
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+
154170 MyJavaFileManager fileManager = fileManagerMap .get (classLoader );
155171 if (fileManager == null ) {
156172 StandardJavaFileManager standardJavaFileManager = s_compiler .getStandardFileManager (null , null , null );
157173 fileManager = getFileManager (standardJavaFileManager );
158174 fileManagerMap .put (classLoader , fileManager );
159175 }
160- final Map <String , byte []> compiled = compileFromJava (className , javaCode , printWriter , fileManager );
176+ final Map <String , byte []> compiled = compileFromJava (className , javaCode , printWriter , diagnosticListener , fileManager );
161177 for (Map .Entry <String , byte []> entry : compiled .entrySet ()) {
162178 String className2 = entry .getKey ();
163179 synchronized (loadedClassesMap ) {
@@ -191,6 +207,42 @@ public Class<?> loadFromJava(@NotNull ClassLoader classLoader,
191207 return clazz ;
192208 }
193209
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+
194246 private @ NotNull MyJavaFileManager getFileManager (StandardJavaFileManager fm ) {
195247 return fileManagerOverride != null
196248 ? fileManagerOverride .apply (fm )
0 commit comments