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