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.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.appsearch.exceptions.IllegalSchemaException; 23 import android.app.appsearch.util.BundleUtil; 24 import android.app.appsearch.util.IndentingStringBuilder; 25 import android.os.Bundle; 26 import android.util.ArraySet; 27 28 import com.android.internal.util.Preconditions; 29 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.Collections; 35 import java.util.List; 36 import java.util.Objects; 37 import java.util.Set; 38 39 /** 40 * The AppSearch Schema for a particular type of document. 41 * 42 * <p>For example, an e-mail message or a music recording could be a schema type. 43 * 44 * <p>The schema consists of type information, properties, and config (like tokenization type). 45 * 46 * @see AppSearchSession#setSchema 47 */ 48 public final class AppSearchSchema { 49 private static final String SCHEMA_TYPE_FIELD = "schemaType"; 50 private static final String PROPERTIES_FIELD = "properties"; 51 52 private final Bundle mBundle; 53 54 /** @hide */ AppSearchSchema(@onNull Bundle bundle)55 public AppSearchSchema(@NonNull Bundle bundle) { 56 Objects.requireNonNull(bundle); 57 mBundle = bundle; 58 } 59 60 /** 61 * Returns the {@link Bundle} populated by this builder. 62 * 63 * @hide 64 */ 65 @NonNull getBundle()66 public Bundle getBundle() { 67 return mBundle; 68 } 69 70 @Override 71 @NonNull toString()72 public String toString() { 73 IndentingStringBuilder stringBuilder = new IndentingStringBuilder(); 74 appendAppSearchSchemaString(stringBuilder); 75 return stringBuilder.toString(); 76 } 77 78 /** 79 * Appends a debugging string for the {@link AppSearchSchema} instance to the given string 80 * builder. 81 * 82 * @param builder the builder to append to. 83 */ appendAppSearchSchemaString(@onNull IndentingStringBuilder builder)84 private void appendAppSearchSchemaString(@NonNull IndentingStringBuilder builder) { 85 Objects.requireNonNull(builder); 86 87 builder.append("{\n"); 88 builder.increaseIndentLevel(); 89 builder.append("schemaType: \"").append(getSchemaType()).append("\",\n"); 90 builder.append("properties: [\n"); 91 92 AppSearchSchema.PropertyConfig[] sortedProperties = 93 getProperties().toArray(new AppSearchSchema.PropertyConfig[0]); 94 Arrays.sort(sortedProperties, (o1, o2) -> o1.getName().compareTo(o2.getName())); 95 96 for (int i = 0; i < sortedProperties.length; i++) { 97 AppSearchSchema.PropertyConfig propertyConfig = sortedProperties[i]; 98 builder.increaseIndentLevel(); 99 propertyConfig.appendPropertyConfigString(builder); 100 if (i != sortedProperties.length - 1) { 101 builder.append(",\n"); 102 } 103 builder.decreaseIndentLevel(); 104 } 105 106 builder.append("\n"); 107 builder.append("]\n"); 108 builder.decreaseIndentLevel(); 109 builder.append("}"); 110 } 111 112 /** Returns the name of this schema type, e.g. Email. */ 113 @NonNull getSchemaType()114 public String getSchemaType() { 115 return mBundle.getString(SCHEMA_TYPE_FIELD, ""); 116 } 117 118 /** 119 * Returns the list of {@link PropertyConfig}s that are part of this schema. 120 * 121 * <p>This method creates a new list when called. 122 */ 123 @NonNull 124 @SuppressWarnings("MixedMutabilityReturnType") getProperties()125 public List<PropertyConfig> getProperties() { 126 ArrayList<Bundle> propertyBundles = 127 mBundle.getParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD); 128 if (propertyBundles.isEmpty()) { 129 return Collections.emptyList(); 130 } 131 List<PropertyConfig> ret = new ArrayList<>(propertyBundles.size()); 132 for (int i = 0; i < propertyBundles.size(); i++) { 133 ret.add(PropertyConfig.fromBundle(propertyBundles.get(i))); 134 } 135 return ret; 136 } 137 138 @Override equals(@ullable Object other)139 public boolean equals(@Nullable Object other) { 140 if (this == other) { 141 return true; 142 } 143 if (!(other instanceof AppSearchSchema)) { 144 return false; 145 } 146 AppSearchSchema otherSchema = (AppSearchSchema) other; 147 if (!getSchemaType().equals(otherSchema.getSchemaType())) { 148 return false; 149 } 150 return getProperties().equals(otherSchema.getProperties()); 151 } 152 153 @Override hashCode()154 public int hashCode() { 155 return Objects.hash(getSchemaType(), getProperties()); 156 } 157 158 /** Builder for {@link AppSearchSchema objects}. */ 159 public static final class Builder { 160 private final String mSchemaType; 161 private ArrayList<Bundle> mPropertyBundles = new ArrayList<>(); 162 private final Set<String> mPropertyNames = new ArraySet<>(); 163 private boolean mBuilt = false; 164 165 /** Creates a new {@link AppSearchSchema.Builder}. */ Builder(@onNull String schemaType)166 public Builder(@NonNull String schemaType) { 167 Objects.requireNonNull(schemaType); 168 mSchemaType = schemaType; 169 } 170 171 /** Adds a property to the given type. */ 172 @NonNull addProperty(@onNull PropertyConfig propertyConfig)173 public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) { 174 Objects.requireNonNull(propertyConfig); 175 resetIfBuilt(); 176 String name = propertyConfig.getName(); 177 if (!mPropertyNames.add(name)) { 178 throw new IllegalSchemaException("Property defined more than once: " + name); 179 } 180 mPropertyBundles.add(propertyConfig.mBundle); 181 return this; 182 } 183 184 /** Constructs a new {@link AppSearchSchema} from the contents of this builder. */ 185 @NonNull build()186 public AppSearchSchema build() { 187 Bundle bundle = new Bundle(); 188 bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType); 189 bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles); 190 mBuilt = true; 191 return new AppSearchSchema(bundle); 192 } 193 resetIfBuilt()194 private void resetIfBuilt() { 195 if (mBuilt) { 196 mPropertyBundles = new ArrayList<>(mPropertyBundles); 197 mBuilt = false; 198 } 199 } 200 } 201 202 /** 203 * Common configuration for a single property (field) in a Document. 204 * 205 * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be a 206 * property. 207 */ 208 public abstract static class PropertyConfig { 209 static final String NAME_FIELD = "name"; 210 static final String DATA_TYPE_FIELD = "dataType"; 211 static final String CARDINALITY_FIELD = "cardinality"; 212 213 /** 214 * Physical data-types of the contents of the property. 215 * 216 * @hide 217 */ 218 // NOTE: The integer values of these constants must match the proto enum constants in 219 // com.google.android.icing.proto.PropertyConfigProto.DataType.Code. 220 @IntDef( 221 value = { 222 DATA_TYPE_STRING, 223 DATA_TYPE_LONG, 224 DATA_TYPE_DOUBLE, 225 DATA_TYPE_BOOLEAN, 226 DATA_TYPE_BYTES, 227 DATA_TYPE_DOCUMENT, 228 }) 229 @Retention(RetentionPolicy.SOURCE) 230 public @interface DataType {} 231 232 /** @hide */ 233 public static final int DATA_TYPE_STRING = 1; 234 235 /** @hide */ 236 public static final int DATA_TYPE_LONG = 2; 237 238 /** @hide */ 239 public static final int DATA_TYPE_DOUBLE = 3; 240 241 /** @hide */ 242 public static final int DATA_TYPE_BOOLEAN = 4; 243 244 /** 245 * Unstructured BLOB. 246 * 247 * @hide 248 */ 249 public static final int DATA_TYPE_BYTES = 5; 250 251 /** 252 * Indicates that the property is itself a {@link GenericDocument}, making it part of a 253 * hierarchical schema. Any property using this DataType MUST have a valid {@link 254 * PropertyConfig#getSchemaType}. 255 * 256 * @hide 257 */ 258 public static final int DATA_TYPE_DOCUMENT = 6; 259 260 /** 261 * The cardinality of the property (whether it is required, optional or repeated). 262 * 263 * @hide 264 */ 265 // NOTE: The integer values of these constants must match the proto enum constants in 266 // com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code. 267 @IntDef( 268 value = { 269 CARDINALITY_REPEATED, 270 CARDINALITY_OPTIONAL, 271 CARDINALITY_REQUIRED, 272 }) 273 @Retention(RetentionPolicy.SOURCE) 274 public @interface Cardinality {} 275 276 /** Any number of items (including zero) [0...*]. */ 277 public static final int CARDINALITY_REPEATED = 1; 278 279 /** Zero or one value [0,1]. */ 280 public static final int CARDINALITY_OPTIONAL = 2; 281 282 /** Exactly one value [1]. */ 283 public static final int CARDINALITY_REQUIRED = 3; 284 285 final Bundle mBundle; 286 287 @Nullable private Integer mHashCode; 288 PropertyConfig(@onNull Bundle bundle)289 PropertyConfig(@NonNull Bundle bundle) { 290 mBundle = Objects.requireNonNull(bundle); 291 } 292 293 @Override 294 @NonNull toString()295 public String toString() { 296 IndentingStringBuilder stringBuilder = new IndentingStringBuilder(); 297 appendPropertyConfigString(stringBuilder); 298 return stringBuilder.toString(); 299 } 300 301 /** 302 * Appends a debug string for the {@link AppSearchSchema.PropertyConfig} instance to the 303 * given string builder. 304 * 305 * @param builder the builder to append to. 306 */ appendPropertyConfigString(@onNull IndentingStringBuilder builder)307 void appendPropertyConfigString(@NonNull IndentingStringBuilder builder) { 308 Objects.requireNonNull(builder); 309 310 builder.append("{\n"); 311 builder.increaseIndentLevel(); 312 builder.append("name: \"").append(getName()).append("\",\n"); 313 314 if (this instanceof AppSearchSchema.StringPropertyConfig) { 315 ((StringPropertyConfig) this).appendStringPropertyConfigFields(builder); 316 } else if (this instanceof AppSearchSchema.DocumentPropertyConfig) { 317 ((DocumentPropertyConfig) this).appendDocumentPropertyConfigFields(builder); 318 } 319 320 switch (getCardinality()) { 321 case AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED: 322 builder.append("cardinality: CARDINALITY_REPEATED,\n"); 323 break; 324 case AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL: 325 builder.append("cardinality: CARDINALITY_OPTIONAL,\n"); 326 break; 327 case AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED: 328 builder.append("cardinality: CARDINALITY_REQUIRED,\n"); 329 break; 330 default: 331 builder.append("cardinality: CARDINALITY_UNKNOWN,\n"); 332 } 333 334 switch (getDataType()) { 335 case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING: 336 builder.append("dataType: DATA_TYPE_STRING,\n"); 337 break; 338 case AppSearchSchema.PropertyConfig.DATA_TYPE_LONG: 339 builder.append("dataType: DATA_TYPE_LONG,\n"); 340 break; 341 case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE: 342 builder.append("dataType: DATA_TYPE_DOUBLE,\n"); 343 break; 344 case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN: 345 builder.append("dataType: DATA_TYPE_BOOLEAN,\n"); 346 break; 347 case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES: 348 builder.append("dataType: DATA_TYPE_BYTES,\n"); 349 break; 350 case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT: 351 builder.append("dataType: DATA_TYPE_DOCUMENT,\n"); 352 break; 353 default: 354 builder.append("dataType: DATA_TYPE_UNKNOWN,\n"); 355 } 356 builder.decreaseIndentLevel(); 357 builder.append("}"); 358 } 359 360 /** Returns the name of this property. */ 361 @NonNull getName()362 public String getName() { 363 return mBundle.getString(NAME_FIELD, ""); 364 } 365 366 /** 367 * Returns the type of data the property contains (e.g. string, int, bytes, etc). 368 * 369 * @hide 370 */ getDataType()371 public @DataType int getDataType() { 372 return mBundle.getInt(DATA_TYPE_FIELD, -1); 373 } 374 375 /** 376 * Returns the cardinality of the property (whether it is optional, required or repeated). 377 */ getCardinality()378 public @Cardinality int getCardinality() { 379 return mBundle.getInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL); 380 } 381 382 @Override equals(@ullable Object other)383 public boolean equals(@Nullable Object other) { 384 if (this == other) { 385 return true; 386 } 387 if (!(other instanceof PropertyConfig)) { 388 return false; 389 } 390 PropertyConfig otherProperty = (PropertyConfig) other; 391 return BundleUtil.deepEquals(this.mBundle, otherProperty.mBundle); 392 } 393 394 @Override hashCode()395 public int hashCode() { 396 if (mHashCode == null) { 397 mHashCode = BundleUtil.deepHashCode(mBundle); 398 } 399 return mHashCode; 400 } 401 402 /** 403 * Converts a {@link Bundle} into a {@link PropertyConfig} depending on its internal data 404 * type. 405 * 406 * <p>The bundle is not cloned. 407 * 408 * @throws IllegalArgumentException if the bundle does no contain a recognized value in its 409 * {@code DATA_TYPE_FIELD}. 410 * @hide 411 */ 412 @NonNull fromBundle(@onNull Bundle propertyBundle)413 public static PropertyConfig fromBundle(@NonNull Bundle propertyBundle) { 414 switch (propertyBundle.getInt(PropertyConfig.DATA_TYPE_FIELD)) { 415 case PropertyConfig.DATA_TYPE_STRING: 416 return new StringPropertyConfig(propertyBundle); 417 case PropertyConfig.DATA_TYPE_LONG: 418 return new LongPropertyConfig(propertyBundle); 419 case PropertyConfig.DATA_TYPE_DOUBLE: 420 return new DoublePropertyConfig(propertyBundle); 421 case PropertyConfig.DATA_TYPE_BOOLEAN: 422 return new BooleanPropertyConfig(propertyBundle); 423 case PropertyConfig.DATA_TYPE_BYTES: 424 return new BytesPropertyConfig(propertyBundle); 425 case PropertyConfig.DATA_TYPE_DOCUMENT: 426 return new DocumentPropertyConfig(propertyBundle); 427 default: 428 throw new IllegalArgumentException( 429 "Unsupported property bundle of type " 430 + propertyBundle.getInt(PropertyConfig.DATA_TYPE_FIELD) 431 + "; contents: " 432 + propertyBundle); 433 } 434 } 435 } 436 437 /** Configuration for a property of type String in a Document. */ 438 public static final class StringPropertyConfig extends PropertyConfig { 439 private static final String INDEXING_TYPE_FIELD = "indexingType"; 440 private static final String TOKENIZER_TYPE_FIELD = "tokenizerType"; 441 442 /** 443 * Encapsulates the configurations on how AppSearch should query/index these terms. 444 * 445 * @hide 446 */ 447 @IntDef( 448 value = { 449 INDEXING_TYPE_NONE, 450 INDEXING_TYPE_EXACT_TERMS, 451 INDEXING_TYPE_PREFIXES, 452 }) 453 @Retention(RetentionPolicy.SOURCE) 454 public @interface IndexingType {} 455 456 /** Content in this property will not be tokenized or indexed. */ 457 public static final int INDEXING_TYPE_NONE = 0; 458 459 /** 460 * Content in this property should only be returned for queries matching the exact tokens 461 * appearing in this property. 462 * 463 * <p>Ex. A property with "fool" should NOT match a query for "foo". 464 */ 465 public static final int INDEXING_TYPE_EXACT_TERMS = 1; 466 467 /** 468 * Content in this property should be returned for queries that are either exact matches or 469 * query matches of the tokens appearing in this property. 470 * 471 * <p>Ex. A property with "fool" <b>should</b> match a query for "foo". 472 */ 473 public static final int INDEXING_TYPE_PREFIXES = 2; 474 475 /** 476 * Configures how tokens should be extracted from this property. 477 * 478 * @hide 479 */ 480 // NOTE: The integer values of these constants must match the proto enum constants in 481 // com.google.android.icing.proto.IndexingConfig.TokenizerType.Code. 482 @IntDef( 483 value = { 484 TOKENIZER_TYPE_NONE, 485 TOKENIZER_TYPE_PLAIN, 486 }) 487 @Retention(RetentionPolicy.SOURCE) 488 public @interface TokenizerType {} 489 490 /** 491 * This value indicates that no tokens should be extracted from this property. 492 * 493 * <p>It is only valid for tokenizer_type to be 'NONE' if {@link #getIndexingType} is {@link 494 * #INDEXING_TYPE_NONE}. 495 */ 496 public static final int TOKENIZER_TYPE_NONE = 0; 497 498 /** 499 * Tokenization for plain text. This value indicates that tokens should be extracted from 500 * this property based on word breaks. Segments of whitespace and punctuation are not 501 * considered tokens. 502 * 503 * <p>Ex. A property with "foo bar. baz." will produce tokens for "foo", "bar" and "baz". 504 * The segments " " and "." will not be considered tokens. 505 * 506 * <p>It is only valid for tokenizer_type to be 'PLAIN' if {@link #getIndexingType} is 507 * {@link #INDEXING_TYPE_EXACT_TERMS} or {@link #INDEXING_TYPE_PREFIXES}. 508 */ 509 public static final int TOKENIZER_TYPE_PLAIN = 1; 510 StringPropertyConfig(@onNull Bundle bundle)511 StringPropertyConfig(@NonNull Bundle bundle) { 512 super(bundle); 513 } 514 515 /** Returns how the property is indexed. */ getIndexingType()516 public @IndexingType int getIndexingType() { 517 return mBundle.getInt(INDEXING_TYPE_FIELD); 518 } 519 520 /** Returns how this property is tokenized (split into words). */ getTokenizerType()521 public @TokenizerType int getTokenizerType() { 522 return mBundle.getInt(TOKENIZER_TYPE_FIELD); 523 } 524 525 /** Builder for {@link StringPropertyConfig}. */ 526 public static final class Builder { 527 private final String mPropertyName; 528 private @Cardinality int mCardinality = CARDINALITY_OPTIONAL; 529 private @IndexingType int mIndexingType = INDEXING_TYPE_NONE; 530 private @TokenizerType int mTokenizerType = TOKENIZER_TYPE_NONE; 531 532 /** Creates a new {@link StringPropertyConfig.Builder}. */ Builder(@onNull String propertyName)533 public Builder(@NonNull String propertyName) { 534 mPropertyName = Objects.requireNonNull(propertyName); 535 } 536 537 /** 538 * The cardinality of the property (whether it is optional, required or repeated). 539 * 540 * <p>If this method is not called, the default cardinality is {@link 541 * PropertyConfig#CARDINALITY_OPTIONAL}. 542 */ 543 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 544 @NonNull setCardinality(@ardinality int cardinality)545 public StringPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 546 Preconditions.checkArgumentInRange( 547 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 548 mCardinality = cardinality; 549 return this; 550 } 551 552 /** 553 * Configures how a property should be indexed so that it can be retrieved by queries. 554 * 555 * <p>If this method is not called, the default indexing type is {@link 556 * StringPropertyConfig#INDEXING_TYPE_NONE}, so that it cannot be matched by queries. 557 */ 558 @NonNull setIndexingType(@ndexingType int indexingType)559 public StringPropertyConfig.Builder setIndexingType(@IndexingType int indexingType) { 560 Preconditions.checkArgumentInRange( 561 indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType"); 562 mIndexingType = indexingType; 563 return this; 564 } 565 566 /** 567 * Configures how this property should be tokenized (split into words). 568 * 569 * <p>If this method is not called, the default indexing type is {@link 570 * StringPropertyConfig#TOKENIZER_TYPE_NONE}, so that it is not tokenized. 571 * 572 * <p>This method must be called with a value other than {@link 573 * StringPropertyConfig#TOKENIZER_TYPE_NONE} if the property is indexed (i.e. if {@link 574 * #setIndexingType} has been called with a value other than {@link 575 * StringPropertyConfig#INDEXING_TYPE_NONE}). 576 */ 577 @NonNull setTokenizerType(@okenizerType int tokenizerType)578 public StringPropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) { 579 Preconditions.checkArgumentInRange( 580 tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_PLAIN, "tokenizerType"); 581 mTokenizerType = tokenizerType; 582 return this; 583 } 584 585 /** Constructs a new {@link StringPropertyConfig} from the contents of this builder. */ 586 @NonNull build()587 public StringPropertyConfig build() { 588 if (mTokenizerType == TOKENIZER_TYPE_NONE) { 589 Preconditions.checkState( 590 mIndexingType == INDEXING_TYPE_NONE, 591 "Cannot set " 592 + "TOKENIZER_TYPE_NONE with an indexing type other than " 593 + "INDEXING_TYPE_NONE."); 594 } else { 595 Preconditions.checkState( 596 mIndexingType != INDEXING_TYPE_NONE, 597 "Cannot set " + "TOKENIZER_TYPE_PLAIN with INDEXING_TYPE_NONE."); 598 } 599 Bundle bundle = new Bundle(); 600 bundle.putString(NAME_FIELD, mPropertyName); 601 bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_STRING); 602 bundle.putInt(CARDINALITY_FIELD, mCardinality); 603 bundle.putInt(INDEXING_TYPE_FIELD, mIndexingType); 604 bundle.putInt(TOKENIZER_TYPE_FIELD, mTokenizerType); 605 return new StringPropertyConfig(bundle); 606 } 607 } 608 609 /** 610 * Appends a debug string for the {@link StringPropertyConfig} instance to the given string 611 * builder. 612 * 613 * <p>This appends fields specific to a {@link StringPropertyConfig} instance. 614 * 615 * @param builder the builder to append to. 616 */ appendStringPropertyConfigFields(@onNull IndentingStringBuilder builder)617 void appendStringPropertyConfigFields(@NonNull IndentingStringBuilder builder) { 618 switch (getIndexingType()) { 619 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE: 620 builder.append("indexingType: INDEXING_TYPE_NONE,\n"); 621 break; 622 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS: 623 builder.append("indexingType: INDEXING_TYPE_EXACT_TERMS,\n"); 624 break; 625 case AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES: 626 builder.append("indexingType: INDEXING_TYPE_PREFIXES,\n"); 627 break; 628 default: 629 builder.append("indexingType: INDEXING_TYPE_UNKNOWN,\n"); 630 } 631 632 switch (getTokenizerType()) { 633 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE: 634 builder.append("tokenizerType: TOKENIZER_TYPE_NONE,\n"); 635 break; 636 case AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN: 637 builder.append("tokenizerType: TOKENIZER_TYPE_PLAIN,\n"); 638 break; 639 default: 640 builder.append("tokenizerType: TOKENIZER_TYPE_UNKNOWN,\n"); 641 } 642 } 643 } 644 645 /** Configuration for a property containing a 64-bit integer. */ 646 public static final class LongPropertyConfig extends PropertyConfig { LongPropertyConfig(@onNull Bundle bundle)647 LongPropertyConfig(@NonNull Bundle bundle) { 648 super(bundle); 649 } 650 651 /** Builder for {@link LongPropertyConfig}. */ 652 public static final class Builder { 653 private final String mPropertyName; 654 private @Cardinality int mCardinality = CARDINALITY_OPTIONAL; 655 656 /** Creates a new {@link LongPropertyConfig.Builder}. */ Builder(@onNull String propertyName)657 public Builder(@NonNull String propertyName) { 658 mPropertyName = Objects.requireNonNull(propertyName); 659 } 660 661 /** 662 * The cardinality of the property (whether it is optional, required or repeated). 663 * 664 * <p>If this method is not called, the default cardinality is {@link 665 * PropertyConfig#CARDINALITY_OPTIONAL}. 666 */ 667 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 668 @NonNull setCardinality(@ardinality int cardinality)669 public LongPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 670 Preconditions.checkArgumentInRange( 671 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 672 mCardinality = cardinality; 673 return this; 674 } 675 676 /** Constructs a new {@link LongPropertyConfig} from the contents of this builder. */ 677 @NonNull build()678 public LongPropertyConfig build() { 679 Bundle bundle = new Bundle(); 680 bundle.putString(NAME_FIELD, mPropertyName); 681 bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_LONG); 682 bundle.putInt(CARDINALITY_FIELD, mCardinality); 683 return new LongPropertyConfig(bundle); 684 } 685 } 686 } 687 688 /** Configuration for a property containing a double-precision decimal number. */ 689 public static final class DoublePropertyConfig extends PropertyConfig { DoublePropertyConfig(@onNull Bundle bundle)690 DoublePropertyConfig(@NonNull Bundle bundle) { 691 super(bundle); 692 } 693 694 /** Builder for {@link DoublePropertyConfig}. */ 695 public static final class Builder { 696 private final String mPropertyName; 697 private @Cardinality int mCardinality = CARDINALITY_OPTIONAL; 698 699 /** Creates a new {@link DoublePropertyConfig.Builder}. */ Builder(@onNull String propertyName)700 public Builder(@NonNull String propertyName) { 701 mPropertyName = Objects.requireNonNull(propertyName); 702 } 703 704 /** 705 * The cardinality of the property (whether it is optional, required or repeated). 706 * 707 * <p>If this method is not called, the default cardinality is {@link 708 * PropertyConfig#CARDINALITY_OPTIONAL}. 709 */ 710 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 711 @NonNull setCardinality(@ardinality int cardinality)712 public DoublePropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 713 Preconditions.checkArgumentInRange( 714 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 715 mCardinality = cardinality; 716 return this; 717 } 718 719 /** Constructs a new {@link DoublePropertyConfig} from the contents of this builder. */ 720 @NonNull build()721 public DoublePropertyConfig build() { 722 Bundle bundle = new Bundle(); 723 bundle.putString(NAME_FIELD, mPropertyName); 724 bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOUBLE); 725 bundle.putInt(CARDINALITY_FIELD, mCardinality); 726 return new DoublePropertyConfig(bundle); 727 } 728 } 729 } 730 731 /** Configuration for a property containing a boolean. */ 732 public static final class BooleanPropertyConfig extends PropertyConfig { BooleanPropertyConfig(@onNull Bundle bundle)733 BooleanPropertyConfig(@NonNull Bundle bundle) { 734 super(bundle); 735 } 736 737 /** Builder for {@link BooleanPropertyConfig}. */ 738 public static final class Builder { 739 private final String mPropertyName; 740 private @Cardinality int mCardinality = CARDINALITY_OPTIONAL; 741 742 /** Creates a new {@link BooleanPropertyConfig.Builder}. */ Builder(@onNull String propertyName)743 public Builder(@NonNull String propertyName) { 744 mPropertyName = Objects.requireNonNull(propertyName); 745 } 746 747 /** 748 * The cardinality of the property (whether it is optional, required or repeated). 749 * 750 * <p>If this method is not called, the default cardinality is {@link 751 * PropertyConfig#CARDINALITY_OPTIONAL}. 752 */ 753 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 754 @NonNull setCardinality(@ardinality int cardinality)755 public BooleanPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 756 Preconditions.checkArgumentInRange( 757 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 758 mCardinality = cardinality; 759 return this; 760 } 761 762 /** Constructs a new {@link BooleanPropertyConfig} from the contents of this builder. */ 763 @NonNull build()764 public BooleanPropertyConfig build() { 765 Bundle bundle = new Bundle(); 766 bundle.putString(NAME_FIELD, mPropertyName); 767 bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BOOLEAN); 768 bundle.putInt(CARDINALITY_FIELD, mCardinality); 769 return new BooleanPropertyConfig(bundle); 770 } 771 } 772 } 773 774 /** Configuration for a property containing a byte array. */ 775 public static final class BytesPropertyConfig extends PropertyConfig { BytesPropertyConfig(@onNull Bundle bundle)776 BytesPropertyConfig(@NonNull Bundle bundle) { 777 super(bundle); 778 } 779 780 /** Builder for {@link BytesPropertyConfig}. */ 781 public static final class Builder { 782 private final String mPropertyName; 783 private @Cardinality int mCardinality = CARDINALITY_OPTIONAL; 784 785 /** Creates a new {@link BytesPropertyConfig.Builder}. */ Builder(@onNull String propertyName)786 public Builder(@NonNull String propertyName) { 787 mPropertyName = Objects.requireNonNull(propertyName); 788 } 789 790 /** 791 * The cardinality of the property (whether it is optional, required or repeated). 792 * 793 * <p>If this method is not called, the default cardinality is {@link 794 * PropertyConfig#CARDINALITY_OPTIONAL}. 795 */ 796 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 797 @NonNull setCardinality(@ardinality int cardinality)798 public BytesPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 799 Preconditions.checkArgumentInRange( 800 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 801 mCardinality = cardinality; 802 return this; 803 } 804 805 /** Constructs a new {@link BytesPropertyConfig} from the contents of this builder. */ 806 @NonNull build()807 public BytesPropertyConfig build() { 808 Bundle bundle = new Bundle(); 809 bundle.putString(NAME_FIELD, mPropertyName); 810 bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_BYTES); 811 bundle.putInt(CARDINALITY_FIELD, mCardinality); 812 return new BytesPropertyConfig(bundle); 813 } 814 } 815 } 816 817 /** Configuration for a property containing another Document. */ 818 public static final class DocumentPropertyConfig extends PropertyConfig { 819 private static final String SCHEMA_TYPE_FIELD = "schemaType"; 820 private static final String INDEX_NESTED_PROPERTIES_FIELD = "indexNestedProperties"; 821 DocumentPropertyConfig(@onNull Bundle bundle)822 DocumentPropertyConfig(@NonNull Bundle bundle) { 823 super(bundle); 824 } 825 826 /** Returns the logical schema-type of the contents of this document property. */ 827 @NonNull getSchemaType()828 public String getSchemaType() { 829 return Objects.requireNonNull(mBundle.getString(SCHEMA_TYPE_FIELD)); 830 } 831 832 /** 833 * Returns whether fields in the nested document should be indexed according to that 834 * document's schema. 835 * 836 * <p>If false, the nested document's properties are not indexed regardless of its own 837 * schema. 838 */ shouldIndexNestedProperties()839 public boolean shouldIndexNestedProperties() { 840 return mBundle.getBoolean(INDEX_NESTED_PROPERTIES_FIELD); 841 } 842 843 /** Builder for {@link DocumentPropertyConfig}. */ 844 public static final class Builder { 845 private final String mPropertyName; 846 private final String mSchemaType; 847 private @Cardinality int mCardinality = CARDINALITY_OPTIONAL; 848 private boolean mShouldIndexNestedProperties = false; 849 850 /** 851 * Creates a new {@link DocumentPropertyConfig.Builder}. 852 * 853 * @param propertyName The logical name of the property in the schema, which will be 854 * used as the key for this property in {@link 855 * GenericDocument.Builder#setPropertyDocument}. 856 * @param schemaType The type of documents which will be stored in this property. 857 * Documents of different types cannot be mixed into a single property. 858 */ Builder(@onNull String propertyName, @NonNull String schemaType)859 public Builder(@NonNull String propertyName, @NonNull String schemaType) { 860 mPropertyName = Objects.requireNonNull(propertyName); 861 mSchemaType = Objects.requireNonNull(schemaType); 862 } 863 864 /** 865 * The cardinality of the property (whether it is optional, required or repeated). 866 * 867 * <p>If this method is not called, the default cardinality is {@link 868 * PropertyConfig#CARDINALITY_OPTIONAL}. 869 */ 870 @SuppressWarnings("MissingGetterMatchingBuilder") // getter defined in superclass 871 @NonNull setCardinality(@ardinality int cardinality)872 public DocumentPropertyConfig.Builder setCardinality(@Cardinality int cardinality) { 873 Preconditions.checkArgumentInRange( 874 cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality"); 875 mCardinality = cardinality; 876 return this; 877 } 878 879 /** 880 * Configures whether fields in the nested document should be indexed according to that 881 * document's schema. 882 * 883 * <p>If false, the nested document's properties are not indexed regardless of its own 884 * schema. 885 */ 886 @NonNull setShouldIndexNestedProperties( boolean indexNestedProperties)887 public DocumentPropertyConfig.Builder setShouldIndexNestedProperties( 888 boolean indexNestedProperties) { 889 mShouldIndexNestedProperties = indexNestedProperties; 890 return this; 891 } 892 893 /** Constructs a new {@link PropertyConfig} from the contents of this builder. */ 894 @NonNull build()895 public DocumentPropertyConfig build() { 896 Bundle bundle = new Bundle(); 897 bundle.putString(NAME_FIELD, mPropertyName); 898 bundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOCUMENT); 899 bundle.putInt(CARDINALITY_FIELD, mCardinality); 900 bundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, mShouldIndexNestedProperties); 901 bundle.putString(SCHEMA_TYPE_FIELD, mSchemaType); 902 return new DocumentPropertyConfig(bundle); 903 } 904 } 905 906 /** 907 * Appends a debug string for the {@link DocumentPropertyConfig} instance to the given 908 * string builder. 909 * 910 * <p>This appends fields specific to a {@link DocumentPropertyConfig} instance. 911 * 912 * @param builder the builder to append to. 913 */ appendDocumentPropertyConfigFields(@onNull IndentingStringBuilder builder)914 void appendDocumentPropertyConfigFields(@NonNull IndentingStringBuilder builder) { 915 builder.append("shouldIndexNestedProperties: ") 916 .append(shouldIndexNestedProperties()) 917 .append(",\n"); 918 919 builder.append("schemaType: \"").append(getSchemaType()).append("\",\n"); 920 } 921 } 922 } 923