Skip to content

Commit e3c49a0

Browse files
committed
Support providing DiagnosticListener for compiling
1 parent 38ba24f commit e3c49a0

File tree

3 files changed

+258
-85
lines changed

3 files changed

+258
-85
lines changed

src/main/java/net/openhft/compiler/CachedCompiler.java

+100-48
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
@SuppressWarnings("StaticNonFinalField")
4242
public 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)

src/main/java/net/openhft/compiler/CompilerUtils.java

+10-14
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import java.lang.reflect.Field;
3434
import java.lang.reflect.InvocationTargetException;
3535
import java.lang.reflect.Method;
36-
import java.nio.charset.Charset;
36+
import java.nio.charset.StandardCharsets;
3737
import java.util.Arrays;
3838

3939
/**
@@ -42,11 +42,15 @@
4242
public enum CompilerUtils {
4343
; // none
4444
public static final boolean DEBUGGING = isDebug();
45+
/**
46+
* Singleton {@link CachedCompiler}. Uses default {@code javac} options of
47+
* {@link CachedCompiler#CachedCompiler(File, File)}, and does not write {@code .java}
48+
* source files and {@code .class} files to the file system.
49+
*/
4550
public static final CachedCompiler CACHED_COMPILER = new CachedCompiler(null, null);
4651

4752
private static final Logger LOGGER = LoggerFactory.getLogger(CompilerUtils.class);
4853
private static final Method DEFINE_CLASS_METHOD;
49-
private static final Charset UTF_8 = Charset.forName("UTF-8");
5054
private static final String JAVA_CLASS_PATH = "java.class.path";
5155
static JavaCompiler s_compiler;
5256
static StandardJavaFileManager s_standardJavaFileManager;
@@ -160,7 +164,7 @@ public static void defineClass(@NotNull String className, @NotNull byte[] bytes)
160164
*/
161165
public static Class<?> defineClass(@Nullable ClassLoader classLoader, @NotNull String className, @NotNull byte[] bytes) {
162166
try {
163-
return (Class) DEFINE_CLASS_METHOD.invoke(classLoader, className, bytes, 0, bytes.length);
167+
return (Class<?>) DEFINE_CLASS_METHOD.invoke(classLoader, className, bytes, 0, bytes.length);
164168
} catch (IllegalAccessException e) {
165169
throw new AssertionError(e);
166170
} catch (InvocationTargetException e) {
@@ -173,7 +177,7 @@ private static String readText(@NotNull String resourceName) throws IOException
173177
if (resourceName.startsWith("="))
174178
return resourceName.substring(1);
175179
StringWriter sw = new StringWriter();
176-
Reader isr = new InputStreamReader(getInputStream(resourceName), UTF_8);
180+
Reader isr = new InputStreamReader(getInputStream(resourceName), StandardCharsets.UTF_8);
177181
try {
178182
char[] chars = new char[8 * 1024];
179183
int len;
@@ -187,11 +191,7 @@ private static String readText(@NotNull String resourceName) throws IOException
187191

188192
@NotNull
189193
private static String decodeUTF8(@NotNull byte[] bytes) {
190-
try {
191-
return new String(bytes, UTF_8.name());
192-
} catch (UnsupportedEncodingException e) {
193-
throw new AssertionError(e);
194-
}
194+
return new String(bytes, StandardCharsets.UTF_8);
195195
}
196196

197197
@Nullable
@@ -230,11 +230,7 @@ public static boolean writeText(@NotNull File file, @NotNull String text) {
230230

231231
@NotNull
232232
private static byte[] encodeUTF8(@NotNull String text) {
233-
try {
234-
return text.getBytes(UTF_8.name());
235-
} catch (UnsupportedEncodingException e) {
236-
throw new AssertionError(e);
237-
}
233+
return text.getBytes(StandardCharsets.UTF_8);
238234
}
239235

240236
public static boolean writeBytes(@NotNull File file, @NotNull byte[] bytes) {

0 commit comments

Comments
 (0)