Skip to content

Instantly share code, notes, and snippets.

@Ekalips
Created April 3, 2018 10:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ekalips/e7d71bf5d41de9c703de692b0ca09c01 to your computer and use it in GitHub Desktop.
Save Ekalips/e7d71bf5d41de9c703de692b0ca09c01 to your computer and use it in GitHub Desktop.
Simple DI/ServiceLocator
package com.ekalips.p2pchat.di.modules;
public interface IDependencyProviderModule<T> {
Class<T> getProvidedClass();
T provideDependency();
/**
* Specifies is provided dependency should be re-created each time or cached can be used
*
* @return true if dependency should be re-created each time, false if cached version can be used
*/
boolean isUnique();
}
package com.ekalips.p2pchat.di;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import com.ekalips.p2pchat.di.modules.IDependencyProviderModule;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class ServiceLocator {
private static final Object lock = new Object();
@SuppressLint("StaticFieldLeak")
private static ServiceLocator instance;
private final Context context;
private final Map<Class, IDependencyProviderModule> readyProviderModules = new HashMap<>();
private final Map<Class, Class<? extends IDependencyProviderModule>> rawProviderModules = new HashMap<>();
private final Set<Class> providedWithDefault = new HashSet<>();
private final Map<Class, Object> cachedDependencies = new HashMap<>();
private ServiceLocator(Context context) {
this.context = context.getApplicationContext();
}
public static ServiceLocator init(Context context) {
synchronized (lock) {
if (instance == null) {
instance = new ServiceLocator(context);
}
return instance;
}
}
public ServiceLocator registerProviderModule(@NonNull IDependencyProviderModule module) {
readyProviderModules.put(module.getProvidedClass(), module);
return this;
}
public <T> ServiceLocator registerRawProviderModule(@NonNull Class<T> dependencyClass, @NonNull Class<? extends IDependencyProviderModule<T>> moduleClass) {
rawProviderModules.put(dependencyClass, moduleClass);
return this;
}
/**
* It'll register dependency with default module (provided dependency will be used as singleton
*/
public <T> ServiceLocator registerWithDefaultModule(@NonNull Class<T> dependencyClass) {
providedWithDefault.add(dependencyClass);
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> classInstance) {
if (classInstance.equals(Context.class)) {
return (T) context;
}
if (cachedDependencies.containsKey(classInstance)) {
return (T) cachedDependencies.get(classInstance);
}
if (readyProviderModules.containsKey(classInstance)) {
return cacheIfNeeded((IDependencyProviderModule<T>) readyProviderModules.get(classInstance));
}
if (rawProviderModules.containsKey(classInstance)) {
Class<IDependencyProviderModule<T>> rawClass = (Class<IDependencyProviderModule<T>>) rawProviderModules.get(classInstance);
try {
return cacheIfNeeded(instantiateClass(rawClass));
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException("Can't provide dependency of class: " + classInstance, e);
}
}
if (providedWithDefault.contains(classInstance)) {
try {
T item = instantiateClass(classInstance);
cachedDependencies.put(classInstance, item);
return item;
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new IllegalArgumentException("Can't provide dependency of class: " + classInstance, e);
}
}
throw new IllegalArgumentException("Can't provide dependency of class: " + classInstance);
}
@SuppressWarnings("unchecked")
private <R> R instantiateClass(Class<R> clazz) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException {
Constructor<R>[] constructors = (Constructor<R>[]) clazz.getConstructors();
if (constructors.length == 0) {
throw new IllegalArgumentException("Raw dependency has no available constructors: " + clazz);
}
Constructor<R> rawConstructor = constructors[0];
Object[] params = new Object[rawConstructor.getParameterTypes().length];
Class<?>[] parameterTypes = rawConstructor.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> constructorParam = parameterTypes[i];
if (constructorParam == clazz) {
throw new IllegalArgumentException("Found dependency cycle. Class constructor cannot require same class object: " + clazz);
}
params[i] = get(constructorParam);
}
return rawConstructor.newInstance(params);
}
private <T> T cacheIfNeeded(IDependencyProviderModule<T> module) {
T dep = module.provideDependency();
if (!module.isUnique()) {
cachedDependencies.put(module.getProvidedClass(), dep);
}
return dep;
}
public static ServiceLocator getInstance() {
if (instance == null) {
throw new IllegalStateException("You should initialize service locator first");
} else {
return instance;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment