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