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

Generate shared annotation literals properly #586

Merged
merged 2 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
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
Expand Up @@ -157,7 +157,7 @@ public boolean test(DotName dotName) {
builder.setAdditionalBeanDefiningAnnotations(additionalBeanDefiningAnnotations.stream()
.map((s) -> new BeanDefiningAnnotation(s.getName(), s.getDefaultScope()))
.collect(Collectors.toList()));
builder.setSharedAnnotationLiterals(false);
builder.setSharedAnnotationLiterals(true);
builder.addResourceAnnotations(resourceAnnotations.stream()
.map(ResourceAnnotationBuildItem::getName)
.collect(Collectors.toList()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@

package org.jboss.protean.arc.processor;

import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
Expand All @@ -30,13 +36,16 @@
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ArrayType;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.PrimitiveType;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import org.jboss.protean.arc.ComputingCache;
import org.jboss.protean.arc.processor.AnnotationLiteralProcessor.CacheKey;
import org.jboss.protean.arc.processor.AnnotationLiteralProcessor.Key;
import org.jboss.protean.arc.processor.AnnotationLiteralProcessor.Literal;
import org.jboss.protean.arc.processor.ResourceOutput.Resource;
import org.jboss.protean.gizmo.BytecodeCreator;
import org.jboss.protean.gizmo.ClassCreator;
import org.jboss.protean.gizmo.ClassOutput;
import org.jboss.protean.gizmo.FieldDescriptor;
Expand All @@ -60,13 +69,44 @@ public class AnnotationLiteralGenerator extends AbstractGenerator {
* @param annotationLiterals
* @return a collection of resources
*/
Collection<Resource> generate(String name, BeanDeployment beanDeployment, ComputingCache<CacheKey, String> annotationLiteralsCache) {
ResourceClassOutput classOutput = new ResourceClassOutput(true);
Collection<Resource> generate(String name, BeanDeployment beanDeployment, ComputingCache<Key, Literal> annotationLiteralsCache) {
List<Resource> resources = new ArrayList<>();
annotationLiteralsCache.forEachEntry((key, literal) -> {
ResourceClassOutput classOutput = new ResourceClassOutput(literal.isApplicationClass);
createSharedAnnotationLiteral(classOutput, key, literal);
resources.addAll(classOutput.getResources());
});
return resources;
}

static void createSharedAnnotationLiteral(ClassOutput classOutput, Key key, Literal literal) {
// Ljavax/enterprise/util/AnnotationLiteral<Lcom/foo/MyQualifier;>;Lcom/foo/MyQualifier;
String signature = String.format("Ljavax/enterprise/util/AnnotationLiteral<L%1$s;>;L%1$s;", key.annotationName.toString().replace('.', '/'));
String generatedName = literal.className.replace('.', '/');

annotationLiteralsCache.forEachEntry(
(key, literalName) -> createAnnotationLiteral(classOutput, beanDeployment.getIndex().getClassByName(key.name), key.values, literalName));
ClassCreator annotationLiteral = ClassCreator.builder().classOutput(classOutput).className(generatedName).superClass(AnnotationLiteral.class)
.interfaces(key.annotationName.toString()).signature(signature).build();

MethodCreator constructor = annotationLiteral.getMethodCreator(Methods.INIT, "V",
literal.constructorParams.stream().map(m -> m.returnType().name().toString()).toArray());
constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(AnnotationLiteral.class), constructor.getThis());

return classOutput.getResources();
for (ListIterator<MethodInfo> iterator = literal.constructorParams.listIterator(); iterator.hasNext();) {
MethodInfo param = iterator.next();
String returnType = param.returnType().name().toString();
// field
annotationLiteral.getFieldCreator(param.name(), returnType).setModifiers(ACC_PRIVATE | ACC_FINAL);
// constructor param
constructor.writeInstanceField(FieldDescriptor.of(annotationLiteral.getClassName(), param.name(), returnType), constructor.getThis(),
constructor.getMethodParam(iterator.previousIndex()));
// value method
MethodCreator value = annotationLiteral.getMethodCreator(param.name(), returnType).setModifiers(ACC_PUBLIC);
value.returnValue(value.readInstanceField(FieldDescriptor.of(annotationLiteral.getClassName(), param.name(), returnType), value.getThis()));
}
constructor.returnValue(null);

annotationLiteral.close();
LOGGER.debugf("Shared annotation literal generated: %s", literal.className);
}

static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotationClass, AnnotationInstance annotationInstance, String literalName) {
Expand Down Expand Up @@ -129,56 +169,62 @@ static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotatio
break;
}
} else {
switch (value.kind()) {
case BOOLEAN:
retValue = valueMethod.load(value.asBoolean());
break;
case STRING:
retValue = valueMethod.load(value.asString());
break;
case BYTE:
retValue = valueMethod.load(value.asByte());
break;
case SHORT:
retValue = valueMethod.load(value.asShort());
break;
case LONG:
retValue = valueMethod.load(value.asLong());
break;
case INTEGER:
retValue = valueMethod.load(value.asInt());
break;
case FLOAT:
retValue = valueMethod.load(value.asFloat());
break;
case DOUBLE:
retValue = valueMethod.load(value.asDouble());
break;
case CHARACTER:
retValue = valueMethod.load(value.asChar());
break;
case CLASS:
retValue = valueMethod.loadClass(value.asClass().toString());
break;
case ARRAY:
retValue = arrayValue(value, valueMethod, method, annotationClass);
break;
case ENUM:
retValue = valueMethod
.readStaticField(FieldDescriptor.of(value.asEnumType().toString(), value.asEnum(), value.asEnumType().toString()));
break;
case NESTED:
default:
throw new UnsupportedOperationException();
}
retValue = loadValue(valueMethod, value, annotationClass, method);
}
valueMethod.returnValue(retValue);
}
annotationLiteral.close();
LOGGER.debugf("Annotation literal generated: %s", literalName);
}

static ResultHandle loadValue(BytecodeCreator valueMethod, AnnotationValue value, ClassInfo annotationClass, MethodInfo method) {
ResultHandle retValue;
switch (value.kind()) {
case BOOLEAN:
retValue = valueMethod.load(value.asBoolean());
break;
case STRING:
retValue = valueMethod.load(value.asString());
break;
case BYTE:
retValue = valueMethod.load(value.asByte());
break;
case SHORT:
retValue = valueMethod.load(value.asShort());
break;
case LONG:
retValue = valueMethod.load(value.asLong());
break;
case INTEGER:
retValue = valueMethod.load(value.asInt());
break;
case FLOAT:
retValue = valueMethod.load(value.asFloat());
break;
case DOUBLE:
retValue = valueMethod.load(value.asDouble());
break;
case CHARACTER:
retValue = valueMethod.load(value.asChar());
break;
case CLASS:
retValue = valueMethod.loadClass(value.asClass().toString());
break;
case ARRAY:
retValue = arrayValue(value, valueMethod, method, annotationClass);
break;
case ENUM:
retValue = valueMethod
.readStaticField(FieldDescriptor.of(value.asEnumType().toString(), value.asEnum(), value.asEnumType().toString()));
break;
case NESTED:
default:
throw new UnsupportedOperationException("Unsupported value: " + value);
}
return retValue;
}

private static ResultHandle arrayValue(AnnotationValue value, MethodCreator valueMethod, MethodInfo method, ClassInfo annotationClass) {
static ResultHandle arrayValue(AnnotationValue value, BytecodeCreator valueMethod, MethodInfo method, ClassInfo annotationClass) {
ResultHandle retValue;
switch (value.componentKind()) {
case CLASS:
Expand Down Expand Up @@ -246,9 +292,9 @@ static String componentType(MethodInfo method) {
return arrayType.component().name().toString();
}

static String generatedSharedName(String prefix, String simpleName, AtomicInteger index) {
// com.foo.MyQualifier -> org.jboss.protean.arc.setup.Default_MyQualifier1_AnnotationLiteral
return ComponentsProviderGenerator.SETUP_PACKAGE + "." + prefix + "_" + simpleName + index.incrementAndGet()
static String generatedSharedName(DotName annotationName, AtomicInteger index) {
// com.foo.MyQualifier -> com.foo.MyQualifier1_AnnotationLiteral
return DotNames.packageName(annotationName) + "." + DotNames.simpleName(annotationName) + index.incrementAndGet()
+ AnnotationLiteralGenerator.ANNOTATION_LITERAL_SUFFIX;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,100 +17,138 @@
package org.jboss.protean.arc.processor;

import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.protean.arc.ComputingCache;
import org.jboss.protean.gizmo.BytecodeCreator;
import org.jboss.protean.gizmo.ClassOutput;
import org.jboss.protean.gizmo.MethodDescriptor;
import org.jboss.protean.gizmo.ResultHandle;

/**
*
* @author Martin Kouba
*/
public class AnnotationLiteralProcessor {
class AnnotationLiteralProcessor {

private final AtomicInteger index;

private final ComputingCache<CacheKey, String> cache;
private final ComputingCache<Key, Literal> cache;

public AnnotationLiteralProcessor(String name, boolean shared) {
AnnotationLiteralProcessor(boolean shared, Predicate<DotName> applicationClassPredicate) {
this.index = new AtomicInteger(1);
this.cache = shared ? new ComputingCache<>(key -> AnnotationLiteralGenerator.generatedSharedName(name, DotNames.simpleName(key.name), index)) : null;
this.cache = shared ? new ComputingCache<>(key -> {
return new Literal(AnnotationLiteralGenerator.generatedSharedName(key.annotationName, index), applicationClassPredicate.test(key.annotationName),
key.annotationClass.methods()
.stream()
.filter(m -> !m.name().equals(Methods.CLINIT) && !m.name().equals(Methods.INIT))
.collect(Collectors.toList()));
}) : null;
}

boolean hasLiteralsToGenerate() {
return cache != null && !cache.isEmpty();
}

ComputingCache<CacheKey, String> getCache() {
ComputingCache<Key, Literal> getCache() {
return cache;
}

/**
*
* @param bytecode
* @param classOutput
* @param annotationClass
* @param annotationInstance
* @param targetPackage
* @return an annotation literal class name
*/
String process(ClassOutput classOutput, ClassInfo annotationClass, AnnotationInstance annotationInstance, String targetPackage) {
ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, ClassInfo annotationClass, AnnotationInstance annotationInstance,
String targetPackage) {
if (cache != null) {
return cache.getValue(new CacheKey(annotationInstance.name(), annotationInstance.values()));
Literal literal = cache.getValue(new Key(annotationInstance.name(), annotationClass));

Map<String, AnnotationValue> annotationValues = annotationInstance.values().stream()
.collect(Collectors.toMap(AnnotationValue::name, Function.identity()));

ResultHandle[] constructorParams = new ResultHandle[literal.constructorParams.size()];

for (ListIterator<MethodInfo> iterator = literal.constructorParams.listIterator(); iterator.hasNext();) {
MethodInfo method = iterator.next();
AnnotationValue value = annotationValues.get(method.name());
if (value == null) {
value = method.defaultValue();
}
if (value == null) {
throw new NullPointerException("Value not set for " + method);
}
ResultHandle retValue = AnnotationLiteralGenerator.loadValue(bytecode, value, annotationClass, method);
constructorParams[iterator.previousIndex()] = retValue;
}
return bytecode
.newInstance(MethodDescriptor.ofConstructor(literal.className,
literal.constructorParams.stream().map(m -> m.returnType().name().toString()).toArray()), constructorParams);

} else {
String literalClassName = AnnotationLiteralGenerator.generatedLocalName(targetPackage, DotNames.simpleName(annotationClass.name()), index);
AnnotationLiteralGenerator.createAnnotationLiteral(classOutput, annotationClass, annotationInstance, literalClassName);
return bytecode.newInstance(MethodDescriptor.ofConstructor(literalClassName));
}
String literalName = AnnotationLiteralGenerator.generatedLocalName(targetPackage, DotNames.simpleName(annotationClass.name()), index);
AnnotationLiteralGenerator.createAnnotationLiteral(classOutput, annotationClass, annotationInstance, literalName);
return literalName;
}

static class CacheKey {
static class Literal {

final DotName name;
final String className;

final List<AnnotationValue> values;
final boolean isApplicationClass;

final List<MethodInfo> constructorParams;

public CacheKey(DotName name, List<AnnotationValue> values) {
this.name = name;
this.values = values;
public Literal(String className, boolean isApplicationClass, List<MethodInfo> constructorParams) {
this.className = className;
this.isApplicationClass = isApplicationClass;
this.constructorParams = constructorParams;
}

}

static class Key {

final DotName annotationName;

final ClassInfo annotationClass;

public Key(DotName name, ClassInfo annotationClass) {
this.annotationName = name;
this.annotationClass = annotationClass;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((values == null) ? 0 : values.hashCode());
return result;
return Objects.hash(annotationName);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
if (this == obj)
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof CacheKey)) {
return false;
}
CacheKey other = (CacheKey) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
if (obj == null)
return false;
}
if (values == null) {
if (other.values != null) {
return false;
}
} else if (!values.equals(other.values)) {
if (getClass() != obj.getClass())
return false;
}
return true;
Key other = (Key) obj;
return Objects.equals(annotationName, other.annotationName);
}

}
Expand Down
Loading