package com.guit.rebind.guitview; import com.google.gwt.core.ext.BadPropertyValueException; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.SelectionProperty; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; 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.JType; import com.google.gwt.core.ext.typeinfo.NotFoundException; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.dom.client.TagName; import com.google.gwt.editor.client.Editor.Ignore; import com.google.gwt.editor.client.Editor.Path; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.DataResource; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.uibinder.rebind.UiBinderGenerator; import com.google.inject.Inject; import com.guit.client.ViewProperties; import com.guit.client.binder.ViewField; import com.guit.rebind.binder.GuitBinderGenerator; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXParseException; import java.io.File; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Set; public class GuitViewHelper { static final String BINDER_URI = "urn:ui:com.google.gwt.uibinder"; private static void findUiBundleFields(Set<GuitViewField> list, NodeList childNodes, String binderPrefix, TreeLogger logger, Set<String> providedFields) throws UnableToCompleteException { for (int n = 0; n < childNodes.getLength(); n++) { Node item = childNodes.item(n); if (item.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) item; String field = element.getAttribute("field"); String namespace = element.getAttribute("type"); String name = item.getNodeName(); if (name.startsWith(binderPrefix)) { name = name.substring(binderPrefix.length() + 1); if (name.equals("style")) { if (field.isEmpty()) { field = "style"; } if (namespace.isEmpty()) { namespace = CssResource.class.getCanonicalName(); } } else if (name.equals("image")) { namespace = ImageResource.class.getCanonicalName(); } else if (name.equals("data")) { namespace = DataResource.class.getCanonicalName(); } list.add(new GuitViewField(field, namespace, "@UiField" + (providedFields.contains(field) ? "(provided=true) " : " ") + " public " + namespace + " " + field + ";")); } } } } static HashMap<String, Long> cacheTimes = new HashMap<String, Long>(); static HashMap<String, Set<GuitViewField>> cache = new HashMap<String, Set<GuitViewField>>(); public static Set<GuitViewField> findUiFields(JClassType presenterClass, TreeLogger logger, TypeOracle typeOracle, JClassType editorPojo, HashMap<String, String> editorPaths, String template) throws UnableToCompleteException { String key = presenterClass.getQualifiedSourceName(); try { Long lastModified = getUiTemplateURL(presenterClass, template, logger).openConnection() .getLastModified(); if (lastModified == cacheTimes.get(lastModified)) { return cache.get(key); } else { cacheTimes.put(key, lastModified); } } catch (Exception e) { throw new RuntimeException(e); } Set<GuitViewField> list = new HashSet<GuitViewField>(); Element documentElement = getW3cDoc(presenterClass, template, logger) .getDocumentElement(); // Finds all provided fields HashSet<String> providedFields = getProvidedFields(presenterClass, logger); String binderPrefix = documentElement.lookupPrefix(BINDER_URI); String uiFieldAttribute = binderPrefix + ":field"; findUiFields(list, documentElement, uiFieldAttribute, logger, typeOracle, providedFields, editorPojo, editorPaths); findUiBundleFields(list, documentElement.getChildNodes(), binderPrefix, logger, providedFields); cache.put(key, list); return list; } public static HashSet<String> getProvidedFields(JClassType baseClass, TreeLogger logger) throws UnableToCompleteException { HashSet<String> providedFields = new HashSet<String>(); ArrayList<JField> fields = new ArrayList<JField>(); GuitBinderGenerator.collectAllFields(baseClass, fields); 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() || f.isAnnotationPresent(Inject.class) || f.isAnnotationPresent(javax.inject.Inject.class)) { if (providedFields.contains(name)) { logger.log(Type.ERROR, "There can be only one provided @ViewField for a field. Found: " + baseClass.getQualifiedSourceName() + "." + f.getName()); throw new UnableToCompleteException(); } providedFields.add(name); } } } return providedFields; } private static void findUiFields(Set<GuitViewField> list, Element node, String uiFieldAttribute, TreeLogger logger, TypeOracle typeOracle, Set<String> providedFields, JClassType editorPojo, HashMap<String, String> editorPaths) throws UnableToCompleteException { String field = node.getAttribute(uiFieldAttribute); if (node.hasAttribute(uiFieldAttribute)) { String prefix = node.getPrefix(); String namespace = node.lookupNamespaceURI(prefix); String name; if (namespace != null) { // Widgets if (namespace.startsWith("urn:import:")) { namespace = namespace.substring(11); } else { logger.log(TreeLogger.ERROR, String.format("Bad namespace. Found: %s", node.toString())); throw new UnableToCompleteException(); } name = node.getNodeName().substring(prefix.length() + 1); } else { // Html elements name = findGwtDomElementTypeForTag(node.getNodeName(), typeOracle); namespace = "com.google.gwt.dom.client"; } // GwtEditor String path = editorPojo != null && editorPaths.containsKey(field) ? "@" + Path.class.getCanonicalName() + "(\"" + editorPaths.get(field) + "\") " : ""; JMethod method = null; if (editorPojo != null) { try { method = editorPojo.getMethod("get" + field.substring(0, 1).toUpperCase() + field.substring(1), new JType[0]); } catch (NotFoundException e) { // Do nothing } } String ignore = editorPojo != null && path.isEmpty() && (editorPojo.getField(field) == null && method == null) ? "@" + Ignore.class.getCanonicalName() + " " : ""; String type = namespace + "." + name; list.add(new GuitViewField(field, type, path + ignore + "@UiField" + (providedFields.contains(field) ? "(provided=true) " : " ") + " public " + type + " " + field + ";")); } NodeList children = node.getChildNodes(); for (int n = 0; n < children.getLength(); n++) { Node item = children.item(n); if (item.getNodeType() == Node.ELEMENT_NODE) { findUiFields(list, (Element) item, uiFieldAttribute, logger, typeOracle, providedFields, editorPojo, editorPaths); } } } public static boolean isOnViewPackage(JClassType baseClass, GeneratorContext context, TreeLogger logger) throws UnableToCompleteException { URL url = getDefaultUiTemplateURL(baseClass, context, logger); String path = url.getPath(); path = (path.endsWith("ui.xml")) ? path.substring(0, path.lastIndexOf("/")) : path; return path.endsWith("/view"); } public static URL getDefaultUiTemplateURL(JClassType presenterClass, GeneratorContext context, TreeLogger logger) throws UnableToCompleteException { return getUiTemplateURL(presenterClass, getDeclaredTemplateName(presenterClass, context, logger), logger); } public static URL getUiTemplateURL(JClassType presenterClass, String templateFile, TreeLogger logger) throws UnableToCompleteException { URL url = getResource(presenterClass, templateFile); if (url == null) { url = getResource(presenterClass, "view/" + templateFile); } if (null == url) { logger.log(TreeLogger.ERROR, "Unable to find resource: " + templateFile); throw new UnableToCompleteException(); } return url; } public static boolean uiTemplateExists(JClassType presenterClass, String templateFile, TreeLogger logger) { URL url = getResource(presenterClass, templateFile); if (url == null) { url = getResource(presenterClass, "view/" + templateFile); } if (null == url) { return false; } return true; } private static Document getW3cDoc(JClassType presenterClass, String templateFile, TreeLogger logger) throws UnableToCompleteException { // String templateFile = getDeclaredTemplateName(baseClass); URL url = getUiTemplateURL(presenterClass, templateFile, logger); return getDocument(url, logger); } private static Document getDocument(URL url, TreeLogger logger) throws UnableToCompleteException { Document doc = null; try { doc = new W3cDomHelper(logger).documentFor(url); } catch (SAXParseException e) { logger.log( TreeLogger.ERROR, String.format("Error parsing XML (line " + e.getLineNumber() + "): " + e.getMessage(), e)); throw new UnableToCompleteException(); } return doc; } public static URL getResource(JClassType baseClass, String resourceName) { URL url = null; String packagePath = slashify(baseClass.getPackage().getName()); String templatePath = packagePath + "/" + resourceName; url = UiBinderGenerator.class.getClassLoader().getResource(templatePath); return url; } public static long lastMofified(JClassType baseClass, String resourceName) { URL url = null; String packagePath = slashify(baseClass.getPackage().getName()); String templatePath = packagePath + "/" + resourceName; url = UiBinderGenerator.class.getClassLoader().getResource(templatePath); try { return new File(url.toURI()).lastModified(); } catch (Exception e) { throw new RuntimeException("Can't find " + resourceName); } } private static String findGwtDomElementTypeForTag(String tag, TypeOracle oracle) { JClassType elementClass = oracle .findType("com.google.gwt.dom.client.Element"); JClassType[] types = elementClass.getSubtypes(); for (JClassType type : types) { TagName annotation = type.getAnnotation(TagName.class); if (annotation != null) { for (String annotationTag : annotation.value()) { if (annotationTag.equals(tag)) { return type.getName(); } } } } return elementClass.getName(); } public static String getDeclaredTemplateName(JClassType baseClass, GeneratorContext context, TreeLogger logger) throws UnableToCompleteException { String templateFile = baseClass.getSimpleSourceName() + ".ui.xml"; if (baseClass.isAnnotationPresent(ViewProperties.class)) { String template = baseClass.getAnnotation(ViewProperties.class) .template(); if (!template.isEmpty()) { templateFile = template; } } SelectionProperty uixmlprefix; try { PropertyOracle propertyOracle = context.getPropertyOracle(); uixmlprefix = propertyOracle .getSelectionProperty(logger, "ui.xml.prefix"); } catch (BadPropertyValueException e) { throw new RuntimeException(e); } String currentValue = uixmlprefix.getCurrentValue(); currentValue = "default".equals(currentValue) ? "" : currentValue; String fallbackValue = uixmlprefix.getFallbackValue(); fallbackValue = "default".equals(fallbackValue) ? "" : fallbackValue; if (uiTemplateExists(baseClass, currentValue + templateFile, logger)) { return currentValue + templateFile; } else if (uiTemplateExists(baseClass, fallbackValue + templateFile, logger)) { return fallbackValue + templateFile; } else { logger.log(Type.ERROR, "Cannot find " + currentValue + templateFile + " or " + fallbackValue + templateFile); throw new UnableToCompleteException(); } } static String slashify(String s) { return s.replace(".", "/"); } static String deslashify(String s) { return s.replace("/", "."); } }