Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arc - support initializer injection on superclasses #42

Merged
merged 1 commit into from
Oct 2, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
@@ -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
@@ -211,15 +211,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);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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<>();
@@ -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)) {

@@ -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()) {
@@ -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()
@@ -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()));
Original file line number Diff line number Diff line change
@@ -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
@@ -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
@@ -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)));
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
import org.jboss.jandex.Type;

/**
* Represents an injection point.
*
* @author Martin Kouba
*/
Original file line number Diff line number Diff line change
@@ -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);

@@ -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();
@@ -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()));
}
@@ -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()));
}
@@ -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);
@@ -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);

Original file line number Diff line number Diff line change
@@ -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);
}
}

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;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.jboss.protean.arc.test.injection.superclass.foo;

import javax.inject.Inject;

import org.jboss.protean.arc.test.injection.superclass.SuperclassInjectionTest.Head;
import org.jboss.protean.arc.test.injection.superclass.SuperclassInjectionTest.SuperHarvester;

public abstract class FooHarvester extends SuperHarvester {

private Head head3;

@Inject
Head head4;

@Inject
void setHead3(Head head) {
this.head3 = head;
}

public Head getHead3() {
return head3;
}

public Head getHead4() {
return head4;
}

}