/*
* Copyright (C) 2011 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.codegen;
import static org.jboss.errai.codegen.util.Stmt.loadVariable;
import static org.jboss.errai.codegen.util.Stmt.throw_;
import java.lang.annotation.Annotation;
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.Set;
import org.jboss.errai.codegen.builder.BlockBuilder;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.builder.ElseBlockBuilder;
import org.jboss.errai.codegen.builder.StatementEnd;
import org.jboss.errai.codegen.builder.impl.BooleanExpressionBuilder;
import org.jboss.errai.codegen.builder.impl.ClassBuilder;
import org.jboss.errai.codegen.exception.UnproxyableClassException;
import org.jboss.errai.codegen.literal.MetaClassLiteral;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassFactory;
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.MetaTypeVariable;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaClass;
import org.jboss.errai.codegen.util.GenUtil;
import org.jboss.errai.codegen.util.If;
import org.jboss.errai.codegen.util.PrivateAccessUtil;
import org.jboss.errai.codegen.util.ProxyUtil;
import org.jboss.errai.codegen.util.Refs;
import org.jboss.errai.codegen.util.Stmt;
/**
* @author Mike Brock
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class ProxyMaker {
public static final String PROXY_BIND_METHOD = "__$setProxiedInstance$";
private final Map<MetaMethod, Map<WeaveType, Collection<Statement>>> weavingStatements;
private final Map<String, ProxyProperty> proxyProperties;
private ProxyMaker(final Map<String, ProxyProperty> proxyProperties,
final Map<MetaMethod, Map<WeaveType, Collection<Statement>>> weavingStatements) {
this.proxyProperties = proxyProperties;
this.weavingStatements = weavingStatements;
}
public static BuildMetaClass makeProxy(final String proxyClassName, final Class cls) {
return makeProxy(proxyClassName, cls, "reflection");
}
public static BuildMetaClass makeProxy(final String proxyClassName,
final Class cls,
final String privateAccessorType) {
return makeProxy(proxyClassName, MetaClassFactory.get(cls), privateAccessorType);
}
public static BuildMetaClass makeProxy(final String proxyClassName,
final MetaClass toProxy) {
return makeProxy(proxyClassName, toProxy, "reflection");
}
public static BuildMetaClass makeProxy(final String proxyClassName,
final MetaClass toProxy,
final String privateAccessorType) {
return makeProxy(proxyClassName, toProxy, privateAccessorType, Collections.<String, ProxyProperty>emptyMap(),
Collections.<MetaMethod, Map<WeaveType, Collection<Statement>>>emptyMap());
}
public static BuildMetaClass makeProxy(final MetaClass toProxy,
final String privateAccessorType,
final Map<MetaMethod, Map<WeaveType, Collection<Statement>>> weavingStatements) {
return makeProxy(toProxy, privateAccessorType, Collections.<String, ProxyProperty>emptyMap(), weavingStatements);
}
public static BuildMetaClass makeProxy(final MetaClass toProxy,
final String privateAccessorType,
final Map<String, ProxyProperty> proxyProperties,
final Map<MetaMethod, Map<WeaveType, Collection<Statement>>> weavingStatements) {
return makeProxy(
PrivateAccessUtil.condensify(toProxy.getPackageName()) + "_" + toProxy.getName() + "_proxy",
toProxy,
privateAccessorType, proxyProperties, weavingStatements);
}
public static BuildMetaClass makeProxy(final String proxyClassName,
final MetaClass toProxy,
final String privateAccessorType,
final Map<String, ProxyProperty> proxyProperties,
final Map<MetaMethod, Map<WeaveType, Collection<Statement>>> weavingStatements) {
return new ProxyMaker(proxyProperties, weavingStatements).make(proxyClassName, toProxy, privateAccessorType);
}
private final static Override OVERRIDE_ANNOTATION = new Override() {
@Override
public Class<? extends Annotation> annotationType() {
return Override.class;
}
};
BuildMetaClass make(final String proxyClassName,
final MetaClass toProxy,
final String privateAccessorType) {
final ClassStructureBuilder builder;
final boolean renderEqualsAndHash;
if (!toProxy.isInterface()) {
renderEqualsAndHash = true;
if (toProxy.isFinal()) {
throw new UnproxyableClassException(toProxy, toProxy.getFullyQualifiedName()
+ " is an unproxiable class because it is final");
}
if (!toProxy.isDefaultInstantiable()) {
throw new UnproxyableClassException(toProxy, toProxy.getFullyQualifiedName() + " must have a default " +
"no-arg constructor");
}
builder = ClassBuilder.define(proxyClassName, toProxy).publicScope().body();
}
else {
renderEqualsAndHash = false;
builder = ClassBuilder.define(proxyClassName).publicScope().implementsInterface(toProxy).body();
}
final String proxyVar = "$$_proxy_$$";
final String stateVar = "$$_init_$$";
final Set<String> renderedMethods = new HashSet<String>();
final Map<String, MetaType> typeVariableMap = new HashMap<String, MetaType>();
final MetaParameterizedType metaParameterizedType = toProxy.getParameterizedType();
if (metaParameterizedType != null) {
int i = 0;
for (final MetaTypeVariable metaTypeVariable : toProxy.getTypeParameters()) {
typeVariableMap.put(metaTypeVariable.getName(), metaParameterizedType.getTypeParameters()[i++]);
}
}
builder.privateField(proxyVar, toProxy).finish();
// Create state variable for tracking initialization
builder.privateField(stateVar, boolean.class).finish();
final Set<Map.Entry<String, ProxyProperty>> entries = proxyProperties.entrySet();
for (final Map.Entry<String, ProxyProperty> entry : entries) {
builder.privateField(entry.getValue().getEncodedProperty(), entry.getValue().getType()).finish();
builder.packageMethod(void.class, "$set_" + entry.getKey(), Parameter.of(entry.getValue().getType(), "o"))
.append(Stmt.loadVariable(entry.getValue().getEncodedProperty()).assignValue(Refs.get("o")))
.finish();
}
for (final MetaMethod method : toProxy.getMethods()) {
final String methodString = GenUtil.getMethodString(method);
if (renderedMethods.contains(methodString) || method.getName().equals("hashCode")
|| method.getName().equals("clone") || method.getName().equals("finalize")
|| (method.getName().equals("equals") && method.getParameters().length == 1
&& method.getParameters()[0].getType().getFullyQualifiedName().equals(Object.class.getName())))
continue;
renderedMethods.add(methodString);
if ((!method.isPublic() && !method.isProtected()) ||
method.isSynthetic() ||
method.isFinal() ||
method.isStatic() ||
method.getDeclaringClass().getFullyQualifiedName().equals(Object.class.getName()))
continue;
final List<Parameter> methodParms = new ArrayList<Parameter>();
final MetaParameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
methodParms.add(Parameter.of(parameters[i].getType().getErased(), "a" + i));
}
final DefParameters defParameters = DefParameters.fromParameters(methodParms);
final BlockBuilder methBody = builder.publicMethod(method.getReturnType(), method.getName())
.annotatedWith(OVERRIDE_ANNOTATION)
.parameters(defParameters)
.throws_(method.getCheckedExceptions());
// Put method body into conditional, executed only after initialization
BlockBuilder<ElseBlockBuilder> ifBody = Stmt.create().if_(
BooleanExpressionBuilder.create(Stmt.loadVariable(stateVar)));
ifBody.appendAll(getAroundInvokeStatements(method));
ifBody.appendAll(getBeforeStatements(method));
final List<Parameter> parms = defParameters.getParameters();
final Statement[] statementVars = new Statement[parms.size()];
for (int i = 0; i < parms.size(); i++) {
statementVars[i] = loadVariable(parms.get(i).getName());
}
if (!method.isPublic()) {
PrivateAccessUtil.addPrivateAccessStubs(privateAccessorType, builder, method, new Modifier[0]);
final Statement[] privateAccessStmts = new Statement[statementVars.length + 1];
privateAccessStmts[0] = Refs.get(proxyVar);
System.arraycopy(statementVars, 0, privateAccessStmts, 1, statementVars.length);
if (method.getReturnType().isVoid()) {
ifBody._(loadVariable("this").invoke(PrivateAccessUtil.getPrivateMethodName(method), privateAccessStmts));
}
else {
ifBody._(loadVariable("this").invoke(PrivateAccessUtil.getPrivateMethodName(method), privateAccessStmts).returnValue());
}
}
else {
if (method.getReturnType().isVoid()) {
ifBody._(loadVariable(proxyVar).invoke(method, statementVars));
}
else {
ifBody._(loadVariable(proxyVar).invoke(method, statementVars).returnValue());
}
}
ifBody.appendAll(getAfterStatements(method));
ifBody.appendAll(getAroundInvokeStatements(method));
BlockBuilder<StatementEnd> elseBody = ifBody.finish().else_();
// Must return in else body if method is not void
if (!method.getReturnType().isVoid()) {
elseBody.append(ProxyUtil.generateProxyMethodReturnStatement(method));
}
methBody.append(elseBody.finish());
methBody.finish();
}
if (renderEqualsAndHash) {
// implement hashCode()
builder.publicMethod(int.class, "hashCode")
.annotatedWith(OVERRIDE_ANNOTATION)
.body()
._(
If.isNull(loadVariable(proxyVar))
._(throw_(IllegalStateException.class, "call to hashCode() on an unclosed proxy."))
.finish()
.else_()
._(Stmt.loadVariable(proxyVar).invoke("hashCode").returnValue())
.finish()
)
.finish();
// implements equals()
builder.publicMethod(boolean.class, "equals", Parameter.of(Object.class, "o"))
.annotatedWith(OVERRIDE_ANNOTATION)
.body()
._(
If.isNull(loadVariable(proxyVar))
._(throw_(IllegalStateException.class, "call to equals() on an unclosed proxy."))
.finish()
.else_()
._(Stmt.loadVariable(proxyVar).invoke("equals", Refs.get("o")).returnValue())
.finish()
)
.finish();
}
builder.publicMethod(void.class, PROXY_BIND_METHOD).parameters(DefParameters.of(Parameter.of(toProxy, "proxy")))
._(loadVariable(proxyVar).assignValue(loadVariable("proxy")))
// Set proxy state to initialized
._(loadVariable(stateVar).assignValue(true))
.finish();
return builder.getClassDefinition();
}
private Collection<Statement> getWeavingStatements(final MetaMethod method, final WeaveType type) {
final Map<WeaveType, Collection<Statement>> weaveTypeListMap = weavingStatements.get(method);
if (weaveTypeListMap == null) {
return Collections.emptyList();
}
else {
final Collection<Statement> statementList = weaveTypeListMap.get(type);
if (statementList == null) {
return Collections.emptyList();
}
else {
return Collections.unmodifiableCollection(statementList);
}
}
}
public Collection<Statement> getAroundInvokeStatements(final MetaMethod method) {
return getWeavingStatements(method, WeaveType.AroundInvoke);
}
public Collection<Statement> getBeforeStatements(final MetaMethod method) {
return getWeavingStatements(method, WeaveType.BeforeInvoke);
}
public Collection<Statement> getAfterStatements(final MetaMethod method) {
return getWeavingStatements(method, WeaveType.AfterInvoke);
}
public static Statement closeProxy(final Statement proxyReference, final Statement beanInstance) {
return Stmt.nestedCall(proxyReference).invoke(PROXY_BIND_METHOD, beanInstance);
}
public static Collection<Statement> createAllPropertyBindings(final Statement proxyRef,
final Map<String, ProxyProperty> proxyProperties) {
final List<Statement> statementList = new ArrayList<Statement>();
for (final Map.Entry<String, ProxyProperty> entry : proxyProperties.entrySet()) {
statementList.add(Stmt.nestedCall(proxyRef).invoke("$set_" + entry.getKey(), entry.getValue().getOriginalValueReference()));
}
return statementList;
}
public static class ProxyProperty implements Statement {
private final String propertyName;
private final MetaClass type;
private final Statement valueReference;
public ProxyProperty(final String propertyName, final MetaClass type, final Statement valueReference) {
this.propertyName = propertyName;
this.type = type;
this.valueReference = valueReference;
}
@Override
public MetaClass getType() {
return type;
}
public Statement getOriginalValueReference() {
return valueReference;
}
public Statement getProxiedValueReference() {
return Refs.get(getEncodedProperty());
}
private String getEncodedProperty() {
return "$P_" + propertyName + "_P$";
}
@Override
public String generate(Context context) {
return getEncodedProperty();
}
}
}