001: /*
002: * Copyright 2006, 2007 Odysseus Software GmbH
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package de.odysseus.el;
017:
018: import java.io.IOException;
019: import java.io.ObjectInputStream;
020: import java.io.PrintWriter;
021: import java.util.Arrays;
022:
023: import javax.el.ELContext;
024: import javax.el.ELException;
025: import javax.el.FunctionMapper;
026: import javax.el.MethodInfo;
027: import javax.el.VariableMapper;
028:
029: import de.odysseus.el.misc.LocalMessages;
030: import de.odysseus.el.tree.Bindings;
031: import de.odysseus.el.tree.ExpressionNode;
032: import de.odysseus.el.tree.Tree;
033: import de.odysseus.el.tree.TreeBuilder;
034: import de.odysseus.el.tree.TreeStore;
035: import de.odysseus.el.tree.NodePrinter;
036:
037: /**
038: * A method expression is ready to be evaluated (by calling either
039: * {@link #invoke(ELContext, Object[])} or {@link #getMethodInfo(ELContext)}).
040: *
041: * Instances of this class are usually created using an {@link ExpressionFactoryImpl}.
042: *
043: * @author Christoph Beck
044: */
045: public final class TreeMethodExpression extends
046: javax.el.MethodExpression {
047: private static final long serialVersionUID = 1L;
048:
049: private final TreeBuilder builder;
050: private final Bindings bindings;
051: private final String expr;
052: private final Class<?> type;
053: private final Class[] types;
054: private final boolean deferred;
055: private final boolean is_void; // deserialization fails for type == void.class with JSF-RI
056:
057: private transient ExpressionNode node;
058:
059: private String structure;
060:
061: /**
062: * Create a new method expression.
063: * The expression must be an lvalue expression or literal text.
064: * The expected return type may be <code>null</code>, meaning "don't care".
065: * If it is an lvalue expression, the parameter types must not be <code>null</code>.
066: * If it is literal text, the expected return type must not be <code>void</code>.
067: * @param store used to get the parse tree from.
068: * @param functions the function mapper used to bind functions
069: * @param variables the variable mapper used to bind variables
070: * @param expr the expression string
071: * @param returnType the expected return type (may be <code>null</code>)
072: * @param paramTypes the expected parameter types (must not be <code>null</code> for lvalues)
073: */
074: public TreeMethodExpression(TreeStore store,
075: FunctionMapper functions, VariableMapper variables,
076: String expr, Class<?> returnType, Class<?>[] paramTypes) {
077: super ();
078:
079: Tree tree = store.get(expr);
080:
081: this .builder = store.getBuilder();
082: this .bindings = tree.bind(functions, variables);
083: this .expr = expr;
084: this .type = returnType == void.class ? null : returnType;
085: this .types = paramTypes;
086: this .node = tree.getRoot();
087: this .deferred = tree.isDeferred();
088: this .is_void = returnType == void.class;
089:
090: if (node.isLiteralText()) {
091: if (returnType == void.class) {
092: throw new ELException(LocalMessages.get(
093: "error.method.literal.void", expr));
094: }
095: } else {
096: if (!node.isLeftValue()) {
097: throw new ELException(LocalMessages.get(
098: "error.method.invalid", expr));
099: }
100: if (paramTypes == null) {
101: throw new ELException(LocalMessages
102: .get("error.method.notypes"));
103: }
104: }
105: }
106:
107: private String getStructuralId() {
108: if (structure == null) {
109: structure = node.getStructuralId(bindings);
110: }
111: return structure;
112: }
113:
114: /**
115: * Evaluates the expression and answers information about the method
116: * @param context used to resolve properties (<code>base.property</code> and <code>base[property]</code>)
117: * @return method information or <code>null</code> for literal expressions
118: * @throws ELException if evaluation fails (e.g. suitable method not found)
119: */
120: @Override
121: public MethodInfo getMethodInfo(ELContext context)
122: throws ELException {
123: return node.getMethodInfo(bindings, context,
124: is_void ? void.class : type, types);
125: }
126:
127: @Override
128: public String getExpressionString() {
129: return expr;
130: }
131:
132: /**
133: * Evaluates the expression and invokes the method.
134: * @param context used to resolve properties (<code>base.property</code> and <code>base[property]</code>)
135: * @return method result or <code>null</code> if this is a literal text expression
136: * @throws ELException if evaluation fails (e.g. suitable method not found)
137: */
138: @Override
139: public Object invoke(ELContext context, Object[] paramValues)
140: throws ELException {
141: return node.invoke(bindings, context, is_void ? void.class
142: : type, types, paramValues);
143: }
144:
145: /**
146: * @return <code>true</code> if this is a literal text expression
147: */
148: @Override
149: public boolean isLiteralText() {
150: return node.isLiteralText();
151: }
152:
153: /**
154: * Answer <code>true</code> if this is a deferred expression (starting with <code>#{</code>)
155: */
156: public boolean isDeferred() {
157: return deferred;
158: }
159:
160: /**
161: * Expressions are compared using the concept of a <em>structural id</em>:
162: * variable and function names are anonymized such that two expressions with
163: * same tree structure will also have the same structural id and vice versa.
164: * Two method expressions are equal if
165: * <ol>
166: * <li>their builders are equal</li>
167: * <li>their structural id's are equal</li>
168: * <li>their bindings are equal</li>
169: * <li>their expected types match (see below)</li>
170: * <li>their parameter types are equal (no-text method expressions only)</li>
171: * </ol>
172: * For text method expressions, the two expected types match if both types are the
173: * same or one of them is <code>null</code> and the other is <code>Object.class</code>.
174: * For non-text method expressions, the expected types match if both types are the same
175: * or one of them is <code>null</code>.
176: */
177: @Override
178: public boolean equals(Object obj) {
179: if (obj != null && obj.getClass() == getClass()) {
180: TreeMethodExpression other = (TreeMethodExpression) obj;
181: if (!builder.equals(other.builder)) {
182: return false;
183: }
184: if (type != other.type) {
185: if (type != null && other.type != null) {
186: return false;
187: }
188: if (isLiteralText()) {
189: if (type != Object.class
190: && other.type != Object.class) {
191: return false;
192: }
193: } else {
194: if (type != null && other.is_void
195: || other.type != null && is_void) {
196: return false;
197: }
198: }
199: }
200: if (!isLiteralText() && !Arrays.equals(types, other.types)) {
201: return false;
202: }
203: return getStructuralId().equals(other.getStructuralId())
204: && bindings.equals(other.bindings);
205: }
206: return false;
207: }
208:
209: @Override
210: public int hashCode() {
211: return getStructuralId().hashCode();
212: }
213:
214: @Override
215: public String toString() {
216: return "TreeMethodExpression(" + expr + ")";
217: }
218:
219: /**
220: * Print the parse tree.
221: * @param writer
222: */
223: public void dump(PrintWriter writer) {
224: NodePrinter.dump(writer, node);
225: }
226:
227: private void readObject(ObjectInputStream in) throws IOException,
228: ClassNotFoundException {
229: in.defaultReadObject();
230: try {
231: node = builder.build(expr).getRoot();
232: } catch (ELException e) {
233: throw new IOException(e.getMessage());
234: }
235: }
236: }
|