diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java b/pebble/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java index 0232dba38..4f62f9424 100644 --- a/pebble/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java +++ b/pebble/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java @@ -79,9 +79,8 @@ public class PebbleEngine { * Constructor for the Pebble Engine given an instantiated Loader. This method does only load * those userProvidedExtensions listed here. * - * @param loader The template loader for this engine - * @param syntax the syntax to use for parsing the templates. - * @param extensions The userProvidedExtensions which should be loaded. + * @param loader The template loader for this engine + * @param syntax the syntax to use for parsing the templates. */ private PebbleEngine(Loader loader, Syntax syntax, @@ -90,7 +89,7 @@ private PebbleEngine(Loader loader, PebbleCache tagCache, PebbleCache templateCache, ExecutorService executorService, - Collection extensions, + ExtensionRegistry extensionRegistry, ParserOptions parserOptions, EvaluationOptions evaluationOptions) { @@ -101,7 +100,7 @@ private PebbleEngine(Loader loader, this.tagCache = tagCache; this.executorService = executorService; this.templateCache = templateCache; - this.extensionRegistry = new ExtensionRegistry(extensions); + this.extensionRegistry = extensionRegistry; this.parserOptions = parserOptions; this.evaluationOptions = evaluationOptions; } @@ -283,6 +282,8 @@ public static class Builder { private boolean greedyMatchMethod = false; + private boolean allowOverrideCoreOperators = false; + /** * Creates the builder. */ @@ -422,6 +423,17 @@ public Builder autoEscaping(boolean autoEscaping) { return this; } + /** + * Sets whether or not core operators overrides should be allowed. + * + * @param allowOverrideCoreOperators Whether or not core operators overrides should be allowed. + * @return This builder object + */ + public Builder allowOverrideCoreOperators(boolean allowOverrideCoreOperators) { + this.allowOverrideCoreOperators = allowOverrideCoreOperators; + return this; + } + /** * Sets the default escaping strategy of the built-in escaper extension. * @@ -436,7 +448,7 @@ public Builder defaultEscapingStrategy(String strategy) { /** * Adds an escaping strategy to the built-in escaper extension. * - * @param name The name of the escaping strategy + * @param name The name of the escaping strategy * @param strategy The strategy implementation * @return This builder object */ @@ -472,7 +484,7 @@ public Builder allowGetClass(boolean allowGetClass) { * Enable/disable treat literal decimal as Integer. Default is disabled, treated as Long. * * @param literalDecimalTreatedAsInteger toggle to enable/disable literal decimal treated as - * integer + * integer * @return This builder object */ public Builder literalDecimalTreatedAsInteger(boolean literalDecimalTreatedAsInteger) { @@ -513,13 +525,7 @@ public Builder greedyMatchMethod(boolean greedyMatchMethod) { */ public PebbleEngine build() { - // core extensions - List extensions = new ArrayList<>(); - extensions.add(new CoreExtension()); - extensions.add(this.escaperExtension); - extensions.add(new I18nExtension()); - extensions.addAll(this.userProvidedExtensions); - extensions.add(new AttributeResolverExtension()); + ExtensionRegistry extensionRegistry = buildExtensionRegistry(); // default loader if (this.loader == null) { @@ -562,7 +568,27 @@ public PebbleEngine build() { return new PebbleEngine(this.loader, this.syntax, this.strictVariables, this.defaultLocale, this.tagCache, this.templateCache, - this.executorService, extensions, parserOptions, evaluationOptions); + this.executorService, extensionRegistry, parserOptions, evaluationOptions); + } + + private ExtensionRegistry buildExtensionRegistry() { + ExtensionRegistry extensionRegistry = new ExtensionRegistry(); + + extensionRegistry.addExtension(new CoreExtension()); + extensionRegistry.addExtension(this.escaperExtension); + extensionRegistry.addExtension(new I18nExtension()); + + for (Extension userProvidedExtension : this.userProvidedExtensions) { + if (allowOverrideCoreOperators) { + extensionRegistry.addOperatorOverridingExtension(userProvidedExtension); + } else { + extensionRegistry.addExtension(userProvidedExtension); + } + } + + extensionRegistry.addExtension(new AttributeResolverExtension()); + + return extensionRegistry; } } diff --git a/pebble/src/main/java/com/mitchellbosecke/pebble/extension/ExtensionRegistry.java b/pebble/src/main/java/com/mitchellbosecke/pebble/extension/ExtensionRegistry.java index 71e472342..154690020 100644 --- a/pebble/src/main/java/com/mitchellbosecke/pebble/extension/ExtensionRegistry.java +++ b/pebble/src/main/java/com/mitchellbosecke/pebble/extension/ExtensionRegistry.java @@ -59,74 +59,91 @@ public class ExtensionRegistry { private final List attributeResolver = new ArrayList<>(); + public ExtensionRegistry() { + } + public ExtensionRegistry(Collection extensions) { for (Extension extension : extensions) { - // token parsers - List tokenParsers = extension.getTokenParsers(); - if (tokenParsers != null) { - for (TokenParser tokenParser : tokenParsers) { - this.tokenParsers.put(tokenParser.getTag(), tokenParser); - } + addExtension(extension); + } + } + + public void addOperatorOverridingExtension(Extension extension) { + addExtension(extension, true); + } + + public void addExtension(Extension extension) { + addExtension(extension, false); + } + + private void addExtension(Extension extension, boolean operatorOverriding) { + // token parsers + List tokenParsers = extension.getTokenParsers(); + if (tokenParsers != null) { + for (TokenParser tokenParser : tokenParsers) { + this.tokenParsers.put(tokenParser.getTag(), tokenParser); } + } - // binary operators - List binaryOperators = extension.getBinaryOperators(); - if (binaryOperators != null) { - for (BinaryOperator operator : binaryOperators) { - if (!this.binaryOperators - .containsKey(operator.getSymbol())) { // disallow overriding core operators - this.binaryOperators.put(operator.getSymbol(), operator); - } + // binary operators + List binaryOperators = extension.getBinaryOperators(); + if (binaryOperators != null) { + for (BinaryOperator operator : binaryOperators) { + if (operatorOverriding) { + this.binaryOperators.put(operator.getSymbol(), operator); + } else { + this.binaryOperators.putIfAbsent(operator.getSymbol(), operator); } } + } - // unary operators - List unaryOperators = extension.getUnaryOperators(); - if (unaryOperators != null) { - for (UnaryOperator operator : unaryOperators) { - if (!this.unaryOperators - .containsKey(operator.getSymbol())) { // disallow override core operators - this.unaryOperators.put(operator.getSymbol(), operator); - } + // unary operators + List unaryOperators = extension.getUnaryOperators(); + if (unaryOperators != null) { + for (UnaryOperator operator : unaryOperators) { + if (operatorOverriding) { + this.unaryOperators.put(operator.getSymbol(), operator); + } else { + this.unaryOperators.putIfAbsent(operator.getSymbol(), operator); } } + } - // filters - Map filters = extension.getFilters(); - if (filters != null) { - this.filters.putAll(filters); - } + // filters + Map filters = extension.getFilters(); + if (filters != null) { + this.filters.putAll(filters); + } - // tests - Map tests = extension.getTests(); - if (tests != null) { - this.tests.putAll(tests); - } + // tests + Map tests = extension.getTests(); + if (tests != null) { + this.tests.putAll(tests); + } - // tests - Map functions = extension.getFunctions(); - if (functions != null) { - this.functions.putAll(functions); - } + // functions + Map functions = extension.getFunctions(); + if (functions != null) { + this.functions.putAll(functions); + } - // global variables - Map globalVariables = extension.getGlobalVariables(); - if (globalVariables != null) { - this.globalVariables.putAll(globalVariables); - } + // global variables + Map globalVariables = extension.getGlobalVariables(); + if (globalVariables != null) { + this.globalVariables.putAll(globalVariables); + } - // node visitors - List nodeVisitors = extension.getNodeVisitors(); - if (nodeVisitors != null) { - this.nodeVisitors.addAll(nodeVisitors); - } + // node visitors + List nodeVisitors = extension.getNodeVisitors(); + if (nodeVisitors != null) { + this.nodeVisitors.addAll(nodeVisitors); + } - // attribute resolver - List attributeResolvers = extension.getAttributeResolver(); - if (attributeResolvers != null) { - this.attributeResolver.addAll(attributeResolvers); - } + // attribute resolver + List attributeResolvers = extension.getAttributeResolver(); + if (attributeResolvers != null) { + this.attributeResolver.addAll(attributeResolvers); } } diff --git a/pebble/src/test/java/com/mitchellbosecke/pebble/OverrideCoreExtensionTest.java b/pebble/src/test/java/com/mitchellbosecke/pebble/OverrideCoreExtensionTest.java index 941d95efa..a1333d0d6 100644 --- a/pebble/src/test/java/com/mitchellbosecke/pebble/OverrideCoreExtensionTest.java +++ b/pebble/src/test/java/com/mitchellbosecke/pebble/OverrideCoreExtensionTest.java @@ -1,32 +1,40 @@ package com.mitchellbosecke.pebble; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; + import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.extension.AbstractExtension; import com.mitchellbosecke.pebble.extension.Filter; import com.mitchellbosecke.pebble.extension.Function; import com.mitchellbosecke.pebble.loader.StringLoader; +import com.mitchellbosecke.pebble.node.expression.BinaryExpression; +import com.mitchellbosecke.pebble.node.expression.UnaryExpression; +import com.mitchellbosecke.pebble.operator.Associativity; +import com.mitchellbosecke.pebble.operator.BinaryOperator; +import com.mitchellbosecke.pebble.operator.BinaryOperatorImpl; +import com.mitchellbosecke.pebble.operator.UnaryOperator; +import com.mitchellbosecke.pebble.operator.UnaryOperatorImpl; import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.EvaluationContextImpl; import com.mitchellbosecke.pebble.template.PebbleTemplate; - -import org.junit.Test; - +import com.mitchellbosecke.pebble.template.PebbleTemplateImpl; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.HashMap; import java.util.List; import java.util.Map; - -import static org.junit.Assert.assertEquals; +import org.junit.Test; public class OverrideCoreExtensionTest { + @Test public void testOverrideCodeExtensionFunction() throws IOException { PebbleEngine pebble = new PebbleEngine.Builder() - .loader(new StringLoader()) - .extension(new TestExtension()) - .strictVariables(false) - .build(); + .loader(new StringLoader()) + .extension(new TestExtension()) + .build(); PebbleTemplate template = pebble.getTemplate("{{i18n()}}"); @@ -38,10 +46,9 @@ public void testOverrideCodeExtensionFunction() throws IOException { @Test public void testOverrideCodeExtensionFilter() throws IOException { PebbleEngine pebble = new PebbleEngine.Builder() - .loader(new StringLoader()) - .extension(new TestExtension()) - .strictVariables(false) - .build(); + .loader(new StringLoader()) + .extension(new TestExtension()) + .build(); PebbleTemplate template = pebble.getTemplate("{{ null | date }}"); @@ -50,6 +57,64 @@ public void testOverrideCodeExtensionFilter() throws IOException { assertEquals("custom date filter", writer.toString()); } + @Test + public void testOverrideCoreExtensionUnaryOperator() throws IOException { + PebbleEngine pebble = new PebbleEngine.Builder() + .loader(new StringLoader()) + .extension(new TestExtension()) + .allowOverrideCoreOperators(true) + .build(); + + PebbleTemplate template = pebble.getTemplate("{{ not true }}"); + + Writer writer = new StringWriter(); + template.evaluate(writer); + assertEquals("custom unary operator", writer.toString()); + } + + @Test + public void testByDefaultPreventsOverrideCoreExtensionUnaryOperator() throws IOException { + PebbleEngine pebble = new PebbleEngine.Builder() + .loader(new StringLoader()) + .extension(new TestExtension()) + .build(); + + PebbleTemplate template = pebble.getTemplate("{{ not true }}"); + + Writer writer = new StringWriter(); + template.evaluate(writer); + assertEquals("false", writer.toString()); + } + + @Test + public void testOverrideCoreExtensionBinaryOperator() throws IOException { + PebbleEngine pebble = new PebbleEngine.Builder() + .loader(new StringLoader()) + .extension(new TestExtension()) + .allowOverrideCoreOperators(true) + .build(); + + PebbleTemplate template = pebble.getTemplate("{{ 2 == 2 }}"); + + Writer writer = new StringWriter(); + template.evaluate(writer); + assertEquals("custom binary operator", writer.toString()); + } + + @Test + public void testByDefaultPreventsOverrideCoreExtensionBinaryOperator() throws IOException { + PebbleEngine pebble = new PebbleEngine.Builder() + .loader(new StringLoader()) + .extension(new TestExtension()) + .build(); + + PebbleTemplate template = pebble.getTemplate("{{ 2 == 2 }}"); + + Writer writer = new StringWriter(); + template.evaluate(writer); + assertEquals("true", writer.toString()); + } + private static class TestExtension extends AbstractExtension { @Override @@ -65,11 +130,29 @@ public Map getFilters() { filters.put("date", new CustomDateFilter()); return filters; } + + @Override + public List getBinaryOperators() { + BinaryOperatorImpl equalsOperator = new BinaryOperatorImpl( + "==", 30, FakeEqualsExpression.class, Associativity.LEFT); + + return singletonList(equalsOperator); + } + + @Override + public List getUnaryOperators() { + UnaryOperatorImpl equalsOperator = new UnaryOperatorImpl( + "not", 500, FakeUnaryNotExpression.class); + + return singletonList(equalsOperator); + } } private static class CustomI18nFunction implements Function { + @Override - public Object execute(Map args, PebbleTemplate self, EvaluationContext context, int lineNumber) { + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, + int lineNumber) { return "custom i18n function"; } @@ -79,9 +162,27 @@ public List getArgumentNames() { } } + public static class FakeEqualsExpression extends BinaryExpression { + + @Override + public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) { + return "custom binary operator"; + } + } + + public static class FakeUnaryNotExpression extends UnaryExpression { + + @Override + public String evaluate(PebbleTemplateImpl self, EvaluationContextImpl context) { + return "custom unary operator"; + } + } + private static class CustomDateFilter implements Filter { + @Override - public Object apply(Object input, Map args, PebbleTemplate self, EvaluationContext context, int lineNumber) throws PebbleException { + public Object apply(Object input, Map args, PebbleTemplate self, + EvaluationContext context, int lineNumber) throws PebbleException { return "custom date filter"; }