Skip to content

Commit 9f25944

Browse files
committed
Optimize hashcode
1 parent b072525 commit 9f25944

13 files changed

Lines changed: 116 additions & 44 deletions

File tree

codegen/codegen-core/src/main/java/software/amazon/smithy/java/codegen/generators/StructureGenerator.java

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ public final class ${shape:T} ${^isError}implements ${serializableStruct:T}${/is
146146
writer.putContext(
147147
"equals",
148148
new EqualsGenerator(writer, shape, directive.symbolProvider(), directive.model()));
149-
writer.putContext("hashCode", new HashCodeGenerator(writer, shape, directive.symbolProvider()));
149+
writer.putContext(
150+
"hashCode",
151+
new HashCodeGenerator(writer, shape, directive.symbolProvider(), directive.model()));
150152
writer.putContext("toString", new ToStringGenerator(writer));
151153
writer.putContext(
152154
"serializer",
@@ -428,8 +430,8 @@ private void writePropertyEqualityChecks(JavaWriter writer) {
428430
}
429431
}
430432

431-
private record HashCodeGenerator(JavaWriter writer, Shape shape, SymbolProvider symbolProvider) implements
432-
Runnable {
433+
private record HashCodeGenerator(JavaWriter writer, Shape shape, SymbolProvider symbolProvider, Model model)
434+
implements Runnable {
433435

434436
@Override
435437
public void run() {
@@ -449,32 +451,36 @@ public int hashCode() {
449451
}
450452

451453
private void generate(JavaWriter writer) {
452-
List<String> arrayMemberNames = shape.members()
453-
.stream()
454-
.filter(member -> CodegenUtils.isJavaArray(symbolProvider.toSymbol(member)))
455-
.map(symbolProvider::toMemberName)
456-
.toList();
457-
List<String> objectMemberNames = shape.members()
458-
.stream()
459-
.map(symbolProvider::toMemberName)
460-
.filter(name -> !arrayMemberNames.contains(name))
461-
.toList();
462-
writer.pushState();
463-
writer.putContext("arr", arrayMemberNames);
464-
writer.putContext("obj", objectMemberNames);
465-
writer.putContext("objects", Objects.class);
466-
if (arrayMemberNames.isEmpty()) {
467-
writer.write("return ${objects:T}.hash(${#obj}${value:L}${^key.last}, ${/key.last}${/obj});");
454+
var members = shape.members();
455+
if (members.isEmpty()) {
456+
writer.write("return 0;");
457+
return;
458+
}
459+
460+
var iter = members.iterator();
461+
var first = iter.next();
462+
writer.write("int result = $C;", writer.consumer(w -> writeMemberHash(w, first)));
463+
while (iter.hasNext()) {
464+
var member = iter.next();
465+
writer.write("result = 31 * result + $C;", writer.consumer(w -> writeMemberHash(w, member)));
466+
}
467+
writer.write("return result;");
468+
}
469+
470+
private void writeMemberHash(JavaWriter writer, MemberShape member) {
471+
var memberSymbol = symbolProvider.toSymbol(member);
472+
var memberName = symbolProvider.toMemberName(member);
473+
if (CodegenUtils.isJavaArray(memberSymbol)) {
474+
writer.writeInline("$T.hashCode($L)", Arrays.class, memberName);
475+
} else if (memberSymbol.expectProperty(SymbolProperties.IS_PRIMITIVE)
476+
&& !CodegenUtils.isNullableMember(model, member)) {
477+
writer.writeInline(
478+
"$T.hashCode($L)",
479+
memberSymbol.expectProperty(SymbolProperties.BOXED_TYPE),
480+
memberName);
468481
} else {
469-
writer.putContext("arrays", Arrays.class);
470-
writer.write(
471-
"""
472-
int result = ${objects:T}.hash(${#obj}${value:L}${^key.last}, ${/key.last}${/obj});
473-
result = 31 * result${#arr} + ${arrays:T}.hashCode(${value:L})${/arr};
474-
return result;
475-
""");
482+
writer.writeInline("$T.hashCode($L)", Objects.class, memberName);
476483
}
477-
writer.popState();
478484
}
479485
}
480486

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/defaults/expected/DefaultStructure.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,33 @@ public boolean equals(Object other) {
266266

267267
@Override
268268
public int hashCode() {
269-
return Objects.hash(booleanMember, bigDecimal, bigDecimalWithDoubleDefault, bigDecimalWithLongDefault, bigInteger, bigIntegerWithLongDefault, byteMember, doubleMember, floatMember, integer, longMember, shortMember, string, blob, streamingBlob, boolDoc, stringDoc, numberDoc, floatingPointnumberDoc, listDoc, mapDoc, list, map, timestamp, enumMember, intEnum);
269+
int result = Boolean.hashCode(booleanMember);
270+
result = 31 * result + Objects.hashCode(bigDecimal);
271+
result = 31 * result + Objects.hashCode(bigDecimalWithDoubleDefault);
272+
result = 31 * result + Objects.hashCode(bigDecimalWithLongDefault);
273+
result = 31 * result + Objects.hashCode(bigInteger);
274+
result = 31 * result + Objects.hashCode(bigIntegerWithLongDefault);
275+
result = 31 * result + Byte.hashCode(byteMember);
276+
result = 31 * result + Double.hashCode(doubleMember);
277+
result = 31 * result + Float.hashCode(floatMember);
278+
result = 31 * result + Integer.hashCode(integer);
279+
result = 31 * result + Long.hashCode(longMember);
280+
result = 31 * result + Short.hashCode(shortMember);
281+
result = 31 * result + Objects.hashCode(string);
282+
result = 31 * result + Objects.hashCode(blob);
283+
result = 31 * result + Objects.hashCode(streamingBlob);
284+
result = 31 * result + Objects.hashCode(boolDoc);
285+
result = 31 * result + Objects.hashCode(stringDoc);
286+
result = 31 * result + Objects.hashCode(numberDoc);
287+
result = 31 * result + Objects.hashCode(floatingPointnumberDoc);
288+
result = 31 * result + Objects.hashCode(listDoc);
289+
result = 31 * result + Objects.hashCode(mapDoc);
290+
result = 31 * result + Objects.hashCode(list);
291+
result = 31 * result + Objects.hashCode(map);
292+
result = 31 * result + Objects.hashCode(timestamp);
293+
result = 31 * result + Objects.hashCode(enumMember);
294+
result = 31 * result + Objects.hashCode(intEnum);
295+
return result;
270296
}
271297

272298
@Override

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/jspecify/expected/CollectionStruct.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,15 @@ public boolean equals(Object other) {
182182

183183
@Override
184184
public int hashCode() {
185-
return Objects.hash(nonSparseList, sparseList, nonSparseMap, sparseMap, nonSparseListOfSparseMap, sparseListOfSparseMap, sparseMapOfNonSparseList, nonSparseListOfNonSparseMap);
185+
int result = Objects.hashCode(nonSparseList);
186+
result = 31 * result + Objects.hashCode(sparseList);
187+
result = 31 * result + Objects.hashCode(nonSparseMap);
188+
result = 31 * result + Objects.hashCode(sparseMap);
189+
result = 31 * result + Objects.hashCode(nonSparseListOfSparseMap);
190+
result = 31 * result + Objects.hashCode(sparseListOfSparseMap);
191+
result = 31 * result + Objects.hashCode(sparseMapOfNonSparseList);
192+
result = 31 * result + Objects.hashCode(nonSparseListOfNonSparseMap);
193+
return result;
186194
}
187195

188196
@Override

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/jspecify/expected/JSpecifyStruct.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@ public boolean equals(Object other) {
8888

8989
@Override
9090
public int hashCode() {
91-
return Objects.hash(requiredString, optionalString, requiredPrimitive, sparseList);
91+
int result = Objects.hashCode(requiredString);
92+
result = 31 * result + Objects.hashCode(optionalString);
93+
result = 31 * result + Boolean.hashCode(requiredPrimitive);
94+
result = 31 * result + Objects.hashCode(sparseList);
95+
return result;
9296
}
9397

9498
@Override

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/naming-conflict/expected/BuilderShape.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package software.amazon.smithy.java.example.standalone.model;
22

3-
import java.util.Objects;
43
import software.amazon.smithy.java.core.schema.Schema;
54
import software.amazon.smithy.java.core.schema.SerializableStruct;
65
import software.amazon.smithy.java.core.schema.ShapeBuilder;
@@ -35,7 +34,7 @@ public boolean equals(Object other) {
3534

3635
@Override
3736
public int hashCode() {
38-
return Objects.hash();
37+
return 0;
3938
}
4039

4140
@Override

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/naming-conflict/expected/List.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package software.amazon.smithy.java.example.standalone.model;
22

3-
import java.util.Objects;
43
import software.amazon.smithy.java.core.schema.Schema;
54
import software.amazon.smithy.java.core.schema.SerializableStruct;
65
import software.amazon.smithy.java.core.schema.ShapeBuilder;
@@ -35,7 +34,7 @@ public boolean equals(Object other) {
3534

3635
@Override
3736
public int hashCode() {
38-
return Objects.hash();
37+
return 0;
3938
}
4039

4140
@Override

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/naming-conflict/expected/Map.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package software.amazon.smithy.java.example.standalone.model;
22

3-
import java.util.Objects;
43
import software.amazon.smithy.java.core.schema.Schema;
54
import software.amazon.smithy.java.core.schema.SerializableStruct;
65
import software.amazon.smithy.java.core.schema.ShapeBuilder;
@@ -35,7 +34,7 @@ public boolean equals(Object other) {
3534

3635
@Override
3736
public int hashCode() {
38-
return Objects.hash();
37+
return 0;
3938
}
4039

4140
@Override

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/naming-conflict/expected/NamingStruct.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,16 @@ public boolean equals(Object other) {
127127

128128
@Override
129129
public int hashCode() {
130-
return Objects.hash(other, builderMember, type, objectMember, union, map, list, listOfList, mapOfMap);
130+
int result = Objects.hashCode(other);
131+
result = 31 * result + Objects.hashCode(builderMember);
132+
result = 31 * result + Objects.hashCode(type);
133+
result = 31 * result + Objects.hashCode(objectMember);
134+
result = 31 * result + Objects.hashCode(union);
135+
result = 31 * result + Objects.hashCode(map);
136+
result = 31 * result + Objects.hashCode(list);
137+
result = 31 * result + Objects.hashCode(listOfList);
138+
result = 31 * result + Objects.hashCode(mapOfMap);
139+
return result;
131140
}
132141

133142
@Override

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/naming-conflict/expected/ObjectShape.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,16 @@ public boolean equals(Object other) {
112112

113113
@Override
114114
public int hashCode() {
115-
return Objects.hash(classMember, getClassMember, hashCodeMember, cloneMember, toStringMember, notifyMember, notifyAllMember, waitMember, finalizeMember);
115+
int result = Objects.hashCode(classMember);
116+
result = 31 * result + Objects.hashCode(getClassMember);
117+
result = 31 * result + Objects.hashCode(hashCodeMember);
118+
result = 31 * result + Objects.hashCode(cloneMember);
119+
result = 31 * result + Objects.hashCode(toStringMember);
120+
result = 31 * result + Objects.hashCode(notifyMember);
121+
result = 31 * result + Objects.hashCode(notifyAllMember);
122+
result = 31 * result + Objects.hashCode(waitMember);
123+
result = 31 * result + Objects.hashCode(finalizeMember);
124+
return result;
116125
}
117126

118127
@Override

codegen/codegen-plugin/src/test/resources/software/amazon/smithy/java/codegen/types/test-cases/naming-conflict/expected/Schema.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public boolean equals(Object other) {
4747

4848
@Override
4949
public int hashCode() {
50-
return Objects.hash(oneMember);
50+
int result = Objects.hashCode(oneMember);
51+
return result;
5152
}
5253

5354
@Override

0 commit comments

Comments
 (0)