/*
* Copyright 2011 JBoss, by Red Hat, 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 org.jboss.errai.ioc.rebind.ioc.injector;
import static org.jboss.errai.codegen.meta.MetaClassFactory.parameterizedAs;
import static org.jboss.errai.codegen.meta.MetaClassFactory.typeParametersOf;
import org.jboss.errai.codegen.Context;
import org.jboss.errai.codegen.DefParameters;
import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.builder.AnonymousClassStructureBuilder;
import org.jboss.errai.codegen.builder.BlockBuilder;
import org.jboss.errai.codegen.builder.impl.ObjectBuilder;
import org.jboss.errai.codegen.exception.UnproxyableClassException;
import org.jboss.errai.codegen.meta.HasAnnotations;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaConstructor;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
import org.jboss.errai.codegen.meta.MetaParameter;
import org.jboss.errai.codegen.util.PrivateAccessType;
import org.jboss.errai.codegen.util.PrivateAccessUtil;
import org.jboss.errai.codegen.util.Refs;
import org.jboss.errai.codegen.util.Stmt;
import org.jboss.errai.ioc.client.container.DestructionCallback;
import org.jboss.errai.ioc.client.container.InitializationCallback;
import org.jboss.errai.ioc.rebind.ioc.bootstrapper.IOCProcessingContext;
import org.jboss.errai.ioc.rebind.ioc.exception.InjectionFailure;
import org.jboss.errai.ioc.rebind.ioc.exception.UnsatisfiedDependenciesException;
import org.jboss.errai.ioc.rebind.ioc.injector.api.ConstructionStatusCallback;
import org.jboss.errai.ioc.rebind.ioc.injector.api.ConstructionStrategy;
import org.jboss.errai.ioc.rebind.ioc.injector.api.DecoratorTask;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectableInstance;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionContext;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionTask;
import org.jboss.errai.ioc.rebind.ioc.injector.api.TaskType;
import org.jboss.errai.ioc.rebind.ioc.injector.api.WiringElementType;
import org.jboss.errai.ioc.rebind.ioc.metadata.QualifyingMetadata;
import org.mvel2.util.ReflectionUtil;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Qualifier;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
public class InjectUtil {
private static final AtomicInteger injectorCounter = new AtomicInteger(0);
private static final AtomicInteger uniqueCounter = new AtomicInteger(0);
public static ConstructionStrategy getConstructionStrategy(final Injector injector, final InjectionContext ctx) {
final MetaClass type = injector.getInjectedType();
final List<InjectionTask> injectionTasks = new ArrayList<InjectionTask>();
final List<MetaConstructor> constructorInjectionPoints
= scanForConstructorInjectionPoints(injector, ctx, type, injectionTasks);
injectionTasks.addAll(scanForTasks(injector, ctx, type));
final List<MetaMethod> postConstructTasks = scanForPostConstruct(type);
final List<MetaMethod> preDestroyTasks = scanForPreDestroy(type);
for (final Class<? extends Annotation> a : ctx.getDecoratorAnnotationsBy(ElementType.TYPE)) {
if (type.isAnnotationPresent(a)) {
final DecoratorTask task = new DecoratorTask(injector, type, ctx.getDecorator(a));
injectionTasks.add(task);
}
}
if (!constructorInjectionPoints.isEmpty()) {
if (constructorInjectionPoints.size() > 1) {
throw new InjectionFailure("more than one constructor in "
+ type.getFullyQualifiedName() + " is marked as the injection point!");
}
final MetaConstructor constructor = constructorInjectionPoints.get(0);
return new ConstructionStrategy() {
@Override
public void generateConstructor(final ConstructionStatusCallback callback) {
final Statement[] parameterStatements
= resolveInjectionDependencies(constructor.getParameters(), ctx, constructor);
if (injector.isSingleton() && injector.isCreated()) return;
final IOCProcessingContext processingContext = ctx.getProcessingContext();
processingContext.append(
Stmt.declareFinalVariable(injector.getInstanceVarName(), type, Stmt.newObject(type, parameterStatements))
);
callback.beanConstructed();
handleInjectionTasks(ctx, injectionTasks);
doPostConstruct(ctx, injector, postConstructTasks);
doPreDestroy(ctx, injector, preDestroyTasks);
}
};
}
else {
// field injection
if (!hasDefaultConstructor(type))
throw new InjectionFailure("there is no public default constructor or suitable injection constructor for type: "
+ type.getFullyQualifiedName());
return new ConstructionStrategy() {
@Override
public void generateConstructor(final ConstructionStatusCallback callback) {
if (injector.isSingleton() && injector.isCreated()) return;
final IOCProcessingContext processingContext = ctx.getProcessingContext();
processingContext.append(
Stmt.declareVariable(type)
.asFinal()
.named(injector.getInstanceVarName())
.initializeWith(Stmt.newObject(type))
);
callback.beanConstructed();
handleInjectionTasks(ctx, injectionTasks);
doPostConstruct(ctx, injector, postConstructTasks);
doPreDestroy(ctx, injector, preDestroyTasks);
}
};
}
}
private static void handleInjectionTasks(final InjectionContext ctx,
final List<InjectionTask> tasks) {
for (final InjectionTask task : tasks) {
if (!task.doTask(ctx)) {
throw new InjectionFailure("could perform injection task: " + task);
}
}
}
/**
* Render the post construct InitializationCallback
*
* @param ctx
* -
* @param injector
* -
* @param postConstructTasks
* -
*/
private static void doPostConstruct(final InjectionContext ctx,
final Injector injector,
final List<MetaMethod> postConstructTasks) {
if (postConstructTasks.isEmpty()) return;
final MetaClass initializationCallbackType =
parameterizedAs(InitializationCallback.class, typeParametersOf(injector.getInjectedType()));
final BlockBuilder<AnonymousClassStructureBuilder> initMeth
= ObjectBuilder.newInstanceOf(initializationCallbackType).extend()
.publicOverridesMethod("init", Parameter.of(injector.getInjectedType(), "obj", true));
final String varName = "init_".concat(injector.getInstanceVarName());
injector.setPostInitCallbackVar(varName);
renderLifeCycleEvents(PostConstruct.class, injector, ctx, initMeth, postConstructTasks);
final AnonymousClassStructureBuilder classStructureBuilder = initMeth.finish();
final IOCProcessingContext pc = ctx.getProcessingContext();
pc.getBootstrapBuilder()
.privateField(varName, initializationCallbackType)
.initializesWith(classStructureBuilder.finish()).finish();
pc.append(Stmt.loadVariable("context").invoke("addInitializationCallback",
Refs.get(injector.getInstanceVarName()), Refs.get(varName)));
}
/**
* Render the pre destroy DestructionCallback
*
* @param ctx
* -
* @param injector
* -
* @param preDestroyTasks
* -
*/
private static void doPreDestroy(final InjectionContext ctx,
final Injector injector,
final List<MetaMethod> preDestroyTasks) {
if (preDestroyTasks.isEmpty()) return;
final MetaClass destructionCallbackType =
parameterizedAs(DestructionCallback.class, typeParametersOf(injector.getInjectedType()));
final BlockBuilder<AnonymousClassStructureBuilder> initMeth
= ObjectBuilder.newInstanceOf(destructionCallbackType).extend()
.publicOverridesMethod("destroy", Parameter.of(injector.getInjectedType(), "obj", true));
final String varName = "destroy_".concat(injector.getInstanceVarName());
injector.setPreDestroyCallbackVar(varName);
renderLifeCycleEvents(PreDestroy.class, injector, ctx, initMeth, preDestroyTasks);
final IOCProcessingContext pc = ctx.getProcessingContext();
pc.getBootstrapBuilder().privateField(varName, destructionCallbackType)
.initializesWith(initMeth.finish().finish()).finish();
pc.append(Stmt.loadVariable("context").invoke("addDestructionCallback",
Refs.get(injector.getInstanceVarName()), Refs.get(varName)));
}
private static void renderLifeCycleEvents(final Class<? extends Annotation> type,
final Injector injector,
final InjectionContext ctx,
final BlockBuilder<?> body,
final List<MetaMethod> methods) {
for (final MetaMethod meth : methods) {
renderLifeCycleMethodCall(type, injector, ctx, body, meth);
}
}
private static void renderLifeCycleMethodCall(final Class<? extends Annotation> type,
final Injector injector,
final InjectionContext ctx,
final BlockBuilder<?> body,
final MetaMethod meth) {
if (meth.getParameters().length != 0) {
throw new InjectionFailure(type.getCanonicalName() + " method must contain no parameters: "
+ injector.getInjectedType().getFullyQualifiedName() + "." + meth.getName());
}
if (!meth.isPublic()) {
ctx.addExposedMethod(meth);
}
if (!meth.isPublic()) {
body.append(Stmt.invokeStatic(ctx.getProcessingContext().getBootstrapClass(),
PrivateAccessUtil.getPrivateMethodName(meth), Refs.get("obj")));
}
else {
body.append(Stmt.loadVariable("obj").invoke(meth.getName()));
}
}
private static List<InjectionTask> scanForTasks(final Injector injector,
final InjectionContext ctx,
final MetaClass type) {
final List<InjectionTask> accumulator = new ArrayList<InjectionTask>();
final Set<Class<? extends Annotation>> decorators = ctx.getDecoratorAnnotations();
for (final Class<? extends Annotation> decorator : decorators) {
if (type.isAnnotationPresent(decorator)) {
accumulator.add(new InjectionTask(injector, type));
}
}
MetaClass visit = type;
do {
for (final MetaField field : visit.getDeclaredFields()) {
if (isInjectionPoint(ctx, field)) {
if (!field.isPublic()) {
final MetaMethod meth = visit.getMethod(ReflectionUtil.getSetter(field.getName()),
field.getType());
if (meth == null) {
final InjectionTask task = new InjectionTask(injector, field);
accumulator.add(task);
}
else {
final InjectionTask task = new InjectionTask(injector, meth);
accumulator.add(task);
}
}
else {
accumulator.add(new InjectionTask(injector, field));
}
}
ElementType[] elTypes;
for (final Class<? extends Annotation> a : decorators) {
elTypes = a.isAnnotationPresent(Target.class) ? a.getAnnotation(Target.class).value()
: new ElementType[]{ElementType.FIELD};
for (final ElementType elType : elTypes) {
switch (elType) {
case FIELD:
if (field.isAnnotationPresent(a)) {
accumulator.add(new DecoratorTask(injector, field, ctx.getDecorator(a)));
}
break;
}
}
}
}
for (final MetaMethod meth : visit.getDeclaredMethods()) {
if (isInjectionPoint(ctx, meth)) {
accumulator.add(new InjectionTask(injector, meth));
}
for (final Class<? extends Annotation> a : decorators) {
final ElementType[] elTypes = a.isAnnotationPresent(Target.class) ? a.getAnnotation(Target.class).value()
: new ElementType[]{ElementType.FIELD};
for (final ElementType elType : elTypes) {
switch (elType) {
case METHOD:
if (meth.isAnnotationPresent(a)) {
accumulator.add(new DecoratorTask(injector, meth, ctx.getDecorator(a)));
}
break;
case PARAMETER:
for (final MetaParameter parameter : meth.getParameters()) {
if (parameter.isAnnotationPresent(a)) {
final DecoratorTask task = new DecoratorTask(injector, parameter, ctx.getDecorator(a));
accumulator.add(task);
}
}
}
}
}
}
}
while ((visit = visit.getSuperClass()) != null);
return accumulator;
}
private static List<MetaConstructor> scanForConstructorInjectionPoints(final Injector injector,
final InjectionContext ctx,
final MetaClass type,
final List<InjectionTask> tasks) {
final List<MetaConstructor> accumulator = new ArrayList<MetaConstructor>();
final Set<Class<? extends Annotation>> decorators = ctx.getDecoratorAnnotations();
for (final MetaConstructor cns : type.getConstructors()) {
if (isInjectionPoint(ctx, cns)) {
accumulator.add(cns);
}
ElementType[] elTypes;
for (final Class<? extends Annotation> a : decorators) {
elTypes = a.isAnnotationPresent(Target.class) ? a.getAnnotation(Target.class).value()
: new ElementType[]{ElementType.FIELD};
for (final ElementType elType : elTypes) {
switch (elType) {
case CONSTRUCTOR:
if (cns.isAnnotationPresent(a)) {
tasks.add(new DecoratorTask(injector, cns, ctx.getDecorator(a)));
}
break;
case PARAMETER:
for (final MetaParameter parameter : cns.getParameters()) {
if (parameter.isAnnotationPresent(a)) {
final DecoratorTask task = new DecoratorTask(injector, parameter, ctx.getDecorator(a));
tasks.add(task);
}
}
}
}
}
}
return accumulator;
}
private static List<MetaMethod> scanForPostConstruct(final MetaClass type) {
return scanForAnnotatedMethod(type, PostConstruct.class);
}
private static List<MetaMethod> scanForPreDestroy(final MetaClass type) {
return scanForAnnotatedMethod(type, PreDestroy.class);
}
public static List<MetaMethod> scanForAnnotatedMethod(final MetaClass type,
final Class<? extends Annotation> annotationType) {
final List<MetaMethod> accumulator = new ArrayList<MetaMethod>();
MetaClass clazz = type;
do {
for (final MetaMethod meth : clazz.getDeclaredMethods()) {
if (meth.isAnnotationPresent(annotationType)) {
accumulator.add(meth);
}
}
}
while ((clazz = clazz.getSuperClass()) != null);
Collections.reverse(accumulator);
return accumulator;
}
@SuppressWarnings({"unchecked"})
private static boolean isInjectionPoint(final InjectionContext context, final HasAnnotations hasAnnotations) {
return context.isElementType(WiringElementType.InjectionPoint, hasAnnotations);
}
private static boolean hasDefaultConstructor(final MetaClass type) {
return type.getConstructor(new MetaClass[0]) != null;
}
private static MetaClass[] parametersToClassTypeArray(final MetaParameter[] parms) {
final MetaClass[] newArray = new MetaClass[parms.length];
for (int i = 0; i < parms.length; i++) {
newArray[i] = parms[i].getType();
}
return newArray;
}
public static Statement getInjectorOrProxy(final InjectionContext ctx,
final InjectableInstance injectableInstance,
final MetaClass clazz,
final QualifyingMetadata qualifyingMetadata) {
return getInjectorOrProxy(ctx, injectableInstance, clazz, qualifyingMetadata, false);
}
public static Statement getInjectorOrProxy(final InjectionContext ctx,
final InjectableInstance injectableInstance,
final MetaClass clazz,
final QualifyingMetadata qualifyingMetadata,
final boolean alwaysProxyDependent) {
if (ctx.isInjectableQualified(clazz, qualifyingMetadata)) {
final Injector inj = ctx.getQualifiedInjector(clazz, qualifyingMetadata);
/**
* Special handling for cycles. If two beans directly depend on each other, we shimmy in a call to the
* binding reference to check the context for the instance to avoid a hanging duplicate reference. It is to
* ensure only one instance of each bean is created.
*/
if (ctx.cycles(injectableInstance.getEnclosingType(), clazz) && inj instanceof TypeInjector) {
return Stmt.loadVariable("context").invoke("getInstanceOrNew",
Refs.get(inj.getCreationalCallbackVarName()),
inj.getInjectedType(), inj.getQualifyingMetadata().getQualifiers());
}
return ctx.getQualifiedInjector(clazz, qualifyingMetadata).getBeanInstance(injectableInstance);
}
else {
//todo: refactor the InjectionContext to provide a cleaner API for interface delegates
// try to inject it
try {
if (ctx.isInjectorRegistered(clazz, qualifyingMetadata)) {
final Injector inj = ctx.getQualifiedInjector(clazz, qualifyingMetadata);
if (inj.isProvider()) {
if (inj.isStatic()) {
return inj.getBeanInstance(injectableInstance);
}
/**
* Inform the caller that we are in a proxy and that the operation they're doing must
* necessarily be done within the ProxyResolver resolve operation since this provider operation
* relies on a bean which is not yet available.
*/
ctx.recordCycle(inj.getEnclosingType(), injectableInstance.getEnclosingType());
final ProxyInjector proxyInject = getOrCreateProxy(ctx, inj.getEnclosingType(), qualifyingMetadata);
boolean pushedProxy = false;
try {
if (injectableInstance.getTaskType() == TaskType.Parameter
&& injectableInstance.getConstructor() != null) {
// eek! a producer element is produced by this bean and injected into it's own constructor!
final ProxyInjector producedElementProxy
= getOrCreateProxy(ctx, inj.getInjectedType(), qualifyingMetadata);
proxyInject.addProxyCloseStatement(Stmt.loadVariable("context")
.invoke("addBean", Stmt.load(inj.getInjectedType()),
qualifyingMetadata.getQualifiers(), inj.getBeanInstance(injectableInstance)));
proxyInject.getBeanInstance(injectableInstance);
return producedElementProxy.getBeanInstance(injectableInstance);
}
else {
ctx.getProcessingContext().pushBlockBuilder(proxyInject.getProxyResolverBlockBuilder());
pushedProxy = true;
ctx.markOpenProxy();
return proxyInject.getBeanInstance(injectableInstance);
}
}
finally {
if (!ctx.isProxyOpen() && pushedProxy) {
ctx.getProcessingContext().popBlockBuilder();
}
}
}
else if (inj.isSoftDisabled()
|| (inj.isDependent() &&
(!alwaysProxyDependent || !ctx.typeContainsGraphCycles(inj.getInjectedType())))) {
inj.setEnabled(true);
if (inj.isCreated() && !inj.isRendered()) {
throw new InjectionFailure("unresolveable cycle on dependent scoped bean: "
+ inj.getInjectedType().getFullyQualifiedName() + "; does the bean intersect with a normal scope?");
}
return inj.getBeanInstance(injectableInstance);
}
}
}
catch (InjectionFailure e) {
e.printStackTrace();
}
if (!ctx.isTypeInjectable(clazz)) {
ctx.recordCycle(clazz, injectableInstance.getEnclosingType());
return getOrCreateProxy(ctx, clazz, qualifyingMetadata).getBeanInstance(injectableInstance);
}
else {
throw new InjectionFailure("cannot resolve injectable bean for type: " + clazz.getFullyQualifiedName()
+ "; qualified by: " + qualifyingMetadata.toString());
}
}
}
public static ProxyInjector getOrCreateProxy(final InjectionContext ctx,
final MetaClass clazz,
final QualifyingMetadata qualifyingMetadata) {
final ProxyInjector proxyInjector;
if (ctx.isProxiedInjectorRegistered(clazz, qualifyingMetadata)) {
proxyInjector = (ProxyInjector)
ctx.getProxiedInjector(clazz, qualifyingMetadata);
}
else {
proxyInjector = new ProxyInjector(ctx.getProcessingContext(), clazz, qualifyingMetadata);
ctx.addProxiedInjector(proxyInjector);
}
return proxyInjector;
}
public static Statement[] resolveInjectionDependencies(final MetaParameter[] parms,
final InjectionContext ctx,
final MetaMethod method) {
return resolveInjectionDependencies(parms, ctx, method, true);
}
public static Statement[] resolveInjectionDependencies(final MetaParameter[] parms,
final InjectionContext ctx,
final MetaMethod method,
final boolean inlineReference) {
final MetaClass[] parmTypes = parametersToClassTypeArray(parms);
final Statement[] parmValues = new Statement[parmTypes.length];
for (int i = 0; i < parmTypes.length; i++) {
Statement stmt;
try {
final InjectableInstance injectableInstance = InjectableInstance.getParameterInjectedInstance(
parms[i],
null,
ctx);
stmt = getInjectorOrProxy(ctx, injectableInstance, parmTypes[i],
ctx.getProcessingContext().getQualifyingMetadataFactory().createFrom(parms[i].getAnnotations()));
if (inlineReference) {
stmt = recordInlineReference(stmt, ctx, parms[i]);
}
}
catch (InjectionFailure e) {
e.setTarget(method.getDeclaringClass() + "." + method.getName() + DefParameters.from(method)
.generate(Context.create()));
throw e;
}
parmValues[i] = stmt;
}
return parmValues;
}
public static Statement[] resolveInjectionDependencies(final MetaParameter[] parms,
final InjectionContext ctx,
final MetaConstructor constructor) {
final MetaClass[] parmTypes = parametersToClassTypeArray(parms);
final Statement[] parmValues = new Statement[parmTypes.length];
for (int i = 0; i < parmTypes.length; i++
) {
final Statement stmt;
try {
stmt = recordInlineReference(
getInjectorOrProxy(
ctx,
InjectableInstance.getParameterInjectedInstance(parms[i], null, ctx),
parmTypes[i],
ctx.getProcessingContext().getQualifyingMetadataFactory().createFrom(
parms[i].getAnnotations()
),
true
),
ctx,
parms[i]
);
ctx.closeProxyIfOpen();
}
catch (UnproxyableClassException e) {
final String err = "your object graph has cyclical dependencies and the cycle could not be proxied. " +
"use of the @Dependent scope and @New qualifier may not " +
"produce properly initalized objects for: " + parmTypes[i].getFullyQualifiedName() + "\n" +
"\t Offending node: " + constructor.getDeclaringClass().getFullyQualifiedName() + "\n" +
"\t Note : this issue can be resolved by making "
+ e.getUnproxyableClass() + " proxyable. Introduce a default no-arg constructor and make sure the " +
"class is non-final.";
throw UnsatisfiedDependenciesException.createWithSingleParameterFailure(parms[i], constructor.getDeclaringClass(),
parms[i].getType(), err);
}
catch (InjectionFailure e) {
e.setTarget(constructor.getDeclaringClass() + "." + DefParameters.from(constructor)
.generate(Context.create()));
throw e;
}
parmValues[i] = stmt;
}
return parmValues;
}
private static Statement recordInlineReference(final Statement beanCreationStmt,
final InjectionContext ctx,
final MetaParameter parm) {
final String varName = InjectUtil.getUniqueVarName();
ctx.getProcessingContext()
.append(Stmt.declareFinalVariable(varName, parm.getType(), beanCreationStmt));
final Statement stmt = Refs.get(varName);
ctx.addInlineBeanReference(parm, stmt);
return stmt;
}
public static String getNewInjectorName() {
return "inj".concat(String.valueOf(injectorCounter.addAndGet(1)));
}
public static String getUniqueVarName() {
return "var".concat(String.valueOf(uniqueCounter.addAndGet(1)));
}
public static List<Annotation> extractQualifiers(final InjectableInstance<? extends Annotation> injectableInstance) {
switch (injectableInstance.getTaskType()) {
case Field:
return getQualifiersFromAnnotations(injectableInstance.getField().getAnnotations());
case Method:
return getQualifiersFromAnnotations(injectableInstance.getMethod().getAnnotations());
case Parameter:
return getQualifiersFromAnnotations(injectableInstance.getParm().getAnnotations());
case Type:
return getQualifiersFromAnnotations(injectableInstance.getType().getAnnotations());
default:
return Collections.emptyList();
}
}
public static List<Annotation> getQualifiersFromAnnotations(final Annotation[] annotations) {
final List<Annotation> qualifiers = new ArrayList<Annotation>();
for (final Annotation a : annotations) {
if (a.annotationType().isAnnotationPresent(Qualifier.class)) {
qualifiers.add(a);
}
}
return Collections.unmodifiableList(qualifiers);
}
public static Annotation[] getQualifiersFromAnnotationsAsArray(final Annotation[] annotations) {
final List<Annotation> qualifiers = getQualifiersFromAnnotations(annotations);
return qualifiers.toArray(new Annotation[qualifiers.size()]);
}
public static interface BeanMetric {
public MetaConstructor getInjectorConstructor();
public Collection<MetaField> getFieldInjectors();
public Collection<MetaMethod> getMethodInjectors();
}
public static BeanMetric analyzeBean(final InjectionContext context, final MetaClass clazz) {
return new BeanMetric() {
@Override
public MetaConstructor getInjectorConstructor() {
for (final MetaConstructor constructor : clazz.getDeclaredConstructors()) {
if (isInjectionPoint(context, constructor)) {
return constructor;
}
}
return null;
}
@Override
public Collection<MetaField> getFieldInjectors() {
final Collection<MetaField> fields = new ArrayList<MetaField>();
MetaClass toScan = clazz;
do {
for (final MetaField field : toScan.getDeclaredFields()) {
if (isInjectionPoint(context, field)) {
fields.add(field);
}
}
}
while ((toScan = toScan.getSuperClass()) != null);
return fields;
}
@Override
public Collection<MetaMethod> getMethodInjectors() {
final Collection<MetaMethod> methods = new ArrayList<MetaMethod>();
MetaClass toScan = clazz;
do {
for (final MetaMethod method : toScan.getDeclaredMethods()) {
if (isInjectionPoint(context, method)) {
methods.add(method);
}
}
}
while ((toScan = toScan.getSuperClass()) != null);
return methods;
}
};
}
private static final String BEAN_INJECTOR_STORE = "InjectorBeanManagerStore";
/**
* A utility to get or create the store whereby the code that binds beans to the client
* bean manager can keep track of what it has already bound.
*
* @return -
*/
public static Set<Injector> getBeanInjectionTrackStore(final InjectionContext context) {
@SuppressWarnings("unchecked") Set<Injector> store = (Set<Injector>) context.getAttribute(BEAN_INJECTOR_STORE);
if (store == null) {
context.setAttribute(BEAN_INJECTOR_STORE, store = new HashSet<Injector>());
}
return store;
}
public static boolean checkIfTypeNeedsAddingToBeanStore(final InjectionContext context,
final Injector injector) {
final Set<Injector> store = getBeanInjectionTrackStore(context);
if (store.contains(injector)) {
return false;
}
store.add(injector);
return true;
}
/**
* Retrieves the value of a private field managed IOC component.
*
* @param processingContext
* an instance of the {@link IOCProcessingContext}
* @param obj
* a {@link Statement} reference to the bean instance whose field is to be accessed.
* <tt>null</tt> can be provided for static field access.
* @param field
* the {@link MetaField} which will be privately accessed
*
* @return a {@link Statement} reference to the value of the field.
*/
public static Statement getPrivateFieldValue(final IOCProcessingContext processingContext,
final Statement obj,
final MetaField field) {
if (field.isStatic()) {
return Stmt.invokeStatic(processingContext.getBootstrapClass(),
PrivateAccessUtil.getPrivateFieldInjectorName(field));
}
else {
return Stmt.invokeStatic(processingContext.getBootstrapClass(),
PrivateAccessUtil.getPrivateFieldInjectorName(field), obj);
}
}
/**
* Set the value of a private field on a managed IOC component.
*
* @param processingContext
* an instance of the {@link IOCProcessingContext}
* @param obj
* a {@link Statement} reference to the bean instance whose field is to be accessed.
* <tt>null</tt> can be provided for static field access.
* @param field
* the {@link MetaField} which will be privately accessed
* @param val
* the {@link Statement} reference to the value to be set.
*
* @return the {@link Statement} which will perform the writing to the field.
*/
public static Statement setPrivateFieldValue(final IOCProcessingContext processingContext,
final Statement obj,
final MetaField field,
final Statement val) {
if (field.isStatic()) {
return Stmt.invokeStatic(processingContext.getBootstrapClass(),
PrivateAccessUtil.getPrivateFieldInjectorName(field), obj);
}
else {
return Stmt.invokeStatic(processingContext.getBootstrapClass(),
PrivateAccessUtil.getPrivateFieldInjectorName(field), obj, val);
}
}
/**
* Invokes a private method on a managed IOC component.
*
* @param processingContext
* an instance of the {@link IOCProcessingContext}
* @param obj
* a {@link Statement} reference to the bean instance whose field is to be accessed.
* <tt>null</tt> can be provided for static method calls.
* @param method
* the {@link MetaMethod} to be invoked
* @param arguments
* the arguments to be passed to the private method
*
* @return the {@link Statement} which represents the return value of the method.
*/
public static Statement invokePrivateMethod(final IOCProcessingContext processingContext,
final Statement obj,
final MetaMethod method,
final Statement... arguments) {
final Statement[] args;
if (method.isStatic()) {
args = new Statement[arguments.length];
System.arraycopy(arguments, 0, args, 0, arguments.length);
}
else {
args = new Statement[arguments.length + 1];
args[0] = obj;
System.arraycopy(arguments, 0, args, 1, arguments.length);
}
return Stmt.invokeStatic(processingContext.getBootstrapClass(),
PrivateAccessUtil.getPrivateMethodName(method), args);
}
/**
* Read from the specified field, and automatically determine whether to make a public or private read based on the
* visibility of the specified field.
*
* @param context
* an instance of the {@link InjectionContext}
* @param obj
* a {@link Statement} reference to the bean instance whose field is to be accessed
* @param field
* the {@link MetaField} which will be privately accessed
*
* @return a {@link Statement} reference to the value of the field.
*/
public static Statement getPublicOrPrivateFieldValue(final InjectionContext context,
final Statement obj,
final MetaField field) {
if (!field.isPublic()) {
context.addExposedField(field, PrivateAccessType.Read);
return getPrivateFieldValue(context.getProcessingContext(), obj, field);
}
else {
return Stmt.nestedCall(obj).loadField(field);
}
}
/**
* Write to the specified field, and automatically determine whether to make a public or private write based on the
* visibility of the specified field.
*
* @param context
* an instance of the {@link InjectionContext}
* @param obj
* a {@link Statement} reference to the bean instance whose field is to be accessed
* @param field
* the {@link MetaField} which will be privately accessed
* @param val
* the {@link Statement} reference to the value to be set.
*
* @return the {@link Statement} which will perform the writing to the field.
*/
public static Statement setPublicOrPrivateFieldValue(final InjectionContext context,
final Statement obj,
final MetaField field,
final Statement val) {
if (!field.isPublic()) {
context.addExposedField(field, PrivateAccessType.Write);
return setPrivateFieldValue(context.getProcessingContext(), obj, field, val);
}
else {
return Stmt.nestedCall(obj).loadField(field).assignValue(val);
}
}
/**
* Invoke the specified method, and automatically determine whether to make the invocation public or private based
* on the visibility of the specified method.
*
* @param context
* an instance of the {@link InjectionContext}
* @param obj
* a {@link Statement} reference to the bean instance whose field is to be accessed
* @param method
* the {@link MetaMethod} to be invoked
* @param arguments
* the arguments to be passed to the private method
*
* @return the {@link Statement} which represents the return value of the method.
*/
public static Statement invokePublicOrPrivateMethod(final InjectionContext context,
final Statement obj,
final MetaMethod method,
final Statement... arguments) {
if (!method.isPublic()) {
context.addExposedMethod(method);
return invokePrivateMethod(context.getProcessingContext(), obj, method, arguments);
}
else {
return Stmt.nestedCall(obj).invoke(method, arguments);
}
}
}