1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.jexl;
18
19 import java.io.StringReader;
20
21 import org.apache.commons.jexl.parser.ASTExpressionExpression;
22 import org.apache.commons.jexl.parser.ASTForeachStatement;
23 import org.apache.commons.jexl.parser.ASTIfStatement;
24 import org.apache.commons.jexl.parser.ASTReferenceExpression;
25 import org.apache.commons.jexl.parser.ASTStatementExpression;
26 import org.apache.commons.jexl.parser.ASTWhileStatement;
27 import org.apache.commons.jexl.parser.ParseException;
28 import org.apache.commons.jexl.parser.Parser;
29 import org.apache.commons.jexl.parser.SimpleNode;
30 import org.apache.commons.jexl.parser.TokenMgrError;
31 import org.apache.commons.jexl.util.Introspector;
32 import org.apache.commons.jexl.util.introspection.Uberspect;
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35
36 /**
37 * <p>
38 * Creates Expression objects. To create a JEXL Expression object, pass
39 * valid JEXL syntax to the static createExpression() method:
40 * </p>
41 *
42 * <pre>
43 * String jexl = "array[1]";
44 * Expression expression = ExpressionFactory.createExpression( jexl );
45 * </pre>
46 *
47 * <p>
48 * When an {@link Expression} object is created, the JEXL syntax is
49 * parsed and verified. If the supplied expression is neither an
50 * expression nor a reference, an exception is thrown from createException().
51 * </p>
52 * @since 1.0
53 * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
54 * @version $Id: ExpressionFactory.java 548229 2007-06-18 06:11:32Z dion $
55 */
56 public class ExpressionFactory {
57 /**
58 * The Log to which all ExpressionFactory messages will be logged.
59 */
60 protected static final Log log =
61 LogFactory.getLog("org.apache.commons.jexl.ExpressionFactory");
62
63 /**
64 * The singleton ExpressionFactory also holds a single instance of
65 * {@link Parser}.
66 * When parsing expressions, ExpressionFactory synchronizes on Parser.
67 */
68 protected final Parser parser =
69 new Parser(new StringReader(";")); //$NON-NLS-1$
70
71 /**
72 * ExpressionFactory is a singleton and this is the private
73 * instance fufilling that pattern.
74 */
75 protected static final ExpressionFactory ef = new ExpressionFactory();
76
77 /**
78 * Private constructor, the single instance is always obtained
79 * with a call to getInstance().
80 */
81 private ExpressionFactory() {
82 this(Introspector.getUberspect());
83 }
84
85 /**
86 * Creates an expression factory using the provided {@link Uberspect}.
87 * @param uberspect to allow different introspection behaviour
88 */
89 public ExpressionFactory(Uberspect uberspect) {
90 parser.setUberspect(uberspect);
91 }
92
93 /**
94 * Returns the single instance of ExpressionFactory.
95 * @return the instance of ExpressionFactory.
96 */
97 public static ExpressionFactory getInstance() {
98 return ef;
99 }
100
101 /**
102 * Creates an Expression from a String containing valid
103 * JEXL syntax. This method parses the expression which
104 * must contain either a reference or an expression.
105 * @param expression A String containing valid JEXL syntax
106 * @return An Expression object which can be evaluated with a JexlContext
107 * @throws Exception An exception can be thrown if there is a problem
108 * parsing this expression, or if the expression is neither an
109 * expression or a reference.
110 */
111 public static Expression createExpression(String expression)
112 throws Exception {
113 return getInstance().createNewExpression(expression);
114 }
115
116
117 /**
118 * Creates a new Expression based on the expression string.
119 *
120 * @param expression valid Jexl expression
121 * @return Expression
122 * @throws Exception for a variety of reasons - mostly malformed
123 * Jexl expression
124 */
125 public Expression createNewExpression(final String expression)
126 throws Exception {
127
128 String expr = cleanExpression(expression);
129
130 // Parse the Expression
131 SimpleNode tree;
132 synchronized (parser) {
133 log.debug("Parsing expression: " + expr);
134 try {
135 tree = parser.parse(new StringReader(expr));
136 } catch (TokenMgrError tme) {
137 throw new ParseException(tme.getMessage());
138 }
139 }
140
141 if (tree.jjtGetNumChildren() > 1 && log.isWarnEnabled()) {
142 log.warn("The JEXL Expression created will be a reference"
143 + " to the first expression from the supplied script: \""
144 + expression + "\" ");
145 }
146
147 // Must be a simple reference, expression, statement or if, otherwise
148 // throw an exception.
149 SimpleNode node = (SimpleNode) tree.jjtGetChild(0);
150
151 // TODO: Can we get rid of these checks?
152 if (node instanceof ASTReferenceExpression
153 || node instanceof ASTExpressionExpression
154 || node instanceof ASTStatementExpression
155 || node instanceof ASTIfStatement
156 || node instanceof ASTWhileStatement
157 || node instanceof ASTForeachStatement
158 ) {
159 return new ExpressionImpl(expression, node);
160 }
161 log.error("Invalid Expression, node of type: "
162 + node.getClass().getName());
163 throw new Exception("Invalid Expression: not a Reference, Expression, "
164 + "Statement or If");
165 }
166
167 /**
168 * Trims the expression and adds a semi-colon if missing.
169 * @param expression to clean
170 * @return trimmed expression ending in a semi-colon
171 */
172 private String cleanExpression(String expression) {
173 String expr = expression.trim();
174 if (!expr.endsWith(";")) {
175 expr += ";";
176 }
177 return expr;
178 }
179 }