Merge "Implement hashCode and equals for function nodes." into androidx-main
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/GetSearchStringParameterNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/GetSearchStringParameterNodeCtsTest.java
index d6c07a5..3628fe2 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/GetSearchStringParameterNodeCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/GetSearchStringParameterNodeCtsTest.java
@@ -35,6 +35,18 @@
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Test
+    public void testEquals_identical() {
+        GetSearchStringParameterNode getSearchStringParameterNodeOne =
+                new GetSearchStringParameterNode(1);
+        GetSearchStringParameterNode getSearchStringParameterNodeTwo =
+                new GetSearchStringParameterNode(1);
+
+        assertThat(getSearchStringParameterNodeOne).isEqualTo(getSearchStringParameterNodeTwo);
+        assertThat(getSearchStringParameterNodeOne.hashCode())
+                .isEqualTo(getSearchStringParameterNodeTwo.hashCode());
+    }
+
+    @Test
     public void testConstructor_throwsOnNegativeIndex() {
         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
                 () -> new GetSearchStringParameterNode(-1));
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/HasPropertyNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/HasPropertyNodeCtsTest.java
index 978a00e..345081f 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/HasPropertyNodeCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/HasPropertyNodeCtsTest.java
@@ -38,6 +38,17 @@
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Test
+    public void testEquals_identical() {
+        HasPropertyNode hasPropertyOne =
+                new HasPropertyNode(new PropertyPath("example.property.path"));
+        HasPropertyNode hasPropertyTwo =
+                new HasPropertyNode(new PropertyPath("example.property.path"));
+
+        assertThat(hasPropertyOne).isEqualTo(hasPropertyTwo);
+        assertThat(hasPropertyOne.hashCode()).isEqualTo(hasPropertyTwo.hashCode());
+    }
+
+    @Test
     public void testConstructor_throwsOnNullPointer() {
         assertThrows(NullPointerException.class, () -> new HasPropertyNode(null));
     }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/PropertyDefinedNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/PropertyDefinedNodeCtsTest.java
index 6577fcc..a4cd6ba 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/PropertyDefinedNodeCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/PropertyDefinedNodeCtsTest.java
@@ -38,6 +38,17 @@
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Test
+    public void testEquals_identical() {
+        PropertyDefinedNode propertyDefinedOne = new PropertyDefinedNode(
+                new PropertyPath("example.property.path"));
+        PropertyDefinedNode propertyDefinedTwo = new PropertyDefinedNode(
+                new PropertyPath("example.property.path"));
+
+        assertThat(propertyDefinedOne).isEqualTo(propertyDefinedTwo);
+        assertThat(propertyDefinedOne.hashCode()).isEqualTo(propertyDefinedTwo.hashCode());
+    }
+
+    @Test
     public void testConstructor_throwsOnNullPointer() {
         assertThrows(NullPointerException.class, () -> new PropertyDefinedNode(null));
     }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SearchNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SearchNodeCtsTest.java
index 9f9219e..041af4e 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SearchNodeCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SearchNodeCtsTest.java
@@ -42,6 +42,17 @@
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Test
+    public void testEquals_identical() {
+        SearchNode searchNodeOne = new SearchNode(
+                new TextNode("foo"), List.of(new PropertyPath("example.property.path")));
+        SearchNode searchNodeTwo = new SearchNode(
+                new TextNode("foo"), List.of(new PropertyPath("example.property.path")));
+
+        assertThat(searchNodeOne).isEqualTo(searchNodeTwo);
+        assertThat(searchNodeOne.hashCode()).isEqualTo(searchNodeTwo.hashCode());
+    }
+
+    @Test
     public void testConstructor_defaultValues() {
         TextNode node = new TextNode("foo");
         SearchNode searchNode = new SearchNode(node);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SemanticSearchNodeCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SemanticSearchNodeCtsTest.java
index 41c8eb3..b76f036 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SemanticSearchNodeCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/ast/query/SemanticSearchNodeCtsTest.java
@@ -36,6 +36,17 @@
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Test
+    public void testEquals_identical() {
+        SemanticSearchNode semanticSearchNodeOne = new SemanticSearchNode(0, -1.5f, 3,
+                SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
+        SemanticSearchNode semanticSearchNodeTwo = new SemanticSearchNode(0, -1.5f, 3,
+                SearchSpec.EMBEDDING_SEARCH_METRIC_TYPE_DOT_PRODUCT);
+
+        assertThat(semanticSearchNodeOne).isEqualTo(semanticSearchNodeTwo);
+        assertThat(semanticSearchNodeOne.hashCode()).isEqualTo(semanticSearchNodeTwo.hashCode());
+    }
+
+    @Test
     public void testConstructor_throwsOnNegativeIndex() {
         assertThrows(IllegalArgumentException.class, () -> new SemanticSearchNode(-1));
     }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/GetSearchStringParameterNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/GetSearchStringParameterNode.java
index e427d02..c336dfa 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/GetSearchStringParameterNode.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/GetSearchStringParameterNode.java
@@ -104,4 +104,17 @@
         return FunctionNode.FUNCTION_NAME_GET_SEARCH_STRING_PARAMETER
                 + "(" + mSearchStringIndex + ")";
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        GetSearchStringParameterNode that = (GetSearchStringParameterNode) o;
+        return mSearchStringIndex == that.mSearchStringIndex;
+    }
+
+    @Override
+    public int hashCode() {
+        return Integer.hashCode(mSearchStringIndex);
+    }
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/HasPropertyNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/HasPropertyNode.java
index fef437b..1207f74 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/HasPropertyNode.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/HasPropertyNode.java
@@ -24,6 +24,8 @@
 import androidx.appsearch.flags.Flags;
 import androidx.core.util.Preconditions;
 
+import java.util.Objects;
+
 /**
  * {@link FunctionNode} representing the `hasProperty` query function.
  *
@@ -84,4 +86,17 @@
     public String toString() {
         return FunctionNode.FUNCTION_NAME_HAS_PROPERTY + "(\"" + mProperty + "\")";
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        HasPropertyNode that = (HasPropertyNode) o;
+        return Objects.equals(mProperty, that.mProperty);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mProperty);
+    }
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/PropertyDefinedNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/PropertyDefinedNode.java
index 4423a38..ccfdd96 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/PropertyDefinedNode.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/PropertyDefinedNode.java
@@ -24,6 +24,8 @@
 import androidx.appsearch.flags.Flags;
 import androidx.core.util.Preconditions;
 
+import java.util.Objects;
+
 /**
  * {@link FunctionNode} representing the `propertyDefined` query function.
  *
@@ -85,4 +87,17 @@
     public String toString() {
         return FUNCTION_NAME_PROPERTY_DEFINED + "(\"" + mProperty + "\")";
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PropertyDefinedNode that = (PropertyDefinedNode) o;
+        return Objects.equals(mProperty, that.mProperty);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mProperty);
+    }
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SearchNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SearchNode.java
index 6ed85b4..1bf086c 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SearchNode.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SearchNode.java
@@ -28,6 +28,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * {@link FunctionNode} that represents the search function.
@@ -219,4 +220,18 @@
         }
         return stringBuilder.toString();
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SearchNode that = (SearchNode) o;
+        return Objects.equals(mChildren, that.mChildren) && Objects.equals(
+                mProperties, that.mProperties);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mChildren, mProperties);
+    }
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SemanticSearchNode.java b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SemanticSearchNode.java
index 7f9f07a..8fe088d 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SemanticSearchNode.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/ast/query/SemanticSearchNode.java
@@ -24,6 +24,8 @@
 import androidx.appsearch.flags.Flags;
 import androidx.core.util.Preconditions;
 
+import java.util.Objects;
+
 /**
  * {@link FunctionNode} that represents the semanticSearch function.
  *
@@ -297,4 +299,19 @@
         }
         throw new IllegalStateException("Invalid Metric Type");
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SemanticSearchNode that = (SemanticSearchNode) o;
+        return mVectorIndex == that.mVectorIndex && Float.compare(mLowerBound,
+                that.mLowerBound) == 0 && Float.compare(mUpperBound, that.mUpperBound) == 0
+                && mDistanceMetric == that.mDistanceMetric;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mVectorIndex, mLowerBound, mUpperBound, mDistanceMetric);
+    }
 }