1 /*
2  * Copyright 2020 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 package androidx.appsearch.app;
17 
18 import androidx.annotation.RestrictTo;
19 import androidx.appsearch.annotation.CanIgnoreReturnValue;
20 import androidx.appsearch.flags.FlaggedApi;
21 import androidx.appsearch.flags.Flags;
22 import androidx.collection.ArrayMap;
23 import androidx.core.util.Preconditions;
24 
25 import org.jspecify.annotations.NonNull;
26 import org.jspecify.annotations.Nullable;
27 
28 import java.util.Collections;
29 import java.util.Map;
30 
31 /**
32  * Provides results for AppSearch batch operations which encompass multiple documents.
33  *
34  * <p>Individual results of a batch operation are separated into two maps: one for successes and
35  * one for failures. For successes, {@link #getSuccesses()} will return a map of keys to
36  * instances of the value type. For failures, {@link #getFailures()} will return a map of keys to
37  * {@link AppSearchResult} objects.
38  *
39  * <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for
40  * both successes and failures.
41  *
42  * @param <KeyType> The type of the keys for which the results will be reported.
43  * @param <ValueType> The type of the result objects for successful results.
44  *
45  * @see AppSearchSession#putAsync
46  * @see AppSearchSession#getByDocumentIdAsync
47  * @see AppSearchSession#removeAsync
48  */
49 public final class AppSearchBatchResult<KeyType, ValueType> {
50     private final @NonNull Map<KeyType, @Nullable ValueType> mSuccesses;
51     private final @NonNull Map<KeyType, AppSearchResult<ValueType>> mFailures;
52     private final @NonNull Map<KeyType, AppSearchResult<ValueType>> mAll;
53 
AppSearchBatchResult( @onNull Map<KeyType, @Nullable ValueType> successes, @NonNull Map<KeyType, AppSearchResult<ValueType>> failures, @NonNull Map<KeyType, AppSearchResult<ValueType>> all)54     AppSearchBatchResult(
55             @NonNull Map<KeyType, @Nullable ValueType> successes,
56             @NonNull Map<KeyType, AppSearchResult<ValueType>> failures,
57             @NonNull Map<KeyType, AppSearchResult<ValueType>> all) {
58         mSuccesses = Preconditions.checkNotNull(successes);
59         mFailures = Preconditions.checkNotNull(failures);
60         mAll = Preconditions.checkNotNull(all);
61     }
62 
63     /** Returns {@code true} if this {@link AppSearchBatchResult} has no failures. */
isSuccess()64     public boolean isSuccess() {
65         return mFailures.isEmpty();
66     }
67 
68     /**
69      * Returns a {@link Map} of keys mapped to instances of the value type for all successful
70      * individual results.
71      *
72      * <p>Example: {@link AppSearchSession#getByDocumentIdAsync} returns an
73      * {@link AppSearchBatchResult}. Each key (the document ID, of {@code String} type) will map to
74      * a {@link GenericDocument} object.
75      *
76      * <p>The values of the {@link Map} will not be {@code null}.
77      */
getSuccesses()78     public @NonNull Map<KeyType, ValueType> getSuccesses() {
79         return Collections.unmodifiableMap(mSuccesses);
80     }
81 
82     /**
83      * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all
84      * failed individual results.
85      *
86      * <p>The values of the {@link Map} will not be {@code null}.
87      */
getFailures()88     public @NonNull Map<KeyType, AppSearchResult<ValueType>> getFailures() {
89         return Collections.unmodifiableMap(mFailures);
90     }
91 
92     /**
93      * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all
94      * individual results.
95      *
96      * <p>The values of the {@link Map} will not be {@code null}.
97      */
getAll()98     public @NonNull Map<KeyType, AppSearchResult<ValueType>> getAll() {
99         return Collections.unmodifiableMap(mAll);
100     }
101 
102     /**
103      * Asserts that this {@link AppSearchBatchResult} has no failures.
104      * @exportToFramework:hide
105      */
106     @RestrictTo(RestrictTo.Scope.LIBRARY)
checkSuccess()107     public void checkSuccess() {
108         if (!isSuccess()) {
109             throw new IllegalStateException("AppSearchBatchResult has failures: " + this);
110         }
111     }
112 
113     @Override
toString()114     public @NonNull String toString() {
115         return "{\n  successes: " + mSuccesses + "\n  failures: " + mFailures + "\n}";
116     }
117 
118     /**
119      * Builder for {@link AppSearchBatchResult} objects.
120      *
121      * @param <KeyType> The type of the keys for which the results will be reported.
122      * @param <ValueType> The type of the result objects for successful results.
123      */
124     public static final class Builder<KeyType, ValueType> {
125         private ArrayMap<KeyType, @Nullable ValueType> mSuccesses = new ArrayMap<>();
126         private ArrayMap<KeyType, AppSearchResult<ValueType>> mFailures = new ArrayMap<>();
127         private ArrayMap<KeyType, AppSearchResult<ValueType>> mAll = new ArrayMap<>();
128         private boolean mBuilt = false;
129 
130         /** Creates a new {@link Builder}. */
Builder()131         public Builder() {
132         }
133 
134         /** Creates a new {@link Builder} from the given {@link AppSearchBatchResult}. */
135         @ExperimentalAppSearchApi
136         @FlaggedApi(Flags.FLAG_ENABLE_ADDITIONAL_BUILDER_COPY_CONSTRUCTORS)
Builder(@onNull AppSearchBatchResult<KeyType, ValueType> appSearchBatchResult)137         public Builder(@NonNull AppSearchBatchResult<KeyType, ValueType> appSearchBatchResult) {
138             mSuccesses.putAll(appSearchBatchResult.mSuccesses);
139             mFailures.putAll(appSearchBatchResult.mFailures);
140             mAll.putAll(appSearchBatchResult.mAll);
141         }
142 
143         /**
144          * Associates the {@code key} with the provided successful return value.
145          *
146          * <p>Any previous mapping for a key, whether success or failure, is deleted.
147          *
148          * <p>This is a convenience function which is equivalent to
149          * {@code setResult(key, AppSearchResult.newSuccessfulResult(value))}.
150          *
151          * @param key   The key to associate the result with; usually corresponds to some
152          *              identifier from the input like an ID or name.
153          * @param value An optional value to associate with the successful result of the operation
154          *              being performed.
155          */
156         @CanIgnoreReturnValue
157         @SuppressWarnings("MissingGetterMatchingBuilder")  // See getSuccesses
setSuccess( @onNull KeyType key, @Nullable ValueType value)158         public @NonNull Builder<KeyType, ValueType> setSuccess(
159                 @NonNull KeyType key, @Nullable ValueType value) {
160             Preconditions.checkNotNull(key);
161             resetIfBuilt();
162             return setResult(key, AppSearchResult.newSuccessfulResult(value));
163         }
164 
165         /**
166          * Associates the {@code key} with the provided failure code and error message.
167          *
168          * <p>Any previous mapping for a key, whether success or failure, is deleted.
169          *
170          * <p>This is a convenience function which is equivalent to
171          * {@code setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage))}.
172          *
173          * @param key          The key to associate the result with; usually corresponds to some
174          *                     identifier from the input like an ID or name.
175          * @param resultCode   One of the constants documented in
176          *                     {@link AppSearchResult#getResultCode}.
177          * @param errorMessage An optional string describing the reason or nature of the failure.
178          */
179         @CanIgnoreReturnValue
180         @SuppressWarnings("MissingGetterMatchingBuilder")  // See getFailures
setFailure( @onNull KeyType key, @AppSearchResult.ResultCode int resultCode, @Nullable String errorMessage)181         public @NonNull Builder<KeyType, ValueType> setFailure(
182                 @NonNull KeyType key,
183                 @AppSearchResult.ResultCode int resultCode,
184                 @Nullable String errorMessage) {
185             Preconditions.checkNotNull(key);
186             resetIfBuilt();
187             return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage));
188         }
189 
190         /**
191          * Associates the {@code key} with the provided {@code result}.
192          *
193          * <p>Any previous mapping for a key, whether success or failure, is deleted.
194          *
195          * @param key    The key to associate the result with; usually corresponds to some
196          *               identifier from the input like an ID or name.
197          * @param result The result to associate with the key.
198          */
199         @CanIgnoreReturnValue
200         @SuppressWarnings("MissingGetterMatchingBuilder")  // See getAll
setResult( @onNull KeyType key, @NonNull AppSearchResult<ValueType> result)201         public @NonNull Builder<KeyType, ValueType> setResult(
202                 @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
203             Preconditions.checkNotNull(key);
204             Preconditions.checkNotNull(result);
205             resetIfBuilt();
206             if (result.isSuccess()) {
207                 mSuccesses.put(key, result.getResultValue());
208                 mFailures.remove(key);
209             } else {
210                 mFailures.put(key, result);
211                 mSuccesses.remove(key);
212             }
213             mAll.put(key, result);
214             return this;
215         }
216 
217         /**
218          * Builds an {@link AppSearchBatchResult} object from the contents of this {@link Builder}.
219          */
build()220         public @NonNull AppSearchBatchResult<KeyType, ValueType> build() {
221             mBuilt = true;
222             return new AppSearchBatchResult<>(mSuccesses, mFailures, mAll);
223         }
224 
resetIfBuilt()225         private void resetIfBuilt() {
226             if (mBuilt) {
227                 mSuccesses = new ArrayMap<>(mSuccesses);
228                 mFailures = new ArrayMap<>(mFailures);
229                 mAll = new ArrayMap<>(mAll);
230                 mBuilt = false;
231             }
232         }
233     }
234 }
235