Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions src/main/java/net/openhft/compiler/MyJavaFileManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
* Custom JavaFileManager that stores compiled class files in memory and exposes
* them as byte arrays, while delegating unresolved operations to a wrapped
* StandardJavaFileManager.
*/
public class MyJavaFileManager implements JavaFileManager {
private static final Logger LOG = LoggerFactory.getLogger(MyJavaFileManager.class);
private final static Unsafe unsafe;
private static final long OVERRIDE_OFFSET;

// Unsafe sets AccessibleObject.override for speed and JDK-9+ compatibility
static {
long offset;
try {
Expand All @@ -67,18 +73,35 @@ public class MyJavaFileManager implements JavaFileManager {
// synchronizing due to ConcurrentModificationException
private final Map<String, CloseableByteArrayOutputStream> buffers = Collections.synchronizedMap(new LinkedHashMap<>());

/**
* Create a file manager that delegates to the provided instance while
* keeping compiled class bytes in memory.
*
* @param fileManager the underlying file manager to delegate to
*/
public MyJavaFileManager(StandardJavaFileManager fileManager) {
this.fileManager = fileManager;
}

// Apparently, this method might not be thread-safe.
// See https://github.com/OpenHFT/Java-Runtime-Compiler/issues/85
/**
* Invoke {@code listLocationsForModules} reflectively if available.
* This method synchronises on the current instance as some JDK
* implementations are not thread-safe.
*
* @param location the location whose modules are requested
* @return the module locations or an empty iterable
*/
public synchronized Iterable<Set<Location>> listLocationsForModules(final Location location) {
return invokeNamedMethodIfAvailable(location, "listLocationsForModules");
}

// Apparently, this method might not be thread-safe.
// See https://github.com/OpenHFT/Java-Runtime-Compiler/issues/85
/**
* Reflectively call {@code inferModuleName} if present on the delegate.
* As above, the call is synchronised for safety on older JDKs.
*
* @param location the location to inspect
* @return the inferred module name or {@code null}
*/
public synchronized String inferModuleName(final Location location) {
return invokeNamedMethodIfAvailable(location, "inferModuleName");
}
Expand Down Expand Up @@ -107,6 +130,10 @@ public boolean hasLocation(Location location) {
return fileManager.hasLocation(location);
}

/**
* Return a JavaFileObject backed by the in-memory buffer when the caller
* requests a class that has just been compiled to {@link StandardLocation#CLASS_OUTPUT}.
*/
public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {

if (location == StandardLocation.CLASS_OUTPUT) {
Expand All @@ -129,6 +156,10 @@ public InputStream openInputStream() {
return fileManager.getJavaFileForInput(location, className, kind);
}

/**
* Store compiled class bytes in the internal buffer and return a sink
* that writes into it.
*/
@NotNull
public JavaFileObject getJavaFileForOutput(Location location, final String className, Kind kind, FileObject sibling) {
return new SimpleJavaFileObject(URI.create(className), kind) {
Expand All @@ -138,7 +169,7 @@ public OutputStream openOutputStream() {
CloseableByteArrayOutputStream baos = new CloseableByteArrayOutputStream();

// Reads from getAllBuffers() should be repeatable:
// let's ignore compile result in case compilation of this class was triggered before
// ignore compile result in case compilation of this class was triggered before
buffers.putIfAbsent(className, baos);

return baos;
Expand Down Expand Up @@ -166,10 +197,19 @@ public int isSupportedOption(String option) {
return fileManager.isSupportedOption(option);
}

/**
* Remove all compiled class data from memory.
*/
public void clearBuffers() {
buffers.clear();
}

/**
* Collect all compiled class buffers, blocking until previous compilation
* runs finish.
*
* @return a map of class name to bytecode
*/
@NotNull
public Map<String, byte[]> getAllBuffers() {
Map<String, byte[]> ret = new LinkedHashMap<>(buffers.size() * 2);
Expand Down Expand Up @@ -203,6 +243,10 @@ public Map<String, byte[]> getAllBuffers() {
return ret;
}

/**
* Invoke a method by name on the delegate if it exists, using {@link Unsafe}
* to bypass accessibility checks when required.
*/
@SuppressWarnings("unchecked")
private <T> T invokeNamedMethodIfAvailable(final Location location, final String name) {
final Method[] methods = fileManager.getClass().getDeclaredMethods();
Expand Down