org.osgl.inject.Genie Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of genie Show documentation
Show all versions of genie Show documentation
A JSR330 style dependency injection solution
package org.osgl.inject;
import org.osgl.$;
import org.osgl.exception.UnexpectedException;
import org.osgl.inject.annotation.*;
import org.osgl.inject.provider.*;
import org.osgl.inject.util.AnnotationUtil;
import org.osgl.logging.LogManager;
import org.osgl.logging.Logger;
import org.osgl.util.C;
import org.osgl.util.E;
import org.osgl.util.S;
import javax.inject.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public final class Genie implements Injector {
static final Logger logger = LogManager.get(Genie.class);
private static final ThreadLocal TGT_SPEC = new ThreadLocal();
private static final ThreadLocal AFFINITY = new ThreadLocal();
private static final Provider BEAN_SPEC_PROVIDER = new Provider() {
@Override
public BeanSpec get() {
return TGT_SPEC.get();
}
};
public static class Binder {
private Class type;
private String name;
private Provider extends T> provider;
private List annotations = C.newList();
private Class extends Annotation> scope;
private boolean forceFireEvent;
private boolean fireEvent;
private Constructor extends T> constructor;
private Class extends T> impl;
public Binder(Class type) {
this.type = type;
this.fireEvent = true;
}
public Binder named(String name) {
E.illegalStateIf(null != this.name, "name has already been specified");
this.name = name;
this.fireEvent = false;
return this;
}
public Binder to(final Class extends T> impl) {
ensureNoBinding();
this.impl = $.notNull(impl);
return this;
}
public Binder to(final T instance) {
ensureNoBinding();
this.provider = new Provider() {
@Override
public T get() {
return instance;
}
};
return this;
}
public Binder to(Provider extends T> provider) {
ensureNoBinding();
this.provider = provider;
return this;
}
public Binder to(final Constructor extends T> constructor) {
ensureNoBinding();
this.constructor = constructor;
return this;
}
public Binder toConstructor(Class extends T> implement, Class> ... args) {
ensureNoBinding();
try {
return to(implement.getConstructor(args));
} catch (NoSuchMethodException e) {
throw new InjectException(e, "cannot find constructor for %s with arguments: %s", implement.getName(), $.toString2(args));
}
}
private void ensureNoBinding() {
E.illegalStateIf(bound(), "binding has already been specified");
}
/**
* Specify the bind belongs to a certain scope
* @param scope the scope annotation class
* @return this binder instance
*/
public Binder in(Class extends Annotation> scope) {
if (!scope.isAnnotationPresent(Scope.class)) {
throw new InjectException("Annotation class passed to \"in\" method must have @Scope annotation presented");
}
E.illegalStateIf(null != this.scope, "Scope has already been specified");
this.scope = scope;
this.fireEvent = false;
return this;
}
/**
* Specify the bind that should attach to bean that has been annotated with annotation(s). Usually
* the annotation specified in the parameter should be {@link Qualifier qualifiers}
*
* @param annotations an array of annotation classes
* @return this binder instance
* @see Qualifier
*/
public Binder withAnnotation(Class extends Annotation> ... annotations) {
for (Class extends Annotation> annotation : annotations) {
this.annotations.add(AnnotationUtil.createAnnotation(annotation));
}
this.fireEvent = false;
return this;
}
public Binder withAnnotation(Annotation ... annotations) {
this.annotations.addAll(C.listOf(annotations));
this.fireEvent = false;
return this;
}
public Binder forceFireEvent() {
this.forceFireEvent = true;
this.fireEvent = true;
return this;
}
public Binder doNotFireEvent() {
this.forceFireEvent = false;
this.fireEvent = false;
return this;
}
boolean bound() {
return null != provider || null != constructor;
}
public void register(Genie genie) {
if (null == provider) {
if (null != constructor) {
provider = genie.buildConstructor(constructor, BeanSpec.of(constructor.getDeclaringClass(), null, genie), new HashSet());
} else if (null != impl) {
provider = new LazyProvider<>(impl, genie);
}
}
if (!bound()) {
throw new InjectException("Cannot register without binding specified");
}
BeanSpec spec = beanSpec(genie);
genie.addIntoRegistry(spec, genie.decorate(spec, provider, true), annotations.isEmpty() && S.blank(name));
if (fireEvent || forceFireEvent) {
genie.fireProviderRegisteredEvent(type);
}
}
BeanSpec beanSpec(Genie genie) {
BeanSpec spec = BeanSpec.of(type, annotations.toArray(new Annotation[annotations.size()]), name, genie);
if (scope != null) {
spec.scope(scope);
}
return spec;
}
}
private static class WeightedProvider implements Provider, Comparable> {
private Provider realProvider;
private int affinity;
WeightedProvider(Provider provider) {
realProvider = provider;
affinity = AFFINITY.get();
}
@Override
public T get() {
return realProvider.get();
}
@Override
public int compareTo(WeightedProvider o) {
return this.affinity - o.affinity;
}
static WeightedProvider decorate(Provider provider) {
return provider instanceof WeightedProvider ? (WeightedProvider) provider : new WeightedProvider(provider);
}
}
private static class FieldInjector {
private final Field field;
private final BeanSpec fieldSpec;
private final Provider provider;
FieldInjector(Field field, BeanSpec fieldSpec, Provider provider) {
this.field = field;
this.fieldSpec = fieldSpec;
this.provider = provider;
}
void applyTo(Object bean) {
Object obj = provider.get();
if (null == obj) {
return;
}
try {
field.set(bean, obj);
} catch (Exception e) {
throw new InjectException(e, "Unable to inject field value on %s", bean.getClass());
}
}
@Override
public String toString() {
return $.fmt("Field for %s", field);
}
}
private static class MethodInjector {
private final Method method;
private final Provider[] providers;
MethodInjector(Method method, Provider[] providers) {
this.method = method;
this.providers = providers;
}
Object applyTo(Object bean) {
try {
return method.invoke(bean, params(providers));
} catch (Exception e) {
throw new InjectException(e, "Unable to invoke method[%s] on %s", method.getName(), bean.getClass());
}
}
@Override
public String toString() {
return $.fmt("MethodInjector for %s", method);
}
}
private static final Provider[] NO_PROVIDER = new Provider[0];
private ConcurrentMap> registry = new ConcurrentHashMap>();
private ConcurrentMap expressRegistry = new ConcurrentHashMap();
private Set> qualifierRegistry = new HashSet>();
private Set> injectTagRegistry = new HashSet>();
private Map, Class extends Annotation>> scopeAliases = new HashMap, Class extends Annotation>>();
private Map, ScopeCache> scopeProviders = new HashMap, ScopeCache>();
private ConcurrentMap, PostConstructProcessor>> postConstructProcessors = new ConcurrentHashMap, PostConstructProcessor>>();
private ConcurrentMap beanSpecLookup = new ConcurrentHashMap();
private ConcurrentMap genericTypedBeanLoaders = new ConcurrentHashMap();
private List listeners = new ArrayList();
private boolean supportInjectionPoint = false;
Genie(Object... modules) {
init(false, modules);
}
Genie(boolean noPlugin, Object... modules) {
init(noPlugin, modules);
}
private Genie(InjectListener listener, Object... modules) {
this.listeners.add(listener);
init(false, modules);
}
private void init(boolean noPlugin, Object... modules) {
registerBuiltInProviders();
if (!noPlugin) {
registerBuiltInPlugins();
}
if (modules.length > 0) {
List list = new ArrayList();
for (Object module : modules) {
if (module instanceof InjectListener) {
listeners.add((InjectListener) module);
} else if (module instanceof Class) {
Class moduleClass = (Class) module;
if (InjectListener.class.isAssignableFrom(moduleClass)) {
listeners.add((InjectListener) $.newInstance(moduleClass));
}
}
list.add(module);
}
// register real modules after listener get registered
for (Object module : list) {
registerModule(module);
}
}
}
public void supportInjectionPoint(boolean enabled) {
this.supportInjectionPoint = enabled;
}
public T get(Class type) {
return getProvider(type).get();
}
/**
* Check if a type has already been registered with a binding already
* @param type the class
* @return `true` if the type has already been registered to Genie with a binding
*/
public boolean hasProvider(Class> type) {
return expressRegistry.containsKey(type);
}
@Override
public Provider getProvider(Class type) {
Provider provider = expressRegistry.get(type);
if (null == provider) {
if (type.isArray()) {
provider = ArrayProvider.of(type, this);
expressRegistry.putIfAbsent(type, provider);
return (Provider) provider;
}
BeanSpec spec = beanSpecOf(type);
provider = findProvider(spec, C.empty());
expressRegistry.putIfAbsent(type, provider);
}
return (Provider) provider;
}
public T get(BeanSpec beanSpec) {
Provider provider = findProvider(beanSpec, C.empty());
return (T) provider.get();
}
public void registerProvider(Class type, Provider extends T> provider) {
registerProvider(type, provider, true);
}
private void registerProvider(Class type, Provider extends T> provider, boolean fireEvent) {
AFFINITY.set(0);
bindProviderToClass(type, provider, fireEvent);
}
public void registerQualifiers(Class extends Annotation>... qualifiers) {
this.qualifierRegistry.addAll(C.listOf(qualifiers));
}
public void registerInjectTag(Class extends Annotation>... injectTags) {
this.injectTagRegistry.addAll(C.listOf(injectTags));
}
public void registerQualifiers(Collection> qualifiers) {
this.qualifierRegistry.addAll(qualifiers);
}
public void registerScopeAlias(Class extends Annotation> scopeAnnotation, Class extends Annotation> scopeAlias) {
scopeAliases.put(scopeAlias, scopeAnnotation);
}
public void registerScopeProvider(Class extends Annotation> scopeAnnotation, ScopeCache scopeCache) {
scopeProviders.put(scopeAnnotation, scopeCache);
}
public void registerScopeProvider(Class extends Annotation> scopeAnnotation, Class extends ScopeCache> scopeCacheClass) {
scopeProviders.put(scopeAnnotation, get(scopeCacheClass));
}
public void registerPostConstructProcessor(
Class extends Annotation> annoClass,
PostConstructProcessor> processor
) {
postConstructProcessors.put(annoClass, processor);
}
public void registerGenericTypedBeanLoader(Class type, GenericTypedBeanLoader loader) {
genericTypedBeanLoaders.put(type, loader);
}
public boolean isScope(Class extends Annotation> annoClass) {
if (Singleton.class == annoClass || SessionScoped.class == annoClass || RequestScoped.class == annoClass) {
return true;
}
return scopeAliases.containsKey(annoClass) || scopeProviders.containsKey(annoClass);
}
public boolean isQualifier(Class extends Annotation> annoClass) {
return qualifierRegistry.contains(annoClass) || annoClass.isAnnotationPresent(Qualifier.class);
}
public boolean isPostConstructProcessor(Class extends Annotation> annoClass) {
return postConstructProcessors.containsKey(annoClass) || annoClass.isAnnotationPresent(PostConstructProcess.class);
}
Class extends Annotation> scopeByAlias(Class extends Annotation> alias) {
return scopeAliases.get(alias);
}
ScopeCache scopeCache(Class extends Annotation> scope) {
return scopeProviders.get(scope);
}
PostConstructProcessor postConstructProcessor(Annotation annotation) {
Class extends Annotation> annoClass = annotation.annotationType();
PostConstructProcessor processor = postConstructProcessors.get(annoClass);
if (null == processor) {
if (!annoClass.isAnnotationPresent(PostConstructProcess.class)) {
throw new UnexpectedException("Cannot find PostConstructProcessor on %s", annoClass);
}
PostConstructProcess pcp = annoClass.getAnnotation(PostConstructProcess.class);
Class extends PostConstructProcessor> cls = pcp.value();
processor = get(cls);
PostConstructProcessor p2 = postConstructProcessors.putIfAbsent(annoClass, processor);
if (null != p2) {
processor = p2;
}
}
return processor;
}
private BeanSpec beanSpecOf(Class type) {
BeanSpec spec = beanSpecLookup.get(type);
if (null == spec) {
spec = BeanSpec.of(type, this);
beanSpecLookup.putIfAbsent(type, spec);
}
return spec;
}
private void bindProviderToClass(Class> target, Provider> provider, boolean fireEvent) {
addIntoRegistry(target, provider);
AFFINITY.set(AFFINITY.get() + 1);
Class dad = target.getSuperclass();
if (null != dad && Object.class != dad) {
bindProviderToClass(dad, provider, fireEvent);
}
Class[] roles = target.getInterfaces();
if (null == roles) {
return;
}
for (Class role : roles) {
bindProviderToClass(role, provider, fireEvent);
}
if (fireEvent) {
fireProviderRegisteredEvent(target);
}
}
private void addIntoRegistry(BeanSpec spec, Provider> val, boolean addIntoExpressRegistry) {
WeightedProvider current = WeightedProvider.decorate(val);
Provider> old = registry.get(spec);
if (null == old) {
registry.put(spec, current);
if (addIntoExpressRegistry) {
expressRegistry.put(spec.rawType(), current);
}
return;
}
String newName = providerName(current.realProvider);
if (old instanceof WeightedProvider) {
WeightedProvider weightedOld = (WeightedProvider) old;
String oldName = providerName(weightedOld.realProvider);
if (weightedOld.affinity > current.affinity) {
registry.put(spec, current);
if (addIntoExpressRegistry) {
expressRegistry.put(spec.rawType(), current);
}
if (logger.isTraceEnabled()) {
logger.trace("Provider %s \n\tfor [%s] \n\tis replaced with: %s", oldName, spec, newName);
}
} else {
if (weightedOld.affinity == 0 && current.affinity == 0) {
throw new InjectException("Provider has already registered for spec: %s", spec);
} else {
if (logger.isTraceEnabled()) {
logger.trace("Provider %s \n\t for [%s] \n\t cannot be replaced with: %s", oldName, spec, newName);
}
}
}
} else {
throw E.unexpected("Provider has already registered for spec: %s", spec);
}
}
private void addIntoRegistry(Class> type, Provider> val) {
addIntoRegistry(BeanSpec.of(type, this), val, true);
}
private void registerBuiltInProviders() {
registerProvider(Collection.class, OsglListProvider.INSTANCE, false);
registerProvider(Deque.class, DequeProvider.INSTANCE, false);
registerProvider(ArrayList.class, ArrayListProvider.INSTANCE, false);
registerProvider(LinkedList.class, LinkedListProvider.INSTANCE, false);
registerProvider(C.List.class, OsglListProvider.INSTANCE, false);
registerProvider(C.Set.class, OsglSetProvider.INSTANCE, false);
registerProvider(C.Map.class, OsglMapProvider.INSTANCE, false);
registerProvider(ConcurrentMap.class, ConcurrentMapProvider.INSTANCE, false);
registerProvider(SortedMap.class, SortedMapProvider.INSTANCE, false);
registerProvider(SortedSet.class, SortedSetProvider.INSTANCE, false);
}
private void registerBuiltInPlugins() {
tryRegisterPlugin("org.osgl.inject.CDIAdaptor");
tryRegisterPlugin("org.osgl.inject.GuiceAdaptor");
}
private void tryRegisterPlugin(String pluginClass) {
try {
GeniePlugin plugin = $.newInstance(pluginClass);
plugin.register(this);
} catch (Exception e) {
logger.warn(e, "error registering plug: %s", pluginClass);
} catch (NoClassDefFoundError e) {
// plugin dependency not provided, ignore it
}
}
private void registerModule(Object module) {
boolean isClass = module instanceof Class;
Class moduleClass = isClass ? (Class) module : module.getClass();
if (Module.class.isAssignableFrom(moduleClass)) {
if (isClass) {
module = $.newInstance(moduleClass);
isClass = false;
}
((Module) module).applyTo(this);
}
for (Method method : moduleClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Provides.class)) {
method.setAccessible(true);
boolean isStatic = Modifier.isStatic(method.getModifiers());
if (isClass && !isStatic) {
module = $.newInstance(moduleClass);
isClass = false;
}
registerFactoryMethod(isStatic ? null : module, method);
}
}
}
private void registerFactoryMethod(final Object instance, final Method factory) {
Type retType = factory.getGenericReturnType();
Annotation[] factoryAnnotations = factory.getAnnotations();
final BeanSpec spec = BeanSpec.of(retType, factoryAnnotations, this);
final MethodInjector methodInjector = methodInjector(factory, C.empty());
addIntoRegistry(spec, decorate(spec, new Provider() {
@Override
public Object get() {
return methodInjector.applyTo(instance);
}
@Override
public String toString() {
return S.fmt("%s::%s", instance.getClass().getName(), methodInjector.method.getName());
}
}, true), factoryAnnotations.length == 0);
fireProviderRegisteredEvent(spec.rawType());
}
private Provider> findProvider(final BeanSpec spec, final Set chain) {
// try registry
Provider> provider = registry.get(spec);
if (null != provider) {
return provider;
}
// try without name
if (null != spec.name()) {
provider = registry.get(spec.withoutName());
if (null != provider) {
return provider;
}
}
// does it want to inject a Provider?
if (spec.isProvider() && !spec.typeParams().isEmpty()) {
provider = new Provider>() {
@Override
public Provider> get() {
return new Provider() {
private volatile Provider realProvider;
@Override
public Object get() {
if (null == realProvider) {
synchronized (this) {
if (null == realProvider) {
realProvider = findProvider(spec.toProvidee(), C.empty());
}
}
}
return realProvider.get();
}
};
}
};
registry.putIfAbsent(spec, provider);
return provider;
}
// does it require a value loading logic
if (spec.hasValueLoader()) {
provider = ValueLoaderFactory.create(spec, this);
} else {
// does it require an array
if (spec.isArray()) {
return ArrayProvider.of(spec, this);
}
// check if there is a generic typed bean loader
final GenericTypedBeanLoader loader = genericTypedBeanLoaders.get(spec.rawType());
if (null != loader) {
provider = new Provider
© 2015 - 2025 Weber Informatics LLC | Privacy Policy