/*
* 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());
}
}