001: /*
002: * Copyright 2004-2007 Gary Bentley
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may
005: * not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015: package org.josql.expressions;
016:
017: import java.util.List;
018: import java.util.Map;
019: import java.util.HashMap;
020:
021: import com.gentlyweb.utils.Getter;
022:
023: import org.josql.Query;
024: import org.josql.QueryExecutionException;
025: import org.josql.QueryParseException;
026:
027: import org.josql.internal.Utilities;
028:
029: /**
030: * This class represents a "bind variable" used within a SQL statement.
031: * A bind variable can be either:
032: * <ul>
033: * <li>Named - a named bind variable is prefixed with ":", the value of the
034: * variable is set in the {@link Query} object via: {@link Query#setVariable(String,Object)}.
035: * The parser used (javacc generated) is a little picky about "reserved keywords" and as
036: * such you cannot use the following as the names of bind variables: select, from, limit,
037: * execute, on, all, results, where, having, order, by, group. If you <b>must</b> use one of
038: * reserved names then suffix it with "$", i.e. "execute$", "limit$" and so on.</li>
039: * <li>Anonymous - an anonymous bind variable is represented as "?". The value of the
040: * variable is set in the {@link Query} object via: {@link Query#setVariable(int,Object)}.
041: * Anonymous variables are assigned an internal integer index starting at 1 and they increase
042: * by 1.</li>
043: * <li>Special - there are 3 types of "special" bind variable:
044: * <ul>
045: * <li><b>:_currobj</b> - Indicates the current object within the execution scope.</li>
046: * <li><b>:_allobjs</b> - Indicates the current set of objects within the execution scope.</li>
047: * <li><b>:_query</b> - Indicates the Query object.</li>
048: * <li><b>:_grpby</b> - Indicates the Group By object. If there are more than 1 group by
049: * objects then :_grpby1 ... :_grpby2 ... :_grpbyX can be used to identify which
050: * group by object you mean. Remember that the group by object only has meaning
051: * if there is a group by statement.</li>
052: * <li><b>:_grpbys</b> - Indicates the set of group bys, this will be a list of lists, one
053: * list for each group by key, and each group by column will be in that list.
054: * <li><b>:_groupby</b> - A synonym for <b>:_grpby</b>.</li>
055: * <li><b>:_groupbys</b> - A synonym for <b>:_grpbys</b>.</li>
056: * </ul>
057: * <p>
058: * Special bind variables are mainly used in function calls however they can also be used
059: * just about anywhere.
060: * </li>
061: * </ul>
062: * <h3>Examples</h3>
063: * <pre>
064: * SELECT :_query,
065: * :_allobjs,
066: * :_currobj
067: * FROM java.lang.Object
068: *
069: * SELECT name
070: * FROM java.io.File
071: * WHERE length(:_currobj, name) > :length
072: * AND length > avg(:_query,:_allobjs,length)
073: * AND path LIKE '%' + ?
074: * </pre>
075: * <h3>Accessors</h3>
076: * <p>
077: * It is also possible for bind variables (including the special variables) to have accessors.
078: * For example:
079: * <pre>
080: * SELECT :_query.variables
081: * FROM java.lang.Object
082: * </pre>
083: * <p>
084: * Would cause all the bind variables in the query to be returned.
085: * Also, if the ? in the next query is an instance of <code>java.lang.String</code>.
086: * <pre>
087: * SELECT ?.length
088: * FROM java.lang.Object
089: * </pre>
090: */
091: public class BindVariable extends ValueExpression {
092:
093: public static final String SPECIAL_NAME_PREFIX = "_";
094: private static Map SPECIAL_VAR_NAMES;
095:
096: static {
097:
098: BindVariable.SPECIAL_VAR_NAMES = new HashMap();
099: BindVariable.SPECIAL_VAR_NAMES.put(Query.QUERY_BIND_VAR_NAME,
100: "");
101: BindVariable.SPECIAL_VAR_NAMES.put(Query.CURR_OBJ_VAR_NAME, "");
102: BindVariable.SPECIAL_VAR_NAMES.put(Query.ALL_OBJS_VAR_NAME, "");
103: BindVariable.SPECIAL_VAR_NAMES
104: .put(Query.GRPBY_OBJ_VAR_NAME, "");
105: BindVariable.SPECIAL_VAR_NAMES.put(
106: Query.GRPBY_OBJ_VAR_NAME_SYNONYM, "");
107: BindVariable.SPECIAL_VAR_NAMES.put(Query.PARENT_BIND_VAR_NAME,
108: "");
109:
110: }
111:
112: private String name = null;
113: private Object val = null;
114: private boolean anon = false;
115: private String acc = null;
116: private Getter get = null;
117: private boolean groupByVar = false;
118: private int groupByInd = 0;
119:
120: public boolean equals(Object o) {
121:
122: if (o == null) {
123:
124: return false;
125:
126: }
127:
128: if (!(o instanceof BindVariable)) {
129:
130: return false;
131:
132: }
133:
134: BindVariable b = (BindVariable) o;
135:
136: if ((b.getName() != null) && (this .name != null)
137: && (b.getName().equals(this .name))) {
138:
139: if ((this .acc != null) && (b.getAccessor() != null)
140: && (b.getAccessor().equals(this .acc))) {
141:
142: return true;
143:
144: }
145:
146: }
147:
148: return false;
149:
150: }
151:
152: public String getAccessor() {
153:
154: return this .acc;
155:
156: }
157:
158: public void setAccessor(String a) {
159:
160: this .acc = a;
161:
162: }
163:
164: /**
165: * Get the expected return type.
166: * The exact class returned here is dependent (obviously) on what the bind variable
167: * represents. Wherever possible it attempts to get the most specific class for the
168: * variable. It is generally better to set the variables prior to executing the:
169: * {@link Query#parse(String)} method to ensure that the correct class is returned here.
170: *
171: * @param q The Query object.
172: * @return The return type class or <code>java.lang.Object.class</code> if the class
173: * cannot be determined.
174: * @throws QueryParseException If the type cannot be determined.
175: */
176: public Class getExpectedReturnType(Query q)
177: throws QueryParseException {
178:
179: if (this .get != null) {
180:
181: return this .get.getType();
182:
183: }
184:
185: if (this .val != null) {
186:
187: return this .val.getClass();
188:
189: }
190:
191: return q.getVariableClass(this .name);
192:
193: }
194:
195: private void initForGroupByName(String n, Query q)
196: throws QueryParseException {
197:
198: List grpBys = q.getGroupByColumns();
199:
200: // This is a group by... check for a group by clause.
201: if (grpBys == null) {
202:
203: throw new QueryParseException(
204: "Use of special group by object bind variable: "
205: + name
206: + " is not valid when there are no GROUP BY clauses defined.");
207:
208: }
209:
210: String rest = "";
211:
212: // Trim it down to see if there is a number.
213: if (n.startsWith(Query.GRPBY_OBJ_VAR_NAME)) {
214:
215: rest = n.substring(Query.GRPBY_OBJ_VAR_NAME.length());
216:
217: }
218:
219: if (n.startsWith(Query.GRPBY_OBJ_VAR_NAME_SYNONYM)) {
220:
221: rest = n.substring(Query.GRPBY_OBJ_VAR_NAME_SYNONYM
222: .length());
223:
224: }
225:
226: int grpbyind = 1;
227:
228: if (rest.length() > 0) {
229:
230: // Should be a number.
231: try {
232:
233: grpbyind = Integer.parseInt(rest);
234:
235: } catch (Exception e) {
236:
237: throw new QueryParseException(
238: "Special bind variable name: "
239: + this .name
240: + " is not valid, expected an integer number at end of name for indexing into GROUP BYs.");
241:
242: }
243:
244: if (grpbyind < 1) {
245:
246: throw new QueryParseException(
247: "Special bind variable name: "
248: + this .name
249: + " is not valid, integer to index GROUP BYs must be a minimum of 1.");
250:
251: }
252:
253: if (grpbyind > (grpBys.size())) {
254:
255: throw new QueryParseException(
256: "Special bind variable name: "
257: + this .name
258: + " is not valid, integer references GROUP BY: "
259: + grpbyind
260: + " however there are only: "
261: + grpBys.size() + " GROUP BYs defined.");
262:
263: }
264:
265: }
266:
267: this .groupByVar = true;
268: this .groupByInd = grpbyind;
269:
270: }
271:
272: /**
273: * Initialises this bind variable.
274: * If the bind variable is "anonymous" then a name is gained for it from the
275: * Query object. If there is a value then it is gained from the Query object and
276: * cached. Also, if there is an accessor defined then it is inited where possible.
277: *
278: * @param q The Query object.
279: * @throws QueryParseException If the bind variable cannot be inited.
280: */
281: public void init(Query q) throws QueryParseException {
282:
283: if (this .anon) {
284:
285: this .name = q.getAnonymousBindVariableName();
286:
287: }
288:
289: String n = this .name.toLowerCase();
290:
291: if ((n.startsWith(Query.GRPBY_OBJ_VAR_NAME))
292: || (n.startsWith(Query.GRPBY_OBJ_VAR_NAME_SYNONYM))) {
293:
294: this .initForGroupByName(n, q);
295:
296: } else {
297:
298: if (n.startsWith(BindVariable.SPECIAL_NAME_PREFIX)) {
299:
300: // Make sure it's valid.
301: if (!BindVariable.SPECIAL_VAR_NAMES.containsKey(n)) {
302:
303: throw new QueryParseException(
304: "Bind variable name: "
305: + name
306: + " is not valid, bind variable names starting with: "
307: + BindVariable.SPECIAL_NAME_PREFIX
308: + " are reserved, and must be one of: "
309: + BindVariable.SPECIAL_VAR_NAMES
310: .keySet());
311:
312: }
313:
314: }
315:
316: }
317:
318: // See if we already have this bind variable set...
319: this .val = q.getVariable(this .name);
320:
321: // See if we have a "trailing" accessor.
322: if ((this .val != null) && (this .acc != null)) {
323:
324: this .initGetter(this .val);
325:
326: try {
327:
328: this .val = this .get.getValue(this .val);
329:
330: } catch (Exception e) {
331:
332: throw new QueryParseException(
333: "Unable to get value from accessor: "
334: + this .acc + " and class: "
335: + this .val.getClass().getName()
336: + " from bind variable: " + this .name,
337: e);
338:
339: }
340:
341: }
342:
343: // See if we can init the getter... there are times when it
344: // is possible even if the bind variable isn't available yet.
345: if ((this .acc != null) && (this .get == null)) {
346:
347: // Not over keen on this method but it will do for now...
348: // It precludes the init occurring if we are working on java.lang.Object
349: // objects... but how many times will that happen?
350: Class c = q.getVariableClass(this .name);
351:
352: if (!c.isInstance(new Object())) {
353:
354: // Init the getter.
355: this .initGetter(c);
356:
357: }
358:
359: }
360:
361: }
362:
363: public String getName() {
364:
365: return this .name;
366:
367: }
368:
369: public boolean isAnonymous() {
370:
371: return this .anon;
372:
373: }
374:
375: public void setAnonymous(boolean v) {
376:
377: this .anon = v;
378:
379: }
380:
381: public void setName(String name) {
382:
383: this .name = name;
384:
385: }
386:
387: private void initGetter(Object o) {
388:
389: // Get the class for the value.
390: Class c = o.getClass();
391:
392: this .initGetter(c);
393:
394: }
395:
396: private void initGetter(Class c) {
397:
398: this .get = new Getter(this .acc, c);
399:
400: }
401:
402: /**
403: * Gets the value of this bind variable.
404: *
405: * @param o The current object. Note that this variable isn't used in this method.
406: * @param q The Query object.
407: * @return The value.
408: * @throws QueryExecutionException If something goes wrong during the accessing
409: * of the value.
410: */
411: public Object getValue(Object o, Query q)
412: throws QueryExecutionException {
413:
414: if (this .groupByVar) {
415:
416: o = q.getGroupByVariable(this .groupByInd);
417:
418: } else {
419:
420: o = q.getVariable(this .name);
421:
422: }
423:
424: if ((this .acc != null) && (this .get == null) && (o != null)) {
425:
426: // Unable to get the accessor...
427: this .initGetter(o);
428:
429: }
430:
431: if (this .get != null) {
432:
433: try {
434:
435: o = this .get.getValue(o);
436:
437: } catch (Exception e) {
438:
439: throw new QueryExecutionException(
440: "Unable to get value for accessor: " + this .acc
441: + ", class: "
442: + this .get.getBaseClass().getName()
443: + " from bind variable: " + this .name,
444: e);
445:
446: }
447:
448: }
449:
450: return o;
451:
452: }
453:
454: /**
455: * Returns whether the value of this bind variable represents a <code>true</code>
456: * value. See: {@link ArithmeticExpression#isTrue(Object,Query)} for details of how
457: * the return value is determined.
458: *
459: * @param o The current object. Not used in this method.
460: * @param q The Query object.
461: * @return <code>true</code> if the bind variable evaluates to <code>true</code>.
462: * @throws QueryExecutionException If a problem occurs during evaluation.
463: */
464: public boolean isTrue(Object o, Query q)
465: throws QueryExecutionException {
466:
467: o = this .getValue(o, q);
468:
469: if (o == null) {
470:
471: return false;
472:
473: }
474:
475: if (Utilities.isNumber(o)) {
476:
477: return Utilities.getDouble(o) > 0;
478:
479: }
480:
481: // Not null so return true...
482: return true;
483:
484: }
485:
486: /**
487: * Evaluates the value of this bind variable. This is just a thin-wrapper around:
488: * {@link #getValue(Object,Query)}.
489: *
490: * @param o The current object, not used in this method.
491: * @param q The Query object.
492: * @return The value of this bind variable.
493: * @throws QueryExecutionException If there is a problem getting the value.
494: */
495: public Object evaluate(Object o, Query q)
496: throws QueryExecutionException {
497:
498: return this .getValue(o, q);
499:
500: }
501:
502: /**
503: * Returns a string version of this bind variable.
504: * Returns in the form: ? | ":" [ "_" ] Name
505: *
506: * @return A string version of the bind variable.
507: */
508: public String toString() {
509:
510: StringBuffer buf = new StringBuffer();
511:
512: if (this .anon) {
513:
514: buf.append("?");
515:
516: } else {
517:
518: buf.append(":");
519: buf.append(this .name);
520:
521: }
522:
523: if (this .acc != null) {
524:
525: buf.append(".");
526: buf.append(this .acc);
527:
528: }
529:
530: if (this .isBracketed()) {
531:
532: buf.insert(0, "(");
533: buf.append(")");
534:
535: }
536:
537: return buf.toString();
538:
539: }
540:
541: /**
542: * Will always return false since a bind variable cannot be fixed.
543: *
544: * @param q The Query object.
545: * @return <code>false</code> always.
546: */
547: public boolean hasFixedResult(Query q) {
548:
549: // A bind variable cannot have a fixed result.
550: return false;
551:
552: }
553:
554: }
|