Skip to content

Commit 96acdf2

Browse files
committed
Add runtime support for set-components.
1 parent 1e901ae commit 96acdf2

File tree

8 files changed

+237
-62
lines changed

8 files changed

+237
-62
lines changed

firebase-common/src/main/java/com/google/firebase/components/AbstractComponentContainer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.components;
1616

1717
import com.google.firebase.inject.Provider;
18+
import java.util.Set;
1819

1920
abstract class AbstractComponentContainer implements ComponentContainer {
2021
@Override
@@ -25,4 +26,9 @@ public <T> T get(Class<T> anInterface) {
2526
}
2627
return provider.get();
2728
}
29+
30+
@Override
31+
public <T> Set<T> setOf(Class<T> anInterface) {
32+
return setOfProvider(anInterface).get();
33+
}
2834
}

firebase-common/src/main/java/com/google/firebase/components/ComponentContainer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import com.google.android.gms.common.annotation.KeepForSdk;
1818
import com.google.firebase.inject.Provider;
19+
import java.util.Set;
1920

2021
/** Provides a means to retrieve instances of requested classes/interfaces. */
2122
@KeepForSdk
@@ -25,4 +26,10 @@ public interface ComponentContainer {
2526

2627
@KeepForSdk
2728
<T> Provider<T> getProvider(Class<T> anInterface);
29+
30+
@KeepForSdk
31+
<T> Set<T> setOf(Class<T> anInterface);
32+
33+
@KeepForSdk
34+
<T> Provider<Set<T>> setOfProvider(Class<T> anInterface);
2835
}

firebase-common/src/main/java/com/google/firebase/components/ComponentRuntime.java

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import java.util.ArrayList;
2222
import java.util.Collections;
2323
import java.util.HashMap;
24+
import java.util.HashSet;
2425
import java.util.List;
2526
import java.util.Map;
27+
import java.util.Set;
2628
import java.util.concurrent.Executor;
2729

2830
/**
@@ -32,8 +34,10 @@
3234
* Component}s via {@link #get(Class)} method.
3335
*/
3436
public class ComponentRuntime extends AbstractComponentContainer {
35-
private final List<Component<?>> components;
37+
private static final Provider<Set<Object>> EMPTY_PROVIDER = Collections::emptySet;
38+
private final Map<Component<?>, Lazy<?>> components = new HashMap<>();
3639
private final Map<Class<?>, Lazy<?>> lazyInstanceMap = new HashMap<>();
40+
private final Map<Class<?>, Lazy<Set<?>>> lazySetMap = new HashMap<>();
3741
private final EventBus eventBus;
3842

3943
/**
@@ -54,12 +58,49 @@ public ComponentRuntime(
5458
Collections.addAll(componentsToAdd, additionalComponents);
5559

5660
CycleDetector.detect(componentsToAdd);
57-
components = Collections.unmodifiableList(componentsToAdd);
5861

59-
for (Component<?> component : components) {
62+
for (Component<?> component : componentsToAdd) {
6063
register(component);
6164
}
6265
validateDependencies();
66+
67+
processSetComponents();
68+
}
69+
70+
/** Populates lazySetMap to make set components available for consumption via set dependencies. */
71+
private void processSetComponents() {
72+
Map<Class<?>, Set<Lazy<?>>> setIndex = new HashMap<>();
73+
for (Map.Entry<Component<?>, Lazy<?>> entry : components.entrySet()) {
74+
Component<?> component = entry.getKey();
75+
76+
// only process set components.
77+
if (component.isValue()) {
78+
continue;
79+
}
80+
81+
Lazy<?> lazy = entry.getValue();
82+
83+
for (Class<?> anInterface : component.getProvidedInterfaces()) {
84+
if (!setIndex.containsKey(anInterface)) {
85+
setIndex.put(anInterface, new HashSet<>());
86+
}
87+
setIndex.get(anInterface).add(lazy);
88+
}
89+
}
90+
91+
for (Map.Entry<Class<?>, Set<Lazy<?>>> entry : setIndex.entrySet()) {
92+
Set<Lazy<?>> lazies = entry.getValue();
93+
lazySetMap.put(
94+
entry.getKey(),
95+
new Lazy<>(
96+
() -> {
97+
Set<Object> set = new HashSet<>();
98+
for (Lazy<?> lazy : lazies) {
99+
set.add(lazy.get());
100+
}
101+
return Collections.unmodifiableSet(set);
102+
}));
103+
}
63104
}
64105

65106
@Override
@@ -69,6 +110,16 @@ public <T> Provider<T> getProvider(Class<T> anInterface) {
69110
return (Provider<T>) lazyInstanceMap.get(anInterface);
70111
}
71112

113+
@Override
114+
@SuppressWarnings("unchecked")
115+
public <T> Provider<Set<T>> setOfProvider(Class<T> anInterface) {
116+
Lazy<Set<?>> lazy = lazySetMap.get(anInterface);
117+
if (lazy != null) {
118+
return (Provider<Set<T>>) (Provider<?>) lazy;
119+
}
120+
return (Provider<Set<T>>) (Provider<?>) EMPTY_PROVIDER;
121+
}
122+
72123
/**
73124
* Initializes all eager components.
74125
*
@@ -77,10 +128,12 @@ public <T> Provider<T> getProvider(Class<T> anInterface) {
77128
* <p>Note: the method is idempotent.
78129
*/
79130
public void initializeEagerComponents(boolean isDefaultApp) {
80-
for (Component<?> component : components) {
131+
for (Map.Entry<Component<?>, Lazy<?>> entry : components.entrySet()) {
132+
Component<?> component = entry.getKey();
133+
Lazy<?> lazy = entry.getValue();
134+
81135
if (component.isAlwaysEager() || (component.isEagerInDefaultApp() && isDefaultApp)) {
82-
// at least one interface is guarenteed to be provided by a component.
83-
get(component.getProvidedInterfaces().iterator().next());
136+
lazy.get();
84137
}
85138
}
86139

@@ -89,15 +142,20 @@ public void initializeEagerComponents(boolean isDefaultApp) {
89142

90143
private <T> void register(Component<T> component) {
91144
Lazy<T> lazy =
92-
new Lazy<>(component.getFactory(), new RestrictedComponentContainer(component, this));
145+
new Lazy<>(
146+
() -> component.getFactory().create(new RestrictedComponentContainer(component, this)));
93147

148+
components.put(component, lazy);
149+
if (!component.isValue()) {
150+
return;
151+
}
94152
for (Class<? super T> anInterface : component.getProvidedInterfaces()) {
95153
lazyInstanceMap.put(anInterface, lazy);
96154
}
97155
}
98156

99157
private void validateDependencies() {
100-
for (Component<?> component : components) {
158+
for (Component<?> component : components.keySet()) {
101159
for (Dependency dependency : component.getDependencies()) {
102160
if (dependency.isRequired() && !lazyInstanceMap.containsKey(dependency.getInterface())) {
103161
throw new MissingDependencyException(

firebase-common/src/main/java/com/google/firebase/components/Lazy.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ class Lazy<T> implements Provider<T> {
3838
this.instance = instance;
3939
}
4040

41-
/** Creates a lazy backed by a {@link ComponentFactory} and {@link ComponentContainer}. */
42-
Lazy(ComponentFactory<T> factory, ComponentContainer container) {
43-
provider = () -> factory.create(container);
41+
Lazy(Provider<T> provider) {
42+
this.provider = provider;
4443
}
4544

4645
/** Returns the initialized value. */
@@ -59,7 +58,10 @@ public T get() {
5958
}
6059
}
6160
}
62-
return (T) result;
61+
62+
@SuppressWarnings("unchecked")
63+
T tResult = (T) result;
64+
return tResult;
6365
}
6466

6567
@VisibleForTesting

firebase-common/src/main/java/com/google/firebase/components/RestrictedComponentContainer.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,38 @@
2828
final class RestrictedComponentContainer extends AbstractComponentContainer {
2929
private final Set<Class<?>> allowedDirectInterfaces;
3030
private final Set<Class<?>> allowedProviderInterfaces;
31+
private final Set<Class<?>> allowedSetDirectInterfaces;
32+
private final Set<Class<?>> allowedSetProviderInterfaces;
3133
private final Set<Class<?>> allowedPublishedEvents;
3234
private final ComponentContainer delegateContainer;
3335

3436
RestrictedComponentContainer(Component<?> component, ComponentContainer container) {
3537
Set<Class<?>> directInterfaces = new HashSet<>();
3638
Set<Class<?>> providerInterfaces = new HashSet<>();
39+
Set<Class<?>> setDirectInterfaces = new HashSet<>();
40+
Set<Class<?>> setProviderInterfaces = new HashSet<>();
3741
for (Dependency dependency : component.getDependencies()) {
3842
if (dependency.isDirectInjection()) {
39-
directInterfaces.add(dependency.getInterface());
43+
if (dependency.isSet()) {
44+
setDirectInterfaces.add(dependency.getInterface());
45+
} else {
46+
directInterfaces.add(dependency.getInterface());
47+
}
4048
} else {
41-
providerInterfaces.add(dependency.getInterface());
49+
if (dependency.isSet()) {
50+
setProviderInterfaces.add(dependency.getInterface());
51+
} else {
52+
providerInterfaces.add(dependency.getInterface());
53+
}
4254
}
4355
}
4456
if (!component.getPublishedEvents().isEmpty()) {
4557
directInterfaces.add(Publisher.class);
4658
}
4759
allowedDirectInterfaces = Collections.unmodifiableSet(directInterfaces);
4860
allowedProviderInterfaces = Collections.unmodifiableSet(providerInterfaces);
61+
allowedSetDirectInterfaces = Collections.unmodifiableSet(setDirectInterfaces);
62+
allowedSetProviderInterfaces = Collections.unmodifiableSet(setProviderInterfaces);
4963
allowedPublishedEvents = component.getPublishedEvents();
5064
delegateContainer = container;
5165
}
@@ -54,7 +68,7 @@ final class RestrictedComponentContainer extends AbstractComponentContainer {
5468
public <T> T get(Class<T> anInterface) {
5569
if (!allowedDirectInterfaces.contains(anInterface)) {
5670
throw new IllegalArgumentException(
57-
String.format("Requesting %s is not allowed.", anInterface));
71+
String.format("Attempting to request an undeclared dependency %s.", anInterface));
5872
}
5973

6074
// The container is guaranteed to contain a class keyed with Publisher.class. This is what we
@@ -75,11 +89,31 @@ public <T> T get(Class<T> anInterface) {
7589
public <T> Provider<T> getProvider(Class<T> anInterface) {
7690
if (!allowedProviderInterfaces.contains(anInterface)) {
7791
throw new IllegalArgumentException(
78-
String.format("Requesting Provider<%s> is not allowed.", anInterface));
92+
String.format(
93+
"Attempting to request an undeclared dependency Provider<%s>.", anInterface));
7994
}
8095
return delegateContainer.getProvider(anInterface);
8196
}
8297

98+
@Override
99+
public <T> Provider<Set<T>> setOfProvider(Class<T> anInterface) {
100+
if (!allowedSetProviderInterfaces.contains(anInterface)) {
101+
throw new IllegalArgumentException(
102+
String.format(
103+
"Attempting to request an undeclared dependency Provider<Set<%s>>.", anInterface));
104+
}
105+
return delegateContainer.setOfProvider(anInterface);
106+
}
107+
108+
@Override
109+
public <T> Set<T> setOf(Class<T> anInterface) {
110+
if (!allowedSetDirectInterfaces.contains(anInterface)) {
111+
throw new IllegalArgumentException(
112+
String.format("Attempting to request an undeclared dependency Set<%s>.", anInterface));
113+
}
114+
return delegateContainer.setOf(anInterface);
115+
}
116+
83117
/**
84118
* An implementation of {@link Publisher} that is backed by another delegate {@link Publisher} and
85119
* restricts publishing to only a set of allowed event types.

firebase-common/src/test/java/com/google/firebase/components/ComponentRuntimeTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,37 @@ public void container_shouldExposeAllProvidedInterfacesOfAComponent() {
233233
assertThat(child).isSameAs(parent);
234234
assertThat(child.get()).isSameAs(parent.get());
235235
}
236+
237+
@Test
238+
public void container_shouldExposeAllRegisteredSetValues() {
239+
ComponentRuntime runtime =
240+
new ComponentRuntime(
241+
EXECUTOR,
242+
Collections.emptyList(),
243+
Component.intoSet(1, Integer.class),
244+
Component.intoSet(2, Integer.class));
245+
246+
assertThat(runtime.setOf(Integer.class)).containsExactly(1, 2);
247+
}
248+
249+
@Test
250+
public void setComponents_shouldParticipateInCycleDetection() {
251+
try {
252+
new ComponentRuntime(
253+
EXECUTOR,
254+
Collections.emptyList(),
255+
Component.builder(ComponentOne.class)
256+
.add(Dependency.setOf(Integer.class))
257+
.factory(c -> null)
258+
.build(),
259+
Component.intoSet(1, Integer.class),
260+
Component.intoSetBuilder(Integer.class)
261+
.add(Dependency.required(ComponentOne.class))
262+
.factory(c -> 2)
263+
.build());
264+
fail("Expected exception not thrown.");
265+
} catch (DependencyCycleException ex) {
266+
// success.
267+
}
268+
}
236269
}

0 commit comments

Comments
 (0)