Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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,10 @@ 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.XXH3FieldHash");
writer.addImport("com.hedera.pbj.runtime.hashing.SixtyFourBitHashable");
writer.addImport("static com.hedera.pbj.runtime.hashing.XXH3FieldHash.*");

// Iterate over all the items in the protobuf schema
for (final var item : msgDef.messageBody().messageElement()) {
Expand Down Expand Up @@ -125,7 +113,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 +230,7 @@ public void generate(
bodyContent += "\n";

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

// equals method
Expand Down Expand Up @@ -310,11 +298,11 @@ private void generateClass(
final boolean isComparable,
final ContextualLookupHelper lookupHelper)
throws IOException {
final String implementsComparable;
final String implementsCode;
if (isComparable) {
implementsComparable = "implements Comparable<$javaRecordName> ";
implementsCode = "implements SixtyFourBitHashable, Comparable<$javaRecordName> ";
} else {
implementsComparable = "";
implementsCode = "implements SixtyFourBitHashable ";
}

final String staticModifier = Generator.isInner(msgDef) ? " static" : "";
Expand All @@ -330,14 +318,15 @@ private void generateClass(
// spotless:off
writer.append("""
$javaDocComment$deprecated
@java.lang.SuppressWarnings("ForLoopReplaceableByForEach")
public final$staticModifier class $javaRecordName $implementsComparable{
$bodyContent

"""
.replace("$javaDocComment", javaDocComment)
.replace("$deprecated", deprecated)
.replace("$staticModifier", staticModifier)
.replace("$implementsComparable", implementsComparable)
.replace("$implementsComparable", implementsCode)
.replace("$javaRecordName", javaRecordName)
.replace("$bodyContent", bodyContent));
// spotless:on
Expand Down Expand Up @@ -531,63 +520,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