summaryrefslogtreecommitdiffstats
path: root/sangria-contextual
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2014-04-03 11:00:33 -0400
committerTavian Barnes <tavianator@tavianator.com>2014-04-03 11:00:33 -0400
commit2737784763f9041067e9d07bb935c1c9c3952d11 (patch)
tree24b70f170d4c0c13944beea06a59a28be6a47d01 /sangria-contextual
parentebd97d704c85166d8325ba0599466ffbe46f3823 (diff)
downloadsangria-2737784763f9041067e9d07bb935c1c9c3952d11.tar.xz
contextual: Add an extension SPI.
Diffstat (limited to 'sangria-contextual')
-rw-r--r--sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java144
-rw-r--r--sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBindingVisitor.java45
-rw-r--r--sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProviderInstanceBinding.java25
-rw-r--r--sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProviderKeyBinding.java35
-rw-r--r--sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java92
5 files changed, 308 insertions, 33 deletions
diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java
index 7f86973..f398f71 100644
--- a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java
+++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBinder.java
@@ -18,24 +18,27 @@
package com.tavianator.sangria.contextual;
import java.lang.annotation.Annotation;
-import javax.annotation.Nullable;
+import java.util.*;
import javax.inject.Inject;
import com.google.common.base.Objects;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
+import com.google.inject.ConfigurationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.DependencyAndSource;
import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.ProviderInstanceBinding;
+import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.ProvisionListener;
-import com.google.inject.util.Providers;
import com.tavianator.sangria.core.DelayedError;
@@ -137,7 +140,7 @@ public class ContextSensitiveBinder {
public void toContextSensitiveProvider(Key<? extends ContextSensitiveProvider<? extends T>> key) {
error.cancel();
- binder.bind(bindingKey).toProvider(new ProviderAdapter<>(key));
+ binder.bind(bindingKey).toProvider(new ProviderKeyAdapter<>(key));
binder.bindListener(new BindingMatcher(bindingKey), new Trigger(bindingKey));
}
@@ -145,7 +148,7 @@ public class ContextSensitiveBinder {
public void toContextSensitiveProvider(ContextSensitiveProvider<? extends T> provider) {
error.cancel();
- binder.bind(bindingKey).toProvider(new ProviderAdapter<>(provider));
+ binder.bind(bindingKey).toProvider(new ProviderInstanceAdapter<>(provider));
binder.bindListener(new BindingMatcher(bindingKey), new Trigger(bindingKey));
// Match the behaviour of LinkedBindingBuilder#toProvider(Provider)
binder.requestInjection(provider);
@@ -155,66 +158,143 @@ public class ContextSensitiveBinder {
/**
* Adapter from {@link ContextSensitiveProvider} to {@link Provider}.
*/
- private static class ProviderAdapter<T> implements Provider<T> {
+ private static abstract class ProviderAdapter<T> implements ProviderWithExtensionVisitor<T> {
private static final ThreadLocal<InjectionPoint> CURRENT_CONTEXT = new ThreadLocal<>();
- private final Object equalityKey;
- private final @Nullable Key<? extends ContextSensitiveProvider<? extends T>> providerKey;
- private Provider<? extends ContextSensitiveProvider<? extends T>> provider;
+ static void pushContext(InjectionPoint ip) {
+ CURRENT_CONTEXT.set(ip);
+ }
- ProviderAdapter(Key<? extends ContextSensitiveProvider<? extends T>> providerKey) {
- this.equalityKey = providerKey;
- this.providerKey = providerKey;
+ static void popContext() {
+ CURRENT_CONTEXT.remove();
}
- ProviderAdapter(ContextSensitiveProvider<T> provider) {
- this.equalityKey = provider;
- this.providerKey = null;
- this.provider = Providers.of(provider);
+ @Override
+ public T get() {
+ InjectionPoint ip = CURRENT_CONTEXT.get();
+ if (ip != null) {
+ return delegate().getInContext(ip);
+ } else {
+ return delegate().getInUnknownContext();
+ }
+ }
+
+ abstract ContextSensitiveProvider<? extends T> delegate();
+
+ // Have to implement equals()/hashCode() to support binding de-duplication
+ @Override
+ public abstract boolean equals(Object obj);
+
+ @Override
+ public abstract int hashCode();
+ }
+
+ private static class ProviderKeyAdapter<T> extends ProviderAdapter<T> implements ContextSensitiveProviderKeyBinding<T> {
+ private final Key<? extends ContextSensitiveProvider<? extends T>> providerKey;
+ private Provider<? extends ContextSensitiveProvider<? extends T>> provider;
+
+ ProviderKeyAdapter(Key<? extends ContextSensitiveProvider<? extends T>> providerKey) {
+ this.providerKey = providerKey;
}
@Inject
void inject(Injector injector) {
- if (provider == null) {
- provider = injector.getProvider(providerKey);
+ provider = injector.getProvider(providerKey);
+ }
+
+ @Override
+ ContextSensitiveProvider<? extends T> delegate() {
+ return provider.get();
+ }
+
+ @Override
+ public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding) {
+ if (visitor instanceof ContextSensitiveBindingVisitor) {
+ return ((ContextSensitiveBindingVisitor<T, V>)visitor).visit(this);
+ } else {
+ return visitor.visit(binding);
}
}
- static void pushContext(InjectionPoint ip) {
- CURRENT_CONTEXT.set(ip);
+ @Override
+ public Key<? extends ContextSensitiveProvider<? extends T>> getContextSensitiveProviderKey() {
+ return providerKey;
}
- static void popContext() {
- CURRENT_CONTEXT.remove();
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ } else if (!(obj instanceof ProviderKeyAdapter)) {
+ return false;
+ }
+
+ ProviderKeyAdapter<?> other = (ProviderKeyAdapter<?>)obj;
+ return providerKey.equals(other.providerKey);
}
@Override
- public T get() {
- ContextSensitiveProvider<? extends T> delegate = provider.get();
- InjectionPoint ip = CURRENT_CONTEXT.get();
- if (ip != null) {
- return delegate.getInContext(ip);
+ public int hashCode() {
+ return Objects.hashCode(providerKey);
+ }
+ }
+
+ private static class ProviderInstanceAdapter<T> extends ProviderAdapter<T> implements ContextSensitiveProviderInstanceBinding<T> {
+ private final ContextSensitiveProvider<? extends T> instance;
+ private Set<InjectionPoint> injectionPoints;
+
+ ProviderInstanceAdapter(ContextSensitiveProvider<? extends T> instance) {
+ this.instance = instance;
+
+ Set<InjectionPoint> injectionPoints;
+ try {
+ injectionPoints = InjectionPoint.forInstanceMethodsAndFields(instance.getClass());
+ } catch (ConfigurationException e) {
+ // We can ignore the error, the earlier requestInjection(instance) call will have reported it
+ injectionPoints = e.getPartialValue();
+ }
+ this.injectionPoints = injectionPoints;
+ }
+
+ @Override
+ ContextSensitiveProvider<? extends T> delegate() {
+ return instance;
+ }
+
+ @Override
+ public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding) {
+ if (visitor instanceof ContextSensitiveBindingVisitor) {
+ return ((ContextSensitiveBindingVisitor<T, V>)visitor).visit(this);
} else {
- return delegate.getInUnknownContext();
+ return visitor.visit(binding);
}
}
- // Have to implement equals()/hashCode() here to support binding de-duplication
+ @Override
+ public ContextSensitiveProvider<? extends T> getContextSensitiveProviderInstance() {
+ return instance;
+ }
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ return injectionPoints;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
- } else if (!(obj instanceof ProviderAdapter)) {
+ } else if (!(obj instanceof ProviderInstanceAdapter)) {
return false;
}
- ProviderAdapter<?> other = (ProviderAdapter<?>)obj;
- return equalityKey.equals(other.equalityKey);
+ ProviderInstanceAdapter<?> other = (ProviderInstanceAdapter<?>)obj;
+ return instance.equals(other.instance);
}
@Override
public int hashCode() {
- return Objects.hashCode(equalityKey);
+ return Objects.hashCode(instance);
}
}
diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBindingVisitor.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBindingVisitor.java
new file mode 100644
index 0000000..59a3864
--- /dev/null
+++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveBindingVisitor.java
@@ -0,0 +1,45 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+package com.tavianator.sangria.contextual;
+
+import com.google.inject.spi.BindingTargetVisitor;
+
+/**
+ * Visitor interface for the context-sensitive binding SPI.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.0
+ * @since 1.0
+ */
+public interface ContextSensitiveBindingVisitor<T, V> extends BindingTargetVisitor<T, V> {
+ /**
+ * Visit a {@link ContextSensitiveProviderKeyBinding}.
+ *
+ * @param binding The binding to visit.
+ * @return A value of type {@code V}.
+ */
+ V visit(ContextSensitiveProviderKeyBinding<? extends T> binding);
+
+ /**
+ * Visit a {@link ContextSensitiveProviderKeyBinding}.
+ *
+ * @param binding The binding to visit.
+ * @return A value of type {@code V}.
+ */
+ V visit(ContextSensitiveProviderInstanceBinding<? extends T> binding);
+}
diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProviderInstanceBinding.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProviderInstanceBinding.java
new file mode 100644
index 0000000..2fb3353
--- /dev/null
+++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProviderInstanceBinding.java
@@ -0,0 +1,25 @@
+package com.tavianator.sangria.contextual;
+
+import java.util.*;
+
+import com.google.inject.spi.InjectionPoint;
+
+/**
+ * SPI for {@link ContextSensitiveProvider} key bindings.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.0
+ * @see ContextSensitiveBindingBuilder#toContextSensitiveProvider(ContextSensitiveProvider)
+ * @since 1.0
+ */
+public interface ContextSensitiveProviderInstanceBinding<T> {
+ /**
+ * @return The {@link ContextSensitiveProvider} instance for this binding.
+ */
+ ContextSensitiveProvider<? extends T> getContextSensitiveProviderInstance();
+
+ /**
+ * @return The field and method {@link InjectionPoint}s of the {@link ContextSensitiveProvider} instance.
+ */
+ Set<InjectionPoint> getInjectionPoints();
+}
diff --git a/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProviderKeyBinding.java b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProviderKeyBinding.java
new file mode 100644
index 0000000..cdf025f
--- /dev/null
+++ b/sangria-contextual/src/main/java/com/tavianator/sangria/contextual/ContextSensitiveProviderKeyBinding.java
@@ -0,0 +1,35 @@
+/****************************************************************************
+ * Sangria *
+ * Copyright (C) 2014 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ ****************************************************************************/
+
+package com.tavianator.sangria.contextual;
+
+import com.google.inject.Key;
+
+/**
+ * SPI for {@link ContextSensitiveProvider} key bindings.
+ *
+ * @author Tavian Barnes (tavianator@tavianator.com)
+ * @version 1.0
+ * @since 1.0
+ * @see ContextSensitiveBindingBuilder#toContextSensitiveProvider(Key)
+ */
+public interface ContextSensitiveProviderKeyBinding<T> {
+ /**
+ * @return The {@link Key} used to retrieve the {@link ContextSensitiveProvider}'s binding.
+ */
+ Key<? extends ContextSensitiveProvider<? extends T>> getContextSensitiveProviderKey();
+}
diff --git a/sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java b/sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java
index 3463b0e..1e2ab49 100644
--- a/sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java
+++ b/sangria-contextual/src/test/java/com/tavianator/sangria/contextual/ContextSensitiveBinderTest.java
@@ -17,10 +17,12 @@
package com.tavianator.sangria.contextual;
+import java.util.*;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
@@ -30,6 +32,9 @@ import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
+import com.google.inject.spi.DefaultBindingTargetVisitor;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
import com.google.inject.spi.InjectionPoint;
import org.junit.Rule;
import org.junit.Test;
@@ -49,6 +54,10 @@ public class ContextSensitiveBinderTest {
public @Rule ExpectedException thrown = ExpectedException.none();
private static class SelfProvider implements ContextSensitiveProvider<String> {
+ // For testing getInjectionPoints() in the SPI below
+ @SuppressWarnings("unused")
+ @Inject Injector injector;
+
@Override
public String getInContext(InjectionPoint injectionPoint) {
return injectionPoint.getDeclaringType().getRawType().getSimpleName();
@@ -138,7 +147,7 @@ public class ContextSensitiveBinderTest {
}
@Test
- public void testDeDuplication() {
+ public void testKeyDeDuplication() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
@@ -153,6 +162,30 @@ public class ContextSensitiveBinderTest {
.toContextSensitiveProvider(SelfProvider.class);
}
});
+
+ HasSelf hasSelf = injector.getInstance(HasSelf.class);
+ assertThat(hasSelf.self, equalTo("HasSelf"));
+ assertThat(hasSelf.selfProvider.get(), equalTo("<unknown>"));
+ }
+
+ @Test
+ public void testInstanceDeDuplication() {
+ final SelfProvider selfProvider = new SelfProvider();
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override
+ protected void configure() {
+ ContextSensitiveBinder contextualBinder = ContextSensitiveBinder.create(binder());
+ contextualBinder
+ .bind(String.class)
+ .annotatedWith(Names.named("self"))
+ .toContextSensitiveProvider(selfProvider);
+ contextualBinder
+ .bind(String.class)
+ .annotatedWith(Names.named("self"))
+ .toContextSensitiveProvider(selfProvider);
+ }
+ });
+
HasSelf hasSelf = injector.getInstance(HasSelf.class);
assertThat(hasSelf.self, equalTo("HasSelf"));
assertThat(hasSelf.selfProvider.get(), equalTo("<unknown>"));
@@ -272,4 +305,61 @@ public class ContextSensitiveBinderTest {
}
});
}
+
+ private static class TestVisitor<T> extends DefaultBindingTargetVisitor<T, Boolean> implements ContextSensitiveBindingVisitor<T, Boolean> {
+ @Override
+ public Boolean visit(ContextSensitiveProviderKeyBinding<? extends T> binding) {
+ assertThat(binding.getContextSensitiveProviderKey().equals(new Key<SelfProvider>() { }), is(true));
+ return true;
+ }
+
+ @Override
+ public Boolean visit(ContextSensitiveProviderInstanceBinding<? extends T> binding) {
+ assertThat(binding.getContextSensitiveProviderInstance(), instanceOf(SelfProvider.class));
+ assertThat(binding.getInjectionPoints(), hasSize(1));
+ return true;
+ }
+
+ @Override
+ protected Boolean visitOther(Binding<? extends T> binding) {
+ return false;
+ }
+ }
+
+ private <T> boolean visit(Binding<T> binding) {
+ return binding.acceptTargetVisitor(new TestVisitor<T>());
+ }
+
+ @Test
+ public void testExtensionSpi() {
+ List<Element> elements = Elements.getElements(new AbstractModule() {
+ @Override
+ protected void configure() {
+ ContextSensitiveBinder contextualBinder = ContextSensitiveBinder.create(binder());
+ contextualBinder
+ .bind(String.class)
+ .annotatedWith(Names.named("key"))
+ .toContextSensitiveProvider(SelfProvider.class);
+ contextualBinder
+ .bind(String.class)
+ .annotatedWith(Names.named("instance"))
+ .toContextSensitiveProvider(new SelfProvider());
+ }
+ });
+
+ int passed = 0;
+ for (Element element : elements) {
+ if (element instanceof Binding) {
+ if (visit(((Binding<?>)element))) {
+ ++passed;
+ }
+ }
+ }
+ assertThat(passed, equalTo(2));
+
+ Injector injector = Guice.createInjector(Elements.getModule(elements));
+ assertThat(visit(injector.getBinding(new Key<String>(Names.named("key")) { })), is(true));
+ assertThat(visit(injector.getBinding(new Key<String>(Names.named("instance")) { })), is(true));
+ assertThat(visit(injector.getBinding(SelfProvider.class)), is(false));
+ }
}