Skip to content
Merged
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<!-- JaCoCo: Don't make code coverage worse than: -->
<commons.jacoco.version>0.8.14</commons.jacoco.version>
<commons.jacoco.haltOnFailure>true</commons.jacoco.haltOnFailure>
<commons.jacoco.classRatio>0.96</commons.jacoco.classRatio>
<commons.jacoco.classRatio>0.95</commons.jacoco.classRatio>
<commons.jacoco.instructionRatio>0.89</commons.jacoco.instructionRatio>
<commons.jacoco.methodRatio>0.89</commons.jacoco.methodRatio>
<commons.jacoco.branchRatio>0.80</commons.jacoco.branchRatio>
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/org/apache/commons/jexl3/JxltEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ public interface Template {
/**
* Gets this script pragmas.
*
* @return the (non null, may be empty) pragmas map
* @return the (non-null, possibly empty) pragmas map
* @since 3.1
*/
Map<String, Object> getPragmas();
Expand All @@ -310,6 +310,22 @@ public interface Template {
* @return the prepared version of the template
*/
Template prepare(JexlContext context);

/**
* Prepares this template by expanding any contained deferred TemplateExpression with optional arguments.
* <p>This binds arguments to template parameters for immediate expressions when the template also
* uses deferred/nested expressions.</p>
*
* @param context the context to prepare against
* @param args the arguments to bind (optional)
* @return the prepared version of the template
* @since 3.6.2
*/
default Template prepare(JexlContext context, Object... args) {
throw new UnsupportedOperationException(
"This template implementation does not support prepare with arguments. "
+ "Override this method to provide support.");
}
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/apache/commons/jexl3/internal/Engine.java
Original file line number Diff line number Diff line change
Expand Up @@ -1042,7 +1042,8 @@ protected JexlContext.ThreadLocal putThreadLocal(final JexlContext.ThreadLocal t
}

@Override
public void setClassLoader(final ClassLoader loader) {
public void setClassLoader(final ClassLoader classLoader) {
final ClassLoader loader = classLoader == null? JexlUberspect.class.getClassLoader() : classLoader;
uberspect.setClassLoader(loader);
if (functions != null) {
final Iterable<String> names = new ArrayList<>(functions.keySet());
Expand Down
20 changes: 12 additions & 8 deletions src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1498,19 +1498,23 @@ protected Object visit(final ASTJexlScript script, final Object data) {
}
block = new LexicalFrame(frame, block).defineArgs();
try {
final int numChildren = script.jjtGetNumChildren();
Object result = null;
for (int i = 0; i < numChildren; i++) {
final JexlNode child = script.jjtGetChild(i);
result = child.jjtAccept(this, data);
cancelCheck(child);
}
return result;
return runScript(script, data);
} finally {
block = block.pop();
}
}

protected final Object runScript(final ASTJexlScript script, final Object data) {
final int numChildren = script.jjtGetNumChildren();
Object result = null;
for (int i = 0; i < numChildren; i++) {
final JexlNode child = script.jjtGetChild(i);
result = child.jjtAccept(this, data);
cancelCheck(child);
}
return result;
}

@Override
protected Object visit(final ASTLENode node, final Object data) {
final Object left = node.jjtGetChild(0).jjtAccept(this, data);
Expand Down
156 changes: 81 additions & 75 deletions src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@
* @since 3.0
*/
public final class TemplateEngine extends JxltEngine {

/**
* Abstract the source fragments, verbatim or immediate typed text blocks.
*/
static final class Block {

/** The type of block: verbatim or directive. */
private final BlockType type;

Expand Down Expand Up @@ -116,7 +114,6 @@ void toString(final StringBuilder strb, final String prefix) {
* The enum capturing the difference between verbatim and code source fragments.
*/
enum BlockType {

/** Block is to be output "as is" but may be a unified expression. */
VERBATIM,

Expand All @@ -126,7 +123,6 @@ enum BlockType {

/** A composite unified expression: "... ${...} ... #{...} ...". */
final class CompositeExpression extends TemplateExpression {

/** Bit encoded (deferred count > 0) bit 1, (immediate count > 0) bit 0. */
private final int meta;

Expand Down Expand Up @@ -227,16 +223,11 @@ protected TemplateExpression prepare(final Interpreter interpreter) {

/** A constant unified expression. */
final class ConstantExpression extends TemplateExpression {

/** The constant held by this unified expression. */
private final Object value;

/**
* Creates a constant unified expression.
* <p>
* If the wrapped constant is a string, it is treated
* as a JEXL strings with respect to escaping.
* </p>
*
* @param val the constant value
* @param source the source TemplateExpression if any
Expand Down Expand Up @@ -270,16 +261,14 @@ ExpressionType getType() {

/** A deferred unified expression: #{jexl}. */
final class DeferredExpression extends JexlBasedExpression {

/**
* Creates a deferred unified expression.
*
* @param expr the unified expression as a string
* @param node the unified expression as an AST
* @param source the source unified expression if any
*/
DeferredExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
super(expr, node, source);
DeferredExpression(final CharSequence expr, final JexlNode node) {
super(expr, node, null);
}

@Override
Expand Down Expand Up @@ -308,7 +297,6 @@ protected TemplateExpression prepare(final Interpreter interpreter) {
* Keeps count of sub-expressions by type.
*/
static final class ExpressionBuilder {

/** Per TemplateExpression type counters. */
private final int[] counts;

Expand Down Expand Up @@ -390,7 +378,6 @@ StringBuilder toString(final StringBuilder error) {
* @see ExpressionBuilder
*/
enum ExpressionType {

/** Constant TemplateExpression, count index 0. */
CONSTANT(0),

Expand Down Expand Up @@ -429,7 +416,6 @@ int getIndex() {

/** An immediate unified expression: ${jexl}. */
final class ImmediateExpression extends JexlBasedExpression {

/**
* Creates an immediate unified expression.
*
Expand All @@ -456,7 +442,6 @@ protected TemplateExpression prepare(final Interpreter interpreter) {

/** The base for JEXL based unified expressions. */
abstract class JexlBasedExpression extends TemplateExpression {

/** The JEXL string for this unified expression. */
protected final CharSequence expr;

Expand Down Expand Up @@ -519,17 +504,17 @@ protected JexlOptions options(final JexlContext context) {
* Note that the deferred syntax is JEXL's.
*/
final class NestedExpression extends JexlBasedExpression {

private final Scope scope;
/**
* Creates a nested unified expression.
*
* @param expr the unified expression as a string
* @param node the unified expression as an AST
* @param source the source unified expression if any
*/
NestedExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
super(expr, node, source);
if (this.source != this) {
NestedExpression(final CharSequence expr, final JexlNode node, final Scope sc) {
super(expr, node, null);
this.scope = sc;
if (source != this) {
throw new IllegalArgumentException("Nested TemplateExpression cannot have a source");
}
}
Expand Down Expand Up @@ -558,7 +543,7 @@ public boolean isImmediate() {
@Override
protected TemplateExpression prepare(final Interpreter interpreter) {
final String value = interpreter.interpret(node).toString();
final JexlNode dnode = jexl.jxltParse(node.jexlInfo(), noscript, value, null);
final JexlNode dnode = jexl.jxltParse(node.jexlInfo(), noscript, value, scope);
return new ImmediateExpression(value, dnode, this);
}
}
Expand Down Expand Up @@ -589,7 +574,6 @@ private enum ParseState {
* The abstract base class for all unified expressions, immediate '${...}' and deferred '#{...}'.
*/
abstract class TemplateExpression implements Expression {

/** The source of this template expression(see {@link TemplateEngine.TemplateExpression#prepare}). */
protected final TemplateExpression source;

Expand Down Expand Up @@ -722,14 +706,15 @@ public final TemplateExpression prepare(final JexlContext context) {
*
* @param frame the frame storing parameters and local variables
* @param context the context storing global variables
* @param opts flags and properties that can alter the evaluation behavior.
* @param options flags and properties that can alter the evaluation behavior.
* @return the expression value
* @throws JexlException if expression preparation fails
*/
protected final TemplateExpression prepare(final JexlContext context, final Frame frame, final JexlOptions opts) {
protected final TemplateExpression prepare(final JexlContext context, final Frame frame, final JexlOptions options) {
try {
final JexlOptions interOptions = opts != null ? opts : jexl.evalOptions(context);
final Interpreter interpreter = jexl.createInterpreter(context, frame, interOptions);
final TemplateInterpreter.Arguments args = new TemplateInterpreter.Arguments(jexl).context(context)
.options(options != null ? options : options(context)).frame(frame);
final Interpreter interpreter = jexl.createTemplateInterpreter(args);
return prepare(interpreter);
} catch (final JexlException xjexl) {
final JexlException xuel = createException(xjexl.getInfo(), "prepare", this, xjexl);
Expand Down Expand Up @@ -1068,12 +1053,13 @@ TemplateExpression parseExpression(final JexlInfo info, final String expr, final
// materialize the immediate expr
final String src = escapeString(strb);
final JexlInfo srcInfo = info.at(lineno, column);
final TemplateExpression iexpr = new ImmediateExpression(src, jexl.jxltParse(srcInfo, noscript, src, scope), null);
final TemplateExpression iexpr = new ImmediateExpression(src,
jexl.jxltParse(srcInfo, noscript, src, scope), null);
builder.add(iexpr);
strb.delete(0, Integer.MAX_VALUE);
state = ParseState.CONST;
}
} else {
} else if (!isIgnorable(c)) {
if (c == '{') {
immediate1 += 1;
}
Expand All @@ -1082,58 +1068,59 @@ TemplateExpression parseExpression(final JexlInfo info, final String expr, final
}
break;
case DEFERRED1: // #{...
// skip inner strings (for '}')
// skip inner strings - for '}' -
// nested immediate in deferred; need to balance count of '{' & '}'
// closing '}'
switch (c) {
case '"':
case '\'':
strb.append(c);
column = StringParser.readString(strb, expr, column + 1, c);
continue;
case '{':
if (expr.charAt(column - 1) == immediateChar) {
inner1 += 1;
strb.deleteCharAt(strb.length() - 1);
nested = true;
} else {
deferred1 += 1;
case '"':
case '\'':
strb.append(c);
}
continue;
case '}':
// balance nested immediate
if (deferred1 > 0) {
deferred1 -= 1;
strb.append(c);
} else if (inner1 > 0) {
inner1 -= 1;
} else {
// materialize the nested/deferred expr
final String src = escapeString(strb);
final JexlInfo srcInfo = info.at(lineno, column);
TemplateExpression dexpr;
if (nested) {
dexpr = new NestedExpression(
column = StringParser.readString(strb, expr, column + 1, c);
continue;
case '{':
if (expr.charAt(column - 1) == immediateChar) {
inner1 += 1;
strb.deleteCharAt(strb.length() - 1);
nested = true;
} else {
deferred1 += 1;
strb.append(c);
}
continue;
case '}':
// balance nested immediate
if (deferred1 > 0) {
deferred1 -= 1;
strb.append(c);
} else if (inner1 > 0) {
inner1 -= 1;
} else {
// materialize the nested/deferred expr
final String src = escapeString(strb);
final JexlInfo srcInfo = info.at(lineno, column);
TemplateExpression dexpr;
if (nested) {
dexpr = new NestedExpression(
escapeString(expr.substring(inested, column + 1)),
jexl.jxltParse(srcInfo, noscript, src, scope),
null);
} else {
dexpr = new DeferredExpression(
src,
jexl.jxltParse(srcInfo, noscript, src, scope),
null);
scope);
} else {
dexpr = new DeferredExpression(
src,
jexl.jxltParse(srcInfo, noscript, src, scope));
}
builder.add(dexpr);
strb.delete(0, Integer.MAX_VALUE);
nested = false;
state = ParseState.CONST;
}
builder.add(dexpr);
strb.delete(0, Integer.MAX_VALUE);
nested = false;
state = ParseState.CONST;
}
break;
default:
// do buildup expr
column = append(strb, expr, column, c);
break;
break;
default:
if (!isIgnorable(c)) {
// do buildup expr
column = append(strb, expr, column, c);
}
break;
}
break;
case ESCAPE:
Expand Down Expand Up @@ -1184,6 +1171,25 @@ private String escapeString(final CharSequence str) {
return StringParser.escapeString(str, (char) 0);
}

/**
* Determines whether the given character is considered ignorable whitespace in
* template expressions.
* <p>
* The characters newline ({@code '\n'}), carriage return ({@code '\r'}), tab
* ({@code '\t'}) and form feed ({@code '\f'}) are treated as ignorable within
* template expressions and are skipped by the parser. These characters are
* <em>not</em> ignored between the expression prefix ({@code '$'} or
* {@code '#'}) and the opening brace {@code '{'}; in that position they
* influence parsing instead of being discarded.
* </p>
*
* @param c the character to test
* @return {@code true} if the character is ignorable, {@code false} otherwise
*/
private static boolean isIgnorable(char c) {
return c == '\n' || c == '\r' || c == '\t' || c == '\f';
}

/**
* Reads lines of a template grouping them by typed blocks.
*
Expand Down
Loading
Loading