mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 23:51:55 +08:00
[pigeon] Implement equals for Java data classes (#6992)
Adds implementations of `equals` and `hashCode` to Java data classes. This is frequently useful for native unit tests of plugins using Pigeon (e.g., when using a mock FlutterApi implementation to check that the expected call is being made with the right arguments). Part of https://github.com/flutter/flutter/issues/118087
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
## NEXT
|
||||
## 20.0.2
|
||||
|
||||
* [java] Adds `equals` and `hashCode` support for data classes.
|
||||
* [swift] Fully-qualifies types in Equatable extension test.
|
||||
|
||||
## 20.0.1
|
||||
|
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Generated class from Pigeon. */
|
||||
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
|
||||
@ -132,6 +133,26 @@ public class Messages {
|
||||
/** Constructor is non-public to enforce null safety; use Builder. */
|
||||
MessageData() {}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MessageData that = (MessageData) o;
|
||||
return Objects.equals(name, that.name)
|
||||
&& Objects.equals(description, that.description)
|
||||
&& code.equals(that.code)
|
||||
&& data.equals(that.data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, description, code, data);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable String name;
|
||||
|
@ -13,7 +13,7 @@ import 'ast.dart';
|
||||
/// The current version of pigeon.
|
||||
///
|
||||
/// This must match the version in pubspec.yaml.
|
||||
const String pigeonVersion = '20.0.1';
|
||||
const String pigeonVersion = '20.0.2';
|
||||
|
||||
/// Prefix for all local variables in methods.
|
||||
///
|
||||
|
@ -141,6 +141,7 @@ class JavaGenerator extends StructuredGenerator<JavaOptions> {
|
||||
indent.writeln('import java.util.HashMap;');
|
||||
indent.writeln('import java.util.List;');
|
||||
indent.writeln('import java.util.Map;');
|
||||
indent.writeln('import java.util.Objects;');
|
||||
indent.newln();
|
||||
}
|
||||
|
||||
@ -233,6 +234,7 @@ class JavaGenerator extends StructuredGenerator<JavaOptions> {
|
||||
indent.writeln('${classDefinition.name}() {}');
|
||||
indent.newln();
|
||||
}
|
||||
_writeEquality(indent, classDefinition);
|
||||
|
||||
_writeClassBuilder(generatorOptions, root, indent, classDefinition);
|
||||
writeClassEncode(
|
||||
@ -282,6 +284,62 @@ class JavaGenerator extends StructuredGenerator<JavaOptions> {
|
||||
});
|
||||
}
|
||||
|
||||
void _writeEquality(Indent indent, Class classDefinition) {
|
||||
// Implement equals(...).
|
||||
indent.writeln('@Override');
|
||||
indent.writeScoped('public boolean equals(Object o) {', '}', () {
|
||||
indent.writeln('if (this == o) { return true; }');
|
||||
indent.writeln(
|
||||
'if (o == null || getClass() != o.getClass()) { return false; }');
|
||||
indent.writeln(
|
||||
'${classDefinition.name} that = (${classDefinition.name}) o;');
|
||||
final Iterable<String> checks = classDefinition.fields.map(
|
||||
(NamedType field) {
|
||||
// Objects.equals only does pointer equality for array types.
|
||||
if (_javaTypeIsArray(field.type)) {
|
||||
return 'Arrays.equals(${field.name}, that.${field.name})';
|
||||
}
|
||||
return field.type.isNullable
|
||||
? 'Objects.equals(${field.name}, that.${field.name})'
|
||||
: '${field.name}.equals(that.${field.name})';
|
||||
},
|
||||
);
|
||||
indent.writeln('return ${checks.join(' && ')};');
|
||||
});
|
||||
indent.newln();
|
||||
|
||||
// Implement hashCode().
|
||||
indent.writeln('@Override');
|
||||
indent.writeScoped('public int hashCode() {', '}', () {
|
||||
// As with equalty checks, arrays need special handling.
|
||||
final Iterable<String> arrayFieldNames = classDefinition.fields
|
||||
.where((NamedType field) => _javaTypeIsArray(field.type))
|
||||
.map((NamedType field) => field.name);
|
||||
final Iterable<String> nonArrayFieldNames = classDefinition.fields
|
||||
.where((NamedType field) => !_javaTypeIsArray(field.type))
|
||||
.map((NamedType field) => field.name);
|
||||
final String nonArrayHashValue = nonArrayFieldNames.isNotEmpty
|
||||
? 'Objects.hash(${nonArrayFieldNames.join(', ')})'
|
||||
: '0';
|
||||
|
||||
if (arrayFieldNames.isEmpty) {
|
||||
// Return directly if there are no array variables, to avoid redundant
|
||||
// variable lint warnings.
|
||||
indent.writeln('return $nonArrayHashValue;');
|
||||
} else {
|
||||
const String resultVar = '${varNamePrefix}result';
|
||||
indent.writeln('int $resultVar = $nonArrayHashValue;');
|
||||
// Manually mix in the Arrays.hashCode values.
|
||||
for (final String name in arrayFieldNames) {
|
||||
indent.writeln(
|
||||
'$resultVar = 31 * $resultVar + Arrays.hashCode($name);');
|
||||
}
|
||||
indent.writeln('return $resultVar;');
|
||||
}
|
||||
});
|
||||
indent.newln();
|
||||
}
|
||||
|
||||
void _writeClassBuilder(
|
||||
JavaOptions generatorOptions,
|
||||
Root root,
|
||||
@ -1022,6 +1080,10 @@ String _javaTypeForBuiltinGenericDartType(
|
||||
}
|
||||
}
|
||||
|
||||
bool _javaTypeIsArray(TypeDeclaration type) {
|
||||
return _javaTypeForBuiltinDartType(type)?.endsWith('[]') ?? false;
|
||||
}
|
||||
|
||||
String? _javaTypeForBuiltinDartType(TypeDeclaration type) {
|
||||
const Map<String, String> javaTypeForDartTypeMap = <String, String>{
|
||||
'bool': 'Boolean',
|
||||
|
@ -26,6 +26,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Generated class from Pigeon. */
|
||||
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
|
||||
@ -318,6 +319,58 @@ public class CoreTests {
|
||||
/** Constructor is non-public to enforce null safety; use Builder. */
|
||||
AllTypes() {}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AllTypes that = (AllTypes) o;
|
||||
return aBool.equals(that.aBool)
|
||||
&& anInt.equals(that.anInt)
|
||||
&& anInt64.equals(that.anInt64)
|
||||
&& aDouble.equals(that.aDouble)
|
||||
&& Arrays.equals(aByteArray, that.aByteArray)
|
||||
&& Arrays.equals(a4ByteArray, that.a4ByteArray)
|
||||
&& Arrays.equals(a8ByteArray, that.a8ByteArray)
|
||||
&& Arrays.equals(aFloatArray, that.aFloatArray)
|
||||
&& anEnum.equals(that.anEnum)
|
||||
&& aString.equals(that.aString)
|
||||
&& anObject.equals(that.anObject)
|
||||
&& list.equals(that.list)
|
||||
&& stringList.equals(that.stringList)
|
||||
&& intList.equals(that.intList)
|
||||
&& doubleList.equals(that.doubleList)
|
||||
&& boolList.equals(that.boolList)
|
||||
&& map.equals(that.map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int __pigeon_result =
|
||||
Objects.hash(
|
||||
aBool,
|
||||
anInt,
|
||||
anInt64,
|
||||
aDouble,
|
||||
anEnum,
|
||||
aString,
|
||||
anObject,
|
||||
list,
|
||||
stringList,
|
||||
intList,
|
||||
doubleList,
|
||||
boolList,
|
||||
map);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(a4ByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(a8ByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aFloatArray);
|
||||
return __pigeon_result;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable Boolean aBool;
|
||||
@ -772,6 +825,68 @@ public class CoreTests {
|
||||
this.map = setterArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AllNullableTypes that = (AllNullableTypes) o;
|
||||
return Objects.equals(aNullableBool, that.aNullableBool)
|
||||
&& Objects.equals(aNullableInt, that.aNullableInt)
|
||||
&& Objects.equals(aNullableInt64, that.aNullableInt64)
|
||||
&& Objects.equals(aNullableDouble, that.aNullableDouble)
|
||||
&& Arrays.equals(aNullableByteArray, that.aNullableByteArray)
|
||||
&& Arrays.equals(aNullable4ByteArray, that.aNullable4ByteArray)
|
||||
&& Arrays.equals(aNullable8ByteArray, that.aNullable8ByteArray)
|
||||
&& Arrays.equals(aNullableFloatArray, that.aNullableFloatArray)
|
||||
&& Objects.equals(nullableNestedList, that.nullableNestedList)
|
||||
&& Objects.equals(nullableMapWithAnnotations, that.nullableMapWithAnnotations)
|
||||
&& Objects.equals(nullableMapWithObject, that.nullableMapWithObject)
|
||||
&& Objects.equals(aNullableEnum, that.aNullableEnum)
|
||||
&& Objects.equals(aNullableString, that.aNullableString)
|
||||
&& Objects.equals(aNullableObject, that.aNullableObject)
|
||||
&& Objects.equals(allNullableTypes, that.allNullableTypes)
|
||||
&& Objects.equals(list, that.list)
|
||||
&& Objects.equals(stringList, that.stringList)
|
||||
&& Objects.equals(intList, that.intList)
|
||||
&& Objects.equals(doubleList, that.doubleList)
|
||||
&& Objects.equals(boolList, that.boolList)
|
||||
&& Objects.equals(nestedClassList, that.nestedClassList)
|
||||
&& Objects.equals(map, that.map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int __pigeon_result =
|
||||
Objects.hash(
|
||||
aNullableBool,
|
||||
aNullableInt,
|
||||
aNullableInt64,
|
||||
aNullableDouble,
|
||||
nullableNestedList,
|
||||
nullableMapWithAnnotations,
|
||||
nullableMapWithObject,
|
||||
aNullableEnum,
|
||||
aNullableString,
|
||||
aNullableObject,
|
||||
allNullableTypes,
|
||||
list,
|
||||
stringList,
|
||||
intList,
|
||||
doubleList,
|
||||
boolList,
|
||||
nestedClassList,
|
||||
map);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullableByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullable4ByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullable8ByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullableFloatArray);
|
||||
return __pigeon_result;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable Boolean aNullableBool;
|
||||
@ -1272,6 +1387,64 @@ public class CoreTests {
|
||||
this.map = setterArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AllNullableTypesWithoutRecursion that = (AllNullableTypesWithoutRecursion) o;
|
||||
return Objects.equals(aNullableBool, that.aNullableBool)
|
||||
&& Objects.equals(aNullableInt, that.aNullableInt)
|
||||
&& Objects.equals(aNullableInt64, that.aNullableInt64)
|
||||
&& Objects.equals(aNullableDouble, that.aNullableDouble)
|
||||
&& Arrays.equals(aNullableByteArray, that.aNullableByteArray)
|
||||
&& Arrays.equals(aNullable4ByteArray, that.aNullable4ByteArray)
|
||||
&& Arrays.equals(aNullable8ByteArray, that.aNullable8ByteArray)
|
||||
&& Arrays.equals(aNullableFloatArray, that.aNullableFloatArray)
|
||||
&& Objects.equals(nullableNestedList, that.nullableNestedList)
|
||||
&& Objects.equals(nullableMapWithAnnotations, that.nullableMapWithAnnotations)
|
||||
&& Objects.equals(nullableMapWithObject, that.nullableMapWithObject)
|
||||
&& Objects.equals(aNullableEnum, that.aNullableEnum)
|
||||
&& Objects.equals(aNullableString, that.aNullableString)
|
||||
&& Objects.equals(aNullableObject, that.aNullableObject)
|
||||
&& Objects.equals(list, that.list)
|
||||
&& Objects.equals(stringList, that.stringList)
|
||||
&& Objects.equals(intList, that.intList)
|
||||
&& Objects.equals(doubleList, that.doubleList)
|
||||
&& Objects.equals(boolList, that.boolList)
|
||||
&& Objects.equals(map, that.map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int __pigeon_result =
|
||||
Objects.hash(
|
||||
aNullableBool,
|
||||
aNullableInt,
|
||||
aNullableInt64,
|
||||
aNullableDouble,
|
||||
nullableNestedList,
|
||||
nullableMapWithAnnotations,
|
||||
nullableMapWithObject,
|
||||
aNullableEnum,
|
||||
aNullableString,
|
||||
aNullableObject,
|
||||
list,
|
||||
stringList,
|
||||
intList,
|
||||
doubleList,
|
||||
boolList,
|
||||
map);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullableByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullable4ByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullable8ByteArray);
|
||||
__pigeon_result = 31 * __pigeon_result + Arrays.hashCode(aNullableFloatArray);
|
||||
return __pigeon_result;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable Boolean aNullableBool;
|
||||
@ -1589,6 +1762,25 @@ public class CoreTests {
|
||||
/** Constructor is non-public to enforce null safety; use Builder. */
|
||||
AllClassesWrapper() {}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AllClassesWrapper that = (AllClassesWrapper) o;
|
||||
return allNullableTypes.equals(that.allNullableTypes)
|
||||
&& Objects.equals(allNullableTypesWithoutRecursion, that.allNullableTypesWithoutRecursion)
|
||||
&& Objects.equals(allTypes, that.allTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(allNullableTypes, allNullableTypesWithoutRecursion, allTypes);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable AllNullableTypes allNullableTypes;
|
||||
@ -1663,6 +1855,23 @@ public class CoreTests {
|
||||
this.testList = setterArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TestMessage that = (TestMessage) o;
|
||||
return Objects.equals(testList, that.testList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(testList);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private @Nullable List<Object> testList;
|
||||
|
@ -24,10 +24,10 @@ public class AllDatatypesTest {
|
||||
if (firstTypes == null || secondTypes == null) {
|
||||
return;
|
||||
}
|
||||
// Check all the fields individually to ensure that everything is as expected.
|
||||
assertEquals(firstTypes.getABool(), secondTypes.getABool());
|
||||
assertEquals(firstTypes.getAnInt(), secondTypes.getAnInt());
|
||||
assertEquals(firstTypes.getAnInt64(), secondTypes.getAnInt64());
|
||||
|
||||
assertEquals(firstTypes.getADouble(), secondTypes.getADouble());
|
||||
assertArrayEquals(firstTypes.getAByteArray(), secondTypes.getAByteArray());
|
||||
assertArrayEquals(firstTypes.getA4ByteArray(), secondTypes.getA4ByteArray());
|
||||
@ -44,6 +44,10 @@ public class AllDatatypesTest {
|
||||
firstTypes.getMap().keySet().toArray(), secondTypes.getMap().keySet().toArray());
|
||||
assertArrayEquals(
|
||||
firstTypes.getMap().values().toArray(), secondTypes.getMap().values().toArray());
|
||||
|
||||
// Also check that the implementation of equality works.
|
||||
assertEquals(firstTypes, secondTypes);
|
||||
assertEquals(firstTypes.hashCode(), secondTypes.hashCode());
|
||||
}
|
||||
|
||||
void compareAllNullableTypes(AllNullableTypes firstTypes, AllNullableTypes secondTypes) {
|
||||
@ -51,6 +55,7 @@ public class AllDatatypesTest {
|
||||
if (firstTypes == null || secondTypes == null) {
|
||||
return;
|
||||
}
|
||||
// Check all the fields individually to ensure that everything is as expected.
|
||||
assertEquals(firstTypes.getANullableBool(), secondTypes.getANullableBool());
|
||||
assertEquals(firstTypes.getANullableInt(), secondTypes.getANullableInt());
|
||||
assertEquals(firstTypes.getANullableDouble(), secondTypes.getANullableDouble());
|
||||
@ -74,6 +79,10 @@ public class AllDatatypesTest {
|
||||
firstTypes.getMap().keySet().toArray(), secondTypes.getMap().keySet().toArray());
|
||||
assertArrayEquals(
|
||||
firstTypes.getMap().values().toArray(), secondTypes.getMap().values().toArray());
|
||||
|
||||
// Also check that the implementation of equality works.
|
||||
assertEquals(firstTypes, secondTypes);
|
||||
assertEquals(firstTypes.hashCode(), secondTypes.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -152,6 +161,8 @@ public class AllDatatypesTest {
|
||||
|
||||
@Test
|
||||
public void hasValues() {
|
||||
// Not inline due to warnings about an ambiguous varargs call when inline.
|
||||
final Object[] genericList = new Boolean[] {true, false};
|
||||
AllTypes allEverything =
|
||||
new AllTypes.Builder()
|
||||
.setABool(false)
|
||||
@ -168,7 +179,7 @@ public class AllDatatypesTest {
|
||||
.setBoolList(Arrays.asList(new Boolean[] {true, false}))
|
||||
.setDoubleList(Arrays.asList(new Double[] {0.5, 0.25, 1.5, 1.25}))
|
||||
.setIntList(Arrays.asList(new Long[] {1l, 2l, 3l, 4l}))
|
||||
.setList(Arrays.asList(new int[] {1, 2, 3, 4}))
|
||||
.setList(Arrays.asList(genericList))
|
||||
.setStringList(Arrays.asList(new String[] {"string", "another one"}))
|
||||
.setMap(makeMap("hello", 1234))
|
||||
.build();
|
||||
@ -188,7 +199,7 @@ public class AllDatatypesTest {
|
||||
.setBoolList(Arrays.asList(new Boolean[] {true, false}))
|
||||
.setDoubleList(Arrays.asList(new Double[] {0.5, 0.25, 1.5, 1.25}))
|
||||
.setIntList(Arrays.asList(new Long[] {1l, 2l, 3l, 4l}))
|
||||
.setList(Arrays.asList(new int[] {1, 2, 3, 4}))
|
||||
.setList(Arrays.asList(genericList))
|
||||
.setStringList(Arrays.asList(new String[] {"string", "another one"}))
|
||||
.setMap(makeMap("hello", 1234))
|
||||
.build();
|
||||
|
@ -2,7 +2,7 @@ name: pigeon
|
||||
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
|
||||
repository: https://github.com/flutter/packages/tree/main/packages/pigeon
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pigeon%22
|
||||
version: 20.0.1 # This must match the version in lib/generator_tools.dart
|
||||
version: 20.0.2 # This must match the version in lib/generator_tools.dart
|
||||
|
||||
environment:
|
||||
sdk: ^3.2.0
|
||||
|
Reference in New Issue
Block a user