From 6f05a97c22b1c4fe8fc39f5ee9d2852b2115a4da Mon Sep 17 00:00:00 2001 From: Vladimir Kryachko Date: Thu, 25 Nov 2021 15:49:43 -0500 Subject: [PATCH 1/3] Add qualifier support to firebase components. Details: go/firebase-component-qualifiers --- firebase-common/firebase-common.gradle | 2 +- .../AbstractComponentContainer.java | 34 -------- .../google/firebase/components/Component.java | 63 +++++++++++++-- .../components/ComponentContainer.java | 38 +++++++-- .../firebase/components/ComponentRuntime.java | 20 ++--- .../firebase/components/CycleDetector.java | 6 +- .../firebase/components/Dependency.java | 80 ++++++++++++++++++- .../google/firebase/components/Qualified.java | 64 +++++++++++++++ .../RestrictedComponentContainer.java | 66 ++++++++++----- .../components/ComponentRuntimeTest.java | 41 +++++++++- .../firebase/components/ComponentTest.java | 43 ++++++++-- .../components/CycleDetectorTest.java | 43 +++++++++- .../firebase/components/DependencyTest.java | 70 ++++++++++++++-- .../RestrictedComponentContainerTest.java | 24 ++++-- 14 files changed, 488 insertions(+), 106 deletions(-) delete mode 100644 firebase-components/src/main/java/com/google/firebase/components/AbstractComponentContainer.java create mode 100644 firebase-components/src/main/java/com/google/firebase/components/Qualified.java diff --git a/firebase-common/firebase-common.gradle b/firebase-common/firebase-common.gradle index ac5c0a9c218..47726702701 100644 --- a/firebase-common/firebase-common.gradle +++ b/firebase-common/firebase-common.gradle @@ -62,7 +62,7 @@ android { } dependencies { - // TODO(vkryachko): have sdks depend on components directly once components are released. + implementation project(':firebase-annotations') implementation project(':firebase-components') implementation 'com.google.android.gms:play-services-basement:18.1.0' implementation "com.google.android.gms:play-services-tasks:18.0.1" diff --git a/firebase-components/src/main/java/com/google/firebase/components/AbstractComponentContainer.java b/firebase-components/src/main/java/com/google/firebase/components/AbstractComponentContainer.java deleted file mode 100644 index 143adf0acc7..00000000000 --- a/firebase-components/src/main/java/com/google/firebase/components/AbstractComponentContainer.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 Google LLC -// -// 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 com.google.firebase.components; - -import com.google.firebase.inject.Provider; -import java.util.Set; - -abstract class AbstractComponentContainer implements ComponentContainer { - @Override - public T get(Class anInterface) { - Provider provider = getProvider(anInterface); - if (provider == null) { - return null; - } - return provider.get(); - } - - @Override - public Set setOf(Class anInterface) { - return setOfProvider(anInterface).get(); - } -} diff --git a/firebase-components/src/main/java/com/google/firebase/components/Component.java b/firebase-components/src/main/java/com/google/firebase/components/Component.java index 3e2590fde09..768baa48568 100644 --- a/firebase-components/src/main/java/com/google/firebase/components/Component.java +++ b/firebase-components/src/main/java/com/google/firebase/components/Component.java @@ -80,7 +80,7 @@ public final class Component { } private final String name; - private final Set> providedInterfaces; + private final Set> providedInterfaces; private final Set dependencies; private final @Instantiation int instantiation; private final @ComponentType int type; @@ -89,7 +89,7 @@ public final class Component { private Component( @Nullable String name, - Set> providedInterfaces, + Set> providedInterfaces, Set dependencies, @Instantiation int instantiation, @ComponentType int type, @@ -119,7 +119,7 @@ public String getName() { * *

Note: T conforms to all of these interfaces. */ - public Set> getProvidedInterfaces() { + public Set> getProvidedInterfaces() { return providedInterfaces; } @@ -202,6 +202,18 @@ public static Component.Builder builder( return new Builder<>(anInterface, additionalInterfaces); } + /** Returns a Component builder. */ + public static Component.Builder builder(Qualified anInterface) { + return new Builder<>(anInterface); + } + + /** Returns a Component builder. */ + @SafeVarargs + public static Component.Builder builder( + Qualified anInterface, Qualified... additionalInterfaces) { + return new Builder<>(anInterface, additionalInterfaces); + } + /** * Wraps a value in a {@link Component} with no dependencies. * @@ -219,6 +231,13 @@ public static Component of( return builder(anInterface, additionalInterfaces).factory((args) -> value).build(); } + /** Wraps a value in a {@link Component} with no dependencies. */ + @SafeVarargs + public static Component of( + T value, Qualified anInterface, Qualified... additionalInterfaces) { + return builder(anInterface, additionalInterfaces).factory((args) -> value).build(); + } + /** * Provides a builder for a {@link Set}-multibinding {@link Component}. * @@ -229,6 +248,16 @@ public static Component.Builder intoSetBuilder(Class anInterface) { return builder(anInterface).intoSet(); } + /** + * Provides a builder for a {@link Set}-multibinding {@link Component}. + * + *

Such components can be requested by dependents via {@link ComponentContainer#setOf(Class)} * + * or {@link ComponentContainer#setOfProvider(Class)}. + */ + public static Component.Builder intoSetBuilder(Qualified anInterface) { + return builder(anInterface).intoSet(); + } + /** * Wraps a value in a {@link Set}-multibinding {@link Component} with no dependencies. * * @@ -239,22 +268,42 @@ public static Component intoSet(T value, Class anInterface) { return intoSetBuilder(anInterface).factory(c -> value).build(); } + /** + * Wraps a value in a {@link Set}-multibinding {@link Component} with no dependencies. * + * + *

Such components can be requested by dependents via {@link ComponentContainer#setOf(Class)} * + * or {@link ComponentContainer#setOfProvider(Class)}. + */ + public static Component intoSet(T value, Qualified anInterface) { + return intoSetBuilder(anInterface).factory(c -> value).build(); + } + /** FirebaseComponent builder. */ public static class Builder { private String name = null; - private final Set> providedInterfaces = new HashSet<>(); + private final Set> providedInterfaces = new HashSet<>(); private final Set dependencies = new HashSet<>(); private @Instantiation int instantiation = Instantiation.LAZY; private @ComponentType int type = ComponentType.VALUE; private ComponentFactory factory; - private Set> publishedEvents = new HashSet<>(); + private final Set> publishedEvents = new HashSet<>(); @SafeVarargs private Builder(Class anInterface, Class... additionalInterfaces) { Preconditions.checkNotNull(anInterface, "Null interface"); - providedInterfaces.add(anInterface); + providedInterfaces.add(Qualified.unqualified(anInterface)); for (Class iface : additionalInterfaces) { Preconditions.checkNotNull(iface, "Null interface"); + providedInterfaces.add(Qualified.unqualified(iface)); + } + } + + @SafeVarargs + private Builder(Qualified anInterface, Qualified... additionalInterfaces) { + Preconditions.checkNotNull(anInterface, "Null interface"); + providedInterfaces.add(anInterface); + for (Qualified iface : additionalInterfaces) { + Preconditions.checkNotNull(iface, "Null interface"); } Collections.addAll(providedInterfaces, additionalInterfaces); } @@ -301,7 +350,7 @@ private Builder setInstantiation(@Instantiation int instantiation) { return this; } - private void validateInterface(Class anInterface) { + private void validateInterface(Qualified anInterface) { Preconditions.checkArgument( !providedInterfaces.contains(anInterface), "Components are not allowed to depend on interfaces they themselves provide."); diff --git a/firebase-components/src/main/java/com/google/firebase/components/ComponentContainer.java b/firebase-components/src/main/java/com/google/firebase/components/ComponentContainer.java index d5c349eea9c..0f301eb0e19 100644 --- a/firebase-components/src/main/java/com/google/firebase/components/ComponentContainer.java +++ b/firebase-components/src/main/java/com/google/firebase/components/ComponentContainer.java @@ -20,13 +20,41 @@ /** Provides a means to retrieve instances of requested classes/interfaces. */ public interface ComponentContainer { - T get(Class anInterface); + default T get(Class anInterface) { + return get(Qualified.unqualified(anInterface)); + } - Provider getProvider(Class anInterface); + default Provider getProvider(Class anInterface) { + return getProvider(Qualified.unqualified(anInterface)); + } - Deferred getDeferred(Class anInterface); + default Deferred getDeferred(Class anInterface) { + return getDeferred(Qualified.unqualified(anInterface)); + } - Set setOf(Class anInterface); + default Set setOf(Class anInterface) { + return setOf(Qualified.unqualified(anInterface)); + } - Provider> setOfProvider(Class anInterface); + default Provider> setOfProvider(Class anInterface) { + return setOfProvider(Qualified.unqualified(anInterface)); + } + + default T get(Qualified anInterface) { + Provider provider = getProvider(anInterface); + if (provider == null) { + return null; + } + return provider.get(); + } + + Provider getProvider(Qualified anInterface); + + Deferred getDeferred(Qualified anInterface); + + default Set setOf(Qualified anInterface) { + return setOfProvider(anInterface).get(); + } + + Provider> setOfProvider(Qualified anInterface); } diff --git a/firebase-components/src/main/java/com/google/firebase/components/ComponentRuntime.java b/firebase-components/src/main/java/com/google/firebase/components/ComponentRuntime.java index 895b19fff15..a560cf5b502 100644 --- a/firebase-components/src/main/java/com/google/firebase/components/ComponentRuntime.java +++ b/firebase-components/src/main/java/com/google/firebase/components/ComponentRuntime.java @@ -43,11 +43,11 @@ *

Does {@link Component} dependency resolution and provides access to resolved {@link * Component}s via {@link #get(Class)} method. */ -public class ComponentRuntime extends AbstractComponentContainer implements ComponentLoader { +public class ComponentRuntime implements ComponentContainer, ComponentLoader { private static final Provider> EMPTY_PROVIDER = Collections::emptySet; private final Map, Provider> components = new HashMap<>(); - private final Map, Provider> lazyInstanceMap = new HashMap<>(); - private final Map, LazySet> lazySetMap = new HashMap<>(); + private final Map, Provider> lazyInstanceMap = new HashMap<>(); + private final Map, LazySet> lazySetMap = new HashMap<>(); private final List> unprocessedRegistrarProviders; private final EventBus eventBus; private final AtomicReference eagerComponentsInitializedWith = new AtomicReference<>(); @@ -184,7 +184,7 @@ private List processInstanceComponents(List> componentsTo } Provider provider = components.get(component); - for (Class anInterface : component.getProvidedInterfaces()) { + for (Qualified anInterface : component.getProvidedInterfaces()) { if (!lazyInstanceMap.containsKey(anInterface)) { lazyInstanceMap.put(anInterface, provider); } else { @@ -203,7 +203,7 @@ private List processInstanceComponents(List> componentsTo /** Populates lazySetMap to make set components available for consumption via set dependencies. */ private List processSetComponents() { ArrayList runnables = new ArrayList<>(); - Map, Set>> setIndex = new HashMap<>(); + Map, Set>> setIndex = new HashMap<>(); for (Map.Entry, Provider> entry : components.entrySet()) { Component component = entry.getKey(); @@ -214,7 +214,7 @@ private List processSetComponents() { Provider provider = entry.getValue(); - for (Class anInterface : component.getProvidedInterfaces()) { + for (Qualified anInterface : component.getProvidedInterfaces()) { if (!setIndex.containsKey(anInterface)) { setIndex.put(anInterface, new HashSet<>()); } @@ -222,7 +222,7 @@ private List processSetComponents() { } } - for (Map.Entry, Set>> entry : setIndex.entrySet()) { + for (Map.Entry, Set>> entry : setIndex.entrySet()) { if (!lazySetMap.containsKey(entry.getKey())) { lazySetMap.put(entry.getKey(), LazySet.fromCollection(entry.getValue())); } else { @@ -240,13 +240,13 @@ private List processSetComponents() { @Override @SuppressWarnings("unchecked") - public synchronized Provider getProvider(Class anInterface) { + public synchronized Provider getProvider(Qualified anInterface) { Preconditions.checkNotNull(anInterface, "Null interface requested."); return (Provider) lazyInstanceMap.get(anInterface); } @Override - public Deferred getDeferred(Class anInterface) { + public Deferred getDeferred(Qualified anInterface) { Provider provider = getProvider(anInterface); if (provider == null) { return OptionalProvider.empty(); @@ -259,7 +259,7 @@ public Deferred getDeferred(Class anInterface) { @Override @SuppressWarnings("unchecked") - public synchronized Provider> setOfProvider(Class anInterface) { + public synchronized Provider> setOfProvider(Qualified anInterface) { LazySet provider = lazySetMap.get(anInterface); if (provider != null) { return (Provider>) (Provider) provider; diff --git a/firebase-components/src/main/java/com/google/firebase/components/CycleDetector.java b/firebase-components/src/main/java/com/google/firebase/components/CycleDetector.java index a435d6f3a8c..38d7124c11c 100644 --- a/firebase-components/src/main/java/com/google/firebase/components/CycleDetector.java +++ b/firebase-components/src/main/java/com/google/firebase/components/CycleDetector.java @@ -24,10 +24,10 @@ /** Cycle detector for the {@link Component} dependency graph. */ class CycleDetector { private static class Dep { - private final Class anInterface; + private final Qualified anInterface; private final boolean set; - private Dep(Class anInterface, boolean set) { + private Dep(Qualified anInterface, boolean set) { this.anInterface = anInterface; this.set = set; } @@ -135,7 +135,7 @@ private static Set toGraph(List> components) { Map> componentIndex = new HashMap<>(components.size()); for (Component component : components) { ComponentNode node = new ComponentNode(component); - for (Class anInterface : component.getProvidedInterfaces()) { + for (Qualified anInterface : component.getProvidedInterfaces()) { Dep cmp = new Dep(anInterface, !component.isValue()); if (!componentIndex.containsKey(cmp)) { componentIndex.put(cmp, new HashSet<>()); diff --git a/firebase-components/src/main/java/com/google/firebase/components/Dependency.java b/firebase-components/src/main/java/com/google/firebase/components/Dependency.java index ed8a3c3b66d..9c9ef676a75 100644 --- a/firebase-components/src/main/java/com/google/firebase/components/Dependency.java +++ b/firebase-components/src/main/java/com/google/firebase/components/Dependency.java @@ -37,11 +37,15 @@ public final class Dependency { int DEFERRED = 2; } - private final Class anInterface; - private final @Type int type; + private final Qualified anInterface; + @Type private final int type; private final @Injection int injection; private Dependency(Class anInterface, @Type int type, @Injection int injection) { + this(Qualified.unqualified(anInterface), type, injection); + } + + private Dependency(Qualified anInterface, @Type int type, @Injection int injection) { this.anInterface = Preconditions.checkNotNull(anInterface, "Null dependency anInterface."); this.type = type; this.injection = injection; @@ -71,6 +75,16 @@ public static Dependency deferred(Class anInterface) { return new Dependency(anInterface, Type.OPTIONAL, Injection.DEFERRED); } + /** + * Declares a deferred dependency. + * + *

Such dependencies are optional and may not be present by default. But they can become + * available if a dynamic module that contains them is installed. + */ + public static Dependency deferred(Qualified anInterface) { + return new Dependency(anInterface, Type.OPTIONAL, Injection.DEFERRED); + } + /** * Declares a required dependency. * @@ -83,6 +97,18 @@ public static Dependency required(Class anInterface) { return new Dependency(anInterface, Type.REQUIRED, Injection.DIRECT); } + /** + * Declares a required dependency. + * + *

Such dependencies must be present in order for the dependent component to function. Any + * component with a required dependency should also declare a Maven dependency on an SDK that + * provides it. Failing to do so will result in a {@link MissingDependencyException} to be thrown + * at runtime. + */ + public static Dependency required(Qualified anInterface) { + return new Dependency(anInterface, Type.REQUIRED, Injection.DIRECT); + } + /** * Declares a Set multi-binding dependency. * @@ -94,6 +120,17 @@ public static Dependency setOf(Class anInterface) { return new Dependency(anInterface, Type.SET, Injection.DIRECT); } + /** + * Declares a Set multi-binding dependency. + * + *

Such dependencies provide access to a {@code Set} to dependent components. Note that + * the set is only filled with components that explicitly declare the intent to be a "set" + * dependency via {@link Component#intoSet(Object, Class)}. + */ + public static Dependency setOf(Qualified anInterface) { + return new Dependency(anInterface, Type.SET, Injection.DIRECT); + } + /** * Declares an optional dependency. * @@ -104,6 +141,16 @@ public static Dependency optionalProvider(Class anInterface) { return new Dependency(anInterface, Type.OPTIONAL, Injection.PROVIDER); } + /** + * Declares an optional dependency. + * + *

Optional dependencies can be missing at runtime(being {@code null}) and dependents must be + * ready to handle that. + */ + public static Dependency optionalProvider(Qualified anInterface) { + return new Dependency(anInterface, Type.OPTIONAL, Injection.PROVIDER); + } + /** * Declares a required dependency. * @@ -116,6 +163,18 @@ public static Dependency requiredProvider(Class anInterface) { return new Dependency(anInterface, Type.REQUIRED, Injection.PROVIDER); } + /** + * Declares a required dependency. + * + *

Such dependencies must be present in order for the dependent component to function. Any + * component with a required dependency should also declare a Maven dependency on an SDK that + * provides it. Failing to do so will result in a {@link MissingDependencyException} to be thrown + * at runtime. + */ + public static Dependency requiredProvider(Qualified anInterface) { + return new Dependency(anInterface, Type.REQUIRED, Injection.PROVIDER); + } + /** * Declares a Set multi-binding dependency. * @@ -127,7 +186,18 @@ public static Dependency setOfProvider(Class anInterface) { return new Dependency(anInterface, Type.SET, Injection.PROVIDER); } - public Class getInterface() { + /** + * Declares a Set multi-binding dependency. + * + *

Such dependencies provide access to a {@code Set} to dependent components. Note that + * the set is only filled with components that explicitly declare the intent to be a "set" + * dependency via {@link Component#intoSet(Object, Class)}. + */ + public static Dependency setOfProvider(Qualified anInterface) { + return new Dependency(anInterface, Type.SET, Injection.PROVIDER); + } + + public Qualified getInterface() { return anInterface; } @@ -151,7 +221,9 @@ public boolean isDeferred() { public boolean equals(Object o) { if (o instanceof Dependency) { Dependency other = (Dependency) o; - return anInterface == other.anInterface && type == other.type && injection == other.injection; + return anInterface.equals(other.anInterface) + && type == other.type + && injection == other.injection; } return false; } diff --git a/firebase-components/src/main/java/com/google/firebase/components/Qualified.java b/firebase-components/src/main/java/com/google/firebase/components/Qualified.java new file mode 100644 index 00000000000..395619631dd --- /dev/null +++ b/firebase-components/src/main/java/com/google/firebase/components/Qualified.java @@ -0,0 +1,64 @@ +// Copyright 2021 Google LLC +// +// 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 com.google.firebase.components; + +import java.lang.annotation.Annotation; + +/** Represents a qualified class object. */ +public final class Qualified { + private @interface Unqualified {} + + private final Class qualifier; + private final Class type; + + public Qualified(Class qualifier, Class type) { + this.qualifier = qualifier; + this.type = type; + } + + public static Qualified unqualified(Class type) { + return new Qualified<>(Unqualified.class, type); + } + + public static Qualified qualified(Class qualifier, Class type) { + return new Qualified<>(qualifier, type); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Qualified qualified = (Qualified) o; + + if (!type.equals(qualified.type)) return false; + return qualifier.equals(qualified.qualifier); + } + + @Override + public int hashCode() { + int result = type.hashCode(); + result = 31 * result + qualifier.hashCode(); + return result; + } + + @Override + public String toString() { + if (qualifier == Unqualified.class) { + return type.getName(); + } + return "@" + qualifier.getName() + " " + type.getName(); + } +} diff --git a/firebase-components/src/main/java/com/google/firebase/components/RestrictedComponentContainer.java b/firebase-components/src/main/java/com/google/firebase/components/RestrictedComponentContainer.java index 295dc3eb7cc..0c051e83a5b 100644 --- a/firebase-components/src/main/java/com/google/firebase/components/RestrictedComponentContainer.java +++ b/firebase-components/src/main/java/com/google/firebase/components/RestrictedComponentContainer.java @@ -26,21 +26,21 @@ * An implementation of {@link ComponentContainer} that is backed by another delegate {@link * ComponentContainer} and restricts access to only declared {@link Dependency dependencies}. */ -final class RestrictedComponentContainer extends AbstractComponentContainer { - private final Set> allowedDirectInterfaces; - private final Set> allowedProviderInterfaces; - private final Set> allowedDeferredInterfaces; - private final Set> allowedSetDirectInterfaces; - private final Set> allowedSetProviderInterfaces; +final class RestrictedComponentContainer implements ComponentContainer { + private final Set> allowedDirectInterfaces; + private final Set> allowedProviderInterfaces; + private final Set> allowedDeferredInterfaces; + private final Set> allowedSetDirectInterfaces; + private final Set> allowedSetProviderInterfaces; private final Set> allowedPublishedEvents; private final ComponentContainer delegateContainer; RestrictedComponentContainer(Component component, ComponentContainer container) { - Set> directInterfaces = new HashSet<>(); - Set> providerInterfaces = new HashSet<>(); - Set> deferredInterfaces = new HashSet<>(); - Set> setDirectInterfaces = new HashSet<>(); - Set> setProviderInterfaces = new HashSet<>(); + Set> directInterfaces = new HashSet<>(); + Set> providerInterfaces = new HashSet<>(); + Set> deferredInterfaces = new HashSet<>(); + Set> setDirectInterfaces = new HashSet<>(); + Set> setProviderInterfaces = new HashSet<>(); for (Dependency dependency : component.getDependencies()) { if (dependency.isDirectInjection()) { if (dependency.isSet()) { @@ -59,7 +59,7 @@ final class RestrictedComponentContainer extends AbstractComponentContainer { } } if (!component.getPublishedEvents().isEmpty()) { - directInterfaces.add(Publisher.class); + directInterfaces.add(Qualified.unqualified(Publisher.class)); } allowedDirectInterfaces = Collections.unmodifiableSet(directInterfaces); allowedProviderInterfaces = Collections.unmodifiableSet(providerInterfaces); @@ -77,7 +77,7 @@ final class RestrictedComponentContainer extends AbstractComponentContainer { */ @Override public T get(Class anInterface) { - if (!allowedDirectInterfaces.contains(anInterface)) { + if (!allowedDirectInterfaces.contains(Qualified.unqualified(anInterface))) { throw new DependencyException( String.format("Attempting to request an undeclared dependency %s.", anInterface)); } @@ -96,6 +96,15 @@ public T get(Class anInterface) { return publisher; } + @Override + public T get(Qualified anInterface) { + if (!allowedDirectInterfaces.contains(anInterface)) { + throw new DependencyException( + String.format("Attempting to request an undeclared dependency %s.", anInterface)); + } + return delegateContainer.get(anInterface); + } + /** * Returns an instance of the provider for the requested class if it is allowed. * @@ -103,6 +112,26 @@ public T get(Class anInterface) { */ @Override public Provider getProvider(Class anInterface) { + return getProvider(Qualified.unqualified(anInterface)); + } + + @Override + public Deferred getDeferred(Class anInterface) { + return getDeferred(Qualified.unqualified(anInterface)); + } + + /** + * Returns an instance of the provider for the set of requested classes if it is allowed. + * + * @throws DependencyException otherwise. + */ + @Override + public Provider> setOfProvider(Class anInterface) { + return setOfProvider(Qualified.unqualified(anInterface)); + } + + @Override + public Provider getProvider(Qualified anInterface) { if (!allowedProviderInterfaces.contains(anInterface)) { throw new DependencyException( String.format( @@ -112,7 +141,7 @@ public Provider getProvider(Class anInterface) { } @Override - public Deferred getDeferred(Class anInterface) { + public Deferred getDeferred(Qualified anInterface) { if (!allowedDeferredInterfaces.contains(anInterface)) { throw new DependencyException( String.format( @@ -121,13 +150,8 @@ public Deferred getDeferred(Class anInterface) { return delegateContainer.getDeferred(anInterface); } - /** - * Returns an instance of the provider for the set of requested classes if it is allowed. - * - * @throws DependencyException otherwise. - */ @Override - public Provider> setOfProvider(Class anInterface) { + public Provider> setOfProvider(Qualified anInterface) { if (!allowedSetProviderInterfaces.contains(anInterface)) { throw new DependencyException( String.format( @@ -142,7 +166,7 @@ public Provider> setOfProvider(Class anInterface) { * @throws DependencyException otherwise. */ @Override - public Set setOf(Class anInterface) { + public Set setOf(Qualified anInterface) { if (!allowedSetDirectInterfaces.contains(anInterface)) { throw new DependencyException( String.format("Attempting to request an undeclared dependency Set<%s>.", anInterface)); diff --git a/firebase-components/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java b/firebase-components/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java index ed5087cbdc9..ced659f5cc3 100644 --- a/firebase-components/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java +++ b/firebase-components/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java @@ -15,6 +15,7 @@ package com.google.firebase.components; import static com.google.common.truth.Truth.assertThat; +import static com.google.firebase.components.Qualified.qualified; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; @@ -33,6 +34,10 @@ @RunWith(JUnit4.class) public final class ComponentRuntimeTest { + private @interface Qualifier1 {} + + private @interface Qualifier2 {} + private static final Executor EXECUTOR = Runnable::run; interface ComponentOne { @@ -210,6 +215,21 @@ public void container_withMultipleComponentsRegisteredForSameInterface_shouldThr } } + @Test + public void + container_withMultipleComponentsRegisteredForSameInterfaceButQualified_shouldNotThrow() { + ComponentRuntime runtime = + ComponentRuntime.builder(EXECUTOR) + .addComponent(Component.of(1, Integer.class)) + .addComponent(Component.of(2, qualified(Qualifier1.class, Integer.class))) + .addComponent(Component.of(3, qualified(Qualifier2.class, Integer.class))) + .build(); + + assertThat(runtime.get(Integer.class)).isEqualTo(1); + assertThat(runtime.get(qualified(Qualifier1.class, Integer.class))).isEqualTo(2); + assertThat(runtime.get(qualified(Qualifier2.class, Integer.class))).isEqualTo(3); + } + @Test public void container_withMissingDependencies_shouldThrow() { try { @@ -265,7 +285,7 @@ public void container_withCyclicProviderDependency_shouldProperlyInitialize() { public void get_withNullInterface_shouldThrow() { ComponentRuntime runtime = ComponentRuntime.builder(EXECUTOR).build(); try { - runtime.get(null); + runtime.get((Qualified) null); fail("Expected exception not thrown."); } catch (NullPointerException ex) { // success. @@ -350,6 +370,25 @@ public void setComponents_shouldNotPreventValueComponentsFromBeingRegistered() { assertThat(runtime.get(Double.class)).isEqualTo(4d); } + @Test + public void setComponents_withQualifiers_shouldContributeToAppropriateSets() { + ComponentRuntime runtime = + ComponentRuntime.builder(EXECUTOR) + .addComponent(Component.of(5, Integer.class)) + .addComponent(Component.intoSet(1, Integer.class)) + .addComponent(Component.intoSet(3, Integer.class)) + .addComponent(Component.intoSet(1, qualified(Qualifier1.class, Integer.class))) + .addComponent(Component.intoSet(2, qualified(Qualifier1.class, Integer.class))) + .addComponent(Component.intoSet(3, qualified(Qualifier2.class, Integer.class))) + .addComponent(Component.intoSet(4, qualified(Qualifier2.class, Integer.class))) + .build(); + + assertThat(runtime.get(Integer.class)).isEqualTo(5); + assertThat(runtime.setOf(Integer.class)).containsExactly(1, 3); + assertThat(runtime.setOf(qualified(Qualifier1.class, Integer.class))).containsExactly(1, 2); + assertThat(runtime.setOf(qualified(Qualifier2.class, Integer.class))).containsExactly(3, 4); + } + private static class DependsOnString { private final Provider dep; diff --git a/firebase-components/src/test/java/com/google/firebase/components/ComponentTest.java b/firebase-components/src/test/java/com/google/firebase/components/ComponentTest.java index 92cc6cf3588..13549495ef8 100644 --- a/firebase-components/src/test/java/com/google/firebase/components/ComponentTest.java +++ b/firebase-components/src/test/java/com/google/firebase/components/ComponentTest.java @@ -15,6 +15,8 @@ package com.google.firebase.components; import static com.google.common.truth.Truth.assertThat; +import static com.google.firebase.components.Qualified.qualified; +import static com.google.firebase.components.Qualified.unqualified; import static org.junit.Assert.fail; import java.math.BigDecimal; @@ -25,6 +27,8 @@ @RunWith(JUnit4.class) public class ComponentTest { + @interface TestQualifier {} + interface TestInterface {} private static class TestClass implements TestInterface {} @@ -36,7 +40,7 @@ public void of_withMultipleInterfaces_shouldSetCorrectDefaults() { TestClass testClass = new TestClass(); Component component = Component.of(testClass, TestClass.class, TestInterface.class); assertThat(component.getProvidedInterfaces()) - .containsExactly(TestClass.class, TestInterface.class); + .containsExactly(unqualified(TestClass.class), unqualified(TestInterface.class)); assertThat(component.isLazy()).isTrue(); assertThat(component.isValue()).isTrue(); assertThat(component.isAlwaysEager()).isFalse(); @@ -49,7 +53,22 @@ public void of_withMultipleInterfaces_shouldSetCorrectDefaults() { public void builder_shouldSetCorrectDefaults() { Component component = Component.builder(TestClass.class).factory(nullFactory).build(); - assertThat(component.getProvidedInterfaces()).containsExactly(TestClass.class); + assertThat(component.getProvidedInterfaces()).containsExactly(unqualified(TestClass.class)); + assertThat(component.isLazy()).isTrue(); + assertThat(component.isValue()).isTrue(); + assertThat(component.isAlwaysEager()).isFalse(); + assertThat(component.isEagerInDefaultApp()).isFalse(); + assertThat(component.getDependencies()).isEmpty(); + } + + @Test + public void qualifiedBuilder_shouldSetCorrectDefaults() { + Component component = + Component.builder(qualified(TestQualifier.class, TestClass.class)) + .factory(nullFactory) + .build(); + assertThat(component.getProvidedInterfaces()) + .containsExactly(qualified(TestQualifier.class, TestClass.class)); assertThat(component.isLazy()).isTrue(); assertThat(component.isValue()).isTrue(); assertThat(component.isAlwaysEager()).isFalse(); @@ -61,7 +80,7 @@ public void builder_shouldSetCorrectDefaults() { public void intoSetBuilder_shouldSetCorrectDefaults() { Component component = Component.intoSetBuilder(TestClass.class).factory(nullFactory).build(); - assertThat(component.getProvidedInterfaces()).containsExactly(TestClass.class); + assertThat(component.getProvidedInterfaces()).containsExactly(unqualified(TestClass.class)); assertThat(component.isLazy()).isTrue(); assertThat(component.isValue()).isFalse(); assertThat(component.isAlwaysEager()).isFalse(); @@ -73,7 +92,7 @@ public void intoSetBuilder_shouldSetCorrectDefaults() { public void intoSet_shouldSetCorrectDefaults() { TestClass testClass = new TestClass(); Component component = Component.intoSet(testClass, TestClass.class); - assertThat(component.getProvidedInterfaces()).containsExactly(TestClass.class); + assertThat(component.getProvidedInterfaces()).containsExactly(unqualified(TestClass.class)); assertThat(component.isLazy()).isTrue(); assertThat(component.isValue()).isFalse(); assertThat(component.isAlwaysEager()).isFalse(); @@ -174,7 +193,21 @@ public void builder_withMultipleInterfaces_shouldProperlySetInterfaces() { Component component = Component.builder(TestClass.class, TestInterface.class).factory(nullFactory).build(); assertThat(component.getProvidedInterfaces()) - .containsExactly(TestClass.class, TestInterface.class); + .containsExactly(unqualified(TestClass.class), unqualified(TestInterface.class)); + } + + @Test + public void builder_withMultipleQualifiedInterfaces_shouldProperlySetInterfaces() { + Component component = + Component.builder( + qualified(TestQualifier.class, TestClass.class), + qualified(TestQualifier.class, TestInterface.class)) + .factory(nullFactory) + .build(); + assertThat(component.getProvidedInterfaces()) + .containsExactly( + qualified(TestQualifier.class, TestClass.class), + qualified(TestQualifier.class, TestInterface.class)); } @Test diff --git a/firebase-components/src/test/java/com/google/firebase/components/CycleDetectorTest.java b/firebase-components/src/test/java/com/google/firebase/components/CycleDetectorTest.java index 091b7be8b08..8fc904741fe 100644 --- a/firebase-components/src/test/java/com/google/firebase/components/CycleDetectorTest.java +++ b/firebase-components/src/test/java/com/google/firebase/components/CycleDetectorTest.java @@ -15,11 +15,13 @@ package com.google.firebase.components; import static com.google.common.truth.Truth.assertThat; +import static com.google.firebase.components.Qualified.qualified; import static junit.framework.Assert.fail; import java.util.Arrays; import java.util.Collections; import java.util.List; +import javax.inject.Qualifier; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -246,7 +248,7 @@ public void detect_withProviderDependencyCycle_shouldNotThrow() { } @Test - public void detect_withMultipleComponentsImplementingSameIface_shouldThrow() { + public void detect_withMultipleComponentsImplementingSameInterface_shouldThrow() { List> components = Arrays.asList( Component.builder(TestInterface1.class).factory(nullFactory()).build(), @@ -260,6 +262,45 @@ public void detect_withMultipleComponentsImplementingSameIface_shouldThrow() { } } + @Qualifier + @interface Qualifier1 {} + + @Qualifier + @interface Qualifier2 {} + + @Test + public void detect_withMultipleComponentsImplementingSameQualifiedInterface_shouldNotThrow() { + List> components = + Arrays.asList( + Component.builder(TestInterface1.class).factory(nullFactory()).build(), + Component.builder(qualified(Qualifier1.class, TestInterface1.class)) + .factory(nullFactory()) + .build(), + Component.builder(qualified(Qualifier2.class, TestInterface1.class)) + .factory(nullFactory()) + .build()); + + CycleDetector.detect(components); + } + + @Test + public void + detect_withMultipleComponentsImplementingSameQualifiedInterfaceAndNoDepCycle_shouldNotThrow() { + List> components = + Arrays.asList( + Component.builder(TestInterface1.class).factory(nullFactory()).build(), + Component.builder(qualified(Qualifier1.class, TestInterface1.class)) + .add(Dependency.required(TestInterface1.class)) + .factory(nullFactory()) + .build(), + Component.builder(qualified(Qualifier2.class, TestInterface1.class)) + .add(Dependency.required(qualified(Qualifier1.class, TestInterface1.class))) + .factory(nullFactory()) + .build()); + + CycleDetector.detect(components); + } + private static void detect(List> components) { Collections.shuffle(components); try { diff --git a/firebase-components/src/test/java/com/google/firebase/components/DependencyTest.java b/firebase-components/src/test/java/com/google/firebase/components/DependencyTest.java index 0c3fdaca99c..9c13a0a9fd8 100644 --- a/firebase-components/src/test/java/com/google/firebase/components/DependencyTest.java +++ b/firebase-components/src/test/java/com/google/firebase/components/DependencyTest.java @@ -15,11 +15,17 @@ package com.google.firebase.components; import static com.google.common.truth.Truth.assertThat; +import static com.google.firebase.components.Qualified.qualified; +import static com.google.firebase.components.Qualified.unqualified; +import javax.inject.Qualifier; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +@Qualifier +@interface TestQualifier {} + @RunWith(JUnit4.class) public class DependencyTest { @Test @@ -29,7 +35,7 @@ public void optional_shouldHaveExpectedInvariants() { assertThat(dependency.isRequired()).isFalse(); assertThat(dependency.isSet()).isFalse(); assertThat(dependency.isDirectInjection()).isTrue(); - assertThat(dependency.getInterface()).isEqualTo(String.class); + assertThat(dependency.getInterface()).isEqualTo(unqualified(String.class)); } @Test @@ -39,7 +45,17 @@ public void required_shouldHaveExpectedInvariants() { assertThat(dependency.isRequired()).isTrue(); assertThat(dependency.isSet()).isFalse(); assertThat(dependency.isDirectInjection()).isTrue(); - assertThat(dependency.getInterface()).isEqualTo(String.class); + assertThat(dependency.getInterface()).isEqualTo(unqualified(String.class)); + } + + @Test + public void requiredQualified_shouldHaveExpectedInvariants() { + Dependency dependency = Dependency.required(qualified(TestQualifier.class, String.class)); + + assertThat(dependency.isRequired()).isTrue(); + assertThat(dependency.isSet()).isFalse(); + assertThat(dependency.isDirectInjection()).isTrue(); + assertThat(dependency.getInterface()).isEqualTo(qualified(TestQualifier.class, String.class)); } @Test @@ -49,7 +65,17 @@ public void setOf_shouldHaveExpectedInvariants() { assertThat(dependency.isRequired()).isFalse(); assertThat(dependency.isSet()).isTrue(); assertThat(dependency.isDirectInjection()).isTrue(); - assertThat(dependency.getInterface()).isEqualTo(String.class); + assertThat(dependency.getInterface()).isEqualTo(unqualified(String.class)); + } + + @Test + public void setOfQualified_shouldHaveExpectedInvariants() { + Dependency dependency = Dependency.setOf(qualified(TestQualifier.class, String.class)); + + assertThat(dependency.isRequired()).isFalse(); + assertThat(dependency.isSet()).isTrue(); + assertThat(dependency.isDirectInjection()).isTrue(); + assertThat(dependency.getInterface()).isEqualTo(qualified(TestQualifier.class, String.class)); } @Test @@ -59,7 +85,18 @@ public void optionalProvider_shouldHaveExpectedInvariants() { assertThat(dependency.isRequired()).isFalse(); assertThat(dependency.isSet()).isFalse(); assertThat(dependency.isDirectInjection()).isFalse(); - assertThat(dependency.getInterface()).isEqualTo(String.class); + assertThat(dependency.getInterface()).isEqualTo(unqualified(String.class)); + } + + @Test + public void optionalProviderQualified_shouldHaveExpectedInvariants() { + Dependency dependency = + Dependency.optionalProvider(qualified(TestQualifier.class, String.class)); + + assertThat(dependency.isRequired()).isFalse(); + assertThat(dependency.isSet()).isFalse(); + assertThat(dependency.isDirectInjection()).isFalse(); + assertThat(dependency.getInterface()).isEqualTo(qualified(TestQualifier.class, String.class)); } @Test @@ -69,7 +106,18 @@ public void requiredProvider_shouldHaveExpectedInvariants() { assertThat(dependency.isRequired()).isTrue(); assertThat(dependency.isSet()).isFalse(); assertThat(dependency.isDirectInjection()).isFalse(); - assertThat(dependency.getInterface()).isEqualTo(String.class); + assertThat(dependency.getInterface()).isEqualTo(unqualified(String.class)); + } + + @Test + public void requiredProviderQualified_shouldHaveExpectedInvariants() { + Dependency dependency = + Dependency.requiredProvider(qualified(TestQualifier.class, String.class)); + + assertThat(dependency.isRequired()).isTrue(); + assertThat(dependency.isSet()).isFalse(); + assertThat(dependency.isDirectInjection()).isFalse(); + assertThat(dependency.getInterface()).isEqualTo(qualified(TestQualifier.class, String.class)); } @Test @@ -79,6 +127,16 @@ public void setOfProvider_shouldHaveExpectedInvariants() { assertThat(dependency.isRequired()).isFalse(); assertThat(dependency.isSet()).isTrue(); assertThat(dependency.isDirectInjection()).isFalse(); - assertThat(dependency.getInterface()).isEqualTo(String.class); + assertThat(dependency.getInterface()).isEqualTo(unqualified(String.class)); + } + + @Test + public void setOfProviderQualified_shouldHaveExpectedInvariants() { + Dependency dependency = Dependency.setOfProvider(qualified(TestQualifier.class, String.class)); + + assertThat(dependency.isRequired()).isFalse(); + assertThat(dependency.isSet()).isTrue(); + assertThat(dependency.isDirectInjection()).isFalse(); + assertThat(dependency.getInterface()).isEqualTo(qualified(TestQualifier.class, String.class)); } } diff --git a/firebase-components/src/test/java/com/google/firebase/components/RestrictedComponentContainerTest.java b/firebase-components/src/test/java/com/google/firebase/components/RestrictedComponentContainerTest.java index bda8a135cd5..8367780f302 100644 --- a/firebase-components/src/test/java/com/google/firebase/components/RestrictedComponentContainerTest.java +++ b/firebase-components/src/test/java/com/google/firebase/components/RestrictedComponentContainerTest.java @@ -15,9 +15,11 @@ package com.google.firebase.components; import static com.google.common.truth.Truth.assertThat; +import static com.google.firebase.components.Qualified.unqualified; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -96,10 +98,10 @@ public void get_withPublisher_shouldThrow() { @Test public void getProvider_withAllowedClass_shouldReturnAnInstanceOfThatClass() { Double value = 3.0d; - when(delegate.getProvider(Double.class)).thenReturn(new Lazy<>(value)); + when(delegate.getProvider(unqualified(Double.class))).thenReturn(new Lazy<>(value)); assertThat(container.getProvider(Double.class).get()).isSameInstanceAs(value); - verify(delegate).getProvider(Double.class); + verify(delegate).getProvider(unqualified(Double.class)); } @Test @@ -125,12 +127,16 @@ public void getProvider_withDirectClass_shouldThrow() { @Test public void getDeferred_withAllowedClass_shouldReturnAnInstanceOfThatClass() { Integer value = 3; - when(delegate.getDeferred(Integer.class)).thenReturn(OptionalProvider.of(() -> value)); + when(delegate.getDeferred(unqualified(Integer.class))) + .thenReturn(OptionalProvider.of(() -> value)); AtomicReference returned = new AtomicReference<>(); container.getDeferred(Integer.class).whenAvailable(d -> returned.set(d.get())); assertThat(returned.get()).isSameInstanceAs(value); - verify(delegate).getDeferred(Integer.class); + + container.getDeferred(unqualified(Integer.class)).whenAvailable(d -> returned.set(d.get())); + assertThat(returned.get()).isSameInstanceAs(value); + verify(delegate, times(2)).getDeferred(unqualified(Integer.class)); } @Test @@ -166,10 +172,11 @@ public void getDeferred_withProviderClass_shouldThrow() { @Test public void setOf_withAllowedClass_shouldReturnExpectedSet() { Set set = Collections.emptySet(); - when(delegate.setOf(Long.class)).thenReturn(set); + when(delegate.setOf(unqualified(Long.class))).thenReturn(set); assertThat(container.setOf(Long.class)).isSameInstanceAs(set); - verify(delegate).setOf(Long.class); + assertThat(container.setOf(unqualified(Long.class))).isSameInstanceAs(set); + verify(delegate, times(2)).setOf(unqualified(Long.class)); } @Test @@ -185,10 +192,11 @@ public void setOf_withNotAllowedClass_shouldThrow() { @Test public void setOfProvider_withAllowedClass_shouldReturnExpectedSet() { Set set = Collections.emptySet(); - when(delegate.setOfProvider(Boolean.class)).thenReturn(new Lazy<>(set)); + when(delegate.setOfProvider(unqualified(Boolean.class))).thenReturn(new Lazy<>(set)); assertThat(container.setOfProvider(Boolean.class).get()).isSameInstanceAs(set); - verify(delegate).setOfProvider(Boolean.class); + assertThat(container.setOfProvider(unqualified(Boolean.class)).get()).isSameInstanceAs(set); + verify(delegate, times(2)).setOfProvider(unqualified(Boolean.class)); } @Test From eef5373a2612363f56cb5999eee35ccce045b092 Mon Sep 17 00:00:00 2001 From: Vladimir Kryachko Date: Tue, 8 Nov 2022 10:02:30 -0500 Subject: [PATCH 2/3] fix errorprone error. --- .../com/google/firebase/components/ComponentRuntimeTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/firebase-components/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java b/firebase-components/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java index ced659f5cc3..8e445aee84a 100644 --- a/firebase-components/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java +++ b/firebase-components/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java @@ -16,6 +16,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.firebase.components.Qualified.qualified; +import static com.google.firebase.components.Qualified.unqualified; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; @@ -646,8 +647,8 @@ public void container_withComponentProcessor_shouldDelegateToItForEachComponentR runtime.getAllComponentsForTest().stream() .filter( c -> - (c.getProvidedInterfaces().contains(ComponentOne.class) - || c.getProvidedInterfaces().contains(ComponentTwo.class))) + (c.getProvidedInterfaces().contains(unqualified(ComponentOne.class)) + || c.getProvidedInterfaces().contains(unqualified(ComponentTwo.class)))) .allMatch(c -> c.getFactory() == replacedFactory)) .isTrue(); } From 21d1fe53f696de69a3a0c244b143076d7fbc860f Mon Sep 17 00:00:00 2001 From: Vladimir Kryachko Date: Tue, 8 Nov 2022 10:48:57 -0500 Subject: [PATCH 3/3] change copyright year. --- .../src/main/java/com/google/firebase/components/Qualified.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-components/src/main/java/com/google/firebase/components/Qualified.java b/firebase-components/src/main/java/com/google/firebase/components/Qualified.java index 395619631dd..a0b755ac583 100644 --- a/firebase-components/src/main/java/com/google/firebase/components/Qualified.java +++ b/firebase-components/src/main/java/com/google/firebase/components/Qualified.java @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.