package proton.inject.internal;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import javax.inject.Inject;
import javax.inject.Provider;
import android.app.Application;
import android.content.Context;
import proton.inject.Injector;
import proton.inject.ProvisionException;
import proton.inject.binding.Binding;
import proton.inject.binding.Bindings;
import proton.inject.listener.FieldListeners;
import proton.inject.listener.ProviderListeners;
import proton.inject.scope.ApplicationScoped;
import proton.inject.scope.Dependent;
import proton.inject.util.ArrayDeque;
import proton.inject.util.InjectorUtils;
import proton.inject.util.SparseClassArray;
public class InjectorImpl implements Injector {
private static final Object LOCK = new Object();
private final Context mContext;
private final InjectorImpl mApplicationInjector;
private final Bindings mBindings;
private final ProviderListeners mProviderListeners;
private final FieldListeners mFieldListeners;
private final SparseClassArray<Provider<?>> mProviders = new SparseClassArray<Provider<?>>();
private final Queue<Required> mTraversalQueue = new ArrayDeque<Required>();
private final Provider<Injector> mInjectorProvdier = new Provider<Injector>() {
@Override
public Injector get() {
return InjectorImpl.this;
}
};
public InjectorImpl(Context context, Bindings bindings, ProviderListeners providerListeners,
FieldListeners fieldListeners, InjectorImpl applicationInjector) {
mContext = context;
mBindings = bindings;
mProviderListeners = providerListeners;
mFieldListeners = fieldListeners;
mApplicationInjector = applicationInjector;
}
@Override
public ProviderListeners getProviderListeners() {
return mProviderListeners;
}
@Override
public <T> T getInstance(Class<T> key) {
return getProvider(key).get();
}
@Override
public <T> Provider<T> getProvider(Class<T> key) {
return getProvider(key, null);
}
@SuppressWarnings("unchecked")
private <T> Provider<T> getProvider(Class<T> key, Object requiredBy) {
synchronized (LOCK) {
Provider<T> provider = (Provider<T>) mProviders.get(key);
Binding<T> binding = (Binding<T>) mBindings.get(key);
if (provider == null) {
if (binding == null && InjectorUtils.isAbstract(key))
throwNoFoundBinding(key, requiredBy);
if (!isInScope(key, binding)) {
if (mApplicationInjector == null)
throwNoFoundBinding(key, requiredBy);
return mApplicationInjector.getProvider(key, requiredBy);
}
if (requiredBy != null)
throwNoFoundBinding(key, requiredBy);
addTraversal(key, "root");
pollTraversalQueue();
provider = (Provider<T>) mProviders.get(key);
if (provider == null)
throwNoFoundBinding(key, requiredBy);
}
return (Provider<T>) (provider instanceof ProviderProvider
|| (binding != null && binding.getProviderClass() != null) ? provider.get() : provider);
}
}
@Override
public <T> T inject(T obj) {
Field[] fields;
Binding<?> binding;
synchronized (LOCK) {
Class<?> clazz = obj.getClass();
fields = getFieldsAndAddTraversal(clazz);
binding = mBindings.get(clazz);
pollTraversalQueue();
}
injectFields(obj, fields, binding, obj);
return obj;
}
@Override
public Injector getApplicationInjector() {
return mApplicationInjector == null ? this : mApplicationInjector;
}
@Override
public Context getContext() {
return mContext;
}
private void pollTraversalQueue() {
Required required;
while ((required = mTraversalQueue.poll()) != null) {
if (mProviders.get(required.key) != null)
continue;
mProviders.put(required.key, createProvider(required));
}
if (mApplicationInjector != null)
mApplicationInjector.pollTraversalQueue();
}
private Provider<?> createProvider(Required required) {
Provider<?> provider = null;
if (required.key == Injector.class)
provider = mInjectorProvdier;
else if (required.binding != null && (provider = required.binding.getProvider()) != null) {
provider = new ApplicationProvider(provider, getFieldsAndAddTraversal(provider.getClass()), required.binding);
} else {
Class<?> clazz = (Class<?>) (required.binding != null ? required.binding.getToClass() : required.key);
Field[] fields = getFieldsAndAddTraversal(clazz);
Constructor<?> constructor = getConstructor(clazz, required.requiredBy);
Type[] types = constructor.getGenericParameterTypes();
for (Type type : types)
addTraversal(type, constructor);
if (getScope(clazz, required.binding) == Dependent.class)
provider = new DependentProvider(constructor, types, fields, required.binding, required.requiredBy);
else
provider = createJitProvider(constructor, types, fields, required.binding, required.requiredBy);
}
return provider;
}
private Provider<?> createJitProvider(final Constructor<?> constructor, final Type[] types, final Field[] fields,
final Binding<?> binding, final Object requiredBy) {
return new Provider<Object>() {
private volatile Object obj;
public Object get() {
if (obj == null) {
synchronized (this) {
if (obj == null) {
obj = createInstance(constructor, types, requiredBy);
injectFields(obj, fields, binding, requiredBy);
mProviderListeners.call(InjectorImpl.this, obj, getScope(obj.getClass(), binding));
}
}
}
return obj;
}
};
}
private Object createInstance(Constructor<?> constructor, Type[] types, Object requiredBy) {
Class<?>[] params = constructor.getParameterTypes();
Object[] args = new Object[params.length];
for (int i = 0; i < args.length; i++)
args[i] = getValueOrProvider(params[i], types[i], requiredBy);
return InjectorUtils.newInstance(constructor, args);
}
private void injectFields(Object receiver, Field[] fields, Binding<?> binding, Object requiredBy) {
for (Field field : fields) {
if (field.getAnnotation(Inject.class) != null) {
Object value = getValueOrProvider(field.getType(), field.getGenericType(), requiredBy);
InjectorUtils.setField(receiver, field, value);
}
mFieldListeners.call(this, receiver, getScope(receiver.getClass(), binding), field);
}
}
private Object getValueOrProvider(Class<?> clazz, Type type, Object requiredBy) {
Provider<?> provider = getProvider(InjectorUtils.toActualClass(type), requiredBy);
return Provider.class.isAssignableFrom(clazz) ? provider : provider.get();
}
private Field[] getFieldsAndAddTraversal(Class<?> clazz) {
List<Field> fieldsList = new ArrayList<Field>();
for (Class<?> c = clazz; c != Object.class; c = c.getSuperclass()) {
for (Field field : c.getDeclaredFields()) {
boolean isNoInject;
if ((isNoInject = field.getAnnotation(Inject.class) == null) && !mFieldListeners.hasListener(field))
continue;
field.setAccessible(true);
fieldsList.add(field);
if (!isNoInject)
addTraversal(field.getGenericType(), field);
}
}
return fieldsList.toArray(new Field[fieldsList.size()]);
}
private Constructor<?> getConstructor(Class<?> clazz, Object requiredBy) {
Constructor<?> constructor = null;
for (Constructor<?> c : clazz.getDeclaredConstructors()) {
if (c.getAnnotation(Inject.class) == null)
continue;
if (constructor != null)
throw new ProvisionException("Too many injectable constructors on " + clazz);
c.setAccessible(true);
constructor = c;
}
if (constructor == null) {
try {
constructor = clazz.getConstructor();
} catch (NoSuchMethodException exp) {
throw new ProvisionException(exp);
}
}
return constructor;
}
private void addTraversal(Type type, Object requiredBy) {
Class<?> clazz = InjectorUtils.toActualClass(type);
Binding<?> binding = mBindings.get(clazz);
if (binding == null && InjectorUtils.isAbstract(clazz) && clazz != Injector.class)
throwNoFoundBinding(type, requiredBy);
Required req = new Required(clazz, binding, requiredBy);
if (isInScope(clazz, binding) || (mApplicationInjector == null && clazz == Injector.class))
mTraversalQueue.add(req);
else {
if (mApplicationInjector == null)
throw new ProvisionException(requiredBy + " has illegal scope dependency");
mApplicationInjector.mTraversalQueue.add(req);
}
}
private String throwNoFoundBinding(Type type, Object requiredBy) {
throw new ProvisionException("No found binding for " + type + " required by " + requiredBy);
}
private boolean isInScope(Class<?> clazz, Binding<?> binding) {
return mContext instanceof Application ^ ApplicationScoped.class != getScope(clazz, binding);
}
private Class<? extends Annotation> getScope(Class<?> clazz, Binding<?> binding) {
Class<? extends Annotation> scope;
if (binding != null && (scope = binding.getScope()) != null)
return scope;
return InjectorUtils.getScopeAnnotation(clazz);
}
private interface ProviderProvider extends Provider<Object> {
}
private class ApplicationProvider implements ProviderProvider {
private volatile boolean isInjected;
private final Provider<?> mProvider;
private final Field[] mFields;
private final Binding<?> mBinding;
ApplicationProvider(Provider<?> provider, Field[] fields, Binding<?> binding) {
mProvider = provider;
mFields = fields;
mBinding = binding;
}
@Override
public Object get() {
if (!isInjected) {
synchronized (this) {
if (!isInjected) {
injectFields(mProvider, mFields, mBinding, mProvider);
isInjected = true;
mProviderListeners.call(InjectorImpl.this, this, getScope(getClass(), mBinding));
}
}
}
return mProvider;
}
}
private class DependentProvider implements ProviderProvider {
private final Constructor<?> mConstructor;
private final Type[] mTypes;
private final Field[] mFields;
private final Binding<?> mBinding;
private final Object mRequiredBy;
DependentProvider(Constructor<?> constructor, Type[] types, Field[] fields, Binding<?> binding,
Object requiredBy) {
mConstructor = constructor;
mTypes = types;
mFields = fields;
mBinding = binding;
mRequiredBy = requiredBy;
}
@Override
public Object get() {
return createJitProvider(mConstructor, mTypes, mFields, mBinding, mRequiredBy);
}
}
private static class Required {
private final Class<?> key;
private final Binding<?> binding;
private final Object requiredBy;
private Required(Class<?> key, Binding<?> binding, Object requiredBy) {
this.key = key;
this.binding = binding;
this.requiredBy = requiredBy;
}
}
}