/* AbstractAnnotatedMethodInvoker
Purpose:
Description:
History:
Jun 27, 2012, Created by Ian Tsai(Zanyking)
Copyright (C) 2010 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under ZOL in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.bind.impl;
import static org.zkoss.bind.sys.BinderCtrl.VM;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zkoss.bind.BindContext;
import org.zkoss.bind.Binder;
import org.zkoss.bind.annotation.AfterCompose;
import org.zkoss.bind.annotation.Init;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.UiException;
/**
*
* To help calling a ViewModel annotated method with binding arguments.
*
* @see Init
* @see AfterCompose
* @author Ian Y.T Tsai(zanyking)
*
*/
public abstract class AbstractAnnotatedMethodInvoker<T extends Annotation> {
private static final Logger _log = LoggerFactory.getLogger(AbstractAnnotatedMethodInvoker.class);
private final Map<Class<?>, List<Method>> annoMethodCache;
private Class<T> annoClass;
public AbstractAnnotatedMethodInvoker(Class<T> annoClass, Map<Class<?>, List<Method>> annoMethodCache) {
this.annoClass = annoClass;
this.annoMethodCache = annoMethodCache;
}
public void invokeMethod(Binder binder, Map<String, Object> bindingArgs) {
Component rootComp = binder.getView();
Object viewModel = rootComp.getAttribute(VM);
final Class<?> vmClz = viewModel.getClass();
List<Method> methods = getAnnotateMethods(annoClass, vmClz);
if (methods.size() == 0)
return; //no annotated method
if (bindingArgs != null) {
bindingArgs = BindEvaluatorXUtil.evalArgs(binder.getEvaluatorX(), rootComp, bindingArgs);
}
for (Method m : methods) { //TODO: why paramCall need to be prepared each time?
final BindContext ctx = BindContextUtil.newBindContext(binder, null, false, null, rootComp, null);
try {
ParamCall parCall = binder instanceof BinderImpl ? ((BinderImpl) binder).createParamCall(ctx)
: createParamCall(ctx, binder);
if (bindingArgs != null) {
parCall.setBindingArgs(bindingArgs);
}
parCall.call(viewModel, m);
} catch (Exception e) {
synchronized (annoMethodCache) { //remove it for the hot deploy case if getting any error
annoMethodCache.remove(vmClz);
}
throw UiException.Aide.wrap(e, e.getMessage());
}
}
}
private List<Method> getAnnotateMethods(Class<T> annotationClass, Class<?> vmClass) {
List<Method> methods = null;
synchronized (annoMethodCache) {
//have to synchronized cache, because it calls expunge when get.
methods = annoMethodCache.get(vmClass);
if (methods != null)
return methods;
methods = new ArrayList<Method>(); //if still null in synchronized, scan it
Class<?> curr = vmClass;
String sign = null;
Set<String> signs = new HashSet<String>();
while (curr != null && !curr.equals(Object.class)) {
Method currm = null;
//Annotation should supports to annotate on Type
T annotation = curr.getAnnotation(annotationClass);
//Allow only one annotated method in a class.
for (Method m : curr.getDeclaredMethods()) {
final T i = m.getAnnotation(annotationClass);
if (i == null)
continue;
if (annotation != null) {
throw new UiException(
"more than one [@" + annotationClass.getSimpleName() + "] in the class " + curr);
}
annotation = i;
currm = m;
//don't break, we need to check all annotated methods, we allow only one per class.
}
if (currm != null) {
//check if overrode the same annotated method
sign = MiscUtil.toSimpleMethodSignature(currm);
if (signs.contains(sign)) {
_log.warn("more than one {} method that has same signature '{}' "
+ "in the hierarchy of '{}', the method in extended class will be call "
+ "more than once ", annotationClass.getSimpleName(), sign, vmClass);
} else {
signs.add(sign);
}
//super first
methods.add(0, currm);
}
//check if we should take care super's annotated methods also.
curr = (annotation != null && shouldLookupSuperclass(annotation)) ? curr.getSuperclass() : null;
}
methods = Collections.unmodifiableList(methods);
annoMethodCache.put(vmClass, methods);
}
return methods;
}
/**
* Verify if the super classes need to be traced.
* @param annotation
* @return true, if handler should trace super class, false otherwise.
*/
protected abstract boolean shouldLookupSuperclass(T annotation);
private static ParamCall createParamCall(BindContext ctx, Binder binder) {
final ParamCall call = new ParamCall();
call.setBinder(binder);
call.setBindContext(ctx);
final Component comp = ctx.getComponent();
if (comp != null) {
call.setComponent(comp);
}
final Execution exec = Executions.getCurrent();
if (exec != null) {
call.setExecution(exec);
}
return call;
}
}