Skip to content

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
*/
public record MapField(
/* A synthetic "key" field in a map entry. */
Field keyField,
SingleField keyField,
/* A synthetic "value" field in a map entry. */
Field valueField,
SingleField valueField,
// The rest of the fields below simply implement the Field interface:
boolean repeated,
int fieldNumber,
Expand Down Expand Up @@ -94,7 +94,7 @@ public MapField(Protobuf3Parser.MapFieldContext mapContext, final ContextualLook
*/
public String javaGenericType() {
final String fieldTypeName = valueField().type() == FieldType.MESSAGE
? ((SingleField) valueField()).messageType()
? valueField().messageType()
: valueField().type().boxedType;
return "<%s, %s>".formatted(keyField.type().boxedType, fieldTypeName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ public String javaFieldTypeBase() {
return javaFieldType(false);
}

public String javaFieldTypeBoxed() {
return switch (type) {
case BOOL -> "Boolean";
case INT32, UINT32, SINT32, FIXED32, SFIXED32 -> "Integer";
case INT64, SINT64, UINT64, FIXED64, SFIXED64 -> "Long";
case FLOAT -> "Float";
case DOUBLE -> "Double";
default -> javaFieldType();
};
}

@NonNull
private String javaFieldType(boolean considerRepeated) {
String fieldType =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import static com.hedera.pbj.compiler.impl.Common.camelToUpperSnake;
import static com.hedera.pbj.compiler.impl.Common.cleanDocStr;
import static com.hedera.pbj.compiler.impl.Common.cleanJavaDocComment;
import static com.hedera.pbj.compiler.impl.Common.getFieldsHashCode;
import static com.hedera.pbj.compiler.impl.Common.javaPrimitiveToObjectType;
import static com.hedera.pbj.compiler.impl.generators.EnumGenerator.EnumValue;
import static com.hedera.pbj.compiler.impl.generators.EnumGenerator.createEnum;
Expand Down Expand Up @@ -42,21 +41,6 @@ public final class ModelGenerator implements Generator {

private static final String NON_NULL_ANNOTATION = "@NonNull";

private static final String HASH_CODE_MANIPULATION =
"""
// Shifts: 30, 27, 16, 20, 5, 18, 10, 24, 30
hashCode += hashCode << 30;
hashCode ^= hashCode >>> 27;
hashCode += hashCode << 16;
hashCode ^= hashCode >>> 20;
hashCode += hashCode << 5;
hashCode ^= hashCode >>> 18;
hashCode += hashCode << 10;
hashCode ^= hashCode >>> 24;
hashCode += hashCode << 30;
"""
.indent(DEFAULT_INDENT * 2);

/**
* {@inheritDoc}
*
Expand Down Expand Up @@ -94,6 +78,8 @@ public void generate(
writer.addImport("static " + lookupHelper.getFullyQualifiedMessageClassname(FileType.SCHEMA, msgDef) + ".*");
writer.addImport("java.util.Collections");
writer.addImport("java.util.List");
writer.addImport("com.hedera.pbj.runtime.hashing.XXH3_64");
writer.addImport("com.hedera.pbj.runtime.hashing.XXH3_64.HashingWritableSequentialData");

// Iterate over all the items in the protobuf schema
for (final var item : msgDef.messageBody().messageElement()) {
Expand Down Expand Up @@ -125,7 +111,7 @@ public void generate(
// add precomputed fields to fields
fields.add(new SingleField(
false,
FieldType.FIXED32,
FieldType.FIXED64,
-1,
"$hashCode",
null,
Expand Down Expand Up @@ -242,7 +228,7 @@ public void generate(
bodyContent += "\n";

// hashCode method
bodyContent += generateHashCode(fieldsNoPrecomputed);
bodyContent += ModelHashCodeGenerator.generateHashCode(fieldsNoPrecomputed);
bodyContent += "\n";

// equals method
Expand Down Expand Up @@ -330,6 +316,7 @@ private void generateClass(
// spotless:off
writer.append("""
$javaDocComment$deprecated
@java.lang.SuppressWarnings("ForLoopReplaceableByForEach")
public final$staticModifier class $javaRecordName $implementsComparable{
$bodyContent

Expand Down Expand Up @@ -531,63 +518,6 @@ public boolean equals(Object that) {
return bodyContent;
}

/**
* Generates the hashCode method
*
* @param fields the fields to use for the code generation
*
* @return the generated code
*/
@NonNull
private static String generateHashCode(final List<Field> fields) {
// Generate a call to private method that iterates through fields and calculates the hashcode
final String statements = getFieldsHashCode(fields, "");
// spotless:off
String bodyContent =
"""
/**
* Override the default hashCode method for to make hashCode better distributed and follows protobuf rules
* for default values. This is important for backward compatibility. This also lazy computes and caches the
* hashCode for future calls. It is designed to be thread safe.
*/
@Override
public int hashCode() {
// The $hashCode field is subject to a benign data race, making it crucial to ensure that any
// observable result of the calculation in this method stays correct under any possible read of this
// field. Necessary restrictions to allow this to be correct without explicit memory fences or similar
// concurrency primitives is that we can ever only write to this field for a given Model object
// instance, and that the computation is idempotent and derived from immutable state.
// This is the same trick used in java.lang.String.hashCode() to avoid synchronization.

if($hashCode == -1) {
int result = 1;
""".indent(DEFAULT_INDENT);

bodyContent += statements;

bodyContent +=
"""
if ($unknownFields != null) {
for (int i = 0; i < $unknownFields.size(); i++) {
result = 31 * result + $unknownFields.get(i).hashCode();
}
}
""".indent(DEFAULT_INDENT);

bodyContent +=
"""
long hashCode = result;
$hashCodeManipulation
$hashCode = (int)hashCode;
}
return $hashCode;
}
""".replace("$hashCodeManipulation", HASH_CODE_MANIPULATION)
.indent(DEFAULT_INDENT);
// spotless:on
return bodyContent;
}

/**
* Generates the toString method, based on how Java records generate toStrings
*
Expand Down
Loading
Loading