Skip to content

Commit

Permalink
Merge pull request #42 from mkouba/issue-38
Browse files Browse the repository at this point in the history
Arc - support initializer injection on superclasses
  • Loading branch information
stuartwdouglas authored Oct 2, 2018
2 parents a34ea3f + c06c2d1 commit fd36f15
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 94 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package org.jboss.protean.arc.processor;

import java.lang.reflect.Modifier;

import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;

abstract class AbstractGenerator {

protected String providerName(String name) {
Expand All @@ -11,4 +16,31 @@ protected String getBaseName(BeanInfo bean, String beanClassName) {
String name = Types.getSimpleName(beanClassName);
return name.substring(0, name.indexOf(BeanGenerator.BEAN_SUFFIX));
}

protected boolean isReflectionFallbackNeeded(MethodInfo method, String targetPackage) {
// Reflection fallback is needed for private methods and non-public methods declared on superclasses located in a different package
if (Modifier.isPrivate(method.flags())) {
return true;
}
if (Modifier.isProtected(method.flags()) || isPackagePrivate(method.flags())) {
return !DotNames.packageName(method.declaringClass().name()).equals(targetPackage);
}
return false;
}

protected boolean isReflectionFallbackNeeded(FieldInfo field, String targetPackage) {
// Reflection fallback is needed for private fields and non-public fields declared on superclasses located in a different package
if (Modifier.isPrivate(field.flags())) {
return true;
}
if (Modifier.isProtected(field.flags()) || isPackagePrivate(field.flags())) {
return !DotNames.packageName(field.declaringClass().name()).equals(targetPackage);
}
return false;
}

protected boolean isPackagePrivate(int mod) {
return !(Modifier.isPrivate(mod) || Modifier.isProtected(mod) || Modifier.isPublic(mod));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,19 @@ private List<BeanInfo> findBeans(List<DotName> beanDefiningAnnotations, List<Obs

for (MethodInfo method : beanClass.methods()) {
if (method.hasAnnotation(DotNames.PRODUCES)) {
// Producers are not inherited
producerMethods.add(method);
} else if (method.hasAnnotation(DotNames.DISPOSES)) {
// Disposers are not inherited
disposerMethods.add(method);
} else if (method.hasAnnotation(DotNames.OBSERVES)) {
// TODO observers are inherited
observerMethods.add(method);
}
}
for (FieldInfo field : beanClass.fields()) {
if (field.annotations().stream().anyMatch(a -> a.name().equals(DotNames.PRODUCES))) {
// Producer fields are not inherited
producerFields.add(field);
}
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ Collection<Resource> generate(BeanInfo bean, String beanClassName, ReflectionReg
ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name());
String providerTypeName = providerClass.name().toString();
String baseName = getBaseName(bean, beanClassName);
String generatedName = getProxyPackageName(bean).replace(".", "/") + "/" + baseName + CLIENT_PROXY_SUFFIX;
String targetPackage = getProxyPackageName(bean);
String generatedName = targetPackage.replace(".", "/") + "/" + baseName + CLIENT_PROXY_SUFFIX;

// Foo_ClientProxy extends Foo implements ClientProxy
List<String> interfaces = new ArrayList<>();
Expand All @@ -78,8 +79,8 @@ Collection<Resource> generate(BeanInfo bean, String beanClassName, ReflectionReg
FieldCreator beanField = clientProxy.getFieldCreator("bean", DescriptorUtils.extToInt(beanClassName)).setModifiers(ACC_PRIVATE | ACC_FINAL);

createConstructor(clientProxy, beanClassName, superClass, beanField.getFieldDescriptor());
createDelegate(clientProxy, providerTypeName, beanField.getFieldDescriptor());
createGetContextualInstance(clientProxy, providerTypeName);
implementDelegate(clientProxy, providerTypeName, beanField.getFieldDescriptor());
implementGetContextualInstance(clientProxy, providerTypeName);

for (MethodInfo method : getDelegatingMethods(bean)) {

Expand All @@ -101,9 +102,8 @@ Collection<Resource> generate(BeanInfo bean, String beanClassName, ReflectionReg

if (isInterface) {
ret = forward.invokeInterfaceMethod(method, delegate, params);
} else if (Modifier.isPrivate(method.flags()) ||
(Modifier.isProtected(method.flags()) && !getPackage(method.declaringClass().name().toString()).equals(getPackage(generatedName)))) {
// Reflection fallback for private methods
} else if (isReflectionFallbackNeeded(method, targetPackage)) {
// Reflection fallback
ResultHandle paramTypesArray = forward.newArray(Class.class, forward.load(method.parameters().size()));
int idx = 0;
for (Type param : method.parameters()) {
Expand All @@ -128,22 +128,14 @@ Collection<Resource> generate(BeanInfo bean, String beanClassName, ReflectionReg
return classOutput.getResources();
}

private String getPackage(String name) {
int index = name.lastIndexOf('.');
if(index == -1) {
return "";
}
return name.substring(0, index);
}

void createConstructor(ClassCreator clientProxy, String beanClassName, String superClasName, FieldDescriptor beanField) {
MethodCreator creator = clientProxy.getMethodCreator("<init>", void.class, beanClassName);
creator.invokeSpecialMethod(MethodDescriptor.ofConstructor(superClasName), creator.getThis());
creator.writeInstanceField(beanField, creator.getThis(), creator.getMethodParam(0));
creator.returnValue(null);
}

void createDelegate(ClassCreator clientProxy, String providerTypeName, FieldDescriptor beanField) {
void implementDelegate(ClassCreator clientProxy, String providerTypeName, FieldDescriptor beanField) {
// Arc.container().getContext(bean.getScope()).get(bean, new CreationalContextImpl<>());
MethodCreator creator = clientProxy.getMethodCreator("delegate", providerTypeName).setModifiers(Modifier.PRIVATE);
// Arc.container()
Expand All @@ -161,7 +153,7 @@ void createDelegate(ClassCreator clientProxy, String providerTypeName, FieldDesc
creator.returnValue(result);
}

void createGetContextualInstance(ClassCreator clientProxy, String providerTypeName) {
void implementGetContextualInstance(ClassCreator clientProxy, String providerTypeName) {
MethodCreator creator = clientProxy.getMethodCreator("getContextualInstance", Object.class).setModifiers(Modifier.PUBLIC);
creator.returnValue(
creator.invokeVirtualMethod(MethodDescriptor.ofMethod(clientProxy.getClassName(), "delegate", providerTypeName), creator.getThis()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ static String simpleName(DotName dotName) {

static String packageName(DotName dotName) {
String name = dotName.toString();
return name.contains(".") ? name.substring(0, name.lastIndexOf(".")) : "";
int index = name.lastIndexOf('.');
if(index == -1) {
return "";
}
return name.substring(0, index);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,26 @@
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationTarget.Kind;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.logging.Logger;

/**
* Injection abstraction - an injected field, a bean constructor, an initializer or a disposer method.
* Injection abstraction, basically a collection of injection points plus the annotation target:
* <ul>
* <li>an injected field,</li>
* <li>a bean constructor,</li>
* <li>an initializer method,</li>
* <li>a producer method,</li>
* <li>a disposer method,</li>
* <li>an observer method.</li>
* </ul>
*
* @author Martin Kouba
*/
public class Injection {

private static final Logger LOGGER = Logger.getLogger(Injection.class);

private static final DotName JAVA_LANG_OBJECT = DotName.createSimple(Object.class.getName());
/**
*
* @param beanTarget
Expand Down Expand Up @@ -50,8 +56,8 @@ private static void forClassBean(ClassInfo beanTarget, BeanDeployment beanDeploy
AnnotationTarget injectTarget = injectAnnotation.target();
switch (injectAnnotation.target().kind()) {
case FIELD:
injections.add(new Injection(injectTarget,
Collections.singletonList(InjectionPointInfo.fromField(injectTarget.asField(), beanDeployment))));
injections.add(
new Injection(injectTarget, Collections.singletonList(InjectionPointInfo.fromField(injectTarget.asField(), beanDeployment))));
break;
case METHOD:
injections.add(new Injection(injectTarget, InjectionPointInfo.fromMethod(injectTarget.asMethod(), beanDeployment)));
Expand All @@ -62,9 +68,9 @@ private static void forClassBean(ClassInfo beanTarget, BeanDeployment beanDeploy
}
}
}
if(!beanTarget.superName().equals(JAVA_LANG_OBJECT)) {
if (!beanTarget.superName().equals(DotNames.OBJECT)) {
ClassInfo info = beanDeployment.getIndex().getClassByName(beanTarget.superName());
if(info != null) {
if (info != null) {
forClassBean(info, beanDeployment, injections);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.jboss.jandex.Type;

/**
* Represents an injection point.
*
* @author Martin Kouba
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ Collection<Resource> generate(InterceptorInfo interceptor, AnnotationLiteralProc
}
ClassInfo providerClass = interceptor.getDeployment().getIndex().getClassByName(providerType.name());
String providerTypeName = providerClass.name().toString();
String generatedName = DotNames.packageName(providerType.name()).replace(".", "/") + "/" + baseName + BEAN_SUFFIX;
String targetPackage = DotNames.packageName(providerType.name());
String generatedName = targetPackage.replace(".", "/") + "/" + baseName + BEAN_SUFFIX;

ResourceClassOutput classOutput = new ResourceClassOutput(name -> name.equals(generatedName) ? SpecialType.INTERCEPTOR_BEAN : null);

Expand All @@ -84,17 +85,17 @@ Collection<Resource> generate(InterceptorInfo interceptor, AnnotationLiteralProc
createProviderFields(interceptorCreator, interceptor, injectionPointToProviderField, interceptorToProviderField);
createConstructor(classOutput, interceptorCreator, interceptor, baseName, injectionPointToProviderField, interceptorToProviderField,
bindings.getFieldDescriptor());
createCreate(classOutput, interceptorCreator, interceptor, providerTypeName, baseName, injectionPointToProviderField, interceptorToProviderField,
reflectionRegistration);
createGet(interceptor, interceptorCreator, providerTypeName);
createGetTypes(interceptorCreator, beanTypes.getFieldDescriptor());
implementCreate(classOutput, interceptorCreator, interceptor, providerTypeName, baseName, injectionPointToProviderField, interceptorToProviderField,
reflectionRegistration, targetPackage);
implementGet(interceptor, interceptorCreator, providerTypeName);
implementGetTypes(interceptorCreator, beanTypes.getFieldDescriptor());
// Interceptors are always @Dependent and have always default qualifiers

// InjectableInterceptor methods
createGetInterceptorBindings(interceptorCreator, bindings.getFieldDescriptor());
createIntercepts(interceptorCreator, interceptor);
createIntercept(interceptorCreator, interceptor, providerTypeName, reflectionRegistration);
createGetPriority(interceptorCreator, interceptor);
implementGetInterceptorBindings(interceptorCreator, bindings.getFieldDescriptor());
implementIntercepts(interceptorCreator, interceptor);
implementIntercept(interceptorCreator, interceptor, providerTypeName, reflectionRegistration);
implementGetPriority(interceptorCreator, interceptor);

interceptorCreator.close();
return classOutput.getResources();
Expand Down Expand Up @@ -125,7 +126,7 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator creator,
*
* @see InjectableInterceptor#getInterceptorBindings()
*/
protected void createGetInterceptorBindings(ClassCreator creator, FieldDescriptor bindingsField) {
protected void implementGetInterceptorBindings(ClassCreator creator, FieldDescriptor bindingsField) {
MethodCreator getBindings = creator.getMethodCreator("getInterceptorBindings", Set.class).setModifiers(ACC_PUBLIC);
getBindings.returnValue(getBindings.readInstanceField(bindingsField, getBindings.getThis()));
}
Expand All @@ -134,7 +135,7 @@ protected void createGetInterceptorBindings(ClassCreator creator, FieldDescripto
*
* @see InjectableInterceptor#getPriority()
*/
protected void createGetPriority(ClassCreator creator, InterceptorInfo interceptor) {
protected void implementGetPriority(ClassCreator creator, InterceptorInfo interceptor) {
MethodCreator getPriority = creator.getMethodCreator("getPriority", int.class).setModifiers(ACC_PUBLIC);
getPriority.returnValue(getPriority.load(interceptor.getPriority()));
}
Expand All @@ -144,7 +145,7 @@ protected void createGetPriority(ClassCreator creator, InterceptorInfo intercept
* @return the method
* @see InjectableInterceptor#intercepts(javax.enterprise.inject.spi.InterceptionType)
*/
protected void createIntercepts(ClassCreator creator, InterceptorInfo interceptor) {
protected void implementIntercepts(ClassCreator creator, InterceptorInfo interceptor) {
MethodCreator intercepts = creator.getMethodCreator("intercepts", boolean.class, InterceptionType.class).setModifiers(ACC_PUBLIC);
addIntercepts(interceptor, InterceptionType.AROUND_INVOKE, intercepts);
addIntercepts(interceptor, InterceptionType.POST_CONSTRUCT, intercepts);
Expand All @@ -167,7 +168,8 @@ private void addIntercepts(InterceptorInfo interceptor, InterceptionType interce
*
* @see InjectableInterceptor#intercept(InterceptionType, Object, javax.interceptor.InvocationContext)
*/
protected void createIntercept(ClassCreator creator, InterceptorInfo interceptor, String providerTypeName, ReflectionRegistration reflectionRegistration) {
protected void implementIntercept(ClassCreator creator, InterceptorInfo interceptor, String providerTypeName,
ReflectionRegistration reflectionRegistration) {
MethodCreator intercept = creator.getMethodCreator("intercept", Object.class, InterceptionType.class, Object.class, InvocationContext.class)
.setModifiers(ACC_PUBLIC).addException(Exception.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ public static Object invokeMethod(Class<?> clazz, String name, Class<?>[] paramT
}
return method.invoke(instance, args);
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
System.out.println("INSTANCE: " + instance);
throw new RuntimeException("Cannot invoke method: " + clazz.getName() + "#" + name, e);
throw new RuntimeException("Cannot invoke method: " + clazz.getName() + "#" + name + " on " + instance, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.jboss.protean.arc.test.injection.superclass;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Singleton;

import org.jboss.protean.arc.Arc;
import org.jboss.protean.arc.test.ArcTestContainer;
import org.jboss.protean.arc.test.injection.superclass.foo.FooHarvester;
import org.junit.Rule;
import org.junit.Test;

public class SuperclassInjectionTest {

@Rule
public ArcTestContainer container = new ArcTestContainer(Head.class, CombineHarvester.class, SuperCombineHarvester.class);

@Test
public void testSuperclassSamePackage() {
CombineHarvester combineHarvester = Arc.container().instance(CombineHarvester.class).get();
assertNotNull(combineHarvester.getHead1());
assertNotNull(combineHarvester.getHead2());
assertNotEquals(combineHarvester.getHead1().id, combineHarvester.getHead2().id);
}

@Test
public void testSuperclassDifferentPackage() {
SuperCombineHarvester combineHarvester = Arc.container().instance(SuperCombineHarvester.class).get();
assertNotNull(combineHarvester.getHead1());
assertNotNull(combineHarvester.getHead2());
assertNotNull(combineHarvester.getHead3());
assertNotNull(combineHarvester.getHead4());
assertNotNull(combineHarvester.head5);
Set<String> ids = new HashSet<>();
ids.add(combineHarvester.getHead1().id);
ids.add(combineHarvester.getHead2().id);
ids.add(combineHarvester.getHead3().id);
ids.add(combineHarvester.getHead4().id);
ids.add(combineHarvester.head5.id);
assertEquals("Wrong number of ids: " + ids, 5, ids.size());
}

@Dependent
public static class Head {

String id;

@PostConstruct
void init() {
this.id = UUID.randomUUID().toString();
}

}

@Singleton
static class SuperCombineHarvester extends FooHarvester {

@Inject
Head head5;

}

@ApplicationScoped
static class CombineHarvester extends SuperHarvester {

}

public static class SuperHarvester {

private Head head1;

@Inject
Head head2;

@Inject
void setHead(Head head) {
this.head1 = head;
}

public Head getHead1() {
return head1;
}

public Head getHead2() {
return head2;
}

}
}
Loading

0 comments on commit fd36f15

Please sign in to comment.