Skip to content

Commit 0da6b19

Browse files
committed
feat: Add equals, hashCode, deep copy, and tests to MoonshotChatOptions
This commit enhances MoonshotChatOptions by: - Updating equals and hashCode methods for proper object comparison. - Updating copy() method, creating new instances of mutable collections (List, Set, Map, Metadata) to prevent shared state. - Adding MoonshotChatOptionsTests to verify copy(), builders, setters, and default values. Signed-off-by: Alexandros Pappas <[email protected]>
1 parent 092bbae commit 0da6b19

File tree

2 files changed

+151
-111
lines changed

2 files changed

+151
-111
lines changed

models/spring-ai-moonshot/src/main/java/org/springframework/ai/moonshot/MoonshotChatOptions.java

Lines changed: 25 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,9 +17,11 @@
1717
package org.springframework.ai.moonshot;
1818

1919
import java.util.ArrayList;
20+
import java.util.HashMap;
2021
import java.util.HashSet;
2122
import java.util.List;
2223
import java.util.Map;
24+
import java.util.Objects;
2325
import java.util.Set;
2426

2527
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -141,7 +143,7 @@ public class MoonshotChatOptions implements FunctionCallingOptions {
141143
private Boolean proxyToolCalls;
142144

143145
@JsonIgnore
144-
private Map<String, Object> toolContext;
146+
private Map<String, Object> toolContext = new HashMap<>();
145147

146148
public static Builder builder() {
147149
return new Builder();
@@ -281,6 +283,7 @@ public void setToolContext(Map<String, Object> toolContext) {
281283
}
282284

283285
@Override
286+
@SuppressWarnings("unchecked")
284287
public MoonshotChatOptions copy() {
285288
return builder().model(this.model)
286289
.maxTokens(this.maxTokens)
@@ -289,130 +292,41 @@ public MoonshotChatOptions copy() {
289292
.N(this.n)
290293
.presencePenalty(this.presencePenalty)
291294
.frequencyPenalty(this.frequencyPenalty)
292-
.stop(this.stop)
295+
.stop(this.stop != null ? new ArrayList<>(this.stop) : null)
293296
.user(this.user)
294-
.tools(this.tools)
297+
.tools(this.tools != null ? new ArrayList<>(this.tools) : null)
295298
.toolChoice(this.toolChoice)
296-
.functionCallbacks(this.functionCallbacks)
297-
.functions(this.functions)
299+
.functionCallbacks(this.functionCallbacks != null ? new ArrayList<>(this.functionCallbacks) : null)
300+
.functions(this.functions != null ? new HashSet<>(this.functions) : null)
298301
.proxyToolCalls(this.proxyToolCalls)
299-
.toolContext(this.toolContext)
302+
.toolContext(this.toolContext != null ? new HashMap<>(this.toolContext) : null)
300303
.build();
301304
}
302305

303306
@Override
304307
public int hashCode() {
305-
final int prime = 31;
306-
int result = 1;
307-
result = prime * result + ((this.model == null) ? 0 : this.model.hashCode());
308-
result = prime * result + ((this.frequencyPenalty == null) ? 0 : this.frequencyPenalty.hashCode());
309-
result = prime * result + ((this.maxTokens == null) ? 0 : this.maxTokens.hashCode());
310-
result = prime * result + ((this.n == null) ? 0 : this.n.hashCode());
311-
result = prime * result + ((this.presencePenalty == null) ? 0 : this.presencePenalty.hashCode());
312-
result = prime * result + ((this.stop == null) ? 0 : this.stop.hashCode());
313-
result = prime * result + ((this.temperature == null) ? 0 : this.temperature.hashCode());
314-
result = prime * result + ((this.topP == null) ? 0 : this.topP.hashCode());
315-
result = prime * result + ((this.user == null) ? 0 : this.user.hashCode());
316-
result = prime * result + ((this.proxyToolCalls == null) ? 0 : this.proxyToolCalls.hashCode());
317-
result = prime * result + ((this.toolContext == null) ? 0 : this.toolContext.hashCode());
318-
return result;
308+
return Objects.hash(model, maxTokens, temperature, topP, n, presencePenalty, frequencyPenalty, stop, tools,
309+
toolChoice, functionCallbacks, functions, user, proxyToolCalls, toolContext);
319310
}
320311

321312
@Override
322-
public boolean equals(Object obj) {
323-
if (this == obj) {
313+
public boolean equals(Object o) {
314+
if (this == o) {
324315
return true;
325316
}
326-
if (obj == null) {
317+
if (!(o instanceof MoonshotChatOptions other)) {
327318
return false;
328319
}
329-
if (getClass() != obj.getClass()) {
330-
return false;
331-
}
332-
MoonshotChatOptions other = (MoonshotChatOptions) obj;
333-
if (this.model == null) {
334-
if (other.model != null) {
335-
return false;
336-
}
337-
}
338-
else if (!this.model.equals(other.model)) {
339-
return false;
340-
}
341-
if (this.frequencyPenalty == null) {
342-
if (other.frequencyPenalty != null) {
343-
return false;
344-
}
345-
}
346-
else if (!this.frequencyPenalty.equals(other.frequencyPenalty)) {
347-
return false;
348-
}
349-
if (this.maxTokens == null) {
350-
if (other.maxTokens != null) {
351-
return false;
352-
}
353-
}
354-
else if (!this.maxTokens.equals(other.maxTokens)) {
355-
return false;
356-
}
357-
if (this.n == null) {
358-
if (other.n != null) {
359-
return false;
360-
}
361-
}
362-
else if (!this.n.equals(other.n)) {
363-
return false;
364-
}
365-
if (this.presencePenalty == null) {
366-
if (other.presencePenalty != null) {
367-
return false;
368-
}
369-
}
370-
else if (!this.presencePenalty.equals(other.presencePenalty)) {
371-
return false;
372-
}
373-
if (this.stop == null) {
374-
if (other.stop != null) {
375-
return false;
376-
}
377-
}
378-
else if (!this.stop.equals(other.stop)) {
379-
return false;
380-
}
381-
if (this.temperature == null) {
382-
if (other.temperature != null) {
383-
return false;
384-
}
385-
}
386-
else if (!this.temperature.equals(other.temperature)) {
387-
return false;
388-
}
389-
if (this.topP == null) {
390-
if (other.topP != null) {
391-
return false;
392-
}
393-
}
394-
else if (!this.topP.equals(other.topP)) {
395-
return false;
396-
}
397-
if (this.user == null) {
398-
return other.user == null;
399-
}
400-
else if (!this.user.equals(other.user)) {
401-
return false;
402-
}
403-
if (this.proxyToolCalls == null) {
404-
return other.proxyToolCalls == null;
405-
}
406-
else if (!this.proxyToolCalls.equals(other.proxyToolCalls)) {
407-
return false;
408-
}
409-
if (this.toolContext == null) {
410-
return other.toolContext == null;
411-
}
412-
else if (!this.toolContext.equals(other.toolContext)) {
413-
return false;
414-
}
415-
return true;
320+
return Objects.equals(this.model, other.model) && Objects.equals(this.maxTokens, other.maxTokens)
321+
&& Objects.equals(this.temperature, other.temperature) && Objects.equals(this.topP, other.topP)
322+
&& Objects.equals(this.n, other.n) && Objects.equals(this.presencePenalty, other.presencePenalty)
323+
&& Objects.equals(this.frequencyPenalty, other.frequencyPenalty)
324+
&& Objects.equals(this.stop, other.stop) && Objects.equals(this.tools, other.tools)
325+
&& Objects.equals(this.toolChoice, other.toolChoice)
326+
&& Objects.equals(this.functionCallbacks, other.functionCallbacks)
327+
&& Objects.equals(this.functions, other.functions) && Objects.equals(this.user, other.user)
328+
&& Objects.equals(this.proxyToolCalls, other.proxyToolCalls)
329+
&& Objects.equals(this.toolContext, other.toolContext);
416330
}
417331

418332
public static class Builder {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.moonshot;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
26+
/**
27+
* Tests for {@link MoonshotChatOptions}.
28+
*
29+
* @author Alexandros Pappas
30+
*/
31+
class MoonshotChatOptionsTests {
32+
33+
@Test
34+
void testBuilderWithAllFields() {
35+
MoonshotChatOptions options = MoonshotChatOptions.builder()
36+
.model("test-model")
37+
.frequencyPenalty(0.5)
38+
.maxTokens(10)
39+
.N(1)
40+
.presencePenalty(0.5)
41+
.stop(List.of("test"))
42+
.temperature(0.6)
43+
.topP(0.6)
44+
.toolChoice("test")
45+
.proxyToolCalls(true)
46+
.toolContext(Map.of("key1", "value1"))
47+
.user("test-user")
48+
.build();
49+
50+
assertThat(options)
51+
.extracting("model", "frequencyPenalty", "maxTokens", "N", "presencePenalty", "stop", "temperature", "topP",
52+
"toolChoice", "proxyToolCalls", "toolContext", "user")
53+
.containsExactly("test-model", 0.5, 10, 1, 0.5, List.of("test"), 0.6, 0.6, "test", true,
54+
Map.of("key1", "value1"), "test-user");
55+
}
56+
57+
@Test
58+
void testCopy() {
59+
MoonshotChatOptions original = MoonshotChatOptions.builder()
60+
.model("test-model")
61+
.frequencyPenalty(0.5)
62+
.maxTokens(10)
63+
.N(1)
64+
.presencePenalty(0.5)
65+
.stop(List.of("test"))
66+
.temperature(0.6)
67+
.topP(0.6)
68+
.toolChoice("test")
69+
.proxyToolCalls(true)
70+
.toolContext(Map.of("key1", "value1"))
71+
.user("test-user")
72+
.build();
73+
74+
MoonshotChatOptions copied = original.copy();
75+
76+
assertThat(copied).isNotSameAs(original).isEqualTo(original);
77+
// Ensure deep copy
78+
assertThat(copied.getStop()).isNotSameAs(original.getStop());
79+
assertThat(copied.getToolContext()).isNotSameAs(original.getToolContext());
80+
}
81+
82+
@Test
83+
void testSetters() {
84+
MoonshotChatOptions options = new MoonshotChatOptions();
85+
options.setModel("test-model");
86+
options.setFrequencyPenalty(0.5);
87+
options.setMaxTokens(10);
88+
options.setN(1);
89+
options.setPresencePenalty(0.5);
90+
options.setUser("test-user");
91+
options.setStop(List.of("test"));
92+
options.setTemperature(0.6);
93+
options.setTopP(0.6);
94+
options.setProxyToolCalls(true);
95+
options.setToolContext(Map.of("key1", "value1"));
96+
97+
assertThat(options.getModel()).isEqualTo("test-model");
98+
assertThat(options.getFrequencyPenalty()).isEqualTo(0.5);
99+
assertThat(options.getMaxTokens()).isEqualTo(10);
100+
assertThat(options.getN()).isEqualTo(1);
101+
assertThat(options.getPresencePenalty()).isEqualTo(0.5);
102+
assertThat(options.getUser()).isEqualTo("test-user");
103+
assertThat(options.getStopSequences()).isEqualTo(List.of("test"));
104+
assertThat(options.getTemperature()).isEqualTo(0.6);
105+
assertThat(options.getTopP()).isEqualTo(0.6);
106+
assertThat(options.getProxyToolCalls()).isTrue();
107+
assertThat(options.getToolContext()).isEqualTo(Map.of("key1", "value1"));
108+
}
109+
110+
@Test
111+
void testDefaultValues() {
112+
MoonshotChatOptions options = new MoonshotChatOptions();
113+
assertThat(options.getModel()).isNull();
114+
assertThat(options.getFrequencyPenalty()).isNull();
115+
assertThat(options.getMaxTokens()).isNull();
116+
assertThat(options.getN()).isNull();
117+
assertThat(options.getPresencePenalty()).isNull();
118+
assertThat(options.getUser()).isNull();
119+
assertThat(options.getStopSequences()).isNull();
120+
assertThat(options.getTemperature()).isNull();
121+
assertThat(options.getTopP()).isNull();
122+
assertThat(options.getProxyToolCalls()).isNull();
123+
assertThat(options.getToolContext()).isEqualTo(new java.util.HashMap<>());
124+
}
125+
126+
}

0 commit comments

Comments
 (0)