/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.bootstrapper;
import static org.jboss.errai.codegen.util.PrivateAccessUtil.getPrivateFieldAccessorName;
import static org.jboss.errai.codegen.util.PrivateAccessUtil.getPrivateMethodName;
import static org.jboss.errai.codegen.util.Stmt.castTo;
import static org.jboss.errai.codegen.util.Stmt.declareFinalVariable;
import static org.jboss.errai.codegen.util.Stmt.loadLiteral;
import static org.jboss.errai.codegen.util.Stmt.loadVariable;
import static org.jboss.errai.codegen.util.Stmt.newObject;
import static org.jboss.errai.ioc.rebind.ioc.bootstrapper.FactoryGenerator.getLocalVariableName;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.builder.ContextualStatementBuilder;
import org.jboss.errai.codegen.meta.HasAnnotations;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassMember;
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.ioc.client.api.CodeDecorator;
import org.jboss.errai.ioc.rebind.ioc.extension.IOCDecoratorExtension;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraph;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.Dependency;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.DependencyType;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.FieldDependency;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.ParamDependency;
import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.SetterParameterDependency;
import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable;
import org.jboss.errai.ioc.rebind.ioc.injector.api.Decorable;
import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionContext;
import org.jboss.errai.ioc.rebind.ioc.injector.api.WiringElementType;
import com.google.common.collect.Multimap;
/**
* Generate factories for type injectable beans. Generates code to support
* constructor, field, and method injection.
*
* @see FactoryBodyGenerator
* @see AbstractBodyGenerator
* @author Max Barkley <mbarkley@redhat.com>
*/
class TypeFactoryBodyGenerator extends AbstractBodyGenerator {
@Override
protected void preGenerationHook(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable,
final DependencyGraph graph, final InjectionContext injectionContext) {
runDecorators(injectable, injectionContext, bodyBlockBuilder);
}
@Override
protected List<Statement> generateFactoryInitStatements(final ClassStructureBuilder<?> bodyBlockBuilder,
final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) {
return controller.getFactoryInitializaionStatements();
}
@Override
protected List<Statement> generateCreateInstanceStatements(final ClassStructureBuilder<?> bodyBlockBuilder,
final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) {
final Multimap<DependencyType, Dependency> dependenciesByType = separateByType(injectable.getDependencies());
final Collection<Dependency> constructorDependencies = dependenciesByType.get(DependencyType.Constructor);
final Collection<Dependency> fieldDependencies = dependenciesByType.get(DependencyType.Field);
final Collection<Dependency> setterDependencies = dependenciesByType.get(DependencyType.SetterParameter);
final List<Statement> createInstanceStatements = new ArrayList<>();
constructInstance(injectable, constructorDependencies, createInstanceStatements);
injectFieldDependencies(injectable, fieldDependencies, createInstanceStatements, bodyBlockBuilder);
injectSetterMethodDependencies(injectable, setterDependencies, createInstanceStatements, bodyBlockBuilder);
addInitializationStatements(createInstanceStatements);
addReturnStatement(createInstanceStatements);
return createInstanceStatements;
}
@Override
protected List<Statement> generateDestroyInstanceStatements(final ClassStructureBuilder<?> bodyBlockBuilder,
final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) {
final List<Statement> destructionStmts = new ArrayList<>();
maybeInvokePreDestroys(injectable, destructionStmts, bodyBlockBuilder);
destructionStmts
.addAll(super.generateDestroyInstanceStatements(bodyBlockBuilder, injectable, graph, injectionContext));
return destructionStmts;
}
@Override
protected List<Statement> generateInvokePostConstructsStatements(final ClassStructureBuilder<?> bodyBlockBuilder,
final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) {
final List<Statement> stmts = new ArrayList<>();
final Queue<MetaMethod> postConstructMethods = gatherPostConstructs(injectable);
for (final MetaMethod postConstruct : postConstructMethods) {
if (postConstruct.isPublic()) {
stmts.add(loadVariable("instance").invoke(postConstruct));
} else {
controller.ensureMemberExposed(postConstruct);
final String accessorName = getPrivateMethodName(postConstruct);
stmts.add(invokePrivateAccessorWithNoParams(accessorName));
}
}
return stmts;
}
private void addInitializationStatements(final List<Statement> createInstanceStatements) {
createInstanceStatements.addAll(controller.getInitializationStatements());
}
private void runDecorators(final Injectable injectable, final InjectionContext injectionContext,
final ClassStructureBuilder<?> bodyBlockBuilder) {
final MetaClass type = injectable.getInjectedType();
final Set<HasAnnotations> privateAccessors = new HashSet<>();
final List<DecoratorRunnable> decoratorRunnables = new ArrayList<>();
decoratorRunnables.addAll(generateDecoratorRunnablesForType(injectionContext, type, ElementType.FIELD,
bodyBlockBuilder, privateAccessors, injectable));
decoratorRunnables.addAll(generateDecoratorRunnablesForType(injectionContext, type, ElementType.PARAMETER,
bodyBlockBuilder, privateAccessors, injectable));
decoratorRunnables.addAll(generateDecoratorRunnablesForType(injectionContext, type, ElementType.METHOD,
bodyBlockBuilder, privateAccessors, injectable));
decoratorRunnables.addAll(generateDecoratorRunnablesForType(injectionContext, type, ElementType.TYPE,
bodyBlockBuilder, privateAccessors, injectable));
decoratorRunnables.sort(null);
for (final DecoratorRunnable runnable : decoratorRunnables) {
runnable.run();
}
for (final HasAnnotations annotated : privateAccessors) {
if (annotated instanceof MetaField) {
controller.addExposedField((MetaField) annotated);
} else if (annotated instanceof MetaMethod) {
controller.addExposedMethod((MetaMethod) annotated);
}
}
}
@SuppressWarnings({ "rawtypes" })
private List<DecoratorRunnable> generateDecoratorRunnablesForType(final InjectionContext injectionContext,
final MetaClass type, final ElementType elemType,
final ClassStructureBuilder<?> builder, final Set<HasAnnotations> createdAccessors, final Injectable injectable) {
final List<DecoratorRunnable> decoratorRunnables = new ArrayList<>();
final Collection<Class<? extends Annotation>> decoratorAnnos = injectionContext
.getDecoratorAnnotationsBy(elemType);
for (final Class<? extends Annotation> annoType : decoratorAnnos) {
final List<HasAnnotations> annotatedItems = getAnnotatedWithForElementType(type, elemType, annoType);
final IOCDecoratorExtension[] decorators = injectionContext.getDecorators(annoType);
for (final IOCDecoratorExtension decorator : decorators) {
for (final HasAnnotations annotated : annotatedItems) {
final DecoratorRunnable decoratorRunnable = new DecoratorRunnable(
decorator.getClass().getAnnotation(CodeDecorator.class).order(), elemType,
() -> {
final Decorable decorable = new Decorable(annotated, annotated.getAnnotation(annoType),
Decorable.DecorableType.fromElementType(elemType), injectionContext,
builder.getClassDefinition().getContext(), builder.getClassDefinition(), injectable);
if (isNonPublicField(annotated) && !createdAccessors.contains(annotated)) {
createdAccessors.add(type);
}
else if (isNonPublicMethod(annotated) && !createdAccessors.contains(annotated)) {
createdAccessors.add(annotated);
}
else if (isParamOfNonPublicMethod(annotated)
&& !createdAccessors.contains(((MetaParameter) annotated).getDeclaringMember())) {
final MetaMethod declaringMethod = (MetaMethod) ((MetaParameter) annotated).getDeclaringMember();
createdAccessors.add(declaringMethod);
}
decorator.generateDecorator(decorable, controller);
});
decoratorRunnables.add(decoratorRunnable);
}
}
}
return decoratorRunnables;
}
private boolean isParamOfNonPublicMethod(final HasAnnotations annotated) {
if (!(annotated instanceof MetaParameter)) {
return false;
}
final MetaClassMember member = ((MetaParameter) annotated).getDeclaringMember();
return member instanceof MetaMethod && !member.isPublic();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<HasAnnotations> getAnnotatedWithForElementType(final MetaClass type, final ElementType elemType, final Class<? extends Annotation> annoType) {
final List annotatedItems;
switch (elemType) {
case FIELD:
annotatedItems = type.getFieldsAnnotatedWith(annoType);
break;
case METHOD:
annotatedItems = type.getMethodsAnnotatedWith(annoType);
break;
case PARAMETER:
annotatedItems = type.getParametersAnnotatedWith(annoType);
break;
case TYPE:
annotatedItems = (type.isAnnotationPresent(annoType)) ? Collections.singletonList(type) : Collections.emptyList();
break;
default:
throw new RuntimeException("Not yet implemented.");
}
return annotatedItems;
}
private boolean isNonPublicMethod(final HasAnnotations annotated) {
return annotated instanceof MetaMethod && !((MetaMethod) annotated).isPublic();
}
private boolean isNonPublicField(final HasAnnotations annotated) {
return annotated instanceof MetaField && !((MetaField) annotated).isPublic();
}
private void maybeInvokePreDestroys(final Injectable injectable, final List<Statement> destructionStmts,
final ClassStructureBuilder<?> bodyBlockBuilder) {
final Queue<MetaMethod> preDestroyMethods = gatherPreDestroys(injectable);
for (final MetaMethod preDestroy : preDestroyMethods) {
if (preDestroy.isPublic()) {
destructionStmts.add(loadVariable("instance").invoke(preDestroy));
} else {
controller.ensureMemberExposed(preDestroy);
final String accessorName = getPrivateMethodName(preDestroy);
destructionStmts.add(invokePrivateAccessorWithNoParams(accessorName));
}
}
}
private Statement invokePrivateAccessorWithNoParams(final String accessorName) {
return loadVariable("this").invoke(accessorName, loadVariable("instance"));
}
private Queue<MetaMethod> gatherPreDestroys(final Injectable injectable) {
final Queue<MetaMethod> preDestroyQueue = new LinkedList<>();
MetaClass type = injectable.getInjectedType();
do {
final List<MetaMethod> curPreDestroys = type.getDeclaredMethodsAnnotatedWith(PreDestroy.class);
if (curPreDestroys.size() > 1) {
throw new RuntimeException(type.getFullyQualifiedName() + " has multiple @PreDestroy methods.");
} else if (curPreDestroys.size() == 1) {
final MetaMethod preDestroy = curPreDestroys.get(0);
if (preDestroy.getParameters().length > 0) {
throw new RuntimeException(type.getFullyQualifiedName() + " has a @PreDestroy method with parameters.");
}
preDestroyQueue.add(preDestroy);
}
type = type.getSuperClass();
} while (!type.getFullyQualifiedName().equals("java.lang.Object"));
return preDestroyQueue;
}
private Queue<MetaMethod> gatherPostConstructs(final Injectable injectable) {
MetaClass type = injectable.getInjectedType();
final Deque<MetaMethod> postConstructs = new ArrayDeque<>();
do {
final List<MetaMethod> currentPostConstructs = type.getDeclaredMethodsAnnotatedWith(PostConstruct.class);
if (currentPostConstructs.size() > 0) {
if (currentPostConstructs.size() > 1) {
throw new RuntimeException(type.getFullyQualifiedName() + " has multiple @PostConstruct methods.");
}
final MetaMethod postConstruct = currentPostConstructs.get(0);
if (postConstruct.getParameters().length > 0) {
throw new RuntimeException(type.getFullyQualifiedName() + " has a @PostConstruct method with parameters.");
}
postConstructs.push(postConstruct);
}
type = type.getSuperClass();
} while (!type.getFullyQualifiedName().equals("java.lang.Object"));
return postConstructs;
}
private void injectFieldDependencies(final Injectable injectable, final Collection<Dependency> fieldDependencies,
final List<Statement> createInstanceStatements, final ClassStructureBuilder<?> bodyBlockBuilder) {
for (final Dependency dep : fieldDependencies) {
final FieldDependency fieldDep = FieldDependency.class.cast(dep);
final MetaField field = fieldDep.getField();
final Injectable depInjectable = fieldDep.getInjectable();
final ContextualStatementBuilder injectedValue;
if (depInjectable.isContextual()) {
final MetaClass[] typeArgsClasses = getTypeArguments(field.getType());
final Annotation[] qualifiers = getQualifiers(field).toArray(new Annotation[0]);
injectedValue = castTo(depInjectable.getInjectedType(),
loadVariable("contextManager").invoke("getContextualInstance", loadLiteral(depInjectable.getFactoryName()), typeArgsClasses, qualifiers));
} else {
injectedValue = castTo(depInjectable.getInjectedType(),
loadVariable("contextManager").invoke("getInstance", loadLiteral(depInjectable.getFactoryName())));
}
final String fieldDepVarName = field.getDeclaringClassName().replace('.', '_').replace('$', '_') + "_" + field.getName();
createInstanceStatements.add(declareFinalVariable(fieldDepVarName, depInjectable.getInjectedType(), injectedValue));
if (depInjectable.getWiringElementTypes().contains(WiringElementType.DependentBean)) {
createInstanceStatements
.add(loadVariable("this").invoke("registerDependentScopedReference", loadVariable("instance"), loadVariable(fieldDepVarName)));
}
if (!field.isPublic()) {
controller.addExposedField(field);
final String privateFieldInjectorName = getPrivateFieldAccessorName(field);
createInstanceStatements.add(loadVariable("this").invoke(privateFieldInjectorName, loadVariable("instance"), loadVariable(fieldDepVarName)));
} else {
createInstanceStatements.add(loadVariable("instance").loadField(field).assignValue(loadVariable(fieldDepVarName)));
}
}
}
private void injectSetterMethodDependencies(final Injectable injectable, final Collection<Dependency> setterDependencies,
final List<Statement> createInstanceStatements, final ClassStructureBuilder<?> bodyBlockBuilder) {
for (final Dependency dep : setterDependencies) {
final SetterParameterDependency setterDep = SetterParameterDependency.class.cast(dep);
final MetaMethod setter = setterDep.getMethod();
final Injectable depInjectable = setterDep.getInjectable();
final ContextualStatementBuilder injectedValue;
if (depInjectable.isContextual()) {
final MetaClass[] typeArgsClasses = getTypeArguments(setter.getParameters()[0].getType());
final Annotation[] qualifiers = getQualifiers(setter).toArray(new Annotation[0]);
injectedValue = castTo(depInjectable.getInjectedType(), loadVariable("contextManager")
.invoke("getContextualInstance", loadLiteral(depInjectable.getFactoryName()), typeArgsClasses, qualifiers));
} else {
injectedValue = castTo(depInjectable.getInjectedType(), loadVariable("contextManager").invoke("getInstance", loadLiteral(depInjectable.getFactoryName())));
}
final MetaParameter param = setter.getParameters()[0];
final String paramLocalVarName = getLocalVariableName(param);
createInstanceStatements.add(declareFinalVariable(paramLocalVarName, param.getType(), injectedValue));
if (depInjectable.getWiringElementTypes().contains(WiringElementType.DependentBean)) {
createInstanceStatements
.add(loadVariable("this").invoke("registerDependentScopedReference", loadVariable("instance"), loadVariable(paramLocalVarName)));
}
if (!setter.isPublic()) {
controller.addExposedMethod(setter);
final String privateFieldInjectorName = getPrivateMethodName(setter);
createInstanceStatements.add(loadVariable("this").invoke(privateFieldInjectorName, loadVariable("instance"), loadVariable(paramLocalVarName)));
} else {
createInstanceStatements.add(loadVariable("instance").invoke(setter, loadVariable(paramLocalVarName)));
}
}
}
private void constructInstance(final Injectable injectable, final Collection<Dependency> constructorDependencies,
final List<Statement> createInstanceStatements) {
if (constructorDependencies.size() > 0) {
addConstructorInjectionStatements(injectable, constructorDependencies, createInstanceStatements);
} else {
assignNewObjectWithZeroArgConstructor(injectable, createInstanceStatements);
}
createInstanceStatements.add(loadVariable("this").invoke("setIncompleteInstance", loadVariable("instance")));
}
private void assignNewObjectWithZeroArgConstructor(final Injectable injectable, final List<Statement> createInstanceStatements) {
final MetaConstructor noArgConstr = injectable.getInjectedType().getDeclaredConstructor(new MetaClass[0]);
final Object newObjectStmt;
if (noArgConstr.isPublic()) {
newObjectStmt = newObject(injectable.getInjectedType());
} else {
newObjectStmt = controller.exposedConstructorStmt(noArgConstr);
}
createInstanceStatements.add(declareFinalVariable("instance", injectable.getInjectedType(), newObjectStmt));
}
private void addConstructorInjectionStatements(final Injectable injectable, final Collection<Dependency> constructorDependencies,
final List<Statement> createInstanceStatements) {
final Object[] constructorParameterStatements = new Object[constructorDependencies.size()];
final List<Statement> dependentScopedRegistrationStatements = new ArrayList<>(constructorDependencies.size());
for (final Dependency dep : constructorDependencies) {
processConstructorDependencyStatement(createInstanceStatements, constructorParameterStatements, dependentScopedRegistrationStatements, dep);
}
createInstanceStatements.add(declareFinalVariable("instance", injectable.getInjectedType(),
newObject(injectable.getInjectedType(), constructorParameterStatements)));
createInstanceStatements.addAll(dependentScopedRegistrationStatements);
}
private void processConstructorDependencyStatement(final List<Statement> createInstanceStatements, final Object[] constructorParameterStatements,
final List<Statement> dependentScopedRegistrationStatements, final Dependency dep) {
final Injectable depInjectable = dep.getInjectable();
final ParamDependency paramDep = ParamDependency.class.cast(dep);
final ContextualStatementBuilder injectedValue = getInjectedValue(depInjectable, paramDep);
final String paramLocalVarName = getLocalVariableName(paramDep.getParameter());
createInstanceStatements.add(declareFinalVariable(paramLocalVarName, paramDep.getParameter().getType(), injectedValue));
if (dep.getInjectable().getWiringElementTypes().contains(WiringElementType.DependentBean)) {
dependentScopedRegistrationStatements.add(loadVariable("this").invoke("registerDependentScopedReference",
loadVariable("instance"), loadVariable(paramLocalVarName)));
}
constructorParameterStatements[paramDep.getParamIndex()] = loadVariable(paramLocalVarName);
}
private ContextualStatementBuilder getInjectedValue(final Injectable depInjectable, final ParamDependency paramDep) {
final ContextualStatementBuilder injectedValue;
if (depInjectable.isContextual()) {
final MetaClass[] typeArgsClasses = getTypeArguments(paramDep.getParameter().getType());
final Annotation[] qualifiers = getQualifiers(paramDep.getParameter()).toArray(new Annotation[0]);
injectedValue = castTo(depInjectable.getInjectedType(),
loadVariable("contextManager").invoke("getContextualInstance", loadLiteral(depInjectable.getFactoryName()), typeArgsClasses, qualifiers));
} else {
injectedValue = castTo(depInjectable.getInjectedType(),
loadVariable("contextManager").invoke("getInstance", loadLiteral(depInjectable.getFactoryName())));
}
return injectedValue;
}
}