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 androidx.appsearch.compiler; 18 19 import static androidx.appsearch.compiler.AppSearchCompiler.OUTPUT_DIR_OPTION; 20 import static androidx.appsearch.compiler.AppSearchCompiler.RESTRICT_GENERATED_CODE_TO_LIB_OPTION; 21 22 import static com.google.testing.compile.CompilationSubject.assertThat; 23 24 import androidx.room.compiler.processing.util.Source; 25 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments; 26 import androidx.room.compiler.processing.util.compiler.TestKotlinCompilerKt; 27 28 import com.google.auto.value.processor.AutoValueProcessor; 29 import com.google.common.collect.ImmutableList; 30 import com.google.common.collect.ImmutableMap; 31 import com.google.common.io.CharStreams; 32 import com.google.common.io.Files; 33 import com.google.common.truth.Truth; 34 import com.google.testing.compile.Compilation; 35 import com.google.testing.compile.Compiler; 36 import com.google.testing.compile.JavaFileObjects; 37 38 import org.junit.Before; 39 import org.junit.Rule; 40 import org.junit.Test; 41 import org.junit.rules.TemporaryFolder; 42 import org.junit.rules.TestName; 43 44 import java.io.File; 45 import java.io.IOException; 46 import java.io.InputStream; 47 import java.io.InputStreamReader; 48 import java.nio.charset.StandardCharsets; 49 import java.util.logging.Logger; 50 51 import javax.tools.JavaFileObject; 52 53 public class AppSearchCompilerTest { 54 private static final Logger LOG = Logger.getLogger(AppSearchCompilerTest.class.getSimpleName()); 55 56 @Rule 57 public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); 58 @Rule 59 public TestName mTestName = new TestName(); 60 61 private File mGenFilesDir; 62 63 @Before setUp()64 public void setUp() throws IOException { 65 mGenFilesDir = mTemporaryFolder.newFolder("genFilesDir"); 66 } 67 68 @Test testPrivate()69 public void testPrivate() { 70 Compilation compilation = compile( 71 /* classSimpleName= */"Wrapper", 72 "public class Wrapper {\n" 73 + "@Document\n" 74 + "private class Gift {}\n" 75 + "} // Wrapper\n", 76 /* restrictGeneratedCodeToLibrary= */false 77 ); 78 79 assertThat(compilation).hadErrorContaining("annotated class is private"); 80 } 81 82 @Test testNoId()83 public void testNoId() { 84 Compilation compilation = compile( 85 "@Document\n" 86 + "public class Gift {\n" 87 + " @Document.Namespace String namespace;\n" 88 + "}\n"); 89 90 assertThat(compilation).hadErrorContaining( 91 "must have exactly one field annotated with @Id"); 92 } 93 94 @Test testManyIds()95 public void testManyIds() { 96 Compilation compilation = compile( 97 "@Document\n" 98 + "public class Gift {\n" 99 + " @Document.Namespace String namespace;\n" 100 + " @Document.Id String id1;\n" 101 + " @Document.Id String id2;\n" 102 + "}\n"); 103 104 assertThat(compilation).hadErrorContaining( 105 "Duplicate member annotated with @Id"); 106 } 107 108 @Test testAutoValueInheritance()109 public void testAutoValueInheritance() { 110 Compilation docExtendsAutoValueDoc = compile( 111 "import com.google.auto.value.AutoValue;\n" 112 + "import com.google.auto.value.AutoValue.*;\n" 113 + "@AutoValue\n" 114 + "@Document\n" 115 + "public abstract class Gift {\n" 116 + " @CopyAnnotations @Document.Id abstract String id();\n" 117 + " @CopyAnnotations @Document.Namespace abstract String namespace();\n" 118 + " public static Gift create(String id, String namespace) {\n" 119 + " return new AutoValue_Gift(id,namespace);\n" 120 + " }\n" 121 + " @Document\n" 122 + " static abstract class CoolGift extends Gift {\n" 123 + " @Document.BooleanProperty boolean cool;\n" 124 + " CoolGift(String id, String namespace, boolean cool) {\n" 125 + " super(id, message);\n" 126 + " this.cool = cool;\n" 127 + " }\n" 128 + " }\n" 129 + "}\n"); 130 assertThat(docExtendsAutoValueDoc).hadErrorContaining( 131 "A class annotated with Document cannot inherit from a class annotated with " 132 + "AutoValue"); 133 134 Compilation autoValueDocExtendsDoc = compile( 135 "import com.google.auto.value.AutoValue;\n" 136 + "import com.google.auto.value.AutoValue.*;\n" 137 + "@Document\n" 138 + "public class Gift {\n" 139 + " @Document.Id String id;\n" 140 + " @Document.Namespace String namespace;\n" 141 + " @AutoValue\n" 142 + " @Document\n" 143 + " static abstract class CoolGift extends Gift {\n" 144 + " @CopyAnnotations @Document.BooleanProperty abstract boolean cool()" 145 + ";\n" 146 + " public static CoolGift create(String id, String namespace, boolean" 147 + " cool) {\n" 148 + " return new AutoValue_Gift_CoolGift(cool);\n" 149 + " }\n" 150 + " }\n" 151 + "}\n"); 152 assertThat(autoValueDocExtendsDoc).hadErrorContaining( 153 "A class annotated with AutoValue and Document cannot have a superclass"); 154 } 155 156 @Test testSuperClassErrors()157 public void testSuperClassErrors() { 158 Compilation specialFieldReassigned = compile( 159 "@Document\n" 160 + "public class Gift {\n" 161 + " @Document.Namespace String namespace;\n" 162 + " @Document.Id String id;\n" 163 + " @Document.StringProperty String prop;\n" 164 + " Gift(String id, String namespace, String prop) {\n" 165 + " this.id = id;\n" 166 + " this.namespace = namespace;\n" 167 + " this.prop = prop;\n" 168 + " }\n" 169 + "}\n" 170 + "@Document\n" 171 + "class CoolGift extends Gift {\n" 172 + " @Document.StringProperty String id;\n" 173 + " CoolGift(String id, String namespace) {\n" 174 + " super(id, namespace, \"\");\n" 175 + " this.id = id;\n" 176 + " }\n" 177 + "}\n"); 178 assertThat(specialFieldReassigned).hadErrorContaining( 179 "Property type must stay consistent when overriding annotated " 180 + "members but changed from @Id -> @StringProperty"); 181 182 //error on collision 183 Compilation idCollision = compile( 184 "@Document\n" 185 + "public class Gift {\n" 186 + " @Document.Namespace String namespace;\n" 187 + " @Document.Id String id;\n" 188 + " Gift(String id, String namespace) {\n" 189 + " this.id = id;\n" 190 + " this.namespace = namespace;\n" 191 + " }\n" 192 + "}\n" 193 + "@Document\n" 194 + "class CoolGift extends Gift {\n" 195 + " @Document.BooleanProperty private final boolean cool;\n" 196 + " @Document.Id String badId;\n" 197 + " CoolGift(String id, String namespace, String badId) {\n" 198 + " super(id, namespace);\n" 199 + " this.badId = badId;\n" 200 + " }\n" 201 + " public boolean getBadId() { return badId; }\n" 202 + "}\n"); 203 assertThat(idCollision).hadErrorContaining( 204 "Duplicate member annotated with @Id"); 205 206 Compilation nsCollision = compile( 207 "@Document\n" 208 + "public class Gift {\n" 209 + " @Document.Namespace String namespace;\n" 210 + " @Document.Id String id;\n" 211 + " Gift(String id, String namespace) {\n" 212 + " this.id = id;\n" 213 + " this.namespace = namespace;\n" 214 + " }\n" 215 + "}\n" 216 + "@Document\n" 217 + "class CoolGift extends Gift {\n" 218 + " @Document.Namespace String badNamespace;\n" 219 + " CoolGift(String id, String namespace, String badId) {\n" 220 + " super(id, namespace);\n" 221 + " this.badNamespace = namespace;\n" 222 + " }\n" 223 + "}\n"); 224 assertThat(nsCollision).hadErrorContaining( 225 "Duplicate member annotated with @Namespace"); 226 } 227 228 @Test testSuperClass()229 public void testSuperClass() throws Exception { 230 // Try multiple levels of inheritance, nested, with properties, overriding properties 231 Compilation compilation = compile( 232 "@Document\n" 233 + "class Ancestor {\n" 234 + " @Document.Namespace String namespace;\n" 235 + " @Document.Id String id;\n" 236 + " @Document.StringProperty String note;\n" 237 + " int score;\n" 238 + " Ancestor(String id, String namespace, String note) {\n" 239 + " this.id = id;\n" 240 + " this.namespace = namespace;\n" 241 + " this.note = note;\n" 242 + " }\n" 243 + " public String getNote() { return note; }\n" 244 + "}\n" 245 + "class Parent extends Ancestor {\n" 246 + " Parent(String id, String namespace, String note) {\n" 247 + " super(id, namespace, note);\n" 248 + " }\n" 249 + "}\n" 250 + "@Document\n" 251 + "class Gift extends Parent {\n" 252 + " @Document.StringProperty String sender;\n" 253 + " Gift(String id, String namespace, String sender) {\n" 254 + " super(id, namespace, \"note\");\n" 255 + " this.sender = sender;\n" 256 + " }\n" 257 + " public String getSender() { return sender; }\n" 258 + " @Document\n" 259 + " class FooGift extends Gift {\n" 260 + " @Document.Score int score;\n" 261 + " @Document.BooleanProperty boolean foo;\n" 262 + " FooGift(String id, String namespace, String note, int score, " 263 + "boolean foo) {\n" 264 + " super(id, namespace, note);\n" 265 + " this.score = score;\n" 266 + " this.foo = foo;\n" 267 + " }\n" 268 + " }\n" 269 + "}\n"); 270 assertThat(compilation).succeededWithoutWarnings(); 271 checkEqualsGolden("Gift$$__FooGift.java"); 272 } 273 274 @Test testSuperClass_changeSchemaName()275 public void testSuperClass_changeSchemaName() throws Exception { 276 Compilation compilation = compile( 277 "@Document(name=\"MyParent\")\n" 278 + "class Parent {\n" 279 + " @Document.Namespace String namespace;\n" 280 + " @Document.Id String id;\n" 281 + " @Document.StringProperty String note;\n" 282 + " Parent(String id, String namespace, String note) {\n" 283 + " this.id = id;\n" 284 + " this.namespace = namespace;\n" 285 + " this.note = note;\n" 286 + " }\n" 287 + " public String getNote() { return note; }\n" 288 + "}\n" 289 + "\n" 290 + "@Document(name=\"MyGift\")\n" 291 + "class Gift extends Parent {\n" 292 + " @Document.StringProperty String sender;\n" 293 + " Gift(String id, String namespace, String sender) {\n" 294 + " super(id, namespace, \"note\");\n" 295 + " this.sender = sender;\n" 296 + " }\n" 297 + " public String getSender() { return sender; }\n" 298 + "}\n"); 299 assertThat(compilation).succeededWithoutWarnings(); 300 checkResultContains( 301 /*className=*/"Gift.java", 302 /*content=*/"public static final String SCHEMA_NAME = \"MyGift\";"); 303 checkEqualsGolden("Gift.java"); 304 } 305 306 @Test testSuperClass_multipleChangedSchemaNames()307 public void testSuperClass_multipleChangedSchemaNames() throws Exception { 308 Compilation compilation = compile( 309 "@Document(name=\"MyParent\")\n" 310 + "class Parent {\n" 311 + " @Document.Namespace String namespace;\n" 312 + " @Document.Id String id;\n" 313 + " @Document.StringProperty String note;\n" 314 + " Parent(String id, String namespace, String note) {\n" 315 + " this.id = id;\n" 316 + " this.namespace = namespace;\n" 317 + " this.note = note;\n" 318 + " }\n" 319 + " public String getNote() { return note; }\n" 320 + "}\n" 321 + "\n" 322 + "@Document(name=\"MyGift\")\n" 323 + "class Gift extends Parent {\n" 324 + " @Document.StringProperty String sender;\n" 325 + " Gift(String id, String namespace, String sender) {\n" 326 + " super(id, namespace, \"note\");\n" 327 + " this.sender = sender;\n" 328 + " }\n" 329 + " public String getSender() { return sender; }\n" 330 + "}\n" 331 + "\n" 332 + "@Document\n" 333 + "class FooGift extends Gift {\n" 334 + " @Document.BooleanProperty boolean foo;\n" 335 + " FooGift(String id, String namespace, String note, boolean foo) {\n" 336 + " super(id, namespace, note);\n" 337 + " this.foo = foo;\n" 338 + " }\n" 339 + "}\n"); 340 assertThat(compilation).succeededWithoutWarnings(); 341 checkResultContains( 342 /*className=*/"FooGift.java", 343 /*content=*/"public static final String SCHEMA_NAME = \"MyGift\";"); 344 checkEqualsGolden("FooGift.java"); 345 } 346 347 @Test testSuperClassPojoAncestor()348 public void testSuperClassPojoAncestor() throws Exception { 349 // Try multiple levels of inheritance, nested, with properties, overriding properties 350 Compilation compilation = compile( 351 "class Ancestor {\n" 352 + "}\n" 353 + "@Document\n" 354 + "class Parent extends Ancestor {\n" 355 + " @Document.Namespace String namespace;\n" 356 + " @Document.Id String id;\n" 357 + " @Document.StringProperty String note;\n" 358 + " int score;\n" 359 + " Parent(String id, String namespace, String note) {\n" 360 + " this.id = id;\n" 361 + " this.namespace = namespace;\n" 362 + " this.note = note;\n" 363 + " }\n" 364 + "}\n" 365 + "@Document\n" 366 + "class Gift extends Parent {\n" 367 + " @Document.StringProperty String sender;\n" 368 + " Gift(String id, String namespace, String sender) {\n" 369 + " super(id, namespace, \"note\");\n" 370 + " this.sender = sender;\n" 371 + " }\n" 372 + " public String getSender() { return sender; }\n" 373 + " @Document\n" 374 + " class FooGift extends Gift {\n" 375 + " @Document.Score int score;\n" 376 + " @Document.BooleanProperty boolean foo;\n" 377 + " FooGift(String id, String namespace, String note, int score, " 378 + "boolean foo) {\n" 379 + " super(id, namespace, note);\n" 380 + " this.score = score;\n" 381 + " this.foo = foo;\n" 382 + " }\n" 383 + " }\n" 384 + "}\n"); 385 assertThat(compilation).succeededWithoutWarnings(); 386 checkEqualsGolden("Gift$$__FooGift.java"); 387 } 388 389 @Test testSuperClassWithPrivateFields()390 public void testSuperClassWithPrivateFields() throws Exception { 391 // Parents has private fields with public getter. Children should be able to extend 392 // Parents and inherit the public getters. 393 // TODO(b/262916926): we should be able to support inheriting classes not annotated with 394 // @Document. 395 Compilation compilation = compile( 396 "@Document\n" 397 + "class Ancestor {\n" 398 + " @Document.Namespace private String mNamespace;\n" 399 + " @Document.Id private String mId;\n" 400 + " @Document.StringProperty private String mNote;\n" 401 + " Ancestor(String id, String namespace, String note) {\n" 402 + " this.mId = id;\n" 403 + " this.mNamespace = namespace;\n" 404 + " this.mNote = note;\n" 405 + " }\n" 406 + " public String getNamespace() { return mNamespace; }\n" 407 + " public String getId() { return mId; }\n" 408 + " public String getNote() { return mNote; }\n" 409 + "}\n" 410 + "@Document\n" 411 + "class Parent extends Ancestor {\n" 412 + " @Document.StringProperty private String mReceiver;\n" 413 + " Parent(String id, String namespace, String note, String receiver) {\n" 414 + " super(id, namespace, note);\n" 415 + " this.mReceiver = receiver;\n" 416 + " }\n" 417 + " public String getReceiver() { return mReceiver; }\n" 418 + "}\n" 419 + "@Document\n" 420 + "class Gift extends Parent {\n" 421 + " @Document.StringProperty private String mSender;\n" 422 + " Gift(String id, String namespace, String note, String receiver,\n" 423 + " String sender) {\n" 424 + " super(id, namespace, note, receiver);\n" 425 + " this.mSender = sender;\n" 426 + " }\n" 427 + " public String getSender() { return mSender; }\n" 428 + "}\n"); 429 assertThat(compilation).succeededWithoutWarnings(); 430 431 checkResultContains(/*className=*/"Gift.java", /*content=*/"document.getNote()"); 432 checkResultContains(/*className=*/"Gift.java", /*content=*/"document.getReceiver()"); 433 checkResultContains(/*className=*/"Gift.java", /*content=*/"document.getSender()"); 434 checkEqualsGolden("Gift.java"); 435 } 436 437 @Test testManyCreationTimestamp()438 public void testManyCreationTimestamp() { 439 Compilation compilation = compile( 440 "@Document\n" 441 + "public class Gift {\n" 442 + " @Document.Namespace String namespace;\n" 443 + " @Document.Id String id;\n" 444 + " @Document.CreationTimestampMillis long ts1;\n" 445 + " @Document.CreationTimestampMillis long ts2;\n" 446 + "}\n"); 447 448 assertThat(compilation).hadErrorContaining( 449 "Duplicate member annotated with @CreationTimestampMillis"); 450 } 451 452 @Test testNoNamespace()453 public void testNoNamespace() { 454 Compilation compilation = compile( 455 "@Document\n" 456 + "public class Gift {\n" 457 + " @Document.Id String id;\n" 458 + "}\n"); 459 460 assertThat(compilation).hadErrorContaining( 461 "must have exactly one field annotated with @Namespace"); 462 } 463 464 @Test testManyNamespace()465 public void testManyNamespace() { 466 Compilation compilation = compile( 467 "@Document\n" 468 + "public class Gift {\n" 469 + " @Document.Namespace String ns1;\n" 470 + " @Document.Namespace String ns2;\n" 471 + " @Document.Id String id;\n" 472 + "}\n"); 473 474 assertThat(compilation).hadErrorContaining( 475 "Duplicate member annotated with @Namespace"); 476 } 477 478 @Test testManyTtlMillis()479 public void testManyTtlMillis() { 480 Compilation compilation = compile( 481 "@Document\n" 482 + "public class Gift {\n" 483 + " @Document.Namespace String namespace;\n" 484 + " @Document.Id String id;\n" 485 + " @Document.TtlMillis long ts1;\n" 486 + " @Document.TtlMillis long ts2;\n" 487 + "}\n"); 488 489 assertThat(compilation).hadErrorContaining( 490 "Duplicate member annotated with @TtlMillis"); 491 } 492 493 @Test testManyScore()494 public void testManyScore() { 495 Compilation compilation = compile( 496 "@Document\n" 497 + "public class Gift {\n" 498 + " @Document.Namespace String namespace;\n" 499 + " @Document.Id String id;\n" 500 + " @Document.Score int score1;\n" 501 + " @Document.Score int score2;\n" 502 + "}\n"); 503 504 assertThat(compilation).hadErrorContaining( 505 "Duplicate member annotated with @Score"); 506 } 507 508 @Test testClassSpecialValues()509 public void testClassSpecialValues() throws Exception { 510 Compilation compilation = compile( 511 "@Document\n" 512 + "public class Gift {\n" 513 + " @Document.Namespace\n" 514 + " String mNamespace;\n" 515 + " @Document.Id\n" 516 + " String mId;\n" 517 + " @Document.CreationTimestampMillis\n" 518 + " Long mCreationTimestampMillis;\n" 519 + " @Document.Score\n" 520 + " Integer mScore;\n" 521 + " @Document.TtlMillis\n" 522 + " private Long mTtlMillis;\n" 523 + " public Long getTtlMillis() {\n" 524 + " return mTtlMillis;\n" 525 + " } \n" 526 + " public void setTtlMillis(Long ttlMillis) {\n" 527 + " mTtlMillis = ttlMillis;\n" 528 + " } \n" 529 + " @Document.StringProperty\n" 530 + " String mString;\n" 531 + "}\n"); 532 533 checkEqualsGolden("Gift.java"); 534 } 535 536 @Test testCantRead_noGetter()537 public void testCantRead_noGetter() { 538 Compilation compilation = compile( 539 "@Document\n" 540 + "public class Gift {\n" 541 + " @Document.Namespace String namespace;\n" 542 + " @Document.Id String id;\n" 543 + " @Document.LongProperty private int price;\n" 544 + "}\n"); 545 546 assertThat(compilation).hadErrorContaining( 547 "Field 'price' cannot be read: it is private and has no suitable getters " 548 + "[public] int price() OR [public] int getPrice()"); 549 } 550 551 @Test testCantRead_privateGetter()552 public void testCantRead_privateGetter() { 553 Compilation compilation = compile( 554 "@Document\n" 555 + "public class Gift {\n" 556 + " @Document.Namespace String namespace;\n" 557 + " @Document.Id String id;\n" 558 + " @Document.LongProperty private int price;\n" 559 + " private int getPrice() { return 0; }\n" 560 + "}\n"); 561 562 assertThat(compilation).hadErrorContaining( 563 "Field 'price' cannot be read: it is private and has no suitable getters " 564 + "[public] int price() OR [public] int getPrice()"); 565 assertThat(compilation).hadWarningContaining("Getter cannot be used: private visibility"); 566 } 567 568 @Test testCantRead_wrongParamGetter()569 public void testCantRead_wrongParamGetter() { 570 Compilation compilation = compile( 571 "@Document\n" 572 + "public class Gift {\n" 573 + " @Document.Namespace String namespace;\n" 574 + " @Document.Id String id;\n" 575 + " @Document.LongProperty private int price;\n" 576 + " int getPrice(int n) { return 0; }\n" 577 + "}\n"); 578 579 assertThat(compilation).hadErrorContaining( 580 "Field 'price' cannot be read: it is private and has no suitable getters " 581 + "[public] int price() OR [public] int getPrice()"); 582 assertThat(compilation).hadWarningContaining( 583 "Getter cannot be used: should take no parameters"); 584 } 585 586 @Test testCantRead_isGetterNonBoolean()587 public void testCantRead_isGetterNonBoolean() { 588 Compilation compilation = compile( 589 "@Document\n" 590 + "public class Gift {\n" 591 + " @Document.Namespace String namespace;\n" 592 + " @Document.Id String id;\n" 593 + " @Document.LongProperty private int price;\n" 594 + " int isPrice() { return price; }" 595 + " void setPrice(int price) {}" 596 + "}\n"); 597 598 assertThat(compilation).hadErrorContaining( 599 "Field 'price' cannot be read: it is private and has no suitable getters " 600 + "[public] int price() OR [public] int getPrice()"); 601 } 602 603 @Test testCantRead_noSuitableBooleanGetter()604 public void testCantRead_noSuitableBooleanGetter() { 605 Compilation compilation = compile( 606 "@Document\n" 607 + "public class Gift {\n" 608 + " @Document.Namespace String namespace;\n" 609 + " @Document.Id String id;\n" 610 + " @Document.BooleanProperty private boolean wrapped;\n" 611 + "}\n"); 612 613 assertThat(compilation).hadErrorContaining( 614 "Field 'wrapped' cannot be read: it is private and has no suitable getters " 615 + "[public] boolean wrapped() " 616 + "OR [public] boolean getWrapped() " 617 + "OR [public] boolean isWrapped()"); 618 } 619 620 @Test testRead_MultipleGetters()621 public void testRead_MultipleGetters() throws Exception { 622 Compilation compilation = compile( 623 "@Document\n" 624 + "public class Gift {\n" 625 + " @Document.Namespace String namespace;\n" 626 + " @Document.Id String id;\n" 627 + " @Document.LongProperty private int price;\n" 628 + " int getPrice(int n) { return 0; }\n" 629 + " int getPrice() { return 0; }\n" 630 + " void setPrice(int n) {}\n" 631 + "}\n"); 632 633 assertThat(compilation).succeededWithoutWarnings(); 634 checkEqualsGolden("Gift.java"); 635 } 636 637 @Test testRead_isGetterForBoolean()638 public void testRead_isGetterForBoolean() throws Exception { 639 Compilation compilation = compile( 640 "@Document\n" 641 + "public class Gift {\n" 642 + " @Document.Namespace String namespace;\n" 643 + " @Document.Id String id;\n" 644 + " @Document.BooleanProperty private boolean forSale;\n" 645 + " boolean isForSale() { return forSale; }" 646 + " void setForSale(boolean forSale) {}" 647 + "}\n"); 648 649 assertThat(compilation).succeededWithoutWarnings(); 650 checkEqualsGolden("Gift.java"); 651 } 652 653 @Test testRead_GetterReturnsSubtype()654 public void testRead_GetterReturnsSubtype() throws Exception { 655 Compilation compilation = compile( 656 "import java.util.*;\n" 657 + "import com.google.common.collect.*;\n" 658 + "@Document\n" 659 + "public class Gift {\n" 660 + " @Document.Namespace String namespace;\n" 661 + " @Document.Id String id;\n" 662 + " @Document.StringProperty private List<String> from = \n" 663 + " new ArrayList<>();\n" 664 + " ImmutableList<String> getFrom() {" 665 + " return ImmutableList.copyOf(from);" 666 + " }" 667 + " void setFrom(Collection<String> from) {" 668 + " this.from = new ArrayList<>(from);" 669 + " }" 670 + "}\n"); 671 672 assertThat(compilation).succeededWithoutWarnings(); 673 checkEqualsGolden("Gift.java"); 674 } 675 676 @Test testGetterAndSetterFunctions_withFieldName()677 public void testGetterAndSetterFunctions_withFieldName() throws Exception { 678 Compilation compilation = compile( 679 "@Document\n" 680 + "public class Gift {\n" 681 + " @Document.Namespace String namespace;\n" 682 + " @Document.Id String id;\n" 683 + " @Document.LongProperty private int price;\n" 684 + " int price() { return 0; }\n" 685 + " void price(int n) {}\n" 686 + "}\n"); 687 688 assertThat(compilation).succeededWithoutWarnings(); 689 // Check setter function is identified correctly. 690 checkResultContains(/* className= */ "Gift.java", 691 /* content= */ "builder.setPropertyLong(\"price\", document.price());"); 692 // Check getter function is identified correctly. 693 checkResultContains(/* className= */ "Gift.java", 694 /* content= */ "document.price(priceConv);"); 695 checkEqualsGolden("Gift.java"); 696 } 697 698 @Test testCantWrite_noSetter()699 public void testCantWrite_noSetter() { 700 Compilation compilation = compile( 701 "@Document\n" 702 + "public class Gift {\n" 703 + " @Document.Namespace String namespace;\n" 704 + " @Document.Id String id;\n" 705 + " @Document.LongProperty private int price;\n" 706 + " int getPrice() { return price; }\n" 707 + "}\n"); 708 709 assertThat(compilation).hadErrorContaining( 710 "Could not find a suitable constructor/factory method for " 711 + "\"com.example.appsearch.Gift\" that covers properties: [price]. " 712 + "See the warnings for more details."); 713 assertThat(compilation).hadWarningContaining( 714 "Could not find any of the setter(s): " 715 + "[public] void price(int)|" 716 + "[public] void setPrice(int)"); 717 assertThat(compilation).hadWarningContaining( 718 "Cannot use this constructor to construct the class: " 719 + "\"com.example.appsearch.Gift\". " 720 + "No parameters for the properties: [price]"); 721 } 722 723 @Test testCantWrite_privateSetter()724 public void testCantWrite_privateSetter() { 725 Compilation compilation = compile( 726 "@Document\n" 727 + "public class Gift {\n" 728 + " @Document.Namespace String namespace;\n" 729 + " @Document.Id String id;\n" 730 + " @Document.LongProperty private int price;\n" 731 + " int getPrice() { return price; }\n" 732 + " private void setPrice(int n) {}\n" 733 + "}\n"); 734 735 assertThat(compilation).hadErrorContaining( 736 "Could not find a suitable constructor/factory method for " 737 + "\"com.example.appsearch.Gift\" that covers properties: [price]. " 738 + "See the warnings for more details."); 739 assertThat(compilation).hadWarningContaining( 740 "Could not find any of the setter(s): " 741 + "[public] void price(int)|" 742 + "[public] void setPrice(int)"); 743 assertThat(compilation).hadWarningContaining( 744 "Setter cannot be used: private visibility"); 745 } 746 747 @Test testCantWrite_wrongParamSetter()748 public void testCantWrite_wrongParamSetter() { 749 Compilation compilation = compile( 750 "@Document\n" 751 + "public class Gift {\n" 752 + " @Document.Namespace String namespace;\n" 753 + " @Document.Id String id;\n" 754 + " @Document.LongProperty private int price;\n" 755 + " int getPrice() { return price; }\n" 756 + " void setPrice() {}\n" 757 + "}\n"); 758 759 assertThat(compilation).hadErrorContaining( 760 "Could not find a suitable constructor/factory method for " 761 + "\"com.example.appsearch.Gift\" that covers properties: [price]. " 762 + "See the warnings for more details."); 763 assertThat(compilation).hadWarningContaining( 764 "Could not find any of the setter(s): " 765 + "[public] void price(int)|" 766 + "[public] void setPrice(int)"); 767 assertThat(compilation).hadWarningContaining( 768 "Setter cannot be used: takes 0 parameters instead of 1"); 769 assertThat(compilation).hadWarningContaining( 770 "Cannot use this constructor to construct the class: " 771 + "\"com.example.appsearch.Gift\". " 772 + "No parameters for the properties: [price]"); 773 } 774 775 @Test testWrite_multipleSetters()776 public void testWrite_multipleSetters() throws Exception { 777 Compilation compilation = compile( 778 "@Document\n" 779 + "public class Gift {\n" 780 + " @Document.Namespace String namespace;\n" 781 + " @Document.Id String id;\n" 782 + " @Document.LongProperty private int price;\n" 783 + " int getPrice() { return price; }\n" 784 + " void setPrice() {}\n" 785 + " void setPrice(int n) {}\n" 786 + "}\n"); 787 788 assertThat(compilation).succeededWithoutWarnings(); 789 checkEqualsGolden("Gift.java"); 790 } 791 792 @Test testWrite_privateConstructor()793 public void testWrite_privateConstructor() { 794 Compilation compilation = compile( 795 "@Document\n" 796 + "public class Gift {\n" 797 + " private Gift() {}\n" 798 + " @Document.Namespace String namespace;\n" 799 + " @Document.Id String id;\n" 800 + " @Document.LongProperty int price;\n" 801 + "}\n"); 802 803 assertThat(compilation).hadErrorContaining("Could not find a suitable creation method"); 804 assertThat(compilation).hadWarningContaining( 805 "Method cannot be used to create a document class: private visibility"); 806 } 807 808 @Test testWrite_constructorMissingParams()809 public void testWrite_constructorMissingParams() { 810 Compilation compilation = compile( 811 "@Document\n" 812 + "public class Gift {\n" 813 + " Gift(int price) {}\n" 814 + " @Document.Namespace String namespace;\n" 815 + " @Document.Id final String id;\n" 816 + " @Document.LongProperty int price;\n" 817 + "}\n"); 818 819 assertThat(compilation).hadErrorContaining( 820 "Could not find a suitable constructor/factory method for " 821 + "\"com.example.appsearch.Gift\" that covers properties: [id]. " 822 + "See the warnings for more details."); 823 assertThat(compilation).hadWarningContaining( 824 "Could not find any of the setter(s): " 825 + "[public] void id(java.lang.String)|" 826 + "[public] void setId(java.lang.String)"); 827 assertThat(compilation).hadWarningContaining( 828 "Cannot use this constructor to construct the class: " 829 + "\"com.example.appsearch.Gift\". " 830 + "No parameters for the properties: [id]"); 831 } 832 833 @Test testWrite_factoryMethodOnly()834 public void testWrite_factoryMethodOnly() throws IOException { 835 Compilation compilation = compile( 836 "@Document\n" 837 + "public class Gift {\n" 838 + " private Gift(int price, String id, String namespace) {\n" 839 + " this.id = id;\n" 840 + " this.namespace = namespace;\n" 841 + " this.price = price;\n" 842 + " }\n" 843 + " public static Gift create(String id, String namespace, int price) {\n" 844 + " return new Gift(price, id, namespace);" 845 + " }\n" 846 + " @Document.Namespace String namespace;\n" 847 + " @Document.Id final String id;\n" 848 + " @Document.LongProperty int price;\n" 849 + "}\n"); 850 851 assertThat(compilation).succeededWithoutWarnings(); 852 checkResultContains(/* className= */ "Gift.java", 853 /* content= */ "Gift document = Gift.create(idConv, namespaceConv, priceConv);"); 854 } 855 856 @Test 857 // With golden class for factory method. testWrite_bothUsableFactoryMethodAndConstructor_picksFirstUsableCreationMethod()858 public void testWrite_bothUsableFactoryMethodAndConstructor_picksFirstUsableCreationMethod() 859 throws IOException { 860 Compilation compilation = compile( 861 "@Document\n" 862 + "public class Gift {\n" 863 + " Gift(String id, String namespace, int price) {\n" 864 + " this.id = id;\n" 865 + " this.namespace = namespace;\n" 866 + " this.price = price;\n" 867 + " }\n" 868 + " public static Gift create(String id, String namespace, int price) {\n" 869 + " return new Gift(id, namespace, price);" 870 + " }\n" 871 + " @Document.Namespace String namespace;\n" 872 + " @Document.Id String id;\n" 873 + " @Document.LongProperty int price;\n" 874 + "}\n"); 875 876 assertThat(compilation).succeededWithoutWarnings(); 877 checkResultContains(/* className= */ "Gift.java", 878 /* content= */ "Gift document = new Gift(idConv, namespaceConv, priceConv);"); 879 } 880 881 @Test testWrite_usableFactoryMethod_unusableConstructor()882 public void testWrite_usableFactoryMethod_unusableConstructor() 883 throws IOException { 884 Compilation compilation = compile( 885 "@Document\n" 886 + "public class Gift {\n" 887 + " private Gift(String id, String namespace, int price) {\n" 888 + " this.id = id;\n" 889 + " this.namespace = namespace;\n" 890 + " this.price = price;\n" 891 + " }\n" 892 + " public static Gift create(String id, String namespace, int price) {\n" 893 + " return new Gift(id, namespace, price);" 894 + " }\n" 895 + " @Document.Namespace String namespace;\n" 896 + " @Document.Id final String id;\n" 897 + " @Document.LongProperty int price;\n" 898 + "}\n"); 899 900 assertThat(compilation).succeededWithoutWarnings(); 901 checkResultContains(/* className= */ "Gift.java", 902 /* content= */ "Gift document = Gift.create(idConv, namespaceConv, priceConv);"); 903 checkEqualsGolden("Gift.java"); 904 } 905 906 @Test testWrite_unusableFactoryMethod_usableConstructor()907 public void testWrite_unusableFactoryMethod_usableConstructor() 908 throws IOException { 909 Compilation compilation = compile( 910 "@Document\n" 911 + "public class Gift {\n" 912 + " Gift(String id, String namespace, int price) {\n" 913 + " this.id = id;\n" 914 + " this.namespace = namespace;\n" 915 + " this.price = price;\n" 916 + " }\n" 917 + " private static Gift create(String id, String namespace, int price){\n" 918 + " return new Gift(id, namespace, price);" 919 + " }\n" 920 + " @Document.Namespace String namespace;\n" 921 + " @Document.Id final String id;\n" 922 + " @Document.LongProperty int price;\n" 923 + "}\n"); 924 925 assertThat(compilation).succeededWithoutWarnings(); 926 checkResultContains(/* className= */ "Gift.java", 927 /* content= */ "Gift document = new Gift(idConv, namespaceConv, priceConv);"); 928 } 929 930 @Test testWrite_constructorExtraParams()931 public void testWrite_constructorExtraParams() { 932 Compilation compilation = compile( 933 "@Document\n" 934 + "public class Gift {\n" 935 + " Gift(int price, String id, String namespace, int unknownParam) {\n" 936 + " this.id = id;\n" 937 + " this.namespace = namespace;\n" 938 + " this.price = price;\n" 939 + " }\n" 940 + " @Document.Namespace String namespace;\n" 941 + " @Document.Id final String id;\n" 942 + " @Document.LongProperty int price;\n" 943 + "}\n"); 944 945 assertThat(compilation).hadErrorContaining("Could not find a suitable creation method"); 946 assertThat(compilation).hadWarningContaining( 947 "Parameter \"unknownParam\" is not an AppSearch parameter; don't know how to " 948 + "supply it"); 949 } 950 951 @Test testWrite_multipleConventions()952 public void testWrite_multipleConventions() throws Exception { 953 Compilation compilation = compile( 954 "@Document\n" 955 + "public class Gift {\n" 956 + " @Document.Namespace String namespace;\n" 957 + " @Document.Id private String id_;\n" 958 + " @Document.LongProperty private int price1;\n" 959 + " @Document.LongProperty private int mPrice2;\n" 960 + " @Document.LongProperty private int _price3;\n" 961 + " @Document.LongProperty final int price4_;\n" 962 + " int getPrice1() { return price1; }\n" 963 + " int price2() { return mPrice2; }\n" 964 + " int getPrice3() { return _price3; }\n" 965 + " void setPrice1(int n) {}\n" 966 + " void price2(int n) {}\n" 967 + " void price3(int n) {}\n" 968 + " String getId() {\n" 969 + " return id_;\n" 970 + " }\n" 971 + " public Gift(String id, int price4) {\n" 972 + " id_ = id;" 973 + " price4_ = price4;\n" 974 + " }\n" 975 + "}\n"); 976 assertThat(compilation).succeededWithoutWarnings(); 977 checkEqualsGolden("Gift.java"); 978 } 979 980 @Test testSuccessSimple()981 public void testSuccessSimple() throws Exception { 982 Compilation compilation = compile( 983 "@Document\n" 984 + "public class Gift {\n" 985 + " Gift(boolean dog, String id, String namespace) {\n" 986 + " this.id = id;\n" 987 + " this.namespace = namespace;\n" 988 + " this.dog = dog;\n" 989 + " }\n" 990 + " @Document.Namespace String namespace;\n" 991 + " @Document.Id final String id;\n" 992 + " @Document.LongProperty int price;\n" 993 + " @Document.BooleanProperty boolean cat = false;\n" 994 + " public void setCat(boolean cat) {}\n" 995 + " @Document.BooleanProperty private final boolean dog;\n" 996 + " public boolean getDog() { return dog; }\n" 997 + "}\n"); 998 999 assertThat(compilation).succeededWithoutWarnings(); 1000 checkEqualsGolden("Gift.java"); 1001 } 1002 1003 @Test testDifferentTypeName()1004 public void testDifferentTypeName() throws Exception { 1005 Compilation compilation = compile( 1006 "@Document(name=\"DifferentType\")\n" 1007 + "public class Gift {\n" 1008 + " @Document.Namespace String namespace;\n" 1009 + " @Document.Id String id;\n" 1010 + "}\n"); 1011 1012 assertThat(compilation).succeededWithoutWarnings(); 1013 checkEqualsGolden("Gift.java"); 1014 } 1015 1016 @Test testRepeatedFields()1017 public void testRepeatedFields() throws Exception { 1018 Compilation compilation = compile( 1019 "import java.util.*;\n" 1020 + "@Document\n" 1021 + "public class Gift {\n" 1022 + " @Document.Namespace String namespace;\n" 1023 + " @Document.Id String id;\n" 1024 + " @Document.StringProperty List<String> listOfString;\n" 1025 + " @Document.LongProperty Collection<Integer> setOfInt;\n" 1026 + " @Document.BytesProperty byte[][] repeatedByteArray;\n" 1027 + " @Document.BytesProperty byte[] byteArray;\n" 1028 + "}\n"); 1029 1030 assertThat(compilation).succeededWithoutWarnings(); 1031 checkEqualsGolden("Gift.java"); 1032 } 1033 1034 @Test testCardinality()1035 public void testCardinality() throws Exception { 1036 Compilation compilation = compile( 1037 "import java.util.*;\n" 1038 + "@Document\n" 1039 + "public class Gift {\n" 1040 + " @Document.Namespace String namespace;\n" 1041 + " @Document.Id String id;\n" 1042 + " @Document.StringProperty(required=true) List<String> repeatReq;\n" 1043 + " @Document.StringProperty(required=false) List<String> repeatNoReq;\n" 1044 + " @Document.DoubleProperty(required=true) Float req;\n" 1045 + " @Document.DoubleProperty(required=false) Float noReq;\n" 1046 + "}\n"); 1047 1048 assertThat(compilation).succeededWithoutWarnings(); 1049 checkEqualsGolden("Gift.java"); 1050 } 1051 1052 @Test testAllSingleTypes()1053 public void testAllSingleTypes() throws Exception { 1054 // TODO(b/156296904): Uncomment Gift in this test when it's supported 1055 Compilation compilation = compile( 1056 "import java.util.*;\n" 1057 + "import androidx.appsearch.app.AppSearchBlobHandle;\n" 1058 + "import androidx.appsearch.app.EmbeddingVector;\n" 1059 + "@Document\n" 1060 + "public class Gift {\n" 1061 + " @Document.Namespace String namespace;\n" 1062 + " @Document.Id String id;\n" 1063 + " @StringProperty String stringProp;\n" 1064 + " @LongProperty Integer integerProp;\n" 1065 + " @LongProperty Long longProp;\n" 1066 + " @DoubleProperty Float floatProp;\n" 1067 + " @DoubleProperty Double doubleProp;\n" 1068 + " @BooleanProperty Boolean booleanProp;\n" 1069 + " @BytesProperty byte[] bytesProp;\n" 1070 + " @EmbeddingProperty EmbeddingVector vectorProp;\n" 1071 + " @BlobHandleProperty AppSearchBlobHandle blobHandleProp;\n" 1072 //+ " @DocumentProperty Gift documentProp;\n" 1073 + "}\n"); 1074 1075 assertThat(compilation).succeededWithoutWarnings(); 1076 checkEqualsGolden("Gift.java"); 1077 } 1078 1079 @Test testTokenizerType()1080 public void testTokenizerType() throws Exception { 1081 // AppSearchSchema requires Android and is not available in this desktop test, so we cheat 1082 // by using the integer constants directly. 1083 Compilation compilation = compile( 1084 "import java.util.*;\n" 1085 + "@Document\n" 1086 + "public class Gift {\n" 1087 + " @Document.Namespace String namespace;\n" 1088 + " @Document.Id String id;\n" 1089 + "\n" 1090 // NONE index type will generate a NONE tokenizerType type. 1091 + " @Document.StringProperty(tokenizerType=0, indexingType=0) " 1092 + " String tokNoneInvalid;\n" 1093 + " @Document.StringProperty(tokenizerType=1, indexingType=0) " 1094 + " String tokPlainInvalid;\n" 1095 + " @Document.StringProperty(tokenizerType=2, indexingType=0) " 1096 + " String tokVerbatimInvalid;\n" 1097 + " @Document.StringProperty(tokenizerType=3, indexingType=0) " 1098 + " String tokRfc822Invalid;\n" 1099 + "\n" 1100 // Indexing type exact. 1101 + " @Document.StringProperty(tokenizerType=0, indexingType=1) " 1102 + " String tokNone;\n" 1103 + " @Document.StringProperty(tokenizerType=1, indexingType=1) " 1104 + " String tokPlain;\n" 1105 + " @Document.StringProperty(tokenizerType=2, indexingType=1) " 1106 + " String tokVerbatim;\n" 1107 + " @Document.StringProperty(tokenizerType=3, indexingType=1) " 1108 + " String tokRfc822;\n" 1109 + "\n" 1110 // Indexing type prefix. 1111 + " @Document.StringProperty(tokenizerType=0, indexingType=2) " 1112 + " String tokNonePrefix;\n" 1113 + " @Document.StringProperty(tokenizerType=1, indexingType=2) " 1114 + " String tokPlainPrefix;\n" 1115 + " @Document.StringProperty(tokenizerType=2, indexingType=2) " 1116 + " String tokVerbatimPrefix;\n" 1117 + " @Document.StringProperty(tokenizerType=3, indexingType=2) " 1118 + " String tokRfc822Prefix;\n" 1119 + "}\n"); 1120 1121 assertThat(compilation).succeededWithoutWarnings(); 1122 checkEqualsGolden("Gift.java"); 1123 } 1124 1125 @Test testInvalidTokenizerType()1126 public void testInvalidTokenizerType() { 1127 // AppSearchSchema requires Android and is not available in this desktop test, so we cheat 1128 // by using the integer constants directly. 1129 Compilation compilation = compile( 1130 "import java.util.*;\n" 1131 + "@Document\n" 1132 + "public class Gift {\n" 1133 + " @Document.Namespace String namespace;\n" 1134 + " @Document.Id String id;\n" 1135 + " @Document.StringProperty(indexingType=1, tokenizerType=100)\n" 1136 + " String str;\n" 1137 + "}\n"); 1138 1139 assertThat(compilation).hadErrorContaining("Unknown tokenizer type 100"); 1140 } 1141 1142 @Test testIndexingType()1143 public void testIndexingType() throws Exception { 1144 // AppSearchSchema requires Android and is not available in this desktop test, so we cheat 1145 // by using the integer constants directly. 1146 Compilation compilation = compile( 1147 "import java.util.*;\n" 1148 + "@Document\n" 1149 + "public class Gift {\n" 1150 + " @Document.Namespace String namespace;\n" 1151 + " @Document.Id String id;\n" 1152 + " @Document.StringProperty(indexingType=0) String indexNone;\n" 1153 + " @Document.StringProperty(indexingType=1) String indexExact;\n" 1154 + " @Document.StringProperty(indexingType=2) String indexPrefix;\n" 1155 + "}\n"); 1156 1157 assertThat(compilation).succeededWithoutWarnings(); 1158 checkEqualsGolden("Gift.java"); 1159 } 1160 1161 @Test testInvalidIndexingType()1162 public void testInvalidIndexingType() { 1163 // AppSearchSchema requires Android and is not available in this desktop test, so we cheat 1164 // by using the integer constants directly. 1165 Compilation compilation = compile( 1166 "import java.util.*;\n" 1167 + "@Document\n" 1168 + "public class Gift {\n" 1169 + " @Document.Namespace String namespace;\n" 1170 + " @Document.Id String id;\n" 1171 + " @Document.StringProperty(indexingType=100, tokenizerType=1)\n" 1172 + " String str;\n" 1173 + "}\n"); 1174 1175 assertThat(compilation).hadErrorContaining("Unknown indexing type 100"); 1176 } 1177 1178 @Test testLongPropertyIndexingType()1179 public void testLongPropertyIndexingType() throws Exception { 1180 // AppSearchSchema requires Android and is not available in this desktop test, so we cheat 1181 // by using the integer constants directly. 1182 Compilation compilation = compile( 1183 "import java.util.*;\n" 1184 + "@Document\n" 1185 + "public class Gift {\n" 1186 + " @Document.Namespace String namespace;\n" 1187 + " @Document.Id String id;\n" 1188 + " @Document.LongProperty Long defaultIndexNone;\n" 1189 + " @Document.LongProperty(indexingType=0) Long indexNone;\n" 1190 + " @Document.LongProperty(indexingType=1) Integer boxInt;\n" 1191 + " @Document.LongProperty(indexingType=1) int unboxInt;\n" 1192 + " @Document.LongProperty(indexingType=1) Long boxLong;\n" 1193 + " @Document.LongProperty(indexingType=1) long unboxLong;\n" 1194 + " @Document.LongProperty(indexingType=1) Integer[] arrBoxInt;\n" 1195 + " @Document.LongProperty(indexingType=1) int[] arrUnboxInt;\n" 1196 + " @Document.LongProperty(indexingType=1) Long[] arrBoxLong;\n" 1197 + " @Document.LongProperty(indexingType=1) long[] arrUnboxLong;\n" 1198 + "}\n"); 1199 1200 assertThat(compilation).succeededWithoutWarnings(); 1201 checkEqualsGolden("Gift.java"); 1202 } 1203 1204 @Test testInvalidLongPropertyIndexingType()1205 public void testInvalidLongPropertyIndexingType() { 1206 // AppSearchSchema requires Android and is not available in this desktop test, so we cheat 1207 // by using the integer constants directly. 1208 Compilation compilation = compile( 1209 "import java.util.*;\n" 1210 + "@Document\n" 1211 + "public class Gift {\n" 1212 + " @Document.Namespace String namespace;\n" 1213 + " @Document.Id String id;\n" 1214 + " @Document.LongProperty(indexingType=100) Long invalidProperty;\n" 1215 + "}\n"); 1216 1217 assertThat(compilation).hadErrorContaining("Unknown indexing type 100"); 1218 } 1219 1220 @Test testStringPropertyJoinableType()1221 public void testStringPropertyJoinableType() throws Exception { 1222 Compilation compilation = compile( 1223 "import java.util.*;\n" 1224 + "@Document\n" 1225 + "public class Gift {\n" 1226 + " @Document.Namespace String namespace;\n" 1227 + " @Document.Id String id;\n" 1228 + " @Document.StringProperty(joinableValueType=1)\n" 1229 + " String object;\n" 1230 + "}\n"); 1231 1232 assertThat(compilation).succeededWithoutWarnings(); 1233 checkEqualsGolden("Gift.java"); 1234 } 1235 1236 @Test testRepeatedPropertyJoinableType_throwsError()1237 public void testRepeatedPropertyJoinableType_throwsError() { 1238 Compilation compilation = compile( 1239 "import java.util.*;\n" 1240 + "@Document\n" 1241 + "public class Gift {\n" 1242 + " @Document.Namespace String namespace;\n" 1243 + " @Document.Id String id;\n" 1244 + " @Document.StringProperty(joinableValueType=1)\n" 1245 + " List<String> object;\n" 1246 + "}\n"); 1247 1248 assertThat(compilation).hadErrorContaining( 1249 "Joinable value type 1 not allowed on repeated properties."); 1250 } 1251 1252 @Test testPropertyName()1253 public void testPropertyName() throws Exception { 1254 Compilation compilation = compile( 1255 "import java.util.*;\n" 1256 + "@Document\n" 1257 + "public class Gift {\n" 1258 + " @Document.Namespace String namespace;\n" 1259 + " @Document.Id String id;\n" 1260 + " @Document.StringProperty(name=\"newName\") String oldName;\n" 1261 + "}\n"); 1262 1263 assertThat(compilation).succeededWithoutWarnings(); 1264 checkEqualsGolden("Gift.java"); 1265 } 1266 1267 @Test testToGenericDocument_allSupportedTypes()1268 public void testToGenericDocument_allSupportedTypes() throws Exception { 1269 // TODO(b/156296904): Uncomment Gift and GenericDocument when it's supported 1270 Compilation compilation = compile( 1271 "import java.util.*;\n" 1272 + "import androidx.appsearch.app.AppSearchBlobHandle;\n" 1273 + "import androidx.appsearch.app.GenericDocument;\n" 1274 + "import androidx.appsearch.app.EmbeddingVector;\n" 1275 + "@Document\n" 1276 + "public class Gift {\n" 1277 + " @Namespace String namespace;\n" 1278 + " @Id String id;\n" 1279 + "\n" 1280 + " // Collections\n" 1281 + " @LongProperty Collection<Long> collectLong;\n" // 1a 1282 + " @LongProperty Collection<Integer> collectInteger;\n" // 1a 1283 + " @DoubleProperty Collection<Double> collectDouble;\n" // 1a 1284 + " @DoubleProperty Collection<Float> collectFloat;\n" // 1a 1285 + " @BooleanProperty Collection<Boolean> collectBoolean;\n" // 1a 1286 + " @BytesProperty Collection<byte[]> collectByteArr;\n" // 1a 1287 + " @StringProperty Collection<String> collectString;\n" // 1b 1288 + " @DocumentProperty Collection<Gift> collectGift;\n" // 1c 1289 + " @EmbeddingProperty Collection<EmbeddingVector> collectVec;\n" // 1b 1290 + " @BlobHandleProperty Collection<AppSearchBlobHandle> collectBlob;\n" 1291 //1b 1292 + "\n" 1293 + " // Arrays\n" 1294 + " @LongProperty Long[] arrBoxLong;\n" // 2a 1295 + " @LongProperty long[] arrUnboxLong;\n" // 2b 1296 + " @LongProperty Integer[] arrBoxInteger;\n" // 2a 1297 + " @LongProperty int[] arrUnboxInt;\n" // 2a 1298 + " @DoubleProperty Double[] arrBoxDouble;\n" // 2a 1299 + " @DoubleProperty double[] arrUnboxDouble;\n" // 2b 1300 + " @DoubleProperty Float[] arrBoxFloat;\n" // 2a 1301 + " @DoubleProperty float[] arrUnboxFloat;\n" // 2a 1302 + " @BooleanProperty Boolean[] arrBoxBoolean;\n" // 2a 1303 + " @BooleanProperty boolean[] arrUnboxBoolean;\n" // 2b 1304 + " @BytesProperty byte[][] arrUnboxByteArr;\n" // 2b 1305 + " @StringProperty String[] arrString;\n" // 2b 1306 + " @DocumentProperty Gift[] arrGift;\n" // 2c 1307 + " @EmbeddingProperty EmbeddingVector[] arrVec;\n" // 2b 1308 + " @BlobHandleProperty AppSearchBlobHandle[] arrBlob;\n" // 2b 1309 + "\n" 1310 + " // Single values\n" 1311 + " @StringProperty String string;\n" // 3a 1312 + " @LongProperty Long boxLong;\n" // 3a 1313 + " @LongProperty long unboxLong;\n" // 3b 1314 + " @LongProperty Integer boxInteger;\n" // 3a 1315 + " @LongProperty int unboxInt;\n" // 3b 1316 + " @DoubleProperty Double boxDouble;\n" // 3a 1317 + " @DoubleProperty double unboxDouble;\n" // 3b 1318 + " @DoubleProperty Float boxFloat;\n" // 3a 1319 + " @DoubleProperty float unboxFloat;\n" // 3b 1320 + " @BooleanProperty Boolean boxBoolean;\n" // 3a 1321 + " @BooleanProperty boolean unboxBoolean;\n" // 3b 1322 + " @BytesProperty byte[] unboxByteArr;\n" // 3a 1323 + " @DocumentProperty Gift gift;\n" // 3c 1324 + " @EmbeddingProperty EmbeddingVector vec;\n" // 3a 1325 + " @BlobHandleProperty AppSearchBlobHandle blob;\n" // 3a 1326 + "}\n"); 1327 1328 assertThat(compilation).succeededWithoutWarnings(); 1329 checkEqualsGolden("Gift.java"); 1330 } 1331 1332 @Test testPropertyAnnotation_invalidType()1333 public void testPropertyAnnotation_invalidType() { 1334 Compilation compilation = compile( 1335 "import java.util.*;\n" 1336 + "@Document\n" 1337 + "public class Gift {\n" 1338 + " @Namespace String namespace;\n" 1339 + " @Id String id;\n" 1340 + " @BooleanProperty String[] arrString;\n" 1341 + "}\n"); 1342 1343 assertThat(compilation).hadErrorContaining( 1344 "@BooleanProperty must only be placed on a getter/field of type or array or " 1345 + "collection of boolean|java.lang.Boolean"); 1346 } 1347 1348 @Test testToGenericDocument_invalidTypes()1349 public void testToGenericDocument_invalidTypes() { 1350 Compilation compilation = compile( 1351 "import java.util.*;\n" 1352 + "@Document\n" 1353 + "public class Gift {\n" 1354 + " @Namespace String namespace;\n" 1355 + " @Id String id;\n" 1356 + " @BytesProperty Collection<Byte[]> collectBoxByteArr;\n" 1357 + "}\n"); 1358 assertThat(compilation).hadErrorContaining( 1359 "@BytesProperty must only be placed on a getter/field of type or array or " 1360 + "collection of byte[]"); 1361 1362 compilation = compile( 1363 "import java.util.*;\n" 1364 + "@Document\n" 1365 + "public class Gift {\n" 1366 + " @Namespace String namespace;\n" 1367 + " @Id String id;\n" 1368 + " @BytesProperty Collection<Byte> collectByte;\n" 1369 + "}\n"); 1370 assertThat(compilation).hadErrorContaining( 1371 "@BytesProperty must only be placed on a getter/field of type or array or " 1372 + "collection of byte[]"); 1373 1374 compilation = compile( 1375 "import java.util.*;\n" 1376 + "@Document\n" 1377 + "public class Gift {\n" 1378 + " @Namespace String namespace;\n" 1379 + " @Id String id;\n" 1380 + " @BytesProperty Byte[][] arrBoxByteArr;\n" 1381 + "}\n"); 1382 assertThat(compilation).hadErrorContaining( 1383 "@BytesProperty must only be placed on a getter/field of type or array or " 1384 + "collection of byte[]"); 1385 } 1386 1387 @Test testAllSpecialFields_field()1388 public void testAllSpecialFields_field() throws Exception { 1389 Compilation compilation = compile( 1390 "@Document\n" 1391 + "public class Gift {\n" 1392 + " @Document.Namespace String namespace;\n" 1393 + " @Document.Id String id;\n" 1394 + " @Document.CreationTimestampMillis long creationTs;\n" 1395 + " @Document.TtlMillis int ttlMs;\n" 1396 + " @Document.LongProperty int price;\n" 1397 + " @Document.Score int score;\n" 1398 + "}\n"); 1399 1400 assertThat(compilation).succeededWithoutWarnings(); 1401 checkEqualsGolden("Gift.java"); 1402 } 1403 1404 @Test testAllSpecialFields_getter()1405 public void testAllSpecialFields_getter() throws Exception { 1406 Compilation compilation = compile( 1407 "@Document\n" 1408 + "public class Gift {\n" 1409 + " @Document.Namespace String namespace;\n" 1410 + " @Document.Id private String id;\n" 1411 + " @Document.Score private int score;\n" 1412 + " @Document.CreationTimestampMillis private long creationTs;\n" 1413 + " @Document.TtlMillis private int ttlMs;\n" 1414 + " @Document.LongProperty private int price;\n" 1415 + " public String getId() { return id; }\n" 1416 + " public void setId(String id) { this.id = id; }\n" 1417 + " public String getNamespace() { return namespace; }\n" 1418 + " public void setNamespace(String namespace) {\n" 1419 + " this.namespace = namespace;\n" 1420 + " }\n" 1421 + " public int getScore() { return score; }\n" 1422 + " public void setScore(int score) { this.score = score; }\n" 1423 + " public long getCreationTs() { return creationTs; }\n" 1424 + " public void setCreationTs(int creationTs) {\n" 1425 + " this.creationTs = creationTs;\n" 1426 + " }\n" 1427 + " public int getTtlMs() { return ttlMs; }\n" 1428 + " public void setTtlMs(int ttlMs) { this.ttlMs = ttlMs; }\n" 1429 + " public int getPrice() { return price; }\n" 1430 + " public void setPrice(int price) { this.price = price; }\n" 1431 + "}\n"); 1432 1433 assertThat(compilation).succeededWithoutWarnings(); 1434 checkEqualsGolden("Gift.java"); 1435 } 1436 1437 @Test testMultipleNestedAutoValueDocument()1438 public void testMultipleNestedAutoValueDocument() throws IOException { 1439 Compilation compilation = compile( 1440 "import com.google.auto.value.AutoValue;\n" 1441 + "import com.google.auto.value.AutoValue.*;\n" 1442 + "@Document\n" 1443 + "@AutoValue\n" 1444 + "public abstract class Gift {\n" 1445 + " @CopyAnnotations @Document.Id abstract String id();\n" 1446 + " @CopyAnnotations @Document.Namespace abstract String namespace();\n" 1447 + " @CopyAnnotations\n" 1448 + " @Document.StringProperty abstract String property();\n" 1449 + " public static Gift create(String id, String namespace, String" 1450 + " property) {\n" 1451 + " return new AutoValue_Gift(id, namespace, property);\n" 1452 + " }\n" 1453 + " @Document\n" 1454 + " @AutoValue\n" 1455 + " abstract static class B {\n" 1456 + " @CopyAnnotations @Document.Id abstract String id();\n" 1457 + " @CopyAnnotations @Document.Namespace abstract String namespace();\n" 1458 + " public static B create(String id, String namespace) {\n" 1459 + " return new AutoValue_Gift_B(id, namespace);\n" 1460 + " }\n" 1461 + " }\n" 1462 + " @Document\n" 1463 + " @AutoValue\n" 1464 + " abstract static class A {\n" 1465 + " @CopyAnnotations @Document.Namespace abstract String namespace();\n" 1466 + " @CopyAnnotations @Document.Id abstract String id();\n" 1467 + " public static A create(String id, String namespace) {\n" 1468 + " return new AutoValue_Gift_A(id, namespace);\n" 1469 + " }\n" 1470 + " }\n" 1471 + "}\n"); 1472 assertThat(compilation).succeededWithoutWarnings(); 1473 checkEqualsGolden("AutoValue_Gift_A.java"); 1474 } 1475 1476 @Test testAutoValueDocument()1477 public void testAutoValueDocument() throws IOException { 1478 Compilation compilation = compile( 1479 "import com.google.auto.value.AutoValue;\n" 1480 + "import com.google.auto.value.AutoValue.*;\n" 1481 + "@Document\n" 1482 + "@AutoValue\n" 1483 + "public abstract class Gift {\n" 1484 + " @CopyAnnotations @Document.Namespace abstract String namespace();\n" 1485 + " @CopyAnnotations @Document.Id abstract String id();\n" 1486 + " @CopyAnnotations\n" 1487 + " @Document.StringProperty abstract String property();\n" 1488 + " public static Gift create(String id, String namespace, String" 1489 + " property) {\n" 1490 + " return new AutoValue_Gift(id, namespace, property);\n" 1491 + " }\n" 1492 + "}\n"); 1493 1494 assertThat(compilation).succeededWithoutWarnings(); 1495 checkEqualsGolden("AutoValue_Gift.java"); 1496 checkDocumentMapEqualsGolden(/* roundIndex= */0); 1497 // The number of rounds that the annotation processor takes can vary from setup to setup. 1498 // In this test case, AutoValue documents are processed in the second round because their 1499 // generated classes are not available in the first turn. 1500 checkDocumentMapEqualsGolden(/* roundIndex= */1); 1501 } 1502 1503 @Test testAutoValueDocumentWithNormalDocument()1504 public void testAutoValueDocumentWithNormalDocument() throws IOException { 1505 Compilation compilation = compile( 1506 "import com.google.auto.value.AutoValue;\n" 1507 + "import com.google.auto.value.AutoValue.*;\n" 1508 + "@Document\n" 1509 + "class Person {\n" 1510 + " @Document.Namespace String namespace;\n" 1511 + " @Document.Id String id;\n" 1512 + "}\n" 1513 + "@Document\n" 1514 + "@AutoValue\n" 1515 + "public abstract class Gift {\n" 1516 + " @CopyAnnotations @Document.Id abstract String id();\n" 1517 + " @CopyAnnotations @Document.Namespace abstract String namespace();\n" 1518 + " @CopyAnnotations\n" 1519 + " @Document.StringProperty abstract String property();\n" 1520 + " public static Gift create(String id, String namespace, String" 1521 + " property) {\n" 1522 + " return new AutoValue_Gift(id, namespace, property);\n" 1523 + " }\n" 1524 + "}\n"); 1525 1526 assertThat(compilation).succeededWithoutWarnings(); 1527 checkEqualsGolden("AutoValue_Gift.java"); 1528 checkDocumentMapEqualsGolden(/* roundIndex= */0); 1529 // The number of rounds that the annotation processor takes can vary from setup to setup. 1530 // In this test case, AutoValue documents are processed in the second round because their 1531 // generated classes are not available in the first turn. 1532 checkDocumentMapEqualsGolden(/* roundIndex= */1); 1533 } 1534 1535 @Test testInnerClass()1536 public void testInnerClass() throws Exception { 1537 Compilation compilation = compile( 1538 "import java.util.*;\n" 1539 + "import androidx.appsearch.app.GenericDocument;\n" 1540 + "public class Gift {\n" 1541 + " @Document\n" 1542 + " public static class InnerGift{\n" 1543 + " @Document.Namespace String namespace;\n" 1544 + " @Document.Id String id;\n" 1545 + " @StringProperty String[] arrString;\n" // 2b 1546 + " }\n" 1547 + "}\n"); 1548 1549 assertThat(compilation).succeededWithoutWarnings(); 1550 checkEqualsGolden("Gift$$__InnerGift.java"); 1551 } 1552 1553 @Test testOneBadConstructor()1554 public void testOneBadConstructor() throws Exception { 1555 Compilation compilation = compile( 1556 "@Document\n" 1557 + "public class Gift {\n" 1558 + " @Document.Namespace private String mNamespace;\n" 1559 + " @Document.Id private String mId;\n" 1560 + " public Gift(String id, String namespace, boolean nonAppSearchParam){\n" 1561 + " mId = id;\n" 1562 + " mNamespace = namespace;\n" 1563 + " }\n" 1564 + " public Gift(String id){\n" 1565 + " mId = id;\n" 1566 + " }\n" 1567 + " public String getId(){" 1568 + " return mId;" 1569 + " }\n" 1570 + " public String getNamespace(){" 1571 + " return mNamespace;" 1572 + " }\n" 1573 + " public void setNamespace(String namespace){\n" 1574 + " mNamespace = namespace;" 1575 + " }\n" 1576 + "}\n"); 1577 1578 assertThat(compilation).succeededWithoutWarnings(); 1579 checkEqualsGolden("Gift.java"); 1580 } 1581 1582 @Test testNestedDocumentsIndexing()1583 public void testNestedDocumentsIndexing() throws Exception { 1584 Compilation compilation = compile( 1585 "import java.util.*;\n" 1586 + "@Document\n" 1587 + "public class Gift {\n" 1588 + " @Document.Id String id;\n" 1589 + " @Document.Namespace String namespace;\n" 1590 + " @Document.DocumentProperty(indexNestedProperties = true) " 1591 + "Collection<GiftContent> giftContentsCollection;\n" 1592 + " @Document.DocumentProperty(indexNestedProperties = true) GiftContent[]" 1593 + " giftContentsArray;\n" 1594 + " @Document.DocumentProperty(indexNestedProperties = true) GiftContent " 1595 + "giftContent;\n" 1596 + " @Document.DocumentProperty() Collection<GiftContent> " 1597 + "giftContentsCollectionNotIndexed;\n" 1598 + " @Document.DocumentProperty GiftContent[] " 1599 + "giftContentsArrayNotIndexed;\n" 1600 + " @Document.DocumentProperty GiftContent giftContentNotIndexed;\n" 1601 + "}\n" 1602 + "\n" 1603 + "@Document\n" 1604 + "class GiftContent {\n" 1605 + " @Document.Id String id;\n" 1606 + " @Document.Namespace String namespace;\n" 1607 + " @Document.StringProperty String[] contentsArray;\n" 1608 + " @Document.StringProperty String contents;\n" 1609 + "}\n"); 1610 1611 assertThat(compilation).succeededWithoutWarnings(); 1612 checkEqualsGolden("Gift.java"); 1613 } 1614 1615 @Test testMultipleNesting()1616 public void testMultipleNesting() throws Exception { 1617 Compilation compilation = compile( 1618 "import java.util.*;\n" 1619 + "@Document\n" 1620 + "public class Gift {\n" 1621 + " @Document.Id String id;\n" 1622 + " @Document.Namespace String namespace;\n" 1623 + " @Document.DocumentProperty Middle middleContentA;\n" 1624 + " @Document.DocumentProperty Middle middleContentB;\n" 1625 + "}\n" 1626 + "\n" 1627 + "@Document\n" 1628 + "class Middle {\n" 1629 + " @Document.Id String id;\n" 1630 + " @Document.Namespace String namespace;\n" 1631 + " @Document.DocumentProperty Inner innerContentA;\n" 1632 + " @Document.DocumentProperty Inner innerContentB;\n" 1633 + "}\n" 1634 + "@Document\n" 1635 + "class Inner {\n" 1636 + " @Document.Id String id;\n" 1637 + " @Document.Namespace String namespace;\n" 1638 + " @Document.StringProperty String contents;\n" 1639 + "}\n"); 1640 1641 assertThat(compilation).succeededWithoutWarnings(); 1642 checkEqualsGolden("Gift.java"); 1643 1644 // Check that Gift contains Middle, Middle contains Inner, and Inner returns empty 1645 checkResultContains(/* className= */ "Gift.java", 1646 /* content= */ "classSet.add(Middle.class);\n return classSet;"); 1647 checkResultContains(/* className= */ "Middle.java", 1648 /* content= */ "classSet.add(Inner.class);\n return classSet;"); 1649 checkResultContains(/* className= */ "Inner.java", 1650 /* content= */ "return Collections.emptyList();"); 1651 } 1652 1653 @Test testPolymorphism()1654 public void testPolymorphism() throws Exception { 1655 // Gift should automatically get "note2" via Java's "extends" semantics, but "note1" need 1656 // to be manually provided so that Parent1 can be a parent of Gift. 1657 Compilation compilation = compile( 1658 "@Document\n" 1659 + "class Parent1 {\n" 1660 + " @Document.Namespace String namespace;\n" 1661 + " @Document.Id String id;\n" 1662 + " @Document.StringProperty String note1;\n" 1663 + "}\n" 1664 + "@Document\n" 1665 + "class Parent2 {\n" 1666 + " @Document.Namespace String namespace;\n" 1667 + " @Document.Id String id;\n" 1668 + " @Document.StringProperty String note2;\n" 1669 + "}\n" 1670 + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n" 1671 + "class Gift extends Parent2 {\n" 1672 + " @Document.StringProperty String sender;\n" 1673 + " @Document.StringProperty String note1;\n" 1674 + "}\n"); 1675 assertThat(compilation).succeededWithoutWarnings(); 1676 1677 checkResultContains("Gift.java", "addParentType($$__AppSearch__Parent1.SCHEMA_NAME)"); 1678 checkResultContains("Gift.java", "addParentType($$__AppSearch__Parent2.SCHEMA_NAME)"); 1679 1680 checkEqualsGolden("Gift.java"); 1681 checkDocumentMapEqualsGolden(/* roundIndex= */0); 1682 } 1683 1684 @Test testPolymorphismOverrideExtendedProperty()1685 public void testPolymorphismOverrideExtendedProperty() throws Exception { 1686 Compilation compilation = compile( 1687 "@Document\n" 1688 + "class Parent1 {\n" 1689 + " @Document.Namespace String namespace;\n" 1690 + " @Document.Id String id;\n" 1691 + " @Document.StringProperty String note1;\n" 1692 + "}\n" 1693 + "@Document\n" 1694 + "class Parent2 {\n" 1695 + " @Document.Namespace String namespace;\n" 1696 + " @Document.Id String id;\n" 1697 + " @Document.StringProperty(indexingType=2) String note2;\n" 1698 + "}\n" 1699 + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n" 1700 + "class Gift extends Parent2 {\n" 1701 + " @Document.StringProperty String sender;\n" 1702 + " @Document.StringProperty String note1;\n" 1703 + " @Document.StringProperty(indexingType=1) String note2;\n" 1704 + "}\n"); 1705 assertThat(compilation).succeededWithoutWarnings(); 1706 1707 // Should expect the indexingType of note2 from Gift is 1, which is 1708 // INDEXING_TYPE_EXACT_TERMS, instead of 2. 1709 checkResultContains("Gift.java", 1710 "setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS)"); 1711 1712 checkEqualsGolden("Gift.java"); 1713 } 1714 1715 @Test testPolymorphismOverrideExtendedPropertyInvalid()1716 public void testPolymorphismOverrideExtendedPropertyInvalid() { 1717 // Overridden properties cannot change the names. 1718 Compilation compilation = compile( 1719 "@Document\n" 1720 + "class Parent1 {\n" 1721 + " @Document.Namespace String namespace;\n" 1722 + " @Document.Id String id;\n" 1723 + " @Document.StringProperty String note1;\n" 1724 + "}\n" 1725 + "@Document\n" 1726 + "class Parent2 {\n" 1727 + " @Document.Namespace String namespace;\n" 1728 + " @Document.Id String id;\n" 1729 + " @Document.StringProperty(name=\"note2\", indexingType=2) String " 1730 + "note2;\n" 1731 + "}\n" 1732 + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n" 1733 + "class Gift extends Parent2 {\n" 1734 + " @Document.StringProperty String sender;\n" 1735 + " @Document.StringProperty String note1;\n" 1736 + " @Document.StringProperty(name=\"note2_new\", indexingType=1) String " 1737 + "note2;\n" 1738 + "}\n"); 1739 assertThat(compilation).hadErrorContaining( 1740 "Property name within the annotation must stay consistent when " 1741 + "overriding annotated members but changed from 'note2' -> 'note2_new'"); 1742 1743 // Overridden properties cannot change the types. 1744 compilation = compile( 1745 "@Document\n" 1746 + "class Parent1 {\n" 1747 + " @Document.Namespace String namespace;\n" 1748 + " @Document.Id String id;\n" 1749 + " @Document.StringProperty String note1;\n" 1750 + "}\n" 1751 + "@Document\n" 1752 + "class Parent2 {\n" 1753 + " @Document.Namespace String namespace;\n" 1754 + " @Document.Id String id;\n" 1755 + " @Document.StringProperty String note2;\n" 1756 + "}\n" 1757 + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n" 1758 + "class Gift extends Parent2 {\n" 1759 + " @Document.StringProperty String sender;\n" 1760 + " @Document.StringProperty String note1;\n" 1761 + " @LongProperty Long note2;\n" 1762 + "}\n"); 1763 assertThat(compilation).hadErrorContaining( 1764 "Property type must stay consistent when overriding annotated " 1765 + "members but changed from @StringProperty -> @LongProperty"); 1766 } 1767 1768 @Test testPolymorphismWithNestedType()1769 public void testPolymorphismWithNestedType() throws Exception { 1770 Compilation compilation = compile( 1771 "@Document\n" 1772 + "class Parent1 {\n" 1773 + " @Document.Namespace String namespace;\n" 1774 + " @Document.Id String id;\n" 1775 + " @Document.StringProperty String note1;\n" 1776 + "}\n" 1777 + "@Document\n" 1778 + "class Parent2 {\n" 1779 + " @Document.Namespace String namespace;\n" 1780 + " @Document.Id String id;\n" 1781 + " @Document.StringProperty String note2;\n" 1782 + "}\n" 1783 + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class})\n" 1784 + "class Gift extends Parent2 {\n" 1785 + " @Document.StringProperty String sender;\n" 1786 + " @Document.StringProperty String note1;\n" 1787 + " @Document.DocumentProperty Inner innerContent;\n" 1788 + "}\n" 1789 + "@Document\n" 1790 + "class Inner {\n" 1791 + " @Document.Id String id;\n" 1792 + " @Document.Namespace String namespace;\n" 1793 + " @Document.StringProperty String contents;\n" 1794 + "}\n"); 1795 assertThat(compilation).succeededWithoutWarnings(); 1796 1797 // Should see that both the parent types and nested types are added to the generated 1798 // getDependencyDocumentClasses method in Gift. 1799 checkResultContains("Gift.java", "classSet.add(Parent1.class)"); 1800 checkResultContains("Gift.java", "classSet.add(Parent2.class)"); 1801 checkResultContains("Gift.java", "classSet.add(Inner.class)"); 1802 1803 checkEqualsGolden("Gift.java"); 1804 } 1805 1806 @Test testPolymorphismDuplicatedParents()1807 public void testPolymorphismDuplicatedParents() throws Exception { 1808 // Should see that every parent can only be added once. 1809 Compilation compilation = compile( 1810 "@Document\n" 1811 + "class Parent1 {\n" 1812 + " @Document.Namespace String namespace;\n" 1813 + " @Document.Id String id;\n" 1814 + " @Document.StringProperty String note1;\n" 1815 + "}\n" 1816 + "@Document\n" 1817 + "class Parent2 {\n" 1818 + " @Document.Namespace String namespace;\n" 1819 + " @Document.Id String id;\n" 1820 + " @Document.StringProperty String note2;\n" 1821 + "}\n" 1822 + "@Document(name = \"Gift\", parent = {Parent1.class, Parent2.class, " 1823 + "Parent1.class})\n" 1824 + "class Gift extends Parent2 {\n" 1825 + " @Document.StringProperty String sender;\n" 1826 + " @Document.StringProperty String note1;\n" 1827 + "}\n"); 1828 assertThat(compilation).succeededWithoutWarnings(); 1829 checkEqualsGolden("Gift.java"); 1830 } 1831 1832 @Test testPolymorphismChildTypeWithoutName()1833 public void testPolymorphismChildTypeWithoutName() { 1834 Compilation compilation = compile( 1835 "@Document\n" 1836 + "class Parent {\n" 1837 + " @Document.Namespace String namespace;\n" 1838 + " @Document.Id String id;\n" 1839 + " @Document.StringProperty String note;\n" 1840 + "}\n" 1841 + "@Document(parent = Parent.class)\n" 1842 + "class Gift extends Parent {\n" 1843 + " @Document.StringProperty String sender;\n" 1844 + "}\n"); 1845 assertThat(compilation).hadErrorContaining( 1846 "All @Document classes with a parent must explicitly provide a name"); 1847 } 1848 1849 @Test testIndexableNestedPropertiesListSimple()1850 public void testIndexableNestedPropertiesListSimple() throws Exception { 1851 Compilation compilation = compile( 1852 "@Document\n" 1853 + "class Address {\n" 1854 + " @Document.Namespace String namespace;\n" 1855 + " @Document.Id String id;\n" 1856 + " @Document.LongProperty long streetNumber;\n" 1857 + " @Document.StringProperty String streetName;\n" 1858 + " @Document.StringProperty String state;\n" 1859 + " @Document.LongProperty long zipCode;\n" 1860 + "}\n" 1861 + "@Document\n" 1862 + "class Person {\n" 1863 + " @Document.Namespace String namespace;\n" 1864 + " @Document.Id String id;\n" 1865 + " @Document.StringProperty String name;\n" 1866 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 1867 + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" 1868 + "}\n"); 1869 assertThat(compilation).succeededWithoutWarnings(); 1870 1871 checkResultContains("Person.java", "addIndexableNestedProperties(\"streetNumber\")"); 1872 checkResultContains("Person.java", "addIndexableNestedProperties(\"streetName\")"); 1873 1874 checkEqualsGolden("Person.java"); 1875 } 1876 1877 @Test testIndexableNestedPropertiesListEmpty()1878 public void testIndexableNestedPropertiesListEmpty() throws Exception { 1879 Compilation compilation = compile( 1880 "@Document\n" 1881 + "class Address {\n" 1882 + " @Document.Namespace String namespace;\n" 1883 + " @Document.Id String id;\n" 1884 + " @Document.LongProperty long streetNumber;\n" 1885 + " @Document.StringProperty String streetName;\n" 1886 + " @Document.StringProperty String state;\n" 1887 + " @Document.LongProperty long zipCode;\n" 1888 + "}\n" 1889 + "@Document\n" 1890 + "class Person {\n" 1891 + " @Document.Namespace String namespace;\n" 1892 + " @Document.Id String id;\n" 1893 + " @Document.StringProperty String name;\n" 1894 + " @Document.DocumentProperty(indexableNestedPropertiesList = {})" 1895 + " Address livesAt;\n" 1896 + "}\n"); 1897 assertThat(compilation).succeededWithoutWarnings(); 1898 checkResultDoesNotContain("Person.java", "addIndexableNestedProperties"); 1899 checkEqualsGolden("Person.java"); 1900 } 1901 1902 @Test testIndexableNestedPropertiesListInheritSuperclassTrue()1903 public void testIndexableNestedPropertiesListInheritSuperclassTrue() throws Exception { 1904 Compilation compilation = compile( 1905 "@Document\n" 1906 + "class Address {\n" 1907 + " @Document.Namespace String namespace;\n" 1908 + " @Document.Id String id;\n" 1909 + " @Document.LongProperty long streetNumber;\n" 1910 + " @Document.StringProperty String streetName;\n" 1911 + " @Document.StringProperty String state;\n" 1912 + " @Document.LongProperty long zipCode;\n" 1913 + "}\n" 1914 + "@Document\n" 1915 + "class Person {\n" 1916 + " @Document.Namespace String namespace;\n" 1917 + " @Document.Id String id;\n" 1918 + " @Document.StringProperty String name;\n" 1919 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 1920 + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" 1921 + "}\n" 1922 + "@Document(name = \"Artist\", parent = {Person.class})\n" 1923 + "class Artist extends Person {\n" 1924 + " @Document.StringProperty String mostFamousWork;\n" 1925 + " @Document.DocumentProperty(" 1926 + " indexableNestedPropertiesList = {\"state\"}," 1927 + " inheritIndexableNestedPropertiesFromSuperclass = true)" 1928 + " Address livesAt;\n" 1929 + "}\n"); 1930 assertThat(compilation).succeededWithoutWarnings(); 1931 1932 checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetNumber\")"); 1933 checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetName\")"); 1934 checkResultContains("Artist.java", "addIndexableNestedProperties(\"state\")"); 1935 1936 checkEqualsGolden("Artist.java"); 1937 } 1938 1939 @Test testIndexableNestedPropertiesListInheritSuperclassFalse()1940 public void testIndexableNestedPropertiesListInheritSuperclassFalse() throws Exception { 1941 Compilation compilation = compile( 1942 "@Document\n" 1943 + "class Address {\n" 1944 + " @Document.Namespace String namespace;\n" 1945 + " @Document.Id String id;\n" 1946 + " @Document.LongProperty long streetNumber;\n" 1947 + " @Document.StringProperty String streetName;\n" 1948 + " @Document.StringProperty String state;\n" 1949 + " @Document.LongProperty long zipCode;\n" 1950 + "}\n" 1951 + "@Document\n" 1952 + "class Person {\n" 1953 + " @Document.Namespace String namespace;\n" 1954 + " @Document.Id String id;\n" 1955 + " @Document.StringProperty String name;\n" 1956 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 1957 + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" 1958 + "}\n" 1959 + "@Document(name = \"Artist\", parent = {Person.class})\n" 1960 + "class Artist extends Person {\n" 1961 + " @Document.StringProperty String mostFamousWork;\n" 1962 + " @Document.DocumentProperty(" 1963 + " indexableNestedPropertiesList = {\"state\"}," 1964 + " inheritIndexableNestedPropertiesFromSuperclass = false)" 1965 + " Address livesAt;\n" 1966 + "}\n"); 1967 assertThat(compilation).succeededWithoutWarnings(); 1968 1969 checkResultContains("Artist.java", "addIndexableNestedProperties(\"state\")"); 1970 checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"streetNumber\")"); 1971 checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"streetName\")"); 1972 1973 checkEqualsGolden("Artist.java"); 1974 } 1975 1976 @Test testIndexableNestedPropertiesListInheritWithMultipleParentsClasses()1977 public void testIndexableNestedPropertiesListInheritWithMultipleParentsClasses() 1978 throws Exception { 1979 // Tests that the child class inherits nested properties from the parent correctly. When 1980 // set to true, the field overridden by the child class should only inherit indexable 1981 // nested properties form its java parent class (i.e. the superclass/interface which the 1982 // child class extends from/implements). 1983 // In this test case, Artist's parent class is Person, and ArtistEmployee's parent class 1984 // is Artist. This means that ArtistEmployee.livesAt's indexable list should contain the 1985 // properties s specified in Artist.livesAt. and Person.livesAt (since both Artist and 1986 // ArtistEmployee sets inheritFromParent=true for this field). ArtistEmployee.livesAt 1987 // should not inherit the indexable list from Employee.livesAt since Employee is not 1988 // ArtistEmployee's java class parent. 1989 Compilation compilation = compile( 1990 "@Document\n" 1991 + "class Address {\n" 1992 + " @Document.Namespace String namespace;\n" 1993 + " @Document.Id String id;\n" 1994 + " @Document.LongProperty long streetNumber;\n" 1995 + " @Document.StringProperty String streetName;\n" 1996 + " @Document.StringProperty String state;\n" 1997 + " @Document.LongProperty long zipCode;\n" 1998 + "}\n" 1999 + "@Document\n" 2000 + "class Person {\n" 2001 + " @Document.Namespace String namespace;\n" 2002 + " @Document.Id String id;\n" 2003 + " @Document.StringProperty String name;\n" 2004 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 2005 + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" 2006 + "}\n" 2007 + "@Document(name = \"Artist\", parent = {Person.class})\n" 2008 + "class Artist extends Person {\n" 2009 + " @Document.StringProperty String mostFamousWork;\n" 2010 + " @Document.DocumentProperty(" 2011 + " indexableNestedPropertiesList = {\"state\"}," 2012 + " inheritIndexableNestedPropertiesFromSuperclass = true)" 2013 + " Address livesAt;\n" 2014 + "}\n" 2015 + "@Document(name = \"Employee\", parent = {Person.class})\n" 2016 + "class Employee extends Person {\n" 2017 + " @Document.DocumentProperty(" 2018 + " indexableNestedPropertiesList = {\"zipCode\"}," 2019 + " inheritIndexableNestedPropertiesFromSuperclass = true)" 2020 + " Address livesAt;\n" 2021 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 2022 + " {\"zipCode\", \"streetName\"}) Address worksAt;\n" 2023 + "}\n" 2024 + "@Document(name = \"ArtistEmployee\", parent = {Artist.class," 2025 + "Employee.class})\n" 2026 + "class ArtistEmployee extends Artist {\n" 2027 + " @Document.StringProperty String mostFamousWork;\n" 2028 + " @Document.DocumentProperty(" 2029 + " inheritIndexableNestedPropertiesFromSuperclass = true)" 2030 + " Address livesAt;\n" 2031 + "}\n"); 2032 assertThat(compilation).succeededWithoutWarnings(); 2033 2034 checkResultContains("ArtistEmployee.java", 2035 "addIndexableNestedProperties(\"streetNumber\")"); 2036 checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")"); 2037 checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")"); 2038 // ArtistEmployee's indexable list should not contain 'zipCode' as ArtistEmployee only 2039 // extends Artist, which does not index zipCode 2040 checkResultDoesNotContain("ArtistEmployee.java", 2041 "addIndexableNestedProperties(\"zipCode\")"); 2042 2043 checkEqualsGolden("ArtistEmployee.java"); 2044 } 2045 2046 @Test testIndexableNestedPropertiesListImplicitInheritance()2047 public void testIndexableNestedPropertiesListImplicitInheritance() throws Exception { 2048 // Tests that properties that are not declared in the child class itself but exists in 2049 // the class due to java class inheritance indexes the correct indexable list. 2050 // Artist.livesAt should be defined for Artist and index the same indexable properties as 2051 // Person.livesAt. 2052 Compilation compilation = compile( 2053 "@Document\n" 2054 + "class Address {\n" 2055 + " @Document.Namespace String namespace;\n" 2056 + " @Document.Id String id;\n" 2057 + " @Document.LongProperty long streetNumber;\n" 2058 + " @Document.StringProperty String streetName;\n" 2059 + " @Document.StringProperty String state;\n" 2060 + " @Document.LongProperty long zipCode;\n" 2061 + "}\n" 2062 + "@Document\n" 2063 + "class Person {\n" 2064 + " @Document.Namespace String namespace;\n" 2065 + " @Document.Id String id;\n" 2066 + " @Document.StringProperty String name;\n" 2067 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 2068 + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" 2069 + "}\n" 2070 + "@Document(name = \"Artist\", parent = {Person.class})\n" 2071 + "class Artist extends Person {\n" 2072 + " @Document.StringProperty String mostFamousWork;\n" 2073 + "}\n"); 2074 assertThat(compilation).succeededWithoutWarnings(); 2075 2076 checkResultContains("Artist.java", 2077 "addIndexableNestedProperties(\"streetNumber\")"); 2078 checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetName\")"); 2079 2080 checkResultDoesNotContain("Artist.java", 2081 "addIndexableNestedProperties(\"zipCode\")"); 2082 checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"state\")"); 2083 2084 checkEqualsGolden("Artist.java"); 2085 } 2086 2087 @Test testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels()2088 public void testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels() 2089 throws Exception { 2090 // Tests that the indexable list is inherited correctly across multiple java inheritance 2091 // levels. 2092 // ArtistEmployee.livesAt should index the nested properties defined in Person.livesAt. 2093 Compilation compilation = compile( 2094 "@Document\n" 2095 + "class Address {\n" 2096 + " @Document.Namespace String namespace;\n" 2097 + " @Document.Id String id;\n" 2098 + " @Document.LongProperty long streetNumber;\n" 2099 + " @Document.StringProperty String streetName;\n" 2100 + " @Document.StringProperty String state;\n" 2101 + " @Document.LongProperty long zipCode;\n" 2102 + "}\n" 2103 + "@Document\n" 2104 + "class Person {\n" 2105 + " @Document.Namespace String namespace;\n" 2106 + " @Document.Id String id;\n" 2107 + " @Document.StringProperty String name;\n" 2108 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 2109 + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" 2110 + "}\n" 2111 + "@Document(name = \"Artist\", parent = {Person.class})\n" 2112 + "class Artist extends Person {\n" 2113 + " @Document.StringProperty String mostFamousWork;\n" 2114 + "}\n" 2115 + "@Document(name = \"ArtistEmployee\", parent = {Artist.class})\n" 2116 + "class ArtistEmployee extends Artist {\n" 2117 + " @Document.StringProperty String worksAt;\n" 2118 + " @Document.DocumentProperty(" 2119 + " inheritIndexableNestedPropertiesFromSuperclass = true)" 2120 + " Address livesAt;\n" 2121 + "}\n"); 2122 assertThat(compilation).succeededWithoutWarnings(); 2123 2124 checkResultContains("ArtistEmployee.java", 2125 "addIndexableNestedProperties(\"streetNumber\")"); 2126 checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")"); 2127 2128 checkResultDoesNotContain("ArtistEmployee.java", 2129 "addIndexableNestedProperties(\"zipCode\")"); 2130 checkResultDoesNotContain("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")"); 2131 2132 checkEqualsGolden("ArtistEmployee.java"); 2133 } 2134 2135 @Test testIndexableNestedPropertiesListTopLevelInheritTrue()2136 public void testIndexableNestedPropertiesListTopLevelInheritTrue() throws Exception { 2137 Compilation compilation = compile( 2138 "@Document\n" 2139 + "class Address {\n" 2140 + " @Document.Namespace String namespace;\n" 2141 + " @Document.Id String id;\n" 2142 + " @Document.LongProperty long streetNumber;\n" 2143 + " @Document.StringProperty String streetName;\n" 2144 + " @Document.StringProperty String state;\n" 2145 + " @Document.LongProperty long zipCode;\n" 2146 + "}\n" 2147 + "@Document\n" 2148 + "class Person {\n" 2149 + " @Document.Namespace String namespace;\n" 2150 + " @Document.Id String id;\n" 2151 + " @Document.StringProperty String name;\n" 2152 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 2153 + " {\"streetNumber\", \"streetName\"}," 2154 + " inheritIndexableNestedPropertiesFromSuperclass = true)" 2155 + " Address livesAt;\n" 2156 + "}\n" 2157 + "@Document(name = \"Artist\", parent = {Person.class})\n" 2158 + "class Artist extends Person {\n" 2159 + " @Document.StringProperty String mostFamousWork;\n" 2160 + " @Document.DocumentProperty(indexableNestedPropertiesList =" 2161 + " {\"state\"}, inheritIndexableNestedPropertiesFromSuperclass = true)" 2162 + " Address livesAt;\n" 2163 + "}\n" 2164 + "@Document(name = \"ArtistEmployee\", parent = {Artist.class})\n" 2165 + "class ArtistEmployee extends Artist {\n" 2166 + " @Document.StringProperty String worksAt;\n" 2167 + " @Document.DocumentProperty(" 2168 + " inheritIndexableNestedPropertiesFromSuperclass = true)" 2169 + " Address livesAt;\n" 2170 + "}\n"); 2171 assertThat(compilation).succeededWithoutWarnings(); 2172 2173 checkResultContains("ArtistEmployee.java", 2174 "addIndexableNestedProperties(\"streetNumber\")"); 2175 checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")"); 2176 checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")"); 2177 2178 checkResultDoesNotContain("ArtistEmployee.java", 2179 "addIndexableNestedProperties(\"zipCode\")"); 2180 2181 checkEqualsGolden("ArtistEmployee.java"); 2182 } 2183 2184 @Test testAnnotationOnClassGetter()2185 public void testAnnotationOnClassGetter() throws Exception { 2186 Compilation compilation = compile( 2187 "@Document\n" 2188 + "public class Gift {\n" 2189 + " @Document.Namespace String namespace;\n" 2190 + " @Document.Id String id;\n" 2191 + " @Document.LongProperty public int getPrice() { return 0; }\n" 2192 + " public void setPrice(int price) {}\n" 2193 + "}\n"); 2194 2195 assertThat(compilation).succeededWithoutWarnings(); 2196 checkResultContains("Gift.java", 2197 "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")"); 2198 checkResultContains("Gift.java", "document.setPrice(getPriceConv)"); 2199 checkResultContains("Gift.java", "document.getPrice()"); 2200 checkEqualsGolden("Gift.java"); 2201 } 2202 2203 @Test testAnnotationOnClassGetterUsingFactory()2204 public void testAnnotationOnClassGetterUsingFactory() throws IOException { 2205 Compilation compilation = compile( 2206 "@Document\n" 2207 + "public class Gift {\n" 2208 + " private Gift() {}\n" 2209 + " public static Gift create(String id, String namespace, int price) {\n" 2210 + " return new Gift();\n" 2211 + " }\n" 2212 + " @Document.Namespace public String getNamespace() { return \"hi\"; }\n" 2213 + " @Document.Id public String getId() { return \"0\"; }\n" 2214 + " @Document.LongProperty public int getPrice() { return 0; }\n" 2215 + "}\n"); 2216 2217 assertThat(compilation).succeededWithoutWarnings(); 2218 checkResultContains("Gift.java", 2219 "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")"); 2220 checkResultContains("Gift.java", "Gift.create(getIdConv, getNamespaceConv, getPriceConv)"); 2221 checkResultContains("Gift.java", "document.getPrice()"); 2222 checkEqualsGolden("Gift.java"); 2223 } 2224 2225 @Test testAnnotationOnInterfaceGetter()2226 public void testAnnotationOnInterfaceGetter() throws Exception { 2227 Compilation compilation = compile( 2228 "@Document\n" 2229 + "public interface Gift {\n" 2230 + " public static Gift create(String id, String namespace) {\n" 2231 + " return new GiftImpl(id, namespace);\n" 2232 + " }\n" 2233 + " @Document.Namespace public String getNamespace();\n" 2234 + " @Document.Id public String getId();\n" 2235 + " @Document.LongProperty public int getPrice();\n" 2236 + " public void setPrice(int price);\n" 2237 + "}\n" 2238 + "class GiftImpl implements Gift{\n" 2239 + " public GiftImpl(String id, String namespace) {\n" 2240 + " this.id = id;\n" 2241 + " this.namespace = namespace;\n" 2242 + " }\n" 2243 + " private String namespace;\n" 2244 + " private String id;\n" 2245 + " private int price;\n" 2246 + " public String getNamespace() { return namespace; }\n" 2247 + " public String getId() { return id; }\n" 2248 + " public int getPrice() { return price; }\n" 2249 + " public void setPrice(int price) { this.price = price; }\n" 2250 + "}\n"); 2251 2252 assertThat(compilation).succeededWithoutWarnings(); 2253 checkResultContains("Gift.java", 2254 "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")"); 2255 checkResultContains("Gift.java", "Gift.create(getIdConv, getNamespaceConv)"); 2256 checkResultContains("Gift.java", "document.setPrice(getPriceConv)"); 2257 checkResultContains("Gift.java", "document.getPrice()"); 2258 checkEqualsGolden("Gift.java"); 2259 } 2260 2261 @Test testAnnotationOnGetterWithoutFactory()2262 public void testAnnotationOnGetterWithoutFactory() { 2263 // An interface without any factory method is not able to initialize, as interfaces do 2264 // not have constructors. 2265 Compilation compilation = compile( 2266 "@Document\n" 2267 + "public interface Gift {\n" 2268 + " @Document.Namespace public String getNamespace();\n" 2269 + " @Document.Id public String getId();\n" 2270 + " @Document.LongProperty public int getPrice();\n" 2271 + " public void setPrice(int price);\n" 2272 + "}\n"); 2273 2274 assertThat(compilation).hadErrorContaining("Could not find a suitable creation method"); 2275 } 2276 2277 @Test testAnnotationOnGetterWithoutSetter()2278 public void testAnnotationOnGetterWithoutSetter() { 2279 Compilation compilation = compile( 2280 "@Document\n" 2281 + "public interface Gift {\n" 2282 + " public static Gift create(String id, String namespace) {\n" 2283 + " return new GiftImpl(id, namespace);\n" 2284 + " }\n" 2285 + " @Document.Namespace public String getNamespace();\n" 2286 + " @Document.Id public String getId();\n" 2287 + " @Document.LongProperty public int getPrice();\n" 2288 + "}\n" 2289 + "class GiftImpl implements Gift{\n" 2290 + " public GiftImpl(String id, String namespace) {\n" 2291 + " this.id = id;\n" 2292 + " this.namespace = namespace;\n" 2293 + " }\n" 2294 + " private String namespace;\n" 2295 + " private String id;\n" 2296 + " private int price;\n" 2297 + " public String getNamespace() { return namespace; }\n" 2298 + " public String getId() { return id; }\n" 2299 + " public int getPrice() { return price; }\n" 2300 + "}\n"); 2301 2302 assertThat(compilation).hadWarningContaining( 2303 "Cannot use this creation method to construct the class: " 2304 + "\"com.example.appsearch.Gift\". " 2305 + "No parameters for the properties: [getPrice]"); 2306 assertThat(compilation).hadWarningContaining( 2307 "Could not find any of the setter(s): " 2308 + "[public] void namespace(java.lang.String)|" 2309 + "[public] void setNamespace(java.lang.String)"); 2310 assertThat(compilation).hadWarningContaining( 2311 "Could not find any of the setter(s): " 2312 + "[public] void id(java.lang.String)|" 2313 + "[public] void setId(java.lang.String)"); 2314 assertThat(compilation).hadWarningContaining( 2315 "Could not find any of the setter(s): " 2316 + "[public] void price(int)|" 2317 + "[public] void setPrice(int)"); 2318 } 2319 2320 @Test testInterfaceAsNestedDocument()2321 public void testInterfaceAsNestedDocument() throws Exception { 2322 Compilation compilation = compile( 2323 "@Document\n" 2324 + "interface Thing {\n" 2325 + " public static Thing create(String id, String namespace) {\n" 2326 + " return new ThingImpl(id, namespace);\n" 2327 + " }\n" 2328 + " @Document.Namespace public String getNamespace();\n" 2329 + " @Document.Id public String getId();\n" 2330 + "}\n" 2331 + "class ThingImpl implements Thing {\n" 2332 + " public ThingImpl(String id, String namespace) {\n" 2333 + " this.id = id;\n" 2334 + " this.namespace = namespace;\n" 2335 + " }\n" 2336 + " private String namespace;\n" 2337 + " private String id;\n" 2338 + " public String getNamespace() { return namespace; }\n" 2339 + " public String getId() { return id; }\n" 2340 + "}\n" 2341 + "@Document\n" 2342 + "public class Gift {\n" 2343 + " @Document.Namespace String namespace;\n" 2344 + " @Document.Id String id;\n" 2345 + " @Document.DocumentProperty Thing thing;\n" 2346 + "}\n"); 2347 assertThat(compilation).succeededWithoutWarnings(); 2348 checkResultContains("Thing.java", 2349 "Thing document = Thing.create(getIdConv, getNamespaceConv)"); 2350 checkResultContains("Gift.java", 2351 "thingConv = thingCopy.toDocumentClass(Thing.class, documentClassMappingContext)"); 2352 checkEqualsGolden("Gift.java"); 2353 } 2354 2355 @Test testInterfaceImplementingParents()2356 public void testInterfaceImplementingParents() throws Exception { 2357 Compilation compilation = compile( 2358 "@Document\n" 2359 + "interface Root {\n" 2360 + " @Document.Namespace public String getNamespace();\n" 2361 + " @Document.Id public String getId();\n" 2362 + " public static Root create(String id, String namespace) {\n" 2363 + " return new GiftImpl();\n" 2364 + " }\n" 2365 + "}\n" 2366 + "@Document(name=\"Parent1\", parent=Root.class)\n" 2367 + "interface Parent1 extends Root {\n" 2368 + " @Document.StringProperty public String getStr1();\n" 2369 + " public static Parent1 create(String id, String namespace, String " 2370 + "str1) {\n" 2371 + " return new GiftImpl();\n" 2372 + " }" 2373 + "}\n" 2374 + "@Document(name=\"Parent2\", parent=Root.class)\n" 2375 + "interface Parent2 extends Root {\n" 2376 + " @Document.StringProperty public String getStr2();\n" 2377 + " public static Parent2 create(String id, String namespace, String " 2378 + "str2) {\n" 2379 + " return new GiftImpl();\n" 2380 + " }\n" 2381 + "}\n" 2382 + "@Document(name=\"Gift\", parent={Parent1.class, Parent2.class})\n" 2383 + "public interface Gift extends Parent1, Parent2 {\n" 2384 + " public static Gift create(String id, String namespace, String str1, " 2385 + "String str2, int price) {\n" 2386 + " return new GiftImpl();\n" 2387 + " }\n" 2388 + " @Document.LongProperty public int getPrice();\n" 2389 + "}\n" 2390 + "class GiftImpl implements Gift{\n" 2391 + " public GiftImpl() {}\n" 2392 + " public String getNamespace() { return \"namespace\"; }\n" 2393 + " public String getId() { return \"id\"; }\n" 2394 + " public String getStr1() { return \"str1\"; }\n" 2395 + " public String getStr2() { return \"str2\"; }\n" 2396 + " public int getPrice() { return 0; }\n" 2397 + "}\n"); 2398 assertThat(compilation).succeededWithoutWarnings(); 2399 checkResultContains("Gift.java", 2400 "new AppSearchSchema.StringPropertyConfig.Builder(\"str1\")"); 2401 checkResultContains("Gift.java", 2402 "new AppSearchSchema.StringPropertyConfig.Builder(\"str2\")"); 2403 checkResultContains("Gift.java", 2404 "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")"); 2405 checkResultContains("Gift.java", 2406 "Gift.create(getIdConv, getNamespaceConv, getStr1Conv, getStr2Conv, getPriceConv)"); 2407 checkResultContains("Gift.java", "document.getStr1()"); 2408 checkResultContains("Gift.java", "document.getStr2()"); 2409 checkResultContains("Gift.java", "document.getPrice()"); 2410 checkEqualsGolden("Gift.java"); 2411 checkDocumentMapEqualsGolden(/* roundIndex= */0); 2412 } 2413 2414 @Test testSameNameGetterAndFieldAnnotatingGetter()2415 public void testSameNameGetterAndFieldAnnotatingGetter() throws Exception { 2416 Compilation compilation = compile( 2417 "@Document\n" 2418 + "public class Gift {\n" 2419 + " @Document.Namespace String namespace;\n" 2420 + " @Document.Id String id;\n" 2421 + " @Document.LongProperty public int getPrice() { return 0; }\n" 2422 + " public int price;\n" 2423 + " public void setPrice(int price) {}\n" 2424 + "}\n"); 2425 assertThat(compilation).succeededWithoutWarnings(); 2426 checkResultContains("Gift.java", 2427 "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")"); 2428 checkResultContains("Gift.java", "document.setPrice(getPriceConv)"); 2429 checkResultContains("Gift.java", "document.getPrice()"); 2430 checkEqualsGolden("Gift.java"); 2431 } 2432 2433 @Test testSameNameGetterAndFieldAnnotatingField()2434 public void testSameNameGetterAndFieldAnnotatingField() throws Exception { 2435 Compilation compilation = compile( 2436 "@Document\n" 2437 + "public class Gift {\n" 2438 + " @Document.Namespace String namespace;\n" 2439 + " @Document.Id String id;\n" 2440 + " @Document.LongProperty public int price;\n" 2441 + " public int getPrice() { return 0; }\n" 2442 + " public void setPrice(int price) {}\n" 2443 + "}\n"); 2444 assertThat(compilation).succeededWithoutWarnings(); 2445 checkResultContains("Gift.java", 2446 "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")"); 2447 checkResultContains("Gift.java", "document.price = priceConv"); 2448 checkResultContains("Gift.java", 2449 "builder.setPropertyLong(\"price\", document.price)"); 2450 checkEqualsGolden("Gift.java"); 2451 } 2452 2453 @Test testSameNameGetterAndFieldAnnotatingBoth()2454 public void testSameNameGetterAndFieldAnnotatingBoth() throws Exception { 2455 Compilation compilation = compile( 2456 "@Document\n" 2457 + "public class Gift {\n" 2458 + " @Document.Namespace String namespace;\n" 2459 + " @Document.Id String id;\n" 2460 + " @Document.LongProperty(name=\"price1\")\n" 2461 + " public int getPrice() { return 0; }\n" 2462 + " public void setPrice(int price) {}\n" 2463 + " @Document.LongProperty(name=\"price2\")\n" 2464 + " public int price;\n" 2465 + "}\n"); 2466 assertThat(compilation).hadErrorContaining( 2467 "Normalized name \"price\" is already taken up by pre-existing " 2468 + "int Gift#getPrice(). " 2469 + "Please rename this getter/field to something else."); 2470 } 2471 2472 @Test testSameNameGetterAndFieldAnnotatingBothButGetterIsPrivate()2473 public void testSameNameGetterAndFieldAnnotatingBothButGetterIsPrivate() { 2474 Compilation compilation = compile( 2475 "@Document\n" 2476 + "public class Gift {\n" 2477 + " @Document.Namespace String namespace;\n" 2478 + " @Document.Id String id;\n" 2479 + " @Document.LongProperty(name=\"price1\")\n" 2480 + " private int getPrice() { return 0; }\n" 2481 + " public void setPrice(int price) {}\n" 2482 + " @Document.LongProperty(name=\"price2\")\n" 2483 + " public int price;\n" 2484 + "}\n"); 2485 assertThat(compilation).hadErrorContaining( 2486 "Failed to find a suitable getter for element \"getPrice\""); 2487 assertThat(compilation).hadWarningContaining( 2488 "Getter cannot be used: private visibility"); 2489 } 2490 2491 @Test testNameNormalization()2492 public void testNameNormalization() throws Exception { 2493 // getMPrice should correspond to a field named "mPrice" 2494 // mPrice should correspond to a field named "price" 2495 // isSold should correspond to a field named "sold" 2496 // mx should correspond to a field named "mx" 2497 Compilation compilation = compile( 2498 "@Document\n" 2499 + "public class Gift {\n" 2500 + " @Document.Namespace String namespace;\n" 2501 + " @Document.Id String id;\n" 2502 + " @Document.LongProperty\n" 2503 + " public int getMPrice() { return 0; }\n" 2504 + " public void setMPrice(int price) {}\n" 2505 + " @Document.LongProperty\n" 2506 + " public int mPrice;\n" 2507 + " @Document.BooleanProperty\n" 2508 + " public boolean isSold() { return false; }\n" 2509 + " public void setSold(boolean sold) {}\n" 2510 + " @Document.LongProperty\n" 2511 + " public int mx() { return 0; }\n" 2512 + " public void setMx(int x) {}\n" 2513 + "}\n"); 2514 assertThat(compilation).succeededWithoutWarnings(); 2515 2516 checkResultContains("Gift.java", 2517 "new AppSearchSchema.LongPropertyConfig.Builder(\"mPrice\")"); 2518 checkResultContains("Gift.java", 2519 "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")"); 2520 checkResultContains("Gift.java", 2521 "new AppSearchSchema.BooleanPropertyConfig.Builder(\"sold\")"); 2522 checkResultContains("Gift.java", 2523 "new AppSearchSchema.LongPropertyConfig.Builder(\"mx\")"); 2524 2525 checkResultContains("Gift.java", "document.setMPrice(getMPriceConv)"); 2526 checkResultContains("Gift.java", "document.mPrice = mPriceConv"); 2527 checkResultContains("Gift.java", "document.setSold(isSoldConv)"); 2528 checkResultContains("Gift.java", "document.setMx(mxConv)"); 2529 2530 checkResultContains("Gift.java", 2531 "builder.setPropertyLong(\"mPrice\", document.getMPrice())"); 2532 checkResultContains("Gift.java", 2533 "builder.setPropertyLong(\"price\", document.mPrice)"); 2534 checkResultContains("Gift.java", 2535 "builder.setPropertyBoolean(\"sold\", document.isSold())"); 2536 checkResultContains("Gift.java", 2537 "builder.setPropertyLong(\"mx\", document.mx())"); 2538 2539 checkEqualsGolden("Gift.java"); 2540 } 2541 2542 @Test testGetterWithParameterCannotBeUsed()2543 public void testGetterWithParameterCannotBeUsed() { 2544 Compilation compilation = compile( 2545 "@Document\n" 2546 + "public class Gift {\n" 2547 + " @Document.Namespace String namespace;\n" 2548 + " @Document.Id String id;\n" 2549 + " @Document.LongProperty\n" 2550 + " public int getPrice(int price) { return 0; }\n" 2551 + " public void setPrice(int price) {}\n" 2552 + "}\n"); 2553 assertThat(compilation).hadErrorContaining( 2554 "Failed to find a suitable getter for element \"getPrice\""); 2555 assertThat(compilation).hadWarningContaining( 2556 "Getter cannot be used: should take no parameters"); 2557 } 2558 2559 @Test testPrivateGetterCannotBeUsed()2560 public void testPrivateGetterCannotBeUsed() { 2561 Compilation compilation = compile( 2562 "@Document\n" 2563 + "public class Gift {\n" 2564 + " @Document.Namespace String namespace;\n" 2565 + " @Document.Id String id;\n" 2566 + " @Document.LongProperty\n" 2567 + " private int getPrice() { return 0; }\n" 2568 + " public void setPrice(int price) {}\n" 2569 + "}\n"); 2570 assertThat(compilation).hadErrorContaining( 2571 "Failed to find a suitable getter for element \"getPrice\""); 2572 assertThat(compilation).hadWarningContaining( 2573 "Getter cannot be used: private visibility"); 2574 } 2575 2576 @Test testOverloadedGetterIsOk()2577 public void testOverloadedGetterIsOk() throws Exception { 2578 // Overloaded getter should be ok because annotation processor will find the correct getter 2579 // that can be used. 2580 Compilation compilation = compile( 2581 "@Document\n" 2582 + "public class Gift {\n" 2583 + " @Document.Namespace String namespace;\n" 2584 + " @Document.Id String id;\n" 2585 + " public int getPrice(int price) { return 0; }\n" 2586 + " @Document.LongProperty\n" 2587 + " public int getPrice() { return 0; }\n" 2588 + " public void setPrice(int price) {}\n" 2589 + "}\n"); 2590 assertThat(compilation).succeededWithoutWarnings(); 2591 checkResultContains("Gift.java", 2592 "new AppSearchSchema.LongPropertyConfig.Builder(\"price\")"); 2593 checkResultContains("Gift.java", "document.setPrice(getPriceConv)"); 2594 checkResultContains("Gift.java", "document.getPrice()"); 2595 checkEqualsGolden("Gift.java"); 2596 } 2597 2598 @Test testGetterWithWrongReturnType()2599 public void testGetterWithWrongReturnType() { 2600 Compilation compilation = compile( 2601 "@Document\n" 2602 + "public class Gift {\n" 2603 + " @Document.Namespace String namespace;\n" 2604 + " @Document.Id String id;\n" 2605 + " @Document.StringProperty\n" 2606 + " public int getPrice() { return 0; }\n" 2607 + " public void setPrice(int price) {}\n" 2608 + "}\n"); 2609 assertThat(compilation).hadErrorContaining( 2610 "@StringProperty must only be placed on a getter/field of type or array or " 2611 + "collection of java.lang.String"); 2612 } 2613 testCyclicalSchema()2614 public void testCyclicalSchema() throws Exception { 2615 Compilation compilation = compile( 2616 "@Document\n" 2617 + "public class Gift {\n" 2618 + " @Document.Id String id;\n" 2619 + " @Document.Namespace String namespace;\n" 2620 + " @Document.DocumentProperty Letter letter;\n" 2621 + "}\n" 2622 + "\n" 2623 + "@Document\n" 2624 + "class Letter {\n" 2625 + " @Document.Id String id;\n" 2626 + " @Document.Namespace String namespace;\n" 2627 + " @Document.DocumentProperty Gift gift;\n" 2628 + "}\n"); 2629 2630 assertThat(compilation).succeededWithoutWarnings(); 2631 checkEqualsGolden("Gift.java"); 2632 2633 checkResultContains("Gift.java", 2634 "classSet.add(Letter.class);\n return classSet;"); 2635 checkResultContains("Letter.java", 2636 "classSet.add(Gift.class);\n return classSet;"); 2637 } 2638 2639 @Test testCreationByBuilder()2640 public void testCreationByBuilder() throws Exception { 2641 // Once @Document.BuilderProducer is found, AppSearch compiler will no longer consider other 2642 // creation method, so "create" will not be used. 2643 Compilation compilation = compile( 2644 "@Document\n" 2645 + "public interface Gift {\n" 2646 + " @Document.Namespace public String getNamespace();\n" 2647 + " @Document.Id public String getId();\n" 2648 + " @Document.LongProperty public int getPrice();\n" 2649 + " public static Gift create(String id, String namespace, int price) {\n" 2650 + " return new GiftImpl(id, namespace, price);\n" 2651 + " }\n" 2652 + " @Document.BuilderProducer static GiftBuilder getBuilder() {\n" 2653 + " return new GiftBuilder();\n" 2654 + " }\n" 2655 + "}\n" 2656 + "class GiftImpl implements Gift {\n" 2657 + " public GiftImpl(String id, String namespace, int price) {\n" 2658 + " this.id = id;\n" 2659 + " this.namespace = namespace;\n" 2660 + " this.price = price;\n" 2661 + " }\n" 2662 + " private String namespace;\n" 2663 + " private String id;\n" 2664 + " private int price;\n" 2665 + " public String getNamespace() { return namespace; }\n" 2666 + " public String getId() { return id; }\n" 2667 + " public int getPrice() { return price; }\n" 2668 + "}\n" 2669 + "class GiftBuilder {\n" 2670 + " private String namespace;\n" 2671 + " private String id;\n" 2672 + " private int price;\n" 2673 + " public GiftBuilder setNamespace(String namespace) {\n" 2674 + " this.namespace = namespace;\n" 2675 + " return this;\n" 2676 + " }\n" 2677 + " public GiftBuilder setId(String id) {\n" 2678 + " this.id = id;\n" 2679 + " return this;\n" 2680 + " }\n" 2681 + " public GiftBuilder setPrice(int price) {\n" 2682 + " this.price = price;\n" 2683 + " return this;\n" 2684 + " }\n" 2685 + " public Gift build() {\n" 2686 + " return new GiftImpl(this.id, this.namespace, this.price);\n" 2687 + " }\n" 2688 + "}\n"); 2689 assertThat(compilation).succeededWithoutWarnings(); 2690 checkResultContains("Gift.java", 2691 "GiftBuilder builder = Gift.getBuilder()"); 2692 checkResultContains("Gift.java", "builder.setNamespace(getNamespaceConv)"); 2693 checkResultContains("Gift.java", "builder.setId(getIdConv)"); 2694 checkResultContains("Gift.java", "builder.setPrice(getPriceConv)"); 2695 checkResultContains("Gift.java", "builder.build()"); 2696 checkEqualsGolden("Gift.java"); 2697 } 2698 2699 @Test testBuilderThatUsesGenerics()2700 public void testBuilderThatUsesGenerics() throws Exception { 2701 Compilation compilation = compile( 2702 "@Document\n" 2703 + "public class Gift {\n" 2704 + " @Document.Namespace private final String mNamespace;\n" 2705 + " @Document.Id private final String mId;\n" 2706 + " private Gift(String namespace, String id) {\n" 2707 + " mNamespace = namespace;\n" 2708 + " mId = id;\n" 2709 + " }\n" 2710 + " public String getNamespace() { return mNamespace; }\n" 2711 + " public String getId() { return mId; }\n" 2712 + " public static abstract class BaseBuilder<T> {\n" 2713 + " public final T build() { return buildInternal(false); }\n" 2714 + " // Give this a param to have zero methods with the signature\n" 2715 + " // () -> DocumentClass\n" 2716 + " protected abstract T buildInternal(boolean ignore);\n" 2717 + " }\n" 2718 + " @Document.BuilderProducer\n" 2719 + " public static class Builder extends BaseBuilder<Gift> {\n" 2720 + " private String mNamespace = \"\";\n" 2721 + " private String mId = \"\";\n" 2722 + " @Override\n" 2723 + " protected Gift buildInternal(boolean ignore) {\n" 2724 + " return new Gift(mNamespace, mId);\n" 2725 + " }\n" 2726 + " public Builder setNamespace(String namespace) {\n" 2727 + " mNamespace = namespace;\n" 2728 + " return this;\n" 2729 + " }\n" 2730 + " public Builder setId(String id) {\n" 2731 + " mId = id;\n" 2732 + " return this;\n" 2733 + " }\n" 2734 + " }\n" 2735 + "}"); 2736 assertThat(compilation).succeededWithoutWarnings(); 2737 checkResultContains("Gift.java", "Gift.Builder builder = new Gift.Builder()"); 2738 checkResultContains("Gift.java", "return builder.build()"); 2739 } 2740 2741 @Test testCreationByBuilderWithParameter()2742 public void testCreationByBuilderWithParameter() throws Exception { 2743 Compilation compilation = compile( 2744 "@Document\n" 2745 + "public interface Gift {\n" 2746 + " @Document.Namespace public String getNamespace();\n" 2747 + " @Document.Id public String getId();\n" 2748 + " @Document.LongProperty public int getPrice();\n" 2749 + " @Document.BuilderProducer static GiftBuilder getBuilder(int price) {\n" 2750 + " return new GiftBuilder().setPrice(price);\n" 2751 + " }\n" 2752 + "}\n" 2753 + "class GiftImpl implements Gift{\n" 2754 + " public GiftImpl(String id, String namespace, int price) {\n" 2755 + " this.id = id;\n" 2756 + " this.namespace = namespace;\n" 2757 + " this.price = price;\n" 2758 + " }\n" 2759 + " private String namespace;\n" 2760 + " private String id;\n" 2761 + " private int price;\n" 2762 + " public String getNamespace() { return namespace; }\n" 2763 + " public String getId() { return id; }\n" 2764 + " public int getPrice() { return price; }\n" 2765 + "}\n" 2766 + "class GiftBuilder {\n" 2767 + " private String namespace;\n" 2768 + " private String id;\n" 2769 + " private int price;\n" 2770 + " public GiftBuilder setNamespace(String namespace) {\n" 2771 + " this.namespace = namespace;\n" 2772 + " return this;\n" 2773 + " }\n" 2774 + " public GiftBuilder setId(String id) {\n" 2775 + " this.id = id;\n" 2776 + " return this;\n" 2777 + " }\n" 2778 + " public GiftBuilder setPrice(int price) {\n" 2779 + " this.price = price;\n" 2780 + " return this;\n" 2781 + " }\n" 2782 + " public Gift build() {\n" 2783 + " return new GiftImpl(this.id, this.namespace, this.price);\n" 2784 + " }\n" 2785 + "}\n"); 2786 assertThat(compilation).succeededWithoutWarnings(); 2787 checkResultContains("Gift.java", 2788 "GiftBuilder builder = Gift.getBuilder(getPriceConv)"); 2789 checkResultContains("Gift.java", "builder.setNamespace(getNamespaceConv)"); 2790 checkResultContains("Gift.java", "builder.setId(getIdConv)"); 2791 checkResultContains("Gift.java", "builder.build()"); 2792 checkEqualsGolden("Gift.java"); 2793 } 2794 2795 @Test testCreationByBuilderAnnotatingBuilderClass()2796 public void testCreationByBuilderAnnotatingBuilderClass() throws Exception { 2797 Compilation compilation = compile( 2798 "@Document\n" 2799 + "public interface Gift {\n" 2800 + " @Document.Namespace public String getNamespace();\n" 2801 + " @Document.Id public String getId();\n" 2802 + " @Document.LongProperty public int getPrice();\n" 2803 + " @Document.BuilderProducer\n" 2804 + " class GiftBuilder {\n" 2805 + " private String namespace;\n" 2806 + " private String id;\n" 2807 + " private int price;\n" 2808 + " public GiftBuilder setNamespace(String namespace) {\n" 2809 + " this.namespace = namespace;\n" 2810 + " return this;\n" 2811 + " }\n" 2812 + " public GiftBuilder setId(String id) {\n" 2813 + " this.id = id;\n" 2814 + " return this;\n" 2815 + " }\n" 2816 + " public GiftBuilder setPrice(int price) {\n" 2817 + " this.price = price;\n" 2818 + " return this;\n" 2819 + " }\n" 2820 + " public Gift build() {\n" 2821 + " return new GiftImpl(this.id, this.namespace, this.price);\n" 2822 + " }\n" 2823 + " }\n" 2824 + "}\n" 2825 + "class GiftImpl implements Gift {\n" 2826 + " public GiftImpl(String id, String namespace, int price) {\n" 2827 + " this.id = id;\n" 2828 + " this.namespace = namespace;\n" 2829 + " this.price = price;\n" 2830 + " }\n" 2831 + " private String namespace;\n" 2832 + " private String id;\n" 2833 + " private int price;\n" 2834 + " public String getNamespace() { return namespace; }\n" 2835 + " public String getId() { return id; }\n" 2836 + " public int getPrice() { return price; }\n" 2837 + "}\n"); 2838 assertThat(compilation).succeededWithoutWarnings(); 2839 checkResultContains("Gift.java", 2840 "Gift.GiftBuilder builder = new Gift.GiftBuilder()"); 2841 checkResultContains("Gift.java", "builder.setNamespace(getNamespaceConv)"); 2842 checkResultContains("Gift.java", "builder.setId(getIdConv)"); 2843 checkResultContains("Gift.java", "builder.setPrice(getPriceConv)"); 2844 checkResultContains("Gift.java", "builder.build()"); 2845 checkEqualsGolden("Gift.java"); 2846 } 2847 2848 @Test testCreationByBuilderWithParameterAnnotatingBuilderClass()2849 public void testCreationByBuilderWithParameterAnnotatingBuilderClass() throws Exception { 2850 Compilation compilation = compile( 2851 "@Document\n" 2852 + "public interface Gift {\n" 2853 + " @Document.Namespace public String getNamespace();\n" 2854 + " @Document.Id public String getId();\n" 2855 + " @Document.LongProperty public int getPrice();\n" 2856 + " @Document.BuilderProducer\n" 2857 + " class GiftBuilder {\n" 2858 + " private String namespace;\n" 2859 + " private String id;\n" 2860 + " private int price;\n" 2861 + " public GiftBuilder(int price) {\n" 2862 + " this.price = price;\n" 2863 + " }\n" 2864 + " public GiftBuilder setNamespace(String namespace) {\n" 2865 + " this.namespace = namespace;\n" 2866 + " return this;\n" 2867 + " }\n" 2868 + " public GiftBuilder setId(String id) {\n" 2869 + " this.id = id;\n" 2870 + " return this;\n" 2871 + " }\n" 2872 + " public Gift build() {\n" 2873 + " return new GiftImpl(this.id, this.namespace, this.price);\n" 2874 + " }\n" 2875 + " }\n" 2876 + "}\n" 2877 + "class GiftImpl implements Gift {\n" 2878 + " public GiftImpl(String id, String namespace, int price) {\n" 2879 + " this.id = id;\n" 2880 + " this.namespace = namespace;\n" 2881 + " this.price = price;\n" 2882 + " }\n" 2883 + " private String namespace;\n" 2884 + " private String id;\n" 2885 + " private int price;\n" 2886 + " public String getNamespace() { return namespace; }\n" 2887 + " public String getId() { return id; }\n" 2888 + " public int getPrice() { return price; }\n" 2889 + "}\n"); 2890 assertThat(compilation).succeededWithoutWarnings(); 2891 checkResultContains("Gift.java", 2892 "Gift.GiftBuilder builder = new Gift.GiftBuilder(getPriceConv)"); 2893 checkResultContains("Gift.java", "builder.setNamespace(getNamespaceConv)"); 2894 checkResultContains("Gift.java", "builder.setId(getIdConv)"); 2895 checkResultContains("Gift.java", "builder.build()"); 2896 checkEqualsGolden("Gift.java"); 2897 } 2898 2899 @Test testCreationByBuilderOnly()2900 public void testCreationByBuilderOnly() throws Exception { 2901 // Once a builder producer is provided, AppSearch will only use the builder pattern, even 2902 // if another creation method is available. 2903 Compilation compilation = compile( 2904 "@Document\n" 2905 + "public class Gift {\n" 2906 + " @Document.Namespace String namespace;\n" 2907 + " @Document.Id String id;\n" 2908 + " @Document.LongProperty int price;\n" 2909 + " @Document.BuilderProducer static GiftBuilder getBuilder() {\n" 2910 + " return new GiftBuilder();\n" 2911 + " }\n" 2912 + "}\n" 2913 + "class GiftBuilder {\n" 2914 + " private String namespace;\n" 2915 + " private String id;\n" 2916 + " private int price;\n" 2917 + " public GiftBuilder setNamespace(String namespace) {\n" 2918 + " this.namespace = namespace;\n" 2919 + " return this;\n" 2920 + " }\n" 2921 + " public GiftBuilder setId(String id) {\n" 2922 + " this.id = id;\n" 2923 + " return this;\n" 2924 + " }\n" 2925 + " public GiftBuilder setPrice(int price) {\n" 2926 + " this.price = price;\n" 2927 + " return this;\n" 2928 + " }\n" 2929 + " public Gift build() {\n" 2930 + " return new Gift();\n" 2931 + " }\n" 2932 + "}\n"); 2933 assertThat(compilation).succeededWithoutWarnings(); 2934 checkResultContains("Gift.java", "GiftBuilder builder = Gift.getBuilder()"); 2935 checkResultContains("Gift.java", "builder.setNamespace(namespaceConv)"); 2936 checkResultContains("Gift.java", "builder.setId(idConv)"); 2937 checkResultContains("Gift.java", "builder.setPrice(priceConv)"); 2938 checkResultContains("Gift.java", "builder.build()"); 2939 checkEqualsGolden("Gift.java"); 2940 } 2941 2942 @Test testCreationByBuilderWithAutoValue()2943 public void testCreationByBuilderWithAutoValue() throws IOException { 2944 Compilation compilation = compile( 2945 "import com.google.auto.value.AutoValue;\n" 2946 + "import com.google.auto.value.AutoValue.*;\n" 2947 + "@Document\n" 2948 + "@AutoValue\n" 2949 + "public abstract class Gift {\n" 2950 + " @CopyAnnotations @Document.Id abstract String id();\n" 2951 + " @CopyAnnotations @Document.Namespace abstract String namespace();\n" 2952 + " @CopyAnnotations @Document.LongProperty abstract int price();\n" 2953 + " @Document.BuilderProducer static GiftBuilder getBuilder() {\n" 2954 + " return new GiftBuilder();\n" 2955 + " }\n" 2956 + "}\n" 2957 + "class GiftBuilder {\n" 2958 + " private String namespace;\n" 2959 + " private String id;\n" 2960 + " private int price;\n" 2961 + " public GiftBuilder setNamespace(String namespace) {\n" 2962 + " this.namespace = namespace;\n" 2963 + " return this;\n" 2964 + " }\n" 2965 + " public GiftBuilder setId(String id) {\n" 2966 + " this.id = id;\n" 2967 + " return this;\n" 2968 + " }\n" 2969 + " public GiftBuilder setPrice(int price) {\n" 2970 + " this.price = price;\n" 2971 + " return this;\n" 2972 + " }\n" 2973 + " public Gift build() {\n" 2974 + " return new AutoValue_Gift(id, namespace, price);\n" 2975 + " }\n" 2976 + "}\n"); 2977 2978 assertThat(compilation).succeededWithoutWarnings(); 2979 checkResultContains("AutoValue_Gift.java", "GiftBuilder builder = Gift.getBuilder()"); 2980 checkResultContains("AutoValue_Gift.java", "builder.setNamespace(namespaceConv)"); 2981 checkResultContains("AutoValue_Gift.java", "builder.setId(idConv)"); 2982 checkResultContains("AutoValue_Gift.java", "builder.setPrice(priceConv)"); 2983 checkResultContains("AutoValue_Gift.java", "builder.build()"); 2984 checkEqualsGolden("AutoValue_Gift.java"); 2985 } 2986 2987 @Test testCreationByBuilderErrors()2988 public void testCreationByBuilderErrors() { 2989 // Cannot have multiple builder producer 2990 Compilation compilation = compile( 2991 "@Document\n" 2992 + "public interface Gift {\n" 2993 + " @Document.Namespace public String getNamespace();\n" 2994 + " @Document.Id public String getId();\n" 2995 + " @Document.BuilderProducer static GiftBuilder getBuilder1() {\n" 2996 + " return new GiftBuilder();\n" 2997 + " }\n" 2998 + " @Document.BuilderProducer static GiftBuilder getBuilder2() {\n" 2999 + " return new GiftBuilder();\n" 3000 + " }\n" 3001 + "}\n" 3002 + "class GiftImpl implements Gift{\n" 3003 + " public GiftImpl(String id, String namespace) {\n" 3004 + " this.id = id;\n" 3005 + " this.namespace = namespace;\n" 3006 + " }\n" 3007 + " private String namespace;\n" 3008 + " private String id;\n" 3009 + " public String getNamespace() { return namespace; }\n" 3010 + " public String getId() { return id; }\n" 3011 + "}\n" 3012 + "class GiftBuilder {\n" 3013 + " private String namespace;\n" 3014 + " private String id;\n" 3015 + " public GiftBuilder setNamespace(String namespace) {\n" 3016 + " this.namespace = namespace;\n" 3017 + " return this;\n" 3018 + " }\n" 3019 + " public GiftBuilder setId(String id) {\n" 3020 + " this.id = id;\n" 3021 + " return this;\n" 3022 + " }\n" 3023 + " public Gift build() {\n" 3024 + " return new GiftImpl(this.id, this.namespace);\n" 3025 + " }\n" 3026 + "}\n"); 3027 assertThat(compilation).hadErrorContaining("Found duplicated builder producer"); 3028 3029 // Builder producer method must be static 3030 compilation = compile( 3031 "@Document\n" 3032 + "public interface Gift {\n" 3033 + " @Document.Namespace public String getNamespace();\n" 3034 + " @Document.Id public String getId();\n" 3035 + " @Document.BuilderProducer GiftBuilder getBuilder() {\n" 3036 + " return new GiftBuilder();\n" 3037 + " }\n" 3038 + "}\n" 3039 + "class GiftImpl implements Gift{\n" 3040 + " public GiftImpl(String id, String namespace) {\n" 3041 + " this.id = id;\n" 3042 + " this.namespace = namespace;\n" 3043 + " }\n" 3044 + " private String namespace;\n" 3045 + " private String id;\n" 3046 + " public String getNamespace() { return namespace; }\n" 3047 + " public String getId() { return id; }\n" 3048 + "}\n" 3049 + "class GiftBuilder {\n" 3050 + " private String namespace;\n" 3051 + " private String id;\n" 3052 + " public GiftBuilder setNamespace(String namespace) {\n" 3053 + " this.namespace = namespace;\n" 3054 + " return this;\n" 3055 + " }\n" 3056 + " public GiftBuilder setId(String id) {\n" 3057 + " this.id = id;\n" 3058 + " return this;\n" 3059 + " }\n" 3060 + " public Gift build() {\n" 3061 + " return new GiftImpl(this.id, this.namespace);\n" 3062 + " }\n" 3063 + "}\n"); 3064 assertThat(compilation).hadErrorContaining("Builder producer must be static"); 3065 3066 // Builder producer class must be static 3067 compilation = compile( 3068 "@Document\n" 3069 + "public class Gift {\n" 3070 + " public Gift(String id, String namespace) {\n" 3071 + " this.id = id;\n" 3072 + " this.namespace = namespace;\n" 3073 + " }\n" 3074 + " @Document.Namespace public String namespace;\n" 3075 + " @Document.Id public String id;\n" 3076 + " @Document.BuilderProducer\n" 3077 + " class Builder {\n" 3078 + " private String namespace;\n" 3079 + " private String id;\n" 3080 + " public Builder setNamespace(String namespace) {\n" 3081 + " this.namespace = namespace;\n" 3082 + " return this;\n" 3083 + " }\n" 3084 + " public Builder setId(String id) {\n" 3085 + " this.id = id;\n" 3086 + " return this;\n" 3087 + " }\n" 3088 + " public Gift build() {\n" 3089 + " return new Gift(this.id, this.namespace);\n" 3090 + " }\n" 3091 + " }\n" 3092 + "}\n"); 3093 assertThat(compilation).hadErrorContaining("Builder producer must be static"); 3094 3095 // Builder producer method cannot be private 3096 compilation = compile( 3097 "@Document\n" 3098 + "public interface Gift {\n" 3099 + " @Document.Namespace public String getNamespace();\n" 3100 + " @Document.Id public String getId();\n" 3101 + " @Document.BuilderProducer private static GiftBuilder getBuilder() {\n" 3102 + " return new GiftBuilder();\n" 3103 + " }\n" 3104 + "}\n" 3105 + "class GiftImpl implements Gift{\n" 3106 + " public GiftImpl(String id, String namespace) {\n" 3107 + " this.id = id;\n" 3108 + " this.namespace = namespace;\n" 3109 + " }\n" 3110 + " private String namespace;\n" 3111 + " private String id;\n" 3112 + " public String getNamespace() { return namespace; }\n" 3113 + " public String getId() { return id; }\n" 3114 + "}\n" 3115 + "class GiftBuilder {\n" 3116 + " private String namespace;\n" 3117 + " private String id;\n" 3118 + " public GiftBuilder setNamespace(String namespace) {\n" 3119 + " this.namespace = namespace;\n" 3120 + " return this;\n" 3121 + " }\n" 3122 + " public GiftBuilder setId(String id) {\n" 3123 + " this.id = id;\n" 3124 + " return this;\n" 3125 + " }\n" 3126 + " public Gift build() {\n" 3127 + " return new GiftImpl(this.id, this.namespace);\n" 3128 + " }\n" 3129 + "}\n"); 3130 assertThat(compilation).hadErrorContaining("Builder producer cannot be private"); 3131 3132 // Builder producer class cannot be private 3133 compilation = compile( 3134 "@Document\n" 3135 + "public class Gift {\n" 3136 + " public Gift(String id, String namespace) {\n" 3137 + " this.id = id;\n" 3138 + " this.namespace = namespace;\n" 3139 + " }\n" 3140 + " @Document.Namespace public String namespace;\n" 3141 + " @Document.Id public String id;\n" 3142 + " @Document.BuilderProducer\n" 3143 + " private static class Builder {\n" 3144 + " private String namespace;\n" 3145 + " private String id;\n" 3146 + " public Builder setNamespace(String namespace) {\n" 3147 + " this.namespace = namespace;\n" 3148 + " return this;\n" 3149 + " }\n" 3150 + " public Builder setId(String id) {\n" 3151 + " this.id = id;\n" 3152 + " return this;\n" 3153 + " }\n" 3154 + " public Gift build() {\n" 3155 + " return new Gift(this.id, this.namespace);\n" 3156 + " }\n" 3157 + " }\n" 3158 + "}\n"); 3159 assertThat(compilation).hadErrorContaining("Builder producer cannot be private"); 3160 3161 // Builder producer must be a method or a class. 3162 compilation = compile( 3163 "@Document\n" 3164 + "public class Gift {\n" 3165 + " @Document.Namespace public String namespace;\n" 3166 + " @Document.Id public String id;\n" 3167 + " @Document.BuilderProducer int getBuilder;\n" 3168 + "}\n"); 3169 assertThat(compilation).hadErrorContaining( 3170 "annotation interface not applicable to this kind of declaration"); 3171 3172 // Missing a setter in the builder 3173 compilation = compile( 3174 "@Document\n" 3175 + "public class Gift {\n" 3176 + " @Document.Namespace String namespace;\n" 3177 + " @Document.Id String id;\n" 3178 + " @Document.LongProperty int price;\n" 3179 + " @Document.BuilderProducer static GiftBuilder getBuilder() {\n" 3180 + " return new GiftBuilder();\n" 3181 + " }\n" 3182 + "}\n" 3183 + "class GiftBuilder {\n" 3184 + " private String namespace;\n" 3185 + " private String id;\n" 3186 + " private int price;\n" 3187 + " public GiftBuilder setNamespace(String namespace) {\n" 3188 + " this.namespace = namespace;\n" 3189 + " return this;\n" 3190 + " }\n" 3191 + " public GiftBuilder setId(String id) {\n" 3192 + " this.id = id;\n" 3193 + " return this;\n" 3194 + " }\n" 3195 + " public Gift build() {\n" 3196 + " return new Gift();\n" 3197 + " }\n" 3198 + "}\n"); 3199 assertThat(compilation).hadErrorContaining( 3200 "Could not find a suitable builder producer for " 3201 + "\"com.example.appsearch.Gift\" that covers properties: [price]. " 3202 + "See the warnings for more details."); 3203 assertThat(compilation).hadWarningContaining( 3204 "Could not find any of the setter(s): " 3205 + "[public] void price(int)|" 3206 + "[public] void setPrice(int)"); 3207 assertThat(compilation).hadWarningContaining( 3208 "Cannot use this creation method to construct the class: " 3209 + "\"com.example.appsearch.Gift\". " 3210 + "No parameters for the properties: [price]"); 3211 } 3212 3213 @Test testAbstractConstructor()3214 public void testAbstractConstructor() { 3215 Compilation compilation = compile( 3216 "@Document\n" 3217 + "public abstract class Gift {\n" 3218 + " @Document.Namespace String namespace;\n" 3219 + " @Document.Id String id;\n" 3220 + " public Gift() {}\n" 3221 + "}\n"); 3222 assertThat(compilation).hadErrorContaining("Could not find a suitable creation method"); 3223 assertThat(compilation).hadWarningContaining( 3224 "Method cannot be used to create a document class: abstract constructor"); 3225 } 3226 3227 @Test testDocumentClassesWithDuplicatedNames()3228 public void testDocumentClassesWithDuplicatedNames() throws Exception { 3229 Compilation compilation = compile( 3230 "@Document(name=\"A\")\n" 3231 + "class MyClass1 {\n" 3232 + " @Document.Namespace String namespace;\n" 3233 + " @Document.Id String id;\n" 3234 + "}\n" 3235 + "@Document(name=\"A\")\n" 3236 + "class MyClass2 {\n" 3237 + " @Document.Namespace String namespace;\n" 3238 + " @Document.Id String id;\n" 3239 + "}\n" 3240 + "@Document(name=\"B\")\n" 3241 + "class MyClass3 {\n" 3242 + " @Document.Namespace String namespace;\n" 3243 + " @Document.Id String id;\n" 3244 + "}\n"); 3245 assertThat(compilation).succeededWithoutWarnings(); 3246 checkDocumentMapEqualsGolden(/* roundIndex= */0); 3247 } 3248 3249 @Test testStringSerializer()3250 public void testStringSerializer() throws Exception { 3251 Compilation compilation = compile( 3252 "import androidx.appsearch.app.StringSerializer;\n" 3253 + "import java.net.URL;\n" 3254 + "import java.net.MalformedURLException;\n" 3255 + "import java.util.List;\n" 3256 + "@Document\n" 3257 + "class Gift {\n" 3258 + " @Document.Id String mId;\n" 3259 + " @Document.Namespace String mNamespace;\n" 3260 + " @Document.StringProperty(\n" 3261 + " serializer = UrlAsStringSerializer.class\n" 3262 + " )\n" 3263 + " URL mUrl;\n" 3264 + " @Document.StringProperty(\n" 3265 + " serializer = UrlAsStringSerializer.class\n" 3266 + " )\n" 3267 + " List<URL> mUrlList;\n" 3268 + " @Document.StringProperty(\n" 3269 + " serializer = UrlAsStringSerializer.class\n" 3270 + " )\n" 3271 + " URL[] mUrlArr;\n" 3272 + " static class UrlAsStringSerializer \n" 3273 + " implements StringSerializer<URL> {\n" 3274 + " @Override\n" 3275 + " public String serialize(URL url) {\n" 3276 + " return url.toString();\n" 3277 + " }\n" 3278 + " @Override\n" 3279 + " public URL deserialize(String string) {\n" 3280 + " try {\n" 3281 + " return new URL(string);\n" 3282 + " } catch (MalformedURLException e) {\n" 3283 + " return null;\n" 3284 + " }\n" 3285 + " }\n" 3286 + " }\n" 3287 + "}" 3288 ); 3289 assertThat(compilation).succeededWithoutWarnings(); 3290 checkEqualsGolden("Gift.java"); 3291 checkResultContains( 3292 "Gift.java", 3293 "Gift.UrlAsStringSerializer serializer = new Gift.UrlAsStringSerializer()"); 3294 checkResultContains("Gift.java", "String mUrlConv = serializer.serialize(mUrlCopy)"); 3295 checkResultContains( 3296 "Gift.java", "mUrlConv = new Gift.UrlAsStringSerializer().deserialize(mUrlCopy)"); 3297 checkResultContains("Gift.java", "mUrlListConv[i++] = serializer.serialize(item)"); 3298 checkResultContains("Gift.java", "URL elem = serializer.deserialize(mUrlListCopy[i])"); 3299 checkResultContains("Gift.java", "mUrlArrConv[i] = serializer.serialize(mUrlArrCopy[i])"); 3300 checkResultContains("Gift.java", "URL elem = serializer.deserialize(mUrlArrCopy[i])"); 3301 } 3302 3303 @Test testLongSerializer()3304 public void testLongSerializer() throws Exception { 3305 Compilation compilation = compile( 3306 "import androidx.appsearch.app.LongSerializer;\n" 3307 + "import java.util.Arrays;\n" 3308 + "import java.util.List;\n" 3309 + "@Document\n" 3310 + "class Gift {\n" 3311 + " @Document.Id String mId;\n" 3312 + " @Document.Namespace String mNamespace;\n" 3313 + " @Document.LongProperty(\n" 3314 + " serializer = PricePointAsOrdinalSerializer.class\n" 3315 + " )\n" 3316 + " PricePoint mPricePoint;\n" 3317 + " @Document.LongProperty(\n" 3318 + " serializer = PricePointAsOrdinalSerializer.class\n" 3319 + " )\n" 3320 + " List<PricePoint> mPricePointList;\n" 3321 + " @Document.LongProperty(\n" 3322 + " serializer = PricePointAsOrdinalSerializer.class\n" 3323 + " )\n" 3324 + " PricePoint[] mPricePointArr;\n" 3325 + " enum PricePoint { LOW, MID, HIGH }\n" 3326 + " static class PricePointAsOrdinalSerializer \n" 3327 + " implements LongSerializer<PricePoint> {\n" 3328 + " @Override\n" 3329 + " public long serialize(PricePoint pricePoint) {\n" 3330 + " return pricePoint.ordinal();\n" 3331 + " }\n" 3332 + " @Override\n" 3333 + " public PricePoint deserialize(long l) {\n" 3334 + " return Arrays.stream(PricePoint.values())\n" 3335 + " .filter(pp -> pp.ordinal() == l)\n" 3336 + " .findFirst()\n" 3337 + " .orElse(null);\n" 3338 + " }\n" 3339 + " }\n" 3340 + "}" 3341 ); 3342 assertThat(compilation).succeededWithoutWarnings(); 3343 checkEqualsGolden("Gift.java"); 3344 checkResultContains( 3345 "Gift.java", 3346 "Gift.PricePointAsOrdinalSerializer serializer = " 3347 + "new Gift.PricePointAsOrdinalSerializer()"); 3348 checkResultContains( 3349 "Gift.java", 3350 "mPricePointConv = " 3351 + "new Gift.PricePointAsOrdinalSerializer().deserialize(mPricePointCopy)"); 3352 checkResultContains( 3353 "Gift.java", "long mPricePointConv = serializer.serialize(mPricePointCopy)"); 3354 checkResultContains( 3355 "Gift.java", 3356 "Gift.PricePoint elem = serializer.deserialize(mPricePointListCopy[i])"); 3357 checkResultContains("Gift.java", "mPricePointListConv[i++] = serializer.serialize(item)"); 3358 checkResultContains( 3359 "Gift.java", "mPricePointArrConv[i] = serializer.serialize(mPricePointArrCopy[i])"); 3360 checkResultContains( 3361 "Gift.java", 3362 "Gift.PricePoint elem = serializer.deserialize(mPricePointArrCopy[i])"); 3363 } 3364 3365 @Test testSerializerWithoutDefaultConstructor()3366 public void testSerializerWithoutDefaultConstructor() { 3367 Compilation compilation = compile( 3368 "import androidx.appsearch.app.LongSerializer;\n" 3369 + "import java.time.Instant;\n" 3370 + "@Document\n" 3371 + "class Gift {\n" 3372 + " @Document.Id\n" 3373 + " String mId = null;\n" 3374 + " @Document.Namespace\n" 3375 + " String mNamespace = null;\n" 3376 + " @Document.LongProperty(\n" 3377 + " serializer = InstantAsEpochMillisSerializer.class\n" 3378 + " )\n" 3379 + " Instant mPurchaseTimeStamp = null;\n" 3380 + " final static class InstantAsEpochMillisSerializer \n" 3381 + " implements LongSerializer<Instant> {\n" 3382 + " InstantAsEpochMillisSerializer(boolean someParam) {}\n" 3383 + " @Override\n" 3384 + " public long serialize(Instant instant) {\n" 3385 + " return instant.toEpochMilli();\n" 3386 + " }\n" 3387 + " @Override\n" 3388 + " public Instant deserialize(long l) {\n" 3389 + " return Instant.ofEpochMilli(l);\n" 3390 + " }\n" 3391 + " }\n" 3392 + "}" 3393 ); 3394 assertThat(compilation).hadErrorContaining( 3395 "Serializer com.example.appsearch.Gift.InstantAsEpochMillisSerializer must have a " 3396 + "zero-param constructor"); 3397 } 3398 3399 @Test testSerializerWithPrivateDefaultConstructor()3400 public void testSerializerWithPrivateDefaultConstructor() { 3401 Compilation compilation = compile( 3402 "import androidx.appsearch.app.LongSerializer;\n" 3403 + "import java.time.Instant;\n" 3404 + "@Document\n" 3405 + "class Gift {\n" 3406 + " @Document.Id\n" 3407 + " String mId = null;\n" 3408 + " @Document.Namespace\n" 3409 + " String mNamespace = null;\n" 3410 + " @Document.LongProperty(\n" 3411 + " serializer = InstantAsEpochMillisSerializer.class\n" 3412 + " )\n" 3413 + " Instant mPurchaseTimeStamp = null;\n" 3414 + " final static class InstantAsEpochMillisSerializer \n" 3415 + " implements LongSerializer<Instant> {\n" 3416 + " private InstantAsEpochMillisSerializer() {}\n" 3417 + " @Override\n" 3418 + " public long serialize(Instant instant) {\n" 3419 + " return instant.toEpochMilli();\n" 3420 + " }\n" 3421 + " @Override\n" 3422 + " public Instant deserialize(long l) {\n" 3423 + " return Instant.ofEpochMilli(l);\n" 3424 + " }\n" 3425 + " }\n" 3426 + "}" 3427 ); 3428 assertThat(compilation).hadErrorContaining( 3429 "The zero-param constructor of serializer " 3430 + "com.example.appsearch.Gift.InstantAsEpochMillisSerializer must not " 3431 + "be private"); 3432 } 3433 3434 @Test testPropertyTypeDoesNotMatchSerializer()3435 public void testPropertyTypeDoesNotMatchSerializer() { 3436 Compilation compilation = compile( 3437 "import androidx.appsearch.app.StringSerializer;\n" 3438 + "import java.net.MalformedURLException;\n" 3439 + "import java.net.URL;\n" 3440 + "@Document\n" 3441 + "class Gift {\n" 3442 + " @Document.Id\n" 3443 + " String mId = null;\n" 3444 + " @Document.Namespace\n" 3445 + " String mNamespace = null;\n" 3446 + " @Document.StringProperty(serializer = UrlAsStringSerializer.class)\n" 3447 + " int mProductUrl = null;\n" 3448 + " final static class UrlAsStringSerializer\n" 3449 + " implements StringSerializer<URL> {\n" 3450 + " @Override\n" 3451 + " public String serialize(URL url) {\n" 3452 + " return url.toString();\n" 3453 + " }\n" 3454 + " @Override\n" 3455 + " public URL deserialize(String string) {\n" 3456 + " try {\n" 3457 + " return new URL(string);\n" 3458 + " } catch (MalformedURLException e) {\n" 3459 + " return null;\n" 3460 + " }\n" 3461 + " }\n" 3462 + " }\n" 3463 + "}" 3464 ); 3465 assertThat(compilation).hadErrorContaining( 3466 "@StringProperty with serializer = UrlAsStringSerializer must only be placed on a " 3467 + "getter/field of type or array or collection of java.net.URL"); 3468 } 3469 3470 @Test testPropertyNamedAsDocumentClassMap()3471 public void testPropertyNamedAsDocumentClassMap() throws Exception { 3472 Compilation compilation = compile( 3473 "@Document\n" 3474 + "public class Gift {\n" 3475 + " @Document.Namespace String namespace;\n" 3476 + " @Document.Id String id;\n" 3477 + " @Document.LongProperty int documentClassMap;\n" 3478 + "}\n"); 3479 assertThat(compilation).succeededWithoutWarnings(); 3480 checkResultContains("Gift.java", 3481 "int documentClassMapConv = (int) genericDoc.getPropertyLong" 3482 + "(\"documentClassMap\")"); 3483 checkResultContains("Gift.java", "document.documentClassMap = documentClassMapConv"); 3484 checkEqualsGolden("Gift.java"); 3485 } 3486 3487 @Test testGeneratedCodeRestrictedToLibrary()3488 public void testGeneratedCodeRestrictedToLibrary() throws Exception { 3489 Compilation compilation = compile( 3490 /* classSimpleName=*/"Gift", 3491 "@Document\n" 3492 + "public class Gift {\n" 3493 + " @Document.Namespace String namespace;\n" 3494 + " @Document.Id String id;\n" 3495 + "}\n", 3496 /* restrictGeneratedCodeToLibrary= */true); 3497 assertThat(compilation).succeededWithoutWarnings(); 3498 checkResultContains("Gift.java", "@RestrictTo(RestrictTo.Scope.LIBRARY)"); 3499 checkEqualsGolden("Gift.java"); 3500 } 3501 3502 @Test testEmbeddingFields()3503 public void testEmbeddingFields() throws Exception { 3504 Compilation compilation = compile( 3505 "import java.util.*;\n" 3506 + "import androidx.appsearch.app.EmbeddingVector;\n" 3507 + "@Document\n" 3508 + "public class Gift {\n" 3509 + " @Document.Namespace String namespace;\n" 3510 + " @Document.Id String id;\n" 3511 + " @Document.StringProperty String name;\n" 3512 // Embedding properties 3513 + " @EmbeddingProperty EmbeddingVector defaultIndexNone;\n" 3514 + " @EmbeddingProperty(indexingType=0) EmbeddingVector indexNone;\n" 3515 + " @EmbeddingProperty(indexingType=1) EmbeddingVector vec;\n" 3516 + " @EmbeddingProperty(indexingType=1) List<EmbeddingVector> listVec;\n" 3517 + " @EmbeddingProperty(indexingType=1)" 3518 + " Collection<EmbeddingVector> collectVec;\n" 3519 + " @EmbeddingProperty(indexingType=1) EmbeddingVector[] arrVec;\n" 3520 + "}\n"); 3521 3522 assertThat(compilation).succeededWithoutWarnings(); 3523 checkResultContains("Gift.java", 3524 "new AppSearchSchema.EmbeddingPropertyConfig.Builder"); 3525 checkResultContains("Gift.java", 3526 "AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_SIMILARITY"); 3527 checkResultContains("Gift.java", 3528 "AppSearchSchema.EmbeddingPropertyConfig.INDEXING_TYPE_NONE"); 3529 checkResultContains("Gift.java", 3530 "EmbeddingVector"); 3531 checkEqualsGolden("Gift.java"); 3532 } 3533 3534 @Test testQuantizedEmbeddingFields()3535 public void testQuantizedEmbeddingFields() throws Exception { 3536 Compilation compilation = compile( 3537 "import java.util.*;\n" 3538 + "import androidx.appsearch.app.EmbeddingVector;\n" 3539 + "@Document\n" 3540 + "public class Gift {\n" 3541 + " @Document.Namespace String namespace;\n" 3542 + " @Document.Id String id;\n" 3543 + " @Document.StringProperty String name;\n" 3544 + " @EmbeddingProperty(indexingType=1)" 3545 + " EmbeddingVector defaultUnquantized;\n" 3546 + " @EmbeddingProperty(indexingType=1, quantizationType=0)" 3547 + " EmbeddingVector unquantized;\n" 3548 + " @EmbeddingProperty(indexingType=1, quantizationType=1)" 3549 + " EmbeddingVector quantized;\n" 3550 + "}\n"); 3551 3552 assertThat(compilation).succeededWithoutWarnings(); 3553 checkResultContains("Gift.java", 3554 "AppSearchSchema.EmbeddingPropertyConfig.QUANTIZATION_TYPE_NONE"); 3555 checkResultContains("Gift.java", 3556 "AppSearchSchema.EmbeddingPropertyConfig.QUANTIZATION_TYPE_8_BIT"); 3557 checkEqualsGolden("Gift.java"); 3558 } 3559 3560 @Test testBlobHandleFields()3561 public void testBlobHandleFields() throws Exception { 3562 Compilation compilation = compile( 3563 "import java.util.*;\n" 3564 + "import androidx.appsearch.app.AppSearchBlobHandle;\n" 3565 + "@Document\n" 3566 + "public class Gift {\n" 3567 + " @Document.Namespace String namespace;\n" 3568 + " @Document.Id String id;\n" 3569 + " @Document.StringProperty String name;\n" 3570 // AppSearchBlobHandle properties 3571 + " @BlobHandleProperty AppSearchBlobHandle blob;\n" 3572 + " @BlobHandleProperty Collection<AppSearchBlobHandle> collectBlob;\n" 3573 + " @BlobHandleProperty AppSearchBlobHandle[] arrBlob;\n" 3574 + "}\n"); 3575 3576 assertThat(compilation).succeededWithoutWarnings(); 3577 checkResultContains("Gift.java", 3578 "new AppSearchSchema.BlobHandlePropertyConfig.Builder"); 3579 checkResultContains("Gift.java", "AppSearchBlobHandle"); 3580 checkEqualsGolden("Gift.java"); 3581 } 3582 3583 @Test testKotlinNullability()3584 public void testKotlinNullability() throws Exception { 3585 compileKotlin(""" 3586 @Document 3587 data class KotlinGift( 3588 @Document.Namespace val namespace: String, 3589 @Document.Id val id: String, 3590 @Document.StringProperty val nonNullList: List<String>, 3591 @Document.StringProperty val nullableList: List<String>?, 3592 @Document.BooleanProperty val nonNullBoolean: Boolean, 3593 @Document.BooleanProperty val nullableBoolean: Boolean?, 3594 ) {} 3595 """); 3596 3597 checkEqualsGolden("KotlinGift.java"); 3598 checkResultContains("KotlinGift.java", 3599 "List<String> nonNullListConv = Collections.emptyList();"); 3600 checkResultContains("KotlinGift.java", 3601 "List<String> nullableListConv = null;"); 3602 checkResultContains("KotlinGift.java", 3603 "boolean nonNullBooleanConv = genericDoc.getPropertyBoolean(\"nonNullBoolean\");"); 3604 checkResultContains("KotlinGift.java", 3605 "Boolean nullableBooleanConv = null;"); 3606 } 3607 3608 @Test testKotlinNullability_nullabilityLists()3609 public void testKotlinNullability_nullabilityLists() throws Exception { 3610 compileKotlin(""" 3611 import androidx.appsearch.app.EmbeddingVector 3612 @Document 3613 data class Gift { 3614 @Document.Namespace String namespace 3615 @Document.Id String id 3616 } 3617 @Document 3618 data class KotlinGift( 3619 @Document.Namespace val namespace: String, 3620 @Document.Id val id: String, 3621 @Document.StringProperty val nonNullStrings: List<String>, 3622 @Document.StringProperty val nullableStrings: List<String>?, 3623 @Document.LongProperty val nonNullLongs: List<Long>, 3624 @Document.LongProperty val nullableLongs: List<Long>?, 3625 @Document.BooleanProperty val nonNullBooleans: List<Boolean>, 3626 @Document.BooleanProperty val nullableBooleans: List<Boolean>?, 3627 @Document.DocumentProperty val nonNullCustomTypes: List<Gift>, 3628 @Document.DocumentProperty val nullableCustomTypes: List<Gift>?, 3629 @Document.EmbeddingProperty val nonNullEmbeddings: List<EmbeddingVector>, 3630 @Document.EmbeddingProperty val nullableEmbeddings: List<EmbeddingVector>?, 3631 ) {} 3632 """); 3633 3634 checkEqualsGolden("KotlinGift.java"); 3635 3636 checkResultContains("KotlinGift.java", 3637 "List<String> nonNullStringsConv = Collections.emptyList();"); 3638 checkResultContains("KotlinGift.java", 3639 "List<String> nullableStringsConv = null;"); 3640 checkResultContains("KotlinGift.java", 3641 "List<Long> nonNullLongsConv = Collections.emptyList();"); 3642 checkResultContains("KotlinGift.java", 3643 "List<Long> nullableLongsConv = null;"); 3644 checkResultContains("KotlinGift.java", 3645 "List<Boolean> nonNullBooleansConv = Collections.emptyList();"); 3646 checkResultContains("KotlinGift.java", 3647 "List<Boolean> nullableBooleansConv = null;"); 3648 checkResultContains("KotlinGift.java", 3649 "List<Gift> nonNullCustomTypesConv = Collections.emptyList();"); 3650 checkResultContains("KotlinGift.java", 3651 "List<Gift> nullableCustomTypesConv = null;"); 3652 checkResultContains("KotlinGift.java", 3653 "List<EmbeddingVector> nonNullEmbeddingsConv = Collections.emptyList();"); 3654 checkResultContains("KotlinGift.java", 3655 "List<EmbeddingVector> nullableEmbeddingsConv = null;"); 3656 } 3657 compileKotlin(String classBody)3658 private void compileKotlin(String classBody) throws IOException { 3659 String src = "package com.example.appsearch\n" 3660 + "import androidx.appsearch.annotation.Document\n" 3661 + "import androidx.appsearch.annotation.Document.*\n"; 3662 3663 Source kotlinSource = Source.Companion.kotlin("KotlinGift.kt", src + classBody); 3664 // We're compiling kotlin a bit differently, we need a fresh folder here 3665 File kotlinCompilationDir = mTemporaryFolder.newFolder("kt"); 3666 TestKotlinCompilerKt.compile( 3667 kotlinCompilationDir, 3668 new TestCompilationArguments( 3669 ImmutableList.of(kotlinSource), 3670 /* classpath= */ ImmutableList.of(), 3671 /* inheritClasspath= */ true, 3672 /* javacArguments= */ ImmutableList.of(), 3673 /* kotlincArguments= */ ImmutableList.of("-language-version=1.9", 3674 "-api-version=1.9"), 3675 /* kaptProcessors= */ ImmutableList.of(new AppSearchCompiler()), 3676 /* symbolProcessorProviders= */ ImmutableList.of(), 3677 /* processorOptions= */ 3678 ImmutableMap.of( 3679 "AppSearchCompiler.OutputDir", mGenFilesDir.getAbsolutePath(), 3680 "AppSearchCompiler.RestrictGeneratedCodeToLib", "false") 3681 )); 3682 } 3683 compile(String classBody)3684 private Compilation compile(String classBody) { 3685 return compile("Gift", classBody, /* restrictGeneratedCodeToLibrary= */false); 3686 } 3687 compile( String classSimpleName, String classBody, boolean restrictGeneratedCodeToLibrary)3688 private Compilation compile( 3689 String classSimpleName, String classBody, boolean restrictGeneratedCodeToLibrary) { 3690 String src = "package com.example.appsearch;\n" 3691 + "import androidx.appsearch.annotation.Document;\n" 3692 + "import androidx.appsearch.annotation.Document.*;\n" 3693 + classBody; 3694 JavaFileObject jfo = JavaFileObjects.forSourceString( 3695 "com.example.appsearch." + classSimpleName, 3696 src); 3697 // Fully compiling this source code requires AppSearch to be on the classpath, but it only 3698 // builds on Android. Instead, this test configures the annotation processor to write to a 3699 // test-controlled path which is then diffed. 3700 String outputDirFlag = "-A%s=%s".formatted( 3701 OUTPUT_DIR_OPTION, mGenFilesDir.getAbsolutePath()); 3702 String restrictGeneratedCodeToLibraryFlag = "-A%s=%s".formatted( 3703 RESTRICT_GENERATED_CODE_TO_LIB_OPTION, restrictGeneratedCodeToLibrary); 3704 return Compiler.javac() 3705 .withProcessors(new AppSearchCompiler(), new AutoValueProcessor()) 3706 .withOptions(outputDirFlag, restrictGeneratedCodeToLibraryFlag) 3707 .compile(jfo); 3708 } 3709 checkEqualsGolden(String className)3710 private void checkEqualsGolden(String className) throws IOException { 3711 String goldenResPath = "goldens/" + mTestName.getMethodName() + ".JAVA"; 3712 File actualPackageDir = new File(mGenFilesDir, "com/example/appsearch"); 3713 File actualPath = 3714 new File(actualPackageDir, IntrospectionHelper.GEN_CLASS_PREFIX + className); 3715 checkEqualsGoldenHelper(goldenResPath, actualPath); 3716 } 3717 checkDocumentMapEqualsGolden(int roundIndex)3718 private void checkDocumentMapEqualsGolden(int roundIndex) throws IOException { 3719 String goldenResPath = 3720 "goldens/" + mTestName.getMethodName() + "DocumentMap_" + roundIndex + ".JAVA"; 3721 File actualPackageDir = new File(mGenFilesDir, "com/example/appsearch"); 3722 File[] files = actualPackageDir.listFiles((dir, name) -> 3723 name.startsWith(IntrospectionHelper.GEN_CLASS_PREFIX + "DocumentClassMap") 3724 && name.endsWith("_" + roundIndex + ".java")); 3725 Truth.assertThat(files).isNotNull(); 3726 Truth.assertThat(files).hasLength(1); 3727 checkEqualsGoldenHelper(goldenResPath, files[0]); 3728 } 3729 checkEqualsGoldenHelper(String goldenResPath, File actualPath)3730 private void checkEqualsGoldenHelper(String goldenResPath, File actualPath) throws IOException { 3731 // Get the expected file contents 3732 String expected = ""; 3733 try (InputStream is = getClass().getResourceAsStream(goldenResPath)) { 3734 if (is == null) { 3735 LOG.warning("Failed to find resource \"" + goldenResPath + "\"; treating as empty"); 3736 } else { 3737 InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); 3738 expected = CharStreams.toString(reader); 3739 } 3740 } 3741 3742 // Get the actual file contents 3743 Truth.assertWithMessage("Path " + actualPath + " is not a file") 3744 .that(actualPath.isFile()).isTrue(); 3745 String actual = Files.asCharSource(actualPath, StandardCharsets.UTF_8).read(); 3746 3747 // Compare! 3748 if (expected.equals(actual)) { 3749 return; 3750 } 3751 3752 // Sadness. If we're running in an environment where source is available, rewrite the golden 3753 // to match the actual content for ease of updating the goldens. 3754 try { 3755 // At runtime, our resources come from the build tree. However, our cwd is 3756 // frameworks/support, so find the source tree from that. 3757 File goldenSrcDir = new File("src/test/resources/androidx/appsearch/compiler"); 3758 if (!goldenSrcDir.isDirectory()) { 3759 LOG.warning("Failed to update goldens: golden dir \"" 3760 + goldenSrcDir.getAbsolutePath() + "\" does not exist or is not a folder"); 3761 return; 3762 } 3763 File goldenFile = new File(goldenSrcDir, goldenResPath); 3764 Files.asCharSink(goldenFile, StandardCharsets.UTF_8).write(actual); 3765 LOG.info("Successfully updated golden file \"" + goldenFile + "\""); 3766 } finally { 3767 // Now produce the real exception for the test runner. 3768 Truth.assertThat(actual).isEqualTo(expected); 3769 } 3770 } 3771 checkResultContains(String className, String content)3772 private void checkResultContains(String className, String content) throws IOException { 3773 String fileContents = getClassFileContents(className); 3774 Truth.assertThat(fileContents).contains(content); 3775 } 3776 checkResultDoesNotContain(String className, String content)3777 private void checkResultDoesNotContain(String className, String content) throws IOException { 3778 String fileContents = getClassFileContents(className); 3779 Truth.assertThat(fileContents).doesNotContain(content); 3780 } 3781 getClassFileContents(String className)3782 private String getClassFileContents(String className) throws IOException { 3783 File actualPackageDir = new File(mGenFilesDir, "com/example/appsearch"); 3784 File actualPath = 3785 new File(actualPackageDir, IntrospectionHelper.GEN_CLASS_PREFIX + className); 3786 Truth.assertWithMessage("Path " + actualPath + " is not a file") 3787 .that(actualPath.isFile()).isTrue(); 3788 return Files.asCharSource(actualPath, StandardCharsets.UTF_8).read(); 3789 } 3790 } 3791