Skip to content

Commit 3b672b7

Browse files
authored
Merge pull request #3 from raulgomis/feature/update-coding-style-and-quality
Upgrade to Java 25, modernize build/CI, and refactor dynamic compiler internals
2 parents 6e02197 + f8f7657 commit 3b672b7

19 files changed

Lines changed: 639 additions & 192 deletions
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Feature Branch Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- 'feature/**'
7+
pull_request:
8+
branches:
9+
- master
10+
types: [opened, synchronize, reopened, ready_for_review]
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
test:
17+
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
18+
runs-on: ubuntu-latest
19+
strategy:
20+
matrix:
21+
java: ['17', '21', '25', '26']
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@v4
25+
26+
- name: Set up JDK ${{ matrix.java }}
27+
uses: actions/setup-java@v4
28+
with:
29+
distribution: temurin
30+
java-version: ${{ matrix.java }}
31+
cache: maven
32+
33+
- name: Run tests and generate coverage report
34+
run: mvn -B verify --file pom.xml
35+
36+
- name: Upload coverage report
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: jacoco-coverage-report-java-${{ matrix.java }}
40+
path: target/site/jacoco/
41+
if-no-files-found: error

‎.github/workflows/master.yml‎

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,32 @@ on:
66
pull_request:
77
branches: [master]
88

9+
permissions:
10+
contents: read
11+
912
jobs:
1013
build:
1114
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
java: ['17', '21', '25', '26']
1218
steps:
13-
- uses: actions/checkout@v2
14-
- name: Set up JDK 1.8
15-
uses: actions/setup-java@v1
16-
with:
17-
java-version: 1.8
18-
- name: Build with Maven
19-
run: mvn -B package --file pom.xml
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Set up JDK ${{ matrix.java }}
23+
uses: actions/setup-java@v4
24+
with:
25+
distribution: temurin
26+
java-version: ${{ matrix.java }}
27+
cache: maven
28+
29+
- name: Run tests and generate coverage report
30+
run: mvn -B verify --file pom.xml
31+
32+
- name: Upload coverage report
33+
uses: actions/upload-artifact@v4
34+
with:
35+
name: jacoco-coverage-report-java-${{ matrix.java }}
36+
path: target/site/jacoco/
37+
if-no-files-found: error

‎AGENTS.md‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# AGENTS.md
2+
3+
## Project overview
4+
5+
This repository contains a small Java library for compiling and loading Java
6+
source code at runtime. Production code is under
7+
`src/main/java/com/raulgomis/djc`, and tests are under the matching package in
8+
`src/test/java`.
9+
10+
The project targets Java 17 bytecode and uses Maven, JUnit 5, and JaCoCo.
11+
12+
## Build and verification
13+
14+
Run commands from the repository root.
15+
16+
- `mvn test` runs the unit tests.
17+
- `mvn verify` runs the tests and generates the JaCoCo report.
18+
- `mvn clean verify` performs a clean, full verification when stale build
19+
output may matter.
20+
- The HTML coverage report is written to
21+
`target/site/jacoco/index.html`.
22+
23+
Before handing off a code change, run `mvn verify`. Also run
24+
`git diff --check` to catch whitespace errors.
25+
26+
## Implementation guidelines
27+
28+
- Keep the public API small and compatible unless the task explicitly calls
29+
for an API change.
30+
- Compile with Java 17-compatible language and library features.
31+
- Follow the existing four-space indentation and brace style.
32+
- Prefer standard-library solutions; avoid adding dependencies for behavior
33+
that can be implemented clearly without them.
34+
- Keep compiler diagnostics associated with the individual compilation that
35+
produced them.
36+
- Preserve the in-memory design: generated source and bytecode should not
37+
require temporary source or class files.
38+
- Treat package names, binary class names, source file names, and resource
39+
paths carefully. A source package and the qualified name used for loading
40+
must agree.
41+
- Preserve exception causes when wrapping unexpected compiler, file-manager,
42+
or class-loading failures.
43+
44+
## Testing guidelines
45+
46+
- Use JUnit Jupiter.
47+
- Name tests after observable behavior, such as
48+
`compilesAClassInANamedPackage`.
49+
- Add regression tests for bug fixes and focused tests for new branches.
50+
- Test both successful compilation and relevant failure behavior, including
51+
diagnostics and wrapped causes.
52+
- Tests for package-private implementation classes should remain in
53+
`com.raulgomis.djc` rather than widening production visibility.
54+
- Prefer small inline source strings for a single scenario. Use
55+
`src/test/resources` when a source fixture is substantial or reused.
56+
- Dynamically compiled test code must be deterministic and must not perform
57+
network access, modify user files, or depend on machine-specific state.
58+
- Do not weaken assertions merely to increase the coverage percentage.
59+
60+
## Change hygiene
61+
62+
- The worktree may already contain user changes. Inspect `git status` and
63+
preserve unrelated modifications.
64+
- Do not edit generated files under `target`.
65+
- Keep documentation and CI configuration consistent with the Java version
66+
and Maven commands in `pom.xml`.
67+
- Avoid broad formatting or refactoring unrelated to the requested change.

‎README.md‎

Lines changed: 137 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,156 @@
1-
# What is dynamic-java-compiler?
2-
![Build](https://github.com/raulgomis/dynamic-java-compiler/workflows/Build/badge.svg)
1+
# Dynamic Java Compiler
32

4-
_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.
3+
[![Build](https://github.com/raulgomis/dynamic-java-compiler/actions/workflows/master.yml/badge.svg)](https://github.com/raulgomis/dynamic-java-compiler/actions/workflows/master.yml)
54

6-
# How does it work?
5+
Compile Java source held in a string and load the resulting class directly into
6+
the running JVM.
77

8-
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:
8+
Dynamic Java Compiler wraps the standard Java compiler, an in-memory file
9+
manager, and a dedicated class loader behind a small API. Source and generated
10+
bytecode stay in memory, so callers do not need to manage temporary `.java` or
11+
`.class` files.
12+
13+
## Features
14+
15+
- Compiles Java source at runtime using the JDK compiler
16+
- Loads generated classes without writing bytecode to disk
17+
- Supports packages, imports, and nested classes
18+
- Returns structured compiler diagnostics on failure
19+
- Has no runtime dependencies outside the JDK
20+
21+
## Requirements
22+
23+
- Java 17 or later
24+
- A full JDK that includes the system Java compiler
25+
26+
The project is compiled with `maven.compiler.release=17`, so its bytecode
27+
remains compatible with Java 17 while builds can run on newer JDKs. A minimal
28+
runtime image without the `jdk.compiler` module cannot perform dynamic
29+
compilation.
30+
31+
## Quick start
32+
33+
Create the source code to compile:
934

1035
```java
11-
public class Test01 implements Runnable {
12-
public void run() {
13-
System.out.println("Hello World!");
14-
}
15-
}
36+
String source = """
37+
public class GreetingTask implements Runnable {
38+
@Override
39+
public void run() {
40+
System.out.println("Hello from dynamically compiled code!");
41+
}
42+
}
43+
""";
1644
```
1745

18-
So simple, we just need to instantiate the compiler and compile the code:
46+
Compile, instantiate, and execute it:
1947

2048
```java
49+
import com.raulgomis.djc.DynamicCompiler;
50+
51+
DynamicCompiler<Runnable> compiler = new DynamicCompiler<>();
52+
Class<Runnable> taskClass = compiler.compile(
53+
null,
54+
"GreetingTask",
55+
source
56+
);
57+
58+
Runnable task = taskClass.getDeclaredConstructor().newInstance();
59+
task.run();
60+
```
61+
62+
The first argument to `compile` is the package name. Pass `null` or an empty
63+
string for the default package. The second argument is the simple class name
64+
and must agree with the class declared by the source.
65+
66+
## Compiling a packaged class
67+
68+
The package declared in the source must match the package passed to
69+
`compile`:
70+
71+
```java
72+
String source = """
73+
package example.tasks;
74+
75+
public class GreetingTask implements Runnable {
76+
@Override
77+
public void run() {
78+
System.out.println("Hello!");
79+
}
80+
}
81+
""";
82+
2183
DynamicCompiler<Runnable> compiler = new DynamicCompiler<>();
22-
// Read source code as String
23-
Class<Runnable> clazz = compiler.compile(null, "Test01", source);
24-
final Runnable r;
84+
Class<Runnable> taskClass = compiler.compile(
85+
"example.tasks",
86+
"GreetingTask",
87+
source
88+
);
89+
```
90+
91+
The returned class has the binary name `example.tasks.GreetingTask`.
92+
93+
## Handling compilation errors
94+
95+
Compilation failures throw `DynamicCompilerException`. Use
96+
`getDiagnostics()` for structured JDK diagnostics or
97+
`getDiagnosticsError()` for a readable summary:
98+
99+
```java
100+
import com.raulgomis.djc.DynamicCompilerException;
101+
25102
try {
26-
r = clazz.newInstance();
27-
r.run();
28-
} catch (InstantiationException | IllegalAccessException e) {
29-
e.printStackTrace();
103+
DynamicCompiler<Runnable> compiler = new DynamicCompiler<>();
104+
compiler.compile(null, "BrokenTask", brokenSource);
105+
} catch (DynamicCompilerException exception) {
106+
System.err.print(exception.getDiagnosticsError());
107+
108+
exception.getDiagnostics().forEach(diagnostic -> {
109+
System.err.printf(
110+
"line %d, column %d: %s%n",
111+
diagnostic.getLineNumber(),
112+
diagnostic.getColumnNumber(),
113+
diagnostic.getMessage(null)
114+
);
115+
});
30116
}
31117
```
32118

33-
The final result will be:
119+
Diagnostic wording is produced by the active JDK and may differ between JDK
120+
versions.
121+
122+
## Security
123+
124+
This library compiles code; it does not sandbox it. Once loaded and invoked,
125+
dynamically compiled code runs with the same permissions as the host
126+
application. Do not compile or execute untrusted source without a separate,
127+
appropriately isolated execution environment.
128+
129+
## Building and testing
130+
131+
Run the full verification build:
132+
133+
```shell
134+
mvn verify
34135
```
35-
Hello World!
136+
137+
This runs the JUnit 5 test suite and generates a JaCoCo coverage report at
138+
`target/site/jacoco/index.html`.
139+
140+
For a clean build:
141+
142+
```shell
143+
mvn clean verify
36144
```
37145

38-
39-
## Contribution
146+
## Contributing
147+
148+
Pull requests are welcome. Please include focused tests for behavioral changes
149+
and run `mvn verify` before submitting.
150+
151+
Use the [issue tracker](https://github.com/raulgomis/dynamic-java-compiler/issues)
152+
to report bugs or propose features.
40153

41-
You are welcome to contribute to the project using pull requests on GitHub.
154+
## License
42155

43-
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.
156+
This project is available under the [MIT License](LICENSE).

0 commit comments

Comments
 (0)