/* * 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()); } }