Package com.google.template.soy.tofu.internal

Source Code of com.google.template.soy.tofu.internal.BaseTofu$RendererImpl

/*
* 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.template.soy.tofu.internal;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.google.template.soy.data.SanitizedContent;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
import com.google.template.soy.internal.base.Pair;
import com.google.template.soy.msgs.SoyMsgBundle;
import com.google.template.soy.msgs.internal.InsertMsgsVisitor;
import com.google.template.soy.parseinfo.SoyTemplateInfo;
import com.google.template.soy.shared.SoyCssRenamingMap;
import com.google.template.soy.shared.internal.ApiCallScopeUtils;
import com.google.template.soy.shared.internal.GuiceSimpleScope;
import com.google.template.soy.shared.restricted.ApiCallScopeBindingAnnotations.ApiCall;
import com.google.template.soy.sharedpasses.FindIjParamsVisitor;
import com.google.template.soy.sharedpasses.FindIjParamsVisitor.IjParamsInfo;
import com.google.template.soy.sharedpasses.MarkLocalVarDataRefsVisitor;
import com.google.template.soy.sharedpasses.RenameCssVisitor;
import com.google.template.soy.sharedpasses.opti.SimplifyVisitor;
import com.google.template.soy.sharedpasses.render.RenderException;
import com.google.template.soy.sharedpasses.render.RenderVisitor;
import com.google.template.soy.soytree.SoyFileSetNode;
import com.google.template.soy.soytree.TemplateNode;
import com.google.template.soy.soytree.TemplateRegistry;
import com.google.template.soy.tofu.SoyTofu;
import com.google.template.soy.tofu.SoyTofuException;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;


/**
* Represents a compiled Soy file set. This is the result of compiling Soy to a Java object.
*
* <p> Important: Do not use outside of Soy code (treat as superpackage-private).
*
* @author Kai Huang
*/
public class BaseTofu implements SoyTofu {


  /**
   * Injectable factory for creating an instance of this class.
   *
   * <p> Important: Do not use outside of Soy code (treat as superpackage-private).
   */
  public static interface BaseTofuFactory {

    /**
     * @param soyTree The Soy parse tree containing all the files in the Soy file set.
     * @param isCaching Whether this instance caches intermediate Soy trees after substitutions from
     *     the msgBundle and the cssRenamingMap.
     */
    public BaseTofu create(SoyFileSetNode soyTree, boolean isCaching);
  }


  /** The scope object that manages the API call scope. */
  private final GuiceSimpleScope apiCallScope;

  /** Factory for creating an instance of TofuRenderVisitor. */
  private final TofuRenderVisitorFactory tofuRenderVisitorFactory;

  /** The instanceof of SimplifyVisitor to use. */
  private final SimplifyVisitor simplifyVisitor;

  /** The Soy parse tree containing all the files in the Soy file set. */
  private final SoyFileSetNode soyTree;

  /** Whether this instance caches intermediate Soy trees after substitutions from the msgBundle
   *  and the cssRenamingMap. */
  private final boolean isCaching;

  /** Map of cached template registries. Only applicable when isCaching is true. */
  private final
  Map<Pair<SoyMsgBundle, SoyCssRenamingMap>, TemplateRegistry> cachedTemplateRegistries;

  /** The template registry used for no-caching mode of rendering. Applicable when isCaching is
   *  false or when isCaching is true but doAddToCache is false. */
  private final TemplateRegistry templateRegistryForNoCaching;

  /** Map from template node to injected params info for all templates. */
  private final ImmutableMap<TemplateNode, IjParamsInfo> templateToIjParamsInfoMap;


  /**
   * @param apiCallScope The scope object that manages the API call scope.
   * @param tofuRenderVisitorFactory Factory for creating an instance of TofuRenderVisitor.
   * @param simplifyVisitor The instance of SimplifyVisitor to use.
   * @param soyTree The Soy parse tree containing all the files in the Soy file set.
   * @param isCaching Whether this instance caches intermediate Soy trees after substitutions from
   *     the msgBundle and the cssRenamingMap.
   */
  @AssistedInject
  public BaseTofu(
      @ApiCall GuiceSimpleScope apiCallScope, TofuRenderVisitorFactory tofuRenderVisitorFactory,
      SimplifyVisitor simplifyVisitor, @Assisted SoyFileSetNode soyTree,
      @Assisted boolean isCaching) {

    this.apiCallScope = apiCallScope;
    this.tofuRenderVisitorFactory = tofuRenderVisitorFactory;
    this.simplifyVisitor = simplifyVisitor;
    this.soyTree = soyTree;
    this.isCaching = isCaching;

    if (isCaching) {
      cachedTemplateRegistries = Maps.newHashMap();
      addToCache(null, null);
    } else {
      cachedTemplateRegistries = null;
    }
    SoyFileSetNode soyTreeForNoCaching = soyTree.clone();
    templateRegistryForNoCaching = buildTemplateRegistry(soyTreeForNoCaching);
    templateToIjParamsInfoMap =
        (new FindIjParamsVisitor(templateRegistryForNoCaching)).execForAllTemplates(
            soyTreeForNoCaching);
  }


  /**
   * {@inheritDoc}
   *
   * <p> For objects of this class, the namespace is always null.
   */
  @Override public String getNamespace() {
    return null;
  }


  @Override public SoyTofu forNamespace(@Nullable String namespace) {
    return (namespace == null) ? this : new NamespacedTofu(this, namespace);
  }


  @Override public boolean isCaching() {
    return isCaching;
  }


  @Override public void addToCache(
      @Nullable SoyMsgBundle msgBundle, @Nullable SoyCssRenamingMap cssRenamingMap) {
    if (!isCaching) {
      throw new SoyTofuException("Cannot addToCache() when isCaching is false.");
    }

    apiCallScope.enter();
    try {
      ApiCallScopeUtils.seedSharedParams(
          apiCallScope, msgBundle, 0 /*use msgBundle locale's direction, ltr if null*/);
      getCachedTemplateRegistry(Pair.of(msgBundle, cssRenamingMap), true);
    } finally {
      apiCallScope.exit();
    }
  }


  @Override public Renderer newRenderer(SoyTemplateInfo templateInfo) {
    return new RendererImpl(this, templateInfo.getName());
  }


  @Override public Renderer newRenderer(String templateName) {
    return new RendererImpl(this, templateName);
  }


  @Override public ImmutableSortedSet<String> getUsedIjParamsForTemplate(
      SoyTemplateInfo templateInfo) {
    return getUsedIjParamsForTemplate(templateInfo.getName());
  }


  @Override public ImmutableSortedSet<String> getUsedIjParamsForTemplate(String templateName) {
    TemplateNode template = templateRegistryForNoCaching.getBasicTemplate(templateName);
    if (template == null) {
      throw new SoyTofuException("Template '" + templateName + "' not found.");
    }
    IjParamsInfo ijParamsInfo = templateToIjParamsInfoMap.get(template);
    // TODO: Ideally we'd check that there are no external calls, but we find that in practice many
    // users have written templates that conditionally call to undefined templates. Instead,
    // we'll return a best effor set of what we have here, and over time, we'll encourage users to
    // enforce the "assertNoExternalCalls" flag.
    return ijParamsInfo.ijParamSet;
  }


  // -----------------------------------------------------------------------------------------------
  // Private methods.


  /**
   * Builds a template registry for the given Soy tree.
   * @param soyTree The Soy tree to build a template registry for.
   * @return The newly built template registry.
   */
  private TemplateRegistry buildTemplateRegistry(SoyFileSetNode soyTree) {

    (new MarkParentNodesNeedingEnvFramesVisitor()).exec(soyTree);
    (new MarkLocalVarDataRefsVisitor()).exec(soyTree);
    return new TemplateRegistry(soyTree);
  }


  /**
   * Gets the template registry associated with the given key (a key is a pair of SoyMsgBundle and
   * SoyCssRenamingMap), optionally adding the mapping to the cache if it's not already there.
   *
   * <p> Specifically, if doAddToCache is true, then the mapping will be added to the cache if it's
   * not already there. Thus, after calling this method with doAddToCache set to true, the given key
   * is guaranteed to be found in the cache. On the other hand, if doAddToCache is false and the key
   * is not already in the cache, then this method simply returns null without modifying the cache.
   *
   * @param key The pair of SoyMsgBundle and SoyCssRenamingMap for which to retrieve the
   *     corresponding template registry.
   * @param doAddToCache Whether to add this combination to the cache in the case that it's not
   *     found in the cache.
   * @return The corresponding template registry, or null if not found in cache and doAddToCache is
   *     false.
   */
  private TemplateRegistry getCachedTemplateRegistry(
      Pair<SoyMsgBundle, SoyCssRenamingMap> key, boolean doAddToCache) {

    // This precondition check is for SimplifyVisitor, which we use below after making substitutions
    // from the SoyMsgBundle and SoyCssRenamingMap. While SimplifyVisitor will work correctly
    // outside of an active apiCallScope, always running it within the apiCallScope allows it to
    // potentially do more, such as apply bidi functions/directives that require bidiGlobalDir to be
    // in scope.
    Preconditions.checkState(apiCallScope.isActive());

    TemplateRegistry templateRegistry = cachedTemplateRegistries.get(key);
    if (templateRegistry == null) {
      if (!doAddToCache) {
        return null;
      }
      SoyFileSetNode soyTreeClone = soyTree.clone();
      (new InsertMsgsVisitor(key.first, true)).exec(soyTreeClone);
      (new RenameCssVisitor(key.second)).exec(soyTreeClone);
      simplifyVisitor.exec(soyTreeClone);
      templateRegistry = buildTemplateRegistry(soyTreeClone);
      cachedTemplateRegistries.put(key, templateRegistry);
    }
    return templateRegistry;
  }


  /**
   * Main entry point used by all of the API render() calls.
   *
   * @param templateName The full name of the template to render.
   * @param data The data to call the template with. Can be null if the template has no parameters.
   * @param ijData The injected data to call the template with. Can be null if not used.
   * @param activeDelPackageNames The set of active delegate package names, or null if none.
   * @param msgBundle The bundle of translated messages, or null to use the messages from the Soy
   *     source.
   * @param cssRenamingMap Map for renaming selectors in 'css' tags, or null if not used.
   * @param doAddToCache Whether to add the current combination of msgBundle and cssRenamingMap to
   *     the cache if it's not already there. If set to false, then falls back to the no-caching
   *     mode of rendering when not found in cache. Only applicable if isCaching is true for this
   *     BaseTofu instance.
   * @return The rendered text.
   */
  private String renderMain(
      String templateName, @Nullable SoyMapData data, @Nullable SoyMapData ijData,
      @Nullable Set<String> activeDelPackageNames, @Nullable SoyMsgBundle msgBundle,
      @Nullable SoyCssRenamingMap cssRenamingMap, boolean doAddToCache) {

    StringBuilder outputSb = new StringBuilder();
    renderMain(
        outputSb, templateName, data, ijData, activeDelPackageNames, msgBundle, cssRenamingMap,
        doAddToCache);
    return outputSb.toString();
  }


  /**
   * @param outputBuf The Appendable to write the output to.
   * @param templateName The full name of the template to render.
   * @param data The data to call the template with. Can be null if the template has no parameters.
   * @param ijData The injected data to call the template with. Can be null if not used.
   * @param activeDelPackageNames The set of active delegate package names, or null if none.
   * @param msgBundle The bundle of translated messages, or null to use the messages from the Soy
   *     source.
   * @param cssRenamingMap Map for renaming selectors in 'css' tags, or null if not used.
   * @param doAddToCache Whether to add the current combination of msgBundle and cssRenamingMap to
   *     the cache if it's not already there. If set to false, then falls back to the no-caching
   *     mode of rendering when not found in cache. Only applicable if isCaching is true for this
   *     BaseTofu instance.
   */
  private void renderMain(
      Appendable outputBuf, String templateName, @Nullable SoyMapData data,
      @Nullable SoyMapData ijData, @Nullable Set<String> activeDelPackageNames,
      @Nullable SoyMsgBundle msgBundle, @Nullable SoyCssRenamingMap cssRenamingMap,
      boolean doAddToCache) {

    if (activeDelPackageNames == null) {
      activeDelPackageNames = Collections.emptySet();
    }

    apiCallScope.enter();

    try {
      // Seed the scoped parameters.
      ApiCallScopeUtils.seedSharedParams(
          apiCallScope, msgBundle, 0 /*use msgBundle locale's direction, ltr if null*/);

      // Do the rendering.
      TemplateRegistry cachedTemplateRegistry = isCaching ?
          getCachedTemplateRegistry(Pair.of(msgBundle, cssRenamingMap), doAddToCache) : null;
      // Note: cachedTemplateRegistry may be null even when isCaching is true (specifically, if
      // doAddToCache is false).
      if (cachedTemplateRegistry != null) {
        // Note: Still need to pass msgBundle because we currently don't cache plural/select msgs.
        renderMainHelper(
            cachedTemplateRegistry, outputBuf, templateName, data, ijData, activeDelPackageNames,
            msgBundle, null);
      } else {
        renderMainHelper(
            templateRegistryForNoCaching, outputBuf, templateName, data, ijData,
            activeDelPackageNames, msgBundle, cssRenamingMap);
      }

    } finally {
      apiCallScope.exit();
    }
  }


  /**
   * Renders a template and appends the result to a StringBuilder.
   *
   * @param templateRegistry A registry of all templates.
   * @param outputBuf The Appendable to append the rendered text to.
   * @param templateName The full name of the template to render.
   * @param data The data to call the template with. Can be null if the template has no parameters.
   * @param ijData The injected data to call the template with. Can be null if not used.
   * @param activeDelPackageNames The set of active delegate package names.
   * @param msgBundle The bundle of translated messages, or null to use the messages from the Soy
   *     source.
   * @param cssRenamingMap Map for renaming selectors in 'css' tags, or null if not used.
   */
  private void renderMainHelper(
      TemplateRegistry templateRegistry, Appendable outputBuf, String templateName,
      @Nullable SoyMapData data, @Nullable SoyMapData ijData, Set<String> activeDelPackageNames,
      @Nullable SoyMsgBundle msgBundle, @Nullable SoyCssRenamingMap cssRenamingMap) {

    TemplateNode template = templateRegistry.getBasicTemplate(templateName);
    if (template == null) {
      throw new SoyTofuException("Attempting to render undefined template '" + templateName + "'.");
    }

    if (data == null) {
      data = new SoyMapData();
    }

    try {
      RenderVisitor rv = tofuRenderVisitorFactory.create(
          outputBuf, templateRegistry, data, ijData, null, activeDelPackageNames, msgBundle,
          cssRenamingMap);
      rv.exec(template);

    } catch (RenderException re) {
      throw new SoyTofuException(re);
    }
  }


  // -----------------------------------------------------------------------------------------------
  // Renderer implementation.


  /**
   * Simple implementation of the Renderer interface.
   */
  private static class RendererImpl implements Renderer {

    private final BaseTofu baseTofu;
    private final String templateName;
    private SoyMapData data;
    private SoyMapData ijData;
    private SoyMsgBundle msgBundle;
    private SoyCssRenamingMap cssRenamingMap;
    private Set<String> activeDelPackageNames;
    private boolean doAddToCache;

    /**
     * @param baseTofu The underlying BaseTofu object used to perform the rendering.
     * @param templateName The full template name (including namespace).
     */
    public RendererImpl(BaseTofu baseTofu, String templateName) {
      this.baseTofu = baseTofu;
      this.templateName = templateName;
      this.data = null;
      this.ijData = null;
      this.activeDelPackageNames = null;
      this.msgBundle = null;
      this.cssRenamingMap = null;
      this.doAddToCache = true;
    }

    @Override public Renderer setData(Map<String, ?> data) {
      this.data = (data == null) ? null : new SoyMapData(data);
      return this;
    }

    @Override public Renderer setData(SoyMapData data) {
      this.data = data;
      return this;
    }

    @Override public Renderer setIjData(Map<String, ?> ijData) {
      this.ijData = (ijData == null) ? null : new SoyMapData(ijData);
      return this;
    }

    @Override public Renderer setIjData(SoyMapData ijData) {
      this.ijData = ijData;
      return this;
    }

    @Override public Renderer setActiveDelegatePackageNames(
        Set<String> activeDelegatePackageNames) {
      this.activeDelPackageNames = activeDelegatePackageNames;
      return this;
    }

    @Override public Renderer setMsgBundle(SoyMsgBundle msgBundle) {
      this.msgBundle = msgBundle;
      return this;
    }

    @Override public Renderer setCssRenamingMap(SoyCssRenamingMap cssRenamingMap) {
      this.cssRenamingMap = cssRenamingMap;
      return this;
    }

    @Override public Renderer setDontAddToCache(boolean dontAddToCache) {
      this.doAddToCache = !dontAddToCache;
      return this;
    }

    @Override public String render() {
      return baseTofu.renderMain(
          templateName, data, ijData, activeDelPackageNames, msgBundle, cssRenamingMap,
          doAddToCache);
    }

    @Override public SanitizedContent renderAsSanitizedContent() {
      String resultString = render();
      // The no-caching registry is good enough to get content kind.
      SanitizedContent.ContentKind contentKind =
          baseTofu.templateRegistryForNoCaching.getBasicTemplate(templateName).getContentKind();
      Preconditions.checkArgument(contentKind != null,
          "renderAsSanitizedContent is only valid for templates with autoescape=\"strict\".");
      return UnsafeSanitizedContentOrdainer.ordainAsSafe(resultString, contentKind);
    }

    @Override public void render(Appendable out) {
      baseTofu.renderMain(
          out, templateName, data, ijData, activeDelPackageNames, msgBundle, cssRenamingMap,
          doAddToCache);
    }
  }


  // -----------------------------------------------------------------------------------------------
  // Old render methods.


  @Deprecated
  @Override public String render(SoyTemplateInfo templateInfo, @Nullable Map<String, ?> data,
      @Nullable SoyMsgBundle msgBundle) {
    return renderMain(
        templateInfo.getName(), (data == null) ? null : new SoyMapData(data), null, null, msgBundle,
        null, true);
  }


  @Deprecated
  @Override public String render(SoyTemplateInfo templateInfo, @Nullable SoyMapData data,
      @Nullable SoyMsgBundle msgBundle) {
    return renderMain(templateInfo.getName(), data, null, null, msgBundle, null, true);
  }


  @Deprecated
  @Override public String render(String templateName, @Nullable Map<String, ?> data,
      @Nullable SoyMsgBundle msgBundle) {
    return renderMain(
        templateName, (data == null) ? null : new SoyMapData(data), null, null, msgBundle, null,
        true);
  }


  @Deprecated
  @Override public String render(String templateName, @Nullable SoyMapData data,
      @Nullable SoyMsgBundle msgBundle) {
    return renderMain(templateName, data, null, null, msgBundle, null, true);
  }

}
TOP

Related Classes of com.google.template.soy.tofu.internal.BaseTofu$RendererImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.