Skip to content

feat(#4846): implement a simple caching mechanism in the Maven plugin#4847

Merged
yegor256 merged 2 commits into
objectionary:masterfrom
volodya-lombrozo:4846-new-cache
Feb 3, 2026
Merged

feat(#4846): implement a simple caching mechanism in the Maven plugin#4847
yegor256 merged 2 commits into
objectionary:masterfrom
volodya-lombrozo:4846-new-cache

Conversation

@volodya-lombrozo

@volodya-lombrozo volodya-lombrozo commented Feb 2, 2026

Copy link
Copy Markdown
Member

This PR introduces a new Cache class for improved caching functionality in the eo-maven-plugin.

Related to #4846

Summary by CodeRabbit

  • New Features

    • Added a caching mechanism to improve build performance by reusing compiled output when sources are unchanged.
    • Detects source changes and writes updated compiled output to the cache to ensure targets stay current.
  • Tests

    • Added unit tests validating caching behavior: initial caching and reuse when sources remain unchanged.

Copilot AI review requested due to automatic review settings February 2, 2026 15:08
@coderabbitai

coderabbitai Bot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a package-private Cache class that computes Base64 SHA-256 of a source file, stores compiled outputs and hashes under a base cache directory, and writes cached or newly compiled content to target files. Includes unit tests verifying cache population and reuse.

Changes

Cohort / File(s) Summary
Cache Implementation
eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java
New package-private final Cache class with apply(Path source, Path target, Path tail) that computes Base64-encoded SHA-256 of source, checks/updates a stored hash and cached content, invokes a compilation function on miss, and writes results to cache and target.
Cache Tests
eo-maven-plugin/src/test/java/org/eolang/maven/CacheTest.java
New JUnit 5 tests (two methods) that verify cache population and reuse: one ensures a compiled source is added to cache and files are created; the other ensures a second apply reads from cache (no recompile). Uses temporary directories and a counter-based compilation function.

Sequence Diagram(s)

sequenceDiagram
    participant Source as Source File
    participant Cache as Cache Instance
    participant Storage as Hash & Cache Storage
    participant Comp as Compilation Function
    participant Target as Target File

    Source->>Cache: apply(source, target, tail)
    Cache->>Source: compute SHA-256 (Base64)
    Cache->>Storage: read stored hash & cached content
    alt hash exists and matches
        Storage-->>Cache: return cached content
        Cache->>Target: write cached content
    else missing or mismatch
        Storage-->>Cache: missing or differs
        Cache->>Comp: invoke compilation(source)
        Comp-->>Cache: return compiled content
        Cache->>Storage: write new hash and cached content
        Cache->>Target: write compiled content
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • yegor256

Poem

🐰 I found a hash beneath the ground,
I stored the code and traced the sound,
If nothing changed, I skip the race —
I hop, I read, I save you grace.
— — HopRabbit 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing a simple caching mechanism in the Maven plugin, which directly matches the new Cache class and its functionality introduced in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Failure to add the new IP will result in interrupted reviews.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java`:
- Around line 58-59: The cache branch in Cache currently assumes cfile exists
and calls Files.readString(cfile), causing a failure if the cached file was
deleted; update the cache-hit condition (in class Cache where cfile and target
are handled) to treat Files.notExists(cfile) as a cache miss (i.e., include
Files.exists(cfile) in the hit check or add Files.notExists(cfile) to the miss
condition) so you only call Files.readString(cfile) when the file actually
exists and otherwise recompile/write the fresh content to target.
- Around line 55-56: The write calls Files.writeString(this.hash(tail), hash)
and Files.writeString(cfile, content) can fail if target parent directories
don't exist; in the Cache class ensure you create parent directories before
writing by invoking Files.createDirectories(...) on the parent path(s) of
this.hash(tail) and of cfile (or their resolved Paths) prior to the
Files.writeString calls so the directory tree exists.
- Around line 76-78: In Cache.hash, avoid path-escape by resolving then
normalizing the tail and enforcing it remains under base: compute full =
this.base.resolve(tail).normalize(), then verify full.startsWith(this.base) and
throw an IllegalArgumentException (or similar) if it does not; finally return
full.getParent().resolve(String.format("%s.sha256",
full.getFileName().toString())). This ensures absolute tails or '/../' segments
cannot escape the cache base.
- Line 54: In Cache.fix the compilation call: the
UncheckedFunc<>(this.compilation).apply(...) is currently passed target but
should be passed source so the compilation function reads the original source
file (variable name source) rather than the destination target; update the call
that creates content to use source and ensure it still assigns to final String
content so downstream logic (hashing/target writing) operates on compiled output
from this.compilation.
🧹 Nitpick comments (1)
eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java (1)

88-90: Stream hashing to avoid loading large files into memory.

Files.readAllBytes loads the full source into RAM. For large sources, a streaming digest is more memory‑friendly.

♻️ Suggested refactor
-        final byte[] hash = digest.digest(Files.readAllBytes(file));
-        return Base64.getEncoder().encodeToString(hash);
+        try (var input = Files.newInputStream(file);
+            var dis = new java.security.DigestInputStream(input, digest)) {
+            final byte[] buffer = new byte[8192];
+            while (dis.read(buffer) != -1) {
+                // consume stream
+            }
+        }
+        return Base64.getEncoder().encodeToString(digest.digest());

Comment thread eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java Outdated
Comment on lines +55 to +56
Files.writeString(this.hash(tail), hash);
Files.writeString(cfile, content);

@coderabbitai coderabbitai Bot Feb 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Create parent directories before writing cache files.

Lines 55–56 will throw NoSuchFileException when tail includes subdirectories. Ensure the cache parent directory exists before writing hash/content.

✅ Proposed fix
             final Path hfile = this.hash(tail);
             final Path cfile = this.base.resolve(tail);
+            final Path parent = cfile.getParent();
+            if (parent != null) {
+                Files.createDirectories(parent);
+            }
             if (Files.notExists(hfile) || !Files.readString(hfile).equals(hash)) {
🤖 Prompt for AI Agents
In `@eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java` around lines 55 -
56, The write calls Files.writeString(this.hash(tail), hash) and
Files.writeString(cfile, content) can fail if target parent directories don't
exist; in the Cache class ensure you create parent directories before writing by
invoking Files.createDirectories(...) on the parent path(s) of this.hash(tail)
and of cfile (or their resolved Paths) prior to the Files.writeString calls so
the directory tree exists.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@volodya-lombrozo this comment sounds reasonable to me

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java Outdated
Comment on lines +76 to +78
private Path hash(final Path tail) {
final Path full = this.base.resolve(tail);
return full.getParent().resolve(String.format("%s.sha256", full.getFileName().toString()));

@coderabbitai coderabbitai Bot Feb 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent cache path escape via absolute or .. tail.

Line 77 resolves tail directly; if tail is absolute or contains .., the cache can write outside base. Normalize and enforce that the resolved path stays within base.

🔒 Proposed fix
-        final Path full = this.base.resolve(tail);
-        return full.getParent().resolve(String.format("%s.sha256", full.getFileName().toString()));
+        final Path base = this.base.normalize();
+        final Path full = base.resolve(tail).normalize();
+        if (!full.startsWith(base)) {
+            throw new IllegalArgumentException("Cache tail must be within base directory");
+        }
+        return full.getParent().resolve(String.format("%s.sha256", full.getFileName().toString()));
🤖 Prompt for AI Agents
In `@eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java` around lines 76 -
78, In Cache.hash, avoid path-escape by resolving then normalizing the tail and
enforcing it remains under base: compute full =
this.base.resolve(tail).normalize(), then verify full.startsWith(this.base) and
throw an IllegalArgumentException (or similar) if it does not; finally return
full.getParent().resolve(String.format("%s.sha256",
full.getFileName().toString())). This ensures absolute tails or '/../' segments
cannot escape the cache base.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@volodya-lombrozo this comment sounds reasonable to me

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@volodya-lombrozo

Copy link
Copy Markdown
Member Author

@yegor256 could you have a look, please? This is a new caching mechanism proposal. It uses hashing instead of relying on timestamps. Here is the explanation why it's a bad idea to rely on time:

(AI)
Problems:

Clock skew between machines/filesystems (especially distributed builds)
Sub-second precision loss (FAT/older filesystems)
File copy operations update timestamps incorrectly
Antivirus scanners modify timestamps
Network filesystems (NFS, SMB) have unreliable timestamps
DST changes mess up timestamps

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new Cache class intended to provide hash-based caching for the EO Maven plugin. The PR is related to issue #4846 about inadequate caching implementation.

Changes:

  • Added a new Cache class with SHA-256 hash-based cache validation mechanism
  • Added comprehensive unit tests for the Cache class in CacheTest

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 12 comments.

File Description
eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java New cache implementation using SHA-256 hashes to validate cache freshness and avoid timestamp-based validation
eo-maven-plugin/src/test/java/org/eolang/maven/CacheTest.java Test suite for the new Cache class covering compilation, cache hits, and cache invalidation scenarios

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread eo-maven-plugin/src/test/java/org/eolang/maven/CacheTest.java Outdated
Comment on lines +48 to +60
public void apply(final Path source, final Path target, final Path tail) {
try {
final String hash = Cache.sha(source);
final Path hfile = this.hash(tail);
final Path cfile = this.base.resolve(tail);
if (Files.notExists(hfile) || !Files.readString(hfile).equals(hash)) {
final String content = new UncheckedFunc<>(this.compilation).apply(source);
Files.writeString(this.hash(tail), hash);
Files.writeString(cfile, content);
Files.writeString(target, content);
} else {
Files.writeString(target, Files.readString(cfile));
}

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Cache class lacks thread-safety mechanisms. If multiple threads call the apply method concurrently with the same source file, there could be race conditions where both threads determine the cache is stale and attempt to write simultaneously, potentially leading to corrupted files or inconsistent state. Consider adding synchronization or using atomic file operations. See ChCachedTest.java:63-86 for an example of how concurrency is tested in similar caching classes in this codebase.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@volodya-lombrozo this comment sounds reasonable to me

Comment thread eo-maven-plugin/src/test/java/org/eolang/maven/CacheTest.java
*/
private static String sha(final Path file) throws NoSuchAlgorithmException, IOException {
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
final byte[] hash = digest.digest(Files.readAllBytes(file));

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sha method reads the entire file into memory using Files.readAllBytes(file) which could cause OutOfMemoryError for large files. Consider using a streaming approach with MessageDigest.update() in a loop to hash the file in chunks, similar to how it's typically done for large file hashing operations.

Suggested change
final byte[] hash = digest.digest(Files.readAllBytes(file));
try (var stream = Files.newInputStream(file)) {
final byte[] buf = new byte[8192];
int read = stream.read(buf);
while (read != -1) {
digest.update(buf, 0, read);
read = stream.read(buf);
}
}
final byte[] hash = digest.digest();

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@volodya-lombrozo this comment sounds reasonable to me

*/
private Path hash(final Path tail) {
final Path full = this.base.resolve(tail);
return full.getParent().resolve(String.format("%s.sha256", full.getFileName().toString()));

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the resolved path has no parent directory (e.g., when tail is just a filename without any parent), full.getParent() will return null, causing a NullPointerException when calling resolve on it. Consider handling this case or ensuring that tail always has a parent directory.

Suggested change
return full.getParent().resolve(String.format("%s.sha256", full.getFileName().toString()));
final Path parent = full.getParent() == null ? this.base : full.getParent();
return parent.resolve(String.format("%s.sha256", full.getFileName().toString()));

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +52
final Path hfile = this.hash(tail);
final Path cfile = this.base.resolve(tail);

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the custom coding guideline, compound variable names should be avoided in favor of single-word nouns. The variables 'hfile' and 'cfile' are compound abbreviations. Consider renaming them to single-word nouns like 'signature' and 'cached' respectively.

Copilot generated this review using guidance from organization custom instructions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@volodya-lombrozo this comment sounds reasonable to me

Comment thread eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java
Comment on lines +55 to +57
Files.writeString(this.hash(tail), hash);
Files.writeString(cfile, content);
Files.writeString(target, content);

Copilot AI Feb 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parent directories for the hash file, cache file, and target file may not exist when attempting to write. This will cause an IOException. Consider creating parent directories before writing, similar to how the Saved class does it with this.target.toFile().getParentFile().mkdirs() at line 77 in Saved.java.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@volodya-lombrozo this comment sounds reasonable to me

Comment thread eo-maven-plugin/src/main/java/org/eolang/maven/Cache.java Outdated
Comment thread eo-maven-plugin/src/test/java/org/eolang/maven/CacheTest.java
@github-actions

github-actions Bot commented Feb 2, 2026

Copy link
Copy Markdown
Contributor

🚀 Performance Analysis

All benchmarks are within the acceptable range. No critical degradation detected (threshold is 100%). Please refer to the detailed report for more information.

Click to see the detailed report
Test Base Score PR Score Change % Change Unit Mode
benchmarks.XmirBench.xmirToEO 181.437 208.465 27.029 14.90% ms/op Average Time

⚠️ Performance loss: benchmarks.XmirBench.xmirToEO is slower by 27.029 ms/op (14.90%)

@volodya-lombrozo

Copy link
Copy Markdown
Member Author

@yegor256 could you have a look one more time, please?

@yegor256 yegor256 merged commit 24a7107 into objectionary:master Feb 3, 2026
27 checks passed
@0crat

0crat commented Feb 3, 2026

Copy link
Copy Markdown

@volodya-lombrozo Thank you for your contribution! You've earned +12 points for this submission: +16 as the base reward, +9.65 for 193 hits-of-code (0.05 × 193), and -4 deducted for exceeding 100 hits-of-code, then capped at +12 per policy. Please keep them coming! Your running score is +284; don't forget to check your Zerocracy account too.

@0crat

0crat commented Feb 4, 2026

Copy link
Copy Markdown

@yegor256 Thanks for the excellent review! 🎉 You've earned +7 points (+12 base, -5 for minimal comments). Your total score is now +142 - keep up the great work! Don't forget to check your Zerocracy account for updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants