001: /*
002: * Created on 12-Jan-2006
003: *
004: * TODO To change the template for this generated file go to
005: * Window - Preferences - Java - Code Style - Code Templates
006: */
007: package com.jofti.parser;
008:
009: import java.util.ArrayList;
010: import java.util.Comparator;
011: import java.util.Iterator;
012: import java.util.LinkedHashMap;
013: import java.util.List;
014: import java.util.Map;
015: import java.util.Stack;
016:
017: import antlr.collections.AST;
018:
019: import com.jofti.core.IPredicate;
020: import com.jofti.exception.JoftiException;
021: import com.jofti.introspect.ClassIntrospector;
022: import com.jofti.util.ArrayComparator;
023: import com.jofti.util.CompositeComparator;
024: import com.jofti.util.ReflectionComparator;
025:
026: /**
027: * Provides the base functionality for other Parser types.
028: *
029: * @author xenephon
030: * @version 1.2
031: *
032: */
033: public class AbstractParser {
034:
035: /**
036: *
037: */
038: protected ClassIntrospector introspector = null;
039: protected Map compiledQueries = null;
040:
041: protected Object lockObject = new Object();
042:
043: static final String NULL = "NULL";
044:
045: public AbstractParser(final int cachedQueries,
046: ClassIntrospector introspector) {
047:
048: this .introspector = introspector;
049: // cache of previously parsed queries
050: compiledQueries = new LinkedHashMap() {
051:
052: private final int MAX_ENTRIES = cachedQueries;
053:
054: protected boolean removeEldestEntry(Map.Entry eldest) {
055: return size() > MAX_ENTRIES;
056: }
057:
058: };
059: }
060:
061: /**
062: * Parses the where predicates.
063: * @param expression - start of the where query
064: * @param stack - the predicate stack
065: * @param query
066: * @return
067: * @throws JoftiException
068: */
069: protected Stack parseWherePredicates(AST expression, Stack stack,
070: ParsedQuery query) throws JoftiException {
071:
072: // parse the first expression
073: AST subExpression = parseExpression(expression, stack, query);
074:
075: // keep going while we have a predicate to parse
076: while (subExpression != null) {
077:
078: subExpression = parseExpression(subExpression, stack, query);
079: }
080: return stack;
081: }
082:
083: // parse an individual statement
084: protected AST parseExpression(AST expression, Stack stack,
085: ParsedQuery parsedQuery) throws JoftiException {
086:
087: // is the expression joined via a boolean operator?
088: if (expression.getType() == CommonLexerTokenTypes.AND
089: || expression.getType() == CommonLexerTokenTypes.OR) {
090: // we have joined statements
091:
092: stack.push(new Operator(expression.getType()));
093: return expression.getNextSibling();
094:
095: // is it a grouped expression
096: } else if (expression.getType() == CommonLexerTokenTypes.LPAREN) {
097:
098: // parse the subpredicate
099: AST nextNode = parseSubPredicate(expression, parsedQuery,
100: stack);
101: return nextNode;
102: } else if (expression.getType() == CommonLexerTokenTypes.RPAREN) {
103: //
104: return expression.getNextSibling();
105: } else if (expression != null) {
106:
107: // we have a single predicate
108: IPredicate predicate = constructPredicate(expression,
109: parsedQuery);
110: stack.push(predicate);
111: return expression.getNextSibling();
112: }
113: return expression;
114:
115: }
116:
117: protected AST parseSubPredicate(AST expression, ParsedQuery query,
118: Stack stack) throws JoftiException {
119:
120: Stack subStack = new Stack();
121:
122: // get next expression
123: AST subExpression = expression.getNextSibling();
124: // parse the expression
125: subExpression = parseExpression(subExpression, subStack, query);
126: while (subExpression != null
127: && subExpression.getType() != CommonLexerTokenTypes.RPAREN) {
128: subExpression = parseExpression(subExpression, subStack,
129: query);
130: }
131: if (subExpression != null
132: && subExpression.getType() == CommonLexerTokenTypes.RPAREN) {
133: subExpression = subExpression.getNextSibling();
134: }
135: StackPredicate predicate = new StackPredicate();
136: predicate.setPredicateStack(subStack);
137: // set the predicate in the current stack
138: stack.push(predicate);
139: // return where we are in the tree
140:
141: return subExpression;
142: }
143:
144: protected IPredicate constructPredicate(AST expression,
145: ParsedQuery query) throws JoftiException {
146:
147: // set up the predicate
148: Predicate temp = new Predicate();
149: temp.setOperator(expression.getType());
150:
151: AST field = expression.getFirstChild();
152:
153: String realValue = field.getText();
154:
155: //
156: if (query.getAliasMap().size() > 0) {
157: // we need to make sure that all fields have aliases
158: int dotIndex = realValue.indexOf(".");
159: if (dotIndex < 0) {
160: throw new JoftiException(
161: "Alias prefix is missing from field "
162: + realValue);
163: } else {
164: //make sure the alias is in the map
165: String alias = realValue.substring(0, dotIndex);
166: if (query.getAliasMap().get(alias) == null) {
167: throw new JoftiException(
168: "Alias prefix is missing or incorrect from field "
169: + realValue);
170: } else {
171: // set the field into the predicate
172: temp.setField(realValue.substring(dotIndex + 1));
173: temp.setAlias(alias);
174: }
175:
176: }
177:
178: } else {
179: temp.setField(realValue);
180: }
181:
182: AST value = field.getNextSibling();
183:
184: if (value == null) {
185: throw new JoftiException(
186: "field name is missing from expression - check that you are not trying to use 'and' or 'or' as field names");
187: }
188:
189: // if empty this could be valid
190: if (value.getText().length() == 0) {
191: temp.setValue(value.getText());
192: // else a named parameter could be present
193: } else if (value.getText().charAt(0) == ':') {
194: if (value.getText().length() < 2) {
195: throw new JoftiException("Parameter " + value.getText()
196: + " must have an associated identifier");
197: }
198: temp.setValue(value.getText());
199: // check if numeric one is
200: } else if (value.getText().charAt(0) == '?') {
201: if (value.getText().length() < 2) {
202: throw new JoftiException("Parameter " + value.getText()
203: + " must have an associated identifier");
204:
205: }
206: // see if it is a number
207: try {
208: Integer.parseInt(value.getText().substring(1));
209: } catch (NumberFormatException nfe) {
210: throw new JoftiException("Parameter " + value.getText()
211: + " is not numeric");
212: }
213: temp.setValue(value.getText());
214: } else if (temp.getOperator() == CommonLexerTokenTypes.IS
215: || temp.getOperator() == CommonLexerTokenTypes.NOT) {
216: if (!(NULL.equalsIgnoreCase(value.getText()))) {
217: throw new JoftiException(
218: "Only value 'NULL' is supported for operators IS or NOT");
219:
220: }
221: temp.setValue(value.getText());
222: } else if (temp.getOperator() == CommonLexerTokenTypes.LIKE) {
223: if (!(value.getText().endsWith("%") || value.getText()
224: .length() < 2)) {
225: throw new JoftiException(
226: "Value must end with '%' for operator LIKE and contain at least 1 character prefix");
227:
228: }
229: temp.setValue(value.getText());
230: } else if (temp.getOperator() == CommonLexerTokenTypes.IN) {
231:
232: if (value.getType() != CommonLexerTokenTypes.LPAREN) {
233: throw new JoftiException(
234: "argument for IN operator Collection must be of format (val1,val2,..,valn)");
235:
236: }
237:
238: List tempList = new ArrayList();
239: while (value.getNextSibling() != null
240: && value.getNextSibling().getType() != CommonLexerTokenTypes.RPAREN) {
241: value = value.getNextSibling();
242: if (value.getType() != CommonLexerTokenTypes.COMMA) {
243: tempList.add(value.getText());
244: }
245:
246: }
247: temp.setValue(tempList);
248: } else {
249: temp.setValue(value.getText());
250: }
251:
252: return temp;
253:
254: }
255:
256: protected Object[] getMethodsForProperty(Class singleClass,
257: Map aliasMap, String alias) throws JoftiException {
258: int aliasSeperator = alias.indexOf('.');
259:
260: Class clazz = null;
261:
262: if (singleClass == null) {
263: clazz = getClassForAlias(aliasMap, alias);
264: } else {
265: clazz = singleClass;
266: }
267:
268: String attribute = alias.substring(aliasSeperator + 1, alias
269: .length());
270: if (clazz == null) {
271: throw new JoftiException(
272: "attribute "
273: + alias
274: + " must consist of Class.property and Class must be specified in from clause");
275: }
276:
277: Object[] methods = introspector.getMethodsForAttribute(clazz,
278: attribute);
279: if (methods == null || methods.length == 0) {
280: // log.warn
281: throw new JoftiException("Attribute " + attribute
282: + " is not valid for " + clazz);
283: } else {
284: return methods;
285: }
286:
287: }
288:
289: protected Class getClassForAlias(Map aliasMap, String alias)
290: throws JoftiException {
291: int aliasSeperator = alias.indexOf('.');
292:
293: if (aliasSeperator > 0) {
294: String aliasObj = alias.substring(0, aliasSeperator);
295:
296: // we need to split them up
297: Class clazz = (Class) aliasMap.get(aliasObj);
298: if (clazz == null) {
299: throw new JoftiException("identifier " + aliasObj
300: + " in " + alias
301: + " is not specified as a Class in from clause");
302:
303: }
304: return clazz;
305: } else if (aliasSeperator == 0) {
306: throw new JoftiException("Object identifier:" + alias
307: + " cannot start with '.'");
308: }
309: return null;
310: }
311:
312: protected int getIndexFor(Map aliasMap, Map returnMap, String alias)
313: throws JoftiException {
314: int aliasSeperator = alias.indexOf('.');
315:
316: Class clazz = getClassForAlias(aliasMap, alias);
317:
318: if (clazz == null) {
319: throw new JoftiException(
320: "attribute "
321: + alias
322: + " must consist of Class.property and Class must be specified in from clause");
323: }
324:
325: ClassFieldMethods fieldSet = (ClassFieldMethods) returnMap
326: .get(clazz);
327: if (fieldSet == null) {
328: throw new JoftiException("ordering class " + clazz
329: + " is not in select clause");
330: }
331:
332: String attribute = alias.substring(aliasSeperator + 1, alias
333: .length());
334: int i = -1;
335: Iterator it = fieldSet.getFieldMap().keySet().iterator();
336:
337: for (int j = 0; j < fieldSet.getFieldMap().size(); j++) {
338: String key = (String) it.next();
339: if (key.equals(attribute)) {
340: i = j;
341: }
342: }
343:
344: return i;
345:
346: }
347:
348: protected ParsedQuery parseOrder(AST expression, ParsedQuery query)
349: throws JoftiException {
350:
351: // will be alias for field
352: AST node = expression;
353: boolean arrayIteratorType = false;
354: // work out whether we need object or array iterators
355: if (query.getFieldReturnMap().size() > 1) {
356: throw new JoftiException(
357: "ORDER BY clause is not allowed if multi-object return is specified");
358: } else {
359: //either fields or a whole object is specified
360: Iterator it = query.getFieldReturnMap().values().iterator();
361: for (int i = 0; i < query.getFieldReturnMap().size(); i++) {
362: ClassFieldMethods temp = (ClassFieldMethods) it.next();
363: if (temp != null && temp.getFieldMap() != null
364: && temp.getFieldMap().size() > 0) {
365: // we are using array returns object
366: arrayIteratorType = true;
367: }
368: }
369: }
370:
371: node = node.getFirstChild().getNextSibling();
372:
373: do {
374: // we have an as
375:
376: // put the aliases into the alias and return map
377: CompositeComparator comp = query.getOrderingComparator();
378: Comparator itemComp = null;
379:
380: if (arrayIteratorType) {
381: int index = getIndexFor(query.getAliasMap(), query
382: .getFieldReturnMap(), node.getText().trim());
383: if (index < 0) {
384: throw new JoftiException("ORDER BY attribute "
385: + node.getText().trim()
386: + " must be specified in select clause");
387: }
388: itemComp = new ArrayComparator(index);
389: } else {
390: Object[] methods = getMethodsForProperty(query
391: .getClassName(), query.getAliasMap(), node
392: .getText().trim());
393:
394: itemComp = new ReflectionComparator(methods,
395: introspector);
396: }
397:
398: node = node.getNextSibling();
399: if (node != null) {
400: switch (node.getType()) {
401: case CommonLexerTokenTypes.ASC:
402: comp.addComparator(itemComp);
403: node = node.getNextSibling();
404: break;
405: case CommonLexerTokenTypes.DESC:
406: comp.addComparator(itemComp, true);
407: node = node.getNextSibling();
408: break;
409: default:
410: comp.addComparator(itemComp);
411: break;
412:
413: }
414: } else {
415: comp.addComparator(itemComp);
416:
417: }
418:
419: } while (node != null);
420:
421: return query;
422:
423: }
424:
425: public Map getCompiledQueries() {
426: return compiledQueries;
427: }
428: }
|