blob: 5375154e127e1252559cd41e70fb3e33cf205516 [file] [log] [blame]
/**
* 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.internal.StackTraceElements;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* Injects constructors.
*
* @author crazybob@google.com (Bob Lee)
*/
class ConstructorInjector<T> {
final Class<T> implementation;
final InjectorImpl.SingleMemberInjector[] memberInjectors;
final InjectorImpl.SingleParameterInjector<?>[] parameterInjectors;
final ConstructionProxy<T> constructionProxy;
ConstructorInjector(InjectorImpl injector, Class<T> implementation) {
this.implementation = implementation;
Constructor<T> constructor = findConstructorIn(injector, implementation);
parameterInjectors = createParameterInjector(injector, constructor);
memberInjectors = injector.injectors.get(implementation)
.toArray(new InjectorImpl.SingleMemberInjector[0]);
constructionProxy = injector.constructionProxyFactory.get(constructor);
}
/**
* Used to create an invalid injector.
*/
private ConstructorInjector() {
implementation = null;
memberInjectors = null;
parameterInjectors = null;
constructionProxy = null;
}
InjectorImpl.SingleParameterInjector<?>[] createParameterInjector(
InjectorImpl injector, Constructor<T> constructor) {
try {
return constructor.getParameterTypes().length == 0
? null // default constructor.
: injector.getParametersInjectors(
constructor,
constructor.getParameterAnnotations(),
constructor.getGenericParameterTypes()
);
}
catch (InjectorImpl.MissingDependencyException e) {
e.handle(injector.errorHandler);
return null;
}
}
private static <T> Constructor<T> findConstructorIn(InjectorImpl injector,
Class<T> implementation) {
Constructor<T> found = null;
@SuppressWarnings("unchecked")
Constructor<T>[] constructors
= (Constructor<T>[]) implementation.getDeclaredConstructors();
for (Constructor<T> constructor : constructors) {
Inject inject = constructor.getAnnotation(Inject.class);
if (inject != null) {
if (inject.optional()) {
injector.errorHandler.handle(
StackTraceElements.forMember(constructor),
ErrorMessages.OPTIONAL_CONSTRUCTOR);
}
if (found != null) {
injector.errorHandler.handle(
StackTraceElements.forMember(found),
ErrorMessages.TOO_MANY_CONSTRUCTORS);
return InjectorImpl.invalidConstructor();
}
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) {
injector.errorHandler.handle(
StackTraceElements.forMember(
implementation.getDeclaredConstructors()[0]),
ErrorMessages.MISSING_CONSTRUCTOR,
implementation);
return InjectorImpl.invalidConstructor();
}
}
/**
* Construct an instance. Returns {@code Object} instead of {@code T} because
* it may return a proxy.
*/
Object construct(InternalContext context, Class<?> 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
= InjectorImpl.getParameters(context, parameterInjectors);
t = constructionProxy.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 (InjectorImpl.SingleMemberInjector injector : memberInjectors) {
injector.inject(context, t);
}
return t;
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
throw new ProvisionException(cause,
ErrorMessages.ERROR_INJECTING_CONSTRUCTOR);
}
finally {
constructionContext.removeCurrentReference();
}
}
/**
* Returns an invalid constructor. This enables us to keep running and
* reporting legitimate errors.
*/
static <T> ConstructorInjector<T> invalidConstructor() {
return new ConstructorInjector<T>() {
Object construct(InternalContext context, Class<?> expectedType) {
throw new UnsupportedOperationException();
}
public T get() {
throw new UnsupportedOperationException();
}
};
}
}