1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.appsearch.safeparcel;
18 
19 
20 import android.annotation.SuppressLint;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 
24 import androidx.annotation.OptIn;
25 import androidx.annotation.RestrictTo;
26 import androidx.appsearch.annotation.CanIgnoreReturnValue;
27 import androidx.appsearch.app.AppSearchBlobHandle;
28 import androidx.appsearch.app.EmbeddingVector;
29 import androidx.appsearch.app.ExperimentalAppSearchApi;
30 
31 import org.jspecify.annotations.NonNull;
32 import org.jspecify.annotations.Nullable;
33 
34 import java.util.Arrays;
35 import java.util.Objects;
36 
37 /**
38  * A {@link SafeParcelable} to hold the value of a property in {@code GenericDocument#mProperties}.
39  *
40  * <p>This resembles PropertyProto in IcingLib.
41  *
42  * @exportToFramework:hide
43  */
44 @RestrictTo(RestrictTo.Scope.LIBRARY)
45 @SafeParcelable.Class(creator = "PropertyParcelCreator")
46 // This won't be used to send data over binder, and we have to use Parcelable for code sync purpose.
47 @SuppressLint("BanParcelableUsage")
48 public final class PropertyParcel extends AbstractSafeParcelable implements Parcelable {
49     public static final Parcelable.@NonNull Creator<PropertyParcel> CREATOR =
50             new PropertyParcelCreator();
51 
52     @Field(id = 1, getter = "getPropertyName")
53     private final @NonNull String mPropertyName;
54 
55     @Field(id = 2, getter = "getStringValues")
56     private final String @Nullable [] mStringValues;
57 
58     @Field(id = 3, getter = "getLongValues")
59     private final long @Nullable [] mLongValues;
60 
61     @Field(id = 4, getter = "getDoubleValues")
62     private final double @Nullable [] mDoubleValues;
63 
64     @Field(id = 5, getter = "getBooleanValues")
65     private final boolean @Nullable [] mBooleanValues;
66 
67     @Field(id = 6, getter = "getBytesValues")
68     private final byte @Nullable [][] mBytesValues;
69 
70     @Field(id = 7, getter = "getDocumentValues")
71     private final GenericDocumentParcel @Nullable [] mDocumentValues;
72 
73     @Field(id = 8, getter = "getEmbeddingValues")
74     private final EmbeddingVector @Nullable [] mEmbeddingValues;
75 
76     @Field(id = 9, getter = "getBlobHandleValues")
77     @ExperimentalAppSearchApi
78     private final AppSearchBlobHandle @Nullable [] mBlobHandleValues;
79 
80     private @Nullable Integer mHashCode;
81 
82     @Constructor
83     @OptIn(markerClass = ExperimentalAppSearchApi.class)
PropertyParcel( @aramid = 1) @onNull String propertyName, @Param(id = 2) String @Nullable [] stringValues, @Param(id = 3) long @Nullable [] longValues, @Param(id = 4) double @Nullable [] doubleValues, @Param(id = 5) boolean @Nullable [] booleanValues, @Param(id = 6) byte @Nullable [][] bytesValues, @Param(id = 7) GenericDocumentParcel @Nullable [] documentValues, @Param(id = 8) EmbeddingVector @Nullable [] embeddingValues, @Param(id = 9) AppSearchBlobHandle @Nullable [] blobHandleValues)84     PropertyParcel(
85             @Param(id = 1) @NonNull String propertyName,
86             @Param(id = 2) String @Nullable [] stringValues,
87             @Param(id = 3) long @Nullable [] longValues,
88             @Param(id = 4) double @Nullable [] doubleValues,
89             @Param(id = 5) boolean @Nullable [] booleanValues,
90             @Param(id = 6) byte @Nullable [][] bytesValues,
91             @Param(id = 7) GenericDocumentParcel @Nullable [] documentValues,
92             @Param(id = 8) EmbeddingVector @Nullable [] embeddingValues,
93             @Param(id = 9) AppSearchBlobHandle @Nullable [] blobHandleValues) {
94         mPropertyName = Objects.requireNonNull(propertyName);
95         mStringValues = stringValues;
96         mLongValues = longValues;
97         mDoubleValues = doubleValues;
98         mBooleanValues = booleanValues;
99         mBytesValues = bytesValues;
100         mDocumentValues = documentValues;
101         mEmbeddingValues = embeddingValues;
102         mBlobHandleValues = blobHandleValues;
103         checkOnlyOneArrayCanBeSet();
104     }
105 
106     /** Returns the name of the property. */
getPropertyName()107     public @NonNull String getPropertyName() {
108         return mPropertyName;
109     }
110 
111     /** Returns {@code String} values in an array. */
getStringValues()112     public String @Nullable [] getStringValues() {
113         return mStringValues;
114     }
115 
116     /** Returns {@code long} values in an array. */
getLongValues()117     public long @Nullable [] getLongValues() {
118         return mLongValues;
119     }
120 
121     /** Returns {@code double} values in an array. */
getDoubleValues()122     public double @Nullable [] getDoubleValues() {
123         return mDoubleValues;
124     }
125 
126     /** Returns {@code boolean} values in an array. */
getBooleanValues()127     public boolean @Nullable [] getBooleanValues() {
128         return mBooleanValues;
129     }
130 
131     /** Returns a two-dimension {@code byte} array. */
getBytesValues()132     public byte @Nullable [][] getBytesValues() {
133         return mBytesValues;
134     }
135 
136     /** Returns {@link GenericDocumentParcel}s in an array. */
getDocumentValues()137     public GenericDocumentParcel @Nullable [] getDocumentValues() {
138         return mDocumentValues;
139     }
140 
141     /** Returns {@link EmbeddingVector}s in an array. */
getEmbeddingValues()142     public EmbeddingVector @Nullable [] getEmbeddingValues() {
143         return mEmbeddingValues;
144     }
145 
146     /** Returns {@link AppSearchBlobHandle}s in an array. */
147     @OptIn(markerClass = ExperimentalAppSearchApi.class)
getBlobHandleValues()148     public AppSearchBlobHandle @Nullable [] getBlobHandleValues() {
149         return mBlobHandleValues;
150     }
151 
152     /**
153      * Returns the held values in an array for this property.
154      *
155      * <p>Different from other getter methods, this one will return an {@link Object}.
156      */
157     @OptIn(markerClass = ExperimentalAppSearchApi.class)
getValues()158     public @Nullable Object getValues() {
159         if (mStringValues != null) {
160             return mStringValues;
161         }
162         if (mLongValues != null) {
163             return mLongValues;
164         }
165         if (mDoubleValues != null) {
166             return mDoubleValues;
167         }
168         if (mBooleanValues != null) {
169             return mBooleanValues;
170         }
171         if (mBytesValues != null) {
172             return mBytesValues;
173         }
174         if (mDocumentValues != null) {
175             return mDocumentValues;
176         }
177         if (mEmbeddingValues != null) {
178             return mEmbeddingValues;
179         }
180         if (mBlobHandleValues != null) {
181             return mBlobHandleValues;
182         }
183         return null;
184     }
185 
186     /**
187      * Checks there is one and only one array can be set for the property.
188      *
189      * @throws IllegalArgumentException if 0, or more than 1 arrays are set.
190      */
191     @OptIn(markerClass = ExperimentalAppSearchApi.class)
checkOnlyOneArrayCanBeSet()192     private void checkOnlyOneArrayCanBeSet() {
193         int notNullCount = 0;
194         if (mStringValues != null) {
195             ++notNullCount;
196         }
197         if (mLongValues != null) {
198             ++notNullCount;
199         }
200         if (mDoubleValues != null) {
201             ++notNullCount;
202         }
203         if (mBooleanValues != null) {
204             ++notNullCount;
205         }
206         if (mBytesValues != null) {
207             ++notNullCount;
208         }
209         if (mDocumentValues != null) {
210             ++notNullCount;
211         }
212         if (mEmbeddingValues != null) {
213             ++notNullCount;
214         }
215         if (mBlobHandleValues != null) {
216             ++notNullCount;
217         }
218         if (notNullCount == 0 || notNullCount > 1) {
219             throw new IllegalArgumentException(
220                     "One and only one type array can be set in PropertyParcel");
221         }
222     }
223 
224     @Override
225     @OptIn(markerClass = ExperimentalAppSearchApi.class)
hashCode()226     public int hashCode() {
227         if (mHashCode == null) {
228             int hashCode = 0;
229             if (mStringValues != null) {
230                 hashCode = Arrays.hashCode(mStringValues);
231             } else if (mLongValues != null) {
232                 hashCode = Arrays.hashCode(mLongValues);
233             } else if (mDoubleValues != null) {
234                 hashCode = Arrays.hashCode(mDoubleValues);
235             } else if (mBooleanValues != null) {
236                 hashCode = Arrays.hashCode(mBooleanValues);
237             } else if (mBytesValues != null) {
238                 hashCode = Arrays.deepHashCode(mBytesValues);
239             } else if (mDocumentValues != null) {
240                 hashCode = Arrays.hashCode(mDocumentValues);
241             } else if (mEmbeddingValues != null) {
242                 hashCode = Arrays.deepHashCode(mEmbeddingValues);
243             } else if (mBlobHandleValues != null) {
244                 hashCode = Arrays.deepHashCode(mBlobHandleValues);
245             }
246             mHashCode = Objects.hash(mPropertyName, hashCode);
247         }
248         return mHashCode;
249     }
250 
251     @Override
252     @OptIn(markerClass = ExperimentalAppSearchApi.class)
equals(@ullable Object other)253     public boolean equals(@Nullable Object other) {
254         if (this == other) {
255             return true;
256         }
257         if (!(other instanceof PropertyParcel)) {
258             return false;
259         }
260         PropertyParcel otherPropertyParcel = (PropertyParcel) other;
261         if (!mPropertyName.equals(otherPropertyParcel.mPropertyName)) {
262             return false;
263         }
264         return Arrays.equals(mStringValues, otherPropertyParcel.mStringValues)
265                 && Arrays.equals(mLongValues, otherPropertyParcel.mLongValues)
266                 && Arrays.equals(mDoubleValues, otherPropertyParcel.mDoubleValues)
267                 && Arrays.equals(mBooleanValues, otherPropertyParcel.mBooleanValues)
268                 && Arrays.deepEquals(mBytesValues, otherPropertyParcel.mBytesValues)
269                 && Arrays.equals(mDocumentValues, otherPropertyParcel.mDocumentValues)
270                 && Arrays.deepEquals(mEmbeddingValues, otherPropertyParcel.mEmbeddingValues)
271                 && Arrays.deepEquals(mBlobHandleValues, otherPropertyParcel.mBlobHandleValues);
272     }
273 
274     @Override
writeToParcel(@onNull Parcel dest, int flags)275     public void writeToParcel(@NonNull Parcel dest, int flags) {
276         PropertyParcelCreator.writeToParcel(this, dest, flags);
277     }
278 
279     /** Builder for {@link PropertyParcel}. */
280     @ExperimentalAppSearchApi
281     public static final class Builder {
282         private String mPropertyName;
283         private String[] mStringValues;
284         private long[] mLongValues;
285         private double[] mDoubleValues;
286         private boolean[] mBooleanValues;
287         private byte[][] mBytesValues;
288         private GenericDocumentParcel[] mDocumentValues;
289         private EmbeddingVector[] mEmbeddingValues;
290         private AppSearchBlobHandle[] mBlobHandleValues;
291 
Builder(@onNull String propertyName)292         public Builder(@NonNull String propertyName) {
293             mPropertyName = Objects.requireNonNull(propertyName);
294         }
295 
296         /** Sets String values. */
297         @CanIgnoreReturnValue
setStringValues(String @onNull [] stringValues)298         public @NonNull Builder setStringValues(String @NonNull [] stringValues) {
299             mStringValues = Objects.requireNonNull(stringValues);
300             return this;
301         }
302 
303         /** Sets long values. */
304         @CanIgnoreReturnValue
setLongValues(long @NonNull [] longValues)305         public @NonNull Builder setLongValues(long @NonNull [] longValues) {
306             mLongValues = Objects.requireNonNull(longValues);
307             return this;
308         }
309 
310         /** Sets double values. */
311         @CanIgnoreReturnValue
setDoubleValues(double @NonNull [] doubleValues)312         public @NonNull Builder setDoubleValues(double @NonNull [] doubleValues) {
313             mDoubleValues = Objects.requireNonNull(doubleValues);
314             return this;
315         }
316 
317         /** Sets boolean values. */
318         @CanIgnoreReturnValue
setBooleanValues(boolean @NonNull [] booleanValues)319         public @NonNull Builder setBooleanValues(boolean @NonNull [] booleanValues) {
320             mBooleanValues = Objects.requireNonNull(booleanValues);
321             return this;
322         }
323 
324         /** Sets a two dimension byte array. */
325         @CanIgnoreReturnValue
setBytesValues(byte @NonNull [][] bytesValues)326         public @NonNull Builder setBytesValues(byte @NonNull [][] bytesValues) {
327             mBytesValues = Objects.requireNonNull(bytesValues);
328             return this;
329         }
330 
331         /** Sets document values. */
332         @CanIgnoreReturnValue
setDocumentValues( GenericDocumentParcel @onNull [] documentValues)333         public @NonNull Builder setDocumentValues(
334                 GenericDocumentParcel @NonNull [] documentValues) {
335             mDocumentValues = Objects.requireNonNull(documentValues);
336             return this;
337         }
338 
339         /** Sets embedding values. */
340         @CanIgnoreReturnValue
setEmbeddingValues(EmbeddingVector @onNull [] embeddingValues)341         public @NonNull Builder setEmbeddingValues(EmbeddingVector @NonNull [] embeddingValues) {
342             mEmbeddingValues = Objects.requireNonNull(embeddingValues);
343             return this;
344         }
345 
346         /** Sets {@link AppSearchBlobHandle} values. */
347         @CanIgnoreReturnValue
setBlobHandleValues( AppSearchBlobHandle @onNull [] blobHandleValues)348         public @NonNull Builder setBlobHandleValues(
349                 AppSearchBlobHandle @NonNull [] blobHandleValues) {
350             mBlobHandleValues = Objects.requireNonNull(blobHandleValues);
351             return this;
352         }
353 
354         /** Builds a {@link PropertyParcel}. */
build()355         public @NonNull PropertyParcel build() {
356             return new PropertyParcel(
357                     mPropertyName,
358                     mStringValues,
359                     mLongValues,
360                     mDoubleValues,
361                     mBooleanValues,
362                     mBytesValues,
363                     mDocumentValues,
364                     mEmbeddingValues,
365                     mBlobHandleValues);
366         }
367     }
368 }
369