Skip to content
This repository has been archived by the owner on Aug 26, 2021. It is now read-only.

Don't require entry point classes to have @Inject annotations. #57

Merged
merged 1 commit into from Oct 11, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/main/java/dagger/ObjectGraph.java
Expand Up @@ -167,7 +167,7 @@ private void linkStaticInjections() {

private void linkEntryPoints() {
for (Map.Entry<String, Class<?>> entry : entryPoints.entrySet()) {
linker.requestBinding(entry.getKey(), entry.getValue());
linker.requestEntryPoint(entry.getKey(), entry.getValue());
}
}

Expand Down
7 changes: 3 additions & 4 deletions core/src/main/java/dagger/internal/AtInjectBinding.java
Expand Up @@ -135,11 +135,10 @@ private AtInjectBinding(String provideKey, String membersKey, boolean singleton,
}

/**
* @param forMembersInjection true if the binding is being created to inject
* members only. Such injections do not require {@code @Inject}
* @param mustBeInjectable true if the binding must have {@code @Inject}
* annotations.
*/
public static <T> Binding<T> create(Class<T> type, boolean forMembersInjection) {
public static <T> Binding<T> create(Class<T> type, boolean mustBeInjectable) {
boolean singleton = type.isAnnotationPresent(Singleton.class);
List<String> keys = new ArrayList<String>();

Expand Down Expand Up @@ -170,7 +169,7 @@ public static <T> Binding<T> create(Class<T> type, boolean forMembersInjection)
injectedConstructor = constructor;
}
if (injectedConstructor == null) {
if (injectedFields.isEmpty() && !forMembersInjection) {
if (injectedFields.isEmpty() && mustBeInjectable) {
throw new IllegalArgumentException("No injectable members on " + type.getName()
+ ". Do you want to add an injectable constructor?");
}
Expand Down
37 changes: 28 additions & 9 deletions core/src/main/java/dagger/internal/Linker.java
Expand Up @@ -78,12 +78,14 @@ public final void linkRequested() {
Binding binding;
while ((binding = toLink.poll()) != null) {
if (binding instanceof DeferredBinding) {
String key = ((DeferredBinding<?>) binding).deferredKey;
DeferredBinding deferredBinding = (DeferredBinding) binding;
String key = deferredBinding.deferredKey;
boolean mustBeInjectable = deferredBinding.mustBeInjectable;
if (bindings.containsKey(key)) {
continue; // A binding for this key has since been linked.
}
try {
Binding<?> jitBinding = createJitBinding(key, binding.requiredBy);
Binding<?> jitBinding = createJitBinding(key, binding.requiredBy, mustBeInjectable);
// Fail if the type of binding we got wasn't capable of what was requested.
if (!key.equals(jitBinding.provideKey) && !key.equals(jitBinding.membersKey)) {
throw new IllegalStateException("Unable to create binding for " + key);
Expand Down Expand Up @@ -126,7 +128,8 @@ public final void linkRequested() {
* <li>Injections of other types will use the injectable constructors of those classes.
* </ul>
*/
private Binding<?> createJitBinding(String key, Object requiredBy) throws ClassNotFoundException {
private Binding<?> createJitBinding(String key, Object requiredBy, boolean mustBeInjectable)
throws ClassNotFoundException {
String builtInBindingsKey = Keys.getBuiltInBindingsKey(key);
if (builtInBindingsKey != null) {
return new BuiltInBinding<Object>(key, requiredBy, builtInBindingsKey);
Expand All @@ -138,7 +141,7 @@ private Binding<?> createJitBinding(String key, Object requiredBy) throws ClassN

String className = Keys.getClassName(key);
if (className != null && !Keys.isAnnotated(key)) {
Binding<?> atInjectBinding = createAtInjectBinding(key, className);
Binding<?> atInjectBinding = createAtInjectBinding(key, className, mustBeInjectable);
if (atInjectBinding != null) {
return atInjectBinding;
}
Expand All @@ -151,19 +154,33 @@ private Binding<?> createJitBinding(String key, Object requiredBy) throws ClassN
* Returns a binding that uses {@code @Inject} annotations, or null if no such
* binding can be created.
*/
protected abstract Binding<?> createAtInjectBinding(String key, String className)
throws ClassNotFoundException;
protected abstract Binding<?> createAtInjectBinding(
String key, String className, boolean mustBeInjectable) throws ClassNotFoundException;

/**
* Returns the binding if it exists immediately. Otherwise this returns
* null. If the returned binding didn't exist or was unlinked, it will be
* enqueued to be linked.
*/
public final Binding<?> requestBinding(String key, Object requiredBy) {
return requestBinding(key, true, requiredBy);
}

/**
* Like {@link #requestBinding}, but this doesn't require the referenced key
* to be injectable. This is necessary so that generic framework code can
* inject arbitrary entry points (like JUnit test cases or Android activities)
* without concern for whether the specific entry point is injectable.
*/
public final Binding<?> requestEntryPoint(String key, Class<?> requiredByModule) {
return requestBinding(key, false, requiredByModule);
}

private Binding<?> requestBinding(String key, boolean mustBeInjectable, Object requiredBy) {
Binding<?> binding = bindings.get(key);
if (binding == null) {
// We can't satisfy this binding. Make sure it'll work next time!
DeferredBinding<Object> deferredBinding = new DeferredBinding<Object>(key, requiredBy);
Binding<?> deferredBinding = new DeferredBinding(key, requiredBy, mustBeInjectable);
toLink.add(deferredBinding);
attachSuccess = false;
return null;
Expand Down Expand Up @@ -264,11 +281,13 @@ private SingletonBinding(Binding<T> binding) {
}
}

private static class DeferredBinding<T> extends Binding<T> {
private static class DeferredBinding extends Binding<Object> {
final String deferredKey;
private DeferredBinding(String deferredKey, Object requiredBy) {
final boolean mustBeInjectable;
private DeferredBinding(String deferredKey, Object requiredBy, boolean mustBeInjectable) {
super(null, null, false, requiredBy);
this.deferredKey = deferredKey;
this.mustBeInjectable = mustBeInjectable;
}
}
}
8 changes: 4 additions & 4 deletions core/src/main/java/dagger/internal/RuntimeLinker.java
Expand Up @@ -23,8 +23,8 @@
* and falls back to reflection.
*/
public final class RuntimeLinker extends Linker {
@Override protected Binding<?> createAtInjectBinding(String key, String className)
throws ClassNotFoundException {
@Override protected Binding<?> createAtInjectBinding(
String key, String className, boolean mustBeInjectable) throws ClassNotFoundException {
try {
Class<?> c = Class.forName(className + "$InjectAdapter");
Constructor<?> constructor = c.getConstructor();
Expand All @@ -40,7 +40,7 @@ public final class RuntimeLinker extends Linker {
return null;
}

return AtInjectBinding.create(c, Keys.isMembersInjection(key));
return AtInjectBinding.create(c, mustBeInjectable);
}

@Override protected void reportErrors(List<String> errors) {
Expand All @@ -52,6 +52,6 @@ public final class RuntimeLinker extends Linker {
for (String error : errors) {
message.append("\n ").append(error);
}
throw new IllegalArgumentException(message.toString());
throw new IllegalStateException(message.toString());
}
}
Expand Up @@ -44,11 +44,10 @@ private AtInjectBinding(String provideKey, String membersKey,
}

/**
* @param forMembersInjection true if the binding is being created to inject
* members only. Such injections do not require {@code @Inject}
* @param mustBeInjectable true if the binding must have {@code @Inject}
* annotations.
*/
static AtInjectBinding create(TypeElement type, boolean forMembersInjection) {
static AtInjectBinding create(TypeElement type, boolean mustBeInjectable) {
List<String> requiredKeys = new ArrayList<String>();
boolean hasInjectAnnotatedConstructor = false;
boolean isConstructable = false;
Expand Down Expand Up @@ -87,7 +86,7 @@ static AtInjectBinding create(TypeElement type, boolean forMembersInjection) {
}
}

if (!hasInjectAnnotatedConstructor && requiredKeys.isEmpty() && !forMembersInjection) {
if (!hasInjectAnnotatedConstructor && requiredKeys.isEmpty() && mustBeInjectable) {
throw new IllegalArgumentException("No injectable members on "
+ type.getQualifiedName().toString() + ". Do you want to add an injectable constructor?");
}
Expand Down
Expand Up @@ -16,7 +16,6 @@
package dagger.internal.codegen;

import dagger.internal.Binding;
import dagger.internal.Keys;
import dagger.internal.Linker;
import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
Expand All @@ -39,7 +38,8 @@ final class BuildTimeLinker extends Linker {
this.moduleName = moduleName;
}

@Override protected Binding<?> createAtInjectBinding(String key, String className) {
@Override protected Binding<?> createAtInjectBinding(
String key, String className, boolean mustBeInjectable) {
String sourceClassName = className.replace('$', '.');
TypeElement type = processingEnv.getElementUtils().getTypeElement(sourceClassName);
if (type == null) {
Expand All @@ -52,7 +52,7 @@ final class BuildTimeLinker extends Linker {
if (type.getKind() == ElementKind.INTERFACE) {
return null;
}
return AtInjectBinding.create(type, Keys.isMembersInjection(key));
return AtInjectBinding.create(type, mustBeInjectable);
}

@Override protected void reportErrors(List<String> errors) {
Expand Down
35 changes: 31 additions & 4 deletions core/src/test/java/dagger/InjectionTest.java
Expand Up @@ -247,7 +247,7 @@ class TestModule {
try {
graph.validate();
fail();
} catch (IllegalArgumentException expected) {
} catch (IllegalStateException expected) {
}
}

Expand Down Expand Up @@ -391,7 +391,7 @@ class TestModule {
try {
graph.validate();
fail();
} catch (IllegalArgumentException expected) {
} catch (IllegalStateException expected) {
}
}

Expand All @@ -408,7 +408,7 @@ class TestModule {
try {
graph.validate();
fail();
} catch (IllegalArgumentException expected) {
} catch (IllegalStateException expected) {
}
}

Expand Down Expand Up @@ -499,7 +499,7 @@ class TestModule {
try {
graph.validate();
fail();
} catch (IllegalArgumentException expected) {
} catch (IllegalStateException expected) {
}
}

Expand Down Expand Up @@ -593,4 +593,31 @@ BoundTwoWays provideBoundTwoWays() {
graph.inject(membersInjected);
assertEquals("Coke", membersInjected.s);
}

static class NoInjections {
}

@Test public void entryPointNeedsNoInjectAnnotation() {
@Module(entryPoints = NoInjections.class)
class TestModule {
}

ObjectGraph.get(new TestModule()).validate();
}

@Test public void nonEntryPointNeedsInjectAnnotation() {
@Module
class TestModule {
@Provides String provideString(NoInjections noInjections) {
throw new AssertionError();
}
}

ObjectGraph graph = ObjectGraph.get(new TestModule());
try {
graph.validate();
fail();
} catch (IllegalStateException expected) {
}
}
}
8 changes: 4 additions & 4 deletions core/src/test/java/dagger/MembersInjectorTest.java
Expand Up @@ -94,7 +94,7 @@ class TestModule {
try {
graph.getInstance(TestEntryPoint.class);
fail();
} catch (IllegalArgumentException expected) {
} catch (IllegalStateException expected) {
}
}

Expand All @@ -111,7 +111,7 @@ class TestModule {
try {
graph.getInstance(TestEntryPoint.class);
fail();
} catch (IllegalArgumentException expected) {
} catch (IllegalStateException expected) {
}
}

Expand All @@ -128,7 +128,7 @@ class TestModule {
try {
graph.getInstance(TestEntryPoint.class);
fail();
} catch (IllegalArgumentException expected) {
} catch (IllegalStateException expected) {
}
}

Expand Down Expand Up @@ -177,7 +177,7 @@ class TestModule {
try {
graph.getInstance(TestEntryPoint.class);
fail();
} catch (IllegalArgumentException expected) {
} catch (IllegalStateException expected) {
}
}

Expand Down