com.guit.rebind.binder.GuitBinderGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.guit.rebind.binder.GuitBinderGenerator.java

Source

/*
 * 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;
    }
}