001: /**
002: * MVEL (The MVFLEX Expression Language)
003: *
004: * Copyright (C) 2007 Christopher Brock, MVFLEX/Valhalla Project and the Codehaus
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: */package org.mvel.compiler;
019:
020: import org.mvel.CompileException;
021: import org.mvel.ParserContext;
022: import org.mvel.PropertyAccessException;
023: import org.mvel.ast.Function;
024: import org.mvel.optimizers.AbstractOptimizer;
025: import org.mvel.util.ParseTools;
026: import static org.mvel.util.ParseTools.getBestCandidate;
027: import static org.mvel.util.ParseTools.parseParameterList;
028: import static org.mvel.util.ParseTools.balancedCapture;
029: import static org.mvel.util.PropertyTools.getFieldOrAccessor;
030: import org.mvel.util.StringAppender;
031:
032: import java.lang.reflect.Field;
033: import java.lang.reflect.Member;
034: import java.lang.reflect.Method;
035: import java.util.LinkedList;
036: import java.util.List;
037:
038: public class PropertyVerifier extends AbstractOptimizer {
039: private static final int DONE = -1;
040: private static final int NORM = 0;
041: private static final int METH = 1;
042: private static final int COL = 2;
043:
044: private ParserContext parserContext;
045: private List<String> inputs = new LinkedList<String>();
046: private boolean first = true;
047: private boolean resolvedExternally;
048:
049: public PropertyVerifier(char[] property, ParserContext parserContext) {
050: this .length = (this .expr = property).length;
051: this .parserContext = parserContext;
052: }
053:
054: public PropertyVerifier(String property, ParserContext parserContext) {
055: this .length = (this .expr = property.toCharArray()).length;
056: this .parserContext = parserContext;
057: }
058:
059: public List<String> getInputs() {
060: return inputs;
061: }
062:
063: public void setInputs(List<String> inputs) {
064: this .inputs = inputs;
065: }
066:
067: public Class analyze() {
068: Class ctx = Object.class;
069: resolvedExternally = true;
070:
071: first = true;
072: while (cursor < length) {
073: switch (nextSubToken()) {
074: case NORM:
075: ctx = getBeanProperty(ctx, capture());
076: break;
077: case METH:
078: ctx = getMethod(ctx, capture());
079: break;
080: case COL:
081: ctx = getCollectionProperty();
082: break;
083: case DONE:
084: break;
085: }
086:
087: first = false;
088: }
089: return ctx;
090: }
091:
092: private Class getBeanProperty(Class ctx, String property) {
093: if (first) {
094: if (parserContext.hasVarOrInput(property)) {
095: return parserContext.getVarOrInputType(property);
096: } else if (parserContext.hasImport(property)) {
097: resolvedExternally = false;
098: return parserContext.getImport(property);
099: } else {
100: return Object.class;
101: }
102: }
103:
104: start = cursor;
105:
106: Member member = ctx != null ? getFieldOrAccessor(ctx, property)
107: : null;
108:
109: if (member instanceof Field) {
110: return ((Field) member).getType();
111: } else if (member != null) {
112: return ((Method) member).getReturnType();
113: } else if (parserContext.hasImport(property)) {
114: return parserContext.getImport(property);
115: } else {
116: Object tryStaticMethodRef = tryStaticAccess();
117:
118: if (tryStaticMethodRef != null) {
119: if (tryStaticMethodRef instanceof Class) {
120: return tryStaticMethodRef.getClass();
121: } else if (tryStaticMethodRef instanceof Field) {
122: try {
123: return ((Field) tryStaticMethodRef).get(null)
124: .getClass();
125: } catch (Exception e) {
126: throw new CompileException("in verifier: ", e);
127: }
128: } else {
129: try {
130: return ((Method) tryStaticMethodRef)
131: .getReturnType();
132: } catch (Exception e) {
133: throw new CompileException("in verifier: ", e);
134: }
135: }
136:
137: } else if (ctx != null && ctx.getClass() == Class.class) {
138: for (Method m : ctx.getMethods()) {
139: if (property.equals(m.getName())) {
140: return m.getReturnType();
141: }
142: }
143: }
144:
145: if (parserContext.isStrictTypeEnforcement()) {
146: addFatalError("unqualified type in strict mode for: "
147: + property);
148: }
149: return Object.class;
150: }
151: }
152:
153: private Class getCollectionProperty() {
154: int start = ++cursor;
155:
156: whiteSpaceSkip();
157:
158: if (cursor == length)
159: throw new PropertyAccessException("unterminated '['");
160:
161: if (!scanTo(']')) {
162: addFatalError("unterminated [ in token");
163: }
164:
165: ExpressionCompiler compiler = new ExpressionCompiler(
166: new String(expr, start, cursor - start));
167: compiler._compile();
168:
169: ++cursor;
170:
171: return compiler.getReturnType() == null ? Object.class
172: : compiler.getReturnType();
173: }
174:
175: private Class getMethod(Class ctx, String name) {
176: if (first) {
177: if (parserContext.hasImport(name)) {
178: Method m = parserContext.getStaticImport(name)
179: .getMethod();
180: ctx = m.getDeclaringClass();
181: name = m.getName();
182: first = false;
183: } else if (parserContext.hasFunction(name)) {
184: resolvedExternally = false;
185: Function f = parserContext.getFunction(name);
186: return f.getEgressType();
187: }
188: }
189:
190: int st = cursor;
191:
192: String tk = ((cursor = balancedCapture(expr, cursor, '(')) - st) > 1 ? new String(
193: expr, st + 1, cursor - st - 1)
194: : "";
195:
196: cursor++;
197:
198: if (tk.length() > 0) {
199: for (String token : parseParameterList(tk.toCharArray(), 0,
200: -1)) {
201: new ExpressionCompiler(token)._compile();
202: }
203: }
204:
205: Class[] args;
206:
207: if (tk.length() == 0) {
208: args = new Class[0];
209: } else {
210: String[] subtokens = parseParameterList(tk.toCharArray(),
211: 0, -1);
212: args = new Class[subtokens.length];
213: ExpressionCompiler compiler;
214: for (int i = 0; i < subtokens.length; i++) {
215: (compiler = new ExpressionCompiler(subtokens[i], true))
216: ._compile();
217: args[i] = compiler.getReturnType() != null ? compiler
218: .getReturnType() : Object.class;
219: }
220: }
221:
222: /**
223: * If the target object is an instance of java.lang.Class itself then do not
224: * adjust the Class scope target.
225: */
226:
227: Method m;
228:
229: /**
230: * If we have not cached the method then we need to go ahead and try to resolve it.
231: */
232: /**
233: * Try to find an instance method from the class target.
234: */
235:
236: if ((m = getBestCandidate(args, name, ctx, ctx.getMethods())) == null) {
237: if ((m = getBestCandidate(args, name, ctx, ctx
238: .getDeclaredMethods())) == null) {
239: StringAppender errorBuild = new StringAppender();
240: for (int i = 0; i < args.length; i++) {
241: errorBuild.append(args[i] != null ? args[i]
242: .getClass().getName() : null);
243: if (i < args.length - 1)
244: errorBuild.append(", ");
245: }
246:
247: if ("size".equals(name) && args.length == 0
248: && ctx.isArray()) {
249: return Integer.class;
250: }
251:
252: if (parserContext.isStrictTypeEnforcement()) {
253: addFatalError("unable to resolve method using strict-mode: "
254: + ctx.getName() + "." + name + "(...)");
255: }
256: return Object.class;
257: }
258: }
259:
260: return m.getReturnType();
261: }
262:
263: public boolean isResolvedExternally() {
264: return resolvedExternally;
265: }
266: }
|