Skip to content

Conversation

@chrfwow
Copy link
Contributor

@chrfwow chrfwow commented Dec 9, 2025

This PR

Re-revert of #1768 with associated fixes: The object map of the LayeredEvaluationContext needs to be computed by calling the asObjectMap method of the layered contexts. Otherwise, nested EvaluationContexts will not get unwrapped (i.e. converted to HashMaps)

Related Issues

Fixes #1754

See also open-feature/java-sdk-contrib#1665

@chrfwow chrfwow requested review from a team as code owners December 9, 2025 11:05
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @chrfwow, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the core EvaluationContext implementations, ImmutableContext and LayeredEvaluationContext, to provide robust and correct equals and hashCode behavior. The primary goal is to ensure that context objects are compared and hashed consistently based on their resolved attributes, even when dealing with complex layered or nested structures. This change is crucial for reliable context management and feature evaluation within the SDK.

Highlights

  • Corrected EvaluationContext Equality and Hashing: The equals and hashCode methods for ImmutableContext and LayeredEvaluationContext have been re-implemented to ensure they correctly compare and hash based on their resolved attribute maps, addressing previous inconsistencies.
  • Proper Unwrapping of Nested Contexts: The LayeredEvaluationContext now correctly unwraps nested EvaluationContext instances into HashMaps when asObjectMap() is called, resolving an issue where nested contexts were not being properly converted.
  • Performance Optimization with Caching: Lazy caching mechanisms have been introduced for the resolved attribute map in LayeredEvaluationContext and for the hash code in ImmutableContext, improving performance for repeated calls to asMap(), asUnmodifiableMap(), and hashCode().
  • Comprehensive Test Coverage: New and updated unit tests have been added to validate the correctness of the equals and hashCode implementations, including scenarios involving nested contexts, layered contexts, and mixed context types.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the ImmutableContext and LayeredEvaluationContext classes to improve performance and correctness, particularly concerning object equality and hashing. For ImmutableContext, the Lombok-generated equals and hashCode methods were replaced with custom implementations that lazily compute and cache the hash code based on the context's unmodifiable map. For LayeredEvaluationContext, a cachedMap was introduced to store the resolved attribute map, which is lazily computed and invalidated when underlying contexts change, optimizing repeated map access. Custom equals and hashCode methods were also added for LayeredEvaluationContext, relying on this cached map. The review comment suggests an optimization for LayeredEvaluationContext's map creation, recommending to call ensureKeySet() before initializing the HashMap for the cachedMap to pre-calculate the required capacity and prevent potential HashMap resizing, thus improving performance. New tests were added to validate these changes, including equality and hash code stability for both context types, and behavior of asObjectMap() for LayeredEvaluationContext.

Comment on lines 185 to 196
if (keySet != null && keySet.isEmpty()) {
return new HashMap<>(0);
cachedMap = Collections.emptyMap();
return cachedMap;
}

HashMap<String, Value> map;
if (keySet != null) {
map = new HashMap<>(keySet.size());
// use helper to size the map based on expected entries
map = HashMapUtils.forEntries(keySet.size());
} else {
map = new HashMap<>();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To improve performance and avoid potential HashMap resizing, it's better to compute the full set of keys before initializing the map. By calling ensureKeySet() upfront, you can guarantee that the HashMap is created with the correct initial capacity to hold all entries, which is more efficient than potentially creating a default-sized map that needs to be rehashed.

        ensureKeySet();

        if (this.keySet.isEmpty()) {
            cachedMap = Collections.emptyMap();
            return cachedMap;
        }

        // use helper to size the map based on expected entries
        HashMap<String, Value> map = HashMapUtils.forEntries(this.keySet.size());

@codecov
Copy link

codecov bot commented Dec 9, 2025

Codecov Report

❌ Patch coverage is 80.00000% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.22%. Comparing base (2a37f46) to head (f75201a).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...in/java/dev/openfeature/sdk/EvaluationContext.java 50.00% 2 Missing and 2 partials ⚠️
...in/java/dev/openfeature/sdk/AbstractStructure.java 60.00% 1 Missing and 1 partial ⚠️
.../dev/openfeature/sdk/LayeredEvaluationContext.java 90.00% 1 Missing and 1 partial ⚠️
...ain/java/dev/openfeature/sdk/ImmutableContext.java 90.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1771      +/-   ##
============================================
+ Coverage     92.53%   93.22%   +0.69%     
- Complexity      599      617      +18     
============================================
  Files            55       55              
  Lines          1406     1446      +40     
  Branches        154      161       +7     
============================================
+ Hits           1301     1348      +47     
+ Misses           64       54      -10     
- Partials         41       44       +3     
Flag Coverage Δ
unittests 93.22% <80.00%> (+0.69%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@toddbaert toddbaert force-pushed the fix-as-object-map branch 3 times, most recently from c432b35 to b31f3d2 Compare December 9, 2025 13:09
@toddbaert toddbaert self-requested a review December 9, 2025 13:15
Copy link
Member

@toddbaert toddbaert left a comment

Choose a reason for hiding this comment

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

FYI @chrfwow I built this locally and used it successfully in the contribs e2e suite.

@toddbaert toddbaert marked this pull request as draft December 9, 2025 14:56
@chrfwow chrfwow requested a review from toddbaert December 9, 2025 15:57
@chrfwow chrfwow marked this pull request as ready for review December 9, 2025 16:07
This reverts commit 4cb39a4.

Co-Authored-By: Vishalup29 <[email protected]>
Signed-off-by: Todd Baert <[email protected]>
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 9, 2025

@toddbaert toddbaert changed the title feat: Improve equals and hashcode of EvaluationContext feat: equivalent EvaluationContext impls are .equal Dec 9, 2025
@toddbaert toddbaert enabled auto-merge (squash) December 9, 2025 20:05
@toddbaert toddbaert merged commit ae69411 into open-feature:main Dec 9, 2025
10 of 14 checks passed
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.

Cannot compare (.equals) various context implementaations

3 participants