com.google.template.soy.jbcsrc.shared.RenderContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of soy Show documentation
Show all versions of soy Show documentation
Closure Templates are a client-side and server-side templating system.
/*
* Copyright 2015 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.jbcsrc.shared;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.template.soy.data.LoggingAdvisingAppendable;
import com.google.template.soy.data.SanitizedContent.ContentKind;
import com.google.template.soy.data.SoyRecord;
import com.google.template.soy.data.SoyValue;
import com.google.template.soy.data.restricted.StringData;
import com.google.template.soy.internal.i18n.BidiGlobalDir;
import com.google.template.soy.jbcsrc.api.RenderResult;
import com.google.template.soy.msgs.SoyMsgBundle;
import com.google.template.soy.msgs.restricted.SoyMsg;
import com.google.template.soy.msgs.restricted.SoyMsgPart;
import com.google.template.soy.shared.SoyCssRenamingMap;
import com.google.template.soy.shared.SoyIdRenamingMap;
import com.google.template.soy.shared.restricted.SoyJavaFunction;
import com.google.template.soy.shared.restricted.SoyJavaPrintDirective;
import com.ibm.icu.util.ULocale;
import java.util.Map;
import javax.annotation.Nullable;
/**
* A collection of contextual rendering data. Each top level rendering operation will obtain a
* single instance of this object and it will be propagated throughout the render tree.
*/
public final class RenderContext {
private static final CompiledTemplate EMPTY_TEMPLATE =
new CompiledTemplate() {
@Override
public RenderResult render(LoggingAdvisingAppendable appendable, RenderContext context) {
return RenderResult.done();
}
@Override
@Nullable
public ContentKind kind() {
// The kind doesn't really matter, since the empty string can always be safely escaped
return null;
}
};
// TODO(lukes): within this object most of these fields are constant across all renders while
// some are expected to change frequently (the renaming maps, msgBundle and activeDelPackages).
// Consider splitting this into two objects to represent the changing lifetimes. We are kind of
// doing this now by having SoySauceImpl reuse the Builder, but this is a little strange and could
// be theoretically made more efficient to construct.
private final Predicate activeDelPackageSelector;
private final CompiledTemplates templates;
private final SoyCssRenamingMap cssRenamingMap;
private final SoyIdRenamingMap xidRenamingMap;
private final ImmutableMap soyJavaFunctionsMap;
private final ImmutableMap soyJavaDirectivesMap;
/** The bundle of translated messages */
private final SoyMsgBundle msgBundle;
private final boolean debugSoyTemplateInfo;
private final boolean hasLogger;
private RenderContext(Builder builder) {
this.activeDelPackageSelector = checkNotNull(builder.activeDelPackageSelector);
this.templates = checkNotNull(builder.templates);
this.cssRenamingMap = builder.cssRenamingMap;
this.xidRenamingMap = builder.xidRenamingMap;
this.soyJavaFunctionsMap = builder.soyJavaFunctionsMap;
this.soyJavaDirectivesMap = builder.soyJavaDirectivesMap;
this.msgBundle = builder.msgBundle;
this.debugSoyTemplateInfo = builder.debugSoyTemplateInfo;
this.hasLogger = builder.hasLogger;
}
@Nullable
public ULocale getLocale() {
return msgBundle.getLocale();
}
public BidiGlobalDir getBidiGlobalDir() {
return BidiGlobalDir.forStaticIsRtl(msgBundle.isRtl());
}
public String renameCssSelector(String selector) {
String string = cssRenamingMap.get(selector);
return string == null ? selector : string;
}
public String renameXid(String id) {
String string = xidRenamingMap.get(id);
return string == null ? id + "_" : string;
}
public SoyJavaFunction getFunction(String name) {
SoyJavaFunction fn = soyJavaFunctionsMap.get(name);
if (fn == null) {
throw new IllegalStateException("Failed to find Soy function with name '" + name + "'");
}
return fn;
}
public SoyJavaPrintDirective getPrintDirective(String name) {
SoyJavaPrintDirective printDirective = soyJavaDirectivesMap.get(name);
if (printDirective == null) {
throw new IllegalStateException(
"Failed to find Soy print directive with name '" + name + "'");
}
return printDirective;
}
public Function getEscapingDirectiveAsFunction(String name) {
final SoyJavaPrintDirective printDirective = soyJavaDirectivesMap.get(name);
if (printDirective == null) {
throw new IllegalStateException(
"Failed to find Soy print directive with name '" + name + "'");
}
if (!printDirective.getValidArgsSizes().contains(0)) {
throw new IllegalStateException(
"Soy print directive with name '" + name + "' is not an escaping directive");
}
// TODO(lukes): this adapter is lame. there should just be a way to get the print directive to
// hand us an escaper or a function rather than writing this adapter.
return new Function() {
@Override
public String apply(String input) {
return printDirective
.applyForJava(StringData.forValue(input), ImmutableList.of())
.stringValue();
}
};
}
/**
* Returns a boolean that is used by other parts of the compiler. In particular, if this returns
* true, Soy compiler will render additional HTML comments for runtime inspections (debug only).
*/
public boolean getDebugSoyTemplateInfo() {
return debugSoyTemplateInfo;
}
/** Returns a boolean indicating whether or not there is a logger configured. */
public boolean hasLogger() {
return hasLogger;
}
public CompiledTemplate getDelTemplate(
String calleeName, String variant, boolean allowEmpty, SoyRecord params, SoyRecord ij) {
CompiledTemplate.Factory callee =
templates.selectDelTemplate(calleeName, variant, activeDelPackageSelector);
if (callee == null) {
if (allowEmpty) {
return EMPTY_TEMPLATE;
}
throw new IllegalArgumentException(
"Found no active impl for delegate call to \""
+ calleeName
+ (variant.isEmpty() ? "" : ":" + variant)
+ "\" (and delcall does not set allowemptydefault=\"true\").");
}
return callee.create(params, ij);
}
/** Returns {@code true} if the primary msg should be used instead of the fallback. */
public boolean usePrimaryMsg(long msgId, long fallbackId) {
// Note: we need to make sure the fallback msg is actually present if we are going to fallback.
// use getMsgParts() since if the bundle is a RenderOnlySoyMsgBundleImpl then this will be
// allocation free.
return !msgBundle.getMsgParts(msgId).isEmpty() || msgBundle.getMsgParts(fallbackId).isEmpty();
}
/**
* Returns the {@link SoyMsg} associated with the {@code msgId} or the fallback (aka english)
* translation if there is no such message.
*/
public ImmutableList getSoyMsgParts(
long msgId, ImmutableList defaultMsgParts) {
ImmutableList msgParts = msgBundle.getMsgParts(msgId);
if (msgParts.isEmpty()) {
return defaultMsgParts;
}
return msgParts;
}
@VisibleForTesting
public Builder toBuilder() {
return new Builder()
.withActiveDelPackageSelector(this.activeDelPackageSelector)
.withSoyFunctions(soyJavaFunctionsMap)
.withSoyPrintDirectives(soyJavaDirectivesMap)
.withCssRenamingMap(cssRenamingMap)
.withXidRenamingMap(xidRenamingMap)
.withMessageBundle(msgBundle)
.withCompiledTemplates(templates);
}
/** A builder for configuring the context. */
public static final class Builder {
private CompiledTemplates templates;
private Predicate activeDelPackageSelector = Predicates.alwaysFalse();
private SoyCssRenamingMap cssRenamingMap = SoyCssRenamingMap.EMPTY;
private SoyIdRenamingMap xidRenamingMap = SoyCssRenamingMap.EMPTY;
private ImmutableMap soyJavaFunctionsMap = ImmutableMap.of();
private ImmutableMap soyJavaDirectivesMap = ImmutableMap.of();
private SoyMsgBundle msgBundle = SoyMsgBundle.EMPTY;
private boolean debugSoyTemplateInfo = false;
private boolean hasLogger;
public Builder withCompiledTemplates(CompiledTemplates templates) {
this.templates = checkNotNull(templates);
return this;
}
public Builder withActiveDelPackageSelector(Predicate activeDelPackageSelector) {
this.activeDelPackageSelector = checkNotNull(activeDelPackageSelector);
return this;
}
public Builder withCssRenamingMap(SoyCssRenamingMap cssRenamingMap) {
this.cssRenamingMap = checkNotNull(cssRenamingMap);
return this;
}
public Builder withXidRenamingMap(SoyIdRenamingMap xidRenamingMap) {
this.xidRenamingMap = checkNotNull(xidRenamingMap);
return this;
}
public Builder withSoyFunctions(ImmutableMap functions) {
this.soyJavaFunctionsMap = functions;
return this;
}
public Builder withSoyPrintDirectives(Map directives) {
this.soyJavaDirectivesMap = ImmutableMap.copyOf(directives);
return this;
}
public Builder withMessageBundle(SoyMsgBundle msgBundle) {
this.msgBundle = checkNotNull(msgBundle);
return this;
}
public Builder withDebugSoyTemplateInfo(boolean debugSoyTemplateInfo) {
this.debugSoyTemplateInfo = debugSoyTemplateInfo;
return this;
}
public Builder hasLogger(boolean hasLogger) {
this.hasLogger = hasLogger;
return this;
}
public RenderContext build() {
return new RenderContext(this);
}
}
}