Skip to content

Commit 51fb015

Browse files
authored
Add the concept of Set-components and dependencies. (#169)
* Add the contept of Set-components and dependencies. Set-components are components of type T that contribute values into a larger Set<T> rather than registering one unique T value whose uniqueness is enforced at runtime. * Add license to source file. * Update consumer proguard config.
1 parent 5539d80 commit 51fb015

File tree

5 files changed

+251
-13
lines changed

5 files changed

+251
-13
lines changed

firebase-common/proguard.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
-dontwarn com.google.firebase.components.Component$Instantiation
1+
-dontwarn com.google.firebase.components.Component$Instantiation
2+
-dontwarn com.google.firebase.components.Component$ComponentType

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

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,67 @@
3636
@KeepForSdk
3737
public final class Component<T> {
3838

39+
/** Specifies instantiation behavior of a {@link Component}. */
3940
@IntDef({Instantiation.LAZY, Instantiation.ALWAYS_EAGER, Instantiation.EAGER_IN_DEFAULT_APP})
4041
@Retention(RetentionPolicy.SOURCE)
4142
private @interface Instantiation {
43+
/** Component is not instantiated until requested by developer or a dependent component. */
4244
int LAZY = 0;
45+
46+
/**
47+
* Component is unconditionally instantiated upon startup of the {@link ComponentRuntime}.
48+
*
49+
* <p>Namely when {@link ComponentRuntime#initializeEagerComponents(boolean)} is called.
50+
*/
4351
int ALWAYS_EAGER = 1;
52+
53+
/**
54+
* Component is instantiated upon startup of the {@link ComponentRuntime} if the runtime is
55+
* initialized for the default app.
56+
*/
4457
int EAGER_IN_DEFAULT_APP = 2;
4558
}
4659

60+
/** Specifies the type of a {@link Component}. */
61+
@IntDef({ComponentType.VALUE, ComponentType.SET})
62+
@Retention(RetentionPolicy.SOURCE)
63+
private @interface ComponentType {
64+
/**
65+
* Value components provide scalar values to the {@link ComponentRuntime}.
66+
*
67+
* <p>Such components can be requested by dependents via {@link ComponentContainer#get(Class)}
68+
* or {@link ComponentContainer#getProvider(Class)}. e.g. {@code FirebaseInstanceId}.
69+
*/
70+
int VALUE = 0;
71+
72+
/**
73+
* Set components collectively contribute values of type {@code T} to a {@link Set
74+
* Set&lt;T&gt;}.
75+
*
76+
* <p>Such components can be requested by dependents via {@link ComponentContainer#setOf(Class)}
77+
* or {@link ComponentContainer#setOfProvider(Class)}.
78+
*/
79+
int SET = 1;
80+
}
81+
4782
private final Set<Class<? super T>> providedInterfaces;
4883
private final Set<Dependency> dependencies;
4984
private final @Instantiation int instantiation;
85+
private final @ComponentType int type;
5086
private final ComponentFactory<T> factory;
5187
private final Set<Class<?>> publishedEvents;
5288

5389
private Component(
5490
Set<Class<? super T>> providedInterfaces,
5591
Set<Dependency> dependencies,
5692
@Instantiation int instantiation,
93+
@ComponentType int type,
5794
ComponentFactory<T> factory,
5895
Set<Class<?>> publishedEvents) {
5996
this.providedInterfaces = Collections.unmodifiableSet(providedInterfaces);
6097
this.dependencies = Collections.unmodifiableSet(dependencies);
6198
this.instantiation = instantiation;
99+
this.type = type;
62100
this.factory = factory;
63101
this.publishedEvents = Collections.unmodifiableSet(publishedEvents);
64102
}
@@ -113,30 +151,38 @@ public boolean isEagerInDefaultApp() {
113151
return instantiation == Instantiation.EAGER_IN_DEFAULT_APP;
114152
}
115153

154+
/** Returns whether a component is a Value Component or a Set Component. */
155+
public boolean isValue() {
156+
return type == ComponentType.VALUE;
157+
}
158+
116159
@Override
117160
public String toString() {
118161
StringBuilder sb =
119162
new StringBuilder("Component<")
120163
.append(Arrays.toString(providedInterfaces.toArray()))
121164
.append(">{")
122165
.append(instantiation)
166+
.append(", type=")
167+
.append(type)
123168
.append(", deps=")
124169
.append(Arrays.toString(dependencies.toArray()))
125170
.append("}");
126171
return sb.toString();
127172
}
128173

129-
@KeepForSdk
130174
/** Returns a Component<T> builder. */
175+
@KeepForSdk
131176
public static <T> Component.Builder<T> builder(Class<T> anInterface) {
132-
return new Builder<T>(anInterface);
177+
return new Builder<>(anInterface);
133178
}
134179

135-
@KeepForSdk
136180
/** Returns a Component<T> builder. */
181+
@KeepForSdk
182+
@SafeVarargs
137183
public static <T> Component.Builder<T> builder(
138184
Class<T> anInterface, Class<? super T>... additionalInterfaces) {
139-
return new Builder<T>(anInterface, additionalInterfaces);
185+
return new Builder<>(anInterface, additionalInterfaces);
140186
}
141187

142188
/**
@@ -151,22 +197,46 @@ public static <T> Component<T> of(Class<T> anInterface, T value) {
151197
}
152198

153199
/** Wraps a value in a {@link Component} with no dependencies. */
154-
@SafeVarargs
155200
@KeepForSdk
201+
@SafeVarargs
156202
public static <T> Component<T> of(
157203
T value, Class<T> anInterface, Class<? super T>... additionalInterfaces) {
158204
return builder(anInterface, additionalInterfaces).factory((args) -> value).build();
159205
}
160206

207+
/**
208+
* Provides a builder for a {@link Set}-multibinding {@link Component}.
209+
*
210+
* <p>Such components can be requested by dependents via {@link ComponentContainer#setOf(Class)} *
211+
* or {@link ComponentContainer#setOfProvider(Class)}.
212+
*/
213+
@KeepForSdk
214+
public static <T> Component.Builder<T> intoSetBuilder(Class<T> anInterface) {
215+
return builder(anInterface).intoSet();
216+
}
217+
218+
/**
219+
* Wraps a value in a {@link Set}-multibinding {@link Component} with no dependencies. *
220+
*
221+
* <p>Such components can be requested by dependents via {@link ComponentContainer#setOf(Class)} *
222+
* or {@link ComponentContainer#setOfProvider(Class)}.
223+
*/
224+
@KeepForSdk
225+
public static <T> Component<T> intoSet(T value, Class<T> anInterface) {
226+
return intoSetBuilder(anInterface).factory(c -> value).build();
227+
}
228+
161229
/** FirebaseComponent builder. */
162230
@KeepForSdk
163231
public static class Builder<T> {
164232
private final Set<Class<? super T>> providedInterfaces = new HashSet<>();
165233
private final Set<Dependency> dependencies = new HashSet<>();
166234
private @Instantiation int instantiation = Instantiation.LAZY;
235+
private @ComponentType int type = ComponentType.VALUE;
167236
private ComponentFactory<T> factory;
168237
private Set<Class<?>> publishedEvents = new HashSet<>();
169238

239+
@SafeVarargs
170240
private Builder(Class<T> anInterface, Class<? super T>... additionalInterfaces) {
171241
Preconditions.checkNotNull(anInterface, "Null interface");
172242
providedInterfaces.add(anInterface);
@@ -176,6 +246,7 @@ private Builder(Class<T> anInterface, Class<? super T>... additionalInterfaces)
176246
Collections.addAll(providedInterfaces, additionalInterfaces);
177247
}
178248

249+
/** Add a {@link Dependency} to the {@link Component} being built. */
179250
@KeepForSdk
180251
public Builder<T> add(Dependency dependency) {
181252
Preconditions.checkNotNull(dependency, "Null dependency");
@@ -184,16 +255,19 @@ public Builder<T> add(Dependency dependency) {
184255
return this;
185256
}
186257

258+
/** Make the {@link Component} initialize upon startup. */
187259
@KeepForSdk
188260
public Builder<T> alwaysEager() {
189261
return setInstantiation(Instantiation.ALWAYS_EAGER);
190262
}
191263

264+
/** Make the component initialize upon startup in default app. */
192265
@KeepForSdk
193266
public Builder<T> eagerInDefaultApp() {
194267
return setInstantiation(Instantiation.EAGER_IN_DEFAULT_APP);
195268
}
196269

270+
/** Make the {@link Component} eligible to publish events of provided eventType. */
197271
@KeepForSdk
198272
public Builder<T> publishes(Class<?> eventType) {
199273
publishedEvents.add(eventType);
@@ -213,19 +287,27 @@ private void validateInterface(Class<?> anInterface) {
213287
"Components are not allowed to depend on interfaces they themselves provide.");
214288
}
215289

290+
/** Set the factory that will be used to initialize the {@link Component}. */
216291
@KeepForSdk
217292
public Builder<T> factory(ComponentFactory<T> value) {
218293
factory = Preconditions.checkNotNull(value, "Null factory");
219294
return this;
220295
}
221296

297+
private Builder<T> intoSet() {
298+
type = ComponentType.SET;
299+
return this;
300+
}
301+
302+
/** Return the built {@link Component} definition. */
222303
@KeepForSdk
223304
public Component<T> build() {
224305
Preconditions.checkState(factory != null, "Missing required property: factory.");
225306
return new Component<>(
226307
new HashSet<>(providedInterfaces),
227308
new HashSet<>(dependencies),
228309
instantiation,
310+
type,
229311
factory,
230312
publishedEvents);
231313
}

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
@KeepForSdk
2525
public final class Dependency {
2626
/** Enumerates dependency types. */
27-
@IntDef({Type.OPTIONAL, Type.REQUIRED})
27+
@IntDef({Type.OPTIONAL, Type.REQUIRED, Type.SET})
2828
@Retention(RetentionPolicy.SOURCE)
2929
private @interface Type {
3030
int OPTIONAL = 0;
3131
int REQUIRED = 1;
32+
int SET = 2;
3233
}
3334

3435
@IntDef({Injection.DIRECT, Injection.PROVIDER})
@@ -58,6 +59,11 @@ public static Dependency required(Class<?> anInterface) {
5859
return new Dependency(anInterface, Type.REQUIRED, Injection.DIRECT);
5960
}
6061

62+
@KeepForSdk
63+
public static Dependency setOf(Class<?> anInterface) {
64+
return new Dependency(anInterface, Type.SET, Injection.DIRECT);
65+
}
66+
6167
@KeepForSdk
6268
public static Dependency optionalProvider(Class<?> anInterface) {
6369
return new Dependency(anInterface, Type.OPTIONAL, Injection.PROVIDER);
@@ -68,6 +74,11 @@ public static Dependency requiredProvider(Class<?> anInterface) {
6874
return new Dependency(anInterface, Type.REQUIRED, Injection.PROVIDER);
6975
}
7076

77+
@KeepForSdk
78+
public static Dependency setOfProvider(Class<?> anInterface) {
79+
return new Dependency(anInterface, Type.SET, Injection.PROVIDER);
80+
}
81+
7182
public Class<?> getInterface() {
7283
return anInterface;
7384
}
@@ -76,6 +87,10 @@ public boolean isRequired() {
7687
return type == Type.REQUIRED;
7788
}
7889

90+
public boolean isSet() {
91+
return type == Type.SET;
92+
}
93+
7994
public boolean isDirectInjection() {
8095
return injection == Injection.DIRECT;
8196
}
@@ -105,8 +120,8 @@ public String toString() {
105120
StringBuilder sb =
106121
new StringBuilder("Dependency{anInterface=")
107122
.append(anInterface)
108-
.append(", required=")
109-
.append(type == Type.REQUIRED)
123+
.append(", type=")
124+
.append(type == Type.REQUIRED ? "required" : type == Type.OPTIONAL ? "optional" : "set")
110125
.append(", direct=")
111126
.append(injection == Injection.DIRECT)
112127
.append("}");

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

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.truth.Truth.assertThat;
1818
import static org.junit.Assert.fail;
1919

20+
import java.math.BigDecimal;
2021
import java.util.List;
2122
import org.junit.Test;
2223
import org.junit.runner.RunWith;
@@ -26,16 +27,55 @@
2627
public class ComponentTest {
2728
interface TestInterface {}
2829

29-
static class TestClass implements TestInterface {}
30+
private static class TestClass implements TestInterface {}
3031

3132
private final ComponentFactory<TestClass> nullFactory = container -> null;
3233

34+
@Test
35+
public void of_withMultipleInterfaces_shouldSetCorrectDefaults() {
36+
TestClass testClass = new TestClass();
37+
Component<TestClass> component = Component.of(testClass, TestClass.class, TestInterface.class);
38+
assertThat(component.getProvidedInterfaces())
39+
.containsExactly(TestClass.class, TestInterface.class);
40+
assertThat(component.isLazy()).isTrue();
41+
assertThat(component.isValue()).isTrue();
42+
assertThat(component.isAlwaysEager()).isFalse();
43+
assertThat(component.isEagerInDefaultApp()).isFalse();
44+
assertThat(component.getDependencies()).isEmpty();
45+
assertThat(component.getFactory().create(null)).isSameAs(testClass);
46+
}
47+
3348
@Test
3449
public void builder_shouldSetCorrectDefaults() {
3550
Component<TestClass> component =
3651
Component.builder(TestClass.class).factory(nullFactory).build();
3752
assertThat(component.getProvidedInterfaces()).containsExactly(TestClass.class);
3853
assertThat(component.isLazy()).isTrue();
54+
assertThat(component.isValue()).isTrue();
55+
assertThat(component.isAlwaysEager()).isFalse();
56+
assertThat(component.isEagerInDefaultApp()).isFalse();
57+
assertThat(component.getDependencies()).isEmpty();
58+
}
59+
60+
@Test
61+
public void intoSetBuilder_shouldSetCorrectDefaults() {
62+
Component<TestClass> component =
63+
Component.intoSetBuilder(TestClass.class).factory(nullFactory).build();
64+
assertThat(component.getProvidedInterfaces()).containsExactly(TestClass.class);
65+
assertThat(component.isLazy()).isTrue();
66+
assertThat(component.isValue()).isFalse();
67+
assertThat(component.isAlwaysEager()).isFalse();
68+
assertThat(component.isEagerInDefaultApp()).isFalse();
69+
assertThat(component.getDependencies()).isEmpty();
70+
}
71+
72+
@Test
73+
public void intoSet_shouldSetCorrectDefaults() {
74+
TestClass testClass = new TestClass();
75+
Component<TestClass> component = Component.intoSet(testClass, TestClass.class);
76+
assertThat(component.getProvidedInterfaces()).containsExactly(TestClass.class);
77+
assertThat(component.isLazy()).isTrue();
78+
assertThat(component.isValue()).isFalse();
3979
assertThat(component.isAlwaysEager()).isFalse();
4080
assertThat(component.isEagerInDefaultApp()).isFalse();
4181
assertThat(component.getDependencies()).isEmpty();
@@ -62,7 +102,7 @@ public void eagerInDefaultApp_shouldProperlySetComponentInitialization() {
62102
}
63103

64104
@Test
65-
public void uptatingInstantiationMultipleTimes_shouldThrow() {
105+
public void updatingInstantiationMultipleTimes_shouldThrow() {
66106
Component.Builder<TestClass> builder = Component.builder(TestClass.class).eagerInDefaultApp();
67107

68108
try {
@@ -79,18 +119,22 @@ public void add_shouldProperlyAddDependencies() {
79119
Component.builder(TestClass.class)
80120
.add(Dependency.required(List.class))
81121
.add(Dependency.optional(Integer.class))
122+
.add(Dependency.setOf(Long.class))
82123
.add(Dependency.requiredProvider(Float.class))
83124
.add(Dependency.optionalProvider(Double.class))
125+
.add(Dependency.setOfProvider(BigDecimal.class))
84126
.factory(nullFactory)
85127
.build();
86128

87-
assertThat(component.getDependencies()).hasSize(4);
129+
assertThat(component.getDependencies()).hasSize(6);
88130
assertThat(component.getDependencies())
89131
.containsExactly(
90132
Dependency.required(List.class),
91133
Dependency.optional(Integer.class),
134+
Dependency.setOf(Long.class),
92135
Dependency.requiredProvider(Float.class),
93-
Dependency.optionalProvider(Double.class));
136+
Dependency.optionalProvider(Double.class),
137+
Dependency.setOfProvider(BigDecimal.class));
94138
}
95139

96140
@Test
@@ -113,6 +157,18 @@ public void addOptionalDependency_onSelf_shouldThrow() {
113157
}
114158
}
115159

160+
@Test
161+
public void publishes_shouldProperlyAddToPublishedEvents() {
162+
Component<TestClass> component =
163+
Component.builder(TestClass.class)
164+
.factory(nullFactory)
165+
.publishes(Integer.class)
166+
.publishes(Float.class)
167+
.build();
168+
169+
assertThat(component.getPublishedEvents()).containsExactly(Integer.class, Float.class);
170+
}
171+
116172
@Test
117173
public void builder_withMultipleInterfaces_shouldProperlySetInterfaces() {
118174
Component<TestClass> component =

0 commit comments

Comments
 (0)