Package com.google.template.soy.sharedpasses.render

Source Code of com.google.template.soy.sharedpasses.render.RenderVisitor

/*
* 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.sharedpasses.render;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.template.soy.data.SanitizedContent;
import com.google.template.soy.data.SoyData;
import com.google.template.soy.data.SoyDataException;
import com.google.template.soy.data.SoyListData;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.data.UnsafeSanitizedContentOrdainer;
import com.google.template.soy.data.internal.AugmentedSoyMapData;
import com.google.template.soy.data.restricted.IntegerData;
import com.google.template.soy.data.restricted.StringData;
import com.google.template.soy.data.restricted.UndefinedData;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.msgs.SoyMsgBundle;
import com.google.template.soy.shared.SoyCssRenamingMap;
import com.google.template.soy.shared.restricted.SoyJavaRuntimePrintDirective;
import com.google.template.soy.sharedpasses.render.EvalVisitor.EvalVisitorFactory;
import com.google.template.soy.soytree.AbstractSoyNodeVisitor;
import com.google.template.soy.soytree.CallBasicNode;
import com.google.template.soy.soytree.CallDelegateNode;
import com.google.template.soy.soytree.CallNode;
import com.google.template.soy.soytree.CallParamContentNode;
import com.google.template.soy.soytree.CallParamNode;
import com.google.template.soy.soytree.CallParamValueNode;
import com.google.template.soy.soytree.CssNode;
import com.google.template.soy.soytree.DebuggerNode;
import com.google.template.soy.soytree.ForNode;
import com.google.template.soy.soytree.ForeachNode;
import com.google.template.soy.soytree.ForeachNonemptyNode;
import com.google.template.soy.soytree.IfCondNode;
import com.google.template.soy.soytree.IfElseNode;
import com.google.template.soy.soytree.IfNode;
import com.google.template.soy.soytree.LetContentNode;
import com.google.template.soy.soytree.LetValueNode;
import com.google.template.soy.soytree.LogNode;
import com.google.template.soy.soytree.MsgHtmlTagNode;
import com.google.template.soy.soytree.MsgNode;
import com.google.template.soy.soytree.PrintDirectiveNode;
import com.google.template.soy.soytree.PrintNode;
import com.google.template.soy.soytree.RawTextNode;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.BlockNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;
import com.google.template.soy.soytree.SwitchCaseNode;
import com.google.template.soy.soytree.SwitchDefaultNode;
import com.google.template.soy.soytree.SwitchNode;
import com.google.template.soy.soytree.TemplateDelegateNode;
import com.google.template.soy.soytree.TemplateDelegateNode.DelTemplateKey;
import com.google.template.soy.soytree.TemplateNode;
import com.google.template.soy.soytree.TemplateRegistry;
import com.google.template.soy.soytree.TemplateRegistry.DelegateTemplateConflictException;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

/**
* Visitor for rendering the template subtree rooted at a given SoyNode.
*
* <p> Important: Do not use outside of Soy code (treat as superpackage-private).
*
* <p> The rendered output will be appended to the Appendable provided to the constructor.
*
* @author Kai Huang
*/
public class RenderVisitor extends AbstractSoyNodeVisitor<Void> {


  /** Map of all SoyJavaRuntimePrintDirectives (name to directive). */
  protected final Map<String, SoyJavaRuntimePrintDirective> soyJavaRuntimeDirectivesMap;

  /** Factory for creating an instance of EvalVisitor. */
  protected final EvalVisitorFactory evalVisitorFactory;

  /** The bundle containing all the templates that may be rendered. */
  protected final TemplateRegistry templateRegistry;

  /** The current template data. */
  protected final SoyMapData data;

  /** The current injected data. */
  protected final SoyMapData ijData;

  /** The current environment. */
  protected final Deque<Map<String, SoyData>> env;

  /** The set of active delegate package names. */
  protected final Set<String> activeDelPackageNames;

  /** The bundle of translated messages, or null to use the messages from the Soy source. */
  protected final SoyMsgBundle msgBundle;

  /** CSS renaming map. */
  protected final SoyCssRenamingMap cssRenamingMap;

  /** The EvalVisitor for this instance (can reuse since 'data' and 'env' references stay same). */
  // Note: Don't use directly. Call eval() instead.
  private EvalVisitor evalVisitor;

  /** The assistant visitor for msgs (lazily initialized). */
  private RenderVisitorAssistantForMsgs assistantForMsgs;

  /** The stack of output Appendables (current output buffer is top of stack). */
  protected Deque<Appendable> outputBufStack;

  /** The current Appendable to append the output to. Equals the top element of outputStack. */
  private Appendable currOutputBuf;


  /**
   * @param soyJavaRuntimeDirectivesMap Map of all SoyJavaRuntimePrintDirectives (name to
   *     directive). Can be null if the subclass that is calling this constructor plans to override
   *     the default implementation of {@code applyDirective()}.
   * @param evalVisitorFactory Factory for creating an instance of EvalVisitor.
   * @param outputBuf The Appendable to append the output to.
   * @param templateRegistry A registry of all templates. Should never be null (except in some unit
   *     tests).
   * @param data The current template data.
   * @param ijData The current injected data.
   * @param env The current environment, or null if this is the initial call.
   * @param activeDelPackageNames The set of active delegate package names. Allowed to be null when
   *     known to be irrelevant.
   * @param msgBundle The bundle of translated messages, or null to use the messages from the Soy
   *     source.
   * @param cssRenamingMap The CSS renaming map, or null if not applicable.
   */
  protected RenderVisitor(
      @Nullable Map<String, SoyJavaRuntimePrintDirective> soyJavaRuntimeDirectivesMap,
      EvalVisitorFactory evalVisitorFactory, Appendable outputBuf,
      @Nullable TemplateRegistry templateRegistry, SoyMapData data, @Nullable SoyMapData ijData,
      @Nullable Deque<Map<String, SoyData>> env, @Nullable Set<String> activeDelPackageNames,
      @Nullable SoyMsgBundle msgBundle, @Nullable SoyCssRenamingMap cssRenamingMap) {

    Preconditions.checkNotNull(data);

    this.soyJavaRuntimeDirectivesMap = soyJavaRuntimeDirectivesMap;
    this.evalVisitorFactory = evalVisitorFactory;
    this.templateRegistry = templateRegistry;
    this.data = data;
    this.ijData = ijData;
    this.env = (env != null) ? env : new ArrayDeque<Map<String, SoyData>>();
    this.activeDelPackageNames = activeDelPackageNames;
    this.msgBundle = msgBundle;
    this.cssRenamingMap = cssRenamingMap;

    this.evalVisitor = null// lazily initialized
    this.assistantForMsgs = null// lazily initialized

    this.outputBufStack = new ArrayDeque<Appendable>();
    pushOutputBuf(outputBuf);
  }


  /**
   * Creates a helper instance for rendering a subtemplate.
   *
   * @param outputBuf The Appendable to append the output to.
   * @param data The template data.
   * @return The newly created RenderVisitor instance.
   */
  protected RenderVisitor createHelperInstance(Appendable outputBuf, SoyMapData data) {

    return new RenderVisitor(
        soyJavaRuntimeDirectivesMap, evalVisitorFactory, outputBuf, templateRegistry,
        data, ijData, null, activeDelPackageNames, msgBundle, cssRenamingMap);
  }


  /**
   * This method must only be called by assistant visitors, in particular
   * RenderVisitorAssistantForMsgs.
   */
  void visitForUseByAssistants(SoyNode node) {
    visit(node);
  }


  // -----------------------------------------------------------------------------------------------
  // Implementations for specific nodes.


  @Override protected void visitTemplateNode(TemplateNode node) {
    try {
      visitBlockHelper(node);

    } catch (RenderException re) {
      throw (re.getTemplateName() != null) ? re : re.setTemplateName(node.getTemplateName());
    }
  }


  @Override protected void visitRawTextNode(RawTextNode node) {
    append(currOutputBuf, node.getRawText());
  }


  @Override protected void visitMsgNode(MsgNode node) {
    if (assistantForMsgs == null) {
      assistantForMsgs = new RenderVisitorAssistantForMsgs(this, env, msgBundle);
    }
    assistantForMsgs.visitForUseByMaster(node);
  }


  @Override protected void visitMsgHtmlTagNode(MsgHtmlTagNode node) {
    throw new AssertionError();
  }


  @Override protected void visitPrintNode(PrintNode node) {

    SoyData result = eval(node.getExprUnion().getExpr());
    if (result instanceof UndefinedData) {
      throw new RenderException(
          "In 'print' tag, expression \"" + node.getExprText() + "\" evaluates to undefined.");
    }

    // Process directives.
    for (PrintDirectiveNode directiveNode : node.getChildren()) {

      // Evaluate directive args.
      List<ExprRootNode<?>> argsExprs = directiveNode.getArgs();
      List<SoyData> argsSoyDatas = Lists.newArrayListWithCapacity(argsExprs.size());
      for (ExprRootNode<?> argExpr : argsExprs) {
        argsSoyDatas.add(evalVisitor.exec(argExpr));
      }

      // Apply directive.
      result = applyDirective(directiveNode.getName(), result, argsSoyDatas, node);
    }

    append(currOutputBuf, result.toString());
  }


  @Override protected void visitCssNode(CssNode node) {

    ExprRootNode<?> componentNameExpr = node.getComponentNameExpr();
    if (componentNameExpr != null) {
      append(currOutputBuf, eval(componentNameExpr).toString());
      append(currOutputBuf, "-");
    }

    // CSS statements are of the form {css selector} or {css $component, selector}.
    // We only rename the selector text. The component must derive from a previous
    // css expression and thus is already renamed.
    //
    // For example, in Javascript calling Soy:
    //   Js: var base = goog.getCssName('goog-custom-button');
    //   Soy: {css $base, hover}
    //
    // In a Soy template:
    //   {call .helper}
    //     {param base}{css goog-custom-button}{/param}
    //   {/call}
    //
    //   {template .helper}
    //     {css $base, hover}
    //   {/template}

    String selectorText = node.getSelectorText();
    if (cssRenamingMap != null) {
      String mappedText = cssRenamingMap.get(selectorText);
      if (mappedText != null) {
        selectorText = mappedText;
      }
    }
    append(currOutputBuf, selectorText);
  }


  @Override protected void visitLetValueNode(LetValueNode node) {
    env.peek().put(node.getVarName(), eval(node.getValueExpr()));
  }


  @Override protected void visitLetContentNode(LetContentNode node) {
    SoyData renderedBlock = renderBlock(node);

    // If the let node has a content kind attribute, it will have been autoescaped in the
    // corresponding context by the strict contextual autoescaper. Hence, the result of evaluating
    // the let block is wrapped in SanitizedContent of the specified kind.
    // TODO: Consider adding mutable state to nodes that allows the contextual escaper to tag
    // nodes it has processed, and assert presence of this tag here.
    if (node.getContentKind() != null) {
      renderedBlock = UnsafeSanitizedContentOrdainer.ordainAsSafe(
          renderedBlock.stringValue(), node.getContentKind());
    }

    env.peek().put(node.getVarName(), renderedBlock);
  }


  @Override protected void visitIfNode(IfNode node) {

    for (SoyNode child : node.getChildren()) {

      if (child instanceof IfCondNode) {
        IfCondNode icn = (IfCondNode) child;
        if (eval(icn.getExprUnion().getExpr()).toBoolean()) {
          visit(icn);
          return;
        }

      } else if (child instanceof IfElseNode) {
        visit(child);
        return;

      } else {
        throw new AssertionError();
      }
    }
  }


  @Override protected void visitSwitchNode(SwitchNode node) {

    SoyData switchValue = eval(node.getExpr());

    for (SoyNode child : node.getChildren()) {

      if (child instanceof SwitchCaseNode) {
        SwitchCaseNode scn = (SwitchCaseNode) child;
        for (ExprNode caseExpr : scn.getExprList()) {
          if (switchValue.equals(eval(caseExpr))) {
            visit(scn);
            return;
          }
        }

      } else if (child instanceof SwitchDefaultNode) {
        visit(child);
        return;

      } else {
        throw new AssertionError();
      }
    }
  }


  @Override protected void visitForeachNode(ForeachNode node) {

    SoyData dataRefValue = eval(node.getExpr());
    if (!(dataRefValue instanceof SoyListData)) {
      throw new RenderException(
          "In 'foreach' command " + node.toSourceString() +
          ", the data reference does not resolve to a SoyListData.");
    }
    SoyListData foreachList = (SoyListData) dataRefValue;

    if (foreachList.length() > 0) {
      // Case 1: Nonempty list.
      String varName = node.getVarName();

      Map<String, SoyData> newEnvFrame = Maps.newHashMap();
      // Note: No need to save firstIndex as it's always 0.
      newEnvFrame.put(varName + "__lastIndex", IntegerData.forValue(foreachList.length() - 1));
      env.push(newEnvFrame);

      for (int i = 0; i < foreachList.length(); ++i) {
        newEnvFrame.put(varName + "__index", IntegerData.forValue(i));
        newEnvFrame.put(varName, foreachList.get(i));
        visitChildren((ForeachNonemptyNode) node.getChild(0));
      }

      env.pop();

    } else {
      // Case 2: Empty list. If the 'ifempty' node exists, visit it.
      if (node.numChildren() == 2) {
        visit(node.getChild(1));
      }
    }
  }


  @Override protected void visitForNode(ForNode node) {

    List<Integer> rangeArgValues = Lists.newArrayList();

    for (ExprNode rangeArg : node.getRangeArgs()) {
      SoyData rangeArgValue = eval(rangeArg);
      if (!(rangeArgValue instanceof IntegerData)) {
        throw new RenderException(
            "In 'for' command " + node.toSourceString() + ", the expression \"" +
            rangeArg.toSourceString() + "\" does not resolve to an integer.");
      }
      rangeArgValues.add(((IntegerData) rangeArgValue).getValue());
    }

    int increment = (rangeArgValues.size() == 3) ? rangeArgValues.remove(2) : 1 /* default */;
    int init = (rangeArgValues.size() == 2) ? rangeArgValues.remove(0) : 0 /* default */;
    int limit = rangeArgValues.get(0);

    String localVarName = node.getVarName();
    Map<String, SoyData> newEnvFrame = Maps.newHashMap();
    env.push(newEnvFrame);

    for (int i = init; i < limit; i += increment) {
      newEnvFrame.put(localVarName, IntegerData.forValue(i));
      visitChildren(node);
    }

    env.pop();
  }


  @Override protected void visitCallBasicNode(CallBasicNode node) {

    TemplateNode callee = templateRegistry.getBasicTemplate(node.getCalleeName());
    if (callee == null) {
      throw new RenderException(
          "Attempting to render undefined template '" + node.getCalleeName() + "'.");
    }

    visitCallNodeHelper(node, callee);
  }


  @Override protected void visitCallDelegateNode(CallDelegateNode node) {

    ExprRootNode<?> variantExpr = node.getDelCalleeVariantExpr();
    String variant;
    if (variantExpr == null) {
      variant = "";
    } else {
      try {
        variant = eval(variantExpr).stringValue();
      } catch (SoyDataException e) {
        throw new RenderException(String.format(
            "Variant expression \"%s\" doesn't evaluate to a string.",
            variantExpr.toSourceString()));
      }
    }
    DelTemplateKey delegateKey = new DelTemplateKey(node.getDelCalleeName(), variant);

    TemplateDelegateNode callee;
    try {
      callee = templateRegistry.selectDelTemplate(delegateKey, activeDelPackageNames);
    } catch (DelegateTemplateConflictException e) {
      throw new RenderException(e.getMessage());
    }

    if (callee != null) {
      visitCallNodeHelper(node, callee);

    } else if (node.allowsEmptyDefault()) {
      return// no active delegate implementation, so the call output is empty string

    } else {
      throw new RenderException(
          "Found no active impl for delegate call to '" + node.getDelCalleeName() +
          "' (and no attribute allowemptydefault=\"true\").");
    }
  }


  @SuppressWarnings("ConstantConditions"// for IntelliJ
  private void visitCallNodeHelper(CallNode node, TemplateNode callee) {

    // ------ Build the call data. ------
    SoyMapData dataToPass;
    if (node.isPassingAllData()) {
      dataToPass = data;
    } else if (node.isPassingData()) {
      SoyData dataRefValue = eval(node.getDataExpr());
      if (!(dataRefValue instanceof SoyMapData)) {
        throw new RenderException(
            "In 'call' command " + node.toSourceString() +
            ", the data reference does not resolve to a SoyMapData.");
      }
      dataToPass = (SoyMapData) dataRefValue;
    } else {
      dataToPass = null;
    }

    SoyMapData callData;
    if (!node.isPassingData()) {
      // Case 1: Not passing data. Start with a fresh SoyMapData object.
      callData = new SoyMapData();
    } else if (node.numChildren() == 0) {
      // Case 2: No params. Just pass in the current data.
      callData = dataToPass;
    } else {
      // Case 3: Passing data and adding params. Need to augment the current data.
      callData = new AugmentedSoyMapData(dataToPass);
    }

    for (CallParamNode child : node.getChildren()) {

      if (child instanceof CallParamValueNode) {
        callData.putSingle(
            child.getKey(), eval(((CallParamValueNode) child).getValueExprUnion().getExpr()));

      } else if (child instanceof CallParamContentNode) {
        CallParamContentNode childCpcn = (CallParamContentNode) child;
        SoyData renderedBlock = renderBlock(childCpcn);

        // If the param node has a content kind attribute, it will have been autoescaped in the
        // corresponding context by the strict contextual autoescaper. Hence, the result of
        // evaluating the param block is wrapped in SanitizedContent of the specified kind.
        if (childCpcn.getContentKind() != null) {
          renderedBlock = UnsafeSanitizedContentOrdainer.ordainAsSafe(
              renderedBlock.stringValue(), childCpcn.getContentKind());
        }

        callData.putSingle(child.getKey(), renderedBlock);

      } else {
        throw new AssertionError();
      }
    }

    // ------ Render the callee template with the callData built above. ------

    if (node.getEscapingDirectiveNames().isEmpty()) {
      // No escaping at the call site -- render directly into the output buffer.
      RenderVisitor rv = createHelperInstance(currOutputBuf, callData);
      rv.exec(callee);
    } else {
      // Escaping the call site's result, such as at a strict template boundary.
      // TODO: Some optimization is needed here before Strict Soy can be widely used:
      // - Only create this temporary buffer when contexts mismatch. We could run a pre-pass that
      // eliminates escaping directives when all callers are known.
      // - Instead of creating a temporary buffer and copying, wrap with an escaping StringBuilder.
      StringBuilder calleeBuilder = new StringBuilder();
      RenderVisitor rv = createHelperInstance(calleeBuilder, callData);
      rv.exec(callee);
      SoyData resultData = (callee.getContentKind() != null) ?
          UnsafeSanitizedContentOrdainer.ordainAsSafe(
              calleeBuilder.toString(), callee.getContentKind()) :
          StringData.forValue(calleeBuilder.toString());
      for (String directiveName : node.getEscapingDirectiveNames()) {
        resultData = applyDirective(directiveName, resultData, ImmutableList.<SoyData>of(), node);
      }
      append(currOutputBuf, resultData.toString());
    }
  }


  @Override protected void visitCallParamNode(CallParamNode node) {
    // In this visitor, we never directly visit a CallParamNode.
    throw new AssertionError();
  }


  @Override protected void visitLogNode(LogNode node) {
    System.out.println(renderBlock(node));
  }


  @Override protected void visitDebuggerNode(DebuggerNode node) {
    // The 'debugger' statement does nothing in Java rendering, but the user could theoretically
    // place a breakpoint at this method.
  }


  // -----------------------------------------------------------------------------------------------
  // Fallback implementation.


  @Override protected void visitSoyNode(SoyNode node) {

    if (node instanceof ParentSoyNode<?>) {

      if (node instanceof BlockNode) {
        visitBlockHelper((BlockNode) node);

      } else {
        visitChildren((ParentSoyNode<?>) node);
      }
    }
  }


  // -----------------------------------------------------------------------------------------------
  // Helpers.


  /**
   * Pushes the given output buffer onto the stack (it becomes the current output buffer).
   */
  private void pushOutputBuf(Appendable outputBuf) {
    outputBufStack.push(outputBuf);
    currOutputBuf = outputBuf;
  }


  /**
   * Pops the top output buffer off the stack and returns it (changes the current output buffer).
   */
  private Appendable popOutputBuf() {
    Appendable poppedOutputBuf = outputBufStack.pop();
    currOutputBuf = outputBufStack.peek();
    return poppedOutputBuf;
  }


  /**
   * This method must only be called by assistant visitors, in particular
   * RenderVisitorAssistantForMsgs.
   */
  Appendable getCurrOutputBufForUseByAssistants() {
    return currOutputBuf;
  }


  /**
   * Helper for recursing on a block.
   * @param node The BlockNode to recurse on.
   */
  private void visitBlockHelper(BlockNode node) {

    if (node.needsEnvFrameDuringInterp() != Boolean.FALSE /*true or unknown*/) {
      env.push(Maps.<String, SoyData>newHashMap());
      visitChildren(node);
      env.pop();

    } else {
      visitChildren(node);
    }
  }


  /**
   * Private helper to render the children of a block into a separate string (not directly appended
   * to the current output buffer).
   * @param block The block whose children are to be rendered.
   * @return The result of rendering the block's children, as StringData.
   */
  private StringData renderBlock(BlockNode block) {

    pushOutputBuf(new StringBuilder());
    visitBlockHelper(block);
    Appendable outputBuf = popOutputBuf();
    return StringData.forValue(outputBuf.toString());
  }


  /**
   * Private helper to evaluate an expression. Always use this helper instead of using evalVisitor
   * directly, because this helper creates and throws a RenderException if there's an error.
   */
  private SoyData eval(ExprNode expr) {

    if (expr == null) {
      throw new RenderException("Cannot evaluate expression in V1 syntax.");
    }

    // Lazily initialize evalVisitor.
    if (evalVisitor == null) {
      evalVisitor = evalVisitorFactory.create(data, ijData, env);
    }

    try {
      return evalVisitor.exec(expr);
    } catch (Exception e) {
      Throwable cause = (e instanceof RenderException) ? e.getCause() : e;
      throw new RenderException(
          "When evaluating \"" + expr.toSourceString() + "\": " + e.getMessage(), cause);
    }
  }


  /**
   * This method must only be called by assistant visitors, in particular
   * RenderVisitorAssistantForMsgs.
   */
  SoyData evalForUseByAssistants(ExprNode expr) {
    return eval(expr);
  }


  /**
   * Helper to append text to the output, propagating any exceptions.
   */
  static void append(Appendable outputBuf, CharSequence cs) {
    try {
      outputBuf.append(cs);
    } catch (IOException e) {
      throw Throwables.propagate(e);
    }
  }


  /**
   * Protected helper to apply a print directive.
   *
   * <p> This default implementation can be overridden by subclasses (such as TofuRenderVisitor)
   * that have access to a potentially larger set of print directives.
   *
   * @param directiveName The name of the directive.
   * @param value The value to apply the directive on.
   * @param args The arguments to the directive.
   * @param node The node with the escaping. Only used for error reporting.
   * @return The result of applying the directive with the given arguments to the given value.
   */
  protected SoyData applyDirective(
      String directiveName, SoyData value, List<SoyData> args, SoyNode node) {

    // Get directive.
    SoyJavaRuntimePrintDirective directive = soyJavaRuntimeDirectivesMap.get(directiveName);
    if (directive == null) {
      throw new RenderException(
          "Failed to find Soy print directive with name '" + directiveName + "'" +
          " (tag " + node.toSourceString() + ")");
    }

    // TODO: Add a pass to check num args at compile time.
    if (! directive.getValidArgsSizes().contains(args.size())) {
      throw new RenderException(
          "Print directive '" + directiveName + "' used with the wrong number of" +
          " arguments (tag " + node.toSourceString() + ").");
    }

    try {
      return directive.apply(value, args);

    } catch (RuntimeException e) {
      throw new RenderException(String.format(
          "Failed in applying directive '%s' in tag \"%s\" due to exception: %s",
          directiveName, node.toSourceString(), e.getMessage()));
    }
  }

}
TOP

Related Classes of com.google.template.soy.sharedpasses.render.RenderVisitor

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.