diff --git a/.github/CIFLOW.adoc b/.github/CIFLOW.adoc deleted file mode 100644 index e3abb42..0000000 --- a/.github/CIFLOW.adoc +++ /dev/null @@ -1,269 +0,0 @@ -= Development version and branch handling -:toc: -:icons: font - -== Branches - -Versioning policy of JUDO NG modules are based on GitFlow: https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow. - -Branches: - -* **develop**: development branch contains latest development sources of the last active version -* **feature/JNG-NUMBER_short_summary**: feature branches are based on **develop** and contains sources of new features that will be included in last active version -* **(release/)1_0_beta1**: release branches of 1.0-beta1 (release/ prefix is still reserved for CI) -* **bugfix/JNG-NUMBER_short_summary**, **support/JNG-NUMBER_short_summary**: bugfix and support branches are based on release branches and must be applied to release and development branches of newer versions too -* **master**: contains latest released sources of the last active version - -ifdef::env-github[image::branches.png[title="branches.yml"]] -ifndef::env-github[] -[[branches]] -.branches.yml -[plantuml, branches, alt="branches.yml"] ---------------------------------------------------------------------- -digraph GitFlow { - rankdir="LR"; - bgcolor="transparent"; - - node[width=0.15, height=0.15, shape=point]; - edge[weight=2, arrowhead=vee]; - - 0[shape=box, style=filled, label="master", color=chartreuse] - 1[shape=box, style=filled, label="develop", color=cornflowerblue] - 2[shape=box, style=filled, label="feature/JNG-1", color=gold] - 4[shape=box, style=filled, label="feature/JNG-2", color=gold] - 12[shape=box, style=filled, label="feature/JNG-3", color=gold] - 14[shape=box, style=filled, label="(release/)1.0-beta1", color=cyan] - 17[shape=box, style=filled, label="bugfix/JNG-4", color=firebrick1] - 19[shape=box, style=filled, label="release/1.0-beta2", color=cyan] - 23[shape=box, style=filled, label="support/JNG-5", color=aquamarine] - 26[shape=box, style=filled, label="hotfix/JNG-6", color=firebrick2] - 27[shape=box, style=filled, label="release/1.1-beta1", color=cyan] - - node[group=develop, color=cornflowerblue]; - 0 -> 1 -> 7 -> 8 -> 9 -> 10 -> 11 -> 30 -> 31; - node[group=feature_JNG_1, color=gold]; - 1 -> 4 -> 5 -> 6 -> 8; - node[group=feature_JNG_2, color=gold]; - 1 -> 2 -> 3 -> 7; - node[group=feature_JNG_3, color=gold]; - 8 -> 12 -> 13 -> 9; - node[group=release_1_0_beta1, color=cyan]; - 8 -> 14 -> 15 -> 16 -> 10; - node[group=bugfix_JNG_4, color=firebrick1]; - 14 -> 17 -> 18 -> 16; - node[group=release_1_0_beta2, color=cyan]; - 9 -> 19 -> 20 -> 21 -> 33; - 16 -> 21; - 26 -> 33; - node[group=support_JNG_5, color=aquamarine]; - 19 -> 23 -> 24 -> 20; - node[group=master, color=chartreuse]; - 0 -> 22 -> 25 -> 32; - 21 -> 22; - 21 -> 11; - node[group=hotfix_JNG_6, color=firebrick2]; - 22 -> 26 -> 25; - 26 -> 30; - node[group=release_1_1_beta1, color=cyan]; - 11 -> 27 -> 28 -> 29 -> 31; - 26 -> 28; - 29 -> 32; -} ---------------------------------------------------------------------- -endif::[] - -== Version numbers - -Version numbers are increased using semantic versioning: - -* do not change version numbers on starting feature/ branches -* 2nd number in version of *develop* branch is increased when a release branch started -* do not change version numbers on bugfix/ branches - that are applied on release branches during testing before releasing it (merging to master) -* 3rd number in version of support/ branches is increased when started - it is used to support a previous release including new (minor) changes; support/ branches are merged back to release branch when update is released (without merging changes to master) -* 4th number in version of hotfix/ branches is increased when started (that are applied on both release and master branches) - -=== Gihub action flows - -ifdef::env-github[image::build.png[title="build.yml"]] -ifndef::env-github[] -[[build]] -.build.yml -[plantuml, build, alt="build.yml"] ---------------------------------------------------------------------- -@startuml - -partition build.yml { - skinparam shadowing false - skinparam ActivityBackgroundColor WhiteSmoke - skinparam ActivityBorderColor Black - skinparam ActivityBorderThickness 1 - skinparam ArrowColor Black - skinparam ActivityDiamondBackgroundColor WhiteSmoke - skinparam ActivityDiamondBorderColor Black - - #business:**when**\npush on **__develop__** branch\nor\npull request on **__develop__**, **__master__**, **__increment/*__**, **__release/*__** __branch__; - - if (Commit or Pull request's base branch?) then (master, release/*) - :set **__version__**\nfrom project **pom.xml** (version without '-SNAPSHOT'); - else (develop, increment/*) - :set __version__ **major.minor.qualifier.date_commitId_branchName**\nfrom project **pom.xml** (version without '-SNAPSHOT'); - endif - - :build and deploy to nexus; - - :create git __tag__ **v____**; - - if (Pull request or commit base branch?) then (increment/*, release/*) - :create __tag__ **merge-pr/____**; - fork - #palegreen:**trigger merge-pr-tagged.yml**; - end - fork again - end fork - endif - if (Pull request's or commit base branch?) then (develop) - :build change log; - :create **github release** (prerelease) with change log; - endif - end - -} - -@enduml ---------------------------------------------------------------------- -endif::[] - - -ifdef::env-github[image::merge-pr-tagged.png[title="merge-pr-tagged.yml"]] -ifndef::env-github[] -[[merge-pr-tagged]] -.merge-pr-tagged.yml -[plantuml, merge-pr-tagged, alt="merge-pr-tagged.yml"] ---------------------------------------------------------------------- -@startuml - -partition merge-pr-tagged.yml { - skinparam shadowing false - skinparam ActivityBackgroundColor WhiteSmoke - skinparam ActivityBorderColor Black - skinparam ActivityBorderThickness 1 - skinparam ArrowColor Black - skinparam ActivityDiamondBackgroundColor WhiteSmoke - skinparam ActivityDiamondBorderColor Black - - #business:**when**\npush on **__merge-pr/*__** tag; - - :get ____ from tag name; - - if (check ____ format) then (major.minor.qualifier) - :merge pull request to __master__; - fork - #palegreen:**trigger create-release-on-master.yml**; - end - fork again - end fork - else - :squash pull request to __develop__; - fork - #palegreen:**trigger build.yml**; - end - fork again - end fork - endif - - :delete __tag__ **merge-pr/____**; - end - -} - -@enduml ---------------------------------------------------------------------- -endif::[] - -ifdef::env-github[image::create-release-on-master.png[title="create-release-on-master.yml"]] -ifndef::env-github[] -[[create-release-on-master]] -.create-release-on-master.yml -[plantuml, create-release-on-master, alt="create-release-on-master.yml"] ---------------------------------------------------------------------- -@startuml - -partition create-release-on-master.yml { - skinparam shadowing false - skinparam ActivityBackgroundColor WhiteSmoke - skinparam ActivityBorderColor Black - skinparam ActivityBorderThickness 1 - skinparam ArrowColor Black - skinparam ActivityDiamondBackgroundColor WhiteSmoke - skinparam ActivityDiamondBorderColor Black - - #business:**when**\npush on **__master__** __branch__; - - :get ____ from __tag__ name; - - :build change log; - - :create **github release** (last) with change log; - - end -} - -@enduml ---------------------------------------------------------------------- -endif::[] - - -ifdef::env-github[image::release.png[title="release.yml"]] -ifndef::env-github[] -[[release]] -.release.yml -[plantuml, release, alt="release.yml"] ---------------------------------------------------------------------- -@startuml - -partition release.yml { - skinparam shadowing false - skinparam ActivityBackgroundColor WhiteSmoke - skinparam ActivityBorderColor Black - skinparam ActivityBorderThickness 1 - skinparam ArrowColor Black - skinparam ActivityDiamondBackgroundColor WhiteSmoke - skinparam ActivityDiamondBorderColor Black - - #business:**when**\nmanually triggred with **__given version__**\nwhich is **'auto'** or any other in **major.minor.qualifier** form; - - if (__given version__ is) then ('auto') - :set **__release version__**\nfrom project **pom.xml** (version without '-SNAPSHOT'); - else - :set **__release version__** to given **version**; - endif - - :set **__next version__** to **__release version__**'s qualifier + 1; - - :create pull request on **__master__** with **__release version__**; - fork - #palegreen:**trigger build.yml**; - end - fork again - end fork - :create pull request on **__develop__** with **__next version__**; - fork - #palegreen:**trigger build.yml**; - end - fork again - end fork - end -} - -@enduml ---------------------------------------------------------------------- -endif::[] - - -== How to develop - -For issue tracking we are using https://blackbelt.atlassian.net/jira/dashboards[JIRA]. Golden rule: - -IMPORTANT: *There is no commit without ticket number* - -So for pull request or commit `JNG-xxx` have to be presented in the commit. diff --git a/.github/CIFLOW.md b/.github/CIFLOW.md new file mode 100644 index 0000000..fd0c37f --- /dev/null +++ b/.github/CIFLOW.md @@ -0,0 +1,127 @@ +# Development Version and Branch Handling + +This document describes the GitFlow-based branching strategy, version numbering policy, and CI/CD pipeline for the Java Embedded Compiler project. + +## Branches + +The project uses a [GitFlow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) branching model. Each branch type serves a specific purpose in the release lifecycle: + +| Branch Pattern | Base | Purpose | +|----------------|------|---------| +| `develop` | — | Main development branch. Contains the latest sources for the current active version. | +| `feature/JNG-NUMBER_summary` | `develop` | New features for the next release. | +| `release/X.Y.Z` | `develop` | Stabilization branch for a specific release. The `release/` prefix is reserved for CI. | +| `bugfix/JNG-NUMBER_summary` | release branch | Bug fixes applied during release testing. Must also be applied to newer release and develop branches. | +| `support/JNG-NUMBER_summary` | release branch | Minor changes to a previous release; merged back to the release branch on update. | +| `hotfix/JNG-NUMBER_summary` | `master` | Critical fixes applied to both release and master branches. | +| `master` | — | Latest released (stable) sources of the active version. | + +### Branch Flow Diagram + +```mermaid +gitGraph + commit id: "init" + branch develop order: 1 + checkout develop + commit id: "dev-1" + branch feature/JNG-1 order: 2 + commit id: "feat-1" + commit id: "feat-2" + checkout develop + merge feature/JNG-1 id: "merge-feat-1" + branch feature/JNG-3 order: 3 + commit id: "feat-3" + checkout develop + merge feature/JNG-3 id: "merge-feat-3" + branch release/1.0-beta1 order: 4 + commit id: "rc-1" + branch bugfix/JNG-4 order: 5 + commit id: "fix-1" + checkout release/1.0-beta1 + merge bugfix/JNG-4 id: "merge-fix" + checkout develop + merge release/1.0-beta1 id: "merge-release" + checkout master + merge release/1.0-beta1 id: "release-1.0" +``` + +## Version Numbers + +Versions follow semantic versioning with these rules: + +| Event | Version Change | +|-------|---------------| +| Starting a feature branch | No change — inherits from `develop` | +| Starting a release branch | 2nd number on `develop` is incremented | +| Bugfix branch (on release) | No change — fixes are applied before release | +| Support branch | 3rd number is incremented | +| Hotfix branch | 4th number is incremented | + +### Qualified Versions + +For non-release branches, the CI appends a qualifier to the version: + +``` +...__ +``` + +For release branches (`master`, `release/*`), the version is used as-is from `pom.xml` (without `-SNAPSHOT`). + +## GitHub Actions Workflows + +The CI/CD pipeline consists of several interconnected GitHub Actions workflows: + +### build.yml — Primary Build Pipeline + +```mermaid +flowchart TD + Trigger["Push to develop
or PR to develop/master/release/*"] + Trigger --> BranchCheck{Branch type?} + BranchCheck -->|"master, release/*"| CleanVersion["Version from pom.xml
(no qualifier)"] + BranchCheck -->|"develop, increment/*"| QualifiedVersion["Version with
date_commitId_branch qualifier"] + CleanVersion --> Build["Maven build & test"] + QualifiedVersion --> Build + Build --> DeployNexus["Deploy to Judong Nexus"] + DeployNexus --> Tag["Create git tag
v<version>"] + Tag --> ReleaseBranch{Branch?} + ReleaseBranch -->|"increment/*, release/*"| MergePRTag["Create merge-pr/<version> tag"] + MergePRTag --> TriggerMerge["Triggers merge-pr-tagged.yml"] + ReleaseBranch -->|"develop"| Changelog["Build changelog"] + Changelog --> GHRelease["Create GitHub pre-release"] +``` + +### merge-pr-tagged.yml — Merge Automation + +Triggered when a `merge-pr/*` tag is pushed. Routes merges based on version format: + +- **`major.minor.qualifier`** format → merge PR to `master`, triggers `create-release-on-master.yml` +- **Qualified version** → squash PR to `develop`, triggers `build.yml` + +After processing, the `merge-pr/*` tag is deleted. + +### create-release-on-master.yml — Release Publication + +Triggered by pushes to `master`. Builds a changelog and creates a GitHub release marked as the latest stable release. + +### release.yml — Manual Release Trigger + +Manually triggered with a version parameter (`auto` or explicit `major.minor.qualifier`): + +1. Creates a PR to `master` with the release version +2. Creates a PR to `develop` with the next (qualifier + 1) version +3. Both PRs trigger `build.yml` + +### Other Workflows + +| Workflow | Purpose | +|----------|---------| +| `bump-version.yml` | Version bumping automation | +| `delete-old-draft-releases.yml` | Cleanup stale draft GitHub releases | +| `build-dependabot.yml` | Separate build validation for Dependabot PRs | +| `jira-description-to-pr.yml` | Copies JIRA ticket descriptions into PR bodies | + +## Development Rules + +> **Important:** There is no commit without a ticket number. Every commit and pull request must reference a JIRA ticket in the format `JNG-xxx`. + +Issue tracking is managed in [JIRA](https://blackbelt.atlassian.net/jira/dashboards). diff --git a/.gitignore b/.gitignore index d88d09e..aee3f3e 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,8 @@ wercker.env **/GSModel.log* .release /**/.flattened-pom.xml +.claude/commands/opsx +.claude/skills/openspec-*.md +.gemini +.opencode +.agent diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9f840b3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "java.format.enabled": false, + "java.saveActions.organizeImports": false, + "java.maven.downloadSources": true, + "java.configuration.updateBuildConfiguration": "disabled", + "java.autobuild.enabled": false, + "java.import.exclusions": [ + "**/node_modules/**", + "**/.metadata/**", + "**/archetype-resources/**", + "**/META-INF/maven/**", + "**/frontend-react/**/node_modules/**", + "**/frontend-react/**/dist/**", + "**/target/**" + ], + "java.compile.nullAnalysis.mode": "automatic" +} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..9be4b9a --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,24 @@ +{ + "lsp": { + "jdtls": { + "settings": { + "java": { + "format": { "enabled": false }, + "saveActions": { + "organizeImports": false + }, + "maven": { + "downloadSources": true, + "updateWhenProjectConfigurationChanges": false, + "updateProfiles": [], + "updateArgs": ["validate"] + }, + "autobuild": { + "enabled": false, + "exclude": ["**"] + } + } + } + } + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1282773 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,171 @@ +# Java Embedded Compiler - Project Documentation + +## Project Overview + + +**Repository:** BlackBeltTechnology/java-embedded-compiler +**License:** Apache License 2.0 +**Java Version:** 11 (source and target) +**Build System:** Maven 3.9.4 with OSGi bundle packaging (Felix maven-bundle-plugin) + +1. **Runtime Java compilation** — Provides an API for compiling Java source code at runtime, supporting both in-memory and file-based output +2. **Pluggable compiler backends** — Supports the JDK system compiler (javac via `ToolProvider`) and Eclipse Compiler for Java (ECJ) as interchangeable strategies +3. **OSGi integration** — Full support for OSGi environments with bundle-aware classloading, dynamic compiler service registration via Declarative Services, and Karaf deployment +4. **Flexible classloading** — Composite classloaders, custom classloader wrapping, and compiled-bytecode classloaders enable loading compiled classes in complex classloader hierarchies +5. **ServiceLoader discovery** — Compiler factories are discovered automatically via `java.util.ServiceLoader` in standard Java, or via OSGi DS `@Reference` in OSGi containers + +## Code Instructions + +1. First think through the problem, read the codebase for relevant files. +2. Before you make any major changes, check in with me and I will verify the plan. +3. Please every step of the way just give me a high level explanation of what changes you made. +4. Make every task and code change you do as simple as possible. We want to avoid making any massive or complex changes. Every change should impact as little code as possible. Everything is about simplicity. +5. Maintain a documentation file that describes how the architecture of the app works inside and out. +6. Never speculate about code you have not opened. If the user references a specific file, you MUST read the file before answering. Make sure to investigate and read relevant files BEFORE answering questions about the codebase. Never make any claims about code before investigating unless you are certain of the correct answer - give grounded and hallucination-free answers. +7. For implementation use TDD (Test-Driven Development): write or update tests first to define the expected behaviour, verify they fail, then write the minimal implementation to make them pass. +8. Use DRY (Don't Repeat Yourself): extract reusable logic into separate classes, utilities, or components. If the same pattern appears in multiple places, refactor it into a shared helper. + +## Directory Structure + +``` +java-embedded-compiler/ +├── java-embedded-compiler/ # Core API module +├── java-embedded-compiler-jdt/ # JDK system compiler implementation +├── java-embedded-compiler-ecj/ # Eclipse ECJ compiler implementation +├── java-embedded-compiler-osgi/ # OSGi service wrapper +├── java-embedded-compiler-itest/ # Integration tests (Karaf + Pax Exam) +├── java-embedded-compiler-reports/ # JaCoCo coverage aggregation +├── .github/workflows/ # GitHub Actions CI/CD pipelines +├── .mvn/ # Maven wrapper and JVM config +├── openspec/ # OpenSpec configuration +└── pom.xml # Parent POM +``` + +## Core Modules + +### API Layer + +| Module | Type | Purpose | +|--------|------|---------| +| `java-embedded-compiler/` | Library | Core API with `CompilerContext` (builder), `CompilerFactory` (strategy interface), `CompilerUtil` (static compilation entry point), file managers, classloaders, and file objects | + +### Compiler Implementations + +| Module | Type | Purpose | +|--------|------|---------| +| `java-embedded-compiler-jdt/` | OSGi Bundle | `JdtCompilerFactory` — wraps `ToolProvider.getSystemJavaCompiler()`. Requires a JDK (not JRE). Registered as OSGi component with `compileType=system`. | +| `java-embedded-compiler-ecj/` | OSGi Bundle | `EclipseCompilerFactory` — wraps the Eclipse ECJ compiler with a custom `EclipseCompilerWrapper` that handles two-pass compilation (module-info first, then sources). Registered as `compileType=eclipse`. Passes `-warn:none` by default. | + +### Runtime & Integration + +| Module | Type | Purpose | +|--------|------|---------| +| `java-embedded-compiler-osgi/` | OSGi Bundle | `CompilerService` interface and `CompilerServiceImpl` DS component. Dynamically discovers `CompilerFactory` services, ranks them by `SERVICE_RANKING`, and selects the appropriate compiler based on `CompilerContext.preferEclipseCompiler`. | +| `java-embedded-compiler-itest/` | Test | Karaf 4.4.7 + Pax Exam 4.13.5 integration tests. Tests all compilation modes: file-to-directory, file-to-memory, string-to-memory, compile-as-class, and error handling — for both JDT and ECJ compilers. | +| `java-embedded-compiler-reports/` | Reports | JaCoCo aggregate coverage report across all modules. | + +## Technology Stack + +### Core Technologies +- **Java 11** — source and target compatibility +- **Google Guava 30.0-jre** — `LoadingCache` for in-memory file manager caching, `ImmutableList`, `Preconditions` +- **OSGi Core 6.0** — `BundleContext`, `BundleWiring`, `BundleListener` for bundle-aware compilation +- **OSGi Declarative Services 1.3** — `@Component`, `@Reference` annotations for service registration +- **Eclipse JDT/ECJ (Platform 4.18)** — Eclipse compiler implementation +- **Lombok 1.18.34** — `@Builder`, `@Getter`, `@Slf4j` for boilerplate reduction +- **SLF4J 2.0.16** — Logging abstraction + +### Build & Quality +- **Maven 3.9.4** with Maven wrapper (`mvnw`) +- **Felix maven-bundle-plugin 5.1.8** — OSGi bundle packaging +- **flatten-maven-plugin 1.3.0** — CI-friendly `${revision}` versioning +- **JUnit 4.13.2** + **Hamcrest 2.2** + **Mockito 4.8.0** — Testing +- **Pax Exam 4.13.5** — OSGi container testing with Apache Karaf +- **JaCoCo 0.8.12** — Code coverage +- **SonarQube** — Code quality analysis +- **Logback 1.5.12** — Test logging + +## Build Commands + +The project includes a Maven wrapper. Always use `./mvnw` (or `mvnw.cmd` on Windows). + +```bash +# Full build +./mvnw clean install + +# Tests only +./mvnw clean test + +# Skip tests +./mvnw clean install -DskipTests + +# Build parent POM only (no sub-modules) +./mvnw clean install -DskipModules=true + +# Run a specific test +./mvnw test -Dtest=CompilerUtilITest -f java-embedded-compiler-itest/pom.xml + +# Deploy to Judong Nexus +./mvnw clean deploy -Prelease-judong +``` + +### Maven Profiles + +| Profile | Purpose | +|---------|---------| +| `modules` | Active by default. Includes all sub-modules. Disable with `-DskipModules=true`. | +| `sign-artifacts` | GPG-sign artifacts for Maven Central publication. | +| `release-dummy` | Deploy to local `/tmp/` directory for testing the release process. | +| `release-judong` | Deploy to the Judong Nexus snapshot repository. | +| `release-central` | Deploy to Maven Central via Sonatype OSSRH with auto-release. | +| `generate-github-asciidoc-diagrams` | Render PlantUML diagrams from AsciiDoc sources. | +| `update-source-code-license` | Update Apache 2.0 license headers across all source files. | + +## Key Configuration Files + +| File | Purpose | +|------|---------| +| `pom.xml` | Parent POM: version management (`${revision}` = 1.1.0), dependency management, plugin configuration | +| `.mvn/jvm.config` | JVM flags for Maven: heap sizing (-Xms1024m -Xmx2048m), `--add-opens` for JDK 9+ module system | +| `.mvn/wrapper/maven-wrapper.properties` | Maven wrapper version configuration | +| `logback-test.xml` | Logback configuration for test execution | +| `.github/workflows/build.yml` | Primary CI pipeline: build, test, deploy, tag, release | +| `java-embedded-compiler-itest/src/test/resources/test-features.xml` | Karaf feature definitions for integration tests | + +## Development Environment + +**Required:** +- Java 11 JDK (Azul Zulu recommended) +- Maven 3.9.4+ (or use the included `mvnw` wrapper) + +**JVM flags** are automatically applied via `.mvn/jvm.config`: +- `-Xms1024m -Xmx2048m` +- `--add-opens java.base/java.lang=ALL-UNNAMED` +- `--add-opens java.base/java.util=ALL-UNNAMED` +- `--add-opens java.base/java.time=ALL-UNNAMED` + +## Git Workflow + +- **Main Branch:** `develop` +- **Stable Releases:** `master` +- **Versioning:** CI-friendly `${revision}` property (currently `1.1.0`). Non-release builds get a qualified version: `major.minor.qualifier.date_commitId_branchName`. +- **Branch naming:** `feature/JNG-xxx_summary`, `bugfix/JNG-xxx_summary`, `hotfix/JNG-xxx_summary`, `release/X.Y.Z` +- **Every commit must reference a JIRA ticket** in the format `JNG-xxx` +- **CI/CD:** GitHub Actions on `judong` runner with JDK 21. Builds deploy to Judong Nexus; release branches also deploy to Maven Central. + +## Important Notes + +1. **Two compiler modes exist** — JDT (system javac) requires a full JDK to be present at runtime; ECJ works with just a JRE since it bundles its own compiler +2. **In-memory is the default** — When no `outputDirectory` is set on `CompilerContext`, compilation happens entirely in memory using `InMemoryJavaFileManager` with Guava caching +3. **OSGi service ranking** — `CompilerServiceImpl` selects the highest-ranked `CompilerFactory` unless `preferEclipseCompiler` is set, in which case it filters for the Eclipse implementation +4. **Java 1.8 compilation target in CompilerUtil** — The actual compilation target inside `CompilerUtil` is hardcoded to Java 1.8 (`-source 1.8 -target 1.8`), regardless of the JDK version used to run the build +5. **ECJ two-pass compilation** — `EclipseCompilerWrapper` performs a two-pass compilation: module-info files first, then remaining sources +6. **Test resources** — Integration test source files are in `java-embedded-compiler-itest/src/test/resources/compile1/` (valid) and `compile2/` (intentionally invalid for error testing) +7. **All modules are OSGi bundles** — Every module is packaged as an OSGi bundle via Felix maven-bundle-plugin, exporting `hu.blackbelt.java.embedded.compiler.*` +8. **Class name typo** — `EclipeCompilerFactory` (missing 's' in Eclipse) is the actual class name in the ECJ module; do not "fix" this without a coordinated rename + +## Related Documentation + +- [README.md](README.md) — Project overview with architecture diagrams +- [CONTRIBUTING.md](CONTRIBUTING.md) — Development setup and submission guidelines +- [.github/CIFLOW.md](.github/CIFLOW.md) — CI/CD pipeline and GitFlow branching details diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..285e0f5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@./AGENTS.md diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc deleted file mode 100644 index d7738c9..0000000 --- a/CONTRIBUTING.adoc +++ /dev/null @@ -1,79 +0,0 @@ -# Contributing to java-embedded-compiler - -## Installing the correct versions of Java, Maven and necessary dependencies - -java-embedded-compiler is currently built on top of JDK 11. - -Contributors, and CI/CD agents are bot expected to use https://www.azul.com/downloads/?version=java-11-lts&package=jdk[Zulu JDK] - -Dependency management is handled by https://maven.apache.org/download.cgi[Maven v3.8.x] - -To check if you are using the correct version of Java, you may run `java -version`, which should display something similar: - -``` -openjdk version "11.0.14" 2022-01-18 LTS -OpenJDK Runtime Environment Zulu11.54+23-CA (build 11.0.14+9-LTS) -OpenJDK 64-Bit Server VM Zulu11.54+23-CA (build 11.0.14+9-LTS, mixed mode) -``` - -Checking Maven version is done via running `mvn -version`, which should display something similar to: - -``` -Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537) -Maven home: /opt/homebrew/Cellar/maven/3.8.4/libexec -``` - -## Code Structure - -This project follows a standard Java project structure, governed by Maven. - -TODO: JNG-3832 improve section explaining sub-module functionality - -## Submission Guidelines - -### Submitting an Issue - -Before you submit an issue, please search the issue tracker. An issue for your problem may already exist and has been -resolved, or the discussion might inform you of workarounds readily available. - -We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. Having a -reproducible scenario gives us wealth of important information without going back and forth with you requiring -additional information, such as: - -- the output of `java -version`, `mvn -version` -- `pom.xml` or `.flattened-pom.xml` (when applicable) -- and most importantly - a use-case that fails - -A minimal reproduction allows us to quickly confirm a bug (or point out a coding problem) as well as confirm that we are -fixing the right problem. - -We will be insisting on a minimal reproduction in order to save maintainers' time and ultimately be able to fix more -bugs. We understand that sometimes it might be hard to extract essentials bits of code from a larger codebase, but we -really need to isolate the problem before we can fix it. - -You can file new issues by filling out our https://github.com/BlackBeltTechnology/java-embedded-compiler/issues/new/choose[issue form]. - -### Submitting a PR - -This project follows https://guides.github.com/activities/forking/[GitHub's standard forking model]. Please fork the -project to submit pull requests. - -About the working Continous Integration pipeline, please read the corresponding link:.github/CIFLOW.adoc[CI Flow] -documentation! - - - - -## Commands - -### Run Tests - -```sh -$ mvn clean test -``` - -### Run Full build - -```sh -$ mvn clean install -``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..11bb4f5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,116 @@ +# Contributing to Java Embedded Compiler + +This guide covers everything you need to get a working development environment, understand the project structure, and submit changes. + +## Development Environment + +### Required Tools + +| Tool | Version | Notes | +|------|---------|-------| +| **JDK** | 11+ | [Azul Zulu JDK 11](https://www.azul.com/downloads/?version=java-11-lts&package=jdk) recommended. The CI uses JDK 21 to run the build, but source/target compatibility is Java 11. | +| **Maven** | 3.9.4+ | A Maven wrapper (`mvnw` / `mvnw.cmd`) is included in the repository — prefer using it over a system-installed Maven. | + +Verify your setup: + +```bash +java -version +# Expected: openjdk version "11.x.x" (or higher) + +./mvnw -version +# Expected: Apache Maven 3.9.4+ +``` + +### JVM Configuration + +The project ships a `.mvn/jvm.config` that automatically applies these JVM flags during the Maven build: + +- `-Xms1024m -Xmx2048m` — heap sizing for compilation-heavy builds +- `--add-opens` flags for `java.base/java.lang`, `java.base/java.util`, `java.base/java.time` — required for JDK 9+ module system compatibility + +You do not need to set these manually; Maven picks them up automatically. + +## Build Commands + +```bash +# Full build (compile + test + package + install locally) +./mvnw clean install + +# Run tests only +./mvnw clean test + +# Build without sub-modules (parent POM only) +./mvnw clean install -DskipModules=true + +# Skip tests +./mvnw clean install -DskipTests + +# Run a specific integration test +./mvnw test -Dtest=CompilerUtilITest -f java-embedded-compiler-itest/pom.xml +``` + +### Build Lifecycle + +```mermaid +flowchart LR + clean --> validate + validate --> compile + compile --> test + test --> package + package -->|"maven-bundle-plugin"| OSGi["OSGi Bundle"] + package --> verify + verify -->|"jacoco"| coverage["Coverage Report"] + verify --> install + install -->|"-Prelease-judong"| deploy["Deploy to Nexus"] + install -->|"-Psign-artifacts"| sign["Sign Artifacts"] +``` + +### Maven Profiles + +| Profile | Purpose | +|---------|---------| +| `modules` | Active by default. Includes all sub-modules. Disable with `-DskipModules=true`. | +| `sign-artifacts` | GPG-sign artifacts for Maven Central release. | +| `release-dummy` | Deploy to a local `/tmp/` directory (for testing the release process). | +| `release-judong` | Deploy to the Judong Nexus snapshot repository. | +| `release-central` | Deploy to Maven Central via Sonatype OSSRH. | +| `generate-github-asciidoc-diagrams` | Render PlantUML diagrams from `.github/` AsciiDoc files. | +| `update-source-code-license` | Update Apache 2.0 license headers in source files. | + +## Project Structure + +``` +java-embedded-compiler/ +├── java-embedded-compiler/ # Core API module +│ └── src/main/java/.../api/ +│ ├── CompilerContext.java # Builder for compilation config +│ ├── CompilerFactory.java # Strategy interface for compilers +│ ├── CompilerUtil.java # Main compilation entry point +│ ├── classloader/ # CompositeClassLoader, CompiledJavaFileObjectsClassLoader +│ ├── filemanager/ # InMemory, OutputDirectory, OSGi, CustomClassLoader managers +│ ├── fileobject/ # JavaFileObjects factory, Memory/ClassFile/OSGi file objects +│ └── exception/ # CompileException with diagnostics +├── java-embedded-compiler-jdt/ # JDK system compiler (ToolProvider) +├── java-embedded-compiler-ecj/ # Eclipse ECJ compiler +├── java-embedded-compiler-osgi/ # OSGi CompilerService (DS component) +├── java-embedded-compiler-itest/ # Karaf + Pax Exam integration tests +└── java-embedded-compiler-reports/ # JaCoCo aggregate coverage +``` + +## Submission Guidelines + +### Submitting an Issue + +Before filing a new issue, search the [issue tracker](https://github.com/BlackBeltTechnology/java-embedded-compiler/issues) for existing reports. When filing, include: + +- Output of `java -version` and `mvn -version` +- Relevant `pom.xml` or `.flattened-pom.xml` content +- A minimal reproduction case + +### Submitting a Pull Request + +This project follows [GitHub's standard forking model](https://guides.github.com/activities/forking/). Fork the repository and submit pull requests against the `develop` branch. + +> **Important:** Every commit must reference a JIRA ticket number (e.g. `JNG-xxx`). There is no commit without a ticket number. + +For full CI/CD workflow details, see [CIFLOW.md](.github/CIFLOW.md). diff --git a/README.adoc b/README.adoc deleted file mode 100644 index c24a03d..0000000 --- a/README.adoc +++ /dev/null @@ -1,17 +0,0 @@ -= java-embedded-compiler - -image::https://github.com/BlackBeltTechnology/java-embedded-compiler/actions/workflows/build.yml/badge.svg?branch=develop[link="https://github.com/BlackBeltTechnology/java-embedded-compiler/actions/workflows/build.yml" float="center"] - -== Introduction - -This is a Java compiler API which can be used for onthe fly generated class compilation and classloading in -standard java and OSGi environment. It support JDK supplied API and ECJ (Eclipse) compiler too. - -== Contributing to the project - -Everyone is welcome to contribute to java-embedded-compiler! As a starter, please read the corresponding link:CONTRIBUTING.adoc[CONTRIBUTING] guide for details! - - -== License - -This project is licensed under the https://www.apache.org/licenses/LICENSE-2.0[Apache License 2.0]. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1fc960d --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +# Java Embedded Compiler + +[![Build](https://github.com/BlackBeltTechnology/java-embedded-compiler/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/BlackBeltTechnology/java-embedded-compiler/actions/workflows/build.yml) + +## Introduction + +Java Embedded Compiler is a library that enables on-the-fly Java source compilation and class loading in both standard Java and OSGi environments. It provides a unified API over two pluggable compiler backends — the JDK system compiler (javac via `ToolProvider`) and the Eclipse Compiler for Java (ECJ) — so that application code can compile Java sources at runtime without being coupled to a specific compiler implementation. + +Typical use cases include dynamic code generation, runtime model transformations, and hot-deploying compiled classes inside an OSGi container such as Apache Karaf. + +## Module Overview + +The project is a multi-module Maven build consisting of six modules that layer on top of each other: + +```mermaid +graph TD + Core["java-embedded-compiler
(Core API)"] + JDT["java-embedded-compiler-jdt
(JDK System Compiler)"] + ECJ["java-embedded-compiler-ecj
(Eclipse Compiler)"] + OSGi["java-embedded-compiler-osgi
(OSGi Service Wrapper)"] + ITest["java-embedded-compiler-itest
(Integration Tests)"] + Reports["java-embedded-compiler-reports
(Coverage Reports)"] + + JDT --> Core + ECJ --> Core + OSGi --> Core + ITest -.->|test| Core + ITest -.->|test| JDT + ITest -.->|test| ECJ + ITest -.->|test| OSGi + Reports -.->|aggregate| Core +``` + +| Module | Purpose | +|--------|---------| +| `java-embedded-compiler` | Core API: `CompilerContext`, `CompilerUtil`, file managers, classloaders, and file objects | +| `java-embedded-compiler-jdt` | `JdtCompilerFactory` — wraps the JDK system compiler (`ToolProvider.getSystemJavaCompiler()`) | +| `java-embedded-compiler-ecj` | `EclipseCompilerFactory` — wraps the Eclipse ECJ compiler with a custom compilation task wrapper | +| `java-embedded-compiler-osgi` | `CompilerService` — an OSGi Declarative Services component that dynamically discovers and ranks compiler factories | +| `java-embedded-compiler-itest` | Karaf + Pax Exam integration tests that exercise all compilation modes in a real OSGi container | +| `java-embedded-compiler-reports` | JaCoCo coverage aggregation across modules | + +## Architecture + +### Compilation Flow + +The main entry point is `CompilerUtil.compile()`, which orchestrates the full compilation pipeline: + +```mermaid +sequenceDiagram + participant Client + participant CompilerUtil + participant CompilerFactory + participant FileManager + participant JavaCompiler + + Client->>CompilerUtil: compile(CompilerContext) + CompilerUtil->>CompilerFactory: getCompiler() + CompilerFactory-->>CompilerUtil: JavaCompiler + CompilerUtil->>FileManager: create (InMemory / OutputDir / OSGi) + CompilerUtil->>JavaCompiler: getTask(fileManager, sources, options) + JavaCompiler-->>CompilerUtil: CompilationTask + CompilerUtil->>JavaCompiler: task.call() + JavaCompiler-->>CompilerUtil: success/failure + CompilerUtil-->>Client: Iterable +``` + +### Class Diagram — Key Public APIs + +```mermaid +classDiagram + class CompilerFactory { + <> + +getCompiler() JavaCompiler + +getName() String + +getExtraArgs() List~String~ + } + class CompilerContext { + +bundleContext: BundleContext + +classLoader: ClassLoader + +compilationFiles: Iterable~File~ + +compilationUnits: Iterable~JavaFileObject~ + +outputDirectory: File + +compilerFactory: CompilerFactory + +preferEclipseCompiler: boolean + +includeDebugInfo: boolean + +disablePreprocessors: boolean + } + class CompilerUtil { + +compile(CompilerContext)$ Iterable~JavaFileObject~ + +compileAsClass(CompilerContext)$ Iterable~Class~ + } + class FileOutputManager { + <> + +getOutputJavaFileObjects() Iterable~JavaFileObject~ + } + class JdtCompilerFactory { + +getCompiler() JavaCompiler + } + class EclipseCompilerFactory { + +getCompiler() JavaCompiler + } + + CompilerFactory <|.. JdtCompilerFactory + CompilerFactory <|.. EclipseCompilerFactory + CompilerUtil --> CompilerContext + CompilerUtil --> CompilerFactory + FileOutputManager <|.. InMemoryJavaFileManager + FileOutputManager <|.. StaticOutputDirectoryJavaFileManager +``` + +### Dependency Graph + +```mermaid +graph LR + subgraph External + Guava["Google Guava 30.0"] + OSGiCore["OSGi Core 6.0"] + OSGiDS["OSGi DS Annotations"] + SLF4J["SLF4J 2.0"] + Lombok["Lombok 1.18"] + EclipsePlatform["Eclipse Platform 4.18
(JDT / ECJ)"] + Karaf["Apache Karaf 4.4"] + PaxExam["Pax Exam 4.13"] + end + subgraph Project + Core["Core API"] --> Guava + Core --> OSGiCore + Core --> SLF4J + Core --> Lombok + JDT["JDT Module"] --> EclipsePlatform + ECJ["ECJ Module"] --> EclipsePlatform + OSGiMod["OSGi Module"] --> OSGiDS + ITest["Integration Tests"] --> Karaf + ITest --> PaxExam + end +``` + +## Quick Start + +```java +// In-memory compilation from source string +CompilerContext context = CompilerContext.builder() + .compilationUnits(ImmutableList.of( + JavaFileObjects.forSourceString("com.example.Hello", + "package com.example; public class Hello { }") + )) + .build(); + +Iterable compiled = CompilerUtil.compile(context); + +// Compile and load classes directly +Iterable classes = CompilerUtil.compileAsClass(context); +``` + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, submission guidelines, and CI workflow details. + +## License + +This project is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..d60b6d0 --- /dev/null +++ b/llms.txt @@ -0,0 +1,47 @@ +# Java Embedded Compiler + +> A library for compiling Java source code at runtime with pluggable compiler backends (JDK javac and Eclipse ECJ), supporting both in-memory and file-based output in standard Java and OSGi environments. + +Key facts: +- Java 11 (source and target compatibility) +- Maven 3.9.4 multi-module build, 6 modules +- OSGi bundles with Declarative Services (Felix maven-bundle-plugin) +- Pluggable compiler backends: JDK system compiler (javac) and Eclipse ECJ +- In-memory and file-based compilation with flexible classloading +- ServiceLoader discovery in standard Java, OSGi DS in containers +- Apache License 2.0 + +## Docs + +- [README.md](README.md): Project overview with architecture diagrams and quick start guide +- [AGENTS.md](AGENTS.md): Developer guide with build commands, module details, and coding conventions +- [CONTRIBUTING.md](CONTRIBUTING.md): Development setup and submission guidelines +- [.github/CIFLOW.md](.github/CIFLOW.md): CI/CD pipeline and GitFlow branching details + +## Modules — Core API + +- [java-embedded-compiler/pom.xml](java-embedded-compiler/pom.xml): Core API with CompilerContext (builder), CompilerFactory (strategy interface), CompilerUtil (static compilation entry point), file managers, classloaders, and file objects + +## Modules — Compiler Implementations + +- [java-embedded-compiler-jdt/pom.xml](java-embedded-compiler-jdt/pom.xml): JdtCompilerFactory — wraps the JDK system compiler (ToolProvider.getSystemJavaCompiler()), requires a full JDK at runtime +- [java-embedded-compiler-ecj/pom.xml](java-embedded-compiler-ecj/pom.xml): EclipseCompilerFactory — wraps the Eclipse ECJ compiler with two-pass compilation (module-info first, then sources), works with just a JRE + +## Modules — Runtime & Integration + +- [java-embedded-compiler-osgi/pom.xml](java-embedded-compiler-osgi/pom.xml): CompilerService OSGi DS component that dynamically discovers and ranks CompilerFactory services +- [java-embedded-compiler-itest/pom.xml](java-embedded-compiler-itest/pom.xml): Karaf 4.4.7 + Pax Exam integration tests covering all compilation modes for both compiler backends +- [java-embedded-compiler-reports/pom.xml](java-embedded-compiler-reports/pom.xml): JaCoCo aggregate coverage report across all modules + +## Used by + +- [judo-platform](../../runtime/judo-platform): Model-driven OSGi runtime platform with REST APIs and Karaf deployment +- [judo-runtime-core-esm-itest](../../runtime/judo-runtime-core-esm-itest): Integration tests for ESM-to-RDBMS transformation pipeline +- [judo-services](../../runtime/judo-services): OSGi runtime services enabling multiple data models in a single Karaf container +- [judo-tatami](../../runtime/judo-tatami): Model transformation pipeline — ESM/PSM to deployable application artifacts +- [judo-tatami-base](../../runtime/judo-tatami-base): PSM transformation pipeline — models to schemas, expressions, and configurations + +## Optional + +- [.github/workflows/build.yml](.github/workflows/build.yml): GitHub Actions CI/CD pipeline (build, test, deploy, tag, release) +- [LICENSE.txt](LICENSE.txt): Apache License 2.0 diff --git a/openspec/specs/core-api/spec.md b/openspec/specs/core-api/spec.md new file mode 100644 index 0000000..e94d4c1 --- /dev/null +++ b/openspec/specs/core-api/spec.md @@ -0,0 +1,142 @@ +# core-api Specification + +## Purpose + +The core API module (`java-embedded-compiler`) defines the compilation framework's public contracts, utility classes, classloaders, file managers, and file objects that enable runtime Java source compilation and class loading. + +## Architecture + +The module is organized into five packages under `hu.blackbelt.java.embedded.compiler.api`: + +- **Root package** — `CompilerFactory` (strategy interface), `CompilerContext` (builder configuration), `CompilerUtil` (static entry point), `FileOutputManager` (output abstraction), `FullyQualifiedName` (metadata interface) +- **classloader** — `CompositeClassLoader` (chains multiple classloaders), `CompiledJavaFileObjectsClassLoader` (loads compiled bytecode from JavaFileObjects) +- **filemanager** — `ForwardingStandardJavaFileManager`, `CustomClassLoaderJavaFileManager`, `InMemoryJavaFileManager`, `StaticOutputDirectoryJavaFileManager`, `OsgiJavaFileManager`, `CustomClassLoaderPackageInternalsFinder` +- **fileobject** — `JavaFileObjects` (factory), `MemoryJavaFileObject`, `ClassFileObject`, `CustomClasLoaderJavaFileObject`, `OsgiJavaFileObject`, `OsgiJavaFileFolder` +- **exception** — `CompileException` (wraps compiler diagnostics) + +## Requirements + +### Requirement: CompilerFactory strategy contract + +The `CompilerFactory` interface SHALL provide a pluggable strategy for obtaining a `JavaCompiler` instance, a human-readable name, and optional extra compiler arguments. + +#### Scenario: Obtain a compiler instance +- **GIVEN** a `CompilerFactory` implementation is available +- **WHEN** `getCompiler()` is called +- **THEN** a valid `javax.tools.JavaCompiler` instance is returned + +#### Scenario: Retrieve compiler-specific extra arguments +- **GIVEN** a `CompilerFactory` implementation +- **WHEN** `getExtraArgs()` is called +- **THEN** a list of additional compiler arguments is returned (may be empty) + +### Requirement: CompilerContext configuration builder + +`CompilerContext` SHALL use the builder pattern to configure all aspects of a compilation: source input, classloader, output destination, compiler preference, and compilation options. + +#### Scenario: Build a minimal in-memory compilation context +- **GIVEN** a set of `JavaFileObject` compilation units +- **WHEN** `CompilerContext.builder().compilationUnits(units).build()` is called +- **THEN** a `CompilerContext` is created with default values: `includeDebugInfo=true`, `preferEclipseCompiler=false`, `disablePreprocessors=true`, no output directory (in-memory mode) + +#### Scenario: Build a file-to-directory compilation context +- **GIVEN** a set of source `File` objects and an output `File` directory +- **WHEN** `CompilerContext.builder().compilationFiles(files).outputDirectory(dir).build()` is called +- **THEN** a `CompilerContext` is created targeting filesystem output + +#### Scenario: Custom classloader initialization +- **GIVEN** a `CompilerContext.Builder` with `sameClassLoaderAs(SomeClass.class)` set +- **WHEN** the `CompilerContext` is constructed +- **THEN** the classloader of `SomeClass` is added to the internal `CompositeClassLoader` + +### Requirement: CompilerUtil compilation orchestration + +`CompilerUtil.compile()` SHALL orchestrate the full compilation pipeline: factory discovery, file manager creation, compiler task execution, and result collection. + +#### Scenario: Compile with ServiceLoader discovery +- **GIVEN** a `CompilerContext` with no explicit `compilerFactory` set +- **WHEN** `CompilerUtil.compile(context)` is called +- **THEN** `ServiceLoader.load(CompilerFactory.class)` is used to find the first available factory + +#### Scenario: Compile with explicit factory +- **GIVEN** a `CompilerContext` with `compilerFactory` explicitly set +- **WHEN** `CompilerUtil.compile(context)` is called +- **THEN** the provided factory is used directly + +#### Scenario: Compile to in-memory output +- **GIVEN** a `CompilerContext` with no `outputDirectory` and no `bundleContext` +- **WHEN** `CompilerUtil.compile(context)` is called +- **THEN** an `InMemoryJavaFileManager` is used and compiled `JavaFileObject` instances are returned + +#### Scenario: Compile to output directory +- **GIVEN** a `CompilerContext` with an `outputDirectory` set +- **WHEN** `CompilerUtil.compile(context)` is called +- **THEN** a `StaticOutputDirectoryJavaFileManager` writes `.class` files to the specified directory + +#### Scenario: Compile in OSGi environment +- **GIVEN** a `CompilerContext` with a `bundleContext` and no `outputDirectory` +- **WHEN** `CompilerUtil.compile(context)` is called +- **THEN** an `OsgiJavaFileManager` wrapping an `InMemoryJavaFileManager` is used for bundle-aware class resolution + +#### Scenario: Compilation failure +- **GIVEN** source code with syntax errors +- **WHEN** `CompilerUtil.compile(context)` is called +- **THEN** a `CompileException` is thrown containing the `List` of ERROR-level issues + +### Requirement: CompilerUtil class loading + +`CompilerUtil.compileAsClass()` SHALL compile sources and immediately load them as `Class` objects. + +#### Scenario: Compile and load classes +- **GIVEN** valid source code +- **WHEN** `CompilerUtil.compileAsClass(context)` is called +- **THEN** compiled classes are loaded via `CompiledJavaFileObjectsClassLoader` and returned as `Iterable` + +### Requirement: CompositeClassLoader delegation chain + +`CompositeClassLoader` SHALL delegate class loading across an ordered list of child classloaders, trying each in sequence. + +#### Scenario: Class found in second loader +- **GIVEN** a `CompositeClassLoader` with two child classloaders, where only the second can load class `com.example.Foo` +- **WHEN** `findClass("com.example.Foo")` is called +- **THEN** the class is loaded from the second child classloader + +#### Scenario: Append a new classloader at runtime +- **GIVEN** an existing `CompositeClassLoader` +- **WHEN** `append(newClassLoader)` is called +- **THEN** the new classloader is added to the end of the delegation chain (thread-safe via CopyOnWriteArrayList) + +### Requirement: InMemoryJavaFileManager caching + +`InMemoryJavaFileManager` SHALL store compiled bytecode in memory using Guava's `LoadingCache`. + +#### Scenario: Retrieve compiled output +- **GIVEN** a successful compilation through `InMemoryJavaFileManager` +- **WHEN** `getOutputJavaFileObjects()` is called +- **THEN** an `ImmutableList` containing all compiled class bytecode is returned + +### Requirement: JavaFileObjects factory methods + +`JavaFileObjects` SHALL provide static factory methods for creating `JavaFileObject` instances from various sources. + +#### Scenario: Create from source string +- **WHEN** `JavaFileObjects.forSourceString("com.example.Hello", sourceCode)` is called +- **THEN** a `JavaFileObject` of kind `SOURCE` is returned with the given content + +#### Scenario: Create from resource URL +- **WHEN** `JavaFileObjects.forResource(url)` is called +- **THEN** a `JavaFileObject` wrapping the URL resource is returned + +### Requirement: OsgiJavaFileManager bundle awareness + +`OsgiJavaFileManager` SHALL discover and list Java classes from OSGi bundles and respond to bundle lifecycle events. + +#### Scenario: List classes from active bundles +- **GIVEN** an OSGi environment with active bundles +- **WHEN** `list(PLATFORM_CLASS_PATH, "com.example", kinds, recurse)` is called +- **THEN** classes from active bundles matching the package are returned + +#### Scenario: Cache invalidation on bundle change +- **GIVEN** cached bundle class information +- **WHEN** a `BundleEvent` is received +- **THEN** the cached OSGi file folders are cleared and re-discovered on next access diff --git a/openspec/specs/ecj-compiler/spec.md b/openspec/specs/ecj-compiler/spec.md new file mode 100644 index 0000000..03d7c04 --- /dev/null +++ b/openspec/specs/ecj-compiler/spec.md @@ -0,0 +1,60 @@ +# ecj-compiler Specification + +## Purpose + +The ECJ compiler module (`java-embedded-compiler-ecj`) provides a `CompilerFactory` implementation that wraps the Eclipse Compiler for Java (ECJ), enabling compilation without requiring a full JDK. + +## Architecture + +Two classes: +- `hu.blackbelt.java.embedded.compiler.ecj.EclipeCompilerFactory` — OSGi DS `@Component` implementing `CompilerFactory`, registered with property `compileType=eclipse` +- `hu.blackbelt.java.embedded.compiler.ecj.EclipseCompilerWrapper` — Static utility that wraps `org.eclipse.jdt.internal.compiler.tool.EclipseCompilerImpl` to handle two-pass compilation and module-info processing + +## Requirements + +### Requirement: Eclipse compiler provisioning + +`EclipeCompilerFactory` SHALL provide an Eclipse-based `JavaCompiler` whose `getTask()` method delegates to `EclipseCompilerWrapper`. + +#### Scenario: Obtain Eclipse compiler +- **WHEN** `getCompiler()` is called +- **THEN** a `JavaCompiler` instance is returned that uses the ECJ compiler internally + +### Requirement: Factory identity + +`EclipeCompilerFactory` SHALL identify itself as the "eclipse" compiler. + +#### Scenario: Query compiler name +- **WHEN** `getName()` is called +- **THEN** the string `"eclipse"` is returned + +### Requirement: Warning suppression by default + +`EclipeCompilerFactory` SHALL suppress Eclipse-specific warnings by default. + +#### Scenario: Query extra args +- **WHEN** `getExtraArgs()` is called +- **THEN** a list containing `"-warn:none"` is returned + +### Requirement: Two-pass compilation + +`EclipseCompilerWrapper.getTask()` SHALL perform a two-pass compilation: module-info files first, then remaining source files. + +#### Scenario: Compilation with module-info +- **GIVEN** a set of compilation units including `module-info.java` and regular Java sources +- **WHEN** `EclipseCompilerWrapper.getTask()` is called +- **THEN** module-info files are compiled in a first pass, and remaining sources in a second pass + +#### Scenario: Compilation without module-info +- **GIVEN** a set of compilation units with no `module-info.java` +- **WHEN** `EclipseCompilerWrapper.getTask()` is called +- **THEN** all sources are compiled in a single pass + +### Requirement: OSGi service registration + +`EclipeCompilerFactory` SHALL be discoverable as an OSGi service with the `compileType=eclipse` property. + +#### Scenario: Service lookup in OSGi container +- **GIVEN** the ECJ bundle is active in an OSGi container +- **WHEN** a service lookup for `CompilerFactory` with filter `(compileType=eclipse)` is performed +- **THEN** the `EclipeCompilerFactory` instance is returned diff --git a/openspec/specs/integration-tests/spec.md b/openspec/specs/integration-tests/spec.md new file mode 100644 index 0000000..4098c71 --- /dev/null +++ b/openspec/specs/integration-tests/spec.md @@ -0,0 +1,87 @@ +# integration-tests Specification + +## Purpose + +The integration test module (`java-embedded-compiler-itest`) verifies that all compilation modes work correctly inside a real OSGi container (Apache Karaf) using Pax Exam as the test driver. + +## Architecture + +- `hu.blackbelt.java.embedded.compiler.itest.CompilerUtilITest` — JUnit 4 test class annotated with `@RunWith(PaxExam.class)` and `@ExamReactorStrategy(PerClass.class)`. Injects `BundleContext` and `CompilerService` from the OSGi container. Tests all combinations of compiler backend (JDT/ECJ) and output mode (file/memory/class). +- `hu.blackbelt.java.embedded.compiler.itest.KarafFeatureProvider` — Utility class that configures the Karaf container, provisions test bundles, sets up JVM options for JDK 9+ module access, and provides OSGi service lookup helpers. +- Test resources: `compile1/` (valid Northwind service sources), `compile2/` (intentionally invalid sources for error testing), `test-features.xml` (Karaf feature definitions). + +## Requirements + +### Requirement: File-to-output-directory compilation + +The integration tests SHALL verify that Java source files can be compiled to `.class` files in a filesystem output directory. + +#### Scenario: JDT file-to-directory compilation +- **GIVEN** valid Java source files in `compile1/` and the JDT compiler factory +- **WHEN** `CompilerUtil.compile()` is called with an `outputDirectory` set +- **THEN** compiled `.class` files are written to the output directory + +#### Scenario: ECJ file-to-directory compilation +- **GIVEN** valid Java source files in `compile1/` and the Eclipse compiler factory (via `preferEclipseCompiler=true`) +- **WHEN** `CompilerUtil.compile()` is called with an `outputDirectory` set +- **THEN** compiled `.class` files are written to the output directory + +### Requirement: File-to-memory compilation + +The integration tests SHALL verify that Java source files can be compiled to in-memory `JavaFileObject` instances. + +#### Scenario: JDT file-to-memory compilation +- **GIVEN** valid Java source files in `compile1/` and the JDT compiler factory +- **WHEN** `CompilerUtil.compile()` is called without an `outputDirectory` +- **THEN** compiled `JavaFileObject` instances are returned in memory + +#### Scenario: ECJ file-to-memory compilation +- **GIVEN** valid Java source files in `compile1/` and the Eclipse compiler factory +- **WHEN** `CompilerUtil.compile()` is called without an `outputDirectory` +- **THEN** compiled `JavaFileObject` instances are returned in memory + +### Requirement: String-to-memory compilation + +The integration tests SHALL verify that Java source provided as strings (via `JavaFileObjects.forSourceString()`) can be compiled in memory. + +#### Scenario: JDT string-to-memory compilation +- **GIVEN** Java source code as a string and the JDT compiler factory +- **WHEN** `CompilerUtil.compile()` is called with `compilationUnits` containing string-based `JavaFileObject` instances +- **THEN** compiled `JavaFileObject` instances are returned + +#### Scenario: ECJ string-to-memory compilation +- **GIVEN** Java source code as a string and the Eclipse compiler factory +- **WHEN** `CompilerUtil.compile()` is called with string-based compilation units +- **THEN** compiled `JavaFileObject` instances are returned + +### Requirement: Compile-as-class loading + +The integration tests SHALL verify that compiled bytecode can be immediately loaded as `Class` objects. + +#### Scenario: JDT compile and load +- **GIVEN** valid source files and the JDT compiler factory +- **WHEN** `CompilerUtil.compileAsClass()` is called +- **THEN** loaded `Class` objects are returned and can be instantiated + +#### Scenario: ECJ compile and load +- **GIVEN** valid source files and the Eclipse compiler factory +- **WHEN** `CompilerUtil.compileAsClass()` is called +- **THEN** loaded `Class` objects are returned and can be instantiated + +### Requirement: Compilation error handling + +The integration tests SHALL verify that invalid source code produces a `CompileException` with meaningful diagnostics. + +#### Scenario: Compilation with syntax errors +- **GIVEN** invalid Java source files in `compile2/` +- **WHEN** `CompilerUtil.compile()` is called +- **THEN** a `CompileException` is thrown containing diagnostics with ERROR-level entries + +### Requirement: OSGi container integration + +The integration tests SHALL run inside a fully provisioned Apache Karaf container with all compiler bundles deployed. + +#### Scenario: CompilerService availability +- **GIVEN** the Karaf container is started with all compiler bundles provisioned +- **WHEN** the test class is injected +- **THEN** `CompilerService` is available via `@Inject` and both JDT and ECJ factories are registered diff --git a/openspec/specs/jdt-compiler/spec.md b/openspec/specs/jdt-compiler/spec.md new file mode 100644 index 0000000..9990181 --- /dev/null +++ b/openspec/specs/jdt-compiler/spec.md @@ -0,0 +1,53 @@ +# jdt-compiler Specification + +## Purpose + +The JDT compiler module (`java-embedded-compiler-jdt`) provides a `CompilerFactory` implementation that wraps the JDK system Java compiler accessed via `javax.tools.ToolProvider`. + +## Architecture + +Single class: `hu.blackbelt.java.embedded.compiler.jdt.JdtCompilerFactory` +- Implements `CompilerFactory` interface from the core API +- Registered as an OSGi DS `@Component` with property `compileType=system` +- Delegates to `ToolProvider.getSystemJavaCompiler()` for the actual compiler instance + +## Requirements + +### Requirement: System compiler provisioning + +`JdtCompilerFactory` SHALL provide the JDK system Java compiler via `ToolProvider.getSystemJavaCompiler()`. + +#### Scenario: JDK is available +- **GIVEN** the runtime is a full JDK (not a JRE) +- **WHEN** `getCompiler()` is called +- **THEN** the system `JavaCompiler` instance is returned + +#### Scenario: Only JRE is available +- **GIVEN** the runtime is a JRE without compiler tools +- **WHEN** `getCompiler()` is called +- **THEN** an `IllegalStateException` is thrown indicating JDK is required + +### Requirement: Factory identity + +`JdtCompilerFactory` SHALL identify itself as the "system" compiler. + +#### Scenario: Query compiler name +- **WHEN** `getName()` is called +- **THEN** the string `"system"` is returned + +### Requirement: No extra compiler arguments + +`JdtCompilerFactory` SHALL not inject additional compiler arguments. + +#### Scenario: Query extra args +- **WHEN** `getExtraArgs()` is called +- **THEN** an empty list is returned + +### Requirement: OSGi service registration + +`JdtCompilerFactory` SHALL be discoverable as an OSGi service with the `compileType=system` property. + +#### Scenario: Service lookup in OSGi container +- **GIVEN** the JDT bundle is active in an OSGi container +- **WHEN** a service lookup for `CompilerFactory` with filter `(compileType=system)` is performed +- **THEN** the `JdtCompilerFactory` instance is returned diff --git a/openspec/specs/osgi-service/spec.md b/openspec/specs/osgi-service/spec.md new file mode 100644 index 0000000..09fc166 --- /dev/null +++ b/openspec/specs/osgi-service/spec.md @@ -0,0 +1,54 @@ +# osgi-service Specification + +## Purpose + +The OSGi service module (`java-embedded-compiler-osgi`) wraps the core compilation API as an OSGi Declarative Services component, dynamically discovering and ranking available `CompilerFactory` services. + +## Architecture + +- `hu.blackbelt.java.embedded.compiler.osgi.CompilerService` — Service interface exposing `getCompilerFactory()`, `compile()`, and `compileAsClass()` methods +- `hu.blackbelt.java.embedded.compiler.osgi.CompilerServiceImpl` — DS `@Component` implementation that manages `CompilerFactory` registrations via `@Reference(cardinality=MULTIPLE, policy=DYNAMIC)`, stores them in a `ConcurrentHashMap` keyed by a `ServiceKey` (comparable by rank and name), and delegates compilation to `CompilerUtil` + +## Requirements + +### Requirement: Dynamic compiler factory registration + +`CompilerServiceImpl` SHALL dynamically register and unregister `CompilerFactory` services as they appear and disappear in the OSGi service registry. + +#### Scenario: New CompilerFactory service appears +- **GIVEN** the `CompilerServiceImpl` component is active +- **WHEN** a new `CompilerFactory` service is registered with `SERVICE_RANKING` property +- **THEN** the factory is stored in the internal map keyed by its rank and name + +#### Scenario: CompilerFactory service removed +- **GIVEN** a registered `CompilerFactory` service +- **WHEN** the service is unregistered from the OSGi registry +- **THEN** the factory is removed from the internal map + +### Requirement: Compiler selection by preference + +`CompilerServiceImpl.getCompilerFactory()` SHALL select the appropriate compiler based on the `CompilerContext.preferEclipseCompiler` flag. + +#### Scenario: Default selection (no preference) +- **GIVEN** multiple registered `CompilerFactory` services with different rankings +- **WHEN** `getCompilerFactory(context)` is called with `preferEclipseCompiler=false` +- **THEN** the factory with the highest `SERVICE_RANKING` is returned + +#### Scenario: Eclipse compiler preferred +- **GIVEN** both system and eclipse `CompilerFactory` services are registered +- **WHEN** `getCompilerFactory(context)` is called with `preferEclipseCompiler=true` +- **THEN** the factory with `getName().equals("eclipse")` is returned + +### Requirement: Compilation delegation + +`CompilerServiceImpl` SHALL delegate actual compilation to `CompilerUtil` with the selected factory. + +#### Scenario: Compile via service +- **GIVEN** an active `CompilerServiceImpl` with at least one `CompilerFactory` +- **WHEN** `compile(compilerContext)` is called +- **THEN** `CompilerUtil.compile()` is invoked with the context's `compilerFactory` set to the selected factory + +#### Scenario: Compile as class via service +- **GIVEN** an active `CompilerServiceImpl` with at least one `CompilerFactory` +- **WHEN** `compileAsClass(compilerContext)` is called +- **THEN** `CompilerUtil.compileAsClass()` is invoked with the selected factory