package com.googlecode.gwt.test.uibinder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.dev.Link;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Widget;
import com.googlecode.gwt.test.exceptions.GwtTestUiBinderException;
import com.googlecode.gwt.test.exceptions.ReflectionException;
import com.googlecode.gwt.test.utils.GwtReflectionUtils;
class UiBinderInvocationHandler implements InvocationHandler {
private static final Map<Class<?>, GwtEvent<?>> EVENT_PROTOTYPES = new HashMap<Class<?>, GwtEvent<?>>();
private final Class<UiBinder<?, ?>> proxiedClass;
UiBinderInvocationHandler(Class<UiBinder<?, ?>> proxiedClass) {
this.proxiedClass = proxiedClass;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("createAndBindUi")) {
return createAndBindUi(args[0]);
} else {
throw new GwtTestUiBinderException("Not managed method for UiBinder : " + method.getName());
}
}
@SuppressWarnings("unchecked")
private <H extends EventHandler> void addHandlers(Object owner) {
Map<Method, UiHandler> uiHandlerMethods = GwtReflectionUtils.getAnnotatedMethod(
owner.getClass(), UiHandler.class);
for (Map.Entry<Method, UiHandler> entry : uiHandlerMethods.entrySet()) {
for (String uiFieldName : entry.getValue().value()) {
Widget uiField = GwtReflectionUtils.getPrivateFieldValue(owner, uiFieldName);
GwtEvent.Type<H> eventType = (GwtEvent.Type<H>) getEventType(entry.getKey());
H handler = (H) createHandler(uiField, entry.getKey(), owner);
if (eventType instanceof DomEvent.Type) {
uiField.addDomHandler(handler, (DomEvent.Type<H>) eventType);
} else {
// special case for ValueChangeEvent and HasValueChangeHandlers
if (uiField instanceof HasValueChangeHandlers
&& handler instanceof ValueChangeHandler) {
((HasValueChangeHandlers<Object>) uiField).addValueChangeHandler((ValueChangeHandler<Object>) handler);
} else {
uiField.addHandler(handler, eventType);
}
}
}
}
}
/**
* This method is in charge of the instanciation of DOM object / GWT widget and their binding
* with @UiField in the owner
*
* @param owner The owner UiBinder subclass instance.
* @return The root component, initially returned by {@link Link
* UiBinder#createAndBindUi(Object)}.
*/
private Object createAndBindUi(Object owner) {
UiBinderParser parser = new UiBinderParser();
// parse .ui.xml file and inject @UiField
Object rootComponent = parser.createUiComponent(proxiedClass, owner);
// handle @UiHandlers
addHandlers(owner);
return rootComponent;
}
private InvocationHandler createEventHandlerInvocationHandler(final Method uiHandlerMethod,
final Object uiHandlerOwner) {
return new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return GwtReflectionUtils.callPrivateMethod(uiHandlerOwner, uiHandlerMethod, args);
}
};
}
private EventHandler createHandler(Widget uiField, Method uiHandlerMethod, Object uiHandlerOwner) {
Class<EventHandler> eventHandlerClass = findHandlerClass(uiHandlerMethod);
InvocationHandler eventHandlerInvocationHandler = createEventHandlerInvocationHandler(
uiHandlerMethod, uiHandlerOwner);
return (EventHandler) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class<?>[]{eventHandlerClass}, eventHandlerInvocationHandler);
}
@SuppressWarnings("unchecked")
private Class<EventHandler> findHandlerClass(Method uiHandlerMethod) {
Class<?> eventTypeClass = uiHandlerMethod.getParameterTypes()[0];
String eventHandlerClassName = eventTypeClass.getName().substring(0,
eventTypeClass.getName().lastIndexOf("Event"))
+ "Handler";
try {
return (Class<EventHandler>) GwtReflectionUtils.getClass(eventHandlerClassName);
} catch (ClassNotFoundException e) {
}
String anotherEventHandlerClassName = eventTypeClass.getName() + ".Handler";
try {
return (Class<EventHandler>) GwtReflectionUtils.getClass(anotherEventHandlerClassName);
} catch (ClassNotFoundException e) {
// should never happen
throw new GwtTestUiBinderException("Cannot find handler class for event type '"
+ eventTypeClass.getName() + "'. By convention, it should be '"
+ eventHandlerClassName + "' or '" + anotherEventHandlerClassName + "'");
}
}
private GwtEvent.Type<?> getEventType(Method method) {
Class<?> eventTypeClass = method.getParameterTypes()[0];
try {
return GwtReflectionUtils.callStaticMethod(eventTypeClass, "getType");
} catch (ReflectionException e) {
// custom event does not respect the "TYPE" private static field
// 'standard', we have to try to instanciate it to call
// 'getAssociatedType()' method on it
GwtEvent<?> eventPrototype = EVENT_PROTOTYPES.get(eventTypeClass);
if (eventPrototype == null) {
eventPrototype = (GwtEvent<?>) GwtReflectionUtils.instantiateClass(eventTypeClass);
EVENT_PROTOTYPES.put(eventTypeClass, eventPrototype);
}
return eventPrototype.getAssociatedType();
}
}
}