Skip to content

Commit 95ceb1c

Browse files
committed
fix: incorrect equals and hashCode in Options class
- Added `equalsverifier` to validate `equals` and `hashCode` correctness - Fix the incorrect equals and hashCode methods Signed-off-by: YunKui Lu <[email protected]>
1 parent e0ccc13 commit 95ceb1c

File tree

56 files changed

+1046
-243
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1046
-243
lines changed

models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.Map;
2222

23+
import nl.jqno.equalsverifier.EqualsVerifier;
2324
import org.junit.jupiter.api.Test;
2425

2526
import org.springframework.ai.anthropic.api.AnthropicApi.ChatCompletionRequest.Metadata;
@@ -159,6 +160,8 @@ void testCopyMutationDoesNotAffectOriginal() {
159160
assertThat(copy.getModel()).isEqualTo("modified-model");
160161
assertThat(copy.getMaxTokens()).isEqualTo(200);
161162
assertThat(copy.getTemperature()).isEqualTo(0.8);
163+
164+
EqualsVerifier.simple().forClass(AnthropicChatOptions.class).usingGetClass().verify();
162165
}
163166

164167
@Test

models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiAudioTranscriptionOptions.java

Lines changed: 19 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.ai.azure.openai;
1818

1919
import java.util.List;
20+
import java.util.Objects;
2021

2122
import com.azure.ai.openai.models.AudioTranscriptionFormat;
2223
import com.azure.ai.openai.models.AudioTranscriptionTimestampGranularity;
@@ -66,6 +67,7 @@ public class AzureOpenAiAudioTranscriptionOptions implements AudioTranscriptionO
6667
private @JsonProperty("temperature") Float temperature = 0F;
6768

6869
private @JsonProperty("timestamp_granularities") List<GranularityType> granularityType;
70+
// @formatter:on
6971

7072
public static Builder builder() {
7173
return new Builder();
@@ -129,58 +131,26 @@ public void setGranularityType(List<GranularityType> granularityType) {
129131
}
130132

131133
@Override
132-
public int hashCode() {
133-
final int prime = 31;
134-
int result = 1;
135-
result = prime * result + ((this.model == null) ? 0 : this.model.hashCode());
136-
result = prime * result + ((this.prompt == null) ? 0 : this.prompt.hashCode());
137-
result = prime * result + ((this.language == null) ? 0 : this.language.hashCode());
138-
result = prime * result + ((this.responseFormat == null) ? 0 : this.responseFormat.hashCode());
139-
return result;
134+
public final boolean equals(Object o) {
135+
if (!(o instanceof AzureOpenAiAudioTranscriptionOptions that))
136+
return false;
137+
138+
return Objects.equals(model, that.model) && Objects.equals(deploymentName, that.deploymentName)
139+
&& responseFormat == that.responseFormat && Objects.equals(prompt, that.prompt)
140+
&& Objects.equals(language, that.language) && Objects.equals(temperature, that.temperature)
141+
&& Objects.equals(granularityType, that.granularityType);
140142
}
141143

142144
@Override
143-
public boolean equals(Object obj) {
144-
if (this == obj) {
145-
return true;
146-
}
147-
if (obj == null) {
148-
return false;
149-
}
150-
if (getClass() != obj.getClass()) {
151-
return false;
152-
}
153-
AzureOpenAiAudioTranscriptionOptions other = (AzureOpenAiAudioTranscriptionOptions) obj;
154-
if (this.model == null) {
155-
if (other.model != null) {
156-
return false;
157-
}
158-
}
159-
else if (!this.model.equals(other.model)) {
160-
return false;
161-
}
162-
if (this.prompt == null) {
163-
if (other.prompt != null) {
164-
return false;
165-
}
166-
}
167-
else if (!this.prompt.equals(other.prompt)) {
168-
return false;
169-
}
170-
if (this.language == null) {
171-
if (other.language != null) {
172-
return false;
173-
}
174-
}
175-
else if (!this.language.equals(other.language)) {
176-
return false;
177-
}
178-
if (this.responseFormat == null) {
179-
return other.responseFormat == null;
180-
}
181-
else {
182-
return this.responseFormat.equals(other.responseFormat);
183-
}
145+
public int hashCode() {
146+
int result = Objects.hashCode(model);
147+
result = 31 * result + Objects.hashCode(deploymentName);
148+
result = 31 * result + Objects.hashCode(responseFormat);
149+
result = 31 * result + Objects.hashCode(prompt);
150+
result = 31 * result + Objects.hashCode(language);
151+
result = 31 * result + Objects.hashCode(temperature);
152+
result = 31 * result + Objects.hashCode(granularityType);
153+
return result;
184154
}
185155

186156
public enum WhisperModel {

models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,8 @@ public boolean equals(Object o) {
504504
&& Objects.equals(this.toolCallbacks, that.toolCallbacks)
505505
&& Objects.equals(this.toolNames, that.toolNames)
506506
&& Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled)
507-
&& Objects.equals(this.logprobs, that.logprobs) && Objects.equals(this.topLogProbs, that.topLogProbs)
507+
&& Objects.equals(this.seed, that.seed) && Objects.equals(this.logprobs, that.logprobs)
508+
&& Objects.equals(this.topLogProbs, that.topLogProbs)
508509
&& Objects.equals(this.enhancements, that.enhancements)
509510
&& Objects.equals(this.streamOptions, that.streamOptions)
510511
&& Objects.equals(this.enableStreamUsage, that.enableStreamUsage)

models/spring-ai-azure-openai/src/main/java/org/springframework/ai/azure/openai/AzureOpenAiEmbeddingOptions.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.ai.azure.openai;
1818

1919
import java.util.List;
20+
import java.util.Objects;
2021

2122
import com.fasterxml.jackson.annotation.JsonIgnore;
2223

@@ -105,6 +106,24 @@ public void setDimensions(Integer dimensions) {
105106
this.dimensions = dimensions;
106107
}
107108

109+
@Override
110+
public final boolean equals(Object o) {
111+
if (!(o instanceof AzureOpenAiEmbeddingOptions that))
112+
return false;
113+
114+
return Objects.equals(user, that.user) && Objects.equals(deploymentName, that.deploymentName)
115+
&& Objects.equals(inputType, that.inputType) && Objects.equals(dimensions, that.dimensions);
116+
}
117+
118+
@Override
119+
public int hashCode() {
120+
int result = Objects.hashCode(user);
121+
result = 31 * result + Objects.hashCode(deploymentName);
122+
result = 31 * result + Objects.hashCode(inputType);
123+
result = 31 * result + Objects.hashCode(dimensions);
124+
return result;
125+
}
126+
108127
public com.azure.ai.openai.models.EmbeddingsOptions toAzureOptions(List<String> instructions) {
109128

110129
var azureOptions = new com.azure.ai.openai.models.EmbeddingsOptions(instructions);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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.azure.openai;
18+
19+
import nl.jqno.equalsverifier.EqualsVerifier;
20+
import org.junit.jupiter.api.Test;
21+
22+
class AzureOpenAiAudioTranscriptionOptionsTests {
23+
24+
@Test
25+
void testEqualsAndHashCode() {
26+
EqualsVerifier.simple().forClass(AzureOpenAiAudioTranscriptionOptions.class).usingGetClass().verify();
27+
}
28+
29+
}

models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatOptionsTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.azure.ai.openai.models.AzureChatGroundingEnhancementConfiguration;
2424
import com.azure.ai.openai.models.AzureChatOCREnhancementConfiguration;
2525
import com.azure.ai.openai.models.ChatCompletionStreamOptions;
26+
import nl.jqno.equalsverifier.EqualsVerifier;
2627
import org.junit.jupiter.api.Test;
2728

2829
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,6 +35,11 @@
3435
*/
3536
class AzureOpenAiChatOptionsTests {
3637

38+
@Test
39+
void testEqualsAndHashCode() {
40+
EqualsVerifier.simple().forClass(AzureOpenAiChatOptions.class).usingGetClass().verify();
41+
}
42+
3743
@Test
3844
void testBuilderWithAllFields() {
3945
AzureOpenAiResponseFormat responseFormat = AzureOpenAiResponseFormat.builder()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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.azure.openai;
18+
19+
import nl.jqno.equalsverifier.EqualsVerifier;
20+
import org.junit.jupiter.api.Test;
21+
22+
class AzureOpenAiEmbeddingOptionsTests {
23+
24+
@Test
25+
void testEqualsAndHashCode() {
26+
EqualsVerifier.simple().forClass(AzureOpenAiEmbeddingOptions.class).usingGetClass().verify();
27+
}
28+
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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.azure.openai;
18+
19+
import nl.jqno.equalsverifier.EqualsVerifier;
20+
import org.junit.jupiter.api.Test;
21+
22+
class AzureOpenAiImageOptionsTests {
23+
24+
@Test
25+
void testEqualsAndHashCode() {
26+
EqualsVerifier.simple().forClass(AzureOpenAiImageOptions.class).usingGetClass().verify();
27+
}
28+
29+
}

models/spring-ai-bedrock-converse/src/test/java/org/springframework/ai/bedrock/converse/BedrockChatOptionsTests.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616

1717
package org.springframework.ai.bedrock.converse;
1818

19-
import org.junit.jupiter.api.Test;
20-
2119
import java.util.List;
2220
import java.util.Map;
2321

22+
import nl.jqno.equalsverifier.EqualsVerifier;
23+
import org.junit.jupiter.api.Test;
24+
2425
import static org.assertj.core.api.Assertions.assertThat;
2526

2627
/**
@@ -30,6 +31,11 @@
3031
*/
3132
class BedrockChatOptionsTests {
3233

34+
@Test
35+
void testEqualsAndHashCode() {
36+
EqualsVerifier.simple().forClass(BedrockChatOptions.class).usingGetClass().verify();
37+
}
38+
3339
@Test
3440
void testBuilderWithAllFields() {
3541
BedrockChatOptions options = BedrockChatOptions.builder()

models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingOptions.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.ai.bedrock.cohere;
1818

19+
import java.util.Objects;
20+
1921
import com.fasterxml.jackson.annotation.JsonIgnore;
2022
import com.fasterxml.jackson.annotation.JsonInclude;
2123
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -84,6 +86,21 @@ public Integer getDimensions() {
8486
return null;
8587
}
8688

89+
@Override
90+
public final boolean equals(Object o) {
91+
if (!(o instanceof BedrockCohereEmbeddingOptions that))
92+
return false;
93+
94+
return inputType == that.inputType && truncate == that.truncate;
95+
}
96+
97+
@Override
98+
public int hashCode() {
99+
int result = Objects.hashCode(inputType);
100+
result = 31 * result + Objects.hashCode(truncate);
101+
return result;
102+
}
103+
87104
public static class Builder {
88105

89106
private BedrockCohereEmbeddingOptions options = new BedrockCohereEmbeddingOptions();

0 commit comments

Comments
 (0)