338 lines
12 KiB
Java
338 lines
12 KiB
Java
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You 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 org.apache.el.lang;
|
|
|
|
import java.io.StringReader;
|
|
import java.lang.reflect.Method;
|
|
import java.security.AccessController;
|
|
import java.security.PrivilegedAction;
|
|
|
|
import javax.el.ELContext;
|
|
import javax.el.ELException;
|
|
import javax.el.FunctionMapper;
|
|
import javax.el.MethodExpression;
|
|
import javax.el.ValueExpression;
|
|
import javax.el.VariableMapper;
|
|
|
|
import org.apache.el.MethodExpressionImpl;
|
|
import org.apache.el.MethodExpressionLiteral;
|
|
import org.apache.el.ValueExpressionImpl;
|
|
import org.apache.el.parser.AstDeferredExpression;
|
|
import org.apache.el.parser.AstDynamicExpression;
|
|
import org.apache.el.parser.AstFunction;
|
|
import org.apache.el.parser.AstIdentifier;
|
|
import org.apache.el.parser.AstLiteralExpression;
|
|
import org.apache.el.parser.AstValue;
|
|
import org.apache.el.parser.ELParser;
|
|
import org.apache.el.parser.Node;
|
|
import org.apache.el.parser.NodeVisitor;
|
|
import org.apache.el.util.ConcurrentCache;
|
|
import org.apache.el.util.MessageFactory;
|
|
|
|
/**
|
|
* @author Jacob Hookom [jacob@hookom.net]
|
|
*/
|
|
public final class ExpressionBuilder implements NodeVisitor {
|
|
|
|
private static final SynchronizedStack<ELParser> parserCache = new SynchronizedStack<>();
|
|
|
|
private static final int CACHE_SIZE;
|
|
private static final String CACHE_SIZE_PROP =
|
|
"org.apache.el.ExpressionBuilder.CACHE_SIZE";
|
|
|
|
static {
|
|
String cacheSizeStr;
|
|
if (System.getSecurityManager() == null) {
|
|
cacheSizeStr = System.getProperty(CACHE_SIZE_PROP, "5000");
|
|
} else {
|
|
cacheSizeStr = AccessController.doPrivileged(
|
|
new PrivilegedAction<String>() {
|
|
|
|
@Override
|
|
public String run() {
|
|
return System.getProperty(CACHE_SIZE_PROP, "5000");
|
|
}
|
|
});
|
|
}
|
|
CACHE_SIZE = Integer.parseInt(cacheSizeStr);
|
|
}
|
|
|
|
private static final ConcurrentCache<String, Node> expressionCache =
|
|
new ConcurrentCache<>(CACHE_SIZE);
|
|
|
|
private FunctionMapper fnMapper;
|
|
|
|
private VariableMapper varMapper;
|
|
|
|
private final String expression;
|
|
|
|
public ExpressionBuilder(String expression, ELContext ctx)
|
|
throws ELException {
|
|
this.expression = expression;
|
|
|
|
FunctionMapper ctxFn = ctx.getFunctionMapper();
|
|
VariableMapper ctxVar = ctx.getVariableMapper();
|
|
|
|
if (ctxFn != null) {
|
|
this.fnMapper = new FunctionMapperFactory(ctxFn);
|
|
}
|
|
if (ctxVar != null) {
|
|
this.varMapper = new VariableMapperFactory(ctxVar);
|
|
}
|
|
}
|
|
|
|
public static final Node createNode(String expr) throws ELException {
|
|
Node n = createNodeInternal(expr);
|
|
return n;
|
|
}
|
|
|
|
private static final Node createNodeInternal(String expr)
|
|
throws ELException {
|
|
if (expr == null) {
|
|
throw new ELException(MessageFactory.get("error.null"));
|
|
}
|
|
|
|
Node n = expressionCache.get(expr);
|
|
if (n == null) {
|
|
ELParser parser = parserCache.pop();
|
|
try {
|
|
if (parser == null) {
|
|
parser = new ELParser(new StringReader(expr));
|
|
} else {
|
|
parser.ReInit(new StringReader(expr));
|
|
}
|
|
n = parser.CompositeExpression();
|
|
|
|
// validate composite expression
|
|
int numChildren = n.jjtGetNumChildren();
|
|
if (numChildren == 1) {
|
|
n = n.jjtGetChild(0);
|
|
} else {
|
|
Class<?> type = null;
|
|
Node child = null;
|
|
for (int i = 0; i < numChildren; i++) {
|
|
child = n.jjtGetChild(i);
|
|
if (child instanceof AstLiteralExpression)
|
|
continue;
|
|
if (type == null)
|
|
type = child.getClass();
|
|
else {
|
|
if (!type.equals(child.getClass())) {
|
|
throw new ELException(MessageFactory.get(
|
|
"error.mixed", expr));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (n instanceof AstDeferredExpression
|
|
|| n instanceof AstDynamicExpression) {
|
|
n = n.jjtGetChild(0);
|
|
}
|
|
expressionCache.put(expr, n);
|
|
} catch (Exception e) {
|
|
throw new ELException(
|
|
MessageFactory.get("error.parseFail", expr), e);
|
|
} finally {
|
|
if (parser != null) {
|
|
parserCache.push(parser);
|
|
}
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
private void prepare(Node node) throws ELException {
|
|
try {
|
|
node.accept(this);
|
|
} catch (Exception e) {
|
|
if (e instanceof ELException) {
|
|
throw (ELException) e;
|
|
} else {
|
|
throw (new ELException(e));
|
|
}
|
|
}
|
|
if (this.fnMapper instanceof FunctionMapperFactory) {
|
|
this.fnMapper = ((FunctionMapperFactory) this.fnMapper).create();
|
|
}
|
|
if (this.varMapper instanceof VariableMapperFactory) {
|
|
this.varMapper = ((VariableMapperFactory) this.varMapper).create();
|
|
}
|
|
}
|
|
|
|
private Node build() throws ELException {
|
|
Node n = createNodeInternal(this.expression);
|
|
this.prepare(n);
|
|
if (n instanceof AstDeferredExpression
|
|
|| n instanceof AstDynamicExpression) {
|
|
n = n.jjtGetChild(0);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see com.sun.el.parser.NodeVisitor#visit(com.sun.el.parser.Node)
|
|
*/
|
|
@Override
|
|
public void visit(Node node) throws ELException {
|
|
if (node instanceof AstFunction) {
|
|
|
|
AstFunction funcNode = (AstFunction) node;
|
|
|
|
Method m = null;
|
|
|
|
if (this.fnMapper != null) {
|
|
m = fnMapper.resolveFunction(funcNode.getPrefix(), funcNode
|
|
.getLocalName());
|
|
}
|
|
|
|
// References to variables that refer to lambda expressions will be
|
|
// parsed as functions. This is handled at runtime but at this point
|
|
// need to treat it as a variable rather than a function.
|
|
if (m == null && this.varMapper != null &&
|
|
funcNode.getPrefix().length() == 0) {
|
|
this.varMapper.resolveVariable(funcNode.getLocalName());
|
|
return;
|
|
}
|
|
|
|
if (this.fnMapper == null) {
|
|
throw new ELException(MessageFactory.get("error.fnMapper.null"));
|
|
}
|
|
|
|
if (m == null) {
|
|
throw new ELException(MessageFactory.get(
|
|
"error.fnMapper.method", funcNode.getOutputName()));
|
|
}
|
|
|
|
int methodParameterCount = m.getParameterTypes().length;
|
|
// AstFunction->MethodParameters->Parameters()
|
|
int inputParameterCount = node.jjtGetChild(0).jjtGetNumChildren();
|
|
if (m.isVarArgs() && inputParameterCount < methodParameterCount - 1 ||
|
|
!m.isVarArgs() && inputParameterCount != methodParameterCount) {
|
|
throw new ELException(MessageFactory.get(
|
|
"error.fnMapper.paramcount", funcNode.getOutputName(),
|
|
"" + methodParameterCount, "" + node.jjtGetChild(0).jjtGetNumChildren()));
|
|
}
|
|
} else if (node instanceof AstIdentifier && this.varMapper != null) {
|
|
String variable = ((AstIdentifier) node).getImage();
|
|
|
|
// simply capture it
|
|
this.varMapper.resolveVariable(variable);
|
|
}
|
|
}
|
|
|
|
public ValueExpression createValueExpression(Class<?> expectedType)
|
|
throws ELException {
|
|
Node n = this.build();
|
|
return new ValueExpressionImpl(this.expression, n, this.fnMapper,
|
|
this.varMapper, expectedType);
|
|
}
|
|
|
|
public MethodExpression createMethodExpression(Class<?> expectedReturnType,
|
|
Class<?>[] expectedParamTypes) throws ELException {
|
|
Node n = this.build();
|
|
if (!n.isParametersProvided() && expectedParamTypes == null) {
|
|
throw new NullPointerException(MessageFactory
|
|
.get("error.method.nullParms"));
|
|
}
|
|
if (n instanceof AstValue || n instanceof AstIdentifier) {
|
|
return new MethodExpressionImpl(expression, n, this.fnMapper,
|
|
this.varMapper, expectedReturnType, expectedParamTypes);
|
|
} else if (n instanceof AstLiteralExpression) {
|
|
return new MethodExpressionLiteral(expression, expectedReturnType,
|
|
expectedParamTypes);
|
|
} else {
|
|
throw new ELException("Not a Valid Method Expression: "
|
|
+ expression);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Copied from org.apache.tomcat.util.collections.SynchronizedStack since
|
|
* we don't want the EL implementation to depend on the JAR where that
|
|
* class resides.
|
|
*/
|
|
private static class SynchronizedStack<T> {
|
|
|
|
public static final int DEFAULT_SIZE = 128;
|
|
private static final int DEFAULT_LIMIT = -1;
|
|
|
|
private int size;
|
|
private final int limit;
|
|
|
|
/*
|
|
* Points to the next available object in the stack
|
|
*/
|
|
private int index = -1;
|
|
|
|
private Object[] stack;
|
|
|
|
|
|
public SynchronizedStack() {
|
|
this(DEFAULT_SIZE, DEFAULT_LIMIT);
|
|
}
|
|
|
|
public SynchronizedStack(int size, int limit) {
|
|
this.size = size;
|
|
this.limit = limit;
|
|
stack = new Object[size];
|
|
}
|
|
|
|
|
|
public synchronized boolean push(T obj) {
|
|
index++;
|
|
if (index == size) {
|
|
if (limit == -1 || size < limit) {
|
|
expand();
|
|
} else {
|
|
index--;
|
|
return false;
|
|
}
|
|
}
|
|
stack[index] = obj;
|
|
return true;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public synchronized T pop() {
|
|
if (index == -1) {
|
|
return null;
|
|
}
|
|
T result = (T) stack[index];
|
|
stack[index--] = null;
|
|
return result;
|
|
}
|
|
|
|
private void expand() {
|
|
int newSize = size * 2;
|
|
if (limit != -1 && newSize > limit) {
|
|
newSize = limit;
|
|
}
|
|
Object[] newStack = new Object[newSize];
|
|
System.arraycopy(stack, 0, newStack, 0, size);
|
|
// This is the only point where garbage is created by throwing away the
|
|
// old array. Note it is only the array, not the contents, that becomes
|
|
// garbage.
|
|
stack = newStack;
|
|
size = newSize;
|
|
}
|
|
}
|
|
|
|
}
|