001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.el;
019:
020: import java.io.Externalizable;
021: import java.io.IOException;
022: import java.io.ObjectInput;
023: import java.io.ObjectOutput;
024:
025: import javax.el.ELContext;
026: import javax.el.ELException;
027: import javax.el.ELResolver;
028: import javax.el.Expression;
029: import javax.el.ExpressionFactory;
030: import javax.el.FunctionMapper;
031: import javax.el.MethodExpression;
032: import javax.el.MethodInfo;
033: import javax.el.MethodNotFoundException;
034: import javax.el.PropertyNotFoundException;
035: import javax.el.VariableMapper;
036:
037: import org.apache.el.lang.EvaluationContext;
038: import org.apache.el.lang.ExpressionBuilder;
039: import org.apache.el.parser.Node;
040: import org.apache.el.util.ReflectionUtil;
041:
042: /**
043: * An <code>Expression</code> that refers to a method on an object.
044: *
045: * <p>
046: * <code>The {@link ExpressionFactory#createMethodExpression} method
047: * can be used to parse an expression string and return a concrete instance
048: * of <code>MethodExpression</code> that encapsulates the parsed expression.
049: * The {@link FunctionMapper} is used at parse time, not evaluation time,
050: * so one is not needed to evaluate an expression using this class.
051: * However, the {@link ELContext} is needed at evaluation time.</p>
052: *
053: * <p>The {@link #getMethodInfo} and {@link #invoke} methods will evaluate the
054: * expression each time they are called. The {@link ELResolver} in the
055: * <code>ELContext</code> is used to resolve the top-level variables and to
056: * determine the behavior of the <code>.</code> and <code>[]</code>
057: * operators. For any of the two methods, the {@link ELResolver#getValue}
058: * method is used to resolve all properties up to but excluding the last
059: * one. This provides the <code>base</code> object on which the method
060: * appears. If the <code>base</code> object is null, a
061: * <code>NullPointerException</code> must be thrown. At the last resolution,
062: * the final <code>property</code> is then coerced to a <code>String</code>,
063: * which provides the name of the method to be found. A method matching the
064: * name and expected parameters provided at parse time is found and it is
065: * either queried or invoked (depending on the method called on this
066: * <code>MethodExpression</code>).</p>
067: *
068: * <p>See the notes about comparison, serialization and immutability in
069: * the {@link Expression} javadocs.
070: *
071: * @see javax.el.ELResolver
072: * @see javax.el.Expression
073: * @see javax.el.ExpressionFactory
074: * @see javax.el.MethodExpression
075: *
076: * @author Jacob Hookom [jacob@hookom.net]
077: * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author: markt $
078: */
079: public final class MethodExpressionImpl extends MethodExpression
080: implements Externalizable {
081:
082: private Class expectedType;
083:
084: private String expr;
085:
086: private FunctionMapper fnMapper;
087:
088: private VariableMapper varMapper;
089:
090: private transient Node node;
091:
092: private Class[] paramTypes;
093:
094: /**
095: *
096: */
097: public MethodExpressionImpl() {
098: super ();
099: }
100:
101: /**
102: * @param expr
103: * @param node
104: * @param fnMapper
105: * @param expectedType
106: * @param paramTypes
107: */
108: public MethodExpressionImpl(String expr, Node node,
109: FunctionMapper fnMapper, VariableMapper varMapper,
110: Class expectedType, Class[] paramTypes) {
111: super ();
112: this .expr = expr;
113: this .node = node;
114: this .fnMapper = fnMapper;
115: this .varMapper = varMapper;
116: this .expectedType = expectedType;
117: this .paramTypes = paramTypes;
118: }
119:
120: /**
121: * Determines whether the specified object is equal to this
122: * <code>Expression</code>.
123: *
124: * <p>
125: * The result is <code>true</code> if and only if the argument is not
126: * <code>null</code>, is an <code>Expression</code> object that is the
127: * of the same type (<code>ValueExpression</code> or
128: * <code>MethodExpression</code>), and has an identical parsed
129: * representation.
130: * </p>
131: *
132: * <p>
133: * Note that two expressions can be equal if their expression Strings are
134: * different. For example, <code>${fn1:foo()}</code> and
135: * <code>${fn2:foo()}</code> are equal if their corresponding
136: * <code>FunctionMapper</code>s mapped <code>fn1:foo</code> and
137: * <code>fn2:foo</code> to the same method.
138: * </p>
139: *
140: * @param obj
141: * the <code>Object</code> to test for equality.
142: * @return <code>true</code> if <code>obj</code> equals this
143: * <code>Expression</code>; <code>false</code> otherwise.
144: * @see java.util.Hashtable
145: * @see java.lang.Object#equals(java.lang.Object)
146: */
147: public boolean equals(Object obj) {
148: return (obj instanceof MethodExpressionImpl && obj.hashCode() == this
149: .hashCode());
150: }
151:
152: /**
153: * Returns the original String used to create this <code>Expression</code>,
154: * unmodified.
155: *
156: * <p>
157: * This is used for debugging purposes but also for the purposes of
158: * comparison (e.g. to ensure the expression in a configuration file has not
159: * changed).
160: * </p>
161: *
162: * <p>
163: * This method does not provide sufficient information to re-create an
164: * expression. Two different expressions can have exactly the same
165: * expression string but different function mappings. Serialization should
166: * be used to save and restore the state of an <code>Expression</code>.
167: * </p>
168: *
169: * @return The original expression String.
170: *
171: * @see javax.el.Expression#getExpressionString()
172: */
173: public String getExpressionString() {
174: return this .expr;
175: }
176:
177: /**
178: * Evaluates the expression relative to the provided context, and returns
179: * information about the actual referenced method.
180: *
181: * @param context
182: * The context of this evaluation
183: * @return an instance of <code>MethodInfo</code> containing information
184: * about the method the expression evaluated to.
185: * @throws NullPointerException
186: * if context is <code>null</code> or the base object is
187: * <code>null</code> on the last resolution.
188: * @throws PropertyNotFoundException
189: * if one of the property resolutions failed because a specified
190: * variable or property does not exist or is not readable.
191: * @throws MethodNotFoundException
192: * if no suitable method can be found.
193: * @throws ELException
194: * if an exception was thrown while performing property or
195: * variable resolution. The thrown exception must be included as
196: * the cause property of this exception, if available.
197: * @see javax.el.MethodExpression#getMethodInfo(javax.el.ELContext)
198: */
199: public MethodInfo getMethodInfo(ELContext context)
200: throws PropertyNotFoundException, MethodNotFoundException,
201: ELException {
202: Node n = this .getNode();
203: EvaluationContext ctx = new EvaluationContext(context,
204: this .fnMapper, this .varMapper);
205: return n.getMethodInfo(ctx, this .paramTypes);
206: }
207:
208: /**
209: * @return
210: * @throws ELException
211: */
212: private Node getNode() throws ELException {
213: if (this .node == null) {
214: this .node = ExpressionBuilder.createNode(this .expr);
215: }
216: return this .node;
217: }
218:
219: /**
220: * Returns the hash code for this <code>Expression</code>.
221: *
222: * <p>
223: * See the note in the {@link #equals} method on how two expressions can be
224: * equal if their expression Strings are different. Recall that if two
225: * objects are equal according to the <code>equals(Object)</code> method,
226: * then calling the <code>hashCode</code> method on each of the two
227: * objects must produce the same integer result. Implementations must take
228: * special note and implement <code>hashCode</code> correctly.
229: * </p>
230: *
231: * @return The hash code for this <code>Expression</code>.
232: * @see #equals
233: * @see java.util.Hashtable
234: * @see java.lang.Object#hashCode()
235: */
236: public int hashCode() {
237: return this .expr.hashCode();
238: }
239:
240: /**
241: * Evaluates the expression relative to the provided context, invokes the
242: * method that was found using the supplied parameters, and returns the
243: * result of the method invocation.
244: *
245: * @param context
246: * The context of this evaluation.
247: * @param params
248: * The parameters to pass to the method, or <code>null</code>
249: * if no parameters.
250: * @return the result of the method invocation (<code>null</code> if the
251: * method has a <code>void</code> return type).
252: * @throws NullPointerException
253: * if context is <code>null</code> or the base object is
254: * <code>null</code> on the last resolution.
255: * @throws PropertyNotFoundException
256: * if one of the property resolutions failed because a specified
257: * variable or property does not exist or is not readable.
258: * @throws MethodNotFoundException
259: * if no suitable method can be found.
260: * @throws ELException
261: * if an exception was thrown while performing property or
262: * variable resolution. The thrown exception must be included as
263: * the cause property of this exception, if available. If the
264: * exception thrown is an <code>InvocationTargetException</code>,
265: * extract its <code>cause</code> and pass it to the
266: * <code>ELException</code> constructor.
267: * @see javax.el.MethodExpression#invoke(javax.el.ELContext,
268: * java.lang.Object[])
269: */
270: public Object invoke(ELContext context, Object[] params)
271: throws PropertyNotFoundException, MethodNotFoundException,
272: ELException {
273: EvaluationContext ctx = new EvaluationContext(context,
274: this .fnMapper, this .varMapper);
275: return this .getNode().invoke(ctx, this .paramTypes, params);
276: }
277:
278: /*
279: * (non-Javadoc)
280: *
281: * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
282: */
283: public void readExternal(ObjectInput in) throws IOException,
284: ClassNotFoundException {
285: this .expr = in.readUTF();
286: String type = in.readUTF();
287: if (!"".equals(type)) {
288: this .expectedType = ReflectionUtil.forName(type);
289: }
290: this .paramTypes = ReflectionUtil.toTypeArray(((String[]) in
291: .readObject()));
292: this .fnMapper = (FunctionMapper) in.readObject();
293: this .varMapper = (VariableMapper) in.readObject();
294: }
295:
296: /*
297: * (non-Javadoc)
298: *
299: * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
300: */
301: public void writeExternal(ObjectOutput out) throws IOException {
302: out.writeUTF(this .expr);
303: out.writeUTF((this .expectedType != null) ? this .expectedType
304: .getName() : "");
305: out
306: .writeObject(ReflectionUtil
307: .toTypeNameArray(this .paramTypes));
308: out.writeObject(this .fnMapper);
309: out.writeObject(this .varMapper);
310: }
311:
312: public boolean isLiteralText() {
313: return false;
314: }
315: }
|