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