/*
* Copyright 2011 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.soytree;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.template.soy.data.SanitizedContent.ContentKind;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.SoyErrorKind;
import com.google.template.soy.shared.internal.DelTemplateSelector;
import com.google.template.soy.soytree.TemplateDelegateNode.DelTemplateKey;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.annotation.Nullable;
/**
* A registry or index of all templates in a Soy tree.
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
*/
public final class TemplateRegistry {
private static final SoyErrorKind DUPLICATE_TEMPLATES =
SoyErrorKind.of("Template ''{0}'' already defined at {1}");
private static final SoyErrorKind BASIC_AND_DELTEMPLATE_WITH_SAME_NAME =
SoyErrorKind.of("Found deltemplate {0} with the same name as a basic template at {1}.");
private static final SoyErrorKind DUPLICATE_DEFAULT_DELEGATE_TEMPLATES =
SoyErrorKind.of("Delegate template ''{0}'' already has a default defined at {1}");
private static final SoyErrorKind DUPLICATE_DELEGATE_TEMPLATES_IN_DELPACKAGE =
SoyErrorKind.of("Delegate template ''{0}'' already defined in delpackage {1}: {2}");
/** Map from basic template name to node. */
private final ImmutableMap<String, TemplateBasicNode> basicTemplatesMap;
private final DelTemplateSelector<TemplateDelegateNode> delTemplateSelector;
private final ImmutableList<TemplateNode> allTemplates;
/**
* Constructor.
*
* @param soyTree The Soy tree from which to build a template registry.
*/
public TemplateRegistry(SoyFileSetNode soyTree, ErrorReporter errorReporter) {
// ------ Iterate through all templates to collect data. ------
ImmutableList.Builder<TemplateNode> allTemplatesBuilder = ImmutableList.builder();
DelTemplateSelector.Builder<TemplateDelegateNode> delTemplateSelectorBuilder =
new DelTemplateSelector.Builder<>();
Map<String, TemplateBasicNode> basicTemplates = new LinkedHashMap<>();
Multimap<String, TemplateDelegateNode> delegateTemplates = HashMultimap.create();
for (SoyFileNode soyFile : soyTree.getChildren()) {
for (TemplateNode template : soyFile.getChildren()) {
allTemplatesBuilder.add(template);
if (template instanceof TemplateBasicNode) {
// Case 1: Basic template.
TemplateBasicNode prev =
basicTemplates.put(template.getTemplateName(), (TemplateBasicNode) template);
if (prev != null) {
errorReporter.report(
template.getSourceLocation(),
DUPLICATE_TEMPLATES,
template.getTemplateName(),
prev.getSourceLocation());
}
} else {
// Case 2: Delegate template.
TemplateDelegateNode delTemplate = (TemplateDelegateNode) template;
String delTemplateName = delTemplate.getDelTemplateName();
String delPackageName = delTemplate.getDelPackageName();
String variant = delTemplate.getDelTemplateVariant();
TemplateDelegateNode previous;
if (delPackageName == null) {
// default delegate
previous = delTemplateSelectorBuilder.addDefault(delTemplateName, variant, delTemplate);
if (previous != null) {
errorReporter.report(
delTemplate.getSourceLocation(),
DUPLICATE_DEFAULT_DELEGATE_TEMPLATES,
delTemplateName,
previous.getSourceLocation());
}
} else {
previous =
delTemplateSelectorBuilder.add(
delTemplateName, delPackageName, variant, delTemplate);
if (previous != null) {
errorReporter.report(
delTemplate.getSourceLocation(),
DUPLICATE_DELEGATE_TEMPLATES_IN_DELPACKAGE,
delTemplateName,
delPackageName,
previous.getSourceLocation());
}
}
delegateTemplates.put(delTemplateName, delTemplate);
}
}
}
// make sure no basic nodes conflict with deltemplates
for (Map.Entry<String, TemplateDelegateNode> entry : delegateTemplates.entries()) {
TemplateBasicNode basicNode = basicTemplates.get(entry.getKey());
if (basicNode != null) {
errorReporter.report(
entry.getValue().getSourceLocation(),
BASIC_AND_DELTEMPLATE_WITH_SAME_NAME,
entry.getKey(),
basicNode.getSourceLocation());
}
}
// ------ Build the final data structures. ------
basicTemplatesMap = ImmutableMap.copyOf(basicTemplates);
delTemplateSelector = delTemplateSelectorBuilder.build();
this.allTemplates = allTemplatesBuilder.build();
}
/** Returns a map from basic template name to node. */
public ImmutableMap<String, TemplateBasicNode> getBasicTemplatesMap() {
return basicTemplatesMap;
}
/**
* Retrieves a basic template given the template name.
*
* @param templateName The basic template name to retrieve.
* @return The corresponding basic template, or null if the template name is not defined.
*/
@Nullable
public TemplateBasicNode getBasicTemplate(String templateName) {
return basicTemplatesMap.get(templateName);
}
/** Returns a multimap from delegate template name to set of keys. */
public DelTemplateSelector<TemplateDelegateNode> getDelTemplateSelector() {
return delTemplateSelector;
}
/**
* Returns all registered templates ({@link TemplateBasicNode basic} and {@link
* TemplateDelegateNode delegate} nodes), in no particular order.
*/
public ImmutableList<TemplateNode> getAllTemplates() {
return allTemplates;
}
/**
* Selects a delegate template based on the rendering rules, given the delegate template key (name
* and variant) and the set of active delegate package names.
*
* @param delTemplateKey The delegate template key (name and variant) to select an implementation
* for.
* @param activeDelPackageNameSelector The predicate for testing whether a given delpackage is
* active.
* @return The selected delegate template, or null if there are no active implementations.
* @throws IllegalArgumentException If there are two or more active implementations with equal
* priority (unable to select one over the other).
*/
@Nullable
public TemplateDelegateNode selectDelTemplate(
DelTemplateKey delTemplateKey, Predicate<String> activeDelPackageNameSelector) {
// TODO(lukes): eliminate this method and DelTemplateKey
return delTemplateSelector.selectTemplate(
delTemplateKey.name(), delTemplateKey.variant(), activeDelPackageNameSelector);
}
/**
* Gets the content kind that a call results in. If used with delegate calls, the delegate
* templates must use strict autoescaping. This relies on the fact that all delegate calls must
* have the same kind when using strict autoescaping. This is enforced by CheckDelegatesVisitor.
*
* @param node The {@link CallBasicNode} or {@link CallDelegateNode}.
* @return The kind of content that the call results in.
*/
public Optional<ContentKind> getCallContentKind(CallNode node) {
TemplateNode templateNode = null;
if (node instanceof CallBasicNode) {
String calleeName = ((CallBasicNode) node).getCalleeName();
templateNode = getBasicTemplate(calleeName);
} else {
String calleeName = ((CallDelegateNode) node).getDelCalleeName();
ImmutableList<TemplateDelegateNode> templateNodes =
getDelTemplateSelector().delTemplateNameToValues().get(calleeName);
// For per-file compilation, we may not have any of the delegate templates in the compilation
// unit.
if (!templateNodes.isEmpty()) {
templateNode = templateNodes.get(0);
}
}
// The template node may be null if the template is being compiled in isolation.
if (templateNode == null) {
return Optional.absent();
}
Preconditions.checkState(
templateNode instanceof TemplateBasicNode
|| templateNode.getAutoescapeMode() == AutoescapeMode.STRICT,
"Cannot determine the content kind for a delegate template that does not use strict "
+ "autoescaping.");
return Optional.of(templateNode.getContentKind());
}
}