Skip to content

Commit d26f7e1

Browse files
authored
Add runtime support for Set-components (#175)
* Extend cycle detection to support set-components. * Add runtime support for set-components. * Fix formatting. * Refactored component initialization. * Add javadoc.
1 parent 93c8b48 commit d26f7e1

File tree

10 files changed

+352
-138
lines changed

10 files changed

+352
-138
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: 81 additions & 17 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
/**
@@ -53,21 +57,88 @@ public ComponentRuntime(
5357
}
5458
Collections.addAll(componentsToAdd, additionalComponents);
5559

56-
components = Collections.unmodifiableList(ComponentSorter.sorted(componentsToAdd));
60+
CycleDetector.detect(componentsToAdd);
5761

58-
for (Component<?> component : components) {
59-
register(component);
62+
for (Component<?> component : componentsToAdd) {
63+
Lazy<?> lazy =
64+
new Lazy<>(
65+
() ->
66+
component.getFactory().create(new RestrictedComponentContainer(component, this)));
67+
68+
components.put(component, lazy);
69+
}
70+
processInstanceComponents();
71+
processSetComponents();
72+
}
73+
74+
private void processInstanceComponents() {
75+
for (Map.Entry<Component<?>, Lazy<?>> entry : components.entrySet()) {
76+
Component<?> component = entry.getKey();
77+
if (!component.isValue()) {
78+
return;
79+
}
80+
81+
Lazy<?> lazy = entry.getValue();
82+
for (Class<?> anInterface : component.getProvidedInterfaces()) {
83+
lazyInstanceMap.put(anInterface, lazy);
84+
}
6085
}
6186
validateDependencies();
6287
}
6388

89+
/** Populates lazySetMap to make set components available for consumption via set dependencies. */
90+
private void processSetComponents() {
91+
Map<Class<?>, Set<Lazy<?>>> setIndex = new HashMap<>();
92+
for (Map.Entry<Component<?>, Lazy<?>> entry : components.entrySet()) {
93+
Component<?> component = entry.getKey();
94+
95+
// only process set components.
96+
if (component.isValue()) {
97+
continue;
98+
}
99+
100+
Lazy<?> lazy = entry.getValue();
101+
102+
for (Class<?> anInterface : component.getProvidedInterfaces()) {
103+
if (!setIndex.containsKey(anInterface)) {
104+
setIndex.put(anInterface, new HashSet<>());
105+
}
106+
setIndex.get(anInterface).add(lazy);
107+
}
108+
}
109+
110+
for (Map.Entry<Class<?>, Set<Lazy<?>>> entry : setIndex.entrySet()) {
111+
Set<Lazy<?>> lazies = entry.getValue();
112+
lazySetMap.put(
113+
entry.getKey(),
114+
new Lazy<>(
115+
() -> {
116+
Set<Object> set = new HashSet<>();
117+
for (Lazy<?> lazy : lazies) {
118+
set.add(lazy.get());
119+
}
120+
return Collections.unmodifiableSet(set);
121+
}));
122+
}
123+
}
124+
64125
@Override
65126
@SuppressWarnings("unchecked")
66127
public <T> Provider<T> getProvider(Class<T> anInterface) {
67128
Preconditions.checkNotNull(anInterface, "Null interface requested.");
68129
return (Provider<T>) lazyInstanceMap.get(anInterface);
69130
}
70131

132+
@Override
133+
@SuppressWarnings("unchecked")
134+
public <T> Provider<Set<T>> setOfProvider(Class<T> anInterface) {
135+
Lazy<Set<?>> lazy = lazySetMap.get(anInterface);
136+
if (lazy != null) {
137+
return (Provider<Set<T>>) (Provider<?>) lazy;
138+
}
139+
return (Provider<Set<T>>) (Provider<?>) EMPTY_PROVIDER;
140+
}
141+
71142
/**
72143
* Initializes all eager components.
73144
*
@@ -76,27 +147,20 @@ public <T> Provider<T> getProvider(Class<T> anInterface) {
76147
* <p>Note: the method is idempotent.
77148
*/
78149
public void initializeEagerComponents(boolean isDefaultApp) {
79-
for (Component<?> component : components) {
150+
for (Map.Entry<Component<?>, Lazy<?>> entry : components.entrySet()) {
151+
Component<?> component = entry.getKey();
152+
Lazy<?> lazy = entry.getValue();
153+
80154
if (component.isAlwaysEager() || (component.isEagerInDefaultApp() && isDefaultApp)) {
81-
// at least one interface is guarenteed to be provided by a component.
82-
get(component.getProvidedInterfaces().iterator().next());
155+
lazy.get();
83156
}
84157
}
85158

86159
eventBus.enablePublishingAndFlushPending();
87160
}
88161

89-
private <T> void register(Component<T> component) {
90-
Lazy<T> lazy =
91-
new Lazy<>(component.getFactory(), new RestrictedComponentContainer(component, this));
92-
93-
for (Class<? super T> anInterface : component.getProvidedInterfaces()) {
94-
lazyInstanceMap.put(anInterface, lazy);
95-
}
96-
}
97-
98162
private void validateDependencies() {
99-
for (Component<?> component : components) {
163+
for (Component<?> component : components.keySet()) {
100164
for (Dependency dependency : component.getDependencies()) {
101165
if (dependency.isRequired() && !lazyInstanceMap.containsKey(dependency.getInterface())) {
102166
throw new MissingDependencyException(

firebase-common/src/main/java/com/google/firebase/components/ComponentSorter.java renamed to firebase-common/src/main/java/com/google/firebase/components/CycleDetector.java

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

1717
import java.util.ArrayList;
18-
import java.util.Collections;
1918
import java.util.HashMap;
2019
import java.util.HashSet;
2120
import java.util.List;
2221
import java.util.Map;
2322
import java.util.Set;
2423

25-
/** Implementation of topological sort. */
26-
class ComponentSorter {
24+
/** Cycle detector for the {@link Component} dependency graph. */
25+
class CycleDetector {
26+
private static class Dep {
27+
private final Class<?> anInterface;
28+
private final boolean set;
29+
30+
private Dep(Class<?> anInterface, boolean set) {
31+
this.anInterface = anInterface;
32+
this.set = set;
33+
}
34+
35+
@Override
36+
public boolean equals(Object obj) {
37+
if (obj instanceof Dep) {
38+
Dep dep = (Dep) obj;
39+
return dep.anInterface.equals(anInterface) && dep.set == set;
40+
}
41+
return false;
42+
}
43+
44+
@Override
45+
public int hashCode() {
46+
int h = 1000003;
47+
h ^= anInterface.hashCode();
48+
h *= 1000003;
49+
h ^= Boolean.valueOf(set).hashCode();
50+
return h;
51+
}
52+
}
53+
2754
private static class ComponentNode {
2855
private final Component<?> component;
2956
private final Set<ComponentNode> dependencies = new HashSet<>();
@@ -63,22 +90,21 @@ boolean isLeaf() {
6390
}
6491

6592
/**
66-
* Given a list of components, returns a sorted permutation of it.
93+
* Detect a dependency cycle among provided {@link Component}s.
6794
*
68-
* @param components Components to sort.
69-
* @return Sorted list of components.
95+
* @param components Components to detect cycle between.
7096
* @throws IllegalArgumentException thrown if multiple components implement the same interface.
7197
* @throws DependencyCycleException thrown if a dependency cycle between components is detected.
7298
*/
73-
static List<Component<?>> sorted(List<Component<?>> components) {
99+
static void detect(List<Component<?>> components) {
74100
Set<ComponentNode> graph = toGraph(components);
75101
Set<ComponentNode> roots = getRoots(graph);
76102

77-
List<Component<?>> result = new ArrayList<>();
103+
int numVisited = 0;
78104
while (!roots.isEmpty()) {
79105
ComponentNode node = roots.iterator().next();
80106
roots.remove(node);
81-
result.add(node.getComponent());
107+
numVisited++;
82108

83109
for (ComponentNode dependent : node.getDependencies()) {
84110
dependent.removeDependent(node);
@@ -88,11 +114,10 @@ static List<Component<?>> sorted(List<Component<?>> components) {
88114
}
89115
}
90116

91-
// If there is no dependency cycle in the graph, the size of the resulting component list will
92-
// be equal to the original list, meaning that we were able to sort all components.
93-
if (result.size() == components.size()) {
94-
Collections.reverse(result);
95-
return result;
117+
// If there is no dependency cycle in the graph, the number of visited nodes will be equal to
118+
// the original list.
119+
if (numVisited == components.size()) {
120+
return;
96121
}
97122

98123
// Otherwise there is a cycle.
@@ -107,34 +132,49 @@ static List<Component<?>> sorted(List<Component<?>> components) {
107132
}
108133

109134
private static Set<ComponentNode> toGraph(List<Component<?>> components) {
110-
Map<Class<?>, ComponentNode> componentIndex = new HashMap<>(components.size());
135+
Map<Dep, Set<ComponentNode>> componentIndex = new HashMap<>(components.size());
111136
for (Component<?> component : components) {
112137
ComponentNode node = new ComponentNode(component);
113138
for (Class<?> anInterface : component.getProvidedInterfaces()) {
114-
if (componentIndex.put(anInterface, node) != null) {
139+
Dep cmp = new Dep(anInterface, !component.isValue());
140+
if (!componentIndex.containsKey(cmp)) {
141+
componentIndex.put(cmp, new HashSet<>());
142+
}
143+
Set<ComponentNode> nodes = componentIndex.get(cmp);
144+
if (!nodes.isEmpty() && !cmp.set) {
115145
throw new IllegalArgumentException(
116146
String.format("Multiple components provide %s.", anInterface));
117147
}
148+
nodes.add(node);
118149
}
119150
}
120151

121-
for (ComponentNode component : componentIndex.values()) {
122-
for (Dependency dependency : component.getComponent().getDependencies()) {
123-
if (!dependency.isDirectInjection()) {
124-
continue;
125-
}
126-
127-
ComponentNode depComponent = componentIndex.get(dependency.getInterface());
128-
// Missing dependencies are skipped for the purposes of the sort as there is no component to
129-
// sort.
130-
if (depComponent != null) {
131-
component.addDependency(depComponent);
132-
depComponent.addDependent(component);
152+
for (Set<ComponentNode> componentNodes : componentIndex.values()) {
153+
for (ComponentNode node : componentNodes) {
154+
for (Dependency dependency : node.getComponent().getDependencies()) {
155+
if (!dependency.isDirectInjection()) {
156+
continue;
157+
}
158+
159+
Set<ComponentNode> depComponents =
160+
componentIndex.get(new Dep(dependency.getInterface(), dependency.isSet()));
161+
if (depComponents == null) {
162+
continue;
163+
}
164+
for (ComponentNode depComponent : depComponents) {
165+
node.addDependency(depComponent);
166+
depComponent.addDependent(node);
167+
}
133168
}
134169
}
135170
}
136171

137-
return new HashSet<>(componentIndex.values());
172+
HashSet<ComponentNode> result = new HashSet<>();
173+
for (Set<ComponentNode> componentNodes : componentIndex.values()) {
174+
result.addAll(componentNodes);
175+
}
176+
177+
return result;
138178
}
139179

140180
private static Set<ComponentNode> getRoots(Set<ComponentNode> components) {

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

0 commit comments

Comments
 (0)