Initial checkin.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@2 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/ConstructionContext.java b/src/com/google/inject/ConstructionContext.java
new file mode 100644
index 0000000..2da79e1
--- /dev/null
+++ b/src/com/google/inject/ConstructionContext.java
@@ -0,0 +1,124 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Context of a dependency construction. Used to manage circular references.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+class ConstructionContext<T> {
+
+ T currentReference;
+ boolean constructing;
+
+ List<DelegatingInvocationHandler<T>> invocationHandlers;
+
+ T getCurrentReference() {
+ return currentReference;
+ }
+
+ void removeCurrentReference() {
+ this.currentReference = null;
+ }
+
+ void setCurrentReference(T currentReference) {
+ this.currentReference = currentReference;
+ }
+
+ boolean isConstructing() {
+ return constructing;
+ }
+
+ void startConstruction() {
+ this.constructing = true;
+ }
+
+ void finishConstruction() {
+ this.constructing = false;
+ invocationHandlers = null;
+ }
+
+ Object createProxy(Class<? super T> expectedType) {
+ // TODO: if I create a proxy which implements all the interfaces of
+ // the implementation type, I'll be able to get away with one proxy
+ // instance (as opposed to one per caller).
+
+ if (!expectedType.isInterface()) {
+ throw new DependencyException(
+ expectedType.getName() + " is not an interface.");
+ }
+
+ if (invocationHandlers == null) {
+ invocationHandlers = new ArrayList<DelegatingInvocationHandler<T>>();
+ }
+
+ DelegatingInvocationHandler<T> invocationHandler =
+ new DelegatingInvocationHandler<T>();
+ invocationHandlers.add(invocationHandler);
+
+ return Proxy.newProxyInstance(
+ expectedType.getClassLoader(),
+ new Class[] { expectedType },
+ invocationHandler
+ );
+ }
+
+ void setProxyDelegates(T delegate) {
+ if (invocationHandlers != null) {
+ for (DelegatingInvocationHandler<T> invocationHandler
+ : invocationHandlers) {
+ invocationHandler.setDelegate(delegate);
+ }
+ }
+ }
+
+ static class DelegatingInvocationHandler<T> implements InvocationHandler {
+
+ T delegate;
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ if (delegate == null) {
+ throw new IllegalStateException(
+ "Not finished constructing. Please don't call methods on this"
+ + " object until the caller's construction is complete.");
+ }
+
+ try {
+ return method.invoke(delegate, args);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+
+ void setDelegate(T delegate) {
+ this.delegate = delegate;
+ }
+ }
+}
diff --git a/src/com/google/inject/Container.java b/src/com/google/inject/Container.java
new file mode 100644
index 0000000..8b87ef9
--- /dev/null
+++ b/src/com/google/inject/Container.java
@@ -0,0 +1,103 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+/**
+ * Injects dependencies into constructors, methods and fields annotated with
+ * {@link Inject}. Immutable.
+ *
+ * <p>When injecting a method or constructor, you can additionally annotate
+ * its parameters with {@link Inject} and specify a dependency name. When a
+ * parameter has no annotation, the container uses the name from the method or
+ * constructor's {@link Inject} annotation respectively.
+ *
+ * <p>For example:
+ *
+ * <pre>
+ * class Foo {
+ *
+ * // Inject the int constant named "i".
+ * @Inject("i") int i;
+ *
+ * // Inject the default implementation of Bar and the String constant
+ * // named "s".
+ * @Inject Foo(Bar bar, @Inject("s") String s) {
+ * ...
+ * }
+ *
+ * // Inject the default implementation of Baz and the Bob implementation
+ * // named "foo".
+ * @Inject void initialize(Baz baz, @Inject("foo") Bob bob) {
+ * ...
+ * }
+ *
+ * // Inject the default implementation of Tee.
+ * @Inject void setTee(Tee tee) {
+ * ...
+ * }
+ * }
+ * </pre>
+ *
+ * <p>To create and inject an instance of {@code Foo}:
+ *
+ * <pre>
+ * Container c = ...;
+ * Foo foo = c.inject(Foo.class);
+ * </pre>
+ *
+ * @see ContainerBuilder
+ * @author crazybob@google.com (Bob Lee)
+ */
+public interface Container {
+
+ /**
+ * Default dependency name.
+ */
+ String DEFAULT_NAME = "default";
+
+ /**
+ * Injects dependencies into the fields and methods of an existing object.
+ */
+ void inject(Object o);
+
+ /**
+ * Creates and injects a new instance of type {@code implementation}.
+ */
+ <T> T inject(Class<T> implementation);
+
+ /**
+ * Gets an instance of the given dependency which was declared in
+ * {@link com.google.inject.ContainerBuilder}.
+ */
+ <T> T getInstance(Class<T> type, String name);
+
+ /**
+ * Convenience method. Equivalent to {@code getInstance(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();
+}
diff --git a/src/com/google/inject/ContainerBuilder.java b/src/com/google/inject/ContainerBuilder.java
new file mode 100644
index 0000000..c47e88b
--- /dev/null
+++ b/src/com/google/inject/ContainerBuilder.java
@@ -0,0 +1,483 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import java.lang.reflect.Member;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+/**
+ * Builds a dependency injection {@link Container}. The combination of
+ * dependency type and name uniquely identifies a dependency mapping; you can
+ * use the same name for two different types. Not safe for concurrent use.
+ *
+ * <p>Adds the following factories by default:
+ *
+ * <ul>
+ * <li>Injects the current {@link Container}.
+ * <li>Injects the {@link Logger} for the injected member's declaring class.
+ * </ul>
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public final class ContainerBuilder {
+
+ final Map<Key<?>, InternalFactory<?>> factories =
+ new HashMap<Key<?>, InternalFactory<?>>();
+ final List<InternalFactory<?>> singletonFactories =
+ new ArrayList<InternalFactory<?>>();
+ final List<Class<?>> staticInjections = new ArrayList<Class<?>>();
+ boolean created;
+
+ private static final InternalFactory<Container> CONTAINER_FACTORY =
+ new InternalFactory<Container>() {
+ public Container create(InternalContext context) {
+ return context.getContainer();
+ }
+ };
+
+ private static final InternalFactory<Logger> LOGGER_FACTORY =
+ new InternalFactory<Logger>() {
+ public Logger create(InternalContext context) {
+ Member member = context.getExternalContext().getMember();
+ return member == null ? Logger.getAnonymousLogger()
+ : Logger.getLogger(member.getDeclaringClass().getName());
+ }
+ };
+
+ /**
+ * Constructs a new builder.
+ */
+ public ContainerBuilder() {
+ // In the current container as the default Container implementation.
+ factories.put(Key.newInstance(Container.class, Container.DEFAULT_NAME),
+ CONTAINER_FACTORY);
+
+ // Inject the logger for the injected member's declaring class.
+ factories.put(Key.newInstance(Logger.class, Container.DEFAULT_NAME),
+ LOGGER_FACTORY);
+ }
+
+ /**
+ * Maps a dependency. All methods in this class ultimately funnel through
+ * here.
+ */
+ private <T> ContainerBuilder factory(final Key<T> key,
+ InternalFactory<? extends T> factory, Scope scope) {
+ ensureNotCreated();
+ checkKey(key);
+ final InternalFactory<? extends T> scopedFactory =
+ scope.scopeFactory(key.getType(), key.getName(), factory);
+ factories.put(key, scopedFactory);
+ if (scope == Scope.SINGLETON) {
+ singletonFactories.add(new InternalFactory<T>() {
+ public T create(InternalContext context) {
+ try {
+ context.setExternalContext(ExternalContext.newInstance(
+ null, key, context.getContainerImpl()));
+ return scopedFactory.create(context);
+ } finally {
+ context.setExternalContext(null);
+ }
+ }
+ });
+ }
+ return this;
+ }
+
+ /**
+ * Ensures a key isn't already mapped.
+ */
+ private void checkKey(Key<?> key) {
+ if (factories.containsKey(key)) {
+ throw new DependencyException(
+ "Dependency mapping for " + key + " already exists.");
+ }
+ }
+
+ /**
+ * Maps a factory to a given dependency type and name.
+ *
+ * @param type of dependency
+ * @param name of dependency
+ * @param factory creates objects to inject
+ * @param scope scope of injected instances
+ * @return this builder
+ */
+ public <T> ContainerBuilder factory(final Class<T> type, final String name,
+ final Factory<? extends T> factory, Scope scope) {
+ InternalFactory<T> internalFactory =
+ new InternalFactory<T>() {
+
+ public T create(InternalContext context) {
+ try {
+ Context externalContext = context.getExternalContext();
+ return factory.create(externalContext);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String toString() {
+ return new LinkedHashMap<String, Object>() {{
+ put("type", type);
+ put("name", name);
+ put("factory", factory);
+ }}.toString();
+ }
+ };
+
+ return factory(Key.newInstance(type, name), internalFactory, scope);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type,
+ * Container.DEFAULT_NAME, factory, scope)}.
+ *
+ * @see #factory(Class, String, Factory, Scope)
+ */
+ public <T> ContainerBuilder factory(Class<T> type,
+ Factory<? extends T> factory, Scope scope) {
+ return factory(type, Container.DEFAULT_NAME, factory, scope);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type, name, factory,
+ * Scope.DEFAULT)}.
+ *
+ * @see #factory(Class, String, Factory, Scope)
+ */
+ public <T> ContainerBuilder factory(Class<T> type, String name,
+ Factory<? extends T> factory) {
+ return factory(type, name, factory, Scope.DEFAULT);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type,
+ * Container.DEFAULT_NAME, factory, Scope.DEFAULT)}.
+ *
+ * @see #factory(Class, String, Factory, Scope)
+ */
+ public <T> ContainerBuilder factory(Class<T> type,
+ Factory<? extends T> factory) {
+ return factory(type, Container.DEFAULT_NAME, factory, Scope.DEFAULT);
+ }
+
+ /**
+ * Maps an implementation class to a given dependency type and name. Creates
+ * instances using the container, recursively injecting dependencies.
+ *
+ * @param type of dependency
+ * @param name of dependency
+ * @param implementation class
+ * @param scope scope of injected instances
+ * @return this builder
+ */
+ public <T> ContainerBuilder factory(final Class<T> type, final String name,
+ final Class<? extends T> implementation, final Scope scope) {
+ // This factory creates new instances of the given implementation.
+ // We have to lazy load the constructor because the Container
+ // hasn't been created yet.
+ InternalFactory<? extends T> factory = new InternalFactory<T>() {
+
+ volatile ContainerImpl.ConstructorInjector<? extends T> constructor;
+
+ @SuppressWarnings("unchecked")
+ public T create(InternalContext context) {
+ if (constructor == null) {
+ this.constructor =
+ context.getContainerImpl().getConstructor(implementation);
+ }
+ return (T) constructor.construct(context, type);
+ }
+
+ public String toString() {
+ return new LinkedHashMap<String, Object>() {{
+ put("type", type);
+ put("name", name);
+ put("implementation", implementation);
+ put("scope", scope);
+ }}.toString();
+ }
+ };
+
+ return factory(Key.newInstance(type, name), factory, scope);
+ }
+
+ /**
+ * Maps an implementation class to a given dependency type and name. Creates
+ * instances using the container, recursively injecting dependencies.
+ *
+ * <p>Sets scope to value from {@link Scoped} annotation on the
+ * implementation class. Defaults to {@link Scope#DEFAULT} if no annotation
+ * is found.
+ *
+ * @param type of dependency
+ * @param name of dependency
+ * @param implementation class
+ * @return this builder
+ */
+ public <T> ContainerBuilder factory(final Class<T> type, String name,
+ final Class<? extends T> implementation) {
+ Scoped scoped = implementation.getAnnotation(Scoped.class);
+ Scope scope = scoped == null ? Scope.DEFAULT : scoped.value();
+ return factory(type, name, implementation, scope);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type,
+ * Container.DEFAULT_NAME, implementation)}.
+ *
+ * @see #factory(Class, String, Class)
+ */
+ public <T> ContainerBuilder factory(Class<T> type,
+ Class<? extends T> implementation) {
+ return factory(type, Container.DEFAULT_NAME, implementation);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type,
+ * Container.DEFAULT_NAME, type)}.
+ *
+ * @see #factory(Class, String, Class)
+ */
+ public <T> ContainerBuilder factory(Class<T> type) {
+ return factory(type, Container.DEFAULT_NAME, type);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type, name, type)}.
+ *
+ * @see #factory(Class, String, Class)
+ */
+ public <T> ContainerBuilder factory(Class<T> type, String name) {
+ return factory(type, name, type);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type,
+ * Container.DEFAULT_NAME, implementation, scope)}.
+ *
+ * @see #factory(Class, String, Class, Scope)
+ */
+ public <T> ContainerBuilder factory(Class<T> type,
+ Class<? extends T> implementation, Scope scope) {
+ return factory(type, Container.DEFAULT_NAME, implementation, scope);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type,
+ * Container.DEFAULT_NAME, type, scope)}.
+ *
+ * @see #factory(Class, String, Class, Scope)
+ */
+ public <T> ContainerBuilder factory(Class<T> type, Scope scope) {
+ return factory(type, Container.DEFAULT_NAME, type, scope);
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code factory(type, name, type,
+ * scope)}.
+ *
+ * @see #factory(Class, String, Class, Scope)
+ */
+ public <T> ContainerBuilder factory(Class<T> type, String name, Scope scope) {
+ return factory(type, name, type, scope);
+ }
+
+ /**
+ * Maps a constant value to the given name.
+ */
+ public ContainerBuilder constant(String name, String value) {
+ return constant(String.class, name, value);
+ }
+
+ /**
+ * Maps a constant value to the given name.
+ */
+ public ContainerBuilder constant(String name, int value) {
+ return constant(int.class, name, value);
+ }
+
+ /**
+ * Maps a constant value to the given name.
+ */
+ public ContainerBuilder constant(String name, long value) {
+ return constant(long.class, name, value);
+ }
+
+ /**
+ * Maps a constant value to the given name.
+ */
+ public ContainerBuilder constant(String name, boolean value) {
+ return constant(boolean.class, name, value);
+ }
+
+ /**
+ * Maps a constant value to the given name.
+ */
+ public ContainerBuilder constant(String name, double value) {
+ return constant(double.class, name, value);
+ }
+
+ /**
+ * Maps a constant value to the given name.
+ */
+ public ContainerBuilder constant(String name, float value) {
+ return constant(float.class, name, value);
+ }
+
+ /**
+ * Maps a constant value to the given name.
+ */
+ public ContainerBuilder constant(String name, short value) {
+ return constant(short.class, name, value);
+ }
+
+ /**
+ * Maps a constant value to the given name.
+ */
+ public ContainerBuilder constant(String name, char value) {
+ return constant(char.class, name, value);
+ }
+
+ /**
+ * Maps a class to the given name.
+ */
+ public ContainerBuilder constant(String name, Class value) {
+ return constant(Class.class, name, value);
+ }
+
+ /**
+ * Maps an enum to the given name.
+ */
+ public <E extends Enum<E>> ContainerBuilder constant(String name, E value) {
+ return constant(value.getDeclaringClass(), name, value);
+ }
+
+ /**
+ * Maps a constant value to the given type and name.
+ */
+ private <T> ContainerBuilder constant(final Class<T> type, final String name,
+ final T value) {
+ InternalFactory<T> factory = new InternalFactory<T>() {
+ public T create(InternalContext ignored) {
+ return value;
+ }
+
+ public String toString() {
+ return new LinkedHashMap<String, Object>() {
+ {
+ put("type", type);
+ put("name", name);
+ put("value", value);
+ }
+ }.toString();
+ }
+ };
+
+ return factory(Key.newInstance(type, name), factory, Scope.DEFAULT);
+ }
+
+ /**
+ * Upon creation, the {@link Container} will inject static fields and methods
+ * into the given classes.
+ *
+ * @param types for which static members will be injected
+ */
+ public ContainerBuilder injectStatics(Class<?>... types) {
+ staticInjections.addAll(Arrays.asList(types));
+ return this;
+ }
+
+ /**
+ * Returns true if this builder contains a mapping for the given type and
+ * name.
+ */
+ public boolean contains(Class<?> type, String name) {
+ return factories.containsKey(Key.newInstance(type, name));
+ }
+
+ /**
+ * Convenience method. Equivalent to {@code contains(type,
+ * Container.DEFAULT_NAME)}.
+ */
+ public boolean contains(Class<?> type) {
+ return contains(type, Container.DEFAULT_NAME);
+ }
+
+ /**
+ * Creates a {@link Container} instance. Injects static members for classes
+ * which were registered using {@link #injectStatics(Class...)}.
+ *
+ * @param loadSingletons If true, the container will load all singletons
+ * now. If false, the container will lazily load singletons. Eager loading
+ * is appropriate for production use while lazy loading can speed
+ * development.
+ * @throws IllegalStateException if called more than once
+ */
+ public Container create(boolean loadSingletons) {
+ ensureNotCreated();
+ created = true;
+ final ContainerImpl container = new ContainerImpl(
+ new HashMap<Key<?>, InternalFactory<?>>(factories));
+ if (loadSingletons) {
+ container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
+ public Void call(InternalContext context) {
+ for (InternalFactory<?> factory : singletonFactories) {
+ factory.create(context);
+ }
+ return null;
+ }
+ });
+ }
+ container.injectStatics(staticInjections);
+ return container;
+ }
+
+ /**
+ * Currently we only support creating one Container instance per builder.
+ * If we want to support creating more than one container per builder,
+ * we should move to a "factory factory" model where we create a factory
+ * instance per Container. Right now, one factory instance would be
+ * shared across all the containers, singletons synchronize on the
+ * container when lazy loading, etc.
+ */
+ private void ensureNotCreated() {
+ if (created) {
+ throw new IllegalStateException("Container already created.");
+ }
+ }
+
+ /**
+ * Implemented by classes which participate in building a container.
+ */
+ public interface Command {
+
+ /**
+ * Contributes factories to the given builder.
+ *
+ * @param builder
+ */
+ void build(ContainerBuilder builder);
+ }
+}
diff --git a/src/com/google/inject/ContainerImpl.java b/src/com/google/inject/ContainerImpl.java
new file mode 100644
index 0000000..acadc68
--- /dev/null
+++ b/src/com/google/inject/ContainerImpl.java
@@ -0,0 +1,556 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import com.google.inject.util.ReferenceCache;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Default {@link Container} implementation.
+ *
+ * @see ContainerBuilder
+ * @author crazybob@google.com (Bob Lee)
+ */
+class ContainerImpl implements Container {
+
+ final Map<Key<?>, InternalFactory<?>> factories;
+
+ ContainerImpl(Map<Key<?>, InternalFactory<?>> factories) {
+ this.factories = factories;
+ }
+
+ @SuppressWarnings("unchecked")
+ <T> InternalFactory<? extends T> getFactory(Key<T> key) {
+ return (InternalFactory<T>) factories.get(key);
+ }
+
+ /**
+ * Field and method injectors.
+ */
+ final Map<Class<?>, List<Injector>> injectors =
+ new ReferenceCache<Class<?>, List<Injector>>() {
+ protected List<Injector> create(Class<?> key) {
+ List<Injector> injectors = new ArrayList<Injector>();
+ addInjectors(key, injectors);
+ return injectors;
+ }
+ };
+
+ /**
+ * Recursively adds injectors for fields and methods from the given class to
+ * the given list. Injects parent classes before sub classes.
+ */
+ void addInjectors(Class clazz, List<Injector> injectors) {
+ if (clazz == Object.class) {
+ return;
+ }
+
+ // Add injectors for superclass first.
+ addInjectors(clazz.getSuperclass(), injectors);
+
+ // TODO (crazybob): Filter out overridden members.
+ addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
+ addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
+ }
+
+ void injectStatics(List<Class<?>> staticInjections) {
+ final List<Injector> injectors = new ArrayList<Injector>();
+
+ for (Class<?> clazz : staticInjections) {
+ addInjectorsForFields(clazz.getDeclaredFields(), true, injectors);
+ addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors);
+ }
+
+ callInContext(new ContextualCallable<Void>() {
+ public Void call(InternalContext context) {
+ for (Injector injector : injectors) {
+ injector.inject(context, null);
+ }
+ return null;
+ }
+ });
+ }
+
+ void addInjectorsForMethods(Method[] methods, boolean statics,
+ List<Injector> injectors) {
+ addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
+ new InjectorFactory<Method>() {
+ public Injector create(ContainerImpl container, Method method,
+ String name) throws MissingDependencyException {
+ return new MethodInjector(container, method, name);
+ }
+ });
+ }
+
+ void addInjectorsForFields(Field[] fields, boolean statics,
+ List<Injector> injectors) {
+ addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
+ new InjectorFactory<Field>() {
+ public Injector create(ContainerImpl container, Field field,
+ String name) throws MissingDependencyException {
+ return new FieldInjector(container, field, name);
+ }
+ });
+ }
+
+ <M extends Member & AnnotatedElement> void addInjectorsForMembers(
+ List<M> members, boolean statics, List<Injector> injectors,
+ InjectorFactory<M> injectorFactory) {
+ for (M member : members) {
+ if (isStatic(member) == statics) {
+ Inject inject = member.getAnnotation(Inject.class);
+ if (inject != null) {
+ try {
+ injectors.add(injectorFactory.create(this, member, inject.value()));
+ } catch (MissingDependencyException e) {
+ if (inject.required()) {
+ throw new DependencyException(e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ interface InjectorFactory<M extends Member & AnnotatedElement> {
+ Injector create(ContainerImpl container, M member, String name)
+ throws MissingDependencyException;
+ }
+
+ private boolean isStatic(Member member) {
+ return Modifier.isStatic(member.getModifiers());
+ }
+
+ static class FieldInjector implements Injector {
+
+ final Field field;
+ final InternalFactory<?> factory;
+ final ExternalContext<?> externalContext;
+
+ public FieldInjector(ContainerImpl container, Field field, String name)
+ throws MissingDependencyException {
+ this.field = field;
+ field.setAccessible(true);
+
+ Key<?> key = Key.newInstance(field.getType(), name);
+ factory = container.getFactory(key);
+ if (factory == null) {
+ throw new MissingDependencyException(
+ "No mapping found for dependency " + key + " in " + field + ".");
+ }
+
+ this.externalContext = ExternalContext.newInstance(field, key, container);
+ }
+
+ public void inject(InternalContext context, Object o) {
+ ExternalContext<?> previous = context.getExternalContext();
+ context.setExternalContext(externalContext);
+ try {
+ field.set(o, factory.create(context));
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } finally {
+ context.setExternalContext(previous);
+ }
+ }
+ }
+
+ /**
+ * Gets parameter injectors.
+ *
+ * @param member to which the parameters belong
+ * @param annotations on the parameters
+ * @param parameterTypes parameter types
+ * @return injections
+ */
+ <M extends AccessibleObject & Member> ParameterInjector<?>[]
+ getParametersInjectors(M member,
+ Annotation[][] annotations, Class[] parameterTypes, String defaultName)
+ throws MissingDependencyException {
+ List<ParameterInjector<?>> parameterInjectors =
+ new ArrayList<ParameterInjector<?>>();
+
+ Iterator<Annotation[]> annotationsIterator =
+ Arrays.asList(annotations).iterator();
+ for (Class<?> parameterType : parameterTypes) {
+ Inject annotation = findInject(annotationsIterator.next());
+ String name = annotation == null ? defaultName : annotation.value();
+ Key<?> key = Key.newInstance(parameterType, name);
+ parameterInjectors.add(createParameterInjector(key, member));
+ }
+
+ return toArray(parameterInjectors);
+ }
+
+ <T> ParameterInjector<T> createParameterInjector(
+ Key<T> key, Member member) throws MissingDependencyException {
+ InternalFactory<? extends T> factory = getFactory(key);
+ if (factory == null) {
+ throw new MissingDependencyException(
+ "No mapping found for dependency " + key + " in " + member + ".");
+ }
+
+ ExternalContext<T> externalContext =
+ ExternalContext.newInstance(member, key, this);
+ return new ParameterInjector<T>(externalContext, factory);
+ }
+
+ @SuppressWarnings("unchecked")
+ private ParameterInjector<?>[] toArray(
+ List<ParameterInjector<?>> parameterInjections) {
+ return parameterInjections.toArray(
+ new ParameterInjector[parameterInjections.size()]);
+ }
+
+ /**
+ * Finds the {@link Inject} annotation in an array of annotations.
+ */
+ Inject findInject(Annotation[] annotations) {
+ for (Annotation annotation : annotations) {
+ if (annotation.annotationType() == Inject.class) {
+ return Inject.class.cast(annotation);
+ }
+ }
+ return null;
+ }
+
+ static class MethodInjector implements Injector {
+
+ final Method method;
+ final ParameterInjector<?>[] parameterInjectors;
+
+ public MethodInjector(ContainerImpl container, Method method, String name)
+ throws MissingDependencyException {
+ this.method = method;
+ method.setAccessible(true);
+
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ if (parameterTypes.length == 0) {
+ throw new DependencyException(
+ method + " has no parameters to inject.");
+ }
+ parameterInjectors = container.getParametersInjectors(
+ method, method.getParameterAnnotations(), parameterTypes, name);
+ }
+
+ public void inject(InternalContext context, Object o) {
+ try {
+ method.invoke(o, getParameters(method, context, parameterInjectors));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ Map<Class<?>, ConstructorInjector> constructors =
+ new ReferenceCache<Class<?>, ConstructorInjector>() {
+ @SuppressWarnings("unchecked")
+ protected ConstructorInjector<?> create(Class<?> implementation) {
+ return new ConstructorInjector(ContainerImpl.this, implementation);
+ }
+ };
+
+ static class ConstructorInjector<T> {
+
+ final Class<T> implementation;
+ final List<Injector> injectors;
+ final Constructor<T> constructor;
+ final ParameterInjector<?>[] parameterInjectors;
+
+ ConstructorInjector(ContainerImpl container, Class<T> implementation) {
+ this.implementation = implementation;
+
+ constructor = findConstructorIn(implementation);
+ constructor.setAccessible(true);
+
+ try {
+ Inject inject = constructor.getAnnotation(Inject.class);
+ parameterInjectors = inject == null
+ ? null // default constructor.
+ : container.getParametersInjectors(
+ constructor,
+ constructor.getParameterAnnotations(),
+ constructor.getParameterTypes(),
+ inject.value()
+ );
+ } catch (MissingDependencyException e) {
+ throw new DependencyException(e);
+ }
+ injectors = container.injectors.get(implementation);
+ }
+
+ private Constructor<T> findConstructorIn(Class<T> implementation) {
+ Constructor<T> found = null;
+ for (Constructor<T> constructor
+ : implementation.getDeclaredConstructors()) {
+ if (constructor.getAnnotation(Inject.class) != null) {
+ if (found != null) {
+ throw new DependencyException("More than one constructor annotated"
+ + " with @Inject found in " + implementation + ".");
+ }
+ found = constructor;
+ }
+ }
+ if (found != null) {
+ return found;
+ }
+
+ // If no annotated constructor is found, look for a no-arg constructor
+ // instead.
+ try {
+ return implementation.getDeclaredConstructor();
+ } catch (NoSuchMethodException e) {
+ throw new DependencyException("Could not find a suitable constructor"
+ + " in " + implementation.getName() + ".");
+ }
+ }
+
+ /**
+ * Construct an instance. Returns {@code Object} instead of {@code T}
+ * because it may return a proxy.
+ */
+ Object construct(InternalContext context, Class<? super T> expectedType) {
+ ConstructionContext<T> constructionContext =
+ context.getConstructionContext(this);
+
+ // We have a circular reference between constructors. Return a proxy.
+ if (constructionContext.isConstructing()) {
+ // TODO (crazybob): if we can't proxy this object, can we proxy the
+ // other object?
+ return constructionContext.createProxy(expectedType);
+ }
+
+ // If we're re-entering this factory while injecting fields or methods,
+ // return the same instance. This prevents infinite loops.
+ T t = constructionContext.getCurrentReference();
+ if (t != null) {
+ return t;
+ }
+
+ try {
+ // First time through...
+ constructionContext.startConstruction();
+ try {
+ Object[] parameters =
+ getParameters(constructor, context, parameterInjectors);
+ t = constructor.newInstance(parameters);
+ constructionContext.setProxyDelegates(t);
+ } finally {
+ constructionContext.finishConstruction();
+ }
+
+ // Store reference. If an injector re-enters this factory, they'll
+ // get the same reference.
+ constructionContext.setCurrentReference(t);
+
+ // Inject fields and methods.
+ for (Injector injector : injectors) {
+ injector.inject(context, t);
+ }
+
+ return t;
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ } finally {
+ constructionContext.removeCurrentReference();
+ }
+ }
+ }
+
+ static class ParameterInjector<T> {
+
+ final ExternalContext<T> externalContext;
+ final InternalFactory<? extends T> factory;
+
+ public ParameterInjector(ExternalContext<T> externalContext,
+ InternalFactory<? extends T> factory) {
+ this.externalContext = externalContext;
+ this.factory = factory;
+ }
+
+ T inject(Member member, InternalContext context) {
+ ExternalContext<?> previous = context.getExternalContext();
+ context.setExternalContext(externalContext);
+ try {
+ return factory.create(context);
+ } finally {
+ context.setExternalContext(previous);
+ }
+ }
+ }
+
+ private static Object[] getParameters(Member member, InternalContext context,
+ ParameterInjector[] parameterInjectors) {
+ if (parameterInjectors == null) {
+ return null;
+ }
+
+ Object[] parameters = new Object[parameterInjectors.length];
+ for (int i = 0; i < parameters.length; i++) {
+ parameters[i] = parameterInjectors[i].inject(member, context);
+ }
+ return parameters;
+ }
+
+ void inject(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) {
+ try {
+ ConstructorInjector<T> constructor = getConstructor(implementation);
+ return implementation.cast(
+ constructor.construct(context, implementation));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ <T> T getInstance(Class<T> type, String name, InternalContext context) {
+ ExternalContext<?> previous = context.getExternalContext();
+ Key<T> key = Key.newInstance(type, name);
+ context.setExternalContext(ExternalContext.newInstance(null, key, this));
+ try {
+ return getFactory(key).create(context);
+ } finally {
+ context.setExternalContext(previous);
+ }
+ }
+
+ <T> T getInstance(Class<T> type, InternalContext context) {
+ return getInstance(type, DEFAULT_NAME, context);
+ }
+
+ public void inject(final Object o) {
+ callInContext(new ContextualCallable<Void>() {
+ public Void call(InternalContext context) {
+ inject(o, context);
+ return null;
+ }
+ });
+ }
+
+ public <T> T inject(final Class<T> implementation) {
+ return callInContext(new ContextualCallable<T>() {
+ public T call(InternalContext context) {
+ return inject(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> T getInstance(final Class<T> type) {
+ return callInContext(new ContextualCallable<T>() {
+ public T call(InternalContext context) {
+ return getInstance(type, context);
+ }
+ });
+ }
+
+ ThreadLocal<InternalContext[]> localContext =
+ new ThreadLocal<InternalContext[]>() {
+ protected InternalContext[] initialValue() {
+ return new InternalContext[1];
+ }
+ };
+
+ /**
+ * Looks up thread local context. Creates (and removes) a new context if
+ * necessary.
+ */
+ <T> T callInContext(ContextualCallable<T> callable) {
+ InternalContext[] reference = localContext.get();
+ if (reference[0] == null) {
+ reference[0] = new InternalContext(this);
+ try {
+ return callable.call(reference[0]);
+ } finally {
+ // Only remove the context if this call created it.
+ reference[0] = null;
+ }
+ } else {
+ // Someone else will clean up this context.
+ return callable.call(reference[0]);
+ }
+ }
+
+ interface ContextualCallable<T> {
+ T call(InternalContext context);
+ }
+
+ /**
+ * Gets a constructor function for a given implementation class.
+ */
+ @SuppressWarnings("unchecked")
+ <T> ConstructorInjector<T> getConstructor(Class<T> implementation) {
+ return constructors.get(implementation);
+ }
+
+ 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.
+ */
+ interface Injector {
+ void inject(InternalContext context, Object o);
+ }
+
+ static class MissingDependencyException extends Exception {
+
+ MissingDependencyException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/com/google/inject/Context.java b/src/com/google/inject/Context.java
new file mode 100644
index 0000000..7d2b1b3
--- /dev/null
+++ b/src/com/google/inject/Context.java
@@ -0,0 +1,57 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import java.lang.reflect.Member;
+
+/**
+ * Context of the current injection.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public interface Context {
+
+ /**
+ * Gets the {@link Container}.
+ */
+ 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)}.
+ */
+ Member getMember();
+
+ /**
+ * Gets the type of the field or parameter which is being injected.
+ */
+ Class<?> getType();
+
+ /**
+ * Gets the name of the injection specified by {@link Inject#value()}.
+ */
+ String getName();
+}
diff --git a/src/com/google/inject/DependencyException.java b/src/com/google/inject/DependencyException.java
new file mode 100644
index 0000000..25fa5b1
--- /dev/null
+++ b/src/com/google/inject/DependencyException.java
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+/**
+ * Thrown when a dependency is misconfigured.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class DependencyException extends RuntimeException {
+
+ public DependencyException(String message) {
+ super(message);
+ }
+
+ public DependencyException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public DependencyException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/com/google/inject/ExternalContext.java b/src/com/google/inject/ExternalContext.java
new file mode 100644
index 0000000..f3e5ae2
--- /dev/null
+++ b/src/com/google/inject/ExternalContext.java
@@ -0,0 +1,73 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import java.lang.reflect.Member;
+import java.util.LinkedHashMap;
+
+/**
+ * An immutable snapshot of the current context which is safe to
+ * expose to client code.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+class ExternalContext<T> implements Context {
+
+ final Member member;
+ final Key<T> key;
+ final ContainerImpl container;
+
+ public ExternalContext(Member member, Key<T> key, ContainerImpl container) {
+ this.member = member;
+ this.key = key;
+ this.container = container;
+ }
+
+ public Class<T> getType() {
+ return key.getType();
+ }
+
+ public Scope.Strategy getScopeStrategy() {
+ return container.localScopeStrategy.get();
+ }
+
+ public Container getContainer() {
+ return container;
+ }
+
+ public Member getMember() {
+ 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("container", container);
+ }}.toString();
+ }
+
+ static <T> ExternalContext<T> newInstance(Member member, Key<T> key,
+ ContainerImpl container) {
+ return new ExternalContext<T>(member, key, container);
+ }
+}
diff --git a/src/com/google/inject/Factory.java b/src/com/google/inject/Factory.java
new file mode 100644
index 0000000..130488c
--- /dev/null
+++ b/src/com/google/inject/Factory.java
@@ -0,0 +1,34 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+/**
+ * A custom factory. Creates objects which will be injected.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public interface Factory<T> {
+
+ /**
+ * Creates an object to be injected.
+ *
+ * @param context of this injection
+ * @return instance to be injected
+ * @throws Exception if unable to create object
+ */
+ T create(Context context) throws Exception;
+}
diff --git a/src/com/google/inject/Inject.java b/src/com/google/inject/Inject.java
new file mode 100644
index 0000000..86a9b2b
--- /dev/null
+++ b/src/com/google/inject/Inject.java
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import static com.google.inject.Container.*;
+
+import static java.lang.annotation.ElementType.*;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.*;
+import java.lang.annotation.Target;
+
+/**
+ * <p>Annotates members and parameters which should have their value[s]
+ * injected.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})
+@Retention(RUNTIME)
+public @interface Inject {
+
+ /**
+ * Dependency name. Defaults to {@link Container#DEFAULT_NAME}.
+ */
+ String value() default DEFAULT_NAME;
+
+ /**
+ * Whether or not injection is required. Applicable only to methods and
+ * fields (not constructors or parameters).
+ */
+ boolean required() default true;
+}
diff --git a/src/com/google/inject/InternalContext.java b/src/com/google/inject/InternalContext.java
new file mode 100644
index 0000000..d9d9879
--- /dev/null
+++ b/src/com/google/inject/InternalContext.java
@@ -0,0 +1,80 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Internal context. Used to coordinate injections and support circular
+ * dependencies.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+class InternalContext {
+
+ final ContainerImpl container;
+ final Map<Object, ConstructionContext<?>> constructionContexts =
+ new HashMap<Object, ConstructionContext<?>>();
+ Scope.Strategy scopeStrategy;
+ ExternalContext<?> externalContext;
+
+ InternalContext(ContainerImpl container) {
+ this.container = container;
+ }
+
+ public Container getContainer() {
+ return container;
+ }
+
+ ContainerImpl getContainerImpl() {
+ return container;
+ }
+
+ Scope.Strategy getScopeStrategy() {
+ if (scopeStrategy == null) {
+ scopeStrategy = container.localScopeStrategy.get();
+
+ 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 =
+ (ConstructionContext<T>) constructionContexts.get(key);
+ if (constructionContext == null) {
+ constructionContext = new ConstructionContext<T>();
+ constructionContexts.put(key, constructionContext);
+ }
+ return constructionContext;
+ }
+
+ @SuppressWarnings("unchecked")
+ <T> ExternalContext<T> getExternalContext() {
+ return (ExternalContext<T>) externalContext;
+ }
+
+ void setExternalContext(ExternalContext<?> externalContext) {
+ this.externalContext = externalContext;
+ }
+}
diff --git a/src/com/google/inject/InternalFactory.java b/src/com/google/inject/InternalFactory.java
new file mode 100644
index 0000000..fa5ec04
--- /dev/null
+++ b/src/com/google/inject/InternalFactory.java
@@ -0,0 +1,33 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+/**
+ * Creates objects which will be injected.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+interface InternalFactory<T> {
+
+ /**
+ * Creates an object to be injected.
+ *
+ * @param context of this injection
+ * @return instance to be injected
+ */
+ T create(InternalContext context);
+}
diff --git a/src/com/google/inject/Key.java b/src/com/google/inject/Key.java
new file mode 100644
index 0000000..3066483
--- /dev/null
+++ b/src/com/google/inject/Key.java
@@ -0,0 +1,74 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+/**
+ * Dependency mapping key. Uniquely identified by the required type and name.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+class Key<T> {
+
+ final Class<T> type;
+ final String name;
+ final int hashCode;
+
+ private Key(Class<T> type, String name) {
+ if (type == null) {
+ throw new NullPointerException("Type is null.");
+ }
+ if (name == null) {
+ throw new NullPointerException("Name is null.");
+ }
+
+ this.type = type;
+ this.name = name;
+
+ hashCode = type.hashCode() * 31 + name.hashCode();
+ }
+
+ Class<T> getType() {
+ return type;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ public int hashCode() {
+ return hashCode;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof Key)) {
+ return false;
+ }
+ if (o == this) {
+ return true;
+ }
+ Key other = (Key) o;
+ return name.equals(other.name) && type.equals(other.type);
+ }
+
+ public String toString() {
+ return "[type=" + type.getName() + ", name='" + name + "']";
+ }
+
+ static <T> Key<T> newInstance(Class<T> type, String name) {
+ return new Key<T>(type, name);
+ }
+}
diff --git a/src/com/google/inject/Scope.java b/src/com/google/inject/Scope.java
new file mode 100644
index 0000000..66a4d94
--- /dev/null
+++ b/src/com/google/inject/Scope.java
@@ -0,0 +1,206 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Scope of an injected objects.
+ *
+ * @author crazybob
+ */
+public enum Scope {
+
+ /**
+ * One instance per injection.
+ */
+ DEFAULT {
+ <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
+ InternalFactory<? extends T> factory) {
+ return factory;
+ }
+ },
+
+ /**
+ * One instance per container.
+ */
+ SINGLETON {
+ <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
+ final InternalFactory<? extends T> factory) {
+ return new InternalFactory<T>() {
+ T instance;
+ public T create(InternalContext context) {
+ synchronized (context.getContainer()) {
+ if (instance == null) {
+ instance = factory.create(context);
+ }
+ return instance;
+ }
+ }
+
+ public String toString() {
+ return factory.toString();
+ }
+ };
+ }
+ },
+
+ /**
+ * One instance per thread.
+ *
+ * <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.
+ */
+ THREAD {
+ <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
+ final InternalFactory<? extends T> factory) {
+ return new InternalFactory<T>() {
+ final ThreadLocal<T> threadLocal = new ThreadLocal<T>();
+ public T create(final InternalContext context) {
+ T t = threadLocal.get();
+ if (t == null) {
+ t = factory.create(context);
+ threadLocal.set(t);
+ }
+ return t;
+ }
+
+ public String toString() {
+ return factory.toString();
+ }
+ };
+ }
+ },
+
+ /**
+ * One instance per request.
+ */
+ REQUEST {
+ <T> InternalFactory<? extends T> scopeFactory(final Class<T> type,
+ final String name, final InternalFactory<? extends T> factory) {
+ return new InternalFactory<T>() {
+ public T create(InternalContext context) {
+ Strategy strategy = context.getScopeStrategy();
+ try {
+ return strategy.findInRequest(
+ type, name, 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 Class<T> type,
+ final String name, final InternalFactory<? extends T> factory) {
+ return new InternalFactory<T>() {
+ public T create(InternalContext context) {
+ Strategy strategy = context.getScopeStrategy();
+ try {
+ return strategy.findInSession(
+ type, name, 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 Class<T> type,
+ final String name, final InternalFactory<? extends T> factory) {
+ return new InternalFactory<T>() {
+ public T create(InternalContext context) {
+ Strategy strategy = context.getScopeStrategy();
+ try {
+ return strategy.findInWizard(
+ type, name, 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.create(context);
+ }
+ };
+ }
+
+ /**
+ * Wraps factory with scoping logic.
+ */
+ abstract <T> InternalFactory<? extends T> scopeFactory(
+ Class<T> type, String name, 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(Class<T> type, String name,
+ 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(Class<T> type, String name,
+ 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(Class<T> type, String name,
+ Callable<? extends T> factory) throws Exception;
+ }
+}
diff --git a/src/com/google/inject/Scoped.java b/src/com/google/inject/Scoped.java
new file mode 100644
index 0000000..cf14329
--- /dev/null
+++ b/src/com/google/inject/Scoped.java
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+
+package com.google.inject;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a scoped implementation class.
+ *
+ * @author crazybob
+ */
+@Target(ElementType.TYPE)
+@Retention(RUNTIME)
+public @interface Scoped {
+
+ /**
+ * Scope.
+ */
+ Scope value();
+}
diff --git a/src/com/google/inject/package-info.java b/src/com/google/inject/package-info.java
new file mode 100644
index 0000000..28c8b90
--- /dev/null
+++ b/src/com/google/inject/package-info.java
@@ -0,0 +1,30 @@
+/**
+ * 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.
+ */
+
+/**
+ * <i>Guice</i> (pronounced "juice"). A lightweight dependency injection
+ * container. Features include:
+ *
+ * <ul>
+ * <li>constructor, method, and field injection</li>
+ * <li>static method and field injection</li>
+ * <li>circular reference support (including constructors if you depend upon
+ * interfaces)</li>
+ * <li>high performance</li>
+ * <li>externalize what needs to be and no more</li>
+ * </ul>
+ */
+package com.google.inject;
diff --git a/src/com/google/inject/util/FinalizablePhantomReference.java b/src/com/google/inject/util/FinalizablePhantomReference.java
new file mode 100644
index 0000000..a61a132
--- /dev/null
+++ b/src/com/google/inject/util/FinalizablePhantomReference.java
@@ -0,0 +1,35 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+import java.lang.ref.PhantomReference;
+
+/**
+ * Phantom reference with a {@link #finalizeReferent()} method which a
+ * background thread invokes after the garbage collector reclaims the
+ * referent. This is a simpler alternative to using a {@link
+ * java.lang.ref.ReferenceQueue}.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public abstract class FinalizablePhantomReference<T>
+ extends PhantomReference<T> implements FinalizableReference {
+
+ protected FinalizablePhantomReference(T referent) {
+ super(referent, FinalizableReferenceQueue.getInstance());
+ }
+}
diff --git a/src/com/google/inject/util/FinalizableReference.java b/src/com/google/inject/util/FinalizableReference.java
new file mode 100644
index 0000000..b655b22
--- /dev/null
+++ b/src/com/google/inject/util/FinalizableReference.java
@@ -0,0 +1,32 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+/**
+ * Package-private interface implemented by references that have code to run
+ * after garbage collection of their referents.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+interface FinalizableReference {
+
+ /**
+ * Invoked on a background thread after the referent has been garbage
+ * collected.
+ */
+ void finalizeReferent();
+}
diff --git a/src/com/google/inject/util/FinalizableReferenceQueue.java b/src/com/google/inject/util/FinalizableReferenceQueue.java
new file mode 100644
index 0000000..bcf0ede
--- /dev/null
+++ b/src/com/google/inject/util/FinalizableReferenceQueue.java
@@ -0,0 +1,78 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+import com.google.inject.util.FinalizableReference;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Starts a background thread that cleans up after reclaimed referents.
+ *
+ * @author Bob Lee (crazybob@google.com)
+ */
+class FinalizableReferenceQueue extends ReferenceQueue<Object> {
+
+ private static final Logger logger =
+ Logger.getLogger(FinalizableReferenceQueue.class.getName());
+
+ private FinalizableReferenceQueue() {}
+
+ void cleanUp(Reference reference) {
+ try {
+ ((FinalizableReference) reference).finalizeReferent();
+ } catch (Throwable t) {
+ deliverBadNews(t);
+ }
+ }
+
+ void deliverBadNews(Throwable t) {
+ logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
+ }
+
+ void start() {
+ Thread thread = new Thread("FinalizableReferenceQueue") {
+ public void run() {
+ while (true) {
+ try {
+ cleanUp(remove());
+ } catch (InterruptedException e) { /* ignore */ }
+ }
+ }
+ };
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+ static ReferenceQueue<Object> instance = createAndStart();
+
+ static FinalizableReferenceQueue createAndStart() {
+ FinalizableReferenceQueue queue = new FinalizableReferenceQueue();
+ queue.start();
+ return queue;
+ }
+
+ /**
+ * Gets instance.
+ */
+ public static ReferenceQueue<Object> getInstance() {
+ return instance;
+ }
+}
diff --git a/src/com/google/inject/util/FinalizableSoftReference.java b/src/com/google/inject/util/FinalizableSoftReference.java
new file mode 100644
index 0000000..b9af081
--- /dev/null
+++ b/src/com/google/inject/util/FinalizableSoftReference.java
@@ -0,0 +1,34 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+import java.lang.ref.SoftReference;
+
+/**
+ * Soft reference with a {@link #finalizeReferent()} method which a background
+ * thread invokes after the garbage collector reclaims the referent. This is a
+ * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public abstract class FinalizableSoftReference<T> extends SoftReference<T>
+ implements FinalizableReference {
+
+ protected FinalizableSoftReference(T referent) {
+ super(referent, FinalizableReferenceQueue.getInstance());
+ }
+}
diff --git a/src/com/google/inject/util/FinalizableWeakReference.java b/src/com/google/inject/util/FinalizableWeakReference.java
new file mode 100644
index 0000000..b92b7b8
--- /dev/null
+++ b/src/com/google/inject/util/FinalizableWeakReference.java
@@ -0,0 +1,34 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Weak reference with a {@link #finalizeReferent()} method which a background
+ * thread invokes after the garbage collector reclaims the referent. This is a
+ * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public abstract class FinalizableWeakReference<T> extends WeakReference<T>
+ implements FinalizableReference {
+
+ protected FinalizableWeakReference(T referent) {
+ super(referent, FinalizableReferenceQueue.getInstance());
+ }
+}
diff --git a/src/com/google/inject/util/Function.java b/src/com/google/inject/util/Function.java
new file mode 100644
index 0000000..8d5a349
--- /dev/null
+++ b/src/com/google/inject/util/Function.java
@@ -0,0 +1,44 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+/**
+ * A Function provides a transformation on an object and returns the resulting
+ * object. For example, a {@code StringToIntegerFunction} may implement
+ * <code>Function<String,Integer></code> and transform integers in String
+ * format to Integer format.
+ *
+ * <p>The transformation on the source object does not necessarily result in
+ * an object of a different type. For example, a
+ * {@code FarenheitToCelciusFunction} may implement
+ * <code>Function<Float,Float></code>.
+ *
+ * <p>Implementors of Function which may cause side effects upon evaluation are
+ * strongly encouraged to state this fact clearly in their API documentation.
+ */
+public interface Function<F,T> {
+
+ /**
+ * Applies the function to an object of type {@code F}, resulting in an object
+ * of type {@code T}. Note that types {@code F} and {@code T} may or may not
+ * be the same.
+ *
+ * @param from The source object.
+ * @return The resulting object.
+ */
+ T apply(F from);
+}
diff --git a/src/com/google/inject/util/ReferenceCache.java b/src/com/google/inject/util/ReferenceCache.java
new file mode 100644
index 0000000..8de78a9
--- /dev/null
+++ b/src/com/google/inject/util/ReferenceCache.java
@@ -0,0 +1,188 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+import static com.google.inject.util.ReferenceType.STRONG;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Extends {@link ReferenceMap} to support lazy loading values by overriding
+ * {@link #create(Object)}.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public abstract class ReferenceCache<K, V> extends ReferenceMap<K, V> {
+
+ private static final long serialVersionUID = 0;
+
+ transient ConcurrentMap<Object, Future<V>> futures =
+ new ConcurrentHashMap<Object, Future<V>>();
+
+ transient ThreadLocal<Future<V>> localFuture = new ThreadLocal<Future<V>>();
+
+ public ReferenceCache(ReferenceType keyReferenceType,
+ ReferenceType valueReferenceType) {
+ super(keyReferenceType, valueReferenceType);
+ }
+
+ /**
+ * Equivalent to {@code new ReferenceCache(STRONG, STRONG)}.
+ */
+ public ReferenceCache() {
+ super(STRONG, STRONG);
+ }
+
+ /**
+ * Override to lazy load values. Use as an alternative to {@link
+ * #put(Object,Object)}. Invoked by getter if value isn't already cached.
+ * Must not return {@code null}. This method will not be called again until
+ * the garbage collector reclaims the returned value.
+ */
+ protected abstract V create(K key);
+
+ V internalCreate(K key) {
+ try {
+ FutureTask<V> futureTask = new FutureTask<V>(
+ new CallableCreate(key));
+
+ // use a reference so we get the same equality semantics.
+ Object keyReference = referenceKey(key);
+ Future<V> future = futures.putIfAbsent(keyReference, futureTask);
+ if (future == null) {
+ // winning thread.
+ try {
+ if (localFuture.get() != null) {
+ throw new IllegalStateException(
+ "Nested creations within the same cache are not allowed.");
+ }
+ localFuture.set(futureTask);
+ futureTask.run();
+ V value = futureTask.get();
+ putStrategy().execute(this,
+ keyReference, referenceValue(keyReference, value));
+ return value;
+ } finally {
+ localFuture.remove();
+ futures.remove(keyReference);
+ }
+ } else {
+ // wait for winning thread.
+ return future.get();
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ throw new RuntimeException(cause);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * If this map does not contain an entry for the given key and {@link
+ * #create(Object)} has been overridden, this method will create a new
+ * value, put it in the map, and return it.
+ *
+ * @throws NullPointerException if {@link #create(Object)} returns null.
+ * @throws java.util.concurrent.CancellationException if the creation is
+ * cancelled. See {@link #cancel()}.
+ */
+ @SuppressWarnings("unchecked")
+ @Override public V get(final Object key) {
+ V value = super.get(key);
+ return (value == null)
+ ? internalCreate((K) key)
+ : value;
+ }
+
+ /**
+ * Cancels the current {@link #create(Object)}. Throws {@link
+ * java.util.concurrent.CancellationException} to all clients currently
+ * blocked on {@link #get(Object)}.
+ */
+ protected void cancel() {
+ Future<V> future = localFuture.get();
+ if (future == null) {
+ throw new IllegalStateException("Not in create().");
+ }
+ future.cancel(false);
+ }
+
+ class CallableCreate implements Callable<V> {
+
+ K key;
+
+ public CallableCreate(K key) {
+ this.key = key;
+ }
+
+ public V call() {
+ // try one more time (a previous future could have come and gone.)
+ V value = internalGet(key);
+ if (value != null) {
+ return value;
+ }
+
+ // create value.
+ value = create(key);
+ if (value == null) {
+ throw new NullPointerException(
+ "create(K) returned null for: " + key);
+ }
+ return value;
+ }
+ }
+
+ /**
+ * Returns a {@code ReferenceCache} delegating to the specified {@code
+ * function}. The specified function must not return {@code null}.
+ */
+ public static <K, V> ReferenceCache<K, V> of(
+ ReferenceType keyReferenceType,
+ ReferenceType valueReferenceType,
+ final Function<? super K, ? extends V> function) {
+ ensureNotNull(function);
+ return new ReferenceCache<K, V>(keyReferenceType, valueReferenceType) {
+ protected V create(K key) {
+ return function.apply(key);
+ }
+ private static final long serialVersionUID = 0;
+ };
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException,
+ ClassNotFoundException {
+ in.defaultReadObject();
+ this.futures = new ConcurrentHashMap<Object, Future<V>>();
+ this.localFuture = new ThreadLocal<Future<V>>();
+ }
+
+}
diff --git a/src/com/google/inject/util/ReferenceMap.java b/src/com/google/inject/util/ReferenceMap.java
new file mode 100644
index 0000000..9a5d6f3
--- /dev/null
+++ b/src/com/google/inject/util/ReferenceMap.java
@@ -0,0 +1,615 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+import static com.google.inject.util.ReferenceType.STRONG;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.ref.Reference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Concurrent hash map that wraps keys and/or values in soft or weak
+ * references. Does not support null keys or values. Uses identity equality
+ * for weak and soft keys.
+ *
+ * <p>The concurrent semantics of {@link ConcurrentHashMap} combined with the
+ * fact that the garbage collector can asynchronously reclaim and clean up
+ * after keys and values at any time can lead to some racy semantics. For
+ * example, {@link #size()} returns an upper bound on the size, i.e. the actual
+ * size may be smaller in cases where the key or value has been reclaimed but
+ * the map entry has not been cleaned up yet.
+ *
+ * <p>Another example: If {@link #get(Object)} cannot find an existing entry
+ * for a key, it will try to create one. This operation is not atomic. One
+ * thread could {@link #put(Object, Object)} a value between the time another
+ * thread running {@code get()} checks for an entry and decides to create one.
+ * In this case, the newly created value will replace the put value in the
+ * map. Also, two threads running {@code get()} concurrently can potentially
+ * create duplicate values for a given key.
+ *
+ * <p>In other words, this class is great for caching but not atomicity.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+@SuppressWarnings("unchecked")
+public class ReferenceMap<K, V> implements Map<K, V>, Serializable {
+
+ private static final long serialVersionUID = 0;
+
+ transient ConcurrentMap<Object, Object> delegate;
+
+ final ReferenceType keyReferenceType;
+ final ReferenceType valueReferenceType;
+
+ /**
+ * Concurrent hash map that wraps keys and/or values based on specified
+ * reference types.
+ *
+ * @param keyReferenceType key reference type
+ * @param valueReferenceType value reference type
+ */
+ public ReferenceMap(ReferenceType keyReferenceType,
+ ReferenceType valueReferenceType) {
+ ensureNotNull(keyReferenceType, valueReferenceType);
+
+ if (keyReferenceType == ReferenceType.PHANTOM
+ || valueReferenceType == ReferenceType.PHANTOM) {
+ throw new IllegalArgumentException("Phantom references not supported.");
+ }
+
+ this.delegate = new ConcurrentHashMap<Object, Object>();
+ this.keyReferenceType = keyReferenceType;
+ this.valueReferenceType = valueReferenceType;
+ }
+
+ V internalGet(K key) {
+ Object valueReference = delegate.get(makeKeyReferenceAware(key));
+ return valueReference == null
+ ? null
+ : (V) dereferenceValue(valueReference);
+ }
+
+ public V get(final Object key) {
+ ensureNotNull(key);
+ return internalGet((K) key);
+ }
+
+ V execute(Strategy strategy, K key, V value) {
+ ensureNotNull(key, value);
+ Object keyReference = referenceKey(key);
+ Object valueReference = strategy.execute(
+ this,
+ keyReference,
+ referenceValue(keyReference, value)
+ );
+ return valueReference == null ? null
+ : (V) dereferenceValue(valueReference);
+ }
+
+ public V put(K key, V value) {
+ return execute(putStrategy(), key, value);
+ }
+
+ public V remove(Object key) {
+ ensureNotNull(key);
+ Object referenceAwareKey = makeKeyReferenceAware(key);
+ Object valueReference = delegate.remove(referenceAwareKey);
+ return valueReference == null ? null
+ : (V) dereferenceValue(valueReference);
+ }
+
+ public int size() {
+ return delegate.size();
+ }
+
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ public boolean containsKey(Object key) {
+ ensureNotNull(key);
+ Object referenceAwareKey = makeKeyReferenceAware(key);
+ return delegate.containsKey(referenceAwareKey);
+ }
+
+ public boolean containsValue(Object value) {
+ ensureNotNull(value);
+ for (Object valueReference : delegate.values()) {
+ if (value.equals(dereferenceValue(valueReference))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void putAll(Map<? extends K, ? extends V> t) {
+ for (Map.Entry<? extends K, ? extends V> entry : t.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public void clear() {
+ delegate.clear();
+ }
+
+ /**
+ * Returns an unmodifiable set view of the keys in this map. As this method
+ * creates a defensive copy, the performance is O(n).
+ */
+ public Set<K> keySet() {
+ return Collections.unmodifiableSet(
+ dereferenceKeySet(delegate.keySet()));
+ }
+
+ /**
+ * Returns an unmodifiable set view of the values in this map. As this
+ * method creates a defensive copy, the performance is O(n).
+ */
+ public Collection<V> values() {
+ return Collections.unmodifiableCollection(
+ dereferenceValues(delegate.values()));
+ }
+
+ public V putIfAbsent(K key, V value) {
+ // TODO (crazybob) if the value has been gc'ed but the entry hasn't been
+ // cleaned up yet, this put will fail.
+ return execute(putIfAbsentStrategy(), key, value);
+ }
+
+ public boolean remove(Object key, Object value) {
+ ensureNotNull(key, value);
+ Object referenceAwareKey = makeKeyReferenceAware(key);
+ Object referenceAwareValue = makeValueReferenceAware(value);
+ return delegate.remove(referenceAwareKey, referenceAwareValue);
+ }
+
+ public boolean replace(K key, V oldValue, V newValue) {
+ ensureNotNull(key, oldValue, newValue);
+ Object keyReference = referenceKey(key);
+
+ Object referenceAwareOldValue = makeValueReferenceAware(oldValue);
+ return delegate.replace(
+ keyReference,
+ referenceAwareOldValue,
+ referenceValue(keyReference, newValue)
+ );
+ }
+
+ public V replace(K key, V value) {
+ // TODO (crazybob) if the value has been gc'ed but the entry hasn't been
+ // cleaned up yet, this will succeed when it probably shouldn't.
+ return execute(replaceStrategy(), key, value);
+ }
+
+ /**
+ * Returns an unmodifiable set view of the entries in this map. As this
+ * method creates a defensive copy, the performance is O(n).
+ */
+ public Set<Map.Entry<K, V>> entrySet() {
+ Set<Map.Entry<K, V>> entrySet = new HashSet<Map.Entry<K, V>>();
+ for (Map.Entry<Object, Object> entry : delegate.entrySet()) {
+ Map.Entry<K, V> dereferenced = dereferenceEntry(entry);
+ if (dereferenced != null) {
+ entrySet.add(dereferenced);
+ }
+ }
+ return Collections.unmodifiableSet(entrySet);
+ }
+
+ /**
+ * Dereferences an entry. Returns null if the key or value has been gc'ed.
+ */
+ Entry dereferenceEntry(Map.Entry<Object, Object> entry) {
+ K key = dereferenceKey(entry.getKey());
+ V value = dereferenceValue(entry.getValue());
+ return (key == null || value == null)
+ ? null
+ : new Entry(key, value);
+ }
+
+ /**
+ * Creates a reference for a key.
+ */
+ Object referenceKey(K key) {
+ switch (keyReferenceType) {
+ case STRONG: return key;
+ case SOFT: return new SoftKeyReference(key);
+ case WEAK: return new WeakKeyReference(key);
+ default: throw new AssertionError();
+ }
+ }
+
+ /**
+ * Converts a reference to a key.
+ */
+ K dereferenceKey(Object o) {
+ return (K) dereference(keyReferenceType, o);
+ }
+
+ /**
+ * Converts a reference to a value.
+ */
+ V dereferenceValue(Object o) {
+ return (V) dereference(valueReferenceType, o);
+ }
+
+ /**
+ * Returns the refererent for reference given its reference type.
+ */
+ Object dereference(ReferenceType referenceType, Object reference) {
+ return referenceType == STRONG ? reference : ((Reference) reference).get();
+ }
+
+ /**
+ * Creates a reference for a value.
+ */
+ Object referenceValue(Object keyReference, Object value) {
+ switch (valueReferenceType) {
+ case STRONG: return value;
+ case SOFT: return new SoftValueReference(keyReference, value);
+ case WEAK: return new WeakValueReference(keyReference, value);
+ default: throw new AssertionError();
+ }
+ }
+
+ /**
+ * Dereferences a set of key references.
+ */
+ Set<K> dereferenceKeySet(Set keyReferences) {
+ return keyReferenceType == STRONG
+ ? keyReferences
+ : dereferenceCollection(keyReferenceType, keyReferences, new HashSet());
+ }
+
+ /**
+ * Dereferences a collection of value references.
+ */
+ Collection<V> dereferenceValues(Collection valueReferences) {
+ return valueReferenceType == STRONG
+ ? valueReferences
+ : dereferenceCollection(valueReferenceType, valueReferences,
+ new ArrayList(valueReferences.size()));
+ }
+
+ /**
+ * Wraps key so it can be compared to a referenced key for equality.
+ */
+ Object makeKeyReferenceAware(Object o) {
+ return keyReferenceType == STRONG ? o : new KeyReferenceAwareWrapper(o);
+ }
+
+ /**
+ * Wraps value so it can be compared to a referenced value for equality.
+ */
+ Object makeValueReferenceAware(Object o) {
+ return valueReferenceType == STRONG ? o : new ReferenceAwareWrapper(o);
+ }
+
+ /**
+ * Dereferences elements in {@code in} using
+ * {@code referenceType} and puts them in {@code out}. Returns
+ * {@code out}.
+ */
+ <T extends Collection<Object>> T dereferenceCollection(
+ ReferenceType referenceType, T in, T out) {
+ for (Object reference : in) {
+ out.add(dereference(referenceType, reference));
+ }
+ return out;
+ }
+
+ /**
+ * Marker interface to differentiate external and internal references.
+ */
+ interface InternalReference {}
+
+ static int keyHashCode(Object key) {
+ return System.identityHashCode(key);
+ }
+
+ /**
+ * Tests weak and soft references for identity equality. Compares references
+ * to other references and wrappers. If o is a reference, this returns true
+ * if r == o or if r and o reference the same non null object. If o is a
+ * wrapper, this returns true if r's referent is identical to the wrapped
+ * object.
+ */
+ static boolean referenceEquals(Reference r, Object o) {
+ // compare reference to reference.
+ if (o instanceof InternalReference) {
+ // are they the same reference? used in cleanup.
+ if (o == r) {
+ return true;
+ }
+
+ // do they reference identical values? used in conditional puts.
+ Object referent = ((Reference) o).get();
+ return referent != null && referent == r.get();
+ }
+
+ // is the wrapped object identical to the referent? used in lookups.
+ return ((ReferenceAwareWrapper) o).unwrap() == r.get();
+ }
+
+ /**
+ * Big hack. Used to compare keys and values to referenced keys and values
+ * without creating more references.
+ */
+ static class ReferenceAwareWrapper {
+
+ Object wrapped;
+
+ ReferenceAwareWrapper(Object wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ Object unwrap() {
+ return wrapped;
+ }
+
+ public int hashCode() {
+ return wrapped.hashCode();
+ }
+
+ public boolean equals(Object obj) {
+ // defer to reference's equals() logic.
+ return obj.equals(this);
+ }
+ }
+
+ /**
+ * Used for keys. Overrides hash code to use identity hash code.
+ */
+ static class KeyReferenceAwareWrapper extends ReferenceAwareWrapper {
+
+ public KeyReferenceAwareWrapper(Object wrapped) {
+ super(wrapped);
+ }
+
+ public int hashCode() {
+ return System.identityHashCode(wrapped);
+ }
+ }
+
+ class SoftKeyReference extends FinalizableSoftReference<Object>
+ implements InternalReference {
+
+ int hashCode;
+
+ public SoftKeyReference(Object key) {
+ super(key);
+ this.hashCode = keyHashCode(key);
+ }
+
+ public void finalizeReferent() {
+ delegate.remove(this);
+ }
+
+ @Override public int hashCode() {
+ return this.hashCode;
+ }
+
+ @Override public boolean equals(Object o) {
+ return referenceEquals(this, o);
+ }
+ }
+
+ class WeakKeyReference extends FinalizableWeakReference<Object>
+ implements InternalReference {
+
+ int hashCode;
+
+ public WeakKeyReference(Object key) {
+ super(key);
+ this.hashCode = keyHashCode(key);
+ }
+
+ public void finalizeReferent() {
+ delegate.remove(this);
+ }
+
+ @Override public int hashCode() {
+ return this.hashCode;
+ }
+
+ @Override public boolean equals(Object o) {
+ return referenceEquals(this, o);
+ }
+ }
+
+ class SoftValueReference extends FinalizableSoftReference<Object>
+ implements InternalReference {
+
+ Object keyReference;
+
+ public SoftValueReference(Object keyReference, Object value) {
+ super(value);
+ this.keyReference = keyReference;
+ }
+
+ public void finalizeReferent() {
+ delegate.remove(keyReference, this);
+ }
+
+ @Override public boolean equals(Object obj) {
+ return referenceEquals(this, obj);
+ }
+ }
+
+ class WeakValueReference extends FinalizableWeakReference<Object>
+ implements InternalReference {
+
+ Object keyReference;
+
+ public WeakValueReference(Object keyReference, Object value) {
+ super(value);
+ this.keyReference = keyReference;
+ }
+
+ public void finalizeReferent() {
+ delegate.remove(keyReference, this);
+ }
+
+ @Override public boolean equals(Object obj) {
+ return referenceEquals(this, obj);
+ }
+ }
+
+ protected interface Strategy {
+ public Object execute(ReferenceMap map, Object keyReference,
+ Object valueReference);
+ }
+
+ protected Strategy putStrategy() {
+ return PutStrategy.PUT;
+ }
+
+ protected Strategy putIfAbsentStrategy() {
+ return PutStrategy.PUT_IF_ABSENT;
+ }
+
+ protected Strategy replaceStrategy() {
+ return PutStrategy.REPLACE;
+ }
+
+ private enum PutStrategy implements Strategy {
+ PUT {
+ public Object execute(ReferenceMap map, Object keyReference,
+ Object valueReference) {
+ return map.delegate.put(keyReference, valueReference);
+ }
+ },
+
+ REPLACE {
+ public Object execute(ReferenceMap map, Object keyReference,
+ Object valueReference) {
+ return map.delegate.replace(keyReference, valueReference);
+ }
+ },
+
+ PUT_IF_ABSENT {
+ public Object execute(ReferenceMap map, Object keyReference,
+ Object valueReference) {
+ return map.delegate.putIfAbsent(keyReference, valueReference);
+ }
+ };
+ };
+
+ private static PutStrategy defaultPutStrategy;
+
+ protected PutStrategy getPutStrategy() {
+ return defaultPutStrategy;
+ }
+
+
+ class Entry implements Map.Entry<K, V> {
+
+ K key;
+ V value;
+
+ public Entry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public K getKey() {
+ return this.key;
+ }
+
+ public V getValue() {
+ return this.value;
+ }
+
+ public V setValue(V value) {
+ return put(key, value);
+ }
+
+ public int hashCode() {
+ return key.hashCode() * 31 + value.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof ReferenceMap.Entry)) {
+ return false;
+ }
+
+ Entry entry = (Entry) o;
+ return key.equals(entry.key) && value.equals(entry.value);
+ }
+
+ public String toString() {
+ return key + "=" + value;
+ }
+ }
+
+ static void ensureNotNull(Object o) {
+ if (o == null) {
+ throw new NullPointerException();
+ }
+ }
+
+ static void ensureNotNull(Object... array) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ throw new NullPointerException("Argument #" + i + " is null.");
+ }
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ out.writeInt(size());
+ for (Map.Entry<Object, Object> entry : delegate.entrySet()) {
+ Object key = dereferenceKey(entry.getKey());
+ Object value = dereferenceValue(entry.getValue());
+
+ // don't persist gc'ed entries.
+ if (key != null && value != null) {
+ out.writeObject(key);
+ out.writeObject(value);
+ }
+ }
+ out.writeObject(null);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException,
+ ClassNotFoundException {
+ in.defaultReadObject();
+ int size = in.readInt();
+ this.delegate = new ConcurrentHashMap<Object, Object>(size);
+ while (true) {
+ K key = (K) in.readObject();
+ if (key == null) {
+ break;
+ }
+ V value = (V) in.readObject();
+ put(key, value);
+ }
+ }
+
+}
diff --git a/src/com/google/inject/util/ReferenceType.java b/src/com/google/inject/util/ReferenceType.java
new file mode 100644
index 0000000..8ec8f38
--- /dev/null
+++ b/src/com/google/inject/util/ReferenceType.java
@@ -0,0 +1,55 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+/**
+ * Reference type. Used to specify what type of reference to keep to a
+ * referent.
+ *
+ * @see java.lang.ref.Reference
+ * @author crazybob@google.com (Bob Lee)
+ */
+public enum ReferenceType {
+
+ /**
+ * Prevents referent from being reclaimed by the garbage collector.
+ */
+ STRONG,
+
+ /**
+ * Referent reclaimed in an LRU fashion when the VM runs low on memory and
+ * no strong references exist.
+ *
+ * @see java.lang.ref.SoftReference
+ */
+ SOFT,
+
+ /**
+ * Referent reclaimed when no strong or soft references exist.
+ *
+ * @see java.lang.ref.WeakReference
+ */
+ WEAK,
+
+ /**
+ * Similar to weak references except the garbage collector doesn't actually
+ * reclaim the referent. More flexible alternative to finalization.
+ *
+ * @see java.lang.ref.PhantomReference
+ */
+ PHANTOM;
+}
diff --git a/src/com/google/inject/util/Strings.java b/src/com/google/inject/util/Strings.java
new file mode 100644
index 0000000..56a674e
--- /dev/null
+++ b/src/com/google/inject/util/Strings.java
@@ -0,0 +1,55 @@
+/**
+ * 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.
+ */
+
+package com.google.inject.util;
+
+/**
+ * String utilities.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class Strings {
+
+ /**
+ * Returns a string that is equivalent to the specified string with its
+ * first character converted to uppercase as by {@link String#toUpperCase}.
+ * The returned string will have the same value as the specified string if
+ * its first character is non-alphabetic, if its first character is already
+ * uppercase, or if the specified string is of length 0.
+ *
+ * <p>For example:
+ * <pre>
+ * capitalize("foo bar").equals("Foo bar");
+ * capitalize("2b or not 2b").equals("2b or not 2b")
+ * capitalize("Foo bar").equals("Foo bar");
+ * capitalize("").equals("");
+ * </pre>
+ *
+ * @param s the string whose first character is to be uppercased
+ * @return a string equivalent to <tt>s</tt> with its first character
+ * converted to uppercase
+ * @throws NullPointerException if <tt>s</tt> is null
+ */
+ public static String capitalize(String s) {
+ if (s.length() == 0)
+ return s;
+ char first = s.charAt(0);
+ char capitalized = Character.toUpperCase(first);
+ return (first == capitalized)
+ ? s
+ : capitalized + s.substring(1);
+ }
+}