sk.turn.gwtmvp.gen.ViewGenerator.java Source code

Java tutorial

Introduction

Here is the source code for sk.turn.gwtmvp.gen.ViewGenerator.java

Source

/*
 * Copyright 2016 Turn s.r.o.
 * 
 * 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 sk.turn.gwtmvp.gen;

import java.io.PrintWriter;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.IncrementalGenerator;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.Util;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;

import sk.turn.gwtmvp.client.AsyncView;
import sk.turn.gwtmvp.client.HandlerView;
import sk.turn.gwtmvp.client.HtmlElement;
import sk.turn.gwtmvp.client.HtmlHandler;
import sk.turn.gwtmvp.client.View;
import sk.turn.gwtmvp.client.ViewHtml;

public class ViewGenerator extends IncrementalGenerator {

    @Override
    public long getVersionId() {
        return 6;
    }

    @Override
    public RebindResult generateIncrementally(TreeLogger logger, GeneratorContext context, String typeName)
            throws UnableToCompleteException {
        try {
            TypeOracle typeOracle = context.getTypeOracle();
            JClassType viewType = typeOracle.getType(typeName);
            boolean asyncView = (viewType.getAnnotation(AsyncView.class) != null);
            ViewHtml viewHtml = viewType.getAnnotation(ViewHtml.class);
            String packageName = viewType.getPackage().getName();
            String generatedClassName = viewType.getName().replace('.', '_') + "Impl";
            PrintWriter w = context.tryCreate(logger, packageName, generatedClassName);
            if (w == null) {
                return new RebindResult(RebindMode.USE_ALL_CACHED, packageName + "." + generatedClassName);
            }
            JParameterizedType superType = null;
            String handlerType = null;
            for (JClassType intfc : viewType.getImplementedInterfaces()) {
                if (intfc.getQualifiedSourceName().equals(View.class.getName())
                        || intfc.getQualifiedSourceName().equals(HandlerView.class.getName())) {
                    superType = intfc.isParameterized();
                    if (intfc.getQualifiedSourceName().equals(HandlerView.class.getName())) {
                        handlerType = superType.getTypeArgs()[1].getQualifiedSourceName();
                    }
                    break;
                }
            }
            if (superType == null) {
                throw new Exception(typeName + " does not inherit from " + View.class.getName());
            }
            // If the view HTML is not inline, make sure the view HTML file exists
            Resource htmlResource = null;
            if (viewHtml == null) {
                htmlResource = context.getResourcesOracle().getResource(
                        packageName.replace('.', '/') + "/" + viewType.getSimpleSourceName() + ".html");
                if (htmlResource == null) {
                    throw new Exception("Cannot find " + viewType.getSimpleSourceName() + ".html");
                }
            }
            String rootElementType = superType.getTypeArgs()[0].getQualifiedSourceName();
            // Map all annotated methods
            Map<String, JMethod> fieldsMap = new LinkedHashMap<>();
            // elemId -> { com.google.gwt.event.dom.client.???Handler -> JMethod, ... }, ...
            Map<String, Map<String, JMethod>> handlersMap = new LinkedHashMap<>();
            for (JMethod method : viewType.getMethods()) {
                // Check element mapping
                HtmlElement elemAnn = method.getAnnotation(HtmlElement.class);
                if (elemAnn != null) {
                    if (method.getParameters().length > 0) {
                        throw new Exception("Method " + typeName + "." + method.getName()
                                + "() must have zero parameters (has " + method.getParameters().length + ").");
                    }
                    String id = elemAnn.value();
                    if (id.equals("")) {
                        id = method.getName();
                        if (id.startsWith("get") && id.length() > 3) {
                            id = id.substring(3, 4).toLowerCase() + id.substring(4);
                        }
                    }
                    fieldsMap.put(id, method);
                }
                // Check handler mapping
                HtmlHandler handlerAnn = method.getAnnotation(HtmlHandler.class);
                if (handlerAnn != null) {
                    JParameter param = (method.getParameters().length == 1 ? method.getParameters()[0] : null);
                    JClassType paramType = (param != null ? param.getType().isInterface() : null);
                    if (paramType == null
                            || !paramType.isAssignableTo(typeOracle.getType(EventHandler.class.getName()))) {
                        throw new Exception("Method " + typeName + "." + method.getName()
                                + "() must have exactly one parameter of an EventHandler subinterface (has "
                                + (paramType == null ? "none" : paramType.getQualifiedSourceName()) + ").");
                    }
                    for (String val : handlerAnn.value()) {
                        Map<String, JMethod> methods = handlersMap.get(val);
                        if (methods == null) {
                            methods = new LinkedHashMap<>();
                            handlersMap.put(val, methods);
                        }
                        if (methods.containsKey(paramType.getQualifiedSourceName())) {
                            throw new Exception(
                                    "Element \"" + val + "\" already has a " + paramType.getName() + " defined.");
                        }
                        methods.put(paramType.getQualifiedSourceName(), method);
                    }
                }
            }
            // Check whether dictionary class is defined
            String html = (viewHtml != null ? viewHtml.value()
                    : Util.readStreamAsString(htmlResource.openContents()));
            Matcher dictMatcher = Pattern.compile("^<[^<]+data-mvp-dict=\"(.+)\"").matcher(html);
            String dictClassName = null;
            JClassType dictClass = null;
            if (dictMatcher.find()) {
                dictClassName = dictMatcher.group(1);
                if ((dictClass = typeOracle.getType(dictClassName)) == null) {
                    throw new Exception("Localization classs " + dictClassName + " does not exist");
                }
            }
            // Check whether data-gwtid is still being used
            if (html.indexOf("data-gwtid=\"") != -1) {
                logger.log(TreeLogger.Type.WARN,
                        "The use of \"data-gwtid\" attribute is deprecated and its support will be removed in future versions, please use \"data-mvp-id\" instead.");
            }
            w.println("package " + packageName + ";");
            w.println();
            w.println("import com.google.gwt.core.client.GWT;");
            w.println("import com.google.gwt.dom.client.Element;");
            w.println("import com.google.gwt.dom.client.Document;");
            w.println("import com.google.gwt.dom.client.NodeList;");
            w.println("import com.google.gwt.event.dom.client.DomEvent;");
            w.println("import com.google.gwt.event.shared.HandlerManager;");
            w.println("import com.google.gwt.resources.client.ClientBundle;");
            w.println("import com.google.gwt.resources.client.ExternalTextResource;");
            w.println("import com.google.gwt.resources.client.ResourceCallback;");
            w.println("import com.google.gwt.resources.client.ResourceException;");
            w.println("import com.google.gwt.resources.client.TextResource;");
            w.println("import com.google.gwt.user.client.Event;");
            w.println("import com.google.gwt.user.client.EventListener;");
            w.println("import java.util.HashMap;");
            w.println("import java.util.Map;");
            w.println("import java.util.logging.Logger;");
            w.println();
            w.println("public class " + generatedClassName + " implements " + viewType.getQualifiedSourceName()
                    + " {");
            if (htmlResource != null) {
                w.println("  interface Resources extends ClientBundle {");
                w.println("    Resources INSTANCE = GWT.create(Resources.class);");
                w.println("    @Source(\"" + viewType.getSimpleSourceName() + ".html\") "
                        + (asyncView ? "External" : "") + "TextResource htmlContent();");
                w.println("  }");
                w.println();
            }
            w.println("  private static final Logger LOG = Logger.getLogger(\"" + packageName + "."
                    + generatedClassName + "\");");
            if (asyncView) {
                w.println("  private static String sHtml = null;");
            }
            w.println();
            w.println("  private " + rootElementType + " rootElement = null;");
            w.println("  private final Map<String, Element> elementsMap = new HashMap<>();");
            if (handlerType != null) {
                w.println("  private " + handlerType + " handler;");
            }
            w.println();
            w.println("  @Override");
            w.println(
                    "  public void loadView(final ViewLoadedHandler<" + rootElementType + "> viewLoadedHandler) {");
            w.println("    if (rootElement != null) {");
            w.println("      viewLoadedHandler.onViewLoaded(rootElement);");
            w.println("    }");
            if (asyncView) {
                w.println("    if (sHtml != null) {");
                w.println("      loadView(new String(sHtml), viewLoadedHandler);");
                w.println("    } else {");
                w.println("      try {");
                w.println(
                        "        Resources.INSTANCE.htmlContent().getText(new ResourceCallback<TextResource>() {");
                w.println("          public void onSuccess(TextResource r) {");
                w.println("            sHtml = r.getText();");
                w.println("            loadView(new String(sHtml), viewLoadedHandler);");
                w.println("          }");
                w.println("          public void onError(ResourceException e) {");
                w.println("            LOG.severe(\"Failed to load " + viewType.getSimpleSourceName()
                        + ".html: \" + e);");
                w.println("            viewLoadedHandler.onViewLoaded(null);");
                w.println("          }");
                w.println("        });");
                w.println("      } catch (ResourceException e) {");
                w.println("        LOG.severe(\"Failed to load " + viewType.getSimpleSourceName()
                        + ".html: \" + e);");
                w.println("        viewLoadedHandler.onViewLoaded(null);");
                w.println("      }");
                w.println("    }");
            } else if (viewHtml != null) {
                w.println("    loadView(\"" + escapeJavaString(viewHtml.value()) + "\", viewLoadedHandler);");
            } else {
                w.println("    loadView(Resources.INSTANCE.htmlContent().getText(), viewLoadedHandler);");
            }
            w.println("  }");
            w.println();
            w.println("  private void loadView(String html, ViewLoadedHandler<" + rootElementType
                    + "> viewLoadedHandler) {");
            // Replace any dictionary entries
            new SafeHtmlBuilder();
            if (dictClassName != null) {
                w.println("    " + dictClassName + " dict = GWT.create(" + dictClassName + ".class);");
                w.println("    Object dictEntry;");
                dictMatcher = Pattern.compile("\\{mvpDict\\.([^}]+)\\}").matcher(html);
                Set<String> replacedEntries = new HashSet<>();
                while (dictMatcher.find()) {
                    String dictEntry = dictMatcher.group(1);
                    if (!replacedEntries.contains(dictEntry)) {
                        try {
                            dictClass.getMethod(dictEntry, new JType[] {});
                        } catch (NotFoundException e) {
                            throw new Exception("Localization method " + dictClassName + "." + dictEntry
                                    + "() does not exist.");
                        }
                        w.println("    dictEntry = dict." + dictEntry + "();");
                        w.println("    html = html.replace(\"" + dictMatcher.group(0)
                                + "\", dictEntry instanceof com.google.gwt.safehtml.shared.SafeHtml ? ((com.google.gwt.safehtml.shared.SafeHtml)dictEntry).asString() : dictEntry.toString());");
                        replacedEntries.add(dictEntry);
                    }
                }
            }
            w.println("    Element tempElem = Document.get().create"
                    + (rootElementType.equals("com.google.gwt.dom.client.TableRowElement") ? "TBody"
                            : rootElementType.equals("com.google.gwt.dom.client.TableCellElement") ? "TR" : "Div")
                    + "Element();");
            w.println("    tempElem.setInnerHTML(html);");
            w.println("    rootElement = (" + rootElementType + ") tempElem.getFirstChild();");
            if (dictClassName != null) {
                w.println("    rootElement.removeAttribute(\"data-mvp-dict\");");
            }
            w.println("    addElementToMap(rootElement, elementsMap);");
            w.println("    NodeList<Element> elements = rootElement.getElementsByTagName(\"*\");");
            w.println("    for (int i = 0; i < elements.getLength(); i++) {");
            w.println("      addElementToMap(elements.getItem(i), elementsMap);");
            w.println("    }");
            for (Map.Entry<String, JMethod> entry : fieldsMap.entrySet()) {
                w.println("    if (elementsMap.get(\"" + entry.getKey() + "\") == null) {");
                w.println("      LOG.severe(\"Could not find element with data-mvp-id=\\\"" + entry.getKey()
                        + "\\\" in " + viewType.getSimpleSourceName() + ".html.\");");
                w.println("    }");
            }
            for (Map.Entry<String, Map<String, JMethod>> entry : handlersMap.entrySet()) {
                w.println("    if (elementsMap.get(\"" + entry.getKey() + "\") == null) {");
                w.println("      LOG.severe(\"Could not find element with data-mvp-id=\\\"" + entry.getKey()
                        + "\\\" in " + viewType.getSimpleSourceName() + ".html.\");");
                w.println("    }");
            }
            if (handlerType != null) {
                // Map @HtmlHandlers of enclosing class
                JClassType enclosingType = viewType.getEnclosingType();
                JMethod[] methods = (enclosingType != null ? enclosingType.getMethods() : new JMethod[] {});
                for (JMethod method : methods) {
                    HtmlHandler handlerAnnotation = method.getAnnotation(HtmlHandler.class);
                    if (handlerAnnotation == null) {
                        continue;
                    }
                    String eventType = method.getParameters()[0].getType().getQualifiedSourceName();
                    String eventHandlerType = eventType.substring(0, eventType.length() - 5) + "Handler";
                    String handlerMethod = "on" + eventType.substring(0, eventType.length() - 5)
                            .substring(eventType.lastIndexOf('.') + 1);
                    for (String elemId : handlerAnnotation.value()) {
                        w.println("    sk.turn.gwtmvp.client.EventManager.setEventHandler(getElement(\"" + elemId
                                + "\"), " + eventType + ".getType(), new " + eventHandlerType + "() {");
                        w.println("      @Override");
                        w.println("      public void " + handlerMethod + "(" + eventType + " event) {");
                        w.println("        if (handler != null) {");
                        w.println("          try { handler." + method.getName() + "(event); }");
                        w.println("          catch (Exception e) { LOG.severe(\"Invoke of "
                                + enclosingType.getName() + "." + method.getName() + " failed: \" + e); }");
                        w.println("        } else {");
                        w.println("          LOG.severe(\"Ignoring " + enclosingType.getName() + "."
                                + method.getName() + " - no HandlerView.handler set\");");
                        w.println("        }");
                        w.println("      }");
                        w.println("    });");
                    }
                }
            }
            w.println("    viewLoadedHandler.onViewLoaded(rootElement);");
            w.println("  }");
            w.println();
            w.println("  @Override");
            w.println("  public " + rootElementType + " getRootElement() {");
            w.println("    return rootElement;");
            w.println("  }");
            w.println();
            w.println("  @Override");
            w.println("  public <E2 extends Element> E2 getElement(String mvpId) {");
            w.println("    return (E2) elementsMap.get(mvpId);");
            w.println("  }");
            if (handlerType != null) {
                w.println();
                w.println("  @Override");
                w.println("  public void setHandler(" + handlerType + " handler) {");
                w.println("    this.handler = handler;");
                w.println("  }");
            }
            for (Map.Entry<String, JMethod> entry : fieldsMap.entrySet()) {
                w.println();
                w.println("  @Override");
                w.println("  public " + entry.getValue().getReturnType().getQualifiedSourceName() + " "
                        + entry.getValue().getName() + "() {");
                w.println("    return elementsMap.get(\"" + entry.getKey() + "\").<"
                        + entry.getValue().getReturnType().getQualifiedSourceName() + ">cast();");
                w.println("  }");
            }
            for (JMethod method : viewType.getMethods()) {
                HtmlHandler handlerAnn = method.getAnnotation(HtmlHandler.class);
                if (handlerAnn == null) {
                    continue;
                }
                String paramType = method.getParameters()[0].getType().getQualifiedSourceName();
                String eventType = paramType.substring(0, paramType.length() - 7) + "Event.getType()";
                w.println();
                w.println("  @Override");
                w.println("  public void " + method.getName() + "(" + paramType + " handler) {");
                for (String id : handlerAnn.value()) {
                    w.println("    sk.turn.gwtmvp.client.EventManager.setEventHandler(getElement(\"" + id + "\"), "
                            + eventType + ", handler);");
                }
                w.println("  }");
            }
            w.println();
            w.println("  private void addElementToMap(Element element, Map<String, Element> elementsMap) {");
            w.println(
                    "    String attrName = (element.hasAttribute(\"data-mvp-id\") ? \"data-mvp-id\" : element.hasAttribute(\"data-gwtid\") ? \"data-gwtid\" : null);");
            w.println("    if (attrName == null) {");
            w.println("      return;");
            w.println("    }");
            w.println("    String mvpId = element.getAttribute(attrName);");
            w.println("    if (!mvpId.equals(\"\")) {");
            w.println("      element.removeAttribute(attrName);");
            w.println("      elementsMap.put(mvpId, element);");
            w.println("    }");
            w.println("  }");
            w.println("}");
            context.commit(logger, w);
            return new RebindResult(RebindMode.USE_ALL_NEW, packageName + "." + generatedClassName);
        } catch (Exception e) {
            logger.log(TreeLogger.Type.ERROR,
                    "Failed generating wrapper for class " + typeName + ": " + e.getMessage());
            throw new UnableToCompleteException();
        }
    }

    private String escapeJavaString(String str) {
        return str.replace("\\", "\\\\").replace("\"", "\\\"").replace("\b", "\\b").replace("\n", "\\n")
                .replace("\t", "\\t").replace("\f", "\\f").replace("\r", "\\r");
    }

}