/* * Copyright 2010 Gal Dolber. * * 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 com.guit.rebind.binder; import com.google.gwt.core.client.GWT; import com.google.gwt.core.ext.CachedGeneratorResult; import com.google.gwt.core.ext.RebindMode; import com.google.gwt.core.ext.RebindResult; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JField; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JPackage; import com.google.gwt.core.ext.typeinfo.JParameter; import com.google.gwt.core.ext.typeinfo.JParameterizedType; import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JRealClassType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.dev.javac.typemodel.JAbstractMethod; import com.google.gwt.dev.javac.typemodel.JRawType; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.HasNativeEvent; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.shared.EventBus; import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.logging.client.LogConfiguration; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import com.google.gwt.user.rebind.StringSourceWriter; import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver; import com.google.web.bindery.requestfactory.shared.RequestFactory; import com.guit.client.GuitEntryPoint; import com.guit.client.Implementation; import com.guit.client.ImplicitCast; import com.guit.client.Presenter; import com.guit.client.View; import com.guit.client.ViewAccesor; import com.guit.client.apt.GwtPresenter; import com.guit.client.binder.Attribute; import com.guit.client.binder.ContributorAnnotation; import com.guit.client.binder.EventBusHandler; import com.guit.client.binder.GwtEditor; import com.guit.client.binder.Plugin; import com.guit.client.binder.ViewField; import com.guit.client.binder.ViewHandler; import com.guit.client.binder.ViewInitializer; import com.guit.client.binder.ViewPool; import com.guit.client.dom.Event; import com.guit.client.dom.impl.ElementImpl; import com.guit.client.dom.impl.EventImpl; import com.guit.rebind.common.AbstractGenerator; import com.guit.rebind.gin.GinOracle; import com.guit.rebind.guitview.GuitViewField; import com.guit.rebind.guitview.GuitViewGenerator; import com.guit.rebind.guitview.GuitViewHelper; import java.io.PrintWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import elemental.events.EventListener; public class GuitBinderGenerator extends AbstractGenerator { private static final String GUIT_INFO = "guit-info"; private static Method getAnnotationsMethod; private final GuitViewGenerator guitViewGenerator = new GuitViewGenerator(); private static final String STRINGCANONICALNAME = String.class.getCanonicalName(); protected JClassType hasNativeEventType; protected JPackage domEventsPackage; protected JPackage sharedEventsPackage; protected JClassType gwtEventType; protected JClassType gwtHandlerType; static { try { getAnnotationsMethod = JAbstractMethod.class.getDeclaredMethod("getAnnotations"); getAnnotationsMethod.setAccessible(true); } catch (Exception e) { throw new RuntimeException(e); } } protected static final String handlerRegistrationName = HandlerRegistration.class .getCanonicalName(); protected static final String bindingsDeclaration = ArrayList.class.getCanonicalName() + "<" + handlerRegistrationName + "> bindings = new " + ArrayList.class.getCanonicalName() + "<" + handlerRegistrationName + ">();"; protected static final String eventBusbindingsDeclaration = ArrayList.class.getCanonicalName() + "<" + handlerRegistrationName + "> eventBusBindings = new " + ArrayList.class.getCanonicalName() + "<" + handlerRegistrationName + ">();"; protected String capitalize(String part) { return part.substring(0, 1).toUpperCase() + part.substring(1); } private boolean checkIsPresenter(JClassType clazz) throws UnableToCompleteException { if (clazz.isAssignableTo(getType(Presenter.class.getCanonicalName()))) { return true; } return false; } protected String eventClassNameToEventName(String simpleName) { String eventName; eventName = simpleName.substring(0, simpleName.length() - 5); eventName = eventName.substring(0, 1).toLowerCase() + eventName.substring(1); return eventName; } protected String eventNameToEventClassName(String eventName) { eventName = eventName.substring(0, 1).toUpperCase() + eventName.substring(1) + "Event"; return eventName; } private void findAllMethods(JClassType type, ArrayList<JMethod> methods) { JMethod[] typeMethods = type.getMethods(); methods.addAll(Arrays.asList(typeMethods)); JClassType[] interfaces = type.getImplementedInterfaces(); for (JClassType i : interfaces) { findAllMethods(i, methods); } JClassType superclass = type.getSuperclass(); if (superclass != null) { findAllMethods(superclass, methods); } } @Override protected void generate(SourceWriter writer) throws UnableToCompleteException { // Do we need to check this? JClassType presenterType = baseClass.getImplementedInterfaces()[0].isParameterized().getTypeArgs()[0]; // Presenter or controller boolean isPresenter = checkIsPresenter(presenterType); hasNativeEventType = hasNativeEventType == null ? getType(HasNativeEvent.class.getCanonicalName()) : hasNativeEventType; // If it is a parameterized type we need to find the base type to get // the right method names while (presenterType.isParameterized() != null || presenterType.isRawType() != null) { if (presenterType.isParameterized() != null) { presenterType = presenterType.isParameterized().getBaseType(); } else { presenterType = presenterType.isRawType().getBaseType(); } } checkForRepetition(presenterType); writer.println(bindingsDeclaration); writer.println(eventBusbindingsDeclaration); String viewTypeName = null; HashMap<String, JType> validBindingFieldsTypes = null; if (isPresenter) { viewTypeName = guitViewGenerator.generate(logger, context, presenterType.getQualifiedSourceName()); writer.println(viewTypeName + " view;"); // Field -> Type (for validation purpose) validBindingFieldsTypes = getValidGuitViewBindingFields(presenterType); // View pool writer.println("private static " + ViewPool.class.getCanonicalName() + " pool = new " + ViewPool.class.getCanonicalName() + "();"); } // If is an editor generate the driver boolean isGwtEditor = presenterType.isAnnotationPresent(GwtEditor.class); GwtEditor editor = null; Class<?> editorDriver = null; if (isGwtEditor) { editor = presenterType.getAnnotation(GwtEditor.class); editorDriver = editor.base(); writer.println("public static interface Driver extends " + editorDriver.getCanonicalName() + "<" + editor.pojo().getCanonicalName() + "," + viewTypeName + "> {}"); } writer.println(presenterType.getQualifiedSourceName() + " presenter;"); writer.println(EventBus.class.getCanonicalName() + " eventBus;"); writer.println("boolean isBinded = false;"); /* * Event bus binder. */ writer.println("public void bind(final " + presenterType.getQualifiedSourceName() + " presenter, final " + EventBus.class.getCanonicalName() + " eventBus) {"); writer.indent(); writer.println("this.presenter = presenter;"); writer.println("this.eventBus = eventBus != null ? eventBus : " + GuitEntryPoint.class.getCanonicalName() + ".getEventBus();"); printEventBusBindingMethods(writer, presenterType); writer.outdent(); writer.println("}"); /* * View binder. */ writer.println("public " + View.class.getCanonicalName() + " getView() {"); writer.indent(); if (isPresenter) { // If already binded return the view writer.println("if (isBinded) {"); writer.println(" return view;"); writer.println("}"); // Find a view from the pool or create a new one writer.println("if (pool.isEmpty()) {"); writer.println("view = (" + viewTypeName + ") new " + viewTypeName + "();"); ArrayList<JField> fields = new ArrayList<JField>(); collectAllFields(presenterType, fields); // Provided fields printProvidedFields(presenterType, validBindingFieldsTypes, writer, false, fields); writer.println("view.bind();"); writer.println("} else {"); writer.println("view = (" + viewTypeName + ") pool.pop();"); // Provided fields printProvidedFields(presenterType, validBindingFieldsTypes, writer, true, fields); writer.println("}"); printViewFieldBindings(writer, presenterType, viewTypeName, validBindingFieldsTypes, fields); printViewBindingMethods(writer, presenterType, viewTypeName, validBindingFieldsTypes); // Initializers printPresentersInitilizersCalls(writer, presenterType); writer.println("isBinded = true;"); // GwtEditor if (isGwtEditor) { writer.println("Driver driver = GWT.create(Driver.class);"); if (editorDriver.equals(RequestFactoryEditorDriver.class)) { JField factoryField = presenterType.getField("factory"); if (factoryField == null || !factoryField.getType().isClassOrInterface().isAssignableTo( getType(RequestFactory.class.getCanonicalName())) || factoryField.isPrivate()) { error("The presenter does not have a non-private factory field of the type RequestFactory. Found: " + presenterType.getQualifiedSourceName() + ".factory"); } writer.println("driver.initialize(eventBus, presenter.factory, view);"); } else { writer.println("driver.initialize(view);"); } writer.println("presenter.driver = driver;"); } // Return the view or null writer.println("return view;"); } else { writer.println("return null;"); } writer.outdent(); writer.println("}"); /* * Unbind */ writer.println("public void releaseView() {"); writer.indent(); if (isPresenter) { writer.println("for (" + handlerRegistrationName + " b : bindings) {b.removeHandler();}"); writer.println("bindings.clear();"); if (isPresenter) { // Recycle the view writer.println("if (view != null) {"); writer.indent(); writer.println("pool.push(view);"); writer.println("view = null;"); writer.outdent(); writer.println("}"); } writer.println("isBinded = false;"); } writer.outdent(); writer.println("}"); /* * Destroy. */ writer.println("public void destroy() {"); writer.indent(); writer.println("for (" + handlerRegistrationName + " b : eventBusBindings) {b.removeHandler();}"); if (isPresenter) { writer.println("releaseView();"); } writer.println("bindings = null;"); writer.println("eventBusBindings = null;"); writer.println("presenter = null;"); writer.println("eventBus = null;"); writer.outdent(); writer.println("}"); } private HashMap<String, Object> makeUnitData(JClassType presenterType, boolean isPresenter) throws UnableToCompleteException { HashMap<String, Object> data = new HashMap<String, Object>(); data.put("presenter", getModificationTime(presenterType)); if (isPresenter) { data.put("view.ui.xml", GuitViewHelper.lastMofified(presenterType, "view/" + GuitViewHelper.getDeclaredTemplateName(presenterType, context, logger))); } return data; } private Object getModificationTime(JClassType type) { if (type instanceof JRealClassType) { JRealClassType sourceRealType = (JRealClassType) type; return sourceRealType.getLastModifiedTime(); } else if (type instanceof JRawType) { JRawType r = (JRawType) type; return r.getBaseType().getLastModifiedTime(); } else { throw new RuntimeException(type.getQualifiedSourceName() + " " + type.getClass().getCanonicalName()); } } private void printProvidedFields(JClassType baseClass, HashMap<String, JType> validBindingFieldsTypes, SourceWriter writer, boolean fromPool, ArrayList<JField> fields) throws UnableToCompleteException { for (JField f : fields) { if (f.isAnnotationPresent(ViewField.class)) { String name = f.getAnnotation(ViewField.class).name(); if (name.isEmpty()) { name = f.getName(); } if (f.getAnnotation(ViewField.class).provided()) { if (!fromPool) { // Sanity assert writer.println("assert (" + validBindingFieldsTypes.get(name).getQualifiedSourceName() + ")presenter." + f.getName() + " != null: \"You forgot to provide the field, make sure it is not null. Found: " + baseClass + "." + name + "\";"); writer.println("view." + name + " = (" + validBindingFieldsTypes.get(name).getQualifiedSourceName() + ")presenter." + f.getName() + ";"); } else { writer.println("presenter." + f.getName() + " = (" + validBindingFieldsTypes.get(name).getQualifiedSourceName() + ")view." + name + ";"); } } } } } // TODO cache public static void collectAllFields(JClassType clazz, ArrayList<JField> list) { for (JField jField : clazz.getFields()) { list.add(jField); } JClassType superclass = clazz.getSuperclass(); if (superclass != null) { collectAllFields(superclass, list); } } /** * Find event in dom, shared or context locations. Context = '{currentPackage}/event'. Context * events have priority over shared and dom ones. */ protected JClassType getEventByName(String eventName, JPackage contextEventsPackage) { // Get event class name eventName = eventNameToEventClassName(eventName); // Find in context if (contextEventsPackage != null) { JClassType contextEventType = contextEventsPackage.findType(eventName); if (contextEventType != null) { return contextEventType; } } // Find in dom events JClassType domEventType = domEventsPackage.findType(eventName); if (domEventType != null) { return domEventType; } // Find in shared events JClassType sharedEventType = sharedEventsPackage.findType(eventName); if (sharedEventType != null) { return sharedEventType; } return null; } /** * Retrieves the handler associated with the event. * * @throws UnableToCompleteException */ protected JClassType getHandlerForEvent(JClassType eventType) throws UnableToCompleteException { return eventType.findMethod("getAssociatedType", new JType[0]).getReturnType() .isParameterized().getTypeArgs()[0]; } private HashMap<String, JType> getValidGuitViewBindingFields(JClassType presenterType) throws UnableToCompleteException { Set<GuitViewField> findUiFields = GuitViewHelper.findUiFields(presenterType, logger, typeOracle, null, null, "view/" + GuitViewHelper.getDeclaredTemplateName(presenterType, context, logger)); HashMap<String, JType> fields = new HashMap<String, JType>(); for (GuitViewField f : findUiFields) { JClassType type = getType(f.getType()); if (type == null) { error("The type '%s' of the field '%s' doesn't exists. Found: %s.ui.xml", type, f.getName(), presenterType.getQualifiedSourceName()); } fields.put(f.getName(), type); } return fields; } /** * Finds the valid methods and check all the conventions. */ protected void printEventBusBindingMethods(SourceWriter writer, JClassType presenterType) throws UnableToCompleteException { JPackage contextEventsPackage = getPackage(presenterType.getPackage().getName() + ".event"); for (JMethod m : presenterType.getMethods()) { if (m.isAnnotationPresent(EventBusHandler.class)) { EventBusHandler eventBusHandler = m.getAnnotation(EventBusHandler.class); String name = m.getName(); String presenterName = presenterType.getQualifiedSourceName(); validateHandler(m, name, presenterName); // Find the event type JParameter[] parameters = m.getParameters(); if (!name.startsWith("$") && !name.startsWith("eventBus$")) { error( "Bad method name: %s on class: %s, the method should start with '$' or 'eventBus$'", name, presenterName); } // Clean the name if (name.startsWith("$")) { name = name.substring(1); // Cut off the $ } else { name = name.substring(9); // Cut off the eventBus$ } JClassType eventType = getType(eventBusHandler.value().getCanonicalName()); if (eventType.equals(gwtEventType)) { eventType = getEventByName(name, contextEventsPackage); if (eventType == null) { error( "There is no context, dom or shared event with the name '%s'. Binding method: '%s' in class: '%s'", name, m.getName(), presenterType.getQualifiedSourceName()); } } StringBuilder bindingParameters = new StringBuilder(); ArrayList<String> parameterStrings = new ArrayList<String>(); for (JParameter p : parameters) { String parameter = p.getName(); if (bindingParameters.length() > 0) { bindingParameters.append(", "); } int initlenght = bindingParameters.length(); // Implicit cast bindingParameters.append("(" + p.getType().getErasedType().getParameterizedQualifiedSourceName() + ")"); String getter = "get"; // Check if it is a boolean then the getter is 'is' not // 'get' JPrimitiveType parameterTypeIsPrimitive = p.getType().isPrimitive(); if (parameterTypeIsPrimitive != null && parameterTypeIsPrimitive.equals(JPrimitiveType.BOOLEAN)) { getter = "is"; } // Event getter binding if (parameter.indexOf("$") == -1) { // Event binding bindingParameters.append("event."); bindingParameters.append(getter); bindingParameters.append(capitalize(parameter)); bindingParameters.append("()"); } else { // Event binding nested binding String[] parameterParts = parameter.split("[$]"); bindingParameters.append("event"); for (int n = 0; n < parameterParts.length - 1; n++) { bindingParameters.append(".get"); bindingParameters.append(capitalize(parameterParts[n])); bindingParameters.append("()"); } bindingParameters.append("."); bindingParameters.append(getter); bindingParameters.append(capitalize(parameterParts[parameterParts.length - 1])); bindingParameters.append("()"); } parameterStrings.add(bindingParameters.substring(initlenght, bindingParameters.length())); } // Find the event name String simpleName = eventType.getSimpleSourceName(); if (!simpleName.endsWith("Event")) { error("The event %s does not use the event convention. It should end with 'Event'", eventType.getQualifiedSourceName()); } String eventName = eventClassNameToEventName(simpleName); // Check that the name of the event correspond to the method // name convention if (!eventName.equals(name)) { error( "The method %s on class %s does not use the event bus handler method convention. " + "It should start with '$' or 'eventBus$' " + "and end with the event name. i.e ValueChangeEvent -> $valueChange. Solve it renaming it to '$%s'", m.getName(), presenterName, eventName); } // Get event handler name JClassType handlerType = getHandlerForEvent(eventType); if (handlerType == null) { error("Parameter '%s' is not an event (subclass of GwtEvent).", eventType.getName()); } // Retrieves the single method (usually 'onSomething') related // to all // handlers. Ex: onClick in ClickHandler, onBlur in BlurHandler // ... JMethod[] methods = handlerType.getMethods(); if (methods.length != 1) { error("'%s' has more than one method defined.", handlerType.getName()); } // 'onSomething' method JMethod handlerOnMethod = methods[0]; // Checks if the method has an Event as parameter. Ex: // ClickEvent in onClick, BlurEvent in onBlur ... parameters = handlerOnMethod.getParameters(); if (parameters.length != 1) { error("Method '%s' needs '%s' as parameter", handlerOnMethod.getName(), eventType .getName()); } writer.println("eventBusBindings.add(eventBus.addHandler("); writer.println(eventType.getQualifiedSourceName() + "."); // getType or TYPE ? JField typeField = eventType.getField("TYPE"); if (typeField != null && typeField.isStatic() && typeField.isPublic()) { writer.println("TYPE"); } else { writer.println("getType()"); } writer.println(", "); writer.println("new " + handlerType.getQualifiedSourceName() + "() {"); writer.indent(); writer.println("public void " + handlerOnMethod.getName() + "(final " + eventType.getQualifiedSourceName() + " event) {"); writer.indent(); // Process contributors String bindingParametersString = bindingParameters.toString(); BinderContextImpl binderContext = processMethodContributors(presenterType, null, null, null, m, eventType, parameterStrings.toArray(new String[parameterStrings.size()])); StringSourceWriter handlerWriter = new StringSourceWriter(); handlerWriter.println("if (" + LogConfiguration.class.getCanonicalName() + ".loggingIsEnabled()) {"); handlerWriter.println(Logger.class.getCanonicalName() + ".getLogger(\"Binder\").info(\"" + binderContext.getLog() + "\");"); handlerWriter.println("}"); handlerWriter.println("presenter." + m.getName() + "(" + bindingParametersString + ");"); writer.println(binderContext.build(handlerWriter)); writer.outdent(); writer.println("}"); writer.outdent(); writer.println("}));"); } } } /** * Print calls to @PresenterInitializer methods. */ protected void printPresentersInitilizersCalls(SourceWriter writer, JClassType presenterType) throws UnableToCompleteException { for (JMethod m : presenterType.getMethods()) { if (m.isAnnotationPresent(ViewInitializer.class)) { if (m.getParameters().length > 0) { error("All @PresenterInitializer must have zero parameters. Found: %s.%s", presenterType .getQualifiedSourceName(), m.getName()); } writer.println("presenter." + m.getName() + "();"); } } JClassType superclass = presenterType.getSuperclass(); if (superclass != null && !superclass.getQualifiedSourceName().equals(Object.class.getCanonicalName())) { printPresentersInitilizersCalls(writer, superclass); } } private void printViewBindingMethods(SourceWriter writer, JClassType presenterType, String viewTypeName, HashMap<String, JType> validBindingFieldsTypes) throws UnableToCompleteException { Set<String> validBindingFields = validBindingFieldsTypes.keySet(); JPackage contextEventsPackage = getPackage(presenterType.getPackage().getName() + ".event"); ArrayList<JMethod> methods = new ArrayList<JMethod>(); findAllMethods(presenterType, methods); for (JMethod m : methods) { String name = m.getName(); if (m.isAnnotationPresent(ViewHandler.class)) { validateHandler(m, name, presenterType.getQualifiedSourceName()); String eventName; ViewHandler viewHandlerAnnotation = m.getAnnotation(ViewHandler.class); JClassType eventType = getType(viewHandlerAnnotation.event().getCanonicalName()); boolean fieldsAreElements = false; Set<String> bindingFields = null; boolean addHandlerToView = false; if (viewHandlerAnnotation.fields().length == 0) { if (name.startsWith("$")) { // Direct view binding eventName = name.substring(1); addHandlerToView = true; } else { // View fields binding String[] nameParts = name.split("[$]"); // Check the name format if (nameParts.length < 2) { error( "The method %s on the class %s have a bad binding format. It should be: " + "'{viewField}${eventName}' or for binding multiple fields: '{viewField1}${viewField2}${eventName}'", name, presenterType.getQualifiedSourceName()); } // Check that the binding fields are valid bindingFields = new HashSet<String>(); for (int n = 0; n < nameParts.length - 1; n++) { if (!validBindingFields.contains(nameParts[n])) { error( "The field %s on the class %s is not a valid binding field. It must be public or protected and not static", nameParts[n], presenterType); } bindingFields.add(nameParts[n]); } eventName = nameParts[nameParts.length - 1]; // last // token } // Check the event type and name convention if (eventType.equals(gwtEventType)) { eventType = getEventByName(eventName, contextEventsPackage); if (eventType == null) { error( "There is no context, dom or shared event with the name '%s'. Binding method: '%s' in class: '%s'", eventName, name, presenterType.getQualifiedSourceName()); } } else { // Check that the method name correspond to the event // type String eventNameToEventClassName = eventNameToEventClassName(eventName); if (!eventNameToEventClassName.equals(eventType.getSimpleSourceName())) { error( "The method '%s' in the class '%s' have a typo in the name. The last token should be : ..$%s() {.. ", name, presenterType.getQualifiedSourceName(), eventName); } } } else { String[] fields = viewHandlerAnnotation.fields(); bindingFields = new HashSet<String>(); for (String f : fields) { if (f.isEmpty()) { addHandlerToView = true; } else { if (!validBindingFields.contains(f)) { error( "The field %s on the class %s is not a valid binding field. It must be public or protected and not static", f, presenterType); } bindingFields.add(f); } } if (eventType.equals(gwtEventType)) { error( "When using ViewFields you must specify the event class in the Handler annotation. Found: %s.%s", presenterType.getQualifiedSourceName(), name); } eventName = eventClassNameToEventName(eventType.getSimpleSourceName()); } // If any field is an element all of them should be otherwise none // of them int widgetCount = 0; JClassType widgetJType = getType(Widget.class.getCanonicalName()); JClassType isWidgetJType = getType(IsWidget.class.getCanonicalName()); for (String f : bindingFields) { JClassType classOrInterface = validBindingFieldsTypes.get(f).isClassOrInterface(); if (classOrInterface.isAssignableTo(widgetJType) || classOrInterface.isAssignableTo(isWidgetJType)) { widgetCount++; } } if (widgetCount != bindingFields.size() && widgetCount != 0) { error( "Not all fields on the class %s.%s are either all elements or all widgets. You cannot bind elements and widgets on the same handler", presenterType, name); } fieldsAreElements = widgetCount == 0; /** * Find parameters bindings. The binding can be with the event(cannot have anidation of * getters):'getter'->'getGetter()' or with the view:'$getter'->'view.getGetter()' or with a * view field '{viewField$getter}'->'view.viewField.getGetter();', this last two ones will * support anidation: '{viewField$getter$another}'->'view.viewField.getGetter().getAnother ( * ) ; ' **/ StringBuilder bindingParameters = new StringBuilder(); JParameter[] parameters = m.getParameters(); ArrayList<String> parameterStrings = new ArrayList<String>(); for (JParameter p : parameters) { String parameter = p.getName(); JType parameterType = p.getType(); if (bindingParameters.length() > 0) { bindingParameters.append(", "); } int initlenght = bindingParameters.length(); // Implicit cast bindingParameters.append("(" + parameterType.getErasedType().getParameterizedQualifiedSourceName() + ")"); String getter = "get"; // Check if it is a boolean then the getter is 'is' not // 'get' JPrimitiveType parameterTypeIsPrimitive = parameterType.isPrimitive(); if (parameterTypeIsPrimitive != null && parameterTypeIsPrimitive.equals(JPrimitiveType.BOOLEAN)) { getter = "is"; } if (p.getName().equals("event")) { bindingParameters.append("event"); } else if (p.isAnnotationPresent(Attribute.class)) { // Only valid for domEvents if (!eventType.isAssignableTo(hasNativeEventType)) { error( "Attributes binding are only valid for DomEvents. Found: %s.%s in parameter: %s", presenterType.getQualifiedSourceName(), name, parameter); } String parameterTypeQualifiedSourceName = parameterType.getQualifiedSourceName(); boolean isString = parameterTypeQualifiedSourceName.equals(STRINGCANONICALNAME); if (!isString) { bindingParameters.append(parameterTypeQualifiedSourceName + ".valueOf("); } bindingParameters.append("((" + Element.class.getCanonicalName() + ")event.getNativeEvent().getEventTarget().cast()).getAttribute(\"" + parameter + "\")"); if (!isString) { bindingParameters.append(")"); } } else if (parameter.indexOf("$") == -1) { // Event binding bindingParameters.append("event."); bindingParameters.append(getter); bindingParameters.append(capitalize(parameter)); bindingParameters.append("()"); } else { // Event binding nested binding String[] parameterParts = parameter.split("[$]"); bindingParameters.append("event"); for (int n = 0; n < parameterParts.length - 1; n++) { bindingParameters.append(".get"); bindingParameters.append(capitalize(parameterParts[n])); bindingParameters.append("()"); } bindingParameters.append("."); bindingParameters.append(getter); bindingParameters.append(capitalize(parameterParts[parameterParts.length - 1])); bindingParameters.append("()"); } parameterStrings.add(bindingParameters.substring(initlenght, bindingParameters.length())); } // Get event handler name JClassType handlerType = getHandlerForEvent(eventType); if (handlerType == null) { error("Parameter '%s' is not an event (subclass of GwtEvent).", eventType.getName()); } // Retrieves the single method (usually 'onSomething') related // to all // handlers. Ex: onClick in ClickHandler, onBlur in BlurHandler // ... JMethod[] handlerMethods = handlerType.getMethods(); if (handlerMethods.length != 1) { error("'%s' has more than one method defined.", handlerType.getName()); } // 'onSomething' method JMethod handlerOnMethod = handlerMethods[0]; String methodName = name; String handlerTypeName = handlerType.getQualifiedSourceName(); GwtPresenter presenterAnnotation = presenterType.getAnnotation(GwtPresenter.class); boolean isElemental = presenterAnnotation != null && presenterAnnotation.elemental(); // Write handler SourceWriter eventHandlerWriter = new StringSourceWriter(); if (!fieldsAreElements) { eventHandlerWriter.println("new " + handlerTypeName + "() {"); eventHandlerWriter.indent(); eventHandlerWriter.println("public void " + handlerOnMethod.getName() + "(final " + eventType.getQualifiedSourceName() + " event) {"); eventHandlerWriter.indent(); } else if (isElemental) { eventHandlerWriter.println("new elemental.events.EventListener() {"); eventHandlerWriter.println(" @Override"); eventHandlerWriter.println(" public void handleEvent(elemental.events.Event event) {"); } else { eventHandlerWriter.println("new " + com.guit.client.dom.EventHandler.class.getCanonicalName() + "() {"); eventHandlerWriter.indent(); eventHandlerWriter.println("public void onEvent(" + Event.class.getCanonicalName() + " event_) {"); eventHandlerWriter.println(" " + EventImpl.class.getCanonicalName() + " event = (" + EventImpl.class.getCanonicalName() + ") event_;"); eventHandlerWriter.indent(); } String bindingParametersString = bindingParameters.toString(); // Process contributors BinderContextImpl binderContext = processMethodContributors(presenterType, null, null, viewTypeName, m, eventType, parameterStrings.toArray(new String[parameterStrings.size()])); StringSourceWriter handlerWriter = new StringSourceWriter(); handlerWriter.println("if (" + LogConfiguration.class.getCanonicalName() + ".loggingIsEnabled()) {"); handlerWriter.println(Logger.class.getCanonicalName() + ".getLogger(\"Binder\").info(\"" + binderContext.getLog() + "\");"); handlerWriter.println("}"); handlerWriter.print("presenter." + methodName + "("); handlerWriter.print(bindingParametersString); handlerWriter.println(");"); eventHandlerWriter.println(binderContext.build(handlerWriter)); eventHandlerWriter.outdent(); eventHandlerWriter.println("}"); eventHandlerWriter.outdent(); eventHandlerWriter.print("}"); if (fieldsAreElements) { if (bindingFields != null) { writer.print("final " + (isElemental ? EventListener.class.getCanonicalName() : com.guit.client.dom.EventHandler.class.getCanonicalName()) + " " + methodName + "$" + eventName + " ="); writer.print(eventHandlerWriter.toString()); writer.println(";"); for (String f : bindingFields) { String eventNameLower = eventName.toLowerCase(); boolean isTouchStart = eventNameLower.equals("touchstart"); boolean isTouchEnd = eventNameLower.equals("touchend"); if (isTouchStart || isTouchEnd) { writer.println("if (com.google.gwt.event.dom.client.TouchEvent.isSupported()) {"); } if (isElemental) { writer.println("presenter." + f + ".setOn" + eventNameLower + "(" + methodName + "$" + eventName + ");"); } else { writer.println("bindings.add(new " + ElementImpl.class.getCanonicalName() + "(view." + f + ")." + eventNameLower + "(" + methodName + "$" + eventName + "));"); } if (isTouchStart || isTouchEnd) { writer.println("} else {"); if (isElemental) { writer.println("presenter." + f + ".setOnmouse" + (isTouchStart ? "down" : "up") + "(" + methodName + "$" + eventName + ");"); } else { writer.println("bindings.add(new " + ElementImpl.class.getCanonicalName() + "(view." + f + ")." + (isTouchStart ? "mousedown" : "mouseup") + "(" + methodName + "$" + eventName + "));"); } writer.print("}"); } } } } else if (viewHandlerAnnotation.force()) { String addMethodName = "addDomHandler"; String eventTypeGetter = eventType.getQualifiedSourceName() + "."; JField typeField = eventType.getField("TYPE"); if (typeField != null && typeField.isStatic() && typeField.isPublic()) { eventTypeGetter += "TYPE"; } else { eventTypeGetter += "getType()"; } if (bindingFields != null) { writer.print("final " + handlerTypeName + " " + methodName + " ="); writer.print(eventHandlerWriter.toString()); writer.println(";"); for (String f : bindingFields) { writer.println("bindings.add(view." + f + "." + addMethodName + "(" + methodName + ", " + eventTypeGetter + "));"); } } if (addHandlerToView) { writer.print("bindings.add(view." + addMethodName + "(" + eventHandlerWriter.toString() + ", " + eventTypeGetter + "));"); } } else { String addMethodName = "add" + eventName.substring(0, 1).toUpperCase() + eventName.substring(1) + "Handler"; if (bindingFields != null) { writer.print("final " + handlerTypeName + " " + methodName + " ="); writer.print(eventHandlerWriter.toString()); writer.println(";"); for (String f : bindingFields) { // Small patch for touch events if (addMethodName.equals("addTouchStartHandler") && parameters.length == 0) { writer.println("if (!com.google.gwt.event.dom.client.TouchEvent.isSupported()) {"); writer.println("bindings.add(view." + f + ".addMouseDownHandler(new " + MouseDownHandler.class.getCanonicalName() + "(){public void onMouseDown(" + MouseDownEvent.class.getCanonicalName() + " event){presenter." + methodName + "();} }" + "));"); writer.println("}"); } if (addMethodName.equals("addTouchEndHandler") && parameters.length == 0) { writer.println("if (!com.google.gwt.event.dom.client.TouchEvent.isSupported()) {"); writer.println("bindings.add(view." + f + ".addMouseUpHandler(new " + MouseUpHandler.class.getCanonicalName() + "(){public void onMouseUp(" + MouseUpEvent.class.getCanonicalName() + " event){presenter." + methodName + "();} }" + "));"); writer.println("}"); } writer.println("bindings.add(view." + f + "." + addMethodName + "(" + methodName + "));"); } } if (addHandlerToView) { writer.print("bindings.add(view." + addMethodName + "(" + eventHandlerWriter.toString() + "));"); } } } else { for (Annotation a : m.getAnnotations()) { Class<? extends Annotation> annotationType = a.annotationType(); if (annotationType.isAnnotationPresent(Plugin.class)) { String[] nameParts = name.split("[$]"); // Check that the binding fields are valid StringBuilder fields = new StringBuilder(); for (int n = 0; n < nameParts.length - 1; n++) { if (!validBindingFields.contains(nameParts[n])) { error( "The field %s on the class %s is not a valid binding field. It must be public or protected and not static", nameParts[n], presenterType); } if (fields.length() > 0) { fields.append(","); } fields.append("view." + nameParts[n]); } Class<?> handler = annotationType.getAnnotation(Plugin.class).value(); writer.println("new " + handler.getCanonicalName() + "().install(new com.google.gwt.user.client.Command() {"); writer.println("@Override"); writer.println("public void execute() {"); writer.println(" presenter." + m.getName() + "();"); writer.println("}"); writer.println("}, new Object[]{"); writer.println(fields.toString() + "});"); } } } } } private void checkForRepetition(JClassType presenterType) throws UnableToCompleteException { ArrayList<JField> superFields = new ArrayList<JField>(); collectAllFields(presenterType.getSuperclass(), superFields); ArrayList<String> superFieldsNames = new ArrayList<String>(); for (JField jField : superFields) { superFieldsNames.add(jField.getName()); } for (JField f : presenterType.getFields()) { if (f.isAnnotationPresent(ViewField.class) || f.getName().equals("driver")) { if (superFieldsNames.contains(f.getName())) { error("The field '" + f.getName() + "' is already declared on a superclass, to fix delete the field. Found: " + presenterType.getQualifiedSourceName()); } } } } private void printViewFieldBindings(SourceWriter writer, JClassType presenterType, String viewTypeName, HashMap<String, JType> validBindingFieldsType, ArrayList<JField> fields) throws UnableToCompleteException { Set<String> validBindingFields = validBindingFieldsType.keySet(); JClassType viewAccesorType = getType(ViewAccesor.class.getCanonicalName()); ArrayList<JField> superFields = new ArrayList<JField>(); collectAllFields(presenterType.getSuperclass(), superFields); JClassType elementDomType = getType(com.guit.client.dom.Element.class.getCanonicalName()); for (JField f : fields) { if (f.isAnnotationPresent(ViewField.class)) { // Check for repetided fields if (f.getType().isClassOrInterface().isAssignableTo(elementDomType)) { if (superFields.contains(f)) { } } String name = f.getName(); ViewField viewField = f.getAnnotation(ViewField.class); String viewName = viewField.name(); if (viewName.isEmpty()) { viewName = name; } if (!validBindingFields.contains(viewName)) { error("The field '%s' do not exists. Found: %s.%s", viewName, presenterType .getQualifiedSourceName(), name); } JClassType type = f.getType().isClassOrInterface(); // if (type.isInterface() == null && !viewField.provided()) { // error("A ViewField must be an interface. Found: %s.%s", presenterType // .getQualifiedSourceName(), name); // } if (type.isAssignableTo(viewAccesorType)) { writer.println("{"); writer.indent(); if (!type.isAnnotationPresent(Implementation.class)) { writer.println(type.getQualifiedSourceName() + " accesor = " + GinOracle.getProvidedInstance(type.getQualifiedSourceName()) + ".get();"); } else { JClassType implementation = getType(type.getAnnotation(Implementation.class).value().getCanonicalName()); // If they are parameterized look for the base type JParameterizedType implementationParameterized = implementation.isParameterized(); if (implementationParameterized != null) { implementation = implementationParameterized.getBaseType(); } JParameterizedType typeParameterized = type.isParameterized(); if (typeParameterized != null) { type = typeParameterized.getBaseType(); } // Verify that they are assignable if (!implementation.isAssignableTo(type)) { error( "An implementation of a ViewAccesor must implement the ViewAccesor interface. Found: %s", implementation.getQualifiedSourceName()); } writer.println(type.getQualifiedSourceName() + " accesor = new " + implementation.getQualifiedSourceName() + "();"); } writer.println("accesor.setTarget(view." + viewName + ");"); writer.println("presenter." + name + " = accesor;"); writer.outdent(); writer.print("}"); writer.println(); } else if (type == null || type.isAssignableFrom(validBindingFieldsType.get(viewName).isClassOrInterface()) || type.getQualifiedSourceName().startsWith("elemental.")) { String qualifiedSourceName = f.getType().getQualifiedSourceName(); writer.println("presenter." + name + " = (" + qualifiedSourceName + ") view." + viewName + ";"); writer.println(); } else { // Interface emulation (without exceptions) writer.println("presenter." + name + " = new " + type.getParameterizedQualifiedSourceName() + "() {"); writer.indent(); ArrayList<JMethod> methods = new ArrayList<JMethod>(); findAllMethods(type, methods); for (JMethod m : methods) { writer.print(m.getReadableDeclaration(false, true, true, true, true)); writer.println("{"); writer.indent(); // Find the parameters StringBuilder callParameters = new StringBuilder(); for (JParameter p : m.getParameters()) { if (callParameters.length() > 0) { callParameters.append(", "); } if (p.isAnnotationPresent(ImplicitCast.class)) { callParameters.append("("); callParameters.append(p.getAnnotation(ImplicitCast.class).value() .getCanonicalName()); callParameters.append(") "); } callParameters.append(p.getName()); } JType returnType = m.getReturnType(); if (!returnType.equals(JPrimitiveType.VOID)) { writer.print("return "); // Implicit cast writer.print("(" + returnType.getParameterizedQualifiedSourceName() + ")"); } writer.indent(); writer.println(createdClassName + ".this.view." + viewName + "." + m.getName() + "(" + callParameters.toString() + ");"); writer.outdent(); writer.outdent(); writer.println("}"); writer.println(); } // Get .equals working on emulated interfaces for // event.getSource() comparations writer.println("@Override"); writer.println("public boolean equals(Object obj) {"); writer.indent(); writer.println("return view." + viewName + ".equals(obj);"); writer.outdent(); writer.println("}"); writer.println(); writer.outdent(); writer.println("};"); } } } } @Override protected void processComposer(ClassSourceFileComposerFactory composer) { gwtEventType = getType(GwtEvent.class.getCanonicalName()); gwtHandlerType = getType(EventHandler.class.getName()); domEventsPackage = getPackage("com.google.gwt.event.dom.client"); sharedEventsPackage = getPackage("com.google.gwt.event.logical.shared"); composer.addImport(GWT.class.getCanonicalName()); composer.addImplementedInterface(typeName); } protected BinderContextImpl processMethodContributors(JClassType presenterType, JClassType viewType, JClassType viewInterfaceType, String viewTypeName, JMethod method, JClassType eventType, String[] parameterGetters) throws UnableToCompleteException { Annotation[] annotations; try { annotations = (Annotation[]) getAnnotationsMethod.invoke(method); } catch (Exception e) { throw new RuntimeException(e); } BinderContextImpl binderContext = new BinderContextImpl(method, eventType, presenterType, viewTypeName, parameterGetters); for (Annotation annotation : annotations) { Class<? extends Annotation> annotationType = annotation.annotationType(); if (annotationType.isAnnotationPresent(ContributorAnnotation.class)) { BinderContributor contributor = getContributor(annotationType); binderContext.log(contributor.name()); contributor.contribute(binderContext, logger, context); } } return binderContext; } /** * Finds the contributor by convention. It have to be on the same package than the annotation but * instead of client -> rebind and it have to end with Contributor. */ public BinderContributor getContributor(Class<? extends Annotation> annotationType) { try { return (BinderContributor) Class.forName( annotationType.getCanonicalName().replace(".client.", ".rebind.") + "Contributor") .newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } protected void validateHandler(JMethod m, String name, String presenterName) throws UnableToCompleteException { if (m.isStatic()) { error("All event handlers must not be static. Found: %s.%s", presenterName, name); } if (m.isPrivate()) { error("All event handlers must not be private. Found: %s.%s", presenterName, name); } JPrimitiveType returnTypePrimitive = m.getReturnType().isPrimitive(); if (returnTypePrimitive == null || !returnTypePrimitive.equals(JPrimitiveType.VOID)) { error("All event handlers should return void. Found: %s.%s", presenterName, name); } } @Override public long getVersionId() { return 1L; } }