/* * 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.Parameter.finalOf; import static org.jboss.errai.codegen.meta.MetaClassFactory.parameterizedAs; import static org.jboss.errai.codegen.meta.MetaClassFactory.typeParametersOf; import static org.jboss.errai.codegen.util.PrivateAccessUtil.addPrivateAccessStubs; import static org.jboss.errai.codegen.util.PrivateAccessUtil.getPrivateMethodName; import static org.jboss.errai.codegen.util.Stmt.declareFinalVariable; import static org.jboss.errai.codegen.util.Stmt.if_; import static org.jboss.errai.codegen.util.Stmt.invokeStatic; import static org.jboss.errai.codegen.util.Stmt.load; import static org.jboss.errai.codegen.util.Stmt.loadLiteral; import static org.jboss.errai.codegen.util.Stmt.loadStatic; import static org.jboss.errai.codegen.util.Stmt.loadVariable; import static org.jboss.errai.codegen.util.Stmt.nestedCall; import static org.jboss.errai.codegen.util.Stmt.newArray; import static org.jboss.errai.codegen.util.Stmt.newObject; import static org.jboss.errai.codegen.util.Stmt.throw_; import static org.jboss.errai.codegen.util.Stmt.try_; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Map.Entry; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Any; import javax.enterprise.inject.Default; import javax.enterprise.inject.Typed; import javax.inject.Named; import javax.inject.Qualifier; import org.jboss.errai.codegen.BooleanOperator; import org.jboss.errai.codegen.InnerClass; import org.jboss.errai.codegen.Modifier; import org.jboss.errai.codegen.Parameter; import org.jboss.errai.codegen.Statement; import org.jboss.errai.codegen.builder.BlockBuilder; import org.jboss.errai.codegen.builder.ClassStructureBuilder; import org.jboss.errai.codegen.builder.ConstructorBlockBuilder; import org.jboss.errai.codegen.builder.ContextualStatementBuilder; import org.jboss.errai.codegen.builder.ElseBlockBuilder; import org.jboss.errai.codegen.builder.MethodCommentBuilder; import org.jboss.errai.codegen.builder.impl.AbstractStatementBuilder; import org.jboss.errai.codegen.builder.impl.BooleanExpressionBuilder; import org.jboss.errai.codegen.builder.impl.ClassBuilder; import org.jboss.errai.codegen.builder.impl.StatementBuilder; import org.jboss.errai.codegen.literal.LiteralFactory; import org.jboss.errai.codegen.meta.HasAnnotations; import org.jboss.errai.codegen.meta.MetaClass; import org.jboss.errai.codegen.meta.MetaClassFactory; 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.codegen.meta.MetaParameterizedType; import org.jboss.errai.codegen.meta.MetaType; import org.jboss.errai.codegen.meta.impl.build.BuildMetaClass; import org.jboss.errai.codegen.util.Stmt; import org.jboss.errai.ioc.client.QualifierUtil; import org.jboss.errai.ioc.client.api.ActivatedBy; import org.jboss.errai.ioc.client.api.EntryPoint; import org.jboss.errai.ioc.client.container.BeanActivator; import org.jboss.errai.ioc.client.container.Context; import org.jboss.errai.ioc.client.container.ContextManager; import org.jboss.errai.ioc.client.container.Factory; import org.jboss.errai.ioc.client.container.FactoryHandleImpl; import org.jboss.errai.ioc.client.container.Proxy; import org.jboss.errai.ioc.client.container.ProxyHelper; import org.jboss.errai.ioc.client.container.ProxyHelperImpl; 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.ParamDependency; import org.jboss.errai.ioc.rebind.ioc.graph.api.Injectable; import org.jboss.errai.ioc.rebind.ioc.injector.api.FactoryController; import org.jboss.errai.ioc.rebind.ioc.injector.api.InjectionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; /** * Implements functionality common to most {@link FactoryBodyGenerator} such as * generating proxies and managing * {@link Factory#getReferenceAs(Object, String, Class) references}. * * @see FactoryBodyGenerator * @author Max Barkley <mbarkley@redhat.com> */ public abstract class AbstractBodyGenerator implements FactoryBodyGenerator { private static final Logger logger = LoggerFactory.getLogger(AbstractBodyGenerator.class); protected static Multimap<DependencyType, Dependency> separateByType(final Collection<Dependency> dependencies) { final Multimap<DependencyType, Dependency> separated = HashMultimap.create(); for (final Dependency dep : dependencies) { separated.put(dep.getDependencyType(), dep); } return separated; } /** * This is populated at the start of * {@link #generate(ClassStructureBuilder, Injectable, DependencyGraph, InjectionContext, TreeLogger, GeneratorContext)} * . * * Calls to {@link FactoryController#addInvokeAfter(MetaMethod, Statement)}, * {@link FactoryController#addInvokeBefore(MetaMethod, Statement)}, or * {@link FactoryController#addProxyProperty(String, Class, Statement)} will * require the type produced by the generated factory to be proxiable. */ protected FactoryController controller; protected void maybeImplementCreateProxy(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable) { final MetaClass proxyImpl = maybeCreateProxyImplementation(injectable, bodyBlockBuilder); if (proxyImpl != null) { final BlockBuilder<?> createProxyBody = bodyBlockBuilder .publicMethod(parameterizedAs(Proxy.class, typeParametersOf(injectable.getInjectedType())), "createProxy", finalOf(Context.class, "context")) .body(); final Object proxyInstanceStmt; if (injectable.getInjectedType().isInterface() || getAccessibleNoArgConstructor(injectable.getInjectedType()) != null) { proxyInstanceStmt = newObject(proxyImpl); } else { bodyBlockBuilder .privateMethod(parameterizedAs(Proxy.class, typeParametersOf(injectable.getInjectedType())), "createProxyWithErrorMessage") .body() .append(try_().append(load(newObject(proxyImpl)).returnValue()).finish() .catch_(Throwable.class, "t").append(throw_(RuntimeException.class, loadLiteral(injectableConstructorErrorMessage(injectable)), loadVariable("t"))) .finish()) .finish(); proxyInstanceStmt = loadVariable("this").invoke("createProxyWithErrorMessage"); } createProxyBody .append(declareFinalVariable("proxyImpl", parameterizedAs(Proxy.class, typeParametersOf(injectable.getInjectedType())), proxyInstanceStmt)) .append(loadVariable("proxyImpl").invoke("setProxyContext", loadVariable("context"))) .append(loadVariable("proxyImpl").returnValue()).finish(); } } private String injectableConstructorErrorMessage(final Injectable injectable) { final MetaConstructor constr = getAccessibleConstructor(injectable); return "While creating a proxy for " + injectable.getInjectedType().getFullyQualifiedName() + " an exception was thrown from this constructor: " + constr + "\nTo fix this problem, add a no-argument public or protected constructor for use in proxying."; } /** * @return Returns {@code null} if unproxiable and a proxy is not required. */ private MetaClass maybeCreateProxyImplementation(final Injectable injectable, final ClassStructureBuilder<?> bodyBlockBuilder) { final ClassStructureBuilder<?> proxyImpl; final MetaClass injectedType = injectable.getInjectedType(); final boolean requiresProxy = requiresProxy(injectable); if (requiresProxy && injectedType.isInterface()) { proxyImpl = ClassBuilder .define(injectable.getFactoryName() + "ProxyImpl") .privateScope() .implementsInterface(parameterizedAs(Proxy.class, typeParametersOf(injectedType))) .implementsInterface(injectedType).body(); declareAndInitializeProxyHelper(injectable, proxyImpl); } else if (requiresProxy && isProxiableClass(injectable)) { proxyImpl = ClassBuilder .define(injectable.getFactoryName() + "ProxyImpl", injectedType) .privateScope() .implementsInterface(parameterizedAs(Proxy.class, typeParametersOf(injectedType))).body(); declareAndInitializeProxyHelper(injectable, proxyImpl); } else if (!requiresProxy) { return null; } else { throw new RuntimeException(injectedType + " must be proxiable but is not."); } maybeImplementConstructor(proxyImpl, injectable); implementProxyMethods(proxyImpl, injectable); implementAccessibleMethods(proxyImpl, injectable, bodyBlockBuilder.getClassDefinition()); bodyBlockBuilder.declaresInnerClass(new InnerClass(proxyImpl.getClassDefinition())); return proxyImpl.getClassDefinition(); } private boolean requiresProxy(final Injectable injectable) { return injectable.requiresProxy() || controller.requiresProxy(); } private boolean isProxiableClass(final Injectable injectable) { final MetaClass type = injectable.getInjectedType(); return !type.isFinal() && hasAccessibleConstructor(injectable); } private boolean hasAccessibleConstructor(final Injectable injectable) { return getAccessibleConstructor(injectable) != null; } private MetaConstructor getAccessibleConstructor(final Injectable injectable) { final MetaClass type = injectable.getInjectedType(); final MetaConstructor noArgConstr = getAccessibleNoArgConstructor(type); if (noArgConstr != null) { return noArgConstr; } for (final Dependency dep : injectable.getDependencies()) { if (dep.getDependencyType().equals(DependencyType.Constructor)) { final MetaConstructor injectableConstr = (MetaConstructor) ((ParamDependency) dep).getParameter().getDeclaringMember(); return (injectableConstr.isPublic() || injectableConstr.isProtected()) ? injectableConstr : null; } } return null; } private MetaConstructor getAccessibleNoArgConstructor(final MetaClass type) { final MetaConstructor noArgConstr = type.getDeclaredConstructor(new MetaClass[0]); if (noArgConstr != null && (noArgConstr.isPublic() || noArgConstr.isProtected())) { return noArgConstr; } else { return null; } } private void declareAndInitializeProxyHelper(final Injectable injectable, final ClassStructureBuilder<?> bodyBlockBuilder) { bodyBlockBuilder .privateField("proxyHelper", parameterizedAs(ProxyHelper.class, typeParametersOf(injectable.getInjectedType()))) .modifiers(Modifier.Final) .initializesWith(initializeProxyHelper(injectable)) .finish(); } private Statement initializeProxyHelper(final Injectable injectable) { return newObject( parameterizedAs(ProxyHelperImpl.class, typeParametersOf(injectable.getInjectedType())), injectable.getFactoryName()); } private void maybeImplementConstructor(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { if (injectable.getInjectedType().isInterface()) { return; } final MetaConstructor accessibleConstructor = getAccessibleConstructor(injectable); if (accessibleConstructor.getParameters().length > 0) { implementConstructor(proxyImpl, accessibleConstructor); } } private void implementConstructor(final ClassStructureBuilder<?> proxyImpl, final MetaConstructor accessibleConstructor) { final Object[] args = new Object[accessibleConstructor.getParameters().length]; for (int i = 0; i < args.length; i++) { args[i] = loadLiteral(null); } proxyImpl.publicConstructor().callSuper(args).finish(); } private void implementAccessibleMethods(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable, final BuildMetaClass factoryClass) { final Multimap<String, MetaMethod> proxiedMethodsByName = HashMultimap.create(); final MetaClass injectedType = injectable.getInjectedType(); for (final MetaMethod method : injectedType.getMethods()) { if (shouldProxyMethod(method, proxiedMethodsByName)) { proxiedMethodsByName.put(method.getName(), method); final BlockBuilder<?> body = createProxyMethodDeclaration(proxyImpl, method); final StatementBuilder proxiedInstanceDeclaration = declareFinalVariable("proxiedInstance", injectable.getInjectedType(), loadVariable("proxyHelper").invoke("getInstance", loadVariable("this"))); final ContextualStatementBuilder proxyHelperInvocation = proxyHelperInvocation(method, factoryClass); final Statement nonInitializedCase; final boolean nonInitializedReturns; if (injectedType.isInterface() || method.isAbstract()) { nonInitializedCase = throw_(RuntimeException.class, "Cannot invoke public method on proxied interface before constructor completes."); nonInitializedReturns = false; } else if (!(method.isPublic() || method.isPrivate() || method.isProtected())) { nonInitializedCase = throw_(RuntimeException.class, "Cannot invoke proxied package private method before constructor completes."); nonInitializedReturns = false; } else { nonInitializedCase = loadVariable("super").invoke(method.getName(), getParametersForInvocation(method)); nonInitializedReturns = true; } final BlockBuilder<ElseBlockBuilder> ifBlock = if_( BooleanExpressionBuilder.create(loadVariable("proxyHelper"), BooleanOperator.NotEquals, null)) .append(proxiedInstanceDeclaration) .appendAll(controller.getInvokeBeforeStatements(method)); if (method.getReturnType().isVoid()) { ifBlock.append(proxyHelperInvocation); ifBlock.appendAll(controller.getInvokeAfterStatements(method)); body.append(ifBlock.finish().else_().append(nonInitializedCase).finish()); } else { ifBlock.append(declareFinalVariable("retVal", method.getReturnType().getErased(), proxyHelperInvocation)); ifBlock.appendAll(controller.getInvokeAfterStatements(method)); ifBlock.append(loadVariable("retVal").returnValue()); if (nonInitializedReturns) { body.append(ifBlock.finish().else_().append(nestedCall(nonInitializedCase).returnValue()).finish()); } else { body.append(ifBlock.finish().else_().append(nonInitializedCase).finish()); } } body.finish(); } else if (!(method.isPublic() || method.isPrivate() || method.isProtected()) && injectable.requiresProxy()) { logger.warn("The normal scoped type, " + injectable.getInjectedType().getFullyQualifiedName() + ", has a package-private method, " + method.getName() + ", that cannot be proxied. Invoking this method on an injected instance may cause errors."); } } } private BlockBuilder<?> createProxyMethodDeclaration(final ClassStructureBuilder<?> proxyImpl, final MetaMethod method) { final MethodCommentBuilder<?> methodBuilder; if (method.isPublic()) { methodBuilder = proxyImpl.publicMethod(method.getReturnType().getErased(), method.getName(), getParametersForDeclaration(method)); } else if (method.isProtected()) { methodBuilder = proxyImpl.protectedMethod(method.getReturnType().getErased(), method.getName(), getParametersForDeclaration(method)); } else { final String methodType = (method.isProtected()) ? "private" : "package private"; throw new RuntimeException( "Cannot proxy " + methodType + " method from " + method.getDeclaringClassName()); } if (method.isPublic() || method.isProtected()) { return methodBuilder.annotatedWith(new Override() { @Override public Class<? extends Annotation> annotationType() { return Override.class; } }).throws_(method.getCheckedExceptions()).body(); } else { return methodBuilder.throws_(method.getCheckedExceptions()).body(); } } private ContextualStatementBuilder proxyHelperInvocation(final MetaMethod method, final BuildMetaClass factoryClass) { if (method.isPublic()) { return loadVariable("proxiedInstance").invoke(method.getName(), getParametersForInvocation(method)); } else { controller.addExposedMethod(method); return invokeStatic(factoryClass, getPrivateMethodName(method), getParametersForInvocation(method, loadVariable("proxiedInstance"))); } } private boolean shouldProxyMethod(final MetaMethod method, final Multimap<String, MetaMethod> proxiedMethodsByName) { return (method.getDeclaringClass() != null && method.getDeclaringClass().isInterface()) || !method.isStatic() && (method.isPublic() || method.isProtected()) && !method.isFinal() && methodIsNotFromObjectUnlessHashCode(method) && typesInSignatureAreVisible(method) && isNotAlreadyProxied(method, proxiedMethodsByName); } private boolean isNotAlreadyProxied(final MetaMethod method, final Multimap<String, MetaMethod> proxiedMethodsByName) { methodLoop: for (final MetaMethod proxiedMethod : proxiedMethodsByName.get(method.getName())) { final MetaParameter[] proxiedParams = proxiedMethod.getParameters(); final MetaParameter[] methodParams = method.getParameters(); if (proxiedParams.length == methodParams.length) { for (int i = 0; i < methodParams.length; i++) { if (!proxiedParams[i].getType().isAssignableTo(methodParams[i].getType())) { continue methodLoop; } } return false; } } return true; } private boolean methodIsNotFromObjectUnlessHashCode(final MetaMethod method) { return (method.asMethod() == null || method.asMethod().getDeclaringClass() == null || !method.asMethod().getDeclaringClass().equals(Object.class) || method.getName().equals("hashCode")) && isNotEqualsMethod(method); } private boolean isNotEqualsMethod(final MetaMethod method) { return !(method.getName().equals("equals") && method.getParameters().length == 1); } private boolean typesInSignatureAreVisible(final MetaMethod method) { if (!isVisibleType(method.getReturnType())) { return false; } for (final MetaParameter param : method.getParameters()) { if (!isVisibleType(param.getType())) { return false; } } return true; } private boolean isVisibleType(final MetaClass type) { if (type.isArray()) { return isVisibleType(type.getComponentType()); } else { return type.isPublic() || type.isProtected() || type.isPrimitive(); } } private Object[] getParametersForInvocation(final MetaMethod method, final Object... prependedParams) { final int paramLength = method.getParameters().length + prependedParams.length; final Object[] params = new Object[paramLength]; for (int i = 0; i < prependedParams.length; i++) { params[i] = prependedParams[i]; } final MetaParameter[] declaredParams = method.getParameters(); for (int i = 0; i < declaredParams.length; i++) { params[prependedParams.length+i] = loadVariable(declaredParams[i].getName()); } return params; } private Parameter[] getParametersForDeclaration(final MetaMethod method) { final MetaParameter[] metaParams = method.getParameters(); final Parameter[] params = new Parameter[metaParams.length]; for (int i = 0; i < params.length; i++) { params[i] = Parameter.of(metaParams[i].getType().getErased(), metaParams[i].getName()); } return params; } private void implementProxyMethods(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { implementInitProxyProperties(proxyImpl, injectable); implementAsBeanType(proxyImpl, injectable); implementSetInstance(proxyImpl, injectable); implementClearInstance(proxyImpl, injectable); implementSetContext(proxyImpl, injectable); implementGetContext(proxyImpl, injectable); implementUnwrappedInstance(proxyImpl, injectable); implementEquals(proxyImpl); } private void implementEquals(final ClassStructureBuilder<?> proxyImpl) { proxyImpl.publicMethod(boolean.class, "equals", Parameter.of(Object.class, "obj")).body() .append(loadVariable("obj").assignValue(invokeStatic(Factory.class, "maybeUnwrapProxy", loadVariable("obj")))) .append(loadVariable("proxyHelper").invoke("getInstance", loadVariable("this")).invoke("equals", loadVariable("obj")).returnValue()) .finish(); } private void implementInitProxyProperties(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { final BlockBuilder<?> initBody = proxyImpl .publicMethod(void.class, "initProxyProperties", finalOf(injectable.getInjectedType(), "instance")).body(); for (final Entry<String, Statement> prop : controller.getProxyProperties()) { proxyImpl.privateField(prop.getKey(), prop.getValue().getType()).finish(); initBody.append(loadVariable(prop.getKey()).assignValue(prop.getValue())); } initBody.finish(); } private void implementUnwrappedInstance(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { proxyImpl.publicMethod(Object.class, "unwrap") .body() .append(loadVariable("proxyHelper").invoke("getInstance", loadVariable("this")).returnValue()) .finish(); } private void implementSetContext(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { proxyImpl.publicMethod(void.class, "setProxyContext", finalOf(Context.class, "context")) .body() .append(loadVariable("proxyHelper").invoke("setProxyContext", loadVariable("context"))) .finish(); } private void implementGetContext(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { proxyImpl.publicMethod(Context.class, "getProxyContext") .body() .append(loadVariable("proxyHelper").invoke("getProxyContext").returnValue()) .finish(); } private void implementClearInstance(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { proxyImpl.publicMethod(void.class, "clearInstance") .body() .append(loadVariable("proxyHelper").invoke("clearInstance")) .finish(); } private void implementSetInstance(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { proxyImpl.publicMethod(void.class, "setInstance", finalOf(injectable.getInjectedType(), "instance")) .body() .append(loadVariable("proxyHelper").invoke("setInstance", loadVariable("instance"))) .finish(); } private void implementAsBeanType(final ClassStructureBuilder<?> proxyImpl, final Injectable injectable) { proxyImpl.publicMethod(injectable.getInjectedType(), "asBeanType") .body() .append(loadVariable("this").returnValue()) .finish(); } protected void implementCreateInstance(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final List<Statement> createInstanceStatements) { String createInstanceMethodName; Parameter[] params; if (injectable.isContextual()) { createInstanceMethodName = "createContextualInstance"; params = new Parameter[] { finalOf(ContextManager.class, "contextManager"), finalOf(Class[].class, "typeArgs"), finalOf(Annotation[].class, "qualifiers") }; } else { createInstanceMethodName = "createInstance"; params = new Parameter[] { finalOf(ContextManager.class, "contextManager") }; } bodyBlockBuilder.publicMethod(injectable.getInjectedType(), createInstanceMethodName, params) .appendAll(createInstanceStatements) .finish(); } protected void addReturnStatement(final List<Statement> createInstanceStatements) { createInstanceStatements.add(loadVariable("this").invoke("setIncompleteInstance", loadLiteral(null))); createInstanceStatements.add(loadVariable("instance").returnValue()); } /** * @param bodyBlockBuilder * The {@link ClassStructureBuilder} for the {@link Factory} being * generated. * @param injectable * Contains metadata (including dependencies) or the bean that the * generated factory will produce. * @param graph * The dependency graph that the {@link Injectable} parameter is * from. * @param injectionContext * The single injection context shared between all * {@link FactoryBodyGenerator FactoryBodyGenerators}. * @return A list of statements that will generated in the * {@link Factory#createInstance(ContextManager)} method. */ protected abstract List<Statement> generateCreateInstanceStatements(ClassStructureBuilder<?> bodyBlockBuilder, Injectable injectable, DependencyGraph graph, InjectionContext injectionContext); @Override public void generate(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext, final TreeLogger logger, final GeneratorContext context) { controller = new FactoryController(injectable.getInjectedType(), injectable.getFactoryName(), bodyBlockBuilder.getClassDefinition()); preGenerationHook(bodyBlockBuilder, injectable, graph, injectionContext); final List<Statement> factoryInitStatements = generateFactoryInitStatements(bodyBlockBuilder, injectable, graph, injectionContext); final List<Statement> createInstanceStatements = generateCreateInstanceStatements(bodyBlockBuilder, injectable, graph, injectionContext); final List<Statement> destroyInstanceStatements = generateDestroyInstanceStatements(bodyBlockBuilder, injectable, graph, injectionContext); final List<Statement> invokePostConstructStatements = generateInvokePostConstructsStatements(bodyBlockBuilder, injectable, graph, injectionContext); implementConstructor(bodyBlockBuilder, injectable); maybeImplementFactoryInit(bodyBlockBuilder, injectable, factoryInitStatements); implementCreateInstance(bodyBlockBuilder, injectable, createInstanceStatements); maybeImplementDestroyInstance(bodyBlockBuilder, injectable, destroyInstanceStatements); maybeImplementInvokePostConstructs(bodyBlockBuilder, injectable, invokePostConstructStatements); maybeImplementCreateProxy(bodyBlockBuilder, injectable); addPrivateAccessors(bodyBlockBuilder); } private void addPrivateAccessors(final ClassStructureBuilder<?> bodyBlockBuilder) { for (final MetaField field : controller.getExposedFields()) { addPrivateAccessStubs("jsni", bodyBlockBuilder, field); } for (final MetaMethod method : controller.getExposedMethods()) { addPrivateAccessStubs("jsni", bodyBlockBuilder, method); } for (final MetaConstructor constructor : controller.getExposedConstructors()) { addPrivateAccessStubs("jsni", bodyBlockBuilder, constructor); } } protected void preGenerationHook(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) { } /** * @param bodyBlockBuilder * The {@link ClassStructureBuilder} for the {@link Factory} being * generated. * @param injectable * Contains metadata (including dependencies) or the bean that the * generated factory will produce. * @param graph * The dependency graph that the {@link Injectable} parameter is * from. * @param injectionContext * The single injection context shared between all * {@link FactoryBodyGenerator FactoryBodyGenerators}. * @return A list of statements that will generated in the * {@link Factory#init(Context)} method. */ protected List<Statement> generateFactoryInitStatements(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) { return Collections.emptyList(); } /** * @param bodyBlockBuilder * The {@link ClassStructureBuilder} for the {@link Factory} being * generated. * @param injectable * Contains metadata (including dependencies) or the bean that the * generated factory will produce. * @param graph * The dependency graph that the {@link Injectable} parameter is * from. * @param injectionContext * The single injection context shared between all * {@link FactoryBodyGenerator FactoryBodyGenerators}. * @return A list of statements that will generated in the * {@link Factory#invokePostConstructs(Object)} method. */ protected List<Statement> generateInvokePostConstructsStatements(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) { return Collections.emptyList(); } private void maybeImplementFactoryInit(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final List<Statement> factoryInitStatements) { if (!factoryInitStatements.isEmpty()) { bodyBlockBuilder.publicMethod(void.class, "init", finalOf(Context.class, "context")).appendAll(factoryInitStatements).finish(); } } private void maybeImplementInvokePostConstructs(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final List<Statement> invokePostConstructStatements) { if (!invokePostConstructStatements.isEmpty()) { bodyBlockBuilder .publicMethod(MetaClassFactory.get(void.class), "invokePostConstructs", finalOf(injectable.getInjectedType(), "instance")) .appendAll(invokePostConstructStatements).finish(); } } private void maybeImplementDestroyInstance(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final List<Statement> destroyInstanceStatements) { if (!destroyInstanceStatements.isEmpty()) { bodyBlockBuilder .publicMethod(void.class, "generatedDestroyInstance", finalOf(Object.class, "instance"), finalOf(ContextManager.class, "contextManager")) .append(loadVariable("this").invoke("destroyInstanceHelper", Stmt.castTo(injectable.getInjectedType(), loadVariable("instance")), loadVariable("contextManager"))) .finish(); bodyBlockBuilder.publicMethod(void.class, "destroyInstanceHelper", finalOf(injectable.getInjectedType(), "instance"), finalOf(ContextManager.class, "contextManager")) .appendAll(destroyInstanceStatements).finish(); } } /** * @param bodyBlockBuilder * The {@link ClassStructureBuilder} for the {@link Factory} being * generated. * @param injectable * Contains metadata (including dependencies) or the bean that the * generated factory will produce. * @param graph * The dependency graph that the {@link Injectable} parameter is * from. * @param injectionContext * The single injection context shared between all * {@link FactoryBodyGenerator FactoryBodyGenerators}. * @return A list of statements that will generated in the * {@link Factory#destroyInstance(Object, ContextManager)} method. */ protected List<Statement> generateDestroyInstanceStatements(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable, final DependencyGraph graph, final InjectionContext injectionContext) { return controller.getDestructionStatements(); } protected void implementConstructor(final ClassStructureBuilder<?> bodyBlockBuilder, final Injectable injectable) { final Statement newObject = generateFactoryHandleStatement(injectable); final ConstructorBlockBuilder<?> con = bodyBlockBuilder.publicConstructor(); con.callSuper(newObject); con.append(loadVariable("handle").invoke("setAssignableTypes", getAssignableTypesArrayStmt(injectable))); final org.jboss.errai.ioc.rebind.ioc.graph.api.Qualifier qualifier = injectable.getQualifier(); if (!qualifier.isDefaultQualifier()) { final AbstractStatementBuilder qualArray = getAnnotationArrayStmt(qualifier); con.append(loadVariable("handle").invoke("setQualifiers", qualArray)); } con.finish(); } public static AbstractStatementBuilder getAssignableTypesArrayStmt(final Injectable injectable) { final Object[] assignableTypes = injectable.getAnnotatedObject() .flatMap(annotated -> Optional.ofNullable(annotated.getAnnotation(Typed.class))) .map(typedAnno -> typedAnno.value()) // Ensure that Object is an assignable type .map(beanTypes -> { if (Arrays.stream(beanTypes).anyMatch(type -> Object.class.equals(type))) { return (Object[]) beanTypes; } else { final Class<?>[] copyWithObject = Arrays.copyOf(beanTypes, beanTypes.length+1); copyWithObject[beanTypes.length] = Object.class; return (Object[]) copyWithObject; } }) .orElseGet(() -> getAllAssignableTypes(injectable.getInjectedType()).stream().filter(MetaClass::isPublic).toArray()); return newArray(Class.class).initialize(assignableTypes); } public static AbstractStatementBuilder getAnnotationArrayStmt(final org.jboss.errai.ioc.rebind.ioc.graph.api.Qualifier qualifier) { return newArray(Annotation.class).initialize(qualifier.stream().map(AbstractBodyGenerator::annotationLiteral).toArray()); } protected Statement generateFactoryHandleStatement(final Injectable injectable) { final Statement newObject; if (injectable.getInjectedType().isAnnotationPresent(ActivatedBy.class)) { final Class<? extends BeanActivator> activatorType = injectable.getInjectedType().getAnnotation(ActivatedBy.class).value(); newObject = newObject(FactoryHandleImpl.class, loadLiteral(injectable.getInjectedType()), injectable.getFactoryName(), injectable.getScope(), isEager(injectable.getInjectedType()), injectable.getBeanName(), !injectable.isContextual(), loadLiteral(activatorType)); } else { newObject = newObject(FactoryHandleImpl.class, loadLiteral(injectable.getInjectedType()), injectable.getFactoryName(), injectable.getScope(), isEager(injectable.getInjectedType()), injectable.getBeanName(), !injectable.isContextual()); } return newObject; } protected static Object isEager(final MetaClass injectedType) { return injectedType.isAnnotationPresent(EntryPoint.class) || // TODO review this before adding any scopes other than app-scoped and depdendent (!injectedType.isAnnotationPresent(Dependent.class) && hasStartupAnnotation(injectedType)); } protected static boolean hasStartupAnnotation(final MetaClass injectedType) { for (final Annotation anno : injectedType.getAnnotations()) { if (anno.annotationType().getName().equals("javax.ejb.Startup")) { return true; } } return false; } public static Statement annotationLiteral(final Annotation qual) { if (qual.annotationType().equals(Any.class)) { return loadStatic(QualifierUtil.class, "ANY_ANNOTATION"); } else if (qual.annotationType().equals(Default.class)) { return loadStatic(QualifierUtil.class, "DEFAULT_ANNOTATION"); } else if (qual.annotationType().equals(Named.class)) { return invokeStatic(QualifierUtil.class, "createNamed", ((Named) qual).value()); } else { return LiteralFactory.getLiteral(qual); } } protected Collection<Annotation> getQualifiers(final HasAnnotations injectedType) { final Collection<Annotation> annos = new ArrayList<>(); for (final Annotation anno : injectedType.getAnnotations()) { if (anno.annotationType().isAnnotationPresent(Qualifier.class)) { annos.add(anno); } } return annos; } protected MetaClass[] getTypeArguments(final MetaClass type) { final MetaParameterizedType pType = type.getParameterizedType(); final MetaType[] typeArgs = (pType != null ? pType.getTypeParameters() : new MetaType[0]); final MetaClass[] typeArgsClasses = new MetaClass[typeArgs.length]; for (int i = 0; i < typeArgs.length; i++) { final MetaType argType = typeArgs[i]; if (argType instanceof MetaClass) { typeArgsClasses[i] = (MetaClass) argType; } else if (argType instanceof MetaParameterizedType) { typeArgsClasses[i] = (MetaClass) ((MetaParameterizedType) argType).getRawType(); } } return typeArgsClasses; } public static Collection<MetaClass> getAllAssignableTypes(final MetaClass injectedType) { return injectedType.getAllSuperTypesAndInterfaces(); } }