Skip to content
Draft
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
31 changes: 31 additions & 0 deletions .github/workflows/feature-branches.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Feature Branch Tests

on:
push:
branches:
- 'feature/**'
pull_request:
branches:
- master
types: [opened, synchronize, reopened, ready_for_review]

permissions:
contents: read

jobs:
test:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 25
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '25'
cache: maven

- name: Run unit tests
run: mvn -B test --file pom.xml
22 changes: 15 additions & 7 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@ on:
pull_request:
branches: [master]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 25
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '25'
cache: maven

- name: Run tests
run: mvn -B test --file pom.xml
31 changes: 15 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,42 @@
# What is dynamic-java-compiler?
![Build](https://github.com/raulgomis/dynamic-java-compiler/workflows/Build/badge.svg)

_Dynamic-java-compiler_ is a library that allows users to dynamically compile and execute any java source code. Writing dynamically executed Java applications require some boilerplate code: working with classloaders, compilation error handling, etc. The idea behind this library is to free you from this development and let you focus on your business logic.
_Dynamic-java-compiler_ is a library that allows users to dynamically compile and execute Java source code. Writing dynamically executed Java applications usually requires boilerplate code (working with classloaders, compilation error handling, etc.). This library removes that overhead so you can focus on business logic.

# How does it work?
## Java compatibility

The dynamic ompilation task is very simple with this library. Imagine we want to compile this source code introduced dynamically as a text string by the user:
This project is configured to compile using Java **25** (`maven.compiler.release=25`) and has tests updated for modern JDK APIs.

## How does it work?

The dynamic compilation task is very simple with this library. Imagine we want to compile this source code introduced dynamically as a text string by the user:

```java
public class Test01 implements Runnable {
public void run() {
System.out.println("Hello World!");
}
public void run() {
System.out.println("Hello World!");
}
}
```

So simple, we just need to instantiate the compiler and compile the code:
Now instantiate the compiler and compile the code:

```java
DynamicCompiler<Runnable> compiler = new DynamicCompiler<>();
// Read source code as String
Class<Runnable> clazz = compiler.compile(null, "Test01", source);
final Runnable r;
try {
r = clazz.newInstance();
r.run();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
Runnable runnable = clazz.getDeclaredConstructor().newInstance();
runnable.run();
```

The final result will be:

```
Hello World!
```


## Contribution

You are welcome to contribute to the project using pull requests on GitHub.

If you find a bug or want to request a feature, please use the [issue tracker](https://github.com/raulgomis/dynamic-java-compiler/issues) of Github.
If you find a bug or want to request a feature, please use the [issue tracker](https://github.com/raulgomis/dynamic-java-compiler/issues) on GitHub.
30 changes: 8 additions & 22 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<license>
<name>The MIT License</name>
<url>https://github.com/raulgomis/dynamic-java-compiler/blob/master/LICENSE</url>
</license>
</license>
</licenses>

<developers>
Expand All @@ -29,27 +29,15 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.release>25</maven.compiler.release>
<junit.jupiter.version>5.12.2</junit.jupiter.version>
</properties>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.1</version>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -59,17 +47,15 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.14.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<version>3.5.4</version>
</plugin>
</plugins>
</build>
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/raulgomis/djc/DynamicByteObject.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.raulgomis.djc;

import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import javax.tools.SimpleJavaFileObject;

public class DynamicByteObject extends SimpleJavaFileObject {
private ByteArrayOutputStream outputStream;

private final ByteArrayOutputStream outputStream;

DynamicByteObject(String name, Kind kind) {
super(DynamicCompilerUtils.createURI(name), kind);
Expand Down
22 changes: 8 additions & 14 deletions src/main/java/com/raulgomis/djc/DynamicClassLoader.java
Original file line number Diff line number Diff line change
@@ -1,45 +1,39 @@
package com.raulgomis.djc;

import javax.tools.JavaFileObject.Kind;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.tools.JavaFileObject.Kind;

public final class DynamicClassLoader extends ClassLoader {

private Map<String, DynamicByteObject> classes = new HashMap<>();
private final Map<String, DynamicByteObject> classes = new ConcurrentHashMap<>();

DynamicClassLoader(ClassLoader parentClassLoader) {
super(parentClassLoader);
}

void addClass(DynamicByteObject compiledObj) {
System.out.println("Compiled " + compiledObj.getName());
classes.put(compiledObj.getName(), compiledObj);
}

@Override
public Class<?> findClass(String className) throws ClassNotFoundException {
DynamicByteObject byteObject = classes.get(className);
final DynamicByteObject byteObject = classes.get(className);
if (byteObject != null) {
byte[] bytes = byteObject.getBytes();
final byte[] bytes = byteObject.getBytes();
return defineClass(className, bytes, 0, bytes.length);
}

return super.findClass(className);
}

@Override
protected synchronized Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
return super.loadClass(name, resolve);
}

@Override
public InputStream getResourceAsStream(final String name) {
public InputStream getResourceAsStream(String name) {
if (name.endsWith(Kind.CLASS.extension)) {
String qualifiedClassName = name.substring(0, name.length() - Kind.CLASS.extension.length()).replace('/', '.');
DynamicByteObject file = classes.get(qualifiedClassName);
final String qualifiedClassName = name.substring(0, name.length() - Kind.CLASS.extension.length()).replace('/', '.');
final DynamicByteObject file = classes.get(qualifiedClassName);
if (file != null) {
return new ByteArrayInputStream(file.getBytes());
}
Expand Down
42 changes: 25 additions & 17 deletions src/main/java/com/raulgomis/djc/DynamicCompiler.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
package com.raulgomis.djc;

import java.util.List;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.util.Arrays;

public final class DynamicCompiler<T> {

private JavaCompiler compiler;
private DynamicExtendedFileManager dynamicExtendedFileManager;
private DynamicClassLoader classLoader;

private DiagnosticCollector<JavaFileObject> diagnostics;
private final JavaCompiler compiler;
private final DynamicExtendedFileManager dynamicExtendedFileManager;
private final DynamicClassLoader classLoader;
private final DiagnosticCollector<JavaFileObject> diagnostics;

public DynamicCompiler() throws DynamicCompilerException {
compiler = ToolProvider.getSystemJavaCompiler();
Expand All @@ -26,31 +25,40 @@ public DynamicCompiler() throws DynamicCompilerException {
classLoader = new DynamicClassLoader(Thread.currentThread().getContextClassLoader());
diagnostics = new DiagnosticCollector<>();

StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnostics, null, null);
final StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnostics, null, null);
dynamicExtendedFileManager = new DynamicExtendedFileManager(standardFileManager, classLoader);
}

@SuppressWarnings("unchecked")
public synchronized Class<T> compile(String packageName, String className, String javaSource) throws DynamicCompilerException {
try {
final String qualifiedClassName = DynamicCompilerUtils.getQualifiedClassName(packageName, className);
final DynamicStringObject sourceObj = new DynamicStringObject(className, javaSource);

String qualifiedClassName = DynamicCompilerUtils.getQualifiedClassName(packageName, className);
DynamicStringObject sourceObj = new DynamicStringObject(className, javaSource);

dynamicExtendedFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName,
className + JavaFileObject.Kind.SOURCE.extension, sourceObj);
dynamicExtendedFileManager.putFileForInput(
StandardLocation.SOURCE_PATH,
packageName,
className + JavaFileObject.Kind.SOURCE.extension,
sourceObj
);

CompilationTask task = compiler.getTask(null, dynamicExtendedFileManager, diagnostics, null, null, Arrays.asList(sourceObj));
boolean result = task.call();
final CompilationTask task = compiler.getTask(
null,
dynamicExtendedFileManager,
diagnostics,
null,
null,
List.of(sourceObj)
);

if (!result) {
if (!task.call()) {
throw new DynamicCompilerException("Compilation failure", diagnostics.getDiagnostics());
}

dynamicExtendedFileManager.close();

return (Class<T>) classLoader.loadClass(qualifiedClassName);

} catch (DynamicCompilerException exception) {
throw exception;
} catch (Exception exception) {
throw new DynamicCompilerException(exception, diagnostics.getDiagnostics());
}
Expand Down
19 changes: 9 additions & 10 deletions src/main/java/com/raulgomis/djc/DynamicCompilerException.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
package com.raulgomis.djc;

import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.util.List;

public final class DynamicCompilerException extends Exception {

private List<Diagnostic<? extends JavaFileObject>> diagnostics;
private final List<Diagnostic<? extends JavaFileObject>> diagnostics;

public DynamicCompilerException(String message) {
super(message);
this.diagnostics = List.of();
}

public DynamicCompilerException(String message, List<Diagnostic<? extends JavaFileObject>> diagnostics) {
super(message);
this.diagnostics = diagnostics;
this.diagnostics = diagnostics == null ? List.of() : List.copyOf(diagnostics);
}

public DynamicCompilerException(Throwable e, List<Diagnostic<? extends JavaFileObject>> diagnostics) {
super(e);
this.diagnostics = diagnostics;
this.diagnostics = diagnostics == null ? List.of() : List.copyOf(diagnostics);
}

public String getDiagnosticsError() {
StringBuilder sb = new StringBuilder();
if(diagnostics != null) {
diagnostics.forEach(diagnostic -> sb.append(String.format("Error on line %d: %s\n",
diagnostic.getLineNumber(),
diagnostic.getMessage(null))));
}
final StringBuilder sb = new StringBuilder();
diagnostics.forEach(diagnostic -> sb.append(
String.format("Error on line %d: %s%n", diagnostic.getLineNumber(), diagnostic.getMessage(null))
));
return sb.toString();
}

Expand Down
Loading
Loading