feat(tool): add copy builder to ToolCallParam#1524
Conversation
Add builder(ToolCallParam) factory method to create a builder initialized with values from an existing instance. Mutable fields such as the input map are deep-copied to ensure independence. Closes agentscope-ai#1517
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
The PR adds a clean copy-builder factory ToolCallParam.builder(ToolCallParam source) with thorough unit coverage (7 new tests across copy semantics, null handling, and field replacement). Logic is correct and the implementation is consistent with the existing builder pattern. Two documentation/clarity issues stand out: the Javadoc claim that mutable fields are "deep-copied" overstates what new HashMap<>(source.input) actually does (it is a shallow copy of entries — nested mutable values are still shared), and the test testDeepCopyInputMap does not actually assert deep-copy semantics. A minor nit: the @throws NullPointerException contract relies on implicit dereference; using Objects.requireNonNull(source, "source") would yield a clearer, stable error message. Also note the copy-builder's defensive new HashMap<> is redundant given ToolCallParam's constructor already copies the map at build time, but this is purely a small efficiency concern, not a bug.
| * .build(); | ||
| * }</pre> | ||
| * | ||
| * <p>Note: Mutable fields (such as the input map) are deep-copied to ensure |
There was a problem hiding this comment.
[minor] The Javadoc states 'Mutable fields (such as the input map) are deep-copied', but new HashMap<>(source.input) performs a shallow copy: only the outer map structure is duplicated, while values inside the map (which are typed as Object and may be mutable, e.g. nested List/Map) remain shared with the source. If a caller mutates a nested value on either instance, the change is visible to both. Please either reword to 'shallow-copied' (entries are independent, but nested values are shared) or, if true deep-copy semantics are intended, document the requirement that input values be effectively immutable.
| * @throws NullPointerException if source is null | ||
| */ | ||
| public static Builder builder(ToolCallParam source) { | ||
| return new Builder(source); |
There was a problem hiding this comment.
[nit] The Javadoc declares @throws NullPointerException if source is null, but the NPE is currently raised only implicitly by source.toolUseBlock dereference inside the private Builder(source) constructor. Consider an explicit Objects.requireNonNull(source, "source") here (or at the start of the private constructor) so the failure message is stable and self-describing, and the contract is enforced at the public entry point.
|
|
||
| private Builder(ToolCallParam source) { | ||
| this.toolUseBlock = source.toolUseBlock; | ||
| this.input = source.input.isEmpty() ? null : new HashMap<>(source.input); |
There was a problem hiding this comment.
[nit] This new HashMap<>(source.input) is redundant. The ToolCallParam constructor already performs new HashMap<>(builder.input) at build time (line 54), so when the user calls builder(source).build() without overriding input, the map gets copied twice. You can safely assign this.input = source.input.isEmpty() ? null : source.input; and rely on the existing defensive copy in the constructor. Pure micro-optimization — not blocking.
|
|
||
| @Test | ||
| @DisplayName("Should deep copy input map") | ||
| void testDeepCopyInputMap() { |
There was a problem hiding this comment.
[nit] testDeepCopyInputMap does not actually verify deep-copy behavior. It builds modified from original with a wholly new Map via .input(modifiedInput), which only exercises field replacement (already covered by testModifyCopiedFields). To truly assert deep-copy semantics you would need a nested mutable value, e.g. input.put("list", new ArrayList<>(List.of("a"))), then mutate ((List<?>) copy.getInput().get("list")) and confirm the original list is unchanged. With the current shallow-copy implementation, that assertion would actually fail — which is itself useful information (see the related Javadoc finding).
- Fix Javadoc: describe shallow copy accurately (entries independent, nested values shared) instead of deep-copied - Add explicit Objects.requireNonNull(source) with descriptive message - Remove redundant new HashMap<>() in copy Builder constructor since ToolCallParam constructor already performs defensive copy - Rewrite testShallowCopyInputMap to verify actual shallow copy behavior: nested mutable values are shared by reference, top-level entries remain independent
4d84c11 to
a8f42a4
Compare
Add builder(ToolCallParam) factory method to create a builder initialized with values from an existing instance. Mutable fields such as the input map are deep-copied to ensure independence.
Closes #1517
AgentScope-Java Version
[The version of AgentScope-Java you are working on, e.g. 1.0.12, check your pom.xml dependency version or run
mvn dependency:tree | grep agentscope-parent:pom(only mac/linux)]Description
[Please describe the background, purpose, changes made, and how to test this PR]
Checklist
Please check the following items before code is ready to be reviewed.
mvn spotless:applymvn test)