/* * 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.injector.api; 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.invokeStatic; import static org.jboss.errai.codegen.util.Stmt.loadVariable; import static org.jboss.errai.ioc.rebind.ioc.bootstrapper.InjectUtil.constructGetReference; import static org.jboss.errai.ioc.rebind.ioc.bootstrapper.InjectUtil.constructSetReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.jboss.errai.codegen.Context; import org.jboss.errai.codegen.Statement; 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.MetaClassFactory; 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.codegen.meta.impl.build.BuildMetaClass; import org.jboss.errai.codegen.util.Stmt; import org.jboss.errai.ioc.client.api.CodeDecorator; import org.jboss.errai.ioc.client.container.ContextManager; import org.jboss.errai.ioc.client.container.Factory; import org.jboss.errai.ioc.client.container.Proxy; import org.jboss.errai.ioc.rebind.ioc.injector.api.Decorable.DecorableType; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; /** * The single point of contact for {@link CodeDecorator code decorators} to add * generated code to a {@link Factory}. * * @see CodeDecorator * @see Factory * @author Max Barkley <mbarkley@redhat.com> */ public class FactoryController { private final ListMultimap<MetaMethod, Statement> invokeBefore = ArrayListMultimap.create(); private final ListMultimap<MetaMethod, Statement> invokeAfter = ArrayListMultimap.create(); private final Map<String, Statement> proxyProperties = new HashMap<String, Statement>(); private final List<Statement> initializationStatements = new ArrayList<Statement>(); private final List<Statement> endInitializationStatements = new ArrayList<Statement>(); private final List<Statement> destructionStatements = new ArrayList<Statement>(); private final Map<String, Object> attributes = new HashMap<String, Object>(); private final Set<MetaField> exposedFields = new HashSet<MetaField>(); private final Set<MetaMethod> exposedMethods = new HashSet<MetaMethod>(); private final Set<MetaConstructor> exposedConstructors = new HashSet<MetaConstructor>(); private final List<Statement> factoryInitializationStatements = new ArrayList<Statement>(); private final MetaClass producedType; private final String factoryName; private final BuildMetaClass factory; public FactoryController(final MetaClass producedType, final String factoryName, final BuildMetaClass factory) { this.producedType = producedType; this.factoryName = factoryName; this.factory = factory; } /** * Add a statement to be run in a proxy before the invocation of a bean's * method. Using this method will force the bean to be proxied. */ public void addInvokeBefore(final MetaMethod method, Statement statement) { invokeBefore.put(method, statement); } /** * @return All statements added with {@link #addInvokeBefore(MetaMethod, Statement)} for the given method. */ public List<Statement> getInvokeBeforeStatements(final MetaMethod method) { return invokeBefore.get(method); } /** * Add a statement to be run in a proxy after the invocation of a bean's * method. Using this method will force the bean to be proxied. */ public void addInvokeAfter(final MetaMethod method, Statement statement) { invokeAfter.put(method, statement); } /** * @return All statements added with {@link #addInvokeAfter(MetaMethod, Statement)} for the given method. */ public List<Statement> getInvokeAfterStatements(final MetaMethod method) { return invokeAfter.get(method); } /** * Add a private field to a bean's proxy. Calling this method forces a bean to * be proxied. * * @param name * The name of the field. * @param type * The type of the field. * @param statement * The initialization statement. This statement is invoked in a * generated implementation for * {@link Proxy#initProxyProperties(Object)}. * @return A statement with the loaded proxy field that was just created. This * statement is only valid when used within the context of the proxy. */ public Statement addProxyProperty(final String name, final Class<?> type, final Statement statement) { proxyProperties.put(name, new Statement() { @Override public MetaClass getType() { return MetaClassFactory.get(type); } @Override public String generate(Context context) { return statement.generate(context); } }); return loadVariable(name); } /** * @return All field names and initialization statements added with {@link #addProxyProperty(String, Class, Statement)}. */ public Collection<Entry<String, Statement>> getProxyProperties() { return proxyProperties.entrySet(); } /** * Add a list of statements to the implementation of * {@link Factory#init(org.jboss.errai.ioc.client.container.Context)}. */ public void addFactoryInitializationStatements(final List<Statement> factoryInitializationStatements) { this.factoryInitializationStatements.addAll(factoryInitializationStatements); } /** * @return All statements added with * {@link #addFactoryInitializationStatements(List)}, in the order * they were added. */ public List<Statement> getFactoryInitializaionStatements() { return factoryInitializationStatements; } /** * Add a list of statements to the implementation of * {@link Factory#createInstance(org.jboss.errai.ioc.client.container.ContextManager)} * . */ public void addInitializationStatements(final List<Statement> callbackBodyStatements) { initializationStatements.addAll(callbackBodyStatements); } /** * @return All statements added with * {@link #addInitializationStatements(List)} in the order they were * added followed by all statements added with * {@link #addInitializationStatementsToEnd(List)} in the order they * were added. */ public List<Statement> getInitializationStatements() { final List<Statement> stmts = new ArrayList<Statement>(); stmts.addAll(initializationStatements); stmts.addAll(endInitializationStatements); return stmts; } /** * Add a list of statements to the implementation of * {@link Factory#destroyInstance(Object, org.jboss.errai.ioc.client.container.ContextManager)} * . The {@code instance} parameter in scope is guaranteed to be unproxied. */ public void addDestructionStatements(final List<Statement> callbackInstanceStatement) { destructionStatements.addAll(callbackInstanceStatement); } /** * @return All statements added with {@link #addDestructionStatements(List)} * in the order they were added. */ public List<Statement> getDestructionStatements() { return destructionStatements; } /** * @param name The name of an attribute. * @return True iff an attribute with the given name has been set with {@link #setAttribute(String, Object)}. */ public boolean hasAttribute(final String name) { return attributes.containsKey(name); } /** * Set an attribute that can be looked up with {@link #getAttribute(String)}. */ public void setAttribute(final String name, final Object value) { attributes.put(name, value); } /** * Lookup an attribute that was previously added with {@link #setAttribute(String, Object)}. * * @return The value of the attribute with the given name, or {@code null} if not attribute exists. */ public Object getAttribute(final String name) { return attributes.get(name); } /** * @param instanceStmt * The statement for the instance that will be the first parameter to * {@link ContextManager#getInstanceProperty(Object, String, Class)}. * @param name * The name of the property. * @param refType * The type of the property. * @return A statement that looks up a property of an instance from another * factory via * {@link ContextManager#getInstanceProperty(Object, String, Class)}. */ public ContextualStatementBuilder getInstancePropertyStmt(final Statement instanceStmt, final String name, final Class<?> refType) { return Stmt.castTo(refType, loadVariable("contextManager").invoke("getInstanceProperty", instanceStmt, name, refType)); } /** * @param name * The name of the property. * @param refType * The type of the property. * @return A statement to call * {@link Factory#getReferenceAs(Object, String, Class)} for an * instance in * {@link Factory#createInstance(org.jboss.errai.ioc.client.container.ContextManager)} * and * {@link Factory#destroyInstance(Object, org.jboss.errai.ioc.client.container.ContextManager)} * methods. */ public ContextualStatementBuilder getReferenceStmt(final String name, final Class<?> refType) { return constructGetReference(name, refType); } /** * @param name * The name of the property. * @param refType * The type of the property. * @return A statement to call * {@link Factory#getReferenceAs(Object, String, Class)} for an * instance in * {@link Factory#createInstance(org.jboss.errai.ioc.client.container.ContextManager)} * and * {@link Factory#destroyInstance(Object, org.jboss.errai.ioc.client.container.ContextManager)} * methods. */ public ContextualStatementBuilder getReferenceStmt(final String name, final MetaClass refType) { return constructGetReference(name, refType); } /** * @param name * The name of the property. * @param value * A statement for the value to be set. * @return A statement to call * {@link Factory#setReference(Object, String, Object)} for an * instance in the * {@link Factory#createInstance(org.jboss.errai.ioc.client.container.ContextManager)} * method. */ public ContextualStatementBuilder setReferenceStmt(final String name, final Statement value) { return constructSetReference(name, value); } /** * This should only be called for non-public fields. This method forces a * private accessor to be generated for the field. * * @param field * A field, static or non-static. * @return A statement for accessing the value of a field. */ public ContextualStatementBuilder exposedFieldStmt(final MetaField field) { if (!field.isPublic()) { addExposedField(field); } return DecorableType.FIELD.getAccessStatement(field, factory); } /** * This should only be called for non-public fields. This method forces a * private accessor to be generated for the field. * * @param field * A non-static field. * @return A statement for accessing the value of a field. */ public ContextualStatementBuilder exposedFieldStmt(final Statement instance, final MetaField field) { if (!field.isPublic()) { addExposedField(field); } return DecorableType.FIELD.call(instance, field, factory); } /** * For public methods, fields, constructors, and parameters of public methods, * this method does nothing. Otherwise this method generates private * accessors/mutators. In the case of a parameter this method acts as if * called for the declaring method. * * This method is idempotent. * * @param annotated * A method, field, or parameter that may or may not be public. */ public void ensureMemberExposed(final HasAnnotations annotated) { final MetaClassMember member; if (annotated instanceof MetaParameter) { member = ((MetaParameter) annotated).getDeclaringMember(); } else { member = (MetaClassMember) annotated; } if (!member.isPublic()) { if (member instanceof MetaField) { addExposedField((MetaField) member); } else if (member instanceof MetaMethod) { addExposedMethod((MetaMethod) member); } else if (member instanceof MetaConstructor) { addExposedConstructor((MetaConstructor) member); } } } /** * This method is idempotent. * * @param field A non-public field for which private accessor and mutator methods should be generated. */ public void addExposedField(final MetaField field) { exposedFields.add(field); } /** * This should only be called for non-public methods. This method forces a * private accessor to be generated for the method. Dispatches to variable * {@code instance}. * * @param method * A method, static or non-static. * @param params * Statements for the values to be passed in as parameters. * @return A statement for accessing invoking the given method. */ public ContextualStatementBuilder exposedMethodStmt(final MetaMethod method, final Statement... params) { if (!method.isPublic()) { addExposedMethod(method); } return DecorableType.METHOD.getAccessStatement(method, factory, params); } /** * This should only be called for non-public methods. This method forces a * private accessor to be generated for the method. * * @param instance * A statement for the instance on which this method will be called. * @param method * A non-static method. * @param params * Statements for the values to be passed in as parameters. * @return A statement for accessing invoking the given method. */ public ContextualStatementBuilder exposedMethodStmt(final Statement instance, final MetaMethod method, final Statement... params) { if (!method.isPublic()) { addExposedMethod(method); } return DecorableType.METHOD.call(instance, method, factory, params); } /** * This method is idempotent. * * @param method A non-public method for which a private accessor should be generated. */ public void addExposedMethod(final MetaMethod method) { exposedMethods.add(method); } /** * This should only be called for non-public constructors. This method forces a * private accessor to be generated for the constructor. * * @param constructor * A non-public constructor. * @return A statement for invoking the given constructor. */ public ContextualStatementBuilder exposedConstructorStmt(final MetaConstructor constructor, final Statement... params) { addExposedConstructor(constructor); return invokeStatic(factory, getPrivateMethodName(constructor), (Object[]) params); } /** * This method is idempotent. * * @param constructor A non-public constructor for which a private accessor method should be generated. */ public void addExposedConstructor(final MetaConstructor constructor) { exposedConstructors.add(constructor); } /** * @return A statement for getting an instance of the type produced by this * factory in * {@link Factory#init(org.jboss.errai.ioc.client.container.Context)}. */ public Statement contextGetInstanceStmt() { return castTo(producedType, loadVariable("context").invoke("getInstance", factoryName)); } /** * @return An unmodifiable collection of fields added with * {@link #addExposedField(MetaField)} or * {@link #ensureMemberExposed(HasAnnotations)}. */ public Collection<MetaField> getExposedFields() { return Collections.unmodifiableCollection(exposedFields); } /** * @return An unmodifiable collection of methods added with * {@link #addExposedMethod(MetaMethod)} or * {@link #ensureMemberExposed(HasAnnotations)}. */ public Collection<MetaMethod> getExposedMethods() { return Collections.unmodifiableCollection(exposedMethods); } /** * @return An unmodifiable collection of constructors added with * {@link #addExposedMethod(MetaMethod)} or * {@link #ensureMemberExposed(HasAnnotations)}. */ public Collection<MetaConstructor> getExposedConstructors() { return Collections.unmodifiableCollection(exposedConstructors); } /** * Add a list of statements that are to be added to * {@link Factory#createInstance(ContextManager)} after all statements added * with {@link #addInitializationStatements(List)}. * * @param statements * A collection of statements to be added to the end of * {@link Factory#createInstance(ContextManager)}. */ public void addInitializationStatementsToEnd(final List<Statement> statements) { endInitializationStatements.addAll(statements); } /** * @return True iff any of the following methods have been called: * <ul> * <li>{@link #addInvokeBefore(MetaMethod, Statement)}, * <li>{@link #addInvokeAfter(MetaMethod, Statement)}, * <li>{@link #addProxyProperty(String, Class, Statement)}. * </ul> */ public boolean requiresProxy() { return !(proxyProperties.isEmpty() && invokeAfter.isEmpty() && invokeBefore.isEmpty()); } }