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