/********************************************************************** * Copyright (c) 2014 HubSpot 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.hubspot.jinjava.interpret; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; import com.hubspot.jinjava.lib.Importable; import com.hubspot.jinjava.lib.exptest.ExpTest; import com.hubspot.jinjava.lib.exptest.ExpTestLibrary; import com.hubspot.jinjava.lib.filter.Filter; import com.hubspot.jinjava.lib.filter.FilterLibrary; import com.hubspot.jinjava.lib.fn.ELFunctionDefinition; import com.hubspot.jinjava.lib.fn.FunctionLibrary; import com.hubspot.jinjava.lib.fn.MacroFunction; import com.hubspot.jinjava.lib.tag.Tag; import com.hubspot.jinjava.lib.tag.TagLibrary; import com.hubspot.jinjava.tree.Node; import com.hubspot.jinjava.util.ScopeMap; public class Context extends ScopeMap<String, Object> { public static final String GLOBAL_MACROS_SCOPE_KEY = "__macros__"; private final SetMultimap<String, String> dependencies = HashMultimap.create(); private Map<Library, Set<String>> disabled; public enum Library { EXP_TEST, FILTER, FUNCTION, TAG } private final CallStack extendPathStack; private final CallStack importPathStack; private final CallStack includePathStack; private final CallStack macroStack; private final Set<String> resolvedExpressions = new HashSet<>(); private final Set<String> resolvedValues = new HashSet<>(); private final Set<String> resolvedFunctions = new HashSet<>(); private final ExpTestLibrary expTestLibrary; private final FilterLibrary filterLibrary; private final FunctionLibrary functionLibrary; private final TagLibrary tagLibrary; private final Context parent; private int renderDepth = -1; private Boolean autoEscape; private List<? extends Node> superBlock; public Context() { this(null, null, null); } public Context(Context parent) { this(parent, null, null); } public Context(Context parent, Map<String, ?> bindings) { this(parent, bindings, null); } public Context(Context parent, Map<String, ?> bindings, Map<Library, Set<String>> disabled) { super(parent); this.disabled = disabled; if (bindings != null) { this.putAll(bindings); } this.parent = parent; this.extendPathStack = new CallStack(parent == null ? null : parent.getExtendPathStack(), ExtendsTagCycleException.class); this.importPathStack = new CallStack(parent == null ? null : parent.getImportPathStack(), ImportTagCycleException.class); this.includePathStack = new CallStack(parent == null ? null : parent.getIncludePathStack(), IncludeTagCycleException.class); this.macroStack = new CallStack(parent == null ? null : parent.getMacroStack(), MacroTagCycleException.class); if (disabled == null) { disabled = new HashMap<Library, Set<String>>(); } this.expTestLibrary = new ExpTestLibrary(parent == null, disabled.get(Library.EXP_TEST)); this.filterLibrary = new FilterLibrary(parent == null, disabled.get(Library.FILTER)); this.tagLibrary = new TagLibrary(parent == null, disabled.get(Library.TAG)); this.functionLibrary = new FunctionLibrary(parent == null, disabled.get(Library.FUNCTION)); } @Override public Context getParent() { return parent; } public Map<String, Object> getSessionBindings() { return this.getScope(); } @SuppressWarnings("unchecked") public Map<String, MacroFunction> getGlobalMacros() { Map<String, MacroFunction> macros = (Map<String, MacroFunction>) getScope().get(GLOBAL_MACROS_SCOPE_KEY); if (macros == null) { macros = new HashMap<>(); getScope().put(GLOBAL_MACROS_SCOPE_KEY, macros); } return macros; } public void addGlobalMacro(MacroFunction macro) { getGlobalMacros().put(macro.getName(), macro); } public MacroFunction getGlobalMacro(String identifier) { MacroFunction fn = getGlobalMacros().get(identifier); if (fn == null && parent != null) { fn = parent.getGlobalMacro(identifier); } return fn; } public boolean isGlobalMacro(String identifier) { return getGlobalMacro(identifier) != null; } public boolean isAutoEscape() { if (autoEscape != null) { return autoEscape.booleanValue(); } if (parent != null) { return parent.isAutoEscape(); } return false; } public void setAutoEscape(Boolean autoEscape) { this.autoEscape = autoEscape; } public void addResolvedExpression(String expression) { resolvedExpressions.add(expression); } public Set<String> getResolvedExpressions() { return ImmutableSet.copyOf(resolvedExpressions); } public boolean wasExpressionResolved(String expression) { return resolvedExpressions.contains(expression); } public void addResolvedValue(String value) { resolvedValues.add(value); } public Set<String> getResolvedValues() { return ImmutableSet.copyOf(resolvedValues); } public boolean wasValueResolved(String value) { return resolvedValues.contains(value); } public Set<String> getResolvedFunctions() { return ImmutableSet.copyOf(resolvedFunctions); } public void addResolvedFunction(String value) { resolvedFunctions.add(value); } public List<? extends Node> getSuperBlock() { if (superBlock != null) { return superBlock; } if (parent != null) { return parent.getSuperBlock(); } return null; } public void setSuperBlock(List<? extends Node> superBlock) { this.superBlock = superBlock; } public void removeSuperBlock() { this.superBlock = null; } @SafeVarargs @SuppressWarnings("unchecked") public final void registerClasses(Class<? extends Importable>... classes) { for (Class<? extends Importable> c : classes) { if (ExpTest.class.isAssignableFrom(c)) { expTestLibrary.registerClasses((Class<? extends ExpTest>) c); } else if (Filter.class.isAssignableFrom(c)) { filterLibrary.registerClasses((Class<? extends Filter>) c); } else if (Tag.class.isAssignableFrom(c)) { tagLibrary.registerClasses((Class<? extends Tag>) c); } } } public Collection<ExpTest> getAllExpTests() { List<ExpTest> expTests = new ArrayList<>(expTestLibrary.entries()); if (parent != null) { expTests.addAll(parent.getAllExpTests()); } return expTests; } public ExpTest getExpTest(String name) { ExpTest t = expTestLibrary.getExpTest(name); if (t != null) { return t; } if (parent != null) { return parent.getExpTest(name); } return null; } public void registerExpTest(ExpTest t) { expTestLibrary.addExpTest(t); } public Collection<Filter> getAllFilters() { List<Filter> filters = new ArrayList<>(filterLibrary.entries()); if (parent != null) { filters.addAll(parent.getAllFilters()); } return filters; } public Filter getFilter(String name) { Filter f = filterLibrary.getFilter(name); if (f != null) { return f; } if (parent != null) { return parent.getFilter(name); } return null; } public void registerFilter(Filter f) { filterLibrary.addFilter(f); } public boolean isFunctionDisabled(String name) { return disabled != null && disabled.getOrDefault(Library.FUNCTION, Collections.emptySet()).contains(name); } public ELFunctionDefinition getFunction(String name) { ELFunctionDefinition f = functionLibrary.getFunction(name); if (f != null) { return f; } if (parent != null) { return parent.getFunction(name); } return null; } public Collection<ELFunctionDefinition> getAllFunctions() { List<ELFunctionDefinition> fns = new ArrayList<>(functionLibrary.entries()); if (parent != null) { fns.addAll(parent.getAllFunctions()); } final Set<String> disabledFunctions = disabled == null ? new HashSet<>() : disabled.getOrDefault(Library.FUNCTION, new HashSet<>()); return fns.stream().filter(f -> !disabledFunctions.contains(f.getName())).collect(Collectors.toList()); } public void registerFunction(ELFunctionDefinition f) { functionLibrary.addFunction(f); } public Collection<Tag> getAllTags() { List<Tag> tags = new ArrayList<>(tagLibrary.entries()); if (parent != null) { tags.addAll(parent.getAllTags()); } return tags; } public Tag getTag(String name) { Tag t = tagLibrary.getTag(name); if (t != null) { return t; } if (parent != null) { return parent.getTag(name); } return null; } public void registerTag(Tag t) { tagLibrary.addTag(t); } public CallStack getExtendPathStack() { return extendPathStack; } public CallStack getImportPathStack() { return importPathStack; } public CallStack getIncludePathStack() { return includePathStack; } public CallStack getMacroStack() { return macroStack; } public int getRenderDepth() { if (renderDepth != -1) { return renderDepth; } if (parent != null) { return parent.getRenderDepth(); } return 0; } public void setRenderDepth(int renderDepth) { this.renderDepth = renderDepth; } public void addDependency(String type, String identification) { this.dependencies.get(type).add(identification); } public void addDependencies(SetMultimap<String, String> dependencies) { this.dependencies.putAll(dependencies); } public SetMultimap<String, String> getDependencies() { return this.dependencies; } }