| /* |
| * Copyright 2008 Google Inc. |
| * |
| * 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.google.gwt.uibinder.rebind; |
| |
| import com.google.gwt.core.ext.BadPropertyValueException; |
| import com.google.gwt.core.ext.Generator; |
| import com.google.gwt.core.ext.Generator.RunsLocal; |
| import com.google.gwt.core.ext.GeneratorContext; |
| import com.google.gwt.core.ext.PropertyOracle; |
| 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.NotFoundException; |
| import com.google.gwt.core.ext.typeinfo.TypeOracle; |
| import com.google.gwt.dev.resource.Resource; |
| import com.google.gwt.dev.resource.ResourceOracle; |
| import com.google.gwt.dev.util.Util; |
| import com.google.gwt.resources.rg.GssResourceGenerator; |
| import com.google.gwt.resources.rg.GssResourceGenerator.GssOptions; |
| import com.google.gwt.uibinder.client.UiTemplate; |
| import com.google.gwt.uibinder.rebind.messages.MessagesWriter; |
| import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle; |
| |
| import org.w3c.dom.Document; |
| import org.xml.sax.SAXParseException; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.List; |
| |
| /** |
| * Generator for implementations of {@link com.google.gwt.uibinder.client.UiBinder}. |
| */ |
| @RunsLocal(requiresProperties = { |
| "UiBinder.useSafeHtmlTemplates", "UiBinder.useLazyWidgetBuilders", "CssResource.enableGss", |
| "CssResource.conversionMode", "CssResource.gssDefaultInUiBinder"}) |
| public class UiBinderGenerator extends Generator { |
| |
| private static final String BINDER_URI = "urn:ui:com.google.gwt.uibinder"; |
| |
| private static boolean gaveLazyBuildersWarning; |
| |
| private static boolean gaveSafeHtmlWarning; |
| private static final String LAZY_WIDGET_BUILDERS_PROPERTY = "UiBinder.useLazyWidgetBuilders"; |
| |
| private static final String TEMPLATE_SUFFIX = ".ui.xml"; |
| private static final String XSS_SAFE_CONFIG_PROPERTY = "UiBinder.useSafeHtmlTemplates"; |
| |
| /** |
| * Given a UiBinder interface, return the path to its ui.xml file, suitable for any classloader to |
| * find it as a resource. |
| */ |
| private static String deduceTemplateFile(MortalLogger logger, JClassType interfaceType) |
| throws UnableToCompleteException { |
| String templateName = null; |
| UiTemplate annotation = interfaceType.getAnnotation(UiTemplate.class); |
| if (annotation == null) { |
| // if the interface is defined as a nested class, use the name of the |
| // enclosing type |
| if (interfaceType.getEnclosingType() != null) { |
| interfaceType = interfaceType.getEnclosingType(); |
| } |
| return slashify(interfaceType.getQualifiedBinaryName()) + TEMPLATE_SUFFIX; |
| } else { |
| templateName = annotation.value(); |
| if (!templateName.endsWith(TEMPLATE_SUFFIX)) { |
| logger.die("Template file name must end with " + TEMPLATE_SUFFIX); |
| } |
| |
| /* |
| * If the template file name (minus suffix) has no dots, make it relative to the binder's |
| * package, otherwise slashify the dots |
| */ |
| String unsuffixed = templateName.substring(0, templateName.lastIndexOf(TEMPLATE_SUFFIX)); |
| if (!unsuffixed.contains(".")) { |
| templateName = slashify(interfaceType.getPackage().getName()) + "/" + templateName; |
| } else { |
| templateName = slashify(unsuffixed) + TEMPLATE_SUFFIX; |
| } |
| } |
| return templateName; |
| } |
| |
| private static String slashify(String s) { |
| return s.replace(".", "/").replace("$", "."); |
| } |
| |
| private final UiBinderContext uiBinderCtx = new UiBinderContext(); |
| |
| @Override |
| public String generate(TreeLogger logger, GeneratorContext genCtx, String fqInterfaceName) |
| throws UnableToCompleteException { |
| TypeOracle oracle = genCtx.getTypeOracle(); |
| |
| JClassType interfaceType; |
| try { |
| interfaceType = oracle.getType(fqInterfaceName); |
| } catch (NotFoundException e) { |
| throw new RuntimeException(e); |
| } |
| |
| DesignTimeUtils designTime; |
| if (DesignTimeUtilsImpl.isDesignTime(fqInterfaceName)) { |
| designTime = new DesignTimeUtilsImpl(); |
| } else { |
| designTime = DesignTimeUtilsStub.EMPTY; |
| } |
| |
| String implName = interfaceType.getName().replace('.', '_') + "Impl"; |
| implName = designTime.getImplName(implName); |
| |
| String packageName = interfaceType.getPackage().getName(); |
| PrintWriterManager writers = new PrintWriterManager(genCtx, logger, packageName); |
| PrintWriter printWriter = writers.tryToMakePrintWriterFor(implName); |
| |
| if (printWriter != null) { |
| generateOnce(interfaceType, implName, printWriter, logger, oracle, |
| genCtx.getResourcesOracle(), genCtx.getPropertyOracle(), writers, designTime); |
| } |
| return packageName + "." + implName; |
| } |
| |
| private Boolean extractConfigProperty(MortalLogger logger, PropertyOracle propertyOracle, |
| String configProperty, boolean defaultValue) { |
| List<String> values; |
| try { |
| values = propertyOracle.getConfigurationProperty(configProperty).getValues(); |
| } catch (BadPropertyValueException e) { |
| logger.warn("No value found for configuration property %s.", configProperty); |
| return defaultValue; |
| } |
| |
| String value = values.get(0); |
| if (!value.equals(Boolean.FALSE.toString()) && !value.equals(Boolean.TRUE.toString())) { |
| logger.warn("Unparseable value \"%s\" found for configuration property %s", value, |
| configProperty); |
| return defaultValue; |
| } |
| |
| return Boolean.valueOf(value); |
| } |
| |
| private void generateOnce(JClassType interfaceType, String implName, |
| PrintWriter binderPrintWriter, TreeLogger treeLogger, TypeOracle oracle, |
| ResourceOracle resourceOracle, PropertyOracle propertyOracle, |
| PrintWriterManager writerManager, DesignTimeUtils designTime) |
| throws UnableToCompleteException { |
| |
| MortalLogger logger = new MortalLogger(treeLogger); |
| String templatePath = deduceTemplateFile(logger, interfaceType); |
| MessagesWriter messages = new MessagesWriter(oracle, BINDER_URI, logger, templatePath, |
| interfaceType.getPackage().getName(), implName); |
| |
| boolean useLazyWidgetBuilders = |
| useLazyWidgetBuilders(logger, propertyOracle) && !designTime.isDesignTime(); |
| FieldManager fieldManager = new FieldManager(oracle, logger, useLazyWidgetBuilders); |
| |
| GssOptions gssOptions = |
| GssResourceGenerator.getGssOptions(propertyOracle, logger.getTreeLogger()); |
| |
| UiBinderWriter uiBinderWriter = new UiBinderWriter(interfaceType, implName, templatePath, |
| oracle, logger, fieldManager, messages, designTime, uiBinderCtx, |
| useSafeHtmlTemplates(logger, propertyOracle), useLazyWidgetBuilders, BINDER_URI, |
| resourceOracle, gssOptions); |
| |
| Resource resource = getTemplateResource(logger, templatePath, resourceOracle); |
| |
| // Ensure that generated uibinder source is modified at least as often as synthesized .cssmap |
| // resources, otherwise it would be possible to synthesize a modified .cssmap resource but fail |
| // to retrigger the InlineClientBundleGenerator that processes it. |
| binderPrintWriter.println("// .ui.xml template last modified: " + resource.getLastModified()); |
| Document doc = getW3cDoc(logger, designTime, resourceOracle, templatePath, resource); |
| designTime.rememberPathForElements(doc); |
| |
| uiBinderWriter.parseDocument(doc, binderPrintWriter); |
| |
| if (messages.hasMessages()) { |
| messages.write(writerManager.makePrintWriterFor(messages.getMessagesClassName())); |
| } |
| |
| ImplicitClientBundle bundleClass = uiBinderWriter.getBundleClass(); |
| new BundleWriter(bundleClass, writerManager, oracle, logger).write(); |
| |
| writerManager.commit(); |
| } |
| |
| private Document getW3cDoc(MortalLogger logger, DesignTimeUtils designTime, |
| ResourceOracle resourceOracle, String templatePath, Resource resource) |
| throws UnableToCompleteException { |
| |
| Document doc = null; |
| try { |
| String content = designTime.getTemplateContent(templatePath); |
| if (content == null) { |
| content = Util.readStreamAsString(resource.openContents()); |
| } |
| doc = new W3cDomHelper(logger.getTreeLogger(), resourceOracle).documentFor(content, |
| resource.getPath()); |
| } catch (IOException iex) { |
| logger.die("Error opening resource:" + resource.getLocation(), iex); |
| } catch (SAXParseException e) { |
| logger.die("Error parsing XML (line " + e.getLineNumber() + "): " + e.getMessage(), e); |
| } |
| return doc; |
| } |
| |
| private Resource getTemplateResource(MortalLogger logger, String templatePath, |
| ResourceOracle resourceOracle) throws UnableToCompleteException { |
| Resource resource = resourceOracle.getResource(templatePath); |
| if (null == resource) { |
| logger.die("Unable to find resource: " + templatePath); |
| } |
| return resource; |
| } |
| |
| private Boolean useLazyWidgetBuilders(MortalLogger logger, PropertyOracle propertyOracle) { |
| Boolean rtn = |
| extractConfigProperty(logger, propertyOracle, LAZY_WIDGET_BUILDERS_PROPERTY, true); |
| if (!gaveLazyBuildersWarning && !rtn) { |
| logger.warn("Configuration property %s is false. Deprecated code generation is in play. " |
| + "This property will soon become a no-op.", LAZY_WIDGET_BUILDERS_PROPERTY); |
| gaveLazyBuildersWarning = true; |
| } |
| return rtn; |
| } |
| |
| private Boolean useSafeHtmlTemplates(MortalLogger logger, PropertyOracle propertyOracle) { |
| Boolean rtn = extractConfigProperty(logger, propertyOracle, XSS_SAFE_CONFIG_PROPERTY, true); |
| |
| if (!gaveSafeHtmlWarning && !rtn) { |
| logger.warn("Configuration property %s is false! UiBinder SafeHtml integration is off, " |
| + "leaving your users more vulnerable to cross-site scripting attacks. This property " |
| + "will soon become a no-op, and SafeHtml integration will always be on.", |
| XSS_SAFE_CONFIG_PROPERTY); |
| gaveSafeHtmlWarning = true; |
| } |
| return rtn; |
| } |
| } |