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 17 package android.app.appsearch; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.appsearch.annotation.CanIgnoreReturnValue; 23 import android.app.appsearch.safeparcel.AbstractSafeParcelable; 24 import android.app.appsearch.safeparcel.SafeParcelable; 25 import android.app.appsearch.util.BundleUtil; 26 import android.os.Bundle; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.util.ArrayMap; 30 import android.util.ArraySet; 31 32 import com.android.appsearch.flags.Flags; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Collection; 37 import java.util.Collections; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Objects; 41 import java.util.Set; 42 43 /** 44 * Encapsulates a request to retrieve documents by namespace and IDs from the {@link 45 * AppSearchSession} database. 46 * 47 * @see AppSearchSession#getByDocumentId 48 */ 49 @SuppressWarnings("HiddenSuperclass") 50 @SafeParcelable.Class(creator = "GetByDocumentIdRequestCreator") 51 public final class GetByDocumentIdRequest extends AbstractSafeParcelable { 52 53 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 54 @NonNull 55 public static final Parcelable.Creator<GetByDocumentIdRequest> CREATOR = 56 new GetByDocumentIdRequestCreator(); 57 58 /** 59 * Schema type to be used in {@link GetByDocumentIdRequest.Builder#addProjection} to apply 60 * property paths to all results, excepting any types that have had their own, specific property 61 * paths set. 62 */ 63 public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*"; 64 65 @NonNull 66 @Field(id = 1, getter = "getNamespace") 67 private final String mNamespace; 68 69 @NonNull 70 @Field(id = 2) 71 final List<String> mIds; 72 73 @NonNull 74 @Field(id = 3) 75 final Bundle mTypePropertyPaths; 76 77 /** Cache of the ids. Comes from inflating mIds at first use. */ 78 @Nullable private Set<String> mIdsCached; 79 80 @Constructor GetByDocumentIdRequest( @aramid = 1) @onNull String namespace, @Param(id = 2) @NonNull List<String> ids, @Param(id = 3) @NonNull Bundle typePropertyPaths)81 GetByDocumentIdRequest( 82 @Param(id = 1) @NonNull String namespace, 83 @Param(id = 2) @NonNull List<String> ids, 84 @Param(id = 3) @NonNull Bundle typePropertyPaths) { 85 mNamespace = Objects.requireNonNull(namespace); 86 mIds = Objects.requireNonNull(ids); 87 mTypePropertyPaths = Objects.requireNonNull(typePropertyPaths); 88 } 89 90 /** Returns the namespace attached to the request. */ 91 @NonNull getNamespace()92 public String getNamespace() { 93 return mNamespace; 94 } 95 96 /** Returns the set of document IDs attached to the request. */ 97 @NonNull getIds()98 public Set<String> getIds() { 99 if (mIdsCached == null) { 100 mIdsCached = Collections.unmodifiableSet(new ArraySet<>(mIds)); 101 } 102 return mIdsCached; 103 } 104 105 /** 106 * Returns a map from schema type to property paths to be used for projection. 107 * 108 * <p>If the map is empty, then all properties will be retrieved for all results. 109 * 110 * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this 111 * function, rather than calling it multiple times. 112 */ 113 @NonNull getProjections()114 public Map<String, List<String>> getProjections() { 115 Set<String> schemas = mTypePropertyPaths.keySet(); 116 Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size()); 117 for (String schema : schemas) { 118 List<String> propertyPaths = mTypePropertyPaths.getStringArrayList(schema); 119 if (propertyPaths != null) { 120 typePropertyPathsMap.put(schema, Collections.unmodifiableList(propertyPaths)); 121 } 122 } 123 return typePropertyPathsMap; 124 } 125 126 /** 127 * Returns a map from schema type to property paths to be used for projection. 128 * 129 * <p>If the map is empty, then all properties will be retrieved for all results. 130 * 131 * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this 132 * function, rather than calling it multiple times. 133 */ 134 @NonNull getProjectionPaths()135 public Map<String, List<PropertyPath>> getProjectionPaths() { 136 Set<String> schemas = mTypePropertyPaths.keySet(); 137 Map<String, List<PropertyPath>> typePropertyPathsMap = new ArrayMap<>(schemas.size()); 138 for (String schema : schemas) { 139 List<String> paths = mTypePropertyPaths.getStringArrayList(schema); 140 if (paths != null) { 141 int pathsSize = paths.size(); 142 List<PropertyPath> propertyPathList = new ArrayList<>(pathsSize); 143 for (int i = 0; i < pathsSize; i++) { 144 propertyPathList.add(new PropertyPath(paths.get(i))); 145 } 146 typePropertyPathsMap.put(schema, Collections.unmodifiableList(propertyPathList)); 147 } 148 } 149 return typePropertyPathsMap; 150 } 151 152 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 153 @Override writeToParcel(@onNull Parcel dest, int flags)154 public void writeToParcel(@NonNull Parcel dest, int flags) { 155 GetByDocumentIdRequestCreator.writeToParcel(this, dest, flags); 156 } 157 158 /** Builder for {@link GetByDocumentIdRequest} objects. */ 159 public static final class Builder { 160 private final String mNamespace; 161 private List<String> mIds = new ArrayList<>(); 162 private Bundle mProjectionTypePropertyPaths = new Bundle(); 163 private boolean mBuilt = false; 164 165 /** Creates a {@link GetByDocumentIdRequest.Builder} instance. */ Builder(@onNull String namespace)166 public Builder(@NonNull String namespace) { 167 mNamespace = Objects.requireNonNull(namespace); 168 } 169 170 /** Adds one or more document IDs to the request. */ 171 @CanIgnoreReturnValue 172 @NonNull addIds(@onNull String... ids)173 public Builder addIds(@NonNull String... ids) { 174 Objects.requireNonNull(ids); 175 resetIfBuilt(); 176 return addIds(Arrays.asList(ids)); 177 } 178 179 /** Adds a collection of IDs to the request. */ 180 @CanIgnoreReturnValue 181 @NonNull addIds(@onNull Collection<String> ids)182 public Builder addIds(@NonNull Collection<String> ids) { 183 Objects.requireNonNull(ids); 184 resetIfBuilt(); 185 mIds.addAll(ids); 186 return this; 187 } 188 189 /** 190 * Adds property paths for the specified type to be used for projection. If property paths 191 * are added for a type, then only the properties referred to will be retrieved for results 192 * of that type. If a property path that is specified isn't present in a result, it will be 193 * ignored for that result. Property paths cannot be null. 194 * 195 * <p>If no property paths are added for a particular type, then all properties of results 196 * of that type will be retrieved. 197 * 198 * <p>If property path is added for the {@link 199 * GetByDocumentIdRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will 200 * apply to all results, excepting any types that have their own, specific property paths 201 * set. 202 * 203 * @see SearchSpec.Builder#addProjectionPaths 204 */ 205 @CanIgnoreReturnValue 206 @NonNull addProjection( @onNull String schemaType, @NonNull Collection<String> propertyPaths)207 public Builder addProjection( 208 @NonNull String schemaType, @NonNull Collection<String> propertyPaths) { 209 Objects.requireNonNull(schemaType); 210 Objects.requireNonNull(propertyPaths); 211 resetIfBuilt(); 212 ArrayList<String> propertyPathsList = new ArrayList<>(propertyPaths.size()); 213 for (String propertyPath : propertyPaths) { 214 Objects.requireNonNull(propertyPath); 215 propertyPathsList.add(propertyPath); 216 } 217 mProjectionTypePropertyPaths.putStringArrayList(schemaType, propertyPathsList); 218 return this; 219 } 220 221 /** 222 * Adds property paths for the specified type to be used for projection. If property paths 223 * are added for a type, then only the properties referred to will be retrieved for results 224 * of that type. If a property path that is specified isn't present in a result, it will be 225 * ignored for that result. Property paths cannot be null. 226 * 227 * <p>If no property paths are added for a particular type, then all properties of results 228 * of that type will be retrieved. 229 * 230 * <p>If property path is added for the {@link 231 * GetByDocumentIdRequest#PROJECTION_SCHEMA_TYPE_WILDCARD}, then those property paths will 232 * apply to all results, excepting any types that have their own, specific property paths 233 * set. 234 * 235 * @see SearchSpec.Builder#addProjectionPaths 236 */ 237 @CanIgnoreReturnValue 238 @NonNull addProjectionPaths( @onNull String schemaType, @NonNull Collection<PropertyPath> propertyPaths)239 public Builder addProjectionPaths( 240 @NonNull String schemaType, @NonNull Collection<PropertyPath> propertyPaths) { 241 Objects.requireNonNull(schemaType); 242 Objects.requireNonNull(propertyPaths); 243 List<String> propertyPathsList = new ArrayList<>(propertyPaths.size()); 244 for (PropertyPath propertyPath : propertyPaths) { 245 propertyPathsList.add(propertyPath.toString()); 246 } 247 return addProjection(schemaType, propertyPathsList); 248 } 249 250 /** Builds a new {@link GetByDocumentIdRequest}. */ 251 @NonNull build()252 public GetByDocumentIdRequest build() { 253 mBuilt = true; 254 return new GetByDocumentIdRequest(mNamespace, mIds, mProjectionTypePropertyPaths); 255 } 256 resetIfBuilt()257 private void resetIfBuilt() { 258 if (mBuilt) { 259 mIds = new ArrayList<>(mIds); 260 // No need to clone each propertyPathsList inside mProjectionTypePropertyPaths since 261 // the builder only replaces it, never adds to it. So even if the builder is used 262 // again, the previous one will remain with the object. 263 mProjectionTypePropertyPaths = BundleUtil.deepCopy(mProjectionTypePropertyPaths); 264 mBuilt = false; 265 } 266 } 267 } 268 } 269