Redesigned scopes to enable custom implementations. Renamed DependencyException to ConfigurationException. Cleaned up Container's interface. It now returns a Factory instead of a direct instance so clients don't have to keep passing in the paramters resulting in a map lookup. Added support for injecting Factory<T> where T is any bound type.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@17 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/DependencyException.java b/src/com/google/inject/ConfigurationException.java
similarity index 72%
rename from src/com/google/inject/DependencyException.java
rename to src/com/google/inject/ConfigurationException.java
index 25fa5b1..a1ea74a 100644
--- a/src/com/google/inject/DependencyException.java
+++ b/src/com/google/inject/ConfigurationException.java
@@ -17,21 +17,21 @@
package com.google.inject;
/**
- * Thrown when a dependency is misconfigured.
+ * Thrown when the {@link ContainerBuilder} is misconfigured.
*
* @author crazybob@google.com (Bob Lee)
*/
-public class DependencyException extends RuntimeException {
+public class ConfigurationException extends RuntimeException {
- public DependencyException(String message) {
+ public ConfigurationException(String message) {
super(message);
}
- public DependencyException(String message, Throwable cause) {
+ public ConfigurationException(String message, Throwable cause) {
super(message, cause);
}
- public DependencyException(Throwable cause) {
+ public ConfigurationException(Throwable cause) {
super(cause);
}
}
diff --git a/src/com/google/inject/ConstantConversionException.java b/src/com/google/inject/ConstantConversionException.java
index 6fb0467..23e6558 100644
--- a/src/com/google/inject/ConstantConversionException.java
+++ b/src/com/google/inject/ConstantConversionException.java
@@ -23,7 +23,7 @@
*
* @author crazybob@google.com (Bob Lee)
*/
-class ConstantConversionException extends DependencyException {
+class ConstantConversionException extends ConfigurationException {
ConstantConversionException(Member member, Key<?> key, String value,
String reason) {
diff --git a/src/com/google/inject/ConstructionContext.java b/src/com/google/inject/ConstructionContext.java
index 2da79e1..25a9fe4 100644
--- a/src/com/google/inject/ConstructionContext.java
+++ b/src/com/google/inject/ConstructionContext.java
@@ -66,7 +66,7 @@
// instance (as opposed to one per caller).
if (!expectedType.isInterface()) {
- throw new DependencyException(
+ throw new ConfigurationException(
expectedType.getName() + " is not an interface.");
}
diff --git a/src/com/google/inject/Container.java b/src/com/google/inject/Container.java
index 3fe50a5..44f8e33 100644
--- a/src/com/google/inject/Container.java
+++ b/src/com/google/inject/Container.java
@@ -18,7 +18,7 @@
/**
* Injects dependencies into constructors, methods and fields annotated with
- * {@link Inject}. Immutable.
+ * {@link Inject}.
*
* <p>When injecting a method or constructor, you can additionally annotate
* its parameters with {@link Inject} and specify a dependency name. When a
@@ -52,11 +52,12 @@
* }
* </pre>
*
- * <p>To create and inject an instance of {@code Foo}:
+ * <p>To get an instance of {@code Foo}:
*
* <pre>
* Container c = ...;
- * Foo foo = c.inject(Foo.class);
+ * Factory<Foo> fooFactory = c.getFactory(Key.get(Foo.class));
+ * Foo foo = fooFactory.get();
* </pre>
*
* @see ContainerBuilder
@@ -67,34 +68,17 @@
/**
* Injects dependencies into the fields and methods of an existing object.
*/
- void inject(Object o);
+ void injectMembers(Object o);
/**
* Creates and injects a new instance of type {@code implementation}.
*/
- <T> T inject(Class<T> implementation);
+ <T> T newInstance(Class<T> implementation);
/**
- * Gets an instance of the given dependency which was declared in
- * {@link com.google.inject.ContainerBuilder}.
+ * Gets the factory bound to the given key.
*/
- <T> T getInstance(Class<T> type, String name);
-
- /**
- * Convenience method. Equivalent to {@code get(type,
- * DEFAULT_NAME)}.
- */
- <T> T getInstance(Class<T> type);
-
- /**
- * Sets the scope strategy for the current thread.
- */
- void setScopeStrategy(Scope.Strategy scopeStrategy);
-
- /**
- * Removes the scope strategy for the current thread.
- */
- void removeScopeStrategy();
+ <T> Factory<T> getFactory(Key<T> key);
/**
* Checks whether the container has a binding for given key.
diff --git a/src/com/google/inject/ContainerBuilder.java b/src/com/google/inject/ContainerBuilder.java
index 655a677..c5e7634 100644
--- a/src/com/google/inject/ContainerBuilder.java
+++ b/src/com/google/inject/ContainerBuilder.java
@@ -51,12 +51,16 @@
*/
public final class ContainerBuilder {
+ private static final Logger logger =
+ Logger.getLogger(ContainerBuilder.class.getName());
+
final List<BindingBuilder<?>> bindingBuilders =
new ArrayList<BindingBuilder<?>>();
final List<ConstantBindingBuilder> constantBindingBuilders =
new ArrayList<ConstantBindingBuilder>();
final List<LinkedBindingBuilder<?>> linkedBindingBuilders =
new ArrayList<LinkedBindingBuilder<?>>();
+ final Map<String, Scope> scopes = new HashMap<String, Scope>();
final List<Class<?>> staticInjections = new ArrayList<Class<?>>();
@@ -85,18 +89,28 @@
static final String UNKNOWN_SOURCE = "[unknown source]";
+ static final Scope DEFAULT_SCOPE = new Scope() {
+ public <T> Factory<T> scope(Key<T> key, Factory<T> creator) {
+ // We actually optimize around this.
+ throw new UnsupportedOperationException();
+ }
+ };
+
/**
* Constructs a new builder.
*/
public ContainerBuilder() {
+ put(Scopes.DEFAULT, DEFAULT_SCOPE);
+ put(Scopes.SINGLETON, SingletonScope.INSTANCE);
+
bind(Container.class).to(CONTAINER_FACTORY);
bind(Logger.class).to(LOGGER_FACTORY);
}
/**
- * Creates a source object to be associated with a binding. Called by
- * default for each binding. The default implementation returns {@code
- * ContainerBuilder}'s caller's {@code StackTraceElement}.
+ * Creates a source object to be associated with a binding. Useful for
+ * debugging. Called by default for each binding. The default implementation
+ * returns {@code ContainerBuilder}'s caller's {@code StackTraceElement}.
*
* <p>If you plan on manually setting the source (say for example you've
* implemented an XML configuration), you might override this method and
@@ -112,6 +126,19 @@
}
/**
+ * Maps a {@link Scope} instance to a given name. Scopes should be mapped
+ * before used in bindings. @{@link Scoped#value()} references this name.
+ */
+ public void put(String name, Scope scope) {
+ if (scopes.containsKey(nonNull(name, "name"))) {
+ add(new ErrorMessage(source(), "Scope named '" + name
+ + "' is already defined."));
+ } else {
+ scopes.put(nonNull(name, "name"), nonNull(scope, "scope"));
+ }
+ }
+
+ /**
* Binds the given key.
*/
public <T> BindingBuilder<T> bind(Key<T> key) {
@@ -166,7 +193,7 @@
}
/**
- * Binds string constants based on the given properties.
+ * Binds a string constant for each property.
*/
public ContainerBuilder bindProperties(Map<String, String> properties) {
ensureNotCreated();
@@ -180,7 +207,7 @@
}
/**
- * Binds string constants based on the given properties.
+ * Binds a string constant for each property.
*/
public ContainerBuilder bindProperties(Properties properties) {
ensureNotCreated();
@@ -227,6 +254,7 @@
HashMap<Key<?>, InternalFactory<?>> factories =
new HashMap<Key<?>, InternalFactory<?>>();
+ ContainerImpl container = new ContainerImpl(factories);
for (ConstantBindingBuilder builder : constantBindingBuilders) {
if (builder.hasValue()) {
@@ -242,7 +270,7 @@
for (BindingBuilder<?> builder : bindingBuilders) {
final Key<?> key = builder.getKey();
- final InternalFactory<?> factory = builder.getInternalFactory();
+ final InternalFactory<?> factory = builder.getInternalFactory(container);
factories.put(key, factory);
if (builder.isSingleton()) {
@@ -284,13 +312,12 @@
// TODO: Handle this better.
if (!errorMessages.isEmpty()) {
for (ErrorMessage errorMessage : errorMessages) {
- System.err.println(errorMessage);
- throw new DependencyException("Configuration errors.");
+ logger.severe(errorMessage.toString());
}
+ throw new ConfigurationException("We encountered configuration errors."
+ + " See the log for details.");
}
- final ContainerImpl container = new ContainerImpl(factories);
-
container.injectStatics(staticInjections);
if (loadSingletons) {
@@ -323,21 +350,9 @@
}
/**
- * Implemented by classes which participate in building a container. Useful
- * for encapsulating and reusing configuration logic.
- */
- public interface Command {
-
- /**
- * Configures the given builder.
- */
- void build(ContainerBuilder builder);
- }
-
- /**
* Binds a {@link Key} to an implementation in a given scope.
*/
- public class BindingBuilder<T> implements SourceAware<BindingBuilder<T>> {
+ public class BindingBuilder<T> {
Object source = ContainerBuilder.UNKNOWN_SOURCE;
Key<T> key;
@@ -352,7 +367,7 @@
return key;
}
- public BindingBuilder<T> from(Object source) {
+ BindingBuilder<T> from(Object source) {
this.source = source;
return this;
}
@@ -466,32 +481,79 @@
}
/**
- * Specifies the scope.
+ * Specifies the scope. References the name passed to {@link
+ * ContainerBuilder#put(String, Scope)}.
*/
- public BindingBuilder<T> in(Scope scope) {
- if (this.scope != null) {
- add(new ErrorMessage(source, "Scope set more than once."));
- }
+ public BindingBuilder<T> in(String scopeName) {
+ ensureScopeNotSet();
- this.scope = scope;
+ // We could defer this lookup to when we create the container, but this
+ // is fine for now.
+ this.scope = scopes.get(scopeName);
+ if (this.scope == null) {
+ add(new ErrorMessage(source, "Scope named '" + scopeName
+ + "' not found."));
+ }
return this;
}
- InternalFactory<? extends T> getInternalFactory() {
+ /**
+ * Specifies the scope.
+ */
+ public BindingBuilder<T> in(Scope scope) {
+ ensureScopeNotSet();
+
+ this.scope = nonNull(scope, "scope");
+ return this;
+ }
+
+ private void ensureScopeNotSet() {
+ if (this.scope != null) {
+ add(new ErrorMessage(source, "Scope set more than once."));
+ }
+ }
+
+ InternalFactory<? extends T> getInternalFactory(
+ final ContainerImpl container) {
// If an implementation wasn't specified, use the injection type.
if (this.factory == null) {
to(key.getTypeToken());
}
- if (scope == null) {
+ if (scope == null || scope == DEFAULT_SCOPE) {
return this.factory;
}
- return scope.scopeFactory(key, this.factory);
+ // TODO: This is a little hairy.
+ final InternalFactory<? extends T> internalFactory = this.factory;
+ final Factory<T> factory = scope.scope(this.key, new Factory<T>() {
+ public T get() {
+ return container.callInContext(
+ new ContainerImpl.ContextualCallable<T>() {
+ public T call(InternalContext context) {
+ return internalFactory.get(context);
+ }
+ });
+ }
+
+ public String toString() {
+ return internalFactory.toString();
+ }
+ });
+
+ return new InternalFactory<T>() {
+ public T get(InternalContext context) {
+ return factory.get();
+ }
+
+ public String toString() {
+ return factory.toString();
+ }
+ };
}
boolean isSingleton() {
- return this.scope == Scope.SINGLETON;
+ return this.scope == SingletonScope.INSTANCE;
}
/**
@@ -499,6 +561,7 @@
*/
private class DefaultFactory<I extends T> implements InternalFactory<I> {
+ ContainerImpl container;
volatile ContainerImpl.ConstructorInjector<I> constructor;
private final TypeToken<I> implementation;
@@ -526,8 +589,7 @@
/**
* Builds a constant binding.
*/
- public class ConstantBindingBuilder
- implements SourceAware<ConstantBindingBuilder> {
+ public class ConstantBindingBuilder {
final String name;
Class<?> type;
@@ -554,7 +616,7 @@
return new ConstantFactory<Object>(value);
}
- public ConstantBindingBuilder from(Object source) {
+ ConstantBindingBuilder from(Object source) {
this.source = source;
return this;
}
@@ -642,8 +704,7 @@
/**
* Links one binding to another.
*/
- public class LinkedBindingBuilder<T>
- implements SourceAware<LinkedBindingBuilder<T>> {
+ public class LinkedBindingBuilder<T> {
Key<T> key;
Key<? extends T> destination;
@@ -665,7 +726,7 @@
return destination;
}
- public LinkedBindingBuilder<T> from(Object source) {
+ LinkedBindingBuilder<T> from(Object source) {
this.source = source;
return this;
}
@@ -678,4 +739,9 @@
return this;
}
}
+
+ interface ContainerAwareFactory<T> extends Factory<T> {
+
+ void setContainer(ContainerImpl container);
+ }
}
diff --git a/src/com/google/inject/ContainerImpl.java b/src/com/google/inject/ContainerImpl.java
index 68a6228..b8a6164 100644
--- a/src/com/google/inject/ContainerImpl.java
+++ b/src/com/google/inject/ContainerImpl.java
@@ -29,6 +29,7 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
+import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -62,6 +63,25 @@
return internalFactory;
}
+ // Handle cases where T is a Factory<?>.
+ if (key.getTypeToken().getRawType().equals(Factory.class)) {
+ Type factoryType = key.getTypeToken().getType();
+ if (!(factoryType instanceof ParameterizedType)) {
+ return null;
+ }
+ Type entryType =
+ ((ParameterizedType) factoryType).getActualTypeArguments()[0];
+
+ // This can throw ConfigurationException.
+ final Factory<?> factory = getFactory(Key.get(entryType, key.getName()));
+ return new InternalFactory<T>() {
+ @SuppressWarnings({"unchecked"})
+ public T get(InternalContext context) {
+ return (T) factory;
+ }
+ };
+ }
+
// Do we have a constant String factory of the same name?
InternalFactory<String> stringFactory =
(InternalFactory<String>) factories.get(
@@ -192,7 +212,7 @@
injectors.add(injectorFactory.create(this, member, inject.value()));
} catch (MissingDependencyException e) {
if (inject.required()) {
- throw new DependencyException(e);
+ throw new ConfigurationException(e);
}
}
}
@@ -224,7 +244,8 @@
factory = container.getFactory(field, key);
if (factory == null) {
throw new MissingDependencyException(
- "No mapping found for dependency " + key + " in " + field + ".");
+ "No mapping found for dependency " + key + " for field: "
+ + field);
}
this.externalContext = ExternalContext.newInstance(field, key, container);
@@ -275,7 +296,8 @@
InternalFactory<? extends T> factory = getFactory(member, key);
if (factory == null) {
throw new MissingDependencyException(
- "No mapping found for dependency " + key + " in " + member + ".");
+ "No mapping found for dependency " + key + " for member: "
+ + member + "");
}
ExternalContext<T> externalContext =
@@ -314,7 +336,7 @@
Type[] parameterTypes = method.getGenericParameterTypes();
if (parameterTypes.length == 0) {
- throw new DependencyException(
+ throw new ConfigurationException(
method + " has no parameters to inject.");
}
parameterInjectors = container.getParametersInjectors(
@@ -362,7 +384,7 @@
inject.value()
);
} catch (MissingDependencyException e) {
- throw new DependencyException(e);
+ throw new ConfigurationException(e);
}
injectors = container.injectors.get(implementation);
}
@@ -374,7 +396,7 @@
: implementation.getDeclaredConstructors()) {
if (constructor.getAnnotation(Inject.class) != null) {
if (found != null) {
- throw new DependencyException("More than one constructor annotated"
+ throw new ConfigurationException("More than one constructor annotated"
+ " with @Inject found in " + implementation + ".");
}
found = constructor;
@@ -389,7 +411,7 @@
try {
return implementation.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
- throw new DependencyException("Could not find a suitable constructor"
+ throw new ConfigurationException("Could not find a suitable constructor"
+ " in " + implementation.getName() + ".");
}
}
@@ -485,14 +507,14 @@
return parameters;
}
- void inject(Object o, InternalContext context) {
+ void injectMembers(Object o, InternalContext context) {
List<Injector> injectors = this.injectors.get(o.getClass());
for (Injector injector : injectors) {
injector.inject(context, o);
}
}
- <T> T inject(Class<T> implementation, InternalContext context) {
+ <T> T newInstance(Class<T> implementation, InternalContext context) {
try {
ConstructorInjector<T> constructor = getConstructor(implementation);
return implementation.cast(
@@ -504,26 +526,6 @@
}
}
- @SuppressWarnings("unchecked")
- <T> T getInstance(Class<T> type, String name, InternalContext context) {
- ExternalContext<?> previous = context.getExternalContext();
- Key<T> key = Key.get(type, name);
- context.setExternalContext(ExternalContext.newInstance(null, key, this));
- try {
- InternalFactory<? extends T> factory = getFactory(null, key);
- if (factory == null) {
- throw new DependencyException("Missing binding for " + key + ".");
- }
- return factory.get(context);
- } finally {
- context.setExternalContext(previous);
- }
- }
-
- <T> T getInstance(Class<T> type, InternalContext context) {
- return getInstance(type, Key.DEFAULT_NAME, context);
- }
-
public boolean hasBindingFor(Key<?> key) {
try {
return getFactory(null, key) != null;
@@ -532,37 +534,46 @@
}
}
- public void inject(final Object o) {
+ public void injectMembers(final Object o) {
callInContext(new ContextualCallable<Void>() {
public Void call(InternalContext context) {
- inject(o, context);
+ injectMembers(o, context);
return null;
}
});
}
- public <T> T inject(final Class<T> implementation) {
+ public <T> T newInstance(final Class<T> implementation) {
return callInContext(new ContextualCallable<T>() {
public T call(InternalContext context) {
- return inject(implementation, context);
+ return newInstance(implementation, context);
}
});
}
- public <T> T getInstance(final Class<T> type, final String name) {
- return callInContext(new ContextualCallable<T>() {
- public T call(InternalContext context) {
- return getInstance(type, name, context);
- }
- });
- }
+ public <T> Factory<T> getFactory(final Key<T> key) {
+ final InternalFactory<? extends T> factory = getFactory(null, key);
- public <T> T getInstance(final Class<T> type) {
- return callInContext(new ContextualCallable<T>() {
- public T call(InternalContext context) {
- return getInstance(type, context);
+ if (factory == null) {
+ throw new ConfigurationException("Missing binding for " + key + ".");
+ }
+
+ return new Factory<T>() {
+ public T get() {
+ return callInContext(new ContextualCallable<T>() {
+ public T call(InternalContext context) {
+ ExternalContext<?> previous = context.getExternalContext();
+ context.setExternalContext(
+ ExternalContext.newInstance(null, key, ContainerImpl.this));
+ try {
+ return factory.get(context);
+ } finally {
+ context.setExternalContext(previous);
+ }
+ }
+ });
}
- });
+ };
}
ThreadLocal<InternalContext[]> localContext =
@@ -579,7 +590,7 @@
<T> T callInContext(ContextualCallable<T> callable) {
InternalContext[] reference = localContext.get();
if (reference[0] == null) {
- reference[0] = new InternalContext(this, localScopeStrategy.get());
+ reference[0] = new InternalContext(this);
try {
return callable.call(reference[0]);
} finally {
@@ -609,17 +620,6 @@
return constructors.get(implementation.getRawType());
}
- final ThreadLocal<Scope.Strategy> localScopeStrategy =
- new ThreadLocal<Scope.Strategy>();
-
- public void setScopeStrategy(Scope.Strategy scopeStrategy) {
- this.localScopeStrategy.set(scopeStrategy);
- }
-
- public void removeScopeStrategy() {
- this.localScopeStrategy.remove();
- }
-
/**
* Injects a field or method in a given object.
*/
diff --git a/src/com/google/inject/Context.java b/src/com/google/inject/Context.java
index 866cc31..afab26d 100644
--- a/src/com/google/inject/Context.java
+++ b/src/com/google/inject/Context.java
@@ -31,27 +31,14 @@
Container getContainer();
/**
- * Gets the current scope strategy. See {@link
- * Container#setScopeStrategy(Scope.Strategy)}.
- *
- * @throws IllegalStateException if no strategy has been set
- */
- Scope.Strategy getScopeStrategy();
-
- /**
* Gets the field, method or constructor which is being injected. Returns
- * {@code null} if the object currently being constructed is pre-loaded as
- * a singleton or requested from {@link Container#getInstance(Class)}.
+ * {@code null} if the object isn't being directly injected (i.e. it's
+ * a preloaded singleton, returned from {@link Factory#get()}, etc.
*/
Member getMember();
/**
- * Gets the type of the field or parameter which is being injected.
+ * Gets the binding key for the object currently being retrieved.
*/
- Class<?> getType();
-
- /**
- * Gets the name of the injection specified by {@link @Inject#name()}.
- */
- String getName();
+ Key<?> getKey();
}
diff --git a/src/com/google/inject/ExternalContext.java b/src/com/google/inject/ExternalContext.java
index c6a5e71..4e2c815 100644
--- a/src/com/google/inject/ExternalContext.java
+++ b/src/com/google/inject/ExternalContext.java
@@ -37,12 +37,8 @@
this.container = container;
}
- public Class<T> getType() {
- return key.getRawType();
- }
-
- public Scope.Strategy getScopeStrategy() {
- return container.localScopeStrategy.get();
+ public Key<?> getKey() {
+ return this.key;
}
public Container getContainer() {
@@ -53,15 +49,10 @@
return member;
}
- public String getName() {
- return key.getName();
- }
-
public String toString() {
return "Context" + new LinkedHashMap<String, Object>() {{
put("member", member);
- put("type", getType());
- put("name", getName());
+ put("key", getKey());
put("container", container);
}}.toString();
}
diff --git a/src/com/google/inject/InternalContext.java b/src/com/google/inject/InternalContext.java
index 322e065..d79b5d6 100644
--- a/src/com/google/inject/InternalContext.java
+++ b/src/com/google/inject/InternalContext.java
@@ -30,12 +30,10 @@
final ContainerImpl container;
final Map<Object, ConstructionContext<?>> constructionContexts =
new HashMap<Object, ConstructionContext<?>>();
- final Scope.Strategy scopeStrategy;
ExternalContext<?> externalContext;
- InternalContext(ContainerImpl container, Scope.Strategy scopeStrategy) {
+ InternalContext(ContainerImpl container) {
this.container = container;
- this.scopeStrategy = scopeStrategy;
}
public Container getContainer() {
@@ -46,15 +44,6 @@
return container;
}
- Scope.Strategy getScopeStrategy() {
- if (scopeStrategy == null) {
- throw new IllegalStateException("Scope strategy not set. "
- + "Please call Container.setScopeStrategy().");
- }
-
- return scopeStrategy;
- }
-
@SuppressWarnings("unchecked")
<T> ConstructionContext<T> getConstructionContext(Object key) {
ConstructionContext<T> constructionContext =
diff --git a/src/com/google/inject/Key.java b/src/com/google/inject/Key.java
index 32ffb43..2f9217f 100644
--- a/src/com/google/inject/Key.java
+++ b/src/com/google/inject/Key.java
@@ -26,12 +26,12 @@
*
* <p>For example, <tt>new Key<List<String>>("cities") {}</tt> will match:
*
- * <tt>
- * @Inject("cities")
+ * <pre>
+ * @Inject("cities")
* public void setList(List<String> cities) {
* ...
* }
- * </tt>
+ * </pre>
*
* @author crazybob@google.com (Bob Lee)
*/
diff --git a/src/com/google/inject/SourceAware.java b/src/com/google/inject/Module.java
similarity index 66%
rename from src/com/google/inject/SourceAware.java
rename to src/com/google/inject/Module.java
index d97572c..814e291 100644
--- a/src/com/google/inject/SourceAware.java
+++ b/src/com/google/inject/Module.java
@@ -17,16 +17,13 @@
package com.google.inject;
/**
- * Implemented by classes which can remember their source.
- *
- * @author crazybob@google.com (Bob Lee)
+ * Implemented by classes which participate in building a container. Useful
+ * for encapsulating and reusing configuration logic.
*/
-interface SourceAware<R> {
+public interface Module {
/**
- * Sets the source object. Useful for debugging. Contents may include the
- * name of the file and the line number this binding came from, a code
- * snippet, etc.
+ * Configures the given builder.
*/
- R from(Object source);
+ void configure(ContainerBuilder builder);
}
diff --git a/src/com/google/inject/Scope.java b/src/com/google/inject/Scope.java
index 0db8887..c912189 100644
--- a/src/com/google/inject/Scope.java
+++ b/src/com/google/inject/Scope.java
@@ -1,206 +1,25 @@
-/**
- * Copyright (C) 2006 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.
- */
+// Copyright 2006 Google Inc. All Rights Reserved.
package com.google.inject;
-import java.util.concurrent.Callable;
-
/**
- * Scope of an injected objects.
+ * A scope which bound objects can reside in. Add a new scope using {@link
+ * com.google.inject.ContainerBuilder#put(String, Scope)} and reference it from
+ * bindings using its name.
*
- * @author crazybob
+ * @author crazybob@google.com (Bob Lee)
*/
-public enum Scope {
+public interface Scope {
/**
- * One instance per injection.
- */
- DEFAULT {
- <T> InternalFactory<? extends T> scopeFactory(Key<T> key,
- InternalFactory<? extends T> factory) {
- return factory;
- }
- },
-
- /**
- * One instance per container.
- */
- SINGLETON {
- <T> InternalFactory<? extends T> scopeFactory(Key<T> key,
- final InternalFactory<? extends T> factory) {
- return new InternalFactory<T>() {
- T instance;
- public T get(InternalContext context) {
- synchronized (context.getContainer()) {
- if (instance == null) {
- instance = factory.get(context);
- }
- return instance;
- }
- }
-
- public String toString() {
- return factory.toString();
- }
- };
- }
- },
-
- /**
- * One instance per thread.
+ * Scopes a factory. The returned factory returns objects from this scope.
+ * If an object does not exist in this scope, the factory can use the given
+ * creator to create one.
*
- * <p><b>Note:</b> if a thread local object strongly references its {@link
- * Container}, neither the {@code Container} nor the object will be
- * eligible for garbage collection, i.e. memory leak.
+ * @param key binding key
+ * @param creator creates new instances as needed
+ * @return a new factory which only delegates to the given factory when an
+ * instance of the requested object doesn't already exist in the scope
*/
- THREAD {
- <T> InternalFactory<? extends T> scopeFactory(Key<T> key,
- final InternalFactory<? extends T> factory) {
- return new InternalFactory<T>() {
- final ThreadLocal<T> threadLocal = new ThreadLocal<T>();
- public T get(final InternalContext context) {
- T t = threadLocal.get();
- if (t == null) {
- t = factory.get(context);
- threadLocal.set(t);
- }
- return t;
- }
-
- public String toString() {
- return factory.toString();
- }
- };
- }
- },
-
- /**
- * One instance per request.
- */
- REQUEST {
- <T> InternalFactory<? extends T> scopeFactory(final Key<T> key,
- final InternalFactory<? extends T> factory) {
- return new InternalFactory<T>() {
- public T get(InternalContext context) {
- Strategy strategy = context.getScopeStrategy();
- try {
- return strategy.findInRequest(
- key, toCallable(context, factory));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public String toString() {
- return factory.toString();
- }
- };
- }
- },
-
- /**
- * One instance per session.
- */
- SESSION {
- <T> InternalFactory<? extends T> scopeFactory(final Key<T> key,
- final InternalFactory<? extends T> factory) {
- return new InternalFactory<T>() {
- public T get(InternalContext context) {
- Strategy strategy = context.getScopeStrategy();
- try {
- return strategy.findInSession(
- key, toCallable(context, factory));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public String toString() {
- return factory.toString();
- }
- };
- }
- },
-
- /**
- * One instance per wizard.
- */
- WIZARD {
- <T> InternalFactory<? extends T> scopeFactory(final Key<T> key,
- final InternalFactory<? extends T> factory) {
- return new InternalFactory<T>() {
- public T get(InternalContext context) {
- Strategy strategy = context.getScopeStrategy();
- try {
- return strategy.findInWizard(
- key, toCallable(context, factory));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public String toString() {
- return factory.toString();
- }
- };
- }
- };
-
- <T> Callable<? extends T> toCallable(final InternalContext context,
- final InternalFactory<? extends T> factory) {
- return new Callable<T>() {
- public T call() throws Exception {
- return factory.get(context);
- }
- };
- }
-
- /**
- * Wraps factory with scoping logic.
- */
- abstract <T> InternalFactory<? extends T> scopeFactory(
- Key<T> key, InternalFactory<? extends T> factory);
-
- /**
- * Pluggable scoping strategy. Enables users to provide custom
- * implementations of request, session, and wizard scopes. Implement and
- * pass to {@link
- * Container#setScopeStrategy(com.google.inject.Scope.Strategy)}.
- */
- public interface Strategy {
-
- /**
- * Finds an object for the given type and name in the request scope.
- * Creates a new object if necessary using the given factory.
- */
- <T> T findInRequest(Key<T> key,
- Callable<? extends T> factory) throws Exception;
-
- /**
- * Finds an object for the given type and name in the session scope.
- * Creates a new object if necessary using the given factory.
- */
- <T> T findInSession(Key<T> key,
- Callable<? extends T> factory) throws Exception;
-
- /**
- * Finds an object for the given type and name in the wizard scope.
- * Creates a new object if necessary using the given factory.
- */
- <T> T findInWizard(Key<T> key,
- Callable<? extends T> factory) throws Exception;
- }
+ public <T> Factory<T> scope(Key<T> key, Factory<T> creator);
}
diff --git a/src/com/google/inject/Scoped.java b/src/com/google/inject/Scoped.java
index cf14329..acc7be4 100644
--- a/src/com/google/inject/Scoped.java
+++ b/src/com/google/inject/Scoped.java
@@ -22,8 +22,9 @@
import java.lang.annotation.Target;
/**
- * Annotates a scoped implementation class.
+ * Annotates an implementation class with the name of its scope.
*
+ * @see com.google.inject.ContainerBuilder#put(String, Scope)
* @author crazybob
*/
@Target(ElementType.TYPE)
@@ -31,7 +32,7 @@
public @interface Scoped {
/**
- * Scope.
+ * Scope name.
*/
- Scope value();
+ String value();
}
diff --git a/src/com/google/inject/Scopes.java b/src/com/google/inject/Scopes.java
new file mode 100644
index 0000000..51e99b5
--- /dev/null
+++ b/src/com/google/inject/Scopes.java
@@ -0,0 +1,21 @@
+// Copyright 2006 Google Inc. All Rights Reserved.
+
+package com.google.inject;
+
+/**
+ * Scope constants.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class Scopes {
+
+ /**
+ * Default scope's name. One instance per injection.
+ */
+ public static final String DEFAULT = Key.DEFAULT_NAME;
+
+ /**
+ * Singleton scope's name. One instance per {@link Container}.
+ */
+ public static final String SINGLETON = "singleton";
+}
\ No newline at end of file
diff --git a/src/com/google/inject/SingletonScope.java b/src/com/google/inject/SingletonScope.java
new file mode 100644
index 0000000..bb327bd
--- /dev/null
+++ b/src/com/google/inject/SingletonScope.java
@@ -0,0 +1,39 @@
+// Copyright 2006 Google Inc. All Rights Reserved.
+
+package com.google.inject;
+
+/**
+ * Singleton scope. Returns one instance per {@link Container}.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+class SingletonScope implements Scope {
+
+ static final Scope INSTANCE = new SingletonScope();
+
+ private SingletonScope() {}
+
+ public <T> Factory<T> scope(Key<T> key, final Factory<T> creator) {
+ return new Factory<T>() {
+
+ T instance;
+
+ public T get() {
+ // Use a pretty coarse lock. We don't want to run into deadlocks when
+ // two threads try to load circularly-dependent singletons.
+ // Maybe one of these days we will identify independent graphs of
+ // singletons and offer to load them in parallel.
+ synchronized (Container.class) {
+ if (instance == null) {
+ instance = creator.get();
+ }
+ return instance;
+ }
+ }
+
+ public String toString() {
+ return creator.toString();
+ }
+ };
+ }
+}
diff --git a/src/com/google/inject/TypeToken.java b/src/com/google/inject/TypeToken.java
index ef2ec1e..05bac06 100644
--- a/src/com/google/inject/TypeToken.java
+++ b/src/com/google/inject/TypeToken.java
@@ -22,7 +22,8 @@
import java.lang.reflect.ParameterizedType;
/**
- * Represents a generic type {@code T}.
+ * Represents a generic type {@code T}. Due to erasure, Java doesn't provide
+ * a way to represent generic types. This class enables that.
*
* <p>Assumes {@code Type} implements {@code equals()} and {@code hashCode()}
* as a value (as opposed to identity) comparison.