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