/*
* Copyright 2011 cruxframework.org.
*
* 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.cruxframework.crux.core.rebind.screen.widget;
import java.util.ArrayList;
import java.util.List;
import org.cruxframework.crux.core.client.controller.Expose;
import org.cruxframework.crux.core.client.controller.Factory;
import org.cruxframework.crux.core.client.screen.DeviceAdaptive.Device;
import org.cruxframework.crux.core.rebind.AbstractProxyCreator.SourcePrinter;
import org.cruxframework.crux.core.rebind.CruxGeneratorException;
import org.cruxframework.crux.core.rebind.context.RebindContext;
import org.cruxframework.crux.core.rebind.context.scanner.ResourceNotFoundException;
import org.cruxframework.crux.core.rebind.controller.ControllerProxyCreator;
import org.cruxframework.crux.core.rebind.screen.Event;
import org.cruxframework.crux.core.rebind.screen.EventFactory;
import org.cruxframework.crux.core.rebind.screen.View;
import org.cruxframework.crux.core.utils.JClassUtils;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JGenericType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.dom.client.NativeEvent;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public abstract class EvtProcessor extends AbstractProcessor
{
/**
* @param widgetCreator
*/
public EvtProcessor(WidgetCreator<?> widgetCreator)
{
super(widgetCreator);
}
/**
* @return
*/
public abstract Class<?> getEventClass();
/**
* @return
*/
public abstract Class<?> getEventHandlerClass();
/**
* @return
*/
public abstract String getEventName();
/**
* @param out
* @param eventValue
* @param cruxEvent
*/
public void printEvtCall(SourcePrinter out, String eventValue, String cruxEvent)
{
printEvtCall(out, eventValue, getEventName(), getEventClass(), cruxEvent, getWidgetCreator());
}
/**
* @param eventValue
* @param cruxEvent
*/
public void printPostProcessingEvtCall(String eventValue, String cruxEvent, Device device)
{
printPostProcessingEvtCall(eventValue, getEventName(), getEventClass(), cruxEvent, getWidgetCreator());
}
/**
* @param out
* @param eventValue
* @param parentVariable
* @param widgetId
*/
public void processEvent(SourcePrinter out, String eventValue, String parentVariable, String widgetId)
{
out.println(parentVariable+".add"+getEventHandlerClass().getSimpleName()+"(new "+getEventHandlerClass().getCanonicalName()+"(){");
out.println("public void "+getEventName()+"("+getEventClass().getCanonicalName()+" event){");
printEvtCall(out, eventValue, "event");
out.println("}");
out.println("});");
//TODO adicionar tratamento de erro e logar a widgetId onde deu o erro aqui.
}
/**
* @param out
* @param context
* @param eventValue
*/
public void processEvent(SourcePrinter out, WidgetCreatorContext context, String eventValue)
{
processEvent(out, eventValue, context.getWidget(), context.getWidgetId());
}
/**
*
* @param eventValue
* @param eventName
* @param parameterClassName
* @param context
* @param view
* @param device
* @param allowNoParameterCall
*/
public static boolean checkEvtCall(String eventValue, String eventName,String parameterClassName,
RebindContext context, View view, Device device, boolean allowNoParameterCall)
{
Event event = EventFactory.getEvent(eventName, eventValue);
if (event == null)
{
throw new CruxGeneratorException("Error parsing controller method declaration on view ["+view.getId()+"]. ["+eventValue+"] is not a valid method declaration.");
}
JClassType eventClassType = parameterClassName==null?null:context.getGeneratorContext().getTypeOracle().findType(parameterClassName);
if (!view.useController(event.getController()))
{
throw new CruxGeneratorException("Controller ["+event.getController()+"] , used on view ["+view.getId()+"], was not declared on this view. Use the useController attribute to import the controller into this view.");
}
String controller;
try
{
controller = context.getControllers().getController(event.getController(), device);
}
catch (ResourceNotFoundException e)
{
throw new CruxGeneratorException("Controller ["+event.getController()+"] , declared on view ["+view.getId()+"], not found.");
}
boolean hasEventParameter = true;
JClassType controllerClass = context.getGeneratorContext().getTypeOracle().findType(controller);
if (controllerClass == null)
{
String message = "Controller class ["+controller+"] , declared on view ["+view.getId()+"], could not be loaded. "
+ "\n Possible causes:"
+ "\n\t 1. Check if any type or subtype used by controller refers to another module and if this module is inherited in the .gwt.xml file."
+ "\n\t 2. Check if your controller or its members belongs to a client package."
+ "\n\t 3. Check the versions of all your modules."
;
throw new CruxGeneratorException(message);
}
JMethod exposedMethod = getControllerMethodWithEvent(event.getMethod(), eventClassType, controllerClass);
if (exposedMethod == null)
{
if (allowNoParameterCall)
{
exposedMethod = JClassUtils.getMethod(controllerClass, event.getMethod(), new JType[]{});
if (exposedMethod == null)
{
throw new CruxGeneratorException("View ["+view.getId()+"] tries to invoke the method ["+event.getMethod()+"] on controller ["+controller+"]. That method does not exist.");
}
hasEventParameter = false;
}
else
{
throw new CruxGeneratorException("View ["+view.getId()+"] tries to invoke the method ["+event.getMethod()+"] on controller ["+controller+"]. That method does not exist.");
}
}
checkExposedMethod(event, controller, exposedMethod, context);
return hasEventParameter;
}
/**
* Retrieve a list of possible methods to handle a call to a controller method. This list contains method candidates that
* receives events preceding methods that has no parameters.
* @param context
* @param eventName
* @param eventValue
* @param view
* @param device
* @return
*/
public static JMethod[] getControllerDomEventHandlers(RebindContext context, String eventName, String eventValue, View view, Device device)
{
Event event = EventFactory.getEvent(eventName, eventValue);
if (event == null)
{
throw new CruxGeneratorException("Error parsing controller method declaration on view ["+view.getId()+"]. ["+eventValue+"] is not a valid method declaration.");
}
if (!view.useController(event.getController()))
{
throw new CruxGeneratorException("Controller ["+event.getController()+"] , used on view ["+view.getId()+"], was not declared on this view. Use the useController attribute to import the controller into this view.");
}
String controller;
try
{
controller = context.getControllers().getController(event.getController(), device);
}
catch (ResourceNotFoundException e)
{
throw new CruxGeneratorException("Controller ["+event.getController()+"] , declared on view ["+view.getId()+"], not found.");
}
JClassType controllerClass = context.getGeneratorContext().getTypeOracle().findType(controller);
if (controllerClass == null)
{
String message = "Controller class ["+controller+"] , declared on view ["+view.getId()+"], could not be loaded. "
+ "\n Possible causes:"
+ "\n\t 1. Check if any type or subtype used by controller refers to another module and if this module is inherited in the .gwt.xml file."
+ "\n\t 2. Check if your controller or its members belongs to a client package."
+ "\n\t 3. Check the versions of all your modules."
;
throw new CruxGeneratorException(message);
}
return getControllerDomEventHandlers(context, event.getMethod(), controllerClass);
}
/**
*
* @param out
* @param eventValue
* @param eventName
* @param eventClass
* @param cruxEvent
* @param context
* @param view
* @param controllerAccessHandler
*/
public static void printEvtCall(SourcePrinter out, String eventValue, String eventName, Class<?> eventClass,
String cruxEvent, RebindContext context, View view, ControllerAccessHandler controllerAccessHandler, Device device)
{
printEvtCall(out, eventValue, eventName, eventClass!= null? eventClass.getCanonicalName():null, cruxEvent, context, view,
controllerAccessHandler, device, true);
}
/**
* @param out
* @param eventValue
* @param eventName
* @param eventClass
* @param cruxEvent
* @param creator
*/
public static void printEvtCall(SourcePrinter out, String eventValue, String eventName, Class<?> eventClass, String cruxEvent, WidgetCreator<?> creator)
{
printEvtCall(out, eventValue, eventName, eventClass!= null? eventClass.getCanonicalName():null, cruxEvent, creator);
}
/**
*
* @param out
* @param eventValue
* @param eventName
* @param parameterClassName
* @param cruxEvent
* @param context
* @param view
* @param controllerAccessHandler
*/
public static void printEvtCall(SourcePrinter out, String eventValue, String eventName,String parameterClassName,
String cruxEvent, RebindContext context, View view, ControllerAccessHandler controllerAccessHandler,
Device device, boolean allowNoParameterCall)
{
Event event = EventFactory.getEvent(eventName, eventValue);
boolean hasEventParameter = checkEvtCall(eventValue, eventName, parameterClassName, context, view, device, allowNoParameterCall);
out.print(controllerAccessHandler.getControllerExpression(event.getController(), device)+"."+event.getMethod()+ControllerProxyCreator.EXPOSED_METHOD_SUFFIX+"(");
if (hasEventParameter)
{
out.print(cruxEvent);
}
out.println(");");
}
/**
* @param out
* @param eventValue
* @param eventName
* @param eventClassName
* @param cruxEvent
* @param creator
*/
public static void printEvtCall(SourcePrinter out, String eventValue, String eventName, String eventClassName,
String cruxEvent, WidgetCreator<?> creator)
{
printEvtCall(out, eventValue, eventName, eventClassName, cruxEvent, creator.getContext(), creator.getView(), creator.getControllerAccessorHandler(),
creator.getDevice(), true);
}
public static void printEvtCall(SourcePrinter out, String eventValue, String eventName, String eventClassName,
String cruxEvent, WidgetCreator<?> creator, boolean allowNoParameterCall)
{
printEvtCall(out, eventValue, eventName, eventClassName, cruxEvent, creator.getContext(), creator.getView(), creator.getControllerAccessorHandler(),
creator.getDevice(), allowNoParameterCall);
}
/**
* @param eventValue
* @param eventName
* @param eventClass
* @param cruxEvent
* @param creator
*/
public static void printPostProcessingEvtCall(String eventValue, String eventName, Class<?> eventClass, String cruxEvent, WidgetCreator<?> creator)
{
Event event = EventFactory.getEvent(eventName, eventValue);
JClassType eventClassType = creator.getContext().getGeneratorContext().getTypeOracle().findType(eventClass.getCanonicalName());
if (!creator.getView().useController(event.getController()))
{
throw new CruxGeneratorException("Controller ["+event.getController()+"] , used on view ["+creator.getView().getId()+"], was not declared on this view. Use the useController attribute to import the controller into this view.");
}
String controller;
try
{
controller = creator.getContext().getControllers().getController(event.getController(), creator.getDevice());
}
catch (ResourceNotFoundException e)
{
throw new CruxGeneratorException("Controller ["+event.getController()+"] , declared on view ["+creator.getView().getId()+"], not found.");
}
boolean hasEventParameter = true;
JClassType controllerClass = creator.getContext().getGeneratorContext().getTypeOracle().findType(controller);
if (controllerClass == null)
{
String message = "Controller class ["+controller+"] , declared on view ["+creator.getView().getId()+"], could not be loaded. "
+ "\n Possible causes:"
+ "\n\t 1. Check if any type or subtype used by controller refers to another module and if this module is inherited in the .gwt.xml file."
+ "\n\t 2. Check if your controller or its members belongs to a client package."
+ "\n\t 3. Check the versions of all your modules."
;
throw new CruxGeneratorException(message);
}
JMethod exposedMethod = getControllerMethodWithEvent(event.getMethod(), eventClassType, controllerClass);
if (exposedMethod == null)
{
exposedMethod = JClassUtils.getMethod(controllerClass, event.getMethod(), new JType[]{});
if (exposedMethod == null)
{
throw new CruxGeneratorException("View ["+creator.getView().getId()+"] tries to invoke the method ["+event.getMethod()+"] on controller ["+controller+"]. That method does not exist.");
}
hasEventParameter = false;
}
checkExposedMethod(event, controller, exposedMethod, creator.getContext());
creator.printlnPostProcessing(creator.getControllerAccessorHandler().getControllerExpression(event.getController(), creator.getDevice())+"."+event.getMethod()+ControllerProxyCreator.EXPOSED_METHOD_SUFFIX+"(");
if (hasEventParameter)
{
creator.printlnPostProcessing(cruxEvent);
}
creator.printlnPostProcessing(");");
}
/**
*
* @param context
* @param methodName
* @param controllerClass
* @return
*/
static JMethod[] getControllerDomEventHandlers(RebindContext context, String methodName, JClassType controllerClass)
{
List<JMethod> result = new ArrayList<JMethod>();
JClassType nativeEventClass = context.getGeneratorContext().getTypeOracle().findType(NativeEvent.class.getCanonicalName());
JMethod[] methods = controllerClass.getInheritableMethods();
for (JMethod method : methods)
{
if (method.getName().equals(methodName) && method.isAnnotationPresent(Expose.class) && method.getParameterTypes().length <= 1)
{
JType[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 0)
{
result.add(method);
}
else
{
JClassType parameterClass = parameterTypes[0].isClassOrInterface();
if (parameterClass != null && parameterClass.isAssignableTo(nativeEventClass))
{
result.add(0,method);
}
}
}
}
return result.toArray(new JMethod[result.size()]);
}
/**
* @param methodName
* @param eventClassType
* @param controllerClass
* @return
*/
static JMethod getControllerMethodWithEvent(String methodName, JClassType eventClassType, JClassType controllerClass)
{
if (eventClassType == null)
{
return null;
}
JGenericType genericType = eventClassType.isGenericType();
if (genericType == null)
{
return JClassUtils.getMethod(controllerClass, methodName, new JType[]{eventClassType});
}
else
{
eventClassType = genericType.getRawType();
JClassType superClass = controllerClass;
while (superClass.getSuperclass() != null)
{
JMethod[] methods = superClass.getMethods();
if (methods != null)
{
for (JMethod method : methods)
{
JParameter[] parameters = method.getParameters();
if (method.getName().equals(methodName) && parameters != null && parameters.length==1 &&
parameters[0].getType().isClass() != null && parameters[0].getType().isClass().isAssignableTo(eventClassType))
{
return method;
}
}
}
superClass = superClass.getSuperclass();
}
return null;
}
}
/**
* @param event
* @param controller
* @param exposedMethod
* @param context
*/
private static void checkExposedMethod(Event event, String controller, JMethod exposedMethod, RebindContext context)
{
if (exposedMethod.getAnnotation(Expose.class) == null && exposedMethod.getAnnotation(Factory.class) == null)
{
throw new CruxGeneratorException(" Method ["+event.getMethod()+"] of Controller ["+controller+"] is not exposed, so it can not be called from crux.xml pages.");
}
JClassType runtimeExceptionType = context.getGeneratorContext().getTypeOracle().findType(RuntimeException.class.getCanonicalName());
JClassType[] methodThrows = exposedMethod.getThrows();
if (methodThrows != null)
{
for (JClassType exception : methodThrows)
{
if (!exception.isAssignableTo(runtimeExceptionType))
{
throw new CruxGeneratorException("Method ["+event.getMethod()+"] of Controller ["+controller+"] can not be exposed. It can throw a checked exception.");
}
}
}
}
}