Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,11 @@ public interface Message {
* @return The raw tagged fields.
*/
List<RawTaggedField> unknownTaggedFields();

/**
* Make a deep copy of the message.
*
* @return A copy of the message which does not share any mutable fields.
*/
Message duplicate();
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,13 @@ public static double jsonNodeToDouble(JsonNode node, String about) {
}
return node.asDouble();
}

public static byte[] duplicate(byte[] array) {
if (array == null) {
return null;
}
byte[] newArray = new byte[array.length];
System.arraycopy(array, 0, newArray, 0, array.length);
return newArray;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
Expand Down Expand Up @@ -620,14 +621,29 @@ public void testSimpleMessage() throws Exception {
message.setMyNullableString("notNull");
message.setMyInt16((short) 3);
message.setMyString("test string");
SimpleExampleMessageData duplicate = message.duplicate();
assertEquals(duplicate, message);
assertEquals(message, duplicate);
duplicate.setMyTaggedIntArray(Collections.singletonList(123));
assertFalse(duplicate.equals(message));
assertFalse(message.equals(duplicate));

testAllMessageRoundTripsFromVersion((short) 2, message);
}

private void testAllMessageRoundTrips(Message message) throws Exception {
testDuplication(message);
testAllMessageRoundTripsFromVersion(message.lowestSupportedVersion(), message);
}

private void testDuplication(Message message) {
Message duplicate = message.duplicate();
assertEquals(duplicate, message);
assertEquals(message, duplicate);
assertEquals(duplicate.hashCode(), message.hashCode());
assertEquals(message.hashCode(), duplicate.hashCode());
}

private void testAllMessageRoundTripsBeforeVersion(short beforeVersion, Message message, Message expected) throws Exception {
testAllMessageRoundTripsBetweenVersions((short) 0, beforeVersion, message, expected);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.util.Optional;

public interface FieldType {
String STRUCT_PREFIX = "[]";
String ARRAY_PREFIX = "[]";

final class BoolFieldType implements FieldType {
static final BoolFieldType INSTANCE = new BoolFieldType();
Expand Down Expand Up @@ -268,8 +268,8 @@ static FieldType parse(String string) {
case BytesFieldType.NAME:
return BytesFieldType.INSTANCE;
default:
if (string.startsWith(STRUCT_PREFIX)) {
String elementTypeString = string.substring(STRUCT_PREFIX.length());
if (string.startsWith(ARRAY_PREFIX)) {
String elementTypeString = string.substring(ARRAY_PREFIX.length());
if (elementTypeString.length() == 0) {
throw new RuntimeException("Can't parse array type " + string +
". No element type found.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ private void generateClass(Optional<MessageSpec> topLevelMessageSpec,
buffer.printf("%n");
generateClassHashCode(struct, isSetElement);
buffer.printf("%n");
generateClassDuplicate(className, struct);
buffer.printf("%n");
generateClassToString(className, struct);
generateFieldAccessors(struct, isSetElement);
buffer.printf("%n");
Expand Down Expand Up @@ -193,10 +195,16 @@ private void generateHashSet(String className, StructSpec struct) {
collectionType(className), className);
buffer.incrementIndent();
generateHashSetZeroArgConstructor(className);
buffer.printf("%n");
generateHashSetSizeArgConstructor(className);
buffer.printf("%n");
generateHashSetIteratorConstructor(className);
buffer.printf("%n");
generateHashSetFindMethod(className, struct);
buffer.printf("%n");
generateHashSetFindAllMethod(className, struct);
buffer.printf("%n");
generateCollectionDuplicateMethod(className, struct);
buffer.decrementIndent();
buffer.printf("}%n");
}
Expand All @@ -207,7 +215,6 @@ private void generateHashSetZeroArgConstructor(String className) {
buffer.printf("super();%n");
buffer.decrementIndent();
buffer.printf("}%n");
buffer.printf("%n");
}

private void generateHashSetSizeArgConstructor(String className) {
Expand All @@ -216,7 +223,6 @@ private void generateHashSetSizeArgConstructor(String className) {
buffer.printf("super(expectedNumElements);%n");
buffer.decrementIndent();
buffer.printf("}%n");
buffer.printf("%n");
}

private void generateHashSetIteratorConstructor(String className) {
Expand All @@ -226,7 +232,6 @@ private void generateHashSetIteratorConstructor(String className) {
buffer.printf("super(iterator);%n");
buffer.decrementIndent();
buffer.printf("}%n");
buffer.printf("%n");
}

private void generateHashSetFindMethod(String className, StructSpec struct) {
Expand All @@ -239,7 +244,6 @@ private void generateHashSetFindMethod(String className, StructSpec struct) {
buffer.printf("return find(_key);%n");
buffer.decrementIndent();
buffer.printf("}%n");
buffer.printf("%n");
}

private void generateHashSetFindAllMethod(String className, StructSpec struct) {
Expand All @@ -252,7 +256,6 @@ private void generateHashSetFindAllMethod(String className, StructSpec struct) {
buffer.printf("return findAll(_key);%n");
buffer.decrementIndent();
buffer.printf("}%n");
buffer.printf("%n");
}

private void generateKeyElement(String className, StructSpec struct) {
Expand All @@ -273,6 +276,22 @@ private String commaSeparatedHashSetFieldAndTypes(StructSpec struct) {
collect(Collectors.joining(", "));
}

private void generateCollectionDuplicateMethod(String className, StructSpec struct) {
headerGenerator.addImport(MessageGenerator.LIST_CLASS);
buffer.printf("public %s duplicate() {%n", collectionType(className));
buffer.incrementIndent();
buffer.printf("%s _duplicate = new %s(size());%n",
collectionType(className), collectionType(className));
buffer.printf("for (%s _element : this) {%n", className);
buffer.incrementIndent();
buffer.printf("_duplicate.add(_element.duplicate());%n");
buffer.decrementIndent();
buffer.printf("}%n");
buffer.printf("return _duplicate;%n");
buffer.decrementIndent();
buffer.printf("}%n");
}

private void generateFieldDeclarations(StructSpec struct, boolean isSetElement) {
for (FieldSpec field : struct.fields()) {
generateFieldDeclaration(field);
Expand Down Expand Up @@ -2112,6 +2131,83 @@ private void generateFieldHashCode(FieldSpec field) {
}
}

private void generateClassDuplicate(String className, StructSpec struct) {
buffer.printf("@Override%n");
buffer.printf("public %s duplicate() {%n", className);
buffer.incrementIndent();
buffer.printf("%s _duplicate = new %s();%n", className, className);
for (FieldSpec field : struct.fields()) {
generateFieldDuplicate(new Target(field,
field.camelCaseName(),
field.camelCaseName(),
input -> String.format("_duplicate.%s = %s", field.camelCaseName(), input)));
}
buffer.printf("return _duplicate;%n");
buffer.decrementIndent();
buffer.printf("}%n");
}

private void generateFieldDuplicate(Target target) {
FieldSpec field = target.field();
if ((field.type() instanceof FieldType.BoolFieldType) ||
(field.type() instanceof FieldType.Int8FieldType) ||
(field.type() instanceof FieldType.Int16FieldType) ||
(field.type() instanceof FieldType.Int32FieldType) ||
(field.type() instanceof FieldType.Int64FieldType) ||
(field.type() instanceof FieldType.Float64FieldType) ||
(field.type() instanceof FieldType.UUIDFieldType)) {
buffer.printf("%s;%n", target.assignmentStatement(target.sourceVariable()));
} else {
IsNullConditional cond = IsNullConditional.forName(target.sourceVariable()).
nullableVersions(target.field().nullableVersions()).
ifNull(() -> buffer.printf("%s;%n", target.assignmentStatement("null")));
if (field.type().isBytes()) {
if (field.zeroCopy()) {
cond.ifShouldNotBeNull(() ->
buffer.printf("%s;%n", target.assignmentStatement(
String.format("%s.duplicate()", target.sourceVariable()))));
} else {
cond.ifShouldNotBeNull(() -> {
headerGenerator.addImport(MessageGenerator.MESSAGE_UTIL_CLASS);
buffer.printf("%s;%n", target.assignmentStatement(
String.format("MessageUtil.duplicate(%s)",
target.sourceVariable())));
});
}
} else if (field.type().isStruct()) {
cond.ifShouldNotBeNull(() ->
buffer.printf("%s;%n", target.assignmentStatement(
String.format("%s.duplicate()", target.sourceVariable()))));
} else if (field.type().isString()) {
// Strings are immutable, so we don't need to duplicate them.
cond.ifShouldNotBeNull(() ->
buffer.printf("%s;%n", target.assignmentStatement(
target.sourceVariable())));
} else if (field.type().isArray()) {
cond.ifShouldNotBeNull(() -> {
String newArrayName =
String.format("new%s", field.capitalizedCamelCaseName());
buffer.printf("%s %s = new %s(%s.size());%n",
fieldConcreteJavaType(field), newArrayName,
fieldConcreteJavaType(field), target.sourceVariable());
FieldType.ArrayType arrayType = (FieldType.ArrayType) field.type();
buffer.printf("for (%s _element : %s) {%n",
getBoxedJavaType(arrayType.elementType()), target.sourceVariable());
buffer.incrementIndent();
generateFieldDuplicate(target.arrayElementTarget(input ->
String.format("%s.add(%s)", newArrayName, input)));
buffer.decrementIndent();
buffer.printf("}%n");
buffer.printf("%s;%n", target.assignmentStatement(
String.format("new%s", field.capitalizedCamelCaseName())));
});
} else {
throw new RuntimeException("Unhandled field type " + field.type());
}
cond.generate(buffer);
}
}

private void generateClassToString(String className, StructSpec struct) {
buffer.printf("@Override%n");
buffer.printf("public String toString() {%n");
Expand Down Expand Up @@ -2310,13 +2406,7 @@ private String fieldDefault(FieldSpec field) {
field.name() + ". The only valid default for an array field " +
"is the empty array or null.");
}
FieldType.ArrayType arrayType = (FieldType.ArrayType) field.type();
if (structRegistry.isStructArrayWithKeys(field)) {
return "new " + collectionType(arrayType.elementType().toString()) + "(0)";
} else {
headerGenerator.addImport(MessageGenerator.ARRAYLIST_CLASS);
return "new ArrayList<" + getBoxedJavaType(arrayType.elementType()) + ">()";
}
return String.format("new %s(0)", fieldConcreteJavaType(field));
} else {
throw new RuntimeException("Unsupported field type " + field.type());
}
Expand Down