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
18 package org.apache.el;
19
20 import java.io.Externalizable;
21 import java.io.IOException;
22 import java.io.ObjectInput;
23 import java.io.ObjectOutput;
24
25 import javax.el.ELContext;
26 import javax.el.ELException;
27 import javax.el.ELResolver;
28 import javax.el.Expression;
29 import javax.el.ExpressionFactory;
30 import javax.el.FunctionMapper;
31 import javax.el.MethodExpression;
32 import javax.el.MethodInfo;
33 import javax.el.MethodNotFoundException;
34 import javax.el.PropertyNotFoundException;
35 import javax.el.VariableMapper;
36
37 import org.apache.el.lang.EvaluationContext;
38 import org.apache.el.lang.ExpressionBuilder;
39 import org.apache.el.parser.Node;
40 import org.apache.el.util.ReflectionUtil;
41
42
43 /**
44 * An <code>Expression</code> that refers to a method on an object.
45 *
46 * <p>
47 * <code>The {@link ExpressionFactory#createMethodExpression} method
48 * can be used to parse an expression string and return a concrete instance
49 * of <code>MethodExpression</code> that encapsulates the parsed expression.
50 * The {@link FunctionMapper} is used at parse time, not evaluation time,
51 * so one is not needed to evaluate an expression using this class.
52 * However, the {@link ELContext} is needed at evaluation time.</p>
53 *
54 * <p>The {@link #getMethodInfo} and {@link #invoke} methods will evaluate the
55 * expression each time they are called. The {@link ELResolver} in the
56 * <code>ELContext</code> is used to resolve the top-level variables and to
57 * determine the behavior of the <code>.</code> and <code>[]</code>
58 * operators. For any of the two methods, the {@link ELResolver#getValue}
59 * method is used to resolve all properties up to but excluding the last
60 * one. This provides the <code>base</code> object on which the method
61 * appears. If the <code>base</code> object is null, a
62 * <code>NullPointerException</code> must be thrown. At the last resolution,
63 * the final <code>property</code> is then coerced to a <code>String</code>,
64 * which provides the name of the method to be found. A method matching the
65 * name and expected parameters provided at parse time is found and it is
66 * either queried or invoked (depending on the method called on this
67 * <code>MethodExpression</code>).</p>
68 *
69 * <p>See the notes about comparison, serialization and immutability in
70 * the {@link Expression} javadocs.
71 *
72 * @see javax.el.ELResolver
73 * @see javax.el.Expression
74 * @see javax.el.ExpressionFactory
75 * @see javax.el.MethodExpression
76 *
77 * @author Jacob Hookom [jacob@hookom.net]
78 * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: markt $
79 */
80 public final class MethodExpressionImpl extends MethodExpression implements
81 Externalizable {
82
83 private Class expectedType;
84
85 private String expr;
86
87 private FunctionMapper fnMapper;
88
89 private VariableMapper varMapper;
90
91 private transient Node node;
92
93 private Class[] paramTypes;
94
95 /**
96 *
97 */
98 public MethodExpressionImpl() {
99 super();
100 }
101
102 /**
103 * @param expr
104 * @param node
105 * @param fnMapper
106 * @param expectedType
107 * @param paramTypes
108 */
109 public MethodExpressionImpl(String expr, Node node,
110 FunctionMapper fnMapper, VariableMapper varMapper,
111 Class expectedType, Class[] paramTypes) {
112 super();
113 this.expr = expr;
114 this.node = node;
115 this.fnMapper = fnMapper;
116 this.varMapper = varMapper;
117 this.expectedType = expectedType;
118 this.paramTypes = paramTypes;
119 }
120
121 /**
122 * Determines whether the specified object is equal to this
123 * <code>Expression</code>.
124 *
125 * <p>
126 * The result is <code>true</code> if and only if the argument is not
127 * <code>null</code>, is an <code>Expression</code> object that is the
128 * of the same type (<code>ValueExpression</code> or
129 * <code>MethodExpression</code>), and has an identical parsed
130 * representation.
131 * </p>
132 *
133 * <p>
134 * Note that two expressions can be equal if their expression Strings are
135 * different. For example, <code>${fn1:foo()}</code> and
136 * <code>${fn2:foo()}</code> are equal if their corresponding
137 * <code>FunctionMapper</code>s mapped <code>fn1:foo</code> and
138 * <code>fn2:foo</code> to the same method.
139 * </p>
140 *
141 * @param obj
142 * the <code>Object</code> to test for equality.
143 * @return <code>true</code> if <code>obj</code> equals this
144 * <code>Expression</code>; <code>false</code> otherwise.
145 * @see java.util.Hashtable
146 * @see java.lang.Object#equals(java.lang.Object)
147 */
148 public boolean equals(Object obj) {
149 return (obj instanceof MethodExpressionImpl && obj.hashCode() == this
150 .hashCode());
151 }
152
153 /**
154 * Returns the original String used to create this <code>Expression</code>,
155 * unmodified.
156 *
157 * <p>
158 * This is used for debugging purposes but also for the purposes of
159 * comparison (e.g. to ensure the expression in a configuration file has not
160 * changed).
161 * </p>
162 *
163 * <p>
164 * This method does not provide sufficient information to re-create an
165 * expression. Two different expressions can have exactly the same
166 * expression string but different function mappings. Serialization should
167 * be used to save and restore the state of an <code>Expression</code>.
168 * </p>
169 *
170 * @return The original expression String.
171 *
172 * @see javax.el.Expression#getExpressionString()
173 */
174 public String getExpressionString() {
175 return this.expr;
176 }
177
178 /**
179 * Evaluates the expression relative to the provided context, and returns
180 * information about the actual referenced method.
181 *
182 * @param context
183 * The context of this evaluation
184 * @return an instance of <code>MethodInfo</code> containing information
185 * about the method the expression evaluated to.
186 * @throws NullPointerException
187 * if context is <code>null</code> or the base object is
188 * <code>null</code> on the last resolution.
189 * @throws PropertyNotFoundException
190 * if one of the property resolutions failed because a specified
191 * variable or property does not exist or is not readable.
192 * @throws MethodNotFoundException
193 * if no suitable method can be found.
194 * @throws ELException
195 * if an exception was thrown while performing property or
196 * variable resolution. The thrown exception must be included as
197 * the cause property of this exception, if available.
198 * @see javax.el.MethodExpression#getMethodInfo(javax.el.ELContext)
199 */
200 public MethodInfo getMethodInfo(ELContext context)
201 throws PropertyNotFoundException, MethodNotFoundException,
202 ELException {
203 Node n = this.getNode();
204 EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
205 this.varMapper);
206 return n.getMethodInfo(ctx, this.paramTypes);
207 }
208
209 /**
210 * @return
211 * @throws ELException
212 */
213 private Node getNode() throws ELException {
214 if (this.node == null) {
215 this.node = ExpressionBuilder.createNode(this.expr);
216 }
217 return this.node;
218 }
219
220 /**
221 * Returns the hash code for this <code>Expression</code>.
222 *
223 * <p>
224 * See the note in the {@link #equals} method on how two expressions can be
225 * equal if their expression Strings are different. Recall that if two
226 * objects are equal according to the <code>equals(Object)</code> method,
227 * then calling the <code>hashCode</code> method on each of the two
228 * objects must produce the same integer result. Implementations must take
229 * special note and implement <code>hashCode</code> correctly.
230 * </p>
231 *
232 * @return The hash code for this <code>Expression</code>.
233 * @see #equals
234 * @see java.util.Hashtable
235 * @see java.lang.Object#hashCode()
236 */
237 public int hashCode() {
238 return this.expr.hashCode();
239 }
240
241 /**
242 * Evaluates the expression relative to the provided context, invokes the
243 * method that was found using the supplied parameters, and returns the
244 * result of the method invocation.
245 *
246 * @param context
247 * The context of this evaluation.
248 * @param params
249 * The parameters to pass to the method, or <code>null</code>
250 * if no parameters.
251 * @return the result of the method invocation (<code>null</code> if the
252 * method has a <code>void</code> return type).
253 * @throws NullPointerException
254 * if context is <code>null</code> or the base object is
255 * <code>null</code> on the last resolution.
256 * @throws PropertyNotFoundException
257 * if one of the property resolutions failed because a specified
258 * variable or property does not exist or is not readable.
259 * @throws MethodNotFoundException
260 * if no suitable method can be found.
261 * @throws ELException
262 * if an exception was thrown while performing property or
263 * variable resolution. The thrown exception must be included as
264 * the cause property of this exception, if available. If the
265 * exception thrown is an <code>InvocationTargetException</code>,
266 * extract its <code>cause</code> and pass it to the
267 * <code>ELException</code> constructor.
268 * @see javax.el.MethodExpression#invoke(javax.el.ELContext,
269 * java.lang.Object[])
270 */
271 public Object invoke(ELContext context, Object[] params)
272 throws PropertyNotFoundException, MethodNotFoundException,
273 ELException {
274 EvaluationContext ctx = new EvaluationContext(context, this.fnMapper,
275 this.varMapper);
276 return this.getNode().invoke(ctx, this.paramTypes, params);
277 }
278
279 /*
280 * (non-Javadoc)
281 *
282 * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
283 */
284 public void readExternal(ObjectInput in) throws IOException,
285 ClassNotFoundException {
286 this.expr = in.readUTF();
287 String type = in.readUTF();
288 if (!"".equals(type)) {
289 this.expectedType = ReflectionUtil.forName(type);
290 }
291 this.paramTypes = ReflectionUtil.toTypeArray(((String[]) in
292 .readObject()));
293 this.fnMapper = (FunctionMapper) in.readObject();
294 this.varMapper = (VariableMapper) in.readObject();
295 }
296
297 /*
298 * (non-Javadoc)
299 *
300 * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
301 */
302 public void writeExternal(ObjectOutput out) throws IOException {
303 out.writeUTF(this.expr);
304 out.writeUTF((this.expectedType != null) ? this.expectedType.getName()
305 : "");
306 out.writeObject(ReflectionUtil.toTypeNameArray(this.paramTypes));
307 out.writeObject(this.fnMapper);
308 out.writeObject(this.varMapper);
309 }
310
311 public boolean isLiteralText() {
312 return false;
313 }
314 }