Removing the ScopeChecker proof-of-concept.
I exercised this utility against a large body of production code trying to find problems. It didn't. It reported a few false positives and some dubious decisions, but ultimately I don't think it's worth having.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@1179 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/util/ScopeChecker.java b/src/com/google/inject/util/ScopeChecker.java
deleted file mode 100644
index 86b44b5..0000000
--- a/src/com/google/inject/util/ScopeChecker.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/**
- * Copyright (C) 2009 Google Inc.
- *
- * 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.google.inject.util;
-
-import com.google.inject.Binding;
-import com.google.inject.ConfigurationException;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.Scope;
-import com.google.inject.Scopes;
-import com.google.inject.internal.ImmutableList;
-import com.google.inject.internal.ImmutableMap;
-import com.google.inject.internal.Lists;
-import com.google.inject.internal.Maps;
-import static com.google.inject.internal.Preconditions.checkArgument;
-import com.google.inject.spi.BindingScopingVisitor;
-import com.google.inject.spi.Dependency;
-import com.google.inject.spi.HasDependencies;
-import com.google.inject.spi.Message;
-import com.google.inject.spi.ProviderBinding;
-import java.lang.annotation.Annotation;
-import static java.util.Arrays.asList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Inspects an injector for scoping violations. Scoping violations exist whenever a long-lived
- * object (such as a singleton) depends on a short-lived object (such as a request-scoped object).
- * To use, create an scope checker and call it's {@code check()} method with your scoping
- * annotations in decreasing duration:
- * <pre><code>
- * ScopeChecker scopeChecker = new ScopeChecker(injector);
- * scopeChecker.check(Singleton.class, SessionScoped.class, RequestScoped.class);
- * </code></pre>
- * If there are scoping violations in the injector, the call will fail with a detailed {@code
- * ConfigurationException}.
- *
- * @author jessewilson@google.com (Jesse Wilson)
- */
-public class ScopeChecker {
-
- private final Injector injector;
-
- public ScopeChecker(Injector injector) {
- this.injector = injector;
- }
-
- /**
- * Checks this checker's injector for scoping violations.
- *
- * @param longest the outermost scope, such as {@code Singleton.class}.
- * @param nested a scope immediately nested within {@code longest}
- * @param furtherNested any scopes nested within {@code nested}, in decreasing duration.
- * @throws ConfigurationException if any violations are found.
- */
- public void check(Class<? extends Annotation> longest, Class<? extends Annotation> nested,
- Class<? extends Annotation>... furtherNested) {
- Ranker ranker = new Ranker(longest, nested, furtherNested);
- Map<Key<?>, Node> nodes = Maps.newHashMap();
-
- // build the graph of node dependencies with scope ranks
- for (Binding<?> binding : injector.getAllBindings().values()) {
- Key<?> key = binding.getKey();
- Node node = getNode(nodes, key);
- ranker.rank(binding, node);
-
- // explicitly ignore dependencies that come via providers.
- if (binding instanceof ProviderBinding) {
- continue;
- }
-
- if (binding instanceof HasDependencies) {
- HasDependencies hasDependencies = (HasDependencies) binding;
- for (Dependency<?> dependency : hasDependencies.getDependencies()) {
- getNode(nodes, dependency.getKey()).addUser(node);
- }
- }
- }
-
- // walk through the nodes, pushing effective scopes through dependencies
- for (Node node : nodes.values()) {
- node.pushScopeToUsers();
- }
-
- // on the nodes with dependencies narrower than themselves, print an error
- List<Message> messages = Lists.newArrayList();
- for (Node node : nodes.values()) {
- if (node.isScopedCorrectly()) {
- continue;
- }
-
- StringBuilder error = new StringBuilder("Illegal scoped dependency: ").append(node);
- Node dependency = node;
- do {
- dependency = dependency.effectiveScopeDependency();
- error.append("\n depends on ").append(dependency);
- } while (!dependency.isEffectiveScopeAppliedScope());
- messages.add(new Message(error.toString()));
- }
-
- if (!messages.isEmpty()) {
- throw new ConfigurationException(messages);
- }
- }
-
- private Node getNode(Map<Key<?>, Node> nodes, Key<?> key) {
- Node node = nodes.get(key);
- if (node == null) {
- node = new Node(key);
- nodes.put(key, node);
- }
- return node;
- }
-
- /**
- * Applies the scoping rank to a node. Scopes are stored as integers, and narrower scopes get
- * greater values.
- */
- private class Ranker implements BindingScopingVisitor<Scope> {
- private final ImmutableList<Class<? extends Annotation>> scopeAnnotations;
- private final ImmutableMap<Scope, Integer> scopeToRank;
-
- private Ranker(Class<? extends Annotation> longest, Class<? extends Annotation> nested,
- Class<? extends Annotation>... furtherNested) {
- scopeAnnotations = new ImmutableList.Builder<Class<? extends Annotation>>()
- .add(longest)
- .add(nested)
- .addAll(asList(furtherNested))
- .build();
-
- ImmutableMap.Builder<Scope, Integer> scopeToRankBuilder = ImmutableMap.builder();
- Map<Class<? extends Annotation>, Scope> annotationToScope = injector.getScopeBindings();
- int i = 0;
- for (Class<? extends Annotation> scopeAnnotation : scopeAnnotations) {
- Scope scope = annotationToScope.get(scopeAnnotation);
- checkArgument(scope != null, "No scope binding for %s", scopeAnnotation);
- scopeToRankBuilder.put(scope, i++);
- }
- scopeToRank = scopeToRankBuilder.build();
- }
-
- public void rank(Binding<?> binding, Node node) {
- Scope scope = binding.acceptScopingVisitor(this);
- Integer rank = scopeToRank.get(scope);
- if (rank != null) {
- node.setScopeRank(rank, scopeAnnotations.get(rank));
- }
- }
-
- public Scope visitEagerSingleton() {
- return Scopes.SINGLETON;
- }
-
- public com.google.inject.Scope visitScope(com.google.inject.Scope scope) {
- return scope;
- }
-
- public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
- throw new AssertionError();
- }
-
- public Scope visitNoScoping() {
- return Scopes.NO_SCOPE;
- }
- }
-}
diff --git a/test/com/google/inject/util/ScopeCheckerTest.java b/test/com/google/inject/util/ScopeCheckerTest.java
deleted file mode 100644
index bb1d710..0000000
--- a/test/com/google/inject/util/ScopeCheckerTest.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/**
- * Copyright (C) 2009 Google Inc.
- *
- * 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.google.inject.util;
-
-import com.google.inject.AbstractModule;
-import static com.google.inject.Asserts.assertContains;
-import com.google.inject.ConfigurationException;
-import com.google.inject.Guice;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.Module;
-import com.google.inject.Provider;
-import com.google.inject.Scope;
-import com.google.inject.ScopeAnnotation;
-import com.google.inject.Singleton;
-import static java.lang.annotation.ElementType.TYPE;
-import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import java.lang.annotation.Target;
-import junit.framework.TestCase;
-
-/**
- * @author jessewilson@google.com (Jesse Wilson)
- */
-public class ScopeCheckerTest extends TestCase {
-
- @Target(TYPE) @Retention(RUNTIME) @ScopeAnnotation
- @interface Annually {}
-
- @Target(TYPE) @Retention(RUNTIME) @ScopeAnnotation
- @interface Seasonally {}
-
- @Target(TYPE) @Retention(RUNTIME) @ScopeAnnotation
- @interface Daily {}
-
- Module scopesModule = new AbstractModule() {
- protected void configure() {
- bindScope(Annually.class, newScope());
- bindScope(Seasonally.class, newScope());
- bindScope(Daily.class, newScope());
- }
- };
-
- /** change your shirt daily. Depends on the sleeve length appropriate for the weather */
- static class Shirt {
- @Inject SleeveLength sleeveLength;
- }
-
- /** long sleeves in the winter, short sleeves in the summer, etc. */
- static class SleeveLength {
- @Inject Style style;
- }
-
- /** fashion evolves over time */
- static class Style {}
-
- /** pants can be tweaked (with belts) to fit a changing style */
- static class Pants {
- @Inject Provider<Style> style;
- }
-
- public void testProperlyNestedScopes() {
- Module module = new AbstractModule() {
- protected void configure() {
- bind(Style.class).in(Annually.class);
- bind(SleeveLength.class).in(Seasonally.class);
- bind(Shirt.class).in(Daily.class);
- }
- };
-
- ScopeChecker scopeChecker = new ScopeChecker(Guice.createInjector(scopesModule, module));
- scopeChecker.check(Annually.class, Seasonally.class, Daily.class);
- }
-
- public void testDependingOnUnscoped() {
- Module module = new AbstractModule() {
- protected void configure() {
- bind(Style.class);
- bind(SleeveLength.class);
- bind(Shirt.class).in(Daily.class);
- }
- };
-
- ScopeChecker scopeChecker = new ScopeChecker(Guice.createInjector(scopesModule, module));
- scopeChecker.check(Annually.class, Seasonally.class, Daily.class);
- }
-
- public void testUsedByUnscoped() {
- Module module = new AbstractModule() {
- protected void configure() {
- bind(Style.class).in(Annually.class);
- bind(SleeveLength.class);
- bind(Shirt.class);
- }
- };
-
- ScopeChecker scopeChecker = new ScopeChecker(Guice.createInjector(scopesModule, module));
- scopeChecker.check(Annually.class, Seasonally.class, Daily.class);
- }
-
- public void testDirectViolation() {
- Module module = new AbstractModule() {
- protected void configure() {
- bind(Style.class).in(Annually.class);
- bind(SleeveLength.class).in(Seasonally.class);
- bind(Shirt.class).in(Annually.class);
- }
- };
-
- ScopeChecker scopeChecker = new ScopeChecker(Guice.createInjector(scopesModule, module));
- try {
- scopeChecker.check(Annually.class, Seasonally.class, Daily.class);
- fail();
- } catch (ConfigurationException expected) {
- assertContains(expected.getMessage(),
- "1) Illegal scoped dependency: " + Shirt.class.getName() + " in @Annually",
- " depends on " + SleeveLength.class.getName() + " in @Seasonally");
- }
- }
-
- public void testDirectDependencyOnProvider() {
- Module module = new AbstractModule() {
- protected void configure() {
- bind(Style.class).in(Daily.class);
- bind(Pants.class).in(Seasonally.class);
- }
- };
-
- ScopeChecker scopeChecker = new ScopeChecker(Guice.createInjector(scopesModule, module));
- scopeChecker.check(Annually.class, Seasonally.class, Daily.class);
- }
-
- public void testIndirectViolation() {
- Module module = new AbstractModule() {
- protected void configure() {
- bind(Style.class).in(Seasonally.class);
- bind(Shirt.class).in(Annually.class);
- }
- };
-
- ScopeChecker scopeChecker = new ScopeChecker(Guice.createInjector(scopesModule, module));
- try {
- scopeChecker.check(Annually.class, Seasonally.class, Daily.class);
- fail();
- } catch (ConfigurationException expected) {
- assertContains(expected.getMessage(),
- "1) Illegal scoped dependency: " + Shirt.class.getName() + " in @Annually",
- " depends on " + SleeveLength.class.getName(),
- " depends on " + Style.class.getName() + " in @Seasonally");
- }
- }
-
- public void testValidCircularDependency() {
- Module module = new AbstractModule() {
- protected void configure() {
- bind(Chicken.class).in(Daily.class);
- bind(Egg.class).in(Daily.class);
- }
- };
-
- ScopeChecker scopeChecker = new ScopeChecker(Guice.createInjector(scopesModule, module));
- scopeChecker.check(Annually.class, Seasonally.class, Daily.class);
- }
-
- public void testInvalidCircularDependency() {
- Module module = new AbstractModule() {
- protected void configure() {
- bind(Chicken.class).in(Seasonally.class);
- bind(Egg.class).in(Daily.class);
- }
- };
-
- ScopeChecker scopeChecker = new ScopeChecker(Guice.createInjector(scopesModule, module));
- try {
- scopeChecker.check(Annually.class, Seasonally.class, Daily.class);
- fail();
- } catch (ConfigurationException expected) {
- assertContains(expected.getMessage(),
- "1) Illegal scoped dependency: " + Chicken.class.getName() + " in @Seasonally",
- " depends on " + Egg.class.getName() + " in @Daily");
- }
- }
-
- public void testCheckUnboundScope() {
- Injector injector = Guice.createInjector();
- ScopeChecker scopeChecker = new ScopeChecker(injector);
-
- try {
- scopeChecker.check(Singleton.class, Daily.class);
- fail();
- } catch (IllegalArgumentException expected) {
- assertContains(expected.getMessage(),
- "No scope binding for " + Daily.class);
- }
- }
-
- static class Chicken {
- @Inject Egg source;
- }
- static class Egg {
- @Inject Chicken source;
- }
-
- private Scope newScope() {
- return new Scope() {
- public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
- return unscoped;
- }
- };
- }
-}