Skip to content

Add javadoc and tests #140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
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
11 changes: 5 additions & 6 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,22 @@ Class<?> aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(className, javaCode
Runnable runner = (Runnable) aClass.newInstance();
runner.run();
```

I suggest making your class implement a KnownInterface of your choice as this will allow you to call/manipulate instances of you generated class.

Another more hacky way is to use this to override a class, provided it hasn't been loaded already.
This means you can redefine an existing class and provide the methods and fields used match,
you have compiler redefine a class and code already compiled to use the class will still work.
Another more hacky way is to use this to override a class, provided it hasn't been loaded already.
This means you can redefine an existing class and provide the methods and fields used match, you have compiler redefine a class and code already compiled to use the class will still work.

== Using the CachedCompiler.

In this example, you can configure the compiler to write the files to a specific directory when you are in debug mode.

```java
private static final CachedCompiler JCC = CompilerUtils.DEBUGGING ?
new CachedCompiler(new File(parent, "src/test/java"), new File(parent, "target/compiled")) :
CompilerUtils.CACHED_COMPILER;
```

By selecting the src directory to match where your IDE looks for those files, it will allow your debugger to set into the code you have generated at runtime.

Note: you may need to delete these files if you want to regenerate them.
36 changes: 21 additions & 15 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
~ limitations under the License.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
Expand Down Expand Up @@ -75,14 +76,19 @@
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<properties>
<sonar.organization>openhft</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.organization>openhft</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties>

<build>

<plugins>
Expand Down Expand Up @@ -212,17 +218,17 @@
</build>
</profile>
</profiles>
<distributionManagement>
<repository>
<id>chronicle-enterprise-release</id>
<url>https://nexus.chronicle.software/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>chronicle-enterprise-snapshots</id>
<url>https://nexus.chronicle.software/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<distributionManagement>
<repository>
<id>chronicle-enterprise-release</id>
<url>https://nexus.chronicle.software/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>chronicle-enterprise-snapshots</id>
<url>https://nexus.chronicle.software/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>

<scm>
<url>scm:git:[email protected]:OpenHFT/Java-Runtime-Compiler.git</url>
<connection>scm:git:[email protected]:OpenHFT/Java-Runtime-Compiler.git</connection>
Expand Down
103 changes: 91 additions & 12 deletions src/main/java/net/openhft/compiler/CachedCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,35 +38,59 @@

import static net.openhft.compiler.CompilerUtils.*;

/**
* This class handles the caching and compilation of Java source code.
* It maintains a cache of loaded classes and provides methods to compile
* Java source code and load classes dynamically.
*/
@SuppressWarnings("StaticNonFinalField")
public class CachedCompiler implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(CachedCompiler.class);
private static final PrintWriter DEFAULT_WRITER = new PrintWriter(System.err);
private static final List<String> DEFAULT_OPTIONS = Arrays.asList("-g", "-nowarn");

// Map to store loaded classes, synchronized to handle concurrent access
private final Map<ClassLoader, Map<String, Class<?>>> loadedClassesMap = Collections.synchronizedMap(new WeakHashMap<>());
// Map to store file managers, synchronized to handle concurrent access
private final Map<ClassLoader, MyJavaFileManager> fileManagerMap = Collections.synchronizedMap(new WeakHashMap<>());
public Function<StandardJavaFileManager, MyJavaFileManager> fileManagerOverride;

@Nullable
private final File sourceDir;
private final File sourceDir; // Directory for source files
@Nullable
private final File classDir;
private final File classDir; // Directory for class files
@NotNull
private final List<String> options;
private final List<String> options; // Compilation options

// Map to store Java file objects for compilation
private final ConcurrentMap<String, JavaFileObject> javaFileObjects = new ConcurrentHashMap<>();

/**
* Constructor to initialize CachedCompiler with source and class directories, and default options.
*
* @param sourceDir The directory for source files
* @param classDir The directory for class files
*/
public CachedCompiler(@Nullable File sourceDir, @Nullable File classDir) {
this(sourceDir, classDir, DEFAULT_OPTIONS);
}

/**
* Constructor to initialize CachedCompiler with source and class directories, and custom options.
*
* @param sourceDir The directory for source files
* @param classDir The directory for class files
* @param options The compilation options
*/
public CachedCompiler(@Nullable File sourceDir, @Nullable File classDir, @NotNull List<String> options) {
this.sourceDir = sourceDir;
this.classDir = classDir;
this.options = options;
}

/**
* Closes the CachedCompiler and releases resources associated with the file managers.
*/
public void close() {
try {
for (MyJavaFileManager fileManager : fileManagerMap.values()) {
Expand All @@ -77,28 +101,63 @@ public void close() {
}
}

/**
* Loads a class from the provided Java source code.
*
* @param className The name of the class to be loaded
* @param javaCode The Java source code of the class
* @return The loaded class
* @throws ClassNotFoundException if the class cannot be found
*/
public Class<?> loadFromJava(@NotNull String className, @NotNull String javaCode) throws ClassNotFoundException {
return loadFromJava(getClass().getClassLoader(), className, javaCode, DEFAULT_WRITER);
}

/**
* Loads a class from the provided Java source code using the specified class loader.
*
* @param classLoader The class loader to be used
* @param className The name of the class to be loaded
* @param javaCode The Java source code of the class
* @return The loaded class
* @throws ClassNotFoundException if the class cannot be found
*/
public Class<?> loadFromJava(@NotNull ClassLoader classLoader,
@NotNull String className,
@NotNull String javaCode) throws ClassNotFoundException {
@NotNull String className,
@NotNull String javaCode) throws ClassNotFoundException {
return loadFromJava(classLoader, className, javaCode, DEFAULT_WRITER);
}

/**
* Compiles the provided Java source code and returns a map of class names to byte arrays.
*
* @param className The name of the class to be compiled
* @param javaCode The Java source code of the class
* @param fileManager The file manager to be used for compilation
* @return A map of class names to byte arrays
*/
@NotNull
Map<String, byte[]> compileFromJava(@NotNull String className, @NotNull String javaCode, MyJavaFileManager fileManager) {
return compileFromJava(className, javaCode, DEFAULT_WRITER, fileManager);
}

/**
* Compiles the provided Java source code and returns a map of class names to byte arrays.
*
* @param className The name of the class to be compiled
* @param javaCode The Java source code of the class
* @param writer The PrintWriter to be used for logging
* @param fileManager The file manager to be used for compilation
* @return A map of class names to byte arrays
*/
@NotNull
Map<String, byte[]> compileFromJava(@NotNull String className,
@NotNull String javaCode,
final @NotNull PrintWriter writer,
MyJavaFileManager fileManager) {
Iterable<? extends JavaFileObject> compilationUnits;
if (sourceDir != null) {
// Write source file to disk if sourceDir is specified
String filename = className.replaceAll("\\.", '\\' + File.separator) + ".java";
File file = new File(sourceDir, filename);
writeText(file, javaCode);
Expand All @@ -107,10 +166,11 @@ Map<String, byte[]> compileFromJava(@NotNull String className,
compilationUnits = s_standardJavaFileManager.getJavaFileObjects(file);

} else {
// Use in-memory Java file object if sourceDir is not specified
javaFileObjects.put(className, new JavaSourceFromString(className, javaCode));
compilationUnits = new ArrayList<>(javaFileObjects.values()); // To prevent CME from compiler code
}
// reuse the same file manager to allow caching of jar files
// Reuse the same file manager to allow caching of jar files
boolean ok = s_compiler.getTask(writer, fileManager, new DiagnosticListener<JavaFileObject>() {
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
Expand All @@ -121,23 +181,34 @@ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
}, options, null, compilationUnits).call();

if (!ok) {
// compilation error, so we want to exclude this file from future compilation passes
// Compilation error, so we want to exclude this file from future compilation passes
if (sourceDir == null)
javaFileObjects.remove(className);

// nothing to return due to compiler error
// Nothing to return due to compiler error
return Collections.emptyMap();
} else {
// Return compiled class byte arrays
Map<String, byte[]> result = fileManager.getAllBuffers();

return result;
}
}

/**
* Loads a class from the provided Java source code using the specified class loader and writer.
*
* @param classLoader The class loader to be used
* @param className The name of the class to be loaded
* @param javaCode The Java source code of the class
* @param writer The PrintWriter to be used for logging
* @return The loaded class
* @throws ClassNotFoundException if the class cannot be found
*/
public Class<?> loadFromJava(@NotNull ClassLoader classLoader,
@NotNull String className,
@NotNull String javaCode,
@Nullable PrintWriter writer) throws ClassNotFoundException {
@NotNull String className,
@NotNull String javaCode,
@Nullable PrintWriter writer) throws ClassNotFoundException {
Class<?> clazz = null;
Map<String, Class<?>> loadedClasses;
synchronized (loadedClassesMap) {
Expand Down Expand Up @@ -166,6 +237,7 @@ public Class<?> loadFromJava(@NotNull ClassLoader classLoader,
}
byte[] bytes = entry.getValue();
if (classDir != null) {
// Write compiled class file to disk if classDir is specified
String filename = className2.replaceAll("\\.", '\\' + File.separator) + ".class";
boolean changed = writeBytes(new File(classDir, filename), bytes);
if (changed) {
Expand All @@ -191,7 +263,14 @@ public Class<?> loadFromJava(@NotNull ClassLoader classLoader,
return clazz;
}

private @NotNull MyJavaFileManager getFileManager(StandardJavaFileManager fm) {
/**
* Gets a MyJavaFileManager instance from the provided StandardJavaFileManager.
*
* @param fm The StandardJavaFileManager instance
* @return The MyJavaFileManager instance
*/
@NotNull
public MyJavaFileManager getFileManager(StandardJavaFileManager fm) {
return fileManagerOverride != null
? fileManagerOverride.apply(fm)
: new MyJavaFileManager(fm);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,28 @@
import java.io.ByteArrayOutputStream;
import java.util.concurrent.CompletableFuture;

/**
* This class extends ByteArrayOutputStream and provides a CompletableFuture
* that completes when the stream is closed.
*/
public class CloseableByteArrayOutputStream extends ByteArrayOutputStream {
// CompletableFuture that completes when the stream is closed
private final CompletableFuture<?> closeFuture = new CompletableFuture<>();

/**
* Closes this output stream and completes the closeFuture.
*/
@Override
public void close() {
// Complete the closeFuture to signal that the stream has been closed
closeFuture.complete(null);
}

/**
* Returns the CompletableFuture that completes when the stream is closed.
*
* @return The CompletableFuture that completes when the stream is closed
*/
public CompletableFuture<?> closeFuture() {
return closeFuture;
}
Expand Down
Loading