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;
019:
020: import org.mvel.optimizers.AbstractOptimizer;
021: import org.mvel.optimizers.impl.refl.FieldAccessor;
022: import static org.mvel.util.ParseTools.getBestCandidate;
023: import static org.mvel.util.ParseTools.parseParameterList;
024: import org.mvel.util.PropertyTools;
025: import org.mvel.util.StringAppender;
026:
027: import java.lang.reflect.Field;
028: import java.lang.reflect.Member;
029: import java.lang.reflect.Method;
030: import java.util.LinkedList;
031: import java.util.List;
032:
033: public class PropertyVerifier extends AbstractOptimizer {
034: private static final int DONE = -1;
035: private static final int NORM = 0;
036: private static final int METH = 1;
037: private static final int COL = 2;
038:
039: private ParserContext parserContext;
040: private List<String> inputs = new LinkedList<String>();
041: private boolean first = true;
042:
043: public PropertyVerifier(char[] property, ParserContext parserContext) {
044: this .expr = property;
045: this .length = property.length;
046: this .parserContext = parserContext;
047: }
048:
049: public PropertyVerifier(String property, ParserContext parserContext) {
050: this .length = (this .expr = property.toCharArray()).length;
051: this .parserContext = parserContext;
052: }
053:
054: public List<String> getInputs() {
055: return inputs;
056: }
057:
058: public void setInputs(List<String> inputs) {
059: this .inputs = inputs;
060: }
061:
062: public Class analyze() {
063: Class ctx = Object.class;
064:
065: first = true;
066: while (cursor < length) {
067: switch (nextSubToken()) {
068: case NORM:
069: ctx = getBeanProperty(ctx, capture());
070: break;
071: case METH:
072: ctx = getMethod(ctx, capture());
073: break;
074: case COL:
075: ctx = getCollectionProperty();
076: break;
077: case DONE:
078: break;
079: }
080: first = false;
081: }
082:
083: return ctx;
084: }
085:
086: private Class getBeanProperty(Class ctx, String property) {
087: if (first) {
088: if (parserContext.hasVarOrInput(property)) {
089: return parserContext.getVarOrInputType(property);
090: } else if (AbstractParser.LITERALS.containsKey(property)) {
091: return (Class) AbstractParser.LITERALS.get(property);
092: } else {
093: return Object.class;
094: }
095: }
096:
097: start = cursor;
098:
099: Member member = ctx != null ? PropertyTools.getFieldOrAccessor(
100: ctx, property) : null;
101:
102: if (member instanceof Field) {
103: FieldAccessor accessor = new FieldAccessor();
104: accessor.setField((Field) member);
105:
106: return ((Field) member).getType();
107: } else if (member != null) {
108: return ((Method) member).getReturnType();
109: } else if (AbstractParser.LITERALS.containsKey(property)) {
110: return (Class) AbstractParser.LITERALS.get(property);
111: } else {
112: Object tryStaticMethodRef = tryStaticAccess();
113:
114: if (tryStaticMethodRef != null) {
115: if (tryStaticMethodRef instanceof Class) {
116: return tryStaticMethodRef.getClass();
117: } else {
118: try {
119: return ((Field) tryStaticMethodRef).get(null)
120: .getClass();
121: } catch (Exception e) {
122: throw new CompileException("in verifier: ", e);
123: }
124: }
125:
126: } else if (ctx != null && ctx.getClass() == Class.class) {
127: for (Method m : ctx.getMethods()) {
128: if (property.equals(m.getName())) {
129: return m.getReturnType();
130: }
131: }
132: }
133:
134: if (parserContext.isStrictTypeEnforcement()) {
135: addFatalError("unqualified type in strict mode for: "
136: + property);
137: }
138: return Object.class;
139:
140: }
141: }
142:
143: private Class getCollectionProperty() {
144:
145: int start = ++cursor;
146:
147: whiteSpaceSkip();
148:
149: if (cursor == length)
150: throw new PropertyAccessException("unterminated '['");
151:
152: if (!scanTo(']')) {
153: addFatalError("unterminated [ in token");
154: }
155:
156: ExpressionCompiler compiler = new ExpressionCompiler(
157: new String(expr, start, cursor - start));
158: compiler._compile();
159:
160: ++cursor;
161:
162: return compiler.getReturnType() == null ? Object.class
163: : compiler.getReturnType();
164: }
165:
166: private Class getMethod(Class ctx, String name) {
167: if (first && parserContext.hasImport(name)) {
168: Method m = parserContext.getStaticImport(name);
169: ctx = m.getDeclaringClass();
170: name = m.getName();
171: first = false;
172: }
173:
174: int st = cursor;
175:
176: int depth = 1;
177:
178: while (cursor++ < length - 1 && depth != 0) {
179: switch (expr[cursor]) {
180: case '(':
181: depth++;
182: continue;
183: case ')':
184: depth--;
185: }
186: }
187: cursor--;
188:
189: String tk = (cursor - st) > 1 ? new String(expr, st + 1, cursor
190: - st - 1) : "";
191:
192: cursor++;
193:
194: ExpressionCompiler verifCompiler;
195: if (tk.length() > 0) {
196: String[] subtokens = parseParameterList(tk.toCharArray(),
197: 0, -1);
198: for (String token : subtokens) {
199: verifCompiler = new ExpressionCompiler(token);
200: verifCompiler._compile();
201: }
202: }
203:
204: Class[] args;
205:
206: if (tk.length() == 0) {
207: args = new Class[0];
208: } else {
209: String[] subtokens = parseParameterList(tk.toCharArray(),
210: 0, -1);
211: args = new Class[subtokens.length];
212: for (int i = 0; i < subtokens.length; i++) {
213: ExpressionCompiler compiler = new ExpressionCompiler(
214: subtokens[i]);
215: compiler.setVerifying(true);
216: compiler._compile();
217: args[i] = compiler.getReturnType();
218: }
219: }
220:
221: /**
222: * If the target object is an instance of java.lang.Class itself then do not
223: * adjust the Class scope target.
224: */
225:
226: Method m;
227:
228: /**
229: * If we have not cached the method then we need to go ahead and try to resolve it.
230: */
231: /**
232: * Try to find an instance method from the class target.
233: */
234:
235: if ((m = getBestCandidate(args, name, ctx.getMethods())) == null) {
236: if ((m = getBestCandidate(args, name, ctx
237: .getDeclaredMethods())) == null) {
238: StringAppender errorBuild = new StringAppender();
239: for (int i = 0; i < args.length; i++) {
240: errorBuild.append(args[i] != null ? args[i]
241: .getClass().getName() : null);
242: if (i < args.length - 1)
243: errorBuild.append(", ");
244: }
245:
246: if ("size".equals(name) && args.length == 0
247: && ctx.isArray()) {
248: return Integer.class;
249: }
250:
251: if (parserContext.isStrictTypeEnforcement()) {
252: addFatalError("unable to resolve method using strict-mode: "
253: + ctx.getName() + "." + name + "(...)");
254: }
255: return Object.class;
256: }
257: }
258:
259: return m.getReturnType();
260: }
261: }
|