commit | 9a52e90d17a496b51de8d273fbe7739f5a1d710c | [log] [tgz] |
---|---|---|
author | Dustin Lam | Wed Nov 22 21:35:23 2023 +0000 |
committer | Gerrit Code Review | Wed Nov 22 21:35:23 2023 +0000 |
tree | a6115b4b03ef5d61795a9f23b9c05b62534a98c2 | |
parent | 967f12489638eb507c6a209ebc5dbcbc6502fa97 [diff] | |
parent | 345e727e71f33ce5f7558148619d3eee865a4cf9 [diff] |
Merge "Bump collections to 1.4.0-beta02" into androidx-main
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt index 67c188e..37c352c 100644 --- a/appsearch/appsearch/api/current.txt +++ b/appsearch/appsearch/api/current.txt
@@ -24,6 +24,8 @@ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.DocumentProperty { method public abstract boolean indexNestedProperties() default false; + method public abstract String[] indexableNestedPropertiesList() default {}; + method public abstract boolean inheritIndexableNestedPropertiesFromSuperclass() default false; method public abstract String name() default ""; method public abstract boolean required() default false; }
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt index 67c188e..37c352c 100644 --- a/appsearch/appsearch/api/restricted_current.txt +++ b/appsearch/appsearch/api/restricted_current.txt
@@ -24,6 +24,8 @@ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) public static @interface Document.DocumentProperty { method public abstract boolean indexNestedProperties() default false; + method public abstract String[] indexableNestedPropertiesList() default {}; + method public abstract boolean inheritIndexableNestedPropertiesFromSuperclass() default false; method public abstract String name() default ""; method public abstract boolean required() default false; }
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java index 77db243..5d52268 100644 --- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java +++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/AnnotationProcessorTestBase.java
@@ -16,6 +16,8 @@ // @exportToFramework:skipFile() package androidx.appsearch.app; +import static androidx.appsearch.app.AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE; +import static androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_EXACT_TERMS; import static androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES; import static androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID; import static androidx.appsearch.app.AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN; @@ -875,6 +877,184 @@ assertThat(documents).hasSize(1); } + @Document(name = "Artist", parent = {Root.class}) + static class Artist extends Root { + @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mName; + @Document.LongProperty(indexingType = INDEXING_TYPE_RANGE) long mAge; + @Document.StringProperty(indexingType = INDEXING_TYPE_PREFIXES) String mMostFamousWork; + @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mNationality; + } + + + // Indexed properties for Media type: {"name", "description", "leadActor.name", "leadActor.age"} + @Document(name = "Media", parent = {Root.class}) + static class Media extends Root { + @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mName; + @Document.StringProperty(indexingType = INDEXING_TYPE_PREFIXES) String mDescription; + @Document.DocumentProperty(indexableNestedPropertiesList = {"name", "age"}) + Artist mLeadActor; + } + + // Indexed properties for Movie type: {"name", "description", "leadActor.name", + // "leadActor.nationality"} + // Movie does not index "leadActor.age" as the java class does not extend from Media + @Document(name = "Movie", parent = {Media.class}) + static class Movie extends Root { + @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mName; + @Document.StringProperty(indexingType = INDEXING_TYPE_PREFIXES) String mDescription; + @Document.DocumentProperty( + indexableNestedPropertiesList = {"name", "nationality"}, + inheritIndexableNestedPropertiesFromSuperclass = true) + Artist mLeadActor; + } + + // Indexed properties for Documentary type: {"name", "description", "leadActor.name", + // "leadActor.age", "leadActor.mostFamousWork"} + // Documentary extends Media, so it should index all nested properties in Media.leadActor, as + // well as "leadActor.mostFamousWork" + @Document(name = "Documentary", parent = {Media.class}) + static class Documentary extends Media { + @Document.DocumentProperty( + indexableNestedPropertiesList = {"mostFamousWork"}, + inheritIndexableNestedPropertiesFromSuperclass = true) + Artist mLeadActor; + @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mEvent; + } + + // Indexed properties for DocumentaryMovie type: {"name", "description", "leadActor.name", + // "leadActor.age", "leadActor.mostFamousWork"} + // Documentary extends Media, so it should index all nested properties in Media.leadActor, but + // should not index "leadActor.nationality" as it does not extend from Movie. + @Document(name = "DocumentaryMovie", parent = {Documentary.class, Movie.class}) + static class DocumentaryMovie extends Documentary { + @Document.DocumentProperty( + indexableNestedPropertiesList = {}, + inheritIndexableNestedPropertiesFromSuperclass = true) + Artist mLeadActor; + @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) String mDate; + } + + @Test + public void testIndexableNestedPropertiesList() throws Exception { + assumeTrue(mSession.getFeatures().isFeatureSupported( + Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES)); + + mSession.setSchemaAsync(new SetSchemaRequest.Builder() + // Artist, Media, Movie and Documentary are added automatically as they are + // DocumentaryMovie's dependencies. + .addDocumentClasses(DocumentaryMovie.class) + // Add an unrelated schema type + .addDocumentClasses(Gift.class) + .build()).get(); + + + // Create documents + Artist actor = new Artist(); + actor.mNamespace = "namespace"; + actor.mId = "id1"; + actor.mName = "actor"; + actor.mAge = 30; + actor.mMostFamousWork = "famousWork"; + actor.mNationality = "nationality"; + + Media media = new Media(); + media.mNamespace = "namespace"; + media.mId = "id2"; + media.mName = "media"; + media.mDescription = "mediaDescription"; + media.mLeadActor = actor; + + Movie movie = new Movie(); + movie.mNamespace = "namespace"; + movie.mId = "id3"; + movie.mName = "movie"; + movie.mDescription = "movieDescription"; + movie.mLeadActor = actor; + + Documentary documentary = new Documentary(); + documentary.mNamespace = "namespace"; + documentary.mId = "id4"; + documentary.mName = "documentary"; + documentary.mDescription = "documentaryDescription"; + documentary.mLeadActor = actor; + documentary.mEvent = "documentaryEvent"; + + DocumentaryMovie documentaryMovie = new DocumentaryMovie(); + documentaryMovie.mNamespace = "namespace"; + documentaryMovie.mId = "id5"; + documentaryMovie.mName = "documentaryMovie"; + documentaryMovie.mDescription = "documentaryMovieDescription"; + documentaryMovie.mLeadActor = actor; + documentaryMovie.mEvent = "documentaryMovieEvent"; + documentaryMovie.mDate = "2023-10-12"; + + checkIsBatchResultSuccess(mSession.putAsync( + new PutDocumentsRequest.Builder() + .addDocuments(actor, media, movie, documentary, documentaryMovie) + .build())); + + // Query for all documents + SearchResults searchResults = mSession.search("", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .build()); + Listdocuments = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(5); + + + // Query for "actor" should retrieve all documents. + searchResults = mSession.search("actor", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .build()); + documents = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(5); + + // Query for "leadActor.age == 30" should retrieve Media, Documentary and DocumentaryMovie + searchResults = mSession.search("leadActor.age == 30", + new SearchSpec.Builder() + .setNumericSearchEnabled(true) + .build()); + documents = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(3); + assertThat(documents.get(0).getId()).isEqualTo("id5"); // documentaryMovie + assertThat(documents.get(1).getId()).isEqualTo("id4"); // documentary + assertThat(documents.get(2).getId()).isEqualTo("id2"); // media + + // Query for "famous" should retrieve Actor, Documentary and DocumentaryMovie + searchResults = mSession.search("famous", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_PREFIX) + .build()); + documents = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(3); + assertThat(documents.get(0).getId()).isEqualTo("id5"); // documentaryMovie + assertThat(documents.get(1).getId()).isEqualTo("id4"); // documentary + assertThat(documents.get(2).getId()).isEqualTo("id1"); // actor + + // Query for "nationality" should retrieve Actor and Movie. + searchResults = mSession.search("nationality", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .build()); + documents = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(2); + assertThat(documents.get(0).getId()).isEqualTo("id3"); // movie + assertThat(documents.get(1).getId()).isEqualTo("id1"); // actor + } + + @Test + public void testIndexableNestedPropertiesList_notSupported() { + assumeFalse(mSession.getFeatures().isFeatureSupported( + Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES)); + + assertThrows(UnsupportedOperationException.class, + () -> mSession.setSchemaAsync( + new SetSchemaRequest.Builder() + .addDocumentClasses(DocumentaryMovie.class) + .build())); + } + // A class that some properties are annotated via getters without backing fields. @Document static class FakeMessage { @@ -1640,6 +1820,344 @@ assertThat(documents).containsExactly(businessGeneric); } + @Document(name = "Event", parent = InterfaceRoot.class) + interface Event extends InterfaceRoot { + @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) + String getName(); + + @Document.DocumentProperty(indexableNestedPropertiesList = {"name", "age"}) + Artist getPerformer(); + + @Document.BuilderProducer + class Builder { + String mId; + String mNamespace; + long mCreationTimestamp; + String mName; + Artist mPerformer; + + Builder(String id, String namespace) { + mId = id; + mNamespace = namespace; + } + + public Event build() { + return new EventImpl(mId, mNamespace, mCreationTimestamp, mName, mPerformer); + } + + public Event.Builder setCreationTimestamp(long creationTimestamp) { + mCreationTimestamp = creationTimestamp; + return this; + } + + public Event.Builder setName(String name) { + mName = name; + return this; + } + + public Event.Builder setPerformer(Artist performer) { + mPerformer = performer; + return this; + } + } + } + + @Document(name = "Charity", parent = InterfaceRoot.class) + interface Charity extends InterfaceRoot { + @Document.StringProperty(indexingType = INDEXING_TYPE_EXACT_TERMS) + String getName(); + + @Document.DocumentProperty(indexableNestedPropertiesList = {"name", "nationality"}) + Artist getPerformer(); + + @Document.BuilderProducer + class Builder { + String mId; + String mNamespace; + long mCreationTimestamp; + String mName; + Artist mPerformer; + + Builder(String id, String namespace) { + mId = id; + mNamespace = namespace; + } + + public Charity build() { + return new CharityImpl(mId, mNamespace, mCreationTimestamp, mName, mPerformer); + } + + public Charity.Builder setCreationTimestamp(long creationTimestamp) { + mCreationTimestamp = creationTimestamp; + return this; + } + + public Charity.Builder setName(String name) { + mName = name; + return this; + } + + public Charity.Builder setPerformer(Artist performer) { + mPerformer = performer; + return this; + } + } + } + + @Document(name = "CharityEvent", parent = {Event.class, Charity.class}) + interface CharityEvent extends Event, Charity { + @Document.StringProperty(indexingType = INDEXING_TYPE_PREFIXES) + String getDescription(); + + @Document.DocumentProperty(inheritIndexableNestedPropertiesFromSuperclass = true) + Artist getPerformer(); + + @Document.BuilderProducer + class Builder { + String mId; + String mNamespace; + long mCreationTimestamp; + String mName; + String mDescription; + Artist mPerformer; + + Builder(String id, String namespace) { + mId = id; + mNamespace = namespace; + } + + public CharityEvent build() { + return new CharityEventImpl(mId, mNamespace, mCreationTimestamp, mName, + mDescription, mPerformer); + } + + public CharityEvent.Builder setCreationTimestamp(long creationTimestamp) { + mCreationTimestamp = creationTimestamp; + return this; + } + + public CharityEvent.Builder setName(String name) { + mName = name; + return this; + } + + public CharityEvent.Builder setDescription(String description) { + mDescription = description; + return this; + } + + public CharityEvent.Builder setPerformer(Artist performer) { + mPerformer = performer; + return this; + } + } + } + + + static class EventImpl implements Event { + String mId; + String mNamespace; + long mCreationTimestamp; + String mName; + Artist mPerformer; + + EventImpl(String id, String namespace, long creationTimestamp, String name, + Artist performer) { + mId = id; + mNamespace = namespace; + mCreationTimestamp = creationTimestamp; + mName = name; + mPerformer = performer; + } + + @Override + public String getId() { + return mId; + } + + @Override + public String getNamespace() { + return mNamespace; + } + + @Override + public long getCreationTimestamp() { + return mCreationTimestamp; + } + + @Override + public String getName() { + return mName; + } + + @Override + public Artist getPerformer() { + return mPerformer; + } + } + + static class CharityImpl implements Charity { + String mId; + String mNamespace; + long mCreationTimestamp; + String mName; + Artist mPerformer; + + CharityImpl(String id, String namespace, long creationTimestamp, String name, + Artist performer) { + mId = id; + mNamespace = namespace; + mCreationTimestamp = creationTimestamp; + mName = name; + mPerformer = performer; + } + + @Override + public String getId() { + return mId; + } + + @Override + public String getNamespace() { + return mNamespace; + } + + @Override + public long getCreationTimestamp() { + return mCreationTimestamp; + } + + @Override + public String getName() { + return mName; + } + + @Override + public Artist getPerformer() { + return mPerformer; + } + } + + // CharityEventImpl.performer's nested properties: {performer.name, performer.age, + // performer.nationality} + static class CharityEventImpl implements CharityEvent { + String mId; + String mNamespace; + long mCreationTimestamp; + String mName; + String mDescription; + Artist mPerformer; + + CharityEventImpl(String id, String namespace, long creationTimestamp, String name, + String description, Artist performer) { + mId = id; + mNamespace = namespace; + mCreationTimestamp = creationTimestamp; + mName = name; + mDescription = description; + mPerformer = performer; + } + + public String getId() { + return mId; + } + + public String getNamespace() { + return mNamespace; + } + + public long getCreationTimestamp() { + return mCreationTimestamp; + } + + public String getName() { + return mName; + } + + public Artist getPerformer() { + return mPerformer; + } + + public String getDescription() { + return null; + } + } + + @Test + public void testIndexableNestedPropertiesListForInterface() throws Exception { + assumeTrue(mSession.getFeatures().isFeatureSupported( + Features.SCHEMA_ADD_INDEXABLE_NESTED_PROPERTIES)); + + mSession.setSchemaAsync(new SetSchemaRequest.Builder() + .addDocumentClasses(CharityEventImpl.class) + // Add an unrelated schema type + .addDocumentClasses(Gift.class) + .build()).get(); + + + // Create documents + Artist performer = new Artist(); + performer.mNamespace = "namespace"; + performer.mId = "id1"; + performer.mName = "performer"; + performer.mAge = 30; + performer.mMostFamousWork = "famousWork"; + performer.mNationality = "nationality"; + + CharityEventImpl charityEvent = new CharityEventImpl("id2", "namespace", 0, "charityEvent", + "charityEventDescription", performer); + + checkIsBatchResultSuccess(mSession.putAsync( + new PutDocumentsRequest.Builder() + .addDocuments(performer, charityEvent) + .build())); + + // Query for all documents + SearchResults searchResults = mSession.search("", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .build()); + Listdocuments = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(2); + + + // Query for "performer" should retrieve all documents. + searchResults = mSession.search("performer", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .build()); + documents = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(2); + + // Query for "performer.age == 30" should retrieve CharityEvent + searchResults = mSession.search("performer.age == 30", + new SearchSpec.Builder() + .setNumericSearchEnabled(true) + .build()); + documents = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(1); + assertThat(documents.get(0).getId()).isEqualTo("id2"); // charityEvent + + // Query for "famous" should retrieve Performer + searchResults = mSession.search("famous", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_PREFIX) + .build()); + documents = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(1); + assertThat(documents.get(0).getId()).isEqualTo("id1"); // performer + + // Query for "nationality" should retrieve Performer and CharityEvent. + searchResults = mSession.search("nationality", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .build()); + documents = convertSearchResultsToDocuments(searchResults); + assertThat(documents).hasSize(2); + assertThat(documents.get(0).getId()).isEqualTo("id2"); // charityEvent + assertThat(documents.get(1).getId()).isEqualTo("id1"); // performer + } + @Test public void testAppSearchDocumentClassMap() throws Exception { // Before this test, AppSearch's annotation processor has already generated the maps for
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java index 8e802e6..59415fb 100644 --- a/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java +++ b/appsearch/appsearch/src/main/java/androidx/appsearch/annotation/Document.java
@@ -332,14 +332,64 @@ String name() default ""; /** - * Configures whether fields in the nested document should be indexed. + * Configures whether all fields in the nested document should be indexed. * *+ * + *If false, the nested document's properties are not indexed regardless of its own
- * schema. + * schema, unless {@link #indexableNestedPropertiesList()} is used to index a subset of + * properties from the nested document. + * + *{@link IllegalArgumentException} will be thrown during setSchema if set to true and
+ * defining a non-empty list for {@link #indexableNestedPropertiesList()} */ boolean indexNestedProperties() default false; /** + * The list of properties in the nested document to index. The property will be indexed + * according to its indexing configurations in the document's schema definition. + * + *{@link #indexNestedProperties} is required to be false if this list is non-empty.
+ * {@link IllegalArgumentException} will be thrown during setSchema if this condition is + * not met. + * + * @see + * AppSearchSchema.DocumentPropertyConfig.Builder#addIndexableNestedProperties(Collection) + */ + String[] indexableNestedPropertiesList() default {}; + + /** + * Configures whether to inherit the indexable nested properties list from the Document's + * superclass type definition. When set to true, the indexable property paths will be + * a union of the paths specified in {@link #indexableNestedPropertiesList()} and any + * path specified in the document class's superclass or inherited interfaces. + * Effectively, this is a no-op if none of the document superclasses specify a path for + * this document property. + * + *Ex. Consider the following Document classes:
+ * + * {@code + * @Document + * class Person { + * @Document.DocumentProperty(indexableNestedPropertiesList = {"streetName", "zipcode"}) + * Address livesAt; + * } + * @Document + * class Artist extends Person { + * @Document.DocumentProperty( + * indexableNestedPropertiesList = {"country"}, + * inheritIndexableNestedPropertiesFromSuperclass = true + * ) + * Address livesAt; + * } + * } + *
By setting 'inheritIndexableNestedPropertiesFromSuperclass = true', Artist.livesAt
+ * inherits the indexable nested properties defined by its parent class's livesAt field + * (Person.livesAt) and indexes all three fields: {streetName, zipCode, country} + */ + boolean inheritIndexableNestedPropertiesFromSuperclass() default false; + + /** * Configures whether this property must be specified for the document to be valid. * *This attribute does not apply to properties of a repeated type (e.g. a list).
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentClassCreationInfo.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentClassCreationInfo.java index a2a4fd6..65c7b554 100644 --- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentClassCreationInfo.java +++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentClassCreationInfo.java
@@ -420,9 +420,7 @@ } private static boolean isAnnotatedWithBuilderProducer(@NonNull Element element) { - return element.getAnnotationMirrors().stream() - .anyMatch(annotation -> annotation.getAnnotationType().toString() - .equals(BUILDER_PRODUCER_CLASS.canonicalName())); + return !IntrospectionHelper.getAnnotations(element, BUILDER_PRODUCER_CLASS).isEmpty(); } /**
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java index a6c6a24..216d075 100644 --- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java +++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
@@ -48,6 +48,9 @@ /** * Processes @Document annotations. * + * @see AnnotatedGetterAndFieldAccumulator for the DocumentModel's invariants with regards to its + * getter and field definitions. + * * @exportToFramework:hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java index a458359..7884791 100644 --- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java +++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
@@ -22,6 +22,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; +import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation; +import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation; +import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation; import com.google.auto.value.AutoValue; import com.squareup.javapoet.ClassName; @@ -155,11 +158,58 @@ @Nullable public static AnnotationMirror getDocumentAnnotation(@NonNull Element element) { Objects.requireNonNull(element); - for (AnnotationMirror annotation : element.getAnnotationMirrors()) { - String annotationFq = annotation.getAnnotationType().toString(); - if (IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS.canonicalName().equals( - annotationFq)) { - return annotation; + List extends AnnotationMirror> annotations = getAnnotations(element, + DOCUMENT_ANNOTATION_CLASS); + if (annotations.isEmpty()) { + return null; + } else { + return annotations.get(0); + } + } + + /** + * Returns a list of annotations of a given kind from the input element's annotations, + * specified by the annotation's class name. Returns null if no annotation of such kind is + * found. + */ + @NonNull + public static List extends AnnotationMirror> getAnnotations(@NonNull Element element, + @NonNull ClassName className) { + Objects.requireNonNull(element); + Objects.requireNonNull(className); + return element.getAnnotationMirrors() + .stream() + .filter(annotation -> annotation.getAnnotationType().toString() + .equals(className.canonicalName())) + .toList(); + } + + /** + * Returns the document property annotation that matches the given property name from a given + * class or interface element. + * + *Returns null if the property cannot be found in the class or interface, or if the
+ * property matching the property name is not a document property. + */ + @Nullable + public DocumentPropertyAnnotation getDocumentPropertyAnnotation( + @NonNull TypeElement clazz, @NonNull String propertyName) throws ProcessingException { + Objects.requireNonNull(clazz); + Objects.requireNonNull(propertyName); + for (Element enclosedElement : clazz.getEnclosedElements()) { + AnnotatedGetterOrField getterOrField = + AnnotatedGetterOrField.tryCreateFor(enclosedElement, mEnv); + if (getterOrField == null || !(getterOrField.getAnnotation().getPropertyKind() + == PropertyAnnotation.Kind.DATA_PROPERTY)) { + continue; + } + if (((DataPropertyAnnotation) getterOrField.getAnnotation()).getDataPropertyKind() + == DataPropertyAnnotation.Kind.DOCUMENT_PROPERTY) { + DocumentPropertyAnnotation documentPropertyAnnotation = + (DocumentPropertyAnnotation) getterOrField.getAnnotation(); + if (documentPropertyAnnotation.getName().equals(propertyName)) { + return documentPropertyAnnotation; + } } } return null;
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java index 288a61b..9199c47 100644 --- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java +++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
@@ -21,6 +21,10 @@ import static androidx.appsearch.compiler.IntrospectionHelper.PROPERTY_CONFIG_CLASS; import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentClassFactoryForClass; +import static com.google.auto.common.MoreTypes.asTypeElement; + +import static javax.lang.model.type.TypeKind.DECLARED; + import androidx.annotation.NonNull; import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation; import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation; @@ -36,8 +40,14 @@ import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.WildcardTypeName; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; @@ -47,6 +57,7 @@ /** Generates java code for an {@link androidx.appsearch.app.AppSearchSchema}. */ class SchemaCodeGenerator { private final DocumentModel mModel; + private final IntrospectionHelper mHelper; private final LinkedHashSetmDependencyDocumentClasses; public static void generate( @@ -58,6 +69,7 @@ private SchemaCodeGenerator(@NonNull DocumentModel model, @NonNull ProcessingEnvironment env) { mModel = model; + mHelper = new IntrospectionHelper(env); mDependencyDocumentClasses = computeDependencyClasses(model, env); } @@ -206,6 +218,12 @@ DocumentPropertyAnnotation documentPropertyAnnotation = (DocumentPropertyAnnotation) annotation; codeBlock.add(createSetShouldIndexNestedPropertiesExpr(documentPropertyAnnotation)); + SetindexableNestedProperties = getAllIndexableNestedProperties( + documentPropertyAnnotation); + for (String propertyPath : indexableNestedProperties) { + codeBlock.add( + CodeBlock.of("\n.addIndexableNestedProperties($L)", propertyPath)); + } break; case LONG_PROPERTY: LongPropertyAnnotation longPropertyAnnotation = (LongPropertyAnnotation) annotation; @@ -223,6 +241,59 @@ .build(); } + + /** + * Finds all indexable nested properties for the given type class and document property + * annotation. This includes indexable nested properties that should be inherited from the + * type's parent. + */ + private SetgetAllIndexableNestedProperties( + @NonNull DocumentPropertyAnnotation documentPropertyAnnotation) + throws ProcessingException { + SetindexableNestedProperties = new HashSet<>( + documentPropertyAnnotation.getIndexableNestedPropertiesList()); + + if (documentPropertyAnnotation.shouldInheritIndexableNestedPropertiesFromSuperClass()) { + // List of classes to expand into parent classes to search for the property annotation + QueueclassesToExpand = new ArrayDeque<>(); + Setvisited = new HashSet<>(); + classesToExpand.add(mModel.getClassElement()); + while (!classesToExpand.isEmpty()) { + TypeElement currentClass = classesToExpand.poll(); + if (visited.contains(currentClass)) { + continue; + } + visited.add(currentClass); + // Look for the document property annotation in the class's parent classes + ListparentTypes = new ArrayList<>(); + parentTypes.add(currentClass.getSuperclass()); + parentTypes.addAll(currentClass.getInterfaces()); + for (TypeMirror parent : parentTypes) { + if (!parent.getKind().equals(DECLARED)) { + continue; + } + TypeElement parentElement = asTypeElement(parent); + DocumentPropertyAnnotation annotation = mHelper.getDocumentPropertyAnnotation( + parentElement, documentPropertyAnnotation.getName()); + if (annotation == null) { + // The property is not found in this level. Continue searching in one level + // above as the property could still be defined for this level by class + // inheritance. + classesToExpand.add(parentElement); + } else { + indexableNestedProperties.addAll( + annotation.getIndexableNestedPropertiesList()); + if (annotation.shouldInheritIndexableNestedPropertiesFromSuperClass()) { + // Continue searching in the parent class's parents + classesToExpand.add(parentElement); + } + } + } + } + } + return indexableNestedProperties; + } + /** * Creates an expr like {@code .setCardinality(PropertyConfig.CARDINALITY_REPEATED)}. */
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java index 4bf1789..3807b17 100644 --- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java +++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java
@@ -23,8 +23,11 @@ import androidx.appsearch.compiler.IntrospectionHelper; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import javax.lang.model.type.TypeMirror; @@ -57,10 +60,19 @@ static DocumentPropertyAnnotation parse( @NonNull MapannotationParams, @NonNull String defaultName) { String name = (String) annotationParams.get("name"); + ListindexableNestedPropertiesList = new ArrayList<>(); + Object indexableList = annotationParams.get("indexableNestedPropertiesList"); + if (indexableList instanceof List) { + for (Object property : (List>) indexableList) { + indexableNestedPropertiesList.add(property.toString()); + } + } return new AutoValue_DocumentPropertyAnnotation( name.isEmpty() ? defaultName : name, (boolean) annotationParams.get("required"), - (boolean) annotationParams.get("indexNestedProperties")); + (boolean) annotationParams.get("indexNestedProperties"), + ImmutableList.copyOf(indexableNestedPropertiesList), + (boolean) annotationParams.get("inheritIndexableNestedPropertiesFromSuperclass")); } /** @@ -68,6 +80,19 @@ */ public abstract boolean shouldIndexNestedProperties(); + /** + * Returns the list of nested properties to index for the nested document other than the + * properties inherited from the type's parent. + */ + @NonNull + public abstract ImmutableListgetIndexableNestedPropertiesList(); + + /** + * Specifies whether to inherit the parent class's definition for the indexable nested + * properties list. + */ + public abstract boolean shouldInheritIndexableNestedPropertiesFromSuperClass(); + @NonNull @Override public final Kind getDataPropertyKind() {
diff --git a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java index 18148d7..0b5e501 100644 --- a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java +++ b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
@@ -1828,6 +1828,341 @@ } @Test + public void testIndexableNestedPropertiesListSimple() throws Exception { + Compilation compilation = compile( + "@Document\n" + + "class Address {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.LongProperty long streetNumber;\n" + + " @Document.StringProperty String streetName;\n" + + " @Document.StringProperty String state;\n" + + " @Document.LongProperty long zipCode;\n" + + "}\n" + + "@Document\n" + + "class Person {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.StringProperty String name;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" + + "}\n"); + assertThat(compilation).succeededWithoutWarnings(); + + checkResultContains("Person.java", "addIndexableNestedProperties(\"streetNumber\")"); + checkResultContains("Person.java", "addIndexableNestedProperties(\"streetName\")"); + + checkEqualsGolden("Person.java"); + } + + @Test + public void testIndexableNestedPropertiesListEmpty() throws Exception { + Compilation compilation = compile( + "@Document\n" + + "class Address {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.LongProperty long streetNumber;\n" + + " @Document.StringProperty String streetName;\n" + + " @Document.StringProperty String state;\n" + + " @Document.LongProperty long zipCode;\n" + + "}\n" + + "@Document\n" + + "class Person {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.StringProperty String name;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList = {})" + + " Address livesAt;\n" + + "}\n"); + assertThat(compilation).succeededWithoutWarnings(); + checkResultDoesNotContain("Person.java", "addIndexableNestedProperties"); + checkEqualsGolden("Person.java"); + } + + @Test + public void testIndexableNestedPropertiesListInheritSuperclassTrue() throws Exception { + Compilation compilation = compile( + "@Document\n" + + "class Address {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.LongProperty long streetNumber;\n" + + " @Document.StringProperty String streetName;\n" + + " @Document.StringProperty String state;\n" + + " @Document.LongProperty long zipCode;\n" + + "}\n" + + "@Document\n" + + "class Person {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.StringProperty String name;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" + + "}\n" + + "@Document(name = \"Artist\", parent = {Person.class})\n" + + "class Artist extends Person {\n" + + " @Document.StringProperty String mostFamousWork;\n" + + " @Document.DocumentProperty(" + + " indexableNestedPropertiesList = {\"state\"}," + + " inheritIndexableNestedPropertiesFromSuperclass = true)" + + " Address livesAt;\n" + + "}\n"); + assertThat(compilation).succeededWithoutWarnings(); + + checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetNumber\")"); + checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetName\")"); + checkResultContains("Artist.java", "addIndexableNestedProperties(\"state\")"); + + checkEqualsGolden("Artist.java"); + } + + @Test + public void testIndexableNestedPropertiesListInheritSuperclassFalse() throws Exception { + Compilation compilation = compile( + "@Document\n" + + "class Address {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.LongProperty long streetNumber;\n" + + " @Document.StringProperty String streetName;\n" + + " @Document.StringProperty String state;\n" + + " @Document.LongProperty long zipCode;\n" + + "}\n" + + "@Document\n" + + "class Person {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.StringProperty String name;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" + + "}\n" + + "@Document(name = \"Artist\", parent = {Person.class})\n" + + "class Artist extends Person {\n" + + " @Document.StringProperty String mostFamousWork;\n" + + " @Document.DocumentProperty(" + + " indexableNestedPropertiesList = {\"state\"}," + + " inheritIndexableNestedPropertiesFromSuperclass = false)" + + " Address livesAt;\n" + + "}\n"); + assertThat(compilation).succeededWithoutWarnings(); + + checkResultContains("Artist.java", "addIndexableNestedProperties(\"state\")"); + checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"streetNumber\")"); + checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"streetName\")"); + + checkEqualsGolden("Artist.java"); + } + + @Test + public void testIndexableNestedPropertiesListInheritWithMultipleParentsClasses() + throws Exception { + // Tests that the child class inherits nested properties from the parent correctly. When + // set to true, the field overridden by the child class should only inherit indexable + // nested properties form its java parent class (i.e. the superclass/interface which the + // child class extends from/implements). + // In this test case, Artist's parent class is Person, and ArtistEmployee's parent class + // is Artist. This means that ArtistEmployee.livesAt's indexable list should contain the + // properties s specified in Artist.livesAt. and Person.livesAt (since both Artist and + // ArtistEmployee sets inheritFromParent=true for this field). ArtistEmployee.livesAt + // should not inherit the indexable list from Employee.livesAt since Employee is not + // ArtistEmployee's java class parent. + Compilation compilation = compile( + "@Document\n" + + "class Address {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.LongProperty long streetNumber;\n" + + " @Document.StringProperty String streetName;\n" + + " @Document.StringProperty String state;\n" + + " @Document.LongProperty long zipCode;\n" + + "}\n" + + "@Document\n" + + "class Person {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.StringProperty String name;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" + + "}\n" + + "@Document(name = \"Artist\", parent = {Person.class})\n" + + "class Artist extends Person {\n" + + " @Document.StringProperty String mostFamousWork;\n" + + " @Document.DocumentProperty(" + + " indexableNestedPropertiesList = {\"state\"}," + + " inheritIndexableNestedPropertiesFromSuperclass = true)" + + " Address livesAt;\n" + + "}\n" + + "@Document(name = \"Employee\", parent = {Person.class})\n" + + "class Employee extends Person {\n" + + " @Document.DocumentProperty(" + + " indexableNestedPropertiesList = {\"zipCode\"}," + + " inheritIndexableNestedPropertiesFromSuperclass = true)" + + " Address livesAt;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"zipCode\", \"streetName\"}) Address worksAt;\n" + + "}\n" + + "@Document(name = \"ArtistEmployee\", parent = {Artist.class," + + "Employee.class})\n" + + "class ArtistEmployee extends Artist {\n" + + " @Document.StringProperty String mostFamousWork;\n" + + " @Document.DocumentProperty(" + + " inheritIndexableNestedPropertiesFromSuperclass = true)" + + " Address livesAt;\n" + + "}\n"); + assertThat(compilation).succeededWithoutWarnings(); + + checkResultContains("ArtistEmployee.java", + "addIndexableNestedProperties(\"streetNumber\")"); + checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")"); + checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")"); + // ArtistEmployee's indexable list should not contain 'zipCode' as ArtistEmployee only + // extends Artist, which does not index zipCode + checkResultDoesNotContain("ArtistEmployee.java", + "addIndexableNestedProperties(\"zipCode\")"); + + checkEqualsGolden("ArtistEmployee.java"); + } + + @Test + public void testIndexableNestedPropertiesListImplicitInheritance() throws Exception { + // Tests that properties that are not declared in the child class itself but exists in + // the class due to java class inheritance indexes the correct indexable list. + // Artist.livesAt should be defined for Artist and index the same indexable properties as + // Person.livesAt. + Compilation compilation = compile( + "@Document\n" + + "class Address {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.LongProperty long streetNumber;\n" + + " @Document.StringProperty String streetName;\n" + + " @Document.StringProperty String state;\n" + + " @Document.LongProperty long zipCode;\n" + + "}\n" + + "@Document\n" + + "class Person {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.StringProperty String name;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" + + "}\n" + + "@Document(name = \"Artist\", parent = {Person.class})\n" + + "class Artist extends Person {\n" + + " @Document.StringProperty String mostFamousWork;\n" + + "}\n"); + assertThat(compilation).succeededWithoutWarnings(); + + checkResultContains("Artist.java", + "addIndexableNestedProperties(\"streetNumber\")"); + checkResultContains("Artist.java", "addIndexableNestedProperties(\"streetName\")"); + + checkResultDoesNotContain("Artist.java", + "addIndexableNestedProperties(\"zipCode\")"); + checkResultDoesNotContain("Artist.java", "addIndexableNestedProperties(\"state\")"); + + checkEqualsGolden("Artist.java"); + } + + @Test + public void testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels() + throws Exception { + // Tests that the indexable list is inherited correctly across multiple java inheritance + // levels. + // ArtistEmployee.livesAt should index the nested properties defined in Person.livesAt. + Compilation compilation = compile( + "@Document\n" + + "class Address {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.LongProperty long streetNumber;\n" + + " @Document.StringProperty String streetName;\n" + + " @Document.StringProperty String state;\n" + + " @Document.LongProperty long zipCode;\n" + + "}\n" + + "@Document\n" + + "class Person {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.StringProperty String name;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"streetNumber\", \"streetName\"}) Address livesAt;\n" + + "}\n" + + "@Document(name = \"Artist\", parent = {Person.class})\n" + + "class Artist extends Person {\n" + + " @Document.StringProperty String mostFamousWork;\n" + + "}\n" + + "@Document(name = \"ArtistEmployee\", parent = {Artist.class})\n" + + "class ArtistEmployee extends Artist {\n" + + " @Document.StringProperty String worksAt;\n" + + " @Document.DocumentProperty(" + + " inheritIndexableNestedPropertiesFromSuperclass = true)" + + " Address livesAt;\n" + + "}\n"); + assertThat(compilation).succeededWithoutWarnings(); + + checkResultContains("ArtistEmployee.java", + "addIndexableNestedProperties(\"streetNumber\")"); + checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")"); + + checkResultDoesNotContain("ArtistEmployee.java", + "addIndexableNestedProperties(\"zipCode\")"); + checkResultDoesNotContain("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")"); + + checkEqualsGolden("ArtistEmployee.java"); + } + + @Test + public void testIndexableNestedPropertiesListTopLevelInheritTrue() throws Exception { + Compilation compilation = compile( + "@Document\n" + + "class Address {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.LongProperty long streetNumber;\n" + + " @Document.StringProperty String streetName;\n" + + " @Document.StringProperty String state;\n" + + " @Document.LongProperty long zipCode;\n" + + "}\n" + + "@Document\n" + + "class Person {\n" + + " @Document.Namespace String namespace;\n" + + " @Document.Id String id;\n" + + " @Document.StringProperty String name;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"streetNumber\", \"streetName\"}," + + " inheritIndexableNestedPropertiesFromSuperclass = true)" + + " Address livesAt;\n" + + "}\n" + + "@Document(name = \"Artist\", parent = {Person.class})\n" + + "class Artist extends Person {\n" + + " @Document.StringProperty String mostFamousWork;\n" + + " @Document.DocumentProperty(indexableNestedPropertiesList =" + + " {\"state\"}, inheritIndexableNestedPropertiesFromSuperclass = true)" + + " Address livesAt;\n" + + "}\n" + + "@Document(name = \"ArtistEmployee\", parent = {Artist.class})\n" + + "class ArtistEmployee extends Artist {\n" + + " @Document.StringProperty String worksAt;\n" + + " @Document.DocumentProperty(" + + " inheritIndexableNestedPropertiesFromSuperclass = true)" + + " Address livesAt;\n" + + "}\n"); + assertThat(compilation).succeededWithoutWarnings(); + + checkResultContains("ArtistEmployee.java", + "addIndexableNestedProperties(\"streetNumber\")"); + checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"streetName\")"); + checkResultContains("ArtistEmployee.java", "addIndexableNestedProperties(\"state\")"); + + checkResultDoesNotContain("ArtistEmployee.java", + "addIndexableNestedProperties(\"zipCode\")"); + + checkEqualsGolden("ArtistEmployee.java"); + } + + @Test public void testAnnotationOnClassGetter() throws Exception { Compilation compilation = compile( "@Document\n" @@ -3192,14 +3527,21 @@ } private void checkResultContains(String className, String content) throws IOException { - // Get the actual file contents + String fileContents = getClassFileContents(className); + Truth.assertThat(fileContents).contains(content); + } + + private void checkResultDoesNotContain(String className, String content) throws IOException { + String fileContents = getClassFileContents(className); + Truth.assertThat(fileContents).doesNotContain(content); + } + + private String getClassFileContents(String className) throws IOException { File actualPackageDir = new File(mGenFilesDir, "com/example/appsearch"); File actualPath = new File(actualPackageDir, IntrospectionHelper.GEN_CLASS_PREFIX + className); Truth.assertWithMessage("Path " + actualPath + " is not a file") .that(actualPath.isFile()).isTrue(); - String actual = Files.asCharSource(actualPath, StandardCharsets.UTF_8).read(); - - Truth.assertThat(actual).contains(content); + return Files.asCharSource(actualPath, StandardCharsets.UTF_8).read(); } }
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA new file mode 100644 index 0000000..553f36b --- /dev/null +++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListEmpty.JAVA
@@ -0,0 +1,85 @@ +package com.example.appsearch; + +import androidx.appsearch.app.AppSearchSchema; +import androidx.appsearch.app.DocumentClassFactory; +import androidx.appsearch.app.GenericDocument; +import androidx.appsearch.exceptions.AppSearchException; +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated("androidx.appsearch.compiler.AppSearchCompiler") +public final class $$__AppSearch__Person implements DocumentClassFactory{ + public static final String SCHEMA_NAME = "Person"; + + @Override + public String getSchemaName() { + return SCHEMA_NAME; + } + + @Override + public AppSearchSchema getSchema() throws AppSearchException { + return new AppSearchSchema.Builder(SCHEMA_NAME) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setShouldIndexNestedProperties(false) + .build()) + .build(); + } + + @Override + public List> getDependencyDocumentClasses() throws AppSearchException { + List> classSet = new ArrayList + classSet.add(Address.class); + return classSet; + } + + @Override + public GenericDocument toGenericDocument(Person document) throws AppSearchException { + GenericDocument.Builder> builder = + new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME); + String nameCopy = document.name; + if (nameCopy != null) { + builder.setPropertyString("name", nameCopy); + } + Address livesAtCopy = document.livesAt; + if (livesAtCopy != null) { + GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy); + builder.setPropertyDocument("livesAt", livesAtConv); + } + return builder.build(); + } + + @Override + public Person fromGenericDocument(GenericDocument genericDoc, + Map>(); > documentClassMap) throws AppSearchException { + String namespaceConv = genericDoc.getNamespace(); + String idConv = genericDoc.getId(); + String[] nameCopy = genericDoc.getPropertyStringArray("name"); + String nameConv = null; + if (nameCopy != null && nameCopy.length != 0) { + nameConv = nameCopy[0]; + } + GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt"); + Address livesAtConv = null; + if (livesAtCopy != null) { + livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap); + } + Person document = new Person(); + document.namespace = namespaceConv; + document.id = idConv; + document.name = nameConv; + document.livesAt = livesAtConv; + return document; + } +}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA new file mode 100644 index 0000000..ac8da6b --- /dev/null +++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitInheritance.JAVA
@@ -0,0 +1,105 @@ +package com.example.appsearch; + +import androidx.appsearch.app.AppSearchSchema; +import androidx.appsearch.app.DocumentClassFactory; +import androidx.appsearch.app.GenericDocument; +import androidx.appsearch.exceptions.AppSearchException; +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated("androidx.appsearch.compiler.AppSearchCompiler") +public final class $$__AppSearch__Artist implements DocumentClassFactory{ + public static final String SCHEMA_NAME = "Artist"; + + @Override + public String getSchemaName() { + return SCHEMA_NAME; + } + + @Override + public AppSearchSchema getSchema() throws AppSearchException { + return new AppSearchSchema.Builder(SCHEMA_NAME) + .addParentType($$__AppSearch__Person.SCHEMA_NAME) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setShouldIndexNestedProperties(false) + .addIndexableNestedProperties("streetNumber") + .addIndexableNestedProperties("streetName") + .build()) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .build(); + } + + @Override + public List> getDependencyDocumentClasses() throws AppSearchException { + List> classSet = new ArrayList + classSet.add(Person.class); + classSet.add(Address.class); + return classSet; + } + + @Override + public GenericDocument toGenericDocument(Artist document) throws AppSearchException { + GenericDocument.Builder> builder = + new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME); + String nameCopy = document.name; + if (nameCopy != null) { + builder.setPropertyString("name", nameCopy); + } + Address livesAtCopy = document.livesAt; + if (livesAtCopy != null) { + GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy); + builder.setPropertyDocument("livesAt", livesAtConv); + } + String mostFamousWorkCopy = document.mostFamousWork; + if (mostFamousWorkCopy != null) { + builder.setPropertyString("mostFamousWork", mostFamousWorkCopy); + } + return builder.build(); + } + + @Override + public Artist fromGenericDocument(GenericDocument genericDoc, + Map>(); > documentClassMap) throws AppSearchException { + String namespaceConv = genericDoc.getNamespace(); + String idConv = genericDoc.getId(); + String[] nameCopy = genericDoc.getPropertyStringArray("name"); + String nameConv = null; + if (nameCopy != null && nameCopy.length != 0) { + nameConv = nameCopy[0]; + } + GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt"); + Address livesAtConv = null; + if (livesAtCopy != null) { + livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap); + } + String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork"); + String mostFamousWorkConv = null; + if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) { + mostFamousWorkConv = mostFamousWorkCopy[0]; + } + Artist document = new Artist(); + document.namespace = namespaceConv; + document.id = idConv; + document.name = nameConv; + document.livesAt = livesAtConv; + document.mostFamousWork = mostFamousWorkConv; + return document; + } +}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA new file mode 100644 index 0000000..ce3287d --- /dev/null +++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListImplicitlyInheritFromMultipleLevels.JAVA
@@ -0,0 +1,121 @@ +package com.example.appsearch; + +import androidx.appsearch.app.AppSearchSchema; +import androidx.appsearch.app.DocumentClassFactory; +import androidx.appsearch.app.GenericDocument; +import androidx.appsearch.exceptions.AppSearchException; +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated("androidx.appsearch.compiler.AppSearchCompiler") +public final class $$__AppSearch__ArtistEmployee implements DocumentClassFactory{ + public static final String SCHEMA_NAME = "ArtistEmployee"; + + @Override + public String getSchemaName() { + return SCHEMA_NAME; + } + + @Override + public AppSearchSchema getSchema() throws AppSearchException { + return new AppSearchSchema.Builder(SCHEMA_NAME) + .addParentType($$__AppSearch__Artist.SCHEMA_NAME) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setShouldIndexNestedProperties(false) + .addIndexableNestedProperties("streetNumber") + .addIndexableNestedProperties("streetName") + .build()) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("worksAt") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .build(); + } + + @Override + public List> getDependencyDocumentClasses() throws AppSearchException { + List> classSet = new ArrayList + classSet.add(Artist.class); + classSet.add(Address.class); + return classSet; + } + + @Override + public GenericDocument toGenericDocument(ArtistEmployee document) throws AppSearchException { + GenericDocument.Builder> builder = + new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME); + String nameCopy = document.name; + if (nameCopy != null) { + builder.setPropertyString("name", nameCopy); + } + Address livesAtCopy = document.livesAt; + if (livesAtCopy != null) { + GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy); + builder.setPropertyDocument("livesAt", livesAtConv); + } + String mostFamousWorkCopy = document.mostFamousWork; + if (mostFamousWorkCopy != null) { + builder.setPropertyString("mostFamousWork", mostFamousWorkCopy); + } + String worksAtCopy = document.worksAt; + if (worksAtCopy != null) { + builder.setPropertyString("worksAt", worksAtCopy); + } + return builder.build(); + } + + @Override + public ArtistEmployee fromGenericDocument(GenericDocument genericDoc, + Map>(); > documentClassMap) throws AppSearchException { + String namespaceConv = genericDoc.getNamespace(); + String idConv = genericDoc.getId(); + String[] nameCopy = genericDoc.getPropertyStringArray("name"); + String nameConv = null; + if (nameCopy != null && nameCopy.length != 0) { + nameConv = nameCopy[0]; + } + GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt"); + Address livesAtConv = null; + if (livesAtCopy != null) { + livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap); + } + String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork"); + String mostFamousWorkConv = null; + if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) { + mostFamousWorkConv = mostFamousWorkCopy[0]; + } + String[] worksAtCopy = genericDoc.getPropertyStringArray("worksAt"); + String worksAtConv = null; + if (worksAtCopy != null && worksAtCopy.length != 0) { + worksAtConv = worksAtCopy[0]; + } + ArtistEmployee document = new ArtistEmployee(); + document.namespace = namespaceConv; + document.id = idConv; + document.name = nameConv; + document.livesAt = livesAtConv; + document.mostFamousWork = mostFamousWorkConv; + document.worksAt = worksAtConv; + return document; + } +}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA new file mode 100644 index 0000000..e4d5805 --- /dev/null +++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassFalse.JAVA
@@ -0,0 +1,104 @@ +package com.example.appsearch; + +import androidx.appsearch.app.AppSearchSchema; +import androidx.appsearch.app.DocumentClassFactory; +import androidx.appsearch.app.GenericDocument; +import androidx.appsearch.exceptions.AppSearchException; +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated("androidx.appsearch.compiler.AppSearchCompiler") +public final class $$__AppSearch__Artist implements DocumentClassFactory{ + public static final String SCHEMA_NAME = "Artist"; + + @Override + public String getSchemaName() { + return SCHEMA_NAME; + } + + @Override + public AppSearchSchema getSchema() throws AppSearchException { + return new AppSearchSchema.Builder(SCHEMA_NAME) + .addParentType($$__AppSearch__Person.SCHEMA_NAME) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setShouldIndexNestedProperties(false) + .addIndexableNestedProperties("state") + .build()) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .build(); + } + + @Override + public List> getDependencyDocumentClasses() throws AppSearchException { + List> classSet = new ArrayList + classSet.add(Person.class); + classSet.add(Address.class); + return classSet; + } + + @Override + public GenericDocument toGenericDocument(Artist document) throws AppSearchException { + GenericDocument.Builder> builder = + new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME); + String nameCopy = document.name; + if (nameCopy != null) { + builder.setPropertyString("name", nameCopy); + } + Address livesAtCopy = document.livesAt; + if (livesAtCopy != null) { + GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy); + builder.setPropertyDocument("livesAt", livesAtConv); + } + String mostFamousWorkCopy = document.mostFamousWork; + if (mostFamousWorkCopy != null) { + builder.setPropertyString("mostFamousWork", mostFamousWorkCopy); + } + return builder.build(); + } + + @Override + public Artist fromGenericDocument(GenericDocument genericDoc, + Map>(); > documentClassMap) throws AppSearchException { + String namespaceConv = genericDoc.getNamespace(); + String idConv = genericDoc.getId(); + String[] nameCopy = genericDoc.getPropertyStringArray("name"); + String nameConv = null; + if (nameCopy != null && nameCopy.length != 0) { + nameConv = nameCopy[0]; + } + GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt"); + Address livesAtConv = null; + if (livesAtCopy != null) { + livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap); + } + String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork"); + String mostFamousWorkConv = null; + if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) { + mostFamousWorkConv = mostFamousWorkCopy[0]; + } + Artist document = new Artist(); + document.namespace = namespaceConv; + document.id = idConv; + document.name = nameConv; + document.livesAt = livesAtConv; + document.mostFamousWork = mostFamousWorkConv; + return document; + } +}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA new file mode 100644 index 0000000..1620454 --- /dev/null +++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritSuperclassTrue.JAVA
@@ -0,0 +1,106 @@ +package com.example.appsearch; + +import androidx.appsearch.app.AppSearchSchema; +import androidx.appsearch.app.DocumentClassFactory; +import androidx.appsearch.app.GenericDocument; +import androidx.appsearch.exceptions.AppSearchException; +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated("androidx.appsearch.compiler.AppSearchCompiler") +public final class $$__AppSearch__Artist implements DocumentClassFactory{ + public static final String SCHEMA_NAME = "Artist"; + + @Override + public String getSchemaName() { + return SCHEMA_NAME; + } + + @Override + public AppSearchSchema getSchema() throws AppSearchException { + return new AppSearchSchema.Builder(SCHEMA_NAME) + .addParentType($$__AppSearch__Person.SCHEMA_NAME) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setShouldIndexNestedProperties(false) + .addIndexableNestedProperties("streetNumber") + .addIndexableNestedProperties("state") + .addIndexableNestedProperties("streetName") + .build()) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .build(); + } + + @Override + public List> getDependencyDocumentClasses() throws AppSearchException { + List> classSet = new ArrayList + classSet.add(Person.class); + classSet.add(Address.class); + return classSet; + } + + @Override + public GenericDocument toGenericDocument(Artist document) throws AppSearchException { + GenericDocument.Builder> builder = + new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME); + String nameCopy = document.name; + if (nameCopy != null) { + builder.setPropertyString("name", nameCopy); + } + Address livesAtCopy = document.livesAt; + if (livesAtCopy != null) { + GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy); + builder.setPropertyDocument("livesAt", livesAtConv); + } + String mostFamousWorkCopy = document.mostFamousWork; + if (mostFamousWorkCopy != null) { + builder.setPropertyString("mostFamousWork", mostFamousWorkCopy); + } + return builder.build(); + } + + @Override + public Artist fromGenericDocument(GenericDocument genericDoc, + Map>(); > documentClassMap) throws AppSearchException { + String namespaceConv = genericDoc.getNamespace(); + String idConv = genericDoc.getId(); + String[] nameCopy = genericDoc.getPropertyStringArray("name"); + String nameConv = null; + if (nameCopy != null && nameCopy.length != 0) { + nameConv = nameCopy[0]; + } + GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt"); + Address livesAtConv = null; + if (livesAtCopy != null) { + livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap); + } + String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork"); + String mostFamousWorkConv = null; + if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) { + mostFamousWorkConv = mostFamousWorkCopy[0]; + } + Artist document = new Artist(); + document.namespace = namespaceConv; + document.id = idConv; + document.name = nameConv; + document.livesAt = livesAtConv; + document.mostFamousWork = mostFamousWorkConv; + return document; + } +}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA new file mode 100644 index 0000000..3c4c645 --- /dev/null +++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListInheritWithMultipleParentsClasses.JAVA
@@ -0,0 +1,108 @@ +package com.example.appsearch; + +import androidx.appsearch.app.AppSearchSchema; +import androidx.appsearch.app.DocumentClassFactory; +import androidx.appsearch.app.GenericDocument; +import androidx.appsearch.exceptions.AppSearchException; +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated("androidx.appsearch.compiler.AppSearchCompiler") +public final class $$__AppSearch__ArtistEmployee implements DocumentClassFactory{ + public static final String SCHEMA_NAME = "ArtistEmployee"; + + @Override + public String getSchemaName() { + return SCHEMA_NAME; + } + + @Override + public AppSearchSchema getSchema() throws AppSearchException { + return new AppSearchSchema.Builder(SCHEMA_NAME) + .addParentType($$__AppSearch__Artist.SCHEMA_NAME) + .addParentType($$__AppSearch__Employee.SCHEMA_NAME) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setShouldIndexNestedProperties(false) + .addIndexableNestedProperties("streetNumber") + .addIndexableNestedProperties("state") + .addIndexableNestedProperties("streetName") + .build()) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .build(); + } + + @Override + public List> getDependencyDocumentClasses() throws AppSearchException { + List> classSet = new ArrayList + classSet.add(Artist.class); + classSet.add(Employee.class); + classSet.add(Address.class); + return classSet; + } + + @Override + public GenericDocument toGenericDocument(ArtistEmployee document) throws AppSearchException { + GenericDocument.Builder> builder = + new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME); + String nameCopy = document.name; + if (nameCopy != null) { + builder.setPropertyString("name", nameCopy); + } + Address livesAtCopy = document.livesAt; + if (livesAtCopy != null) { + GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy); + builder.setPropertyDocument("livesAt", livesAtConv); + } + String mostFamousWorkCopy = document.mostFamousWork; + if (mostFamousWorkCopy != null) { + builder.setPropertyString("mostFamousWork", mostFamousWorkCopy); + } + return builder.build(); + } + + @Override + public ArtistEmployee fromGenericDocument(GenericDocument genericDoc, + Map>(); > documentClassMap) throws AppSearchException { + String namespaceConv = genericDoc.getNamespace(); + String idConv = genericDoc.getId(); + String[] nameCopy = genericDoc.getPropertyStringArray("name"); + String nameConv = null; + if (nameCopy != null && nameCopy.length != 0) { + nameConv = nameCopy[0]; + } + GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt"); + Address livesAtConv = null; + if (livesAtCopy != null) { + livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap); + } + String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork"); + String mostFamousWorkConv = null; + if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) { + mostFamousWorkConv = mostFamousWorkCopy[0]; + } + ArtistEmployee document = new ArtistEmployee(); + document.namespace = namespaceConv; + document.id = idConv; + document.name = nameConv; + document.livesAt = livesAtConv; + document.mostFamousWork = mostFamousWorkConv; + return document; + } +}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA new file mode 100644 index 0000000..d7fe77d --- /dev/null +++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListSimple.JAVA
@@ -0,0 +1,87 @@ +package com.example.appsearch; + +import androidx.appsearch.app.AppSearchSchema; +import androidx.appsearch.app.DocumentClassFactory; +import androidx.appsearch.app.GenericDocument; +import androidx.appsearch.exceptions.AppSearchException; +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated("androidx.appsearch.compiler.AppSearchCompiler") +public final class $$__AppSearch__Person implements DocumentClassFactory{ + public static final String SCHEMA_NAME = "Person"; + + @Override + public String getSchemaName() { + return SCHEMA_NAME; + } + + @Override + public AppSearchSchema getSchema() throws AppSearchException { + return new AppSearchSchema.Builder(SCHEMA_NAME) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setShouldIndexNestedProperties(false) + .addIndexableNestedProperties("streetNumber") + .addIndexableNestedProperties("streetName") + .build()) + .build(); + } + + @Override + public List> getDependencyDocumentClasses() throws AppSearchException { + List> classSet = new ArrayList + classSet.add(Address.class); + return classSet; + } + + @Override + public GenericDocument toGenericDocument(Person document) throws AppSearchException { + GenericDocument.Builder> builder = + new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME); + String nameCopy = document.name; + if (nameCopy != null) { + builder.setPropertyString("name", nameCopy); + } + Address livesAtCopy = document.livesAt; + if (livesAtCopy != null) { + GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy); + builder.setPropertyDocument("livesAt", livesAtConv); + } + return builder.build(); + } + + @Override + public Person fromGenericDocument(GenericDocument genericDoc, + Map>(); > documentClassMap) throws AppSearchException { + String namespaceConv = genericDoc.getNamespace(); + String idConv = genericDoc.getId(); + String[] nameCopy = genericDoc.getPropertyStringArray("name"); + String nameConv = null; + if (nameCopy != null && nameCopy.length != 0) { + nameConv = nameCopy[0]; + } + GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt"); + Address livesAtConv = null; + if (livesAtCopy != null) { + livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap); + } + Person document = new Person(); + document.namespace = namespaceConv; + document.id = idConv; + document.name = nameConv; + document.livesAt = livesAtConv; + return document; + } +}
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA new file mode 100644 index 0000000..821dcc4 --- /dev/null +++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testIndexableNestedPropertiesListTopLevelInheritTrue.JAVA
@@ -0,0 +1,122 @@ +package com.example.appsearch; + +import androidx.appsearch.app.AppSearchSchema; +import androidx.appsearch.app.DocumentClassFactory; +import androidx.appsearch.app.GenericDocument; +import androidx.appsearch.exceptions.AppSearchException; +import java.lang.Class; +import java.lang.Override; +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated("androidx.appsearch.compiler.AppSearchCompiler") +public final class $$__AppSearch__ArtistEmployee implements DocumentClassFactory{ + public static final String SCHEMA_NAME = "ArtistEmployee"; + + @Override + public String getSchemaName() { + return SCHEMA_NAME; + } + + @Override + public AppSearchSchema getSchema() throws AppSearchException { + return new AppSearchSchema.Builder(SCHEMA_NAME) + .addParentType($$__AppSearch__Artist.SCHEMA_NAME) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("name") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder("livesAt", $$__AppSearch__Address.SCHEMA_NAME) + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setShouldIndexNestedProperties(false) + .addIndexableNestedProperties("streetNumber") + .addIndexableNestedProperties("state") + .addIndexableNestedProperties("streetName") + .build()) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("mostFamousWork") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .addProperty(new AppSearchSchema.StringPropertyConfig.Builder("worksAt") + .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setTokenizerType(AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_NONE) + .setIndexingType(AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_NONE) + .setJoinableValueType(AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE) + .build()) + .build(); + } + + @Override + public List> getDependencyDocumentClasses() throws AppSearchException { + List> classSet = new ArrayList + classSet.add(Artist.class); + classSet.add(Address.class); + return classSet; + } + + @Override + public GenericDocument toGenericDocument(ArtistEmployee document) throws AppSearchException { + GenericDocument.Builder> builder = + new GenericDocument.Builder<>(document.namespace, document.id, SCHEMA_NAME); + String nameCopy = document.name; + if (nameCopy != null) { + builder.setPropertyString("name", nameCopy); + } + Address livesAtCopy = document.livesAt; + if (livesAtCopy != null) { + GenericDocument livesAtConv = GenericDocument.fromDocumentClass(livesAtCopy); + builder.setPropertyDocument("livesAt", livesAtConv); + } + String mostFamousWorkCopy = document.mostFamousWork; + if (mostFamousWorkCopy != null) { + builder.setPropertyString("mostFamousWork", mostFamousWorkCopy); + } + String worksAtCopy = document.worksAt; + if (worksAtCopy != null) { + builder.setPropertyString("worksAt", worksAtCopy); + } + return builder.build(); + } + + @Override + public ArtistEmployee fromGenericDocument(GenericDocument genericDoc, + Map>(); > documentClassMap) throws AppSearchException { + String namespaceConv = genericDoc.getNamespace(); + String idConv = genericDoc.getId(); + String[] nameCopy = genericDoc.getPropertyStringArray("name"); + String nameConv = null; + if (nameCopy != null && nameCopy.length != 0) { + nameConv = nameCopy[0]; + } + GenericDocument livesAtCopy = genericDoc.getPropertyDocument("livesAt"); + Address livesAtConv = null; + if (livesAtCopy != null) { + livesAtConv = livesAtCopy.toDocumentClass(Address.class, documentClassMap); + } + String[] mostFamousWorkCopy = genericDoc.getPropertyStringArray("mostFamousWork"); + String mostFamousWorkConv = null; + if (mostFamousWorkCopy != null && mostFamousWorkCopy.length != 0) { + mostFamousWorkConv = mostFamousWorkCopy[0]; + } + String[] worksAtCopy = genericDoc.getPropertyStringArray("worksAt"); + String worksAtConv = null; + if (worksAtCopy != null && worksAtCopy.length != 0) { + worksAtConv = worksAtCopy[0]; + } + ArtistEmployee document = new ArtistEmployee(); + document.namespace = namespaceConv; + document.id = idConv; + document.name = nameConv; + document.livesAt = livesAtConv; + document.mostFamousWork = mostFamousWorkConv; + document.worksAt = worksAtConv; + return document; + } +}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt index fe8a394..2f2b284 100644 --- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt +++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -262,7 +262,7 @@ logger.warn( """ A baseline profile was generated for the variant `${variantName.get()}`: - $absolutePath + file:///$absolutePath """.trimIndent() ) } @@ -312,7 +312,7 @@ logger.warn( """ A startup profile was generated for the variant `${variantName.get()}`: - $absolutePath + file:///$absolutePath """.trimIndent() ) }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt index b7686bc..aa4cf94 100644 --- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt +++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -111,7 +111,7 @@ gradleRunner.build("generateBaselineProfile") { val notFound = it.lines().requireInOrder( "A baseline profile was generated for the variant `release`:", - baselineProfileFile("main").canonicalPath + "file:///${baselineProfileFile("main").canonicalPath}" ) assertThat(notFound).isEmpty() } @@ -154,9 +154,9 @@ gradleRunner.build("generateBaselineProfile") { val notFound = it.lines().requireInOrder( "A baseline profile was generated for the variant `release`:", - baselineProfileFile("release").canonicalPath, + "file:///${baselineProfileFile("release").canonicalPath}", "A startup profile was generated for the variant `release`:", - startupProfileFile("release").canonicalPath + "file:///${startupProfileFile("release").canonicalPath}" ) assertThat(notFound).isEmpty() } @@ -237,9 +237,9 @@ val notFound = it.lines().requireInOrder( "A baseline profile was generated for the variant `$variantName`:", - baselineProfileFile(variantName).canonicalPath, + "file:///${baselineProfileFile(variantName).canonicalPath}", "A startup profile was generated for the variant `$variantName`:", - startupProfileFile(variantName).canonicalPath + "file:///${startupProfileFile(variantName).canonicalPath}" ) assertWithMessage(
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt index 87e0497..9aec5bf 100644 --- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt +++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
@@ -749,6 +749,42 @@ } """, ) + + @Test + fun testMemoizationWStableCapture() = verifyGoldenComposeIrTransform( + source = """ + import androidx.compose.runtime.* + + @Composable fun Test(param: String, unstable: List<*>) { + Wrapper { + println(param) + } + } + """, + extra = """ + import androidx.compose.runtime.* + + @Composable fun Wrapper(block: () -> Unit) {} + """, + ) + + @Test + fun testMemoizationWUnstableCapture() = verifyGoldenComposeIrTransform( + source = """ + import androidx.compose.runtime.* + + @Composable fun Test(param: String, unstable: List<*>) { + Wrapper { + println(unstable) + } + } + """, + extra = """ + import androidx.compose.runtime.* + + @Composable fun Wrapper(block: () -> Unit) {} + """, + ) } class RememberIntrinsicTransformTestsStrongSkipping( @@ -795,4 +831,17 @@ @Composable fun Wrapper(block: () -> Unit) {} """, ) + + @Test + fun testRememberWithUnstableParam() = verifyGoldenComposeIrTransform( + source = """ + import androidx.compose.runtime.* + + @Composable fun Test(param: String, unstable: List<*>) { + remember(unstable) { + unstable[0] + } + } + """, + ) }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = false\135.txt" new file mode 100644 index 0000000..f4fb2cc --- /dev/null +++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = false\135.txt"
@@ -0,0 +1,49 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + +@Composable fun Test(param: String, unstable: List<*>) { + Wrapper { + println(param) + } +} + +// +// Transformed IR +// ------------------------------------------ + +@Composable +fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(Test)<{>,:Test.kt") + val %dirty = %changed + if (%changed and 0b1110 == 0) { + %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010 + } + if (%dirty and 0b1011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + Wrapper({ + %composer.startReplaceableGroup(<>) + sourceInformation(%composer, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(%dirty and 0b1110 == 0b0100) { + { + println(param) + } + } + %composer.endReplaceableGroup() + tmp0_group + }, %composer, 0) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001)) + } +}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = true\135.txt" new file mode 100644 index 0000000..f4fb2cc --- /dev/null +++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWStableCapture\133useFir = true\135.txt"
@@ -0,0 +1,49 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + +@Composable fun Test(param: String, unstable: List<*>) { + Wrapper { + println(param) + } +} + +// +// Transformed IR +// ------------------------------------------ + +@Composable +fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(Test)<{>,:Test.kt") + val %dirty = %changed + if (%changed and 0b1110 == 0) { + %dirty = %dirty or if (%composer.changed(param)) 0b0100 else 0b0010 + } + if (%dirty and 0b1011 != 0b0010 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + Wrapper({ + %composer.startReplaceableGroup(<>) + sourceInformation(%composer, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(%dirty and 0b1110 == 0b0100) { + { + println(param) + } + } + %composer.endReplaceableGroup() + tmp0_group + }, %composer, 0) + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001)) + } +}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = false\135.txt" new file mode 100644 index 0000000..aa030ee --- /dev/null +++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = false\135.txt"
@@ -0,0 +1,33 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + +@Composable fun Test(param: String, unstable: List<*>) { + Wrapper { + println(unstable) + } +} + +// +// Transformed IR +// ------------------------------------------ + +@Composable +fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(Test):Test.kt") + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + Wrapper({ + println(unstable) + }, %composer, 0) + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001)) + } +}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = true\135.txt" new file mode 100644 index 0000000..aa030ee --- /dev/null +++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTests/testMemoizationWUnstableCapture\133useFir = true\135.txt"
@@ -0,0 +1,33 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + +@Composable fun Test(param: String, unstable: List<*>) { + Wrapper { + println(unstable) + } +} + +// +// Transformed IR +// ------------------------------------------ + +@Composable +fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(Test):Test.kt") + if (isTraceInProgress()) { + traceEventStart(<>, %changed, -1, <>) + } + Wrapper({ + println(unstable) + }, %composer, 0) + if (isTraceInProgress()) { + traceEventEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001)) + } +}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = false\135.txt" new file mode 100644 index 0000000..9cc440b --- /dev/null +++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = false\135.txt"
@@ -0,0 +1,45 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + +@Composable fun Test(param: String, unstable: List<*>) { + remember(unstable) { + unstable[0] + } +} + +// +// Transformed IR +// ------------------------------------------ + +@Composable +fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(Test):Test.kt") + val %dirty = %changed + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(unstable)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010001 != 0b00010000 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %composer.startReplaceableGroup(<>) + sourceInformation(%composer, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(%composer.changed(unstable)) { + unstable[0] + } + %composer.endReplaceableGroup() + tmp0_group + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001)) + } +}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = true\135.txt" new file mode 100644 index 0000000..9cc440b --- /dev/null +++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.RememberIntrinsicTransformTestsStrongSkipping/testRememberWithUnstableParam\133useFir = true\135.txt"
@@ -0,0 +1,45 @@ +// +// Source +// ------------------------------------------ + +import androidx.compose.runtime.* + +@Composable fun Test(param: String, unstable: List<*>) { + remember(unstable) { + unstable[0] + } +} + +// +// Transformed IR +// ------------------------------------------ + +@Composable +fun Test(param: String, unstable: List<*>, %composer: Composer?, %changed: Int) { + %composer = %composer.startRestartGroup(<>) + sourceInformation(%composer, "C(Test):Test.kt") + val %dirty = %changed + if (%changed and 0b00110000 == 0) { + %dirty = %dirty or if (%composer.changedInstance(unstable)) 0b00100000 else 0b00010000 + } + if (%dirty and 0b00010001 != 0b00010000 || !%composer.skipping) { + if (isTraceInProgress()) { + traceEventStart(<>, %dirty, -1, <>) + } + %composer.startReplaceableGroup(<>) + sourceInformation(%composer, "CC(remember):Test.kt#9igjgp") + val tmp0_group = %composer.cache(%composer.changed(unstable)) { + unstable[0] + } + %composer.endReplaceableGroup() + tmp0_group + if (isTraceInProgress()) { + traceEventEnd() + } + } else { + %composer.skipToGroupEnd() + } + %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int -> + Test(param, unstable, %composer, updateChangedFlags(%changed or 0b0001)) + } +}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt index f7a2f24..117e9f7 100644 --- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt +++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/AbstractComposeLowering.kt
@@ -1204,7 +1204,7 @@ currentComposer: IrExpression, value: IrExpression, inferredStable: Boolean, - strongSkippingEnabled: Boolean + compareInstanceForUnstableValues: Boolean ): IrExpression { // compose has a unique opportunity to avoid inline class boxing for changed calls, since // we know that the only thing that we are detecting here is "changed or not", we can @@ -1222,7 +1222,7 @@ val primitiveDescriptor = type.toPrimitiveType() .let { changedPrimitiveFunctions[it] } - return if (!strongSkippingEnabled) { + return if (!compareInstanceForUnstableValues) { val descriptor = primitiveDescriptor ?: if (type.isFunction()) changedInstanceFunction else changedFunction irMethodCall(currentComposer, descriptor).also {
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt index c18ad5c..409e4ac 100644 --- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt +++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposableFunctionBodyTransformer.kt
@@ -983,7 +983,7 @@ } } - scope.applyIntrinsicRememberFixups { args, metas -> + scope.applyIntrinsicRememberFixups { isMemoizedLambda, args, metas -> // replace dirty with changed param in meta used for inference, as we are not // populating dirty if (!canSkipExecution) { @@ -993,7 +993,7 @@ } } } - irIntrinsicRememberInvalid(args, metas, ::irInferredChanged) + irIntrinsicRememberInvalid(isMemoizedLambda, args, metas, ::irInferredChanged) } if (canSkipExecution) { @@ -1149,7 +1149,7 @@ dirty } else changedParam - scope.applyIntrinsicRememberFixups { args, metas -> + scope.applyIntrinsicRememberFixups { isMemoizedLambda, args, metas -> // replace dirty with changed param in meta used for inference, as we are not // populating dirty if (!canSkipExecution) { @@ -1159,7 +1159,7 @@ } } } - irIntrinsicRememberInvalid(args, metas, ::irInferredChanged) + irIntrinsicRememberInvalid(isMemoizedLambda, args, metas, ::irInferredChanged) } val transformedBody = if (canSkipExecution) { @@ -1636,13 +1636,13 @@ irCurrentComposer(), irGet(param), inferredStable = true, - strongSkippingEnabled = true + compareInstanceForUnstableValues = true ), elsePart = irChanged( irCurrentComposer(), irGet(param), inferredStable = false, - strongSkippingEnabled = true + compareInstanceForUnstableValues = true ) ) } else { @@ -2119,11 +2119,14 @@ return irMethodCall(scope.irCurrentComposer(), endRestartGroupFunction) } - private fun irChanged(value: IrExpression): IrExpression = irChanged( + private fun irChanged( + value: IrExpression, + compareInstanceForUnstableValues: Boolean = strongSkippingEnabled + ): IrExpression = irChanged( irCurrentComposer(), value, inferredStable = false, - strongSkippingEnabled = strongSkippingEnabled + compareInstanceForUnstableValues = compareInstanceForUnstableValues ) private fun irSkipToGroupEnd(startOffset: Int, endOffset: Int): IrExpression { @@ -3033,18 +3036,25 @@ } val usesDirty = inputArgMetas.any { it.maskParam is IrChangedBitMaskVariable } + val isMemoizedLambda = expression.origin == ComposeMemoizedLambdaOrigin + // We can only rely on the $changed or $dirty if the flags are correctly updated in // the restart function or the result of replacing remember with cached will be // different. val metaMaskConsistent = updateChangedFlagsFunction != null - val changedFunction: (IrExpression, ParamMeta) -> IrExpression? = + val changedFunction: (Boolean, IrExpression, ParamMeta) -> IrExpression? = if (usesDirty || !metaMaskConsistent) { - { arg, _ -> irChanged(arg) } + { _, arg, _ -> irChanged(arg, compareInstanceForUnstableValues = isMemoizedLambda) } } else { ::irInferredChanged } - val invalidExpr = irIntrinsicRememberInvalid(inputArgs, inputArgMetas, changedFunction) + val invalidExpr = irIntrinsicRememberInvalid( + isMemoizedLambda, + inputArgs, + inputArgMetas, + changedFunction + ) val functionScope = currentFunctionScope val cacheCall = irCache( irCurrentComposer(), @@ -3056,6 +3066,7 @@ ) if (usesDirty && metaMaskConsistent) { functionScope.recordIntrinsicRememberFixUp( + isMemoizedLambda, inputArgs, inputArgMetas, cacheCall @@ -3103,16 +3114,21 @@ } private fun irIntrinsicRememberInvalid( + isMemoizedLambda: Boolean, args: List, metas: List, - changedExpr: (IrExpression, ParamMeta) -> IrExpression? + changedExpr: (Boolean, IrExpression, ParamMeta) -> IrExpression? ): IrExpression = args - .mapIndexedNotNull { i, arg -> changedExpr(arg, metas[i]) } + .mapIndexedNotNull { i, arg -> changedExpr(isMemoizedLambda, arg, metas[i]) } .reduceOrNull { acc, changed -> irBooleanOr(acc, changed) } ?: irConst(false) - private fun irInferredChanged(arg: IrExpression, meta: ParamMeta): IrExpression? { + private fun irInferredChanged( + isMemoizedLambda: Boolean, + arg: IrExpression, + meta: ParamMeta + ): IrExpression? { val param = meta.maskParam return when { meta.isStatic -> null @@ -3144,7 +3160,7 @@ val stableBits = param.irSlotAnd(meta.maskSlot, StabilityBits.UNSTABLE.bits) val maskIsUnstableAndChanged = irAndAnd( irNotEqual(stableBits, irConst(0)), - irChanged(arg) + irChanged(arg, compareInstanceForUnstableValues = isMemoizedLambda) ) irOrOr( maskIsStableAndDifferent, @@ -3173,7 +3189,7 @@ irOrOr( irAndAnd( maskIsUnstableOrUncertain, - irChanged(arg) + irChanged(arg, compareInstanceForUnstableValues = isMemoizedLambda) ), irEqual( param.irIsolateBitsAtSlot(meta.maskSlot, includeStableBit = false), @@ -3181,7 +3197,7 @@ ) ) } - else -> irChanged(arg) + else -> irChanged(arg, compareInstanceForUnstableValues = isMemoizedLambda) } } @@ -3936,6 +3952,7 @@ } private class IntrinsicRememberFixup( + val isMemoizedLambda: Boolean, val args: List, val metas: List, val call: IrCall @@ -3943,13 +3960,16 @@ private val intrinsicRememberFixups = mutableListOf() fun recordIntrinsicRememberFixUp( + isMemoizedLambda: Boolean, args: List, metas: List, call: IrCall ) { val dirty = metas.find { it.maskParam is IrChangedBitMaskVariable } if (dirty?.maskParam == this.dirty) { - intrinsicRememberFixups.add(IntrinsicRememberFixup(args, metas, call)) + intrinsicRememberFixups.add( + IntrinsicRememberFixup(isMemoizedLambda, args, metas, call) + ) } else { // capturing dirty is only allowed from inline function context, which doesn't // have dirty params. @@ -3957,15 +3977,19 @@ // means that we should apply the fixup higher in the tree. var scope = parent while (scope !is FunctionScope) scope = scope!!.parent - scope.recordIntrinsicRememberFixUp(args, metas, call) + scope.recordIntrinsicRememberFixUp(isMemoizedLambda, args, metas, call) } } fun applyIntrinsicRememberFixups( - invalidExpr: (List, List + invalidExpr: ( + isMemoizedLambda: Boolean, + List) -> IrExpression , + List+ ) -> IrExpression ) { intrinsicRememberFixups.forEach { - val invalid = invalidExpr(it.args, it.metas) + val invalid = invalidExpr(it.isMemoizedLambda, it.args, it.metas) // $composer.cache(invalid, calc) it.call.putValueArgument(0, invalid) }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt index c727084..ff49298 100644 --- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt +++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -996,7 +996,8 @@ return irBuilder.irCall( callee = rememberFunctionSymbol, - type = expression.type + type = expression.type, + origin = ComposeMemoizedLambdaOrigin ).apply { // The result type type parameter is first, followed by the argument types putTypeArgument(0, expression.type) @@ -1042,7 +1043,7 @@ irCurrentComposer(), value, inferredStable = false, - strongSkippingEnabled = strongSkippingModeEnabled + compareInstanceForUnstableValues = strongSkippingModeEnabled ) private fun IrValueDeclaration.isVar(): Boolean = @@ -1135,3 +1136,5 @@ // This must match the highest value of FunctionXX which is current Function22 private const val MAX_RESTART_ARGUMENT_COUNT = 22 + +internal object ComposeMemoizedLambdaOrigin : IrStatementOrigin
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt index c7eae7a..f83cf84 100644 --- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt +++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/FontScalingScreenshotTest.kt
@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.testutils.assertAgainstGolden -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.captureToImage @@ -41,7 +40,6 @@ import androidx.compose.ui.text.style.LineHeightStyle import androidx.compose.ui.text.style.LineHeightStyle.Alignment import androidx.compose.ui.text.style.LineHeightStyle.Trim -import androidx.compose.ui.unit.DisableNonLinearFontScalingInCompose import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp @@ -58,7 +56,6 @@ @MediumTest @RunWith(AndroidJUnit4::class) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) -@OptIn(ExperimentalComposeUiApi::class) class FontScalingScreenshotTest { @get:Rule val rule = createAndroidComposeRule() @@ -71,7 +68,6 @@ @After fun teardown() { AndroidFontScaleHelper.resetSystemFontScale(rule.activityRule.scenario) - DisableNonLinearFontScalingInCompose = false } @Test @@ -184,40 +180,6 @@ .assertAgainstGolden(screenshotRule, "fontScaling2x_drawText") } - @Test - fun fontScaling2x_DisableNonLinearFontScalingFlag_lineHeightDoubleSp() { - DisableNonLinearFontScalingInCompose = true - AndroidFontScaleHelper.setSystemFontScale(2f, rule.activityRule.scenario) - rule.waitForIdle() - - rule.setContent { - TestLayout(lineHeight = 28.sp) - } - rule.onNodeWithTag(containerTag) - .captureToImage() - .assertAgainstGolden( - screenshotRule, - "fontScaling2x_DisableNonLinearFontScalingFlag_lineHeightDoubleSp" - ) - } - - @Test - fun fontScaling2x_DisableNonLinearFontScalingFlag_drawText() { - DisableNonLinearFontScalingInCompose = true - AndroidFontScaleHelper.setSystemFontScale(2f, rule.activityRule.scenario) - rule.waitForIdle() - - rule.setContent { - TestDrawTextLayout() - } - rule.onNodeWithTag(containerTag) - .captureToImage() - .assertAgainstGolden( - screenshotRule, - "fontScaling2x_DisableNonLinearFontScalingFlag_drawText" - ) - } - @Composable private fun TestLayout( lineHeight: TextUnit,
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt index 3c26f67..b49c3b3 100644 --- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt +++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
@@ -105,6 +105,18 @@ examples = CardExamples ) +private val Carousel = Component( + id = nextId(), + name = "Carousel", + description = "Carousels are stylized versions of lists that provide a unique viewing and " + + "behavior that suit large imagery and other visually rich content.", + // No carousel icon + guidelinesUrl = "$StyleGuidelinesUrl/carousel", + docsUrl = "$PackageSummaryUrl#carousel", + sourceUrl = "$Material3SourceUrl/Carousel.kt", + examples = CarouselExamples +) + private val Checkboxes = Component( id = nextId(), name = "Checkboxes", @@ -414,6 +426,7 @@ BottomSheets, Buttons, Card, + Carousel, Checkboxes, Chips, DatePickers,
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt index 63dc67e..08cafdb 100644 --- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt +++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -35,6 +35,7 @@ import androidx.compose.material3.samples.ButtonSample import androidx.compose.material3.samples.ButtonWithIconSample import androidx.compose.material3.samples.CardSample +import androidx.compose.material3.samples.CarouselSample import androidx.compose.material3.samples.CheckboxSample import androidx.compose.material3.samples.CheckboxWithTextSample import androidx.compose.material3.samples.ChipGroupReflowSample @@ -286,6 +287,18 @@ } ) +private const val CarouselExampleDescription = "Carousel examples" +private const val CarouselExampleSourceUrl = "$SampleSourceUrl/CarouselSamples.kt" +val CarouselExamples = listOf( + Example( + name = ::CarouselSample.name, + description = CarouselExampleDescription, + sourceUrl = CarouselExampleSourceUrl + ) { + CarouselSample() + } +) + private const val CheckboxesExampleDescription = "Checkboxes examples" private const val CheckboxesExampleSourceUrl = "$SampleSourceUrl/CheckboxSamples.kt" val CheckboxesExamples = listOf(
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt new file mode 100644 index 0000000..a55a9bd --- /dev/null +++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/CarouselSamples.kt
@@ -0,0 +1,74 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.material3.samples + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +fun CarouselSample() { + data class CarouselItem( + val id: Int, + @DrawableRes val imageResId: Int, + @StringRes val contentDescriptionResId: Int + ) + + val Items = listOf( + CarouselItem(0, R.drawable.carousel_image_1, R.string.carousel_image_1_description), + CarouselItem(1, R.drawable.carousel_image_2, R.string.carousel_image_2_description), + CarouselItem(2, R.drawable.carousel_image_3, R.string.carousel_image_3_description), + CarouselItem(3, R.drawable.carousel_image_4, R.string.carousel_image_4_description), + CarouselItem(4, R.drawable.carousel_image_5, R.string.carousel_image_5_description), + ) + + LazyRow( + modifier = Modifier.fillMaxWidth(), + state = rememberLazyListState() + ) { + itemsIndexed(Items) { _, item -> + Card( + modifier = Modifier + .width(350.dp) + .height(200.dp), + ) { + Image( + painter = painterResource(id = item.imageResId), + contentDescription = stringResource(item.contentDescriptionResId), + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop + ) + } + } + } +}
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_1.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_1.jpg new file mode 100644 index 0000000..b02612e --- /dev/null +++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_1.jpg Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_2.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_2.jpg new file mode 100644 index 0000000..73162bb --- /dev/null +++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_2.jpg Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_3.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_3.jpg new file mode 100644 index 0000000..d31f632 --- /dev/null +++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_3.jpg Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_4.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_4.jpg new file mode 100644 index 0000000..8362062 --- /dev/null +++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_4.jpg Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_5.jpg b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_5.jpg new file mode 100644 index 0000000..f5eb364 --- /dev/null +++ b/compose/material3/material3/samples/src/main/res/drawable-nodpi/carousel_image_5.jpg Binary files differ
diff --git a/compose/material3/material3/samples/src/main/res/values/strings.xml b/compose/material3/material3/samples/src/main/res/values/strings.xml new file mode 100644 index 0000000..b52b1ea --- /dev/null +++ b/compose/material3/material3/samples/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@ + + + ++ A racecar +Dotonburi, Osaka +The Grand Canyon +A rock structure with its reflection mirrored over the sea +A car on a long stretch of desert road + \ No newline at end of file
diff --git a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml index 0d2dad7..bc6bb20 100644 --- a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml +++ b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
@@ -61,8 +61,8 @@"Desprega o panel inferior" "Cadro de información" "Mostrar o cadro de información" -"PM" -"AM" +"p.m." +"a.m." "Selecciona a.m. ou p.m." "Selecciona a hora" "Selecciona os minutos"
diff --git a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml index 9810ca8..91ff286 100644 --- a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml +++ b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
@@ -61,8 +61,8 @@"Extinde foaia din partea de jos" "Balon explicativ" "Afișează balonul explicativ" -"PM" -"AM" +"p.m." +"a.m." "Selectează AM sau PM" "Selectează ora" "Selectează minutele"
diff --git a/compose/ui/ui-unit/api/current.ignore b/compose/ui/ui-unit/api/current.ignore new file mode 100644 index 0000000..0e3bda5 --- /dev/null +++ b/compose/ui/ui-unit/api/current.ignore
@@ -0,0 +1,3 @@ +// Baseline format: 1.0 +RemovedClass: androidx.compose.ui.unit.FontScalingKt: + Removed class androidx.compose.ui.unit.FontScalingKt
diff --git a/compose/ui/ui-unit/api/current.txt b/compose/ui/ui-unit/api/current.txt index eba7f2c..c7a1cfb 100644 --- a/compose/ui/ui-unit/api/current.txt +++ b/compose/ui/ui-unit/api/current.txt
@@ -199,12 +199,6 @@ property public abstract float fontScale; } - public final class FontScalingKt { - method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getDisableNonLinearFontScalingInCompose(); - method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setDisableNonLinearFontScalingInCompose(boolean); - property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean DisableNonLinearFontScalingInCompose; - } - @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset { method @androidx.compose.runtime.Stable public operator int component1(); method @androidx.compose.runtime.Stable public operator int component2();
diff --git a/compose/ui/ui-unit/api/restricted_current.ignore b/compose/ui/ui-unit/api/restricted_current.ignore new file mode 100644 index 0000000..0e3bda5 --- /dev/null +++ b/compose/ui/ui-unit/api/restricted_current.ignore
@@ -0,0 +1,3 @@ +// Baseline format: 1.0 +RemovedClass: androidx.compose.ui.unit.FontScalingKt: + Removed class androidx.compose.ui.unit.FontScalingKt
diff --git a/compose/ui/ui-unit/api/restricted_current.txt b/compose/ui/ui-unit/api/restricted_current.txt index e9b932e..4065f7f 100644 --- a/compose/ui/ui-unit/api/restricted_current.txt +++ b/compose/ui/ui-unit/api/restricted_current.txt
@@ -199,12 +199,6 @@ property public abstract float fontScale; } - public final class FontScalingKt { - method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static boolean getDisableNonLinearFontScalingInCompose(); - method @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static void setDisableNonLinearFontScalingInCompose(boolean); - property @SuppressCompatibility @androidx.compose.ui.ExperimentalComposeUiApi public static final boolean DisableNonLinearFontScalingInCompose; - } - @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class IntOffset { method @androidx.compose.runtime.Stable public operator int component1(); method @androidx.compose.runtime.Stable public operator int component2();
diff --git a/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/AndroidDensity.android.kt b/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/AndroidDensity.android.kt index c52998f..37e1c09 100644 --- a/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/AndroidDensity.android.kt +++ b/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/AndroidDensity.android.kt
@@ -17,7 +17,6 @@ package androidx.compose.ui.unit import android.content.Context -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.unit.fontscaling.FontScaleConverter import androidx.compose.ui.unit.fontscaling.FontScaleConverterFactory @@ -26,16 +25,12 @@ * * @param context density values will be extracted from this [Context] */ -@OptIn(ExperimentalComposeUiApi::class) fun Density(context: Context): Density { val fontScale = context.resources.configuration.fontScale - val converter = if (DisableNonLinearFontScalingInCompose) LinearFontScaleConverter(fontScale) - else FontScaleConverterFactory.forScale(fontScale) ?: LinearFontScaleConverter(fontScale) - return DensityWithConverter( context.resources.displayMetrics.density, fontScale, - converter + FontScaleConverterFactory.forScale(fontScale) ?: LinearFontScaleConverter(fontScale) ) }
diff --git a/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/FontScaling.android.kt b/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/FontScaling.android.kt index e9f3565..e18c850 100644 --- a/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/FontScaling.android.kt +++ b/compose/ui/ui-unit/src/androidMain/kotlin/androidx/compose/ui/unit/FontScaling.android.kt
@@ -18,7 +18,6 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.unit.fontscaling.FontScaleConverterFactory import androidx.compose.ui.unit.internal.JvmDefaultWithCompatibility @@ -31,7 +30,6 @@ */ @Immutable @JvmDefaultWithCompatibility -@OptIn(ExperimentalComposeUiApi::class) actual interface FontScaling { /** * Current user preference for the scaling factor for fonts. @@ -44,8 +42,7 @@ */ @Stable actual fun Dp.toSp(): TextUnit { - if (!FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale) || - DisableNonLinearFontScalingInCompose) { + if (!FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale)) { return (value / fontScale).sp } @@ -60,8 +57,7 @@ @Stable actual fun TextUnit.toDp(): Dp { check(type == TextUnitType.Sp) { "Only Sp can convert to Px" } - if (!FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale) || - DisableNonLinearFontScalingInCompose) { + if (!FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale)) { return Dp(value * fontScale) }
diff --git a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/FontScaling.kt b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/FontScaling.kt index 65d23a8..68c3238 100644 --- a/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/FontScaling.kt +++ b/compose/ui/ui-unit/src/commonMain/kotlin/androidx/compose/ui/unit/FontScaling.kt
@@ -19,30 +19,9 @@ import androidx.annotation.RestrictTo import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.unit.internal.JvmDefaultWithCompatibility /** - * Flag indicating if [Density] will use non-linear font scaling for Compose. - * - * Set this flag to true to keep the old linear font scaling behavior. Note that this only affects - * Compose. Views always use non-linear font scaling in Android 14 and later. - * - * This flag will be removed in Compose 1.6.0-beta01. If you encounter any issues with the - * new behavior, please file an issue at: issuetracker.google.com/issues/new?component=779818 - */ -// TODO(b/300538470): Remove flag before beta -@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET") -@get:Suppress("GetterSetterNames") -@set:ExperimentalComposeUiApi -@get:ExperimentalComposeUiApi -@ExperimentalComposeUiApi -var DisableNonLinearFontScalingInCompose by mutableStateOf(false) - -/** * Converts [TextUnit] to [Dp] and vice-versa. * * If you are implementing this interface yourself on Android, please check the docs for important
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java index 6601791..ba339c6 100644 --- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java +++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
@@ -19,6 +19,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -198,6 +199,51 @@ assertTrue(onRouteEnabledLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } + @Test + @MediumTest + public void addUserRouteFromMr1_isSystemRoute_returnsFalse() throws Exception { + getInstrumentation() + .runOnMainSync( + () -> { + android.media.MediaRouter mediaRouter1 = + (android.media.MediaRouter) + mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); + + android.media.MediaRouter.RouteCategory sampleRouteCategory = + mediaRouter1.createRouteCategory( + "SAMPLE_ROUTE_CATEGORY", /* isGroupable= */ false); + + android.media.MediaRouter.UserRouteInfo sampleUserRoute = + mediaRouter1.createUserRoute(sampleRouteCategory); + sampleUserRoute.setName("SAMPLE_USER_ROUTE"); + + mediaRouter1.addUserRoute(sampleUserRoute); + + for (RouteInfo routeInfo : mRouter.getRoutes()) { + // We are checking for this route using getRoutes rather than + // through the onRouteAdded callback because of b/312700919 + if (routeInfo.getName().equals("SAMPLE_USER_ROUTE")) { + assertFalse(routeInfo.isSystemRoute()); + } + } + }); + + } + + @Test + @MediumTest + public void defaultAndBluetoothRoutes_isSystemRoute_returnsTrue() { + getInstrumentation() + .runOnMainSync( + () -> { + for (RouteInfo routeInfo : mRouter.getRoutes()) { + if (routeInfo.isDefaultOrBluetooth()) { + assertTrue(routeInfo.isSystemRoute()); + } + } + }); + } + @SmallTest @Test public void setRouteVolume_onStaticNonGroupRoute() {
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java index 2d3bdfb..7cf8cff 100644 --- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java +++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/SystemMediaRouteProvider.java
@@ -516,7 +516,7 @@ builder.setVolume(record.mRoute.getVolume()); builder.setVolumeMax(record.mRoute.getVolumeMax()); builder.setVolumeHandling(record.mRoute.getVolumeHandling()); - builder.setIsSystemRoute(true); + builder.setIsSystemRoute((supportedTypes & ROUTE_TYPE_USER) == 0); if (!record.mRoute.isEnabled()) { builder.setEnabled(false);