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