/* * 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.Stmt.castTo; import static org.jboss.errai.codegen.util.Stmt.declareFinalVariable; import static org.jboss.errai.codegen.util.Stmt.declareVariable; import static org.jboss.errai.codegen.util.Stmt.invokeStatic; import static org.jboss.errai.codegen.util.Stmt.loadVariable; import static org.jboss.errai.ioc.rebind.ioc.bootstrapper.FactoryGenerator.getLocalVariableName; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.enterprise.inject.Disposes; 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.MetaClass; import org.jboss.errai.codegen.meta.MetaClassMember; 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.meta.impl.build.BuildMetaClass; import org.jboss.errai.codegen.util.Stmt; import org.jboss.errai.ioc.client.container.Factory; 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.DisposerMethodDependency; import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.ParamDependency; import org.jboss.errai.ioc.rebind.ioc.graph.api.DependencyGraphBuilder.ProducerInstanceDependency; import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable; 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; /** * Generates factories for beans of producer methods or fields. * * @see FactoryBodyGenerator * @see AbstractBodyGenerator * @author Max Barkley <mbarkley@redhat.com> */ public class ProducerFactoryBodyGenerator extends AbstractBodyGenerator { private static final String PRODUCER_INSTANCE = "producerInstance"; @Override protected List<Statement> generateCreateInstanceStatements(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) { final Multimap<DependencyType, Dependency> depsByType = separateByType(injectable.getDependencies()); if (depsByType.get(DependencyType.ProducerMember).size() != 1) { throw new RuntimeException("A produced type must have exactly 1 producing instance but " + depsByType.get(DependencyType.ProducerMember).size() + " were found."); } final ProducerInstanceDependency producerInstanceDep = (ProducerInstanceDependency) depsByType.get(DependencyType.ProducerMember).iterator().next(); final Injectable producerInjectable = producerInstanceDep.getInjectable(); final MetaClassMember producingMember = producerInstanceDep.getProducingMember(); if (producingMember instanceof MetaField) { return fieldCreateInstanceStatements((MetaField) producingMember, producerInjectable, injectable, bodyBlockBuilder); } else if (producingMember instanceof MetaMethod) { return methodCreateInstanceStatements((MetaMethod) producingMember, producerInjectable, injectable, depsByType.get(DependencyType.ProducerParameter), bodyBlockBuilder); } else { throw new RuntimeException("Unrecognized producing member: " + producingMember); } } private List<Statement> methodCreateInstanceStatements(final MetaMethod producingMember, final Injectable producerInjectable, final Injectable producedInjectable, final Collection<Dependency> paramDeps, final ClassStructureBuilder<?> bodyBlockBuilder) { final List<Statement> stmts = new ArrayList<>(); controller.ensureMemberExposed(producingMember); if (!producingMember.isStatic()) { final Statement producerInstanceValue = loadVariable("contextManager").invoke("getInstance", producerInjectable.getFactoryName()); stmts.add(declareVariable(PRODUCER_INSTANCE, producerInjectable.getInjectedType(), producerInstanceValue)); stmts.add(loadVariable(PRODUCER_INSTANCE).assignValue(castTo(producerInjectable.getInjectedType(), invokeStatic(Factory.class, "maybeUnwrapProxy", loadVariable(PRODUCER_INSTANCE))))); } final List<Statement> depScopeRegistrationStmts = new ArrayList<>(); final Statement[] producerParams = generateProducerParams(producingMember, paramDeps, stmts, depScopeRegistrationStmts); final Statement invocation = controller.exposedMethodStmt(loadVariable(PRODUCER_INSTANCE), producingMember, producerParams); stmts.add(declareFinalVariable("instance", producedInjectable.getInjectedType(), invocation)); if (!producingMember.isStatic()) { stmts.add(setProducerInstanceReference()); if (producerInjectable.getWiringElementTypes().contains(WiringElementType.DependentBean)) { stmts.add(loadVariable("this").invoke("registerDependentScopedReference", loadVariable("instance"), loadVariable(PRODUCER_INSTANCE))); } } stmts.addAll(depScopeRegistrationStmts); stmts.add(loadVariable("instance").returnValue()); return stmts; } private Statement[] generateProducerParams(final MetaMethod producingMember, final Collection<Dependency> paramDeps, final List<Statement> varDeclarationStmts, final List<Statement> depScopeRegistrationStmts) { // TODO validate params final Statement[] params = new Statement[producingMember.getParameters().length]; for (final Dependency dep : paramDeps) { final ParamDependency paramDep = (ParamDependency) dep; final ContextualStatementBuilder producerParamCreationStmt = generateProducerParamCreationStmt(paramDep); final String paramVarName = getLocalVariableName(paramDep.getParameter()); varDeclarationStmts.add(declareFinalVariable(paramVarName, paramDep.getParameter().getType(), producerParamCreationStmt)); if (paramDep.getInjectable().getWiringElementTypes().contains(WiringElementType.DependentBean)) { depScopeRegistrationStmts.add(loadVariable("this").invoke("registerDependentScopedReference", loadVariable("instance"), loadVariable(paramVarName))); } params[paramDep.getParamIndex()] = loadVariable(paramVarName); } return params; } private ContextualStatementBuilder generateProducerParamCreationStmt(final ParamDependency paramDep) { ContextualStatementBuilder producerParamCreationStmt; if (paramDep.getInjectable().isContextual()) { final MetaParameter param = paramDep.getParameter(); final MetaClass[] typeArgs = getTypeArguments(param.getType()); final Annotation[] annotations = param.getAnnotations(); producerParamCreationStmt = castTo(paramDep.getInjectable().getInjectedType(), loadVariable("contextManager").invoke("getContextualInstance", paramDep.getInjectable().getFactoryName(), typeArgs, annotations)); } else { producerParamCreationStmt = castTo(paramDep.getInjectable().getInjectedType(), loadVariable("contextManager").invoke("getInstance", paramDep.getInjectable().getFactoryName())); } return producerParamCreationStmt; } private List<Statement> fieldCreateInstanceStatements(final MetaField producingMember, final Injectable producerInjectable, final Injectable producedInjectable, final ClassStructureBuilder<?> bodyBlockBuilder) { final List<Statement> stmts = new ArrayList<>(); controller.ensureMemberExposed(producingMember); if (!producingMember.isStatic()) { final Statement producerInstanceValue = loadVariable("contextManager").invoke("getInstance", producerInjectable.getFactoryName()); stmts.add(declareVariable(PRODUCER_INSTANCE, producerInjectable.getInjectedType(), producerInstanceValue)); stmts.add(loadVariable(PRODUCER_INSTANCE).assignValue(Stmt.castTo(producerInjectable.getInjectedType(), invokeStatic(Factory.class, "maybeUnwrapProxy", loadVariable(PRODUCER_INSTANCE))))); } final Statement invocation = controller.exposedFieldStmt(loadVariable(PRODUCER_INSTANCE), producingMember); stmts.add(declareFinalVariable("instance", producedInjectable.getInjectedType(), invocation)); if (!producingMember.isStatic()) { stmts.add(setProducerInstanceReference()); if (producerInjectable.getWiringElementTypes().contains(WiringElementType.DependentBean)) { stmts.add(loadVariable("this").invoke("registerDependentScopedReference", loadVariable("instance"), loadVariable(PRODUCER_INSTANCE))); } } stmts.add(loadVariable("instance").returnValue()); return stmts; } private Statement setProducerInstanceReference() { return controller.setReferenceStmt(PRODUCER_INSTANCE, loadVariable(PRODUCER_INSTANCE)); } @Override protected List<Statement> generateDestroyInstanceStatements(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) { final List<Statement> destroyInstanceStmts = new ArrayList<>(); final Multimap<DependencyType, Dependency> depsByType = separateByType(injectable.getDependencies()); final Collection<Dependency> producerMemberDeps = depsByType.get(DependencyType.ProducerMember); if (producerMemberDeps.size() != 1) { throw new RuntimeException("A produced type must have exactly 1 producing instance but " + producerMemberDeps.size() + " were found."); } final Collection<Dependency> disposerMethodDeps = depsByType.get(DependencyType.DisposerMethod); if (disposerMethodDeps.size() > 1) { // TODO error message with matching disposer names. throw new RuntimeException(); } else if (disposerMethodDeps.size() == 1) { final DisposerMethodDependency disposerDep = (DisposerMethodDependency) disposerMethodDeps.iterator().next(); final MetaMethod disposer = disposerDep.getDisposerMethod(); controller.ensureMemberExposed(disposer); final Statement invocation = controller.exposedMethodStmt( controller.getReferenceStmt(PRODUCER_INSTANCE, disposer.getDeclaringClass()), disposer, getDisposerParams(disposer, depsByType.get(DependencyType.DisposerParameter), bodyBlockBuilder.getClassDefinition())); destroyInstanceStmts.add(invocation); } return destroyInstanceStmts; } private Statement[] getDisposerParams(final MetaMethod disposer, final Collection<Dependency> disposerParams, final BuildMetaClass factory) { final Statement[] params = new Statement[disposer.getParameters().length]; for (final Dependency dep : disposerParams) { final ParamDependency paramDep = (ParamDependency) dep; final ContextualStatementBuilder paramInstance = castTo(paramDep.getInjectable().getInjectedType(), loadVariable("contextManager").invoke("getInstance", paramDep.getInjectable().getFactoryName())); final ContextualStatementBuilder paramExpression; if (paramDep.getInjectable().getWiringElementTypes().contains(WiringElementType.DependentBean)) { paramExpression = loadVariable("this").invoke("registerDependentScopedReference", loadVariable("instance"), paramInstance); } else { paramExpression = paramInstance; } params[paramDep.getParamIndex()] = paramExpression; } final MetaParameter disposesParam = disposer.getParametersAnnotatedWith(Disposes.class).get(0); params[disposesParam.getIndex()] = loadVariable("instance"); return params; } }