Skip to content

Commit 54e2b0c

Browse files
authored
Add an extension point to ComponentRuntime. (#3967)
* Add an extension point to ComponentRuntime. It allows augmenting/replacing components as they are requested from ComponentRegistrars. The motivation is to be able to measure initialization time of certain components and at the same time not to introduce this measurement logic directly to the components framework. * add license.
1 parent 5986e67 commit 54e2b0c

File tree

5 files changed

+144
-5
lines changed

5 files changed

+144
-5
lines changed

firebase-components/src/main/java/com/google/firebase/components/Component.java

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

1717
import androidx.annotation.IntDef;
18+
import androidx.annotation.NonNull;
19+
import androidx.annotation.Nullable;
1820
import java.lang.annotation.Retention;
1921
import java.lang.annotation.RetentionPolicy;
2022
import java.util.Arrays;
@@ -76,6 +78,7 @@ public final class Component<T> {
7678
int SET = 1;
7779
}
7880

81+
private final String name;
7982
private final Set<Class<? super T>> providedInterfaces;
8083
private final Set<Dependency> dependencies;
8184
private final @Instantiation int instantiation;
@@ -84,12 +87,14 @@ public final class Component<T> {
8487
private final Set<Class<?>> publishedEvents;
8588

8689
private Component(
90+
@Nullable String name,
8791
Set<Class<? super T>> providedInterfaces,
8892
Set<Dependency> dependencies,
8993
@Instantiation int instantiation,
9094
@ComponentType int type,
9195
ComponentFactory<T> factory,
9296
Set<Class<?>> publishedEvents) {
97+
this.name = name;
9398
this.providedInterfaces = Collections.unmodifiableSet(providedInterfaces);
9499
this.dependencies = Collections.unmodifiableSet(dependencies);
95100
this.instantiation = instantiation;
@@ -98,6 +103,16 @@ private Component(
98103
this.publishedEvents = Collections.unmodifiableSet(publishedEvents);
99104
}
100105

106+
/**
107+
* Optional name of the component.
108+
*
109+
* <p>Used for debug purposes only.
110+
*/
111+
@Nullable
112+
public String getName() {
113+
return name;
114+
}
115+
101116
/**
102117
* Returns all interfaces this component provides.
103118
*
@@ -153,6 +168,12 @@ public boolean isValue() {
153168
return type == ComponentType.VALUE;
154169
}
155170

171+
/** Creates a copy of the component with {@link ComponentFactory} replaced. */
172+
public Component<T> withFactory(ComponentFactory<T> factory) {
173+
return new Component<>(
174+
name, providedInterfaces, dependencies, instantiation, type, factory, publishedEvents);
175+
}
176+
156177
@Override
157178
public String toString() {
158179
StringBuilder sb =
@@ -219,6 +240,7 @@ public static <T> Component<T> intoSet(T value, Class<T> anInterface) {
219240

220241
/** FirebaseComponent builder. */
221242
public static class Builder<T> {
243+
private String name = null;
222244
private final Set<Class<? super T>> providedInterfaces = new HashSet<>();
223245
private final Set<Dependency> dependencies = new HashSet<>();
224246
private @Instantiation int instantiation = Instantiation.LAZY;
@@ -236,6 +258,12 @@ private Builder(Class<T> anInterface, Class<? super T>... additionalInterfaces)
236258
Collections.addAll(providedInterfaces, additionalInterfaces);
237259
}
238260

261+
/** Set a name for the {@link Component} being built. */
262+
public Builder<T> name(@NonNull String name) {
263+
this.name = name;
264+
return this;
265+
}
266+
239267
/** Add a {@link Dependency} to the {@link Component} being built. */
240268
public Builder<T> add(Dependency dependency) {
241269
Preconditions.checkNotNull(dependency, "Null dependency");
@@ -288,6 +316,7 @@ private Builder<T> intoSet() {
288316
public Component<T> build() {
289317
Preconditions.checkState(factory != null, "Missing required property: factory.");
290318
return new Component<>(
319+
name,
291320
new HashSet<>(providedInterfaces),
292321
new HashSet<>(dependencies),
293322
instantiation,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.components;
16+
17+
import java.util.List;
18+
19+
/**
20+
* Provides the ability to customize/decorate components as they are requested by {@link
21+
* ComponentRuntime}.
22+
*
23+
* <p>This makes it possible to do validation, change/add dependencies, or customize components'
24+
* initialization logic by decorating their {@link ComponentFactory factories}.
25+
*/
26+
public interface ComponentRegistrarProcessor {
27+
28+
/** Default "noop" processor. */
29+
ComponentRegistrarProcessor NOOP = ComponentRegistrar::getComponents;
30+
31+
List<Component<?>> processRegistrar(ComponentRegistrar registrar);
32+
}

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public class ComponentRuntime extends AbstractComponentContainer implements Comp
5050
private final List<Provider<ComponentRegistrar>> unprocessedRegistrarProviders;
5151
private final EventBus eventBus;
5252
private final AtomicReference<Boolean> eagerComponentsInitializedWith = new AtomicReference<>();
53+
private final ComponentRegistrarProcessor componentRegistrarProcessor;
5354

5455
/**
5556
* Creates an instance of {@link ComponentRuntime} for the provided {@link ComponentRegistrar}s
@@ -62,7 +63,11 @@ public ComponentRuntime(
6263
Executor defaultEventExecutor,
6364
Iterable<ComponentRegistrar> registrars,
6465
Component<?>... additionalComponents) {
65-
this(defaultEventExecutor, toProviders(registrars), Arrays.asList(additionalComponents));
66+
this(
67+
defaultEventExecutor,
68+
toProviders(registrars),
69+
Arrays.asList(additionalComponents),
70+
ComponentRegistrarProcessor.NOOP);
6671
}
6772

6873
/** A builder for creating {@link ComponentRuntime} instances. */
@@ -73,8 +78,10 @@ public static Builder builder(Executor defaultEventExecutor) {
7378
private ComponentRuntime(
7479
Executor defaultEventExecutor,
7580
Iterable<Provider<ComponentRegistrar>> registrars,
76-
Collection<Component<?>> additionalComponents) {
81+
Collection<Component<?>> additionalComponents,
82+
ComponentRegistrarProcessor componentRegistrarProcessor) {
7783
eventBus = new EventBus(defaultEventExecutor);
84+
this.componentRegistrarProcessor = componentRegistrarProcessor;
7885

7986
List<Component<?>> componentsToAdd = new ArrayList<>();
8087

@@ -106,7 +113,7 @@ private void discoverComponents(List<Component<?>> componentsToAdd) {
106113
try {
107114
ComponentRegistrar registrar = provider.get();
108115
if (registrar != null) {
109-
componentsToAdd.addAll(registrar.getComponents());
116+
componentsToAdd.addAll(componentRegistrarProcessor.processRegistrar(registrar));
110117
iterator.remove();
111118
}
112119
} catch (InvalidRegistrarException ex) {
@@ -218,9 +225,12 @@ private List<Runnable> processSetComponents() {
218225
if (!lazySetMap.containsKey(entry.getKey())) {
219226
lazySetMap.put(entry.getKey(), LazySet.fromCollection(entry.getValue()));
220227
} else {
228+
@SuppressWarnings("unchecked")
221229
LazySet<Object> existingSet = (LazySet<Object>) lazySetMap.get(entry.getKey());
222230
for (Provider<?> provider : entry.getValue()) {
223-
runnables.add(() -> existingSet.add((Provider<Object>) provider));
231+
@SuppressWarnings("unchecked")
232+
Provider<Object> castedProvider = (Provider<Object>) provider;
233+
runnables.add(() -> existingSet.add(castedProvider));
224234
}
225235
}
226236
}
@@ -332,10 +342,17 @@ private void processDependencies() {
332342
}
333343
}
334344

345+
@VisibleForTesting
346+
Collection<Component<?>> getAllComponentsForTest() {
347+
return components.keySet();
348+
}
349+
335350
public static final class Builder {
336351
private final Executor defaultExecutor;
337352
private final List<Provider<ComponentRegistrar>> lazyRegistrars = new ArrayList<>();
338353
private final List<Component<?>> additionalComponents = new ArrayList<>();
354+
private ComponentRegistrarProcessor componentRegistrarProcessor =
355+
ComponentRegistrarProcessor.NOOP;
339356

340357
Builder(Executor defaultExecutor) {
341358
this.defaultExecutor = defaultExecutor;
@@ -356,8 +373,14 @@ public Builder addComponent(Component<?> component) {
356373
return this;
357374
}
358375

376+
public Builder setProcessor(ComponentRegistrarProcessor processor) {
377+
this.componentRegistrarProcessor = processor;
378+
return this;
379+
}
380+
359381
public ComponentRuntime build() {
360-
return new ComponentRuntime(defaultExecutor, lazyRegistrars, additionalComponents);
382+
return new ComponentRuntime(
383+
defaultExecutor, lazyRegistrars, additionalComponents, componentRegistrarProcessor);
361384
}
362385
}
363386
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.List;
2727
import java.util.Set;
2828
import java.util.concurrent.Executor;
29+
import java.util.stream.Collectors;
2930
import org.junit.Test;
3031
import org.junit.runner.RunWith;
3132
import org.junit.runners.JUnit4;
@@ -583,4 +584,32 @@ public void newlyDiscoveredComponent_shouldBecomeAvailableThroughItsDeferred() {
583584
componentLoader.discoverComponents();
584585
assertThat(dependsOnDeferredString.value).isEqualTo("hello");
585586
}
587+
588+
@Test
589+
public void container_withComponentProcessor_shouldDelegateToItForEachComponentRegistrar() {
590+
InitTracker initTracker = new InitTracker();
591+
592+
ComponentFactory<Object> replacedFactory = c -> null;
593+
ComponentRegistrarProcessor processor =
594+
r ->
595+
r.getComponents().stream()
596+
.map(c -> ((Component<Object>) c).withFactory(replacedFactory))
597+
.collect(Collectors.toList());
598+
599+
ComponentRuntime runtime =
600+
ComponentRuntime.builder(EXECUTOR)
601+
.addComponentRegistrar(new ComponentRegistrarImpl(Eagerness.ALWAYS))
602+
.addComponent(Component.of(initTracker, InitTracker.class))
603+
.setProcessor(processor)
604+
.build();
605+
606+
assertThat(
607+
runtime.getAllComponentsForTest().stream()
608+
.filter(
609+
c ->
610+
(c.getProvidedInterfaces().contains(ComponentOne.class)
611+
|| c.getProvidedInterfaces().contains(ComponentTwo.class)))
612+
.allMatch(c -> c.getFactory() == replacedFactory))
613+
.isTrue();
614+
}
586615
}

firebase-components/src/test/java/com/google/firebase/components/ComponentTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,30 @@ public void getFactory_shouldReturnFactorySetInBuilder() {
195195
Component.builder(TestClass.class).factory(nullFactory).build();
196196
assertThat(component.getFactory()).isSameInstanceAs(nullFactory);
197197
}
198+
199+
@Test
200+
public void withFactory_shouldReplaceTheFactorySetInBuilder() {
201+
Component<TestClass> component =
202+
Component.builder(TestClass.class).factory(nullFactory).build();
203+
204+
ComponentFactory<TestClass> newFactory = c -> null;
205+
assertThat(component.withFactory(newFactory).getFactory()).isSameInstanceAs(newFactory);
206+
}
207+
208+
@Test
209+
public void name_shouldDefaultToNull() {
210+
Component<TestClass> component =
211+
Component.builder(TestClass.class).factory(nullFactory).build();
212+
213+
assertThat(component.getName()).isNull();
214+
}
215+
216+
@Test
217+
public void name_shouldReturnNameSetInBuilder() {
218+
String name = "myName";
219+
Component<TestClass> component =
220+
Component.builder(TestClass.class).name(name).factory(nullFactory).build();
221+
222+
assertThat(component.getName()).isEqualTo(name);
223+
}
198224
}

0 commit comments

Comments
 (0)