Support namespaces in the annotation processor.
Bug: 156296904
Test: presubmit
Change-Id: I17bee84ff253cee3226de86165b0f018e0cb6a8e
diff --git a/appsearch/annotation/src/main/java/androidx/appsearch/compiler/AppSearchDocumentModel.java b/appsearch/annotation/src/main/java/androidx/appsearch/compiler/AppSearchDocumentModel.java
index 38fb581..76578db 100644
--- a/appsearch/annotation/src/main/java/androidx/appsearch/compiler/AppSearchDocumentModel.java
+++ b/appsearch/annotation/src/main/java/androidx/appsearch/compiler/AppSearchDocumentModel.java
@@ -47,7 +47,7 @@
class AppSearchDocumentModel {
/** Enumeration of fields that must be handled specially (i.e. are not properties) */
- enum SpecialField { URI, CREATION_TIMESTAMP_MILLIS, TTL_MILLIS, SCORE }
+ enum SpecialField { URI, NAMESPACE, CREATION_TIMESTAMP_MILLIS, TTL_MILLIS, SCORE }
/** Determines how the annotation processor has decided to read the value of a field. */
enum ReadKind { FIELD, GETTER }
/** Determines how the annotation processor has decided to write the value of a field. */
@@ -162,6 +162,7 @@
private void scanFields() throws ProcessingException {
Element uriField = null;
+ Element namespaceField = null;
Element creationTimestampField = null;
Element ttlField = null;
Element scoreField = null;
@@ -180,6 +181,14 @@
uriField = child;
mSpecialFieldNames.put(SpecialField.URI, fieldName);
+ } else if (IntrospectionHelper.NAMESPACE_CLASS.equals(annotationFq)) {
+ if (namespaceField != null) {
+ throw new ProcessingException(
+ "Class contains multiple fields annotated @Namespace", child);
+ }
+ namespaceField = child;
+ mSpecialFieldNames.put(SpecialField.NAMESPACE, fieldName);
+
} else if (
IntrospectionHelper.CREATION_TIMESTAMP_MILLIS_CLASS.equals(annotationFq)) {
if (creationTimestampField != null) {
diff --git a/appsearch/annotation/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java b/appsearch/annotation/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
index 7cdea01..fe33e97 100644
--- a/appsearch/annotation/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
+++ b/appsearch/annotation/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
@@ -598,6 +598,9 @@
case URI:
method.addStatement("String $NConv = genericDoc.getUri()", fieldName);
break;
+ case NAMESPACE:
+ method.addStatement("String $NConv = genericDoc.getNamespace()", fieldName);
+ break;
case CREATION_TIMESTAMP_MILLIS:
method.addStatement(
"long $NConv = genericDoc.getCreationTimestampMillis()", fieldName);
diff --git a/appsearch/annotation/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java b/appsearch/annotation/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
index b489a3c..6986572 100644
--- a/appsearch/annotation/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
+++ b/appsearch/annotation/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
@@ -46,6 +46,8 @@
"androidx.appsearch.annotation.AppSearchDocument";
static final String URI_CLASS =
"androidx.appsearch.annotation.AppSearchDocument.Uri";
+ static final String NAMESPACE_CLASS =
+ "androidx.appsearch.annotation.AppSearchDocument.Namespace";
static final String CREATION_TIMESTAMP_MILLIS_CLASS =
"androidx.appsearch.annotation.AppSearchDocument.CreationTimestampMillis";
static final String TTL_MILLIS_CLASS =
diff --git a/appsearch/annotation/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java b/appsearch/annotation/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
index 052c0e7..d68af02 100644
--- a/appsearch/annotation/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
+++ b/appsearch/annotation/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
@@ -522,6 +522,16 @@
switch (specialField) {
case URI:
break; // Always provided to builder constructor; cannot be set separately.
+ case NAMESPACE:
+ method.addCode(CodeBlock.builder()
+ .addStatement(
+ "String $NCopy = $L",
+ fieldName, createAppSearchFieldRead(fieldName))
+ .add("if ($NCopy != null) {\n", fieldName).indent()
+ .addStatement("builder.setNamespace($NCopy)", fieldName)
+ .unindent().add("}\n")
+ .build());
+ break;
case CREATION_TIMESTAMP_MILLIS:
method.addStatement(
"builder.setCreationTimestampMillis($L)",
diff --git a/appsearch/annotation/src/test/java/androidx/appsearch/compiler/DocumentProcessorTest.java b/appsearch/annotation/src/test/java/androidx/appsearch/compiler/DocumentProcessorTest.java
index 14b1eb5..a5a15a5 100644
--- a/appsearch/annotation/src/test/java/androidx/appsearch/compiler/DocumentProcessorTest.java
+++ b/appsearch/annotation/src/test/java/androidx/appsearch/compiler/DocumentProcessorTest.java
@@ -111,6 +111,19 @@
}
@Test
+ public void testManyNamespace() {
+ Compilation compilation = compile(
+ "@AppSearchDocument\n"
+ + "public class Gift {\n"
+ + " @AppSearchDocument.Uri String uri;\n"
+ + " @AppSearchDocument.Namespace String ns1;\n"
+ + " @AppSearchDocument.Namespace String ns2;\n"
+ + "}\n");
+ CompilationSubject.assertThat(compilation).hadErrorContaining(
+ "contains multiple fields annotated @Namespace");
+ }
+
+ @Test
public void testManyTtlMillis() {
Compilation compilation = compile(
"@AppSearchDocument\n"
@@ -616,10 +629,11 @@
"@AppSearchDocument\n"
+ "public class Gift {\n"
+ " @AppSearchDocument.Uri String uri;\n"
- + " @AppSearchDocument.Score int score;\n"
+ + " @AppSearchDocument.Namespace String namespace;\n"
+ " @AppSearchDocument.CreationTimestampMillis long creationTs;\n"
+ " @AppSearchDocument.TtlMillis int ttlMs;\n"
+ " @AppSearchDocument.Property int price;\n"
+ + " @AppSearchDocument.Score int score;\n"
+ "}\n");
CompilationSubject.assertThat(compilation).succeededWithoutWarnings();
checkEqualsGolden();
diff --git a/appsearch/annotation/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Field.JAVA b/appsearch/annotation/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Field.JAVA
index 5c36bd6..a3f831d 100644
--- a/appsearch/annotation/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Field.JAVA
+++ b/appsearch/annotation/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_Field.JAVA
@@ -19,6 +19,10 @@
public GenericDocument toGenericDocument(Gift dataClass) {
GenericDocument.Builder<?> builder =
new GenericDocument.Builder<>(dataClass.uri, SCHEMA_TYPE);
+ String namespaceCopy = dataClass.namespace;
+ if (namespaceCopy != null) {
+ builder.setNamespace(namespaceCopy);
+ }
builder.setCreationTimestampMillis(dataClass.creationTs);
builder.setTtlMillis(dataClass.ttlMs);
builder.setScore(dataClass.score);
@@ -28,16 +32,18 @@
public Gift fromGenericDocument(GenericDocument genericDoc) {
String uriConv = genericDoc.getUri();
+ String namespaceConv = genericDoc.getNamespace();
long creationTsConv = genericDoc.getCreationTimestampMillis();
long ttlMsConv = genericDoc.getTtlMillis();
int scoreConv = genericDoc.getScore();
int priceConv = genericDoc.getPropertyLong("price");
Gift dataClass = new Gift();
dataClass.uri = uriConv;
- dataClass.score = scoreConv;
+ dataClass.namespace = namespaceConv;
dataClass.creationTs = creationTsConv;
dataClass.ttlMs = ttlMsConv;
dataClass.price = priceConv;
+ dataClass.score = scoreConv;
return dataClass;
}
}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/AppSearchDocument.java b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/AppSearchDocument.java
index 96cf3f3..8d1bf2f 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/AppSearchDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/AppSearchDocument.java
@@ -90,6 +90,27 @@
@interface Uri {}
/**
+ * Marks a member field of a document as the document's namespace.
+ *
+ * <p>The namespace is an arbitrary user-provided string that can be used to group documents
+ * during querying or deletion. Indexing a document with a particular {@link java.net.URI}
+ * replaces any existing documents with the same URI in that namespace.
+ *
+ * <p>This field is not required. If not present or not set, the document will be assigned to
+ * the default namespace, {@link androidx.appsearch.app.GenericDocument#DEFAULT_NAMESPACE}.
+ *
+ * <p>If present, the field must be of type {@code String}.
+ *
+ * <p>See the class description of {@link AppSearchDocument} for other requirements (i.e. if
+ * present it must be visible, or have a visible getter and setter, or be exposed through a
+ * visible constructor).
+ */
+ @Documented
+ @Retention(RetentionPolicy.CLASS)
+ @Target(ElementType.FIELD)
+ @interface Namespace {}
+
+ /**
* Marks a member field of a document as the document's creation timestamp.
*
* <p>The creation timestamp is used for document expiry (see {@link TtlMillis}) and as one