1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.kernel.jpql;
20
21 import java.io.PrintStream;
22 import java.io.Serializable;
23 import java.lang.reflect.Field;
24 import java.math.BigDecimal;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.Set;
30 import java.util.Stack;
31 import java.util.TreeSet;
32
33 import org.apache.commons.collections.map.LinkedMap;
34 import org.apache.openjpa.kernel.ExpressionStoreQuery;
35 import org.apache.openjpa.kernel.QueryContext;
36 import org.apache.openjpa.kernel.QueryOperations;
37 import org.apache.openjpa.kernel.StoreContext;
38 import org.apache.openjpa.kernel.BrokerFactory;
39 import org.apache.openjpa.kernel.exps.AbstractExpressionBuilder;
40 import org.apache.openjpa.kernel.exps.Expression;
41 import org.apache.openjpa.kernel.exps.ExpressionFactory;
42 import org.apache.openjpa.kernel.exps.Literal;
43 import org.apache.openjpa.kernel.exps.Parameter;
44 import org.apache.openjpa.kernel.exps.Path;
45 import org.apache.openjpa.kernel.exps.QueryExpressions;
46 import org.apache.openjpa.kernel.exps.Subquery;
47 import org.apache.openjpa.kernel.exps.Value;
48 import org.apache.openjpa.lib.util.Localizer;
49 import org.apache.openjpa.lib.log.Log;
50 import org.apache.openjpa.meta.ClassMetaData;
51 import org.apache.openjpa.meta.FieldMetaData;
52 import org.apache.openjpa.meta.MetaDataRepository;
53 import org.apache.openjpa.meta.ValueMetaData;
54 import org.apache.openjpa.util.InternalException;
55 import org.apache.openjpa.util.UserException;
56 import org.apache.openjpa.conf.Compatibility;
57 import org.apache.openjpa.conf.OpenJPAConfiguration;
58 import serp.util.Numbers;
59
60 /**
61 * Builder for JPQL expressions. This class takes the query parsed
62 * in {@link JPQL} and converts it to an expression tree using
63 * an {@link ExpressionFactory}. Public for unit testing purposes.
64 *
65 * @author Marc Prud'hommeaux
66 * @author Patrick Linskey
67 * @nojavadoc
68 */
69 public class JPQLExpressionBuilder
70 extends AbstractExpressionBuilder
71 implements JPQLTreeConstants {
72
73 private static final int VAR_PATH = 1;
74 private static final int VAR_ERROR = 2;
75
76 private static final Localizer _loc = Localizer.forPackage
77 (JPQLExpressionBuilder.class);
78
79 private final Stack contexts = new Stack();
80 private LinkedMap parameterTypes;
81 private int aliasCount = 0;
82
83 /**
84 * Constructor.
85 *
86 * @param factory the expression factory to use
87 * @param query used to resolve variables, parameters,
88 * and class names used in the query
89 * @param parsedQuery the parsed query
90 */
91 public JPQLExpressionBuilder(ExpressionFactory factory,
92 ExpressionStoreQuery query, Object parsedQuery) {
93 super(factory, query.getResolver());
94
95 contexts.push(new Context(parsedQuery instanceof ParsedJPQL
96 ? (ParsedJPQL) parsedQuery
97 : parsedQuery instanceof String
98 ? getParsedQuery((String) parsedQuery)
99 : null, null));
100
101 if (ctx().parsed == null)
102 throw new InternalException(parsedQuery + "");
103 }
104
105 protected Localizer getLocalizer() {
106 return _loc;
107 }
108
109 protected ClassLoader getClassLoader() {
110 // we don't resolve in the context of anything but ourselves
111 return getClass().getClassLoader();
112 }
113
114 protected ParsedJPQL getParsedQuery() {
115 return ctx().parsed;
116 }
117
118 protected ParsedJPQL getParsedQuery(String jpql) {
119 return new ParsedJPQL(jpql);
120 }
121
122 private void setCandidate(ClassMetaData cmd, String schemaAlias) {
123 addAccessPath(cmd);
124
125 if (cmd != null)
126 ctx().meta = cmd;
127
128 if (schemaAlias != null)
129 ctx().schemaAlias = schemaAlias;
130 }
131
132 private String nextAlias() {
133 return "jpqlalias" + (++aliasCount);
134 }
135
136 protected ClassMetaData resolveClassMetaData(JPQLNode node) {
137 // handle looking up alias names
138 String schemaName = assertSchemaName(node);
139 ClassMetaData cmd = getClassMetaData(schemaName, false);
140 if (cmd != null)
141 return cmd;
142
143 // we might be referencing a collection field of a subquery's parent
144 if (isPath(node)) {
145 Path path = getPath(node);
146 return getFieldType(path.last());
147 }
148
149 // now run again to throw the correct exception
150 return getClassMetaData(schemaName, true);
151 }
152
153 private ClassMetaData getClassMetaData(String alias, boolean assertValid) {
154 ClassLoader loader = getClassLoader();
155 MetaDataRepository repos = resolver.getConfiguration().
156 getMetaDataRepositoryInstance();
157
158 // first check for the alias
159 ClassMetaData cmd = repos.getMetaData(alias, loader, false);
160
161 if (cmd != null)
162 return cmd;
163
164 // now check for the class name; this is not technically permitted
165 // by the JPA spec, but is required in order to be able to execute
166 // JPQL queries from other facades (like JDO) that do not have
167 // the concept of entity names or aliases
168 Class c = resolver.classForName(alias, null);
169 if (c != null)
170 cmd = repos.getMetaData(c, loader, assertValid);
171 else if (assertValid)
172 cmd = repos.getMetaData(alias, loader, false);
173
174 if (cmd == null && assertValid) {
175 String close = repos.getClosestAliasName(alias);
176 if (close != null)
177 throw parseException(EX_USER, "not-schema-name-hint",
178 new Object[]{ alias, close, repos.getAliasNames() }, null);
179 else
180 throw parseException(EX_USER, "not-schema-name",
181 new Object[]{ alias, repos.getAliasNames() }, null);
182 }
183
184 return cmd;
185 }
186
187 private Class getCandidateType() {
188 return getCandidateMetaData().getDescribedType();
189 }
190
191 private ClassMetaData getCandidateMetaData() {
192 if (ctx().meta != null)
193 return ctx().meta;
194
195 ClassMetaData cls = getCandidateMetaData(root());
196 if (cls == null)
197 throw parseException(EX_USER, "not-schema-name",
198 new Object[]{ root() }, null);
199
200 setCandidate(cls, null);
201 return cls;
202 }
203
204 protected ClassMetaData getCandidateMetaData(JPQLNode node) {
205 // examing the node to find the candidate query
206 // ### this should actually be the primary SELECT instance
207 // resolved against the from variable declarations
208 JPQLNode from = node.findChildByID(JJTFROMITEM, true);
209 if (from == null) {
210 // OPENJPA-15 allow subquery without a FROMITEM
211 if (node.id == JJTSUBSELECT) {
212 from = node.findChildByID(JJTFROM, true);
213 }
214 else {
215 throw parseException(EX_USER, "no-from-clause", null, null);
216 }
217 }
218
219 for (int i = 0; i < from.children.length; i++) {
220 JPQLNode n = from.children[i];
221
222 if (n.id == JJTABSTRACTSCHEMANAME) {
223 // we simply return the first abstract schema child
224 // as resolved into a class
225 ClassMetaData cmd = resolveClassMetaData(n);
226
227 if (cmd != null)
228 return cmd;
229
230 // not a schema: treat it as a class
231 String cls = assertSchemaName(n);
232 if (cls == null)
233 throw parseException(EX_USER, "not-schema-name",
234 new Object[]{ root() }, null);
235
236 return getClassMetaData(cls, true);
237 }
238 // OPENJPA-15 support subquery's from clause do not start with
239 // identification_variable_declaration()
240 if (node.id == JJTSUBSELECT) {
241 if (n.id == JJTINNERJOIN) {
242 n = n.getChild(0);
243 }
244 if (n.id == JJTPATH) {
245 Path path = getPath(n);
246 ClassMetaData cmd = getFieldType(path.last());
247 if (cmd != null) {
248 return cmd;
249 }
250 else {
251 throw parseException(EX_USER, "no-alias",
252 new Object[]{ n }, null);
253 }
254 }
255 }
256 }
257
258 return null;
259 }
260
261 protected String currentQuery() {
262 return ctx().parsed == null || root().parser == null ? null
263 : root().parser.jpql;
264 }
265
266 QueryExpressions getQueryExpressions() {
267 QueryExpressions exps = new QueryExpressions();
268
269 evalQueryOperation(exps);
270
271 Expression filter = null;
272 filter = and(evalFromClause(root().id == JJTSELECT), filter);
273 filter = and(evalWhereClause(), filter);
274 filter = and(evalSelectClause(exps), filter);
275
276 exps.filter = filter == null ? factory.emptyExpression() : filter;
277
278 evalGroupingClause(exps);
279 evalHavingClause(exps);
280 evalFetchJoins(exps);
281 evalSetClause(exps);
282 evalOrderingClauses(exps);
283
284 if (parameterTypes != null)
285 exps.parameterTypes = parameterTypes;
286
287 exps.accessPath = getAccessPath();
288
289 return exps;
290 }
291
292 private Expression and(Expression e1, Expression e2) {
293 return e1 == null ? e2 : e2 == null ? e1 : factory.and(e1, e2);
294 }
295
296 private static String assemble(JPQLNode node) {
297 return assemble(node, ".", 0);
298 }
299
300 /**
301 * Assemble the children of the specific node by appending each
302 * child, separated by the delimiter.
303 */
304 private static String assemble(JPQLNode node, String delimiter, int last) {
305 StringBuffer result = new StringBuffer();
306 JPQLNode[] parts = node.children;
307 for (int i = 0; parts != null && i < parts.length - last; i++)
308 result.append(result.length() > 0 ? delimiter : "").
309 append(parts[i].text);
310
311 return result.toString();
312 }
313
314 private Expression assignProjections(JPQLNode parametersNode,
315 QueryExpressions exps) {
316 int count = parametersNode.getChildCount();
317 exps.projections = new Value[count];
318 exps.projectionClauses = new String[count];
319 exps.projectionAliases = new String[count];
320
321 Expression exp = null;
322 for (int i = 0; i < count; i++) {
323 JPQLNode parent = parametersNode.getChild(i);
324 JPQLNode node = onlyChild(parent);
325 Value proj = getValue(node);
326 exps.projections[i] = proj;
327 exps.projectionClauses[i] = assemble(node);
328 exps.projectionAliases[i] = nextAlias();
329 }
330 return exp;
331 }
332
333 private void evalQueryOperation(QueryExpressions exps) {
334 // determine whether we want to select, delete, or update
335 if (root().id == JJTSELECT || root().id == JJTSUBSELECT)
336 exps.operation = QueryOperations.OP_SELECT;
337 else if (root().id == JJTDELETE)
338 exps.operation = QueryOperations.OP_DELETE;
339 else if (root().id == JJTUPDATE)
340 exps.operation = QueryOperations.OP_UPDATE;
341 else
342 throw parseException(EX_UNSUPPORTED, "unrecognized-operation",
343 new Object[]{ root() }, null);
344 }
345
346 private void evalGroupingClause(QueryExpressions exps) {
347 // handle GROUP BY clauses
348 JPQLNode groupByNode = root().findChildByID(JJTGROUPBY, true);
349
350 if (groupByNode == null)
351 return;
352
353 int groupByCount = groupByNode.getChildCount();
354
355 exps.grouping = new Value[groupByCount];
356
357 for (int i = 0; i < groupByCount; i++) {
358 JPQLNode node = groupByNode.getChild(i);
359 exps.grouping[i] = getValue(node);
360 }
361 }
362
363 private void evalHavingClause(QueryExpressions exps) {
364 // handle HAVING clauses
365 JPQLNode havingNode = root().findChildByID(JJTHAVING, true);
366
367 if (havingNode == null)
368 return;
369
370 exps.having = getExpression(onlyChild(havingNode));
371 }
372
373 private void evalOrderingClauses(QueryExpressions exps) {
374 // handle ORDER BY clauses
375 JPQLNode orderby = root().findChildByID(JJTORDERBY, false);
376 if (orderby != null) {
377 int ordercount = orderby.getChildCount();
378 exps.ordering = new Value[ordercount];
379 exps.orderingClauses = new String[ordercount];
380 exps.ascending = new boolean[ordercount];
381 for (int i = 0; i < ordercount; i++) {
382 JPQLNode node = orderby.getChild(i);
383 exps.ordering[i] = getValue(firstChild(node));
384 exps.orderingClauses[i] = assemble(firstChild(node));
385 // ommission of ASC/DESC token implies ascending
386 exps.ascending[i] = node.getChildCount() <= 1 ||
387 lastChild(node).id == JJTASCENDING ? true : false;
388 }
389 }
390 }
391
392 private Expression evalSelectClause(QueryExpressions exps) {
393 if (exps.operation != QueryOperations.OP_SELECT)
394 return null;
395
396 JPQLNode selectNode = root();
397
398 JPQLNode selectClause = selectNode.
399 findChildByID(JJTSELECTCLAUSE, false);
400 if (selectClause != null && selectClause.hasChildID(JJTDISTINCT))
401 exps.distinct = exps.DISTINCT_TRUE | exps.DISTINCT_AUTO;
402 else
403 exps.distinct = exps.DISTINCT_FALSE;
404
405 JPQLNode constructor = selectNode.findChildByID(JJTCONSTRUCTOR, true);
406 if (constructor != null) {
407 // build up the fully-qualified result class name by
408 // appending together the components of the children
409 String resultClassName = assemble(left(constructor));
410 exps.resultClass = resolver.classForName(resultClassName, null);
411
412 // now assign the arguments to the select clause as the projections
413 return assignProjections(right(constructor), exps);
414 } else {
415 // handle SELECT clauses
416 JPQLNode expNode = selectNode.
417 findChildByID(JJTSELECTEXPRESSIONS, true);
418 if (expNode == null)
419 return null;
420
421 int selectCount = expNode.getChildCount();
422 JPQLNode selectChild = firstChild(expNode);
423
424 // if we are selecting just one thing and that thing is the
425 // schema's alias, then do not treat it as a projection
426 if (selectCount == 1 && selectChild != null &&
427 selectChild.getChildCount() == 1 &&
428 onlyChild(selectChild) != null &&
429 assertSchemaAlias().
430 equalsIgnoreCase(onlyChild(selectChild).text)) {
431 return null;
432 } else {
433 // JPQL does not filter relational joins for projections
434 exps.distinct &= ~exps.DISTINCT_AUTO;
435 return assignProjections(expNode, exps);
436 }
437 }
438 }
439
440 private String assertSchemaAlias() {
441 String alias = ctx().schemaAlias;
442
443 if (alias == null)
444 throw parseException(EX_USER, "alias-required",
445 new Object[]{ ctx().meta }, null);
446
447 return alias;
448 }
449
450 protected Expression evalFetchJoins(QueryExpressions exps) {
451 Expression filter = null;
452
453 // handle JOIN FETCH
454 Set joins = null;
455 Set innerJoins = null;
456
457 JPQLNode[] outers = root().findChildrenByID(JJTOUTERFETCHJOIN);
458 for (int i = 0; outers != null && i < outers.length; i++)
459 (joins == null ? joins = new TreeSet() : joins).
460 add(getPath(onlyChild(outers[i])).last().getFullName(false));
461
462 JPQLNode[] inners = root().findChildrenByID(JJTINNERFETCHJOIN);
463 for (int i = 0; inners != null && i < inners.length; i++) {
464 String path = getPath(onlyChild(inners[i])).last()
465 .getFullName(false);
466 (joins == null ? joins = new TreeSet() : joins).add(path);
467 (innerJoins == null ? innerJoins = new TreeSet() : innerJoins).
468 add(path);
469 }
470
471 if (joins != null)
472 exps.fetchPaths = (String[]) joins.
473 toArray(new String[joins.size()]);
474 if (innerJoins != null)
475 exps.fetchInnerPaths = (String[]) innerJoins.
476 toArray(new String[innerJoins.size()]);
477
478 return filter;
479 }
480
481 protected void evalSetClause(QueryExpressions exps) {
482 // handle SET field = value
483 JPQLNode[] nodes = root().findChildrenByID(JJTUPDATEITEM);
484 for (int i = 0; nodes != null && i < nodes.length; i++) {
485 Path path = getPath(firstChild(nodes[i]));
486 Value val = getValue(onlyChild(lastChild(nodes[i])));
487 exps.putUpdate(path, val);
488 }
489 }
490
491 private Expression evalWhereClause() {
492 // evaluate the WHERE clause
493 JPQLNode whereNode = root().findChildByID(JJTWHERE, false);
494 if (whereNode == null)
495 return null;
496 return (Expression) eval(whereNode);
497 }
498
499 private Expression evalFromClause(boolean needsAlias) {
500 Expression exp = null;
501
502 // build up the alias map in the FROM clause
503 JPQLNode from = root().findChildByID(JJTFROM, false);
504 if (from == null)
505 throw parseException(EX_USER, "no-from-clause", null, null);
506
507 for (int i = 0; i < from.children.length; i++) {
508 JPQLNode node = from.children[i];
509
510 if (node.id == JJTFROMITEM)
511 exp = evalFromItem(exp, node, needsAlias);
512 else if (node.id == JJTOUTERJOIN)
513 exp = addJoin(node, false, exp);
514 else if (node.id == JJTINNERJOIN)
515 exp = addJoin(node, true, exp);
516 else if (node.id == JJTINNERFETCHJOIN)
517 ; // we handle inner fetch joins in the evalFetchJoins() method
518 else if (node.id == JJTOUTERFETCHJOIN)
519 ; // we handle outer fetch joins in the evalFetchJoins() method
520 else
521 throw parseException(EX_USER, "not-schema-name",
522 new Object[]{ node }, null);
523 }
524
525 return exp;
526 }
527
528 /**
529 * Adds a join condition to the given expression.
530 *
531 * @param node the node to check
532 * @param inner whether or not the join should be an inner join
533 * @param exp an existing expression to AND, or null if none
534 * @return the Expression with the join condition added
535 */
536 private Expression addJoin(JPQLNode node, boolean inner, Expression exp) {
537 // the type will be the declared type for the field
538 Path path = getPath(firstChild(node), false, inner);
539
540 JPQLNode alias = node.getChildCount() >= 2 ? right(node) : null;
541 // OPENJPA-15 support subquery's from clause do not start with
542 // identification_variable_declaration()
543 if (inner && ctx().subquery != null && ctx().schemaAlias == null) {
544 setCandidate(getFieldType(path.last()), alias.text);
545
546 Path subpath = factory.newPath(ctx().subquery);
547 subpath.setMetaData(ctx().subquery.getMetaData());
548 exp = and(exp, factory.equal(path, subpath));
549 }
550
551 return addJoin(path, alias, exp);
552 }
553
554 private Expression addJoin(Path path, JPQLNode aliasNode,
555 Expression exp) {
556 FieldMetaData fmd = path.last();
557
558 if (fmd == null)
559 throw parseException(EX_USER, "path-no-meta",
560 new Object[]{ path, null }, null);
561
562 String alias = aliasNode != null ? aliasNode.text : nextAlias();
563
564 Value var = getVariable(alias, true);
565 var.setMetaData(getFieldType(fmd));
566
567 Expression join = null;
568
569 // if the variable is already bound, get the var's value and
570 // do a regular contains with that
571 boolean bound = isBound(var);
572 if (bound) {
573 var = getValue(aliasNode, VAR_PATH);
574 } else {
575 bind(var);
576 join = and(join, factory.bindVariable(var, path));
577 }
578
579 if (!fmd.isTypePC()) // multi-valued relation
580 {
581 if (bound)
582 join = and(join, factory.contains(path, var));
583
584 setImplicitContainsTypes(path, var, CONTAINS_TYPE_ELEMENT);
585 }
586
587 return and(exp, join);
588 }
589
590 private Expression evalFromItem(Expression exp, JPQLNode node,
591 boolean needsAlias) {
592 ClassMetaData cmd = resolveClassMetaData(firstChild(node));
593
594 String alias = null;
595
596 if (node.getChildCount() < 2) {
597 if (needsAlias)
598 throw parseException(EX_USER, "alias-required",
599 new Object[]{ cmd }, null);
600 } else {
601 alias = right(node).text;
602 JPQLNode left = left(node);
603
604 // check to see if the we are referring to a path in the from
605 // clause, since we might be in a subquery against a collection
606 if (isPath(left)) {
607 Path path = getPath(left);
608 setCandidate(getFieldType(path.last()), alias);
609
610 Path subpath = factory.newPath(ctx().subquery);
611 subpath.setMetaData(ctx().subquery.getMetaData());
612 return and(exp, factory.equal(path, subpath));
613 } else {
614 // we have an alias: bind it as a variable
615 Value var = getVariable(alias, true);
616 var.setMetaData(cmd);
617 bind(var);
618 }
619 }
620
621 // ### we assign the first FROMITEM instance we see as
622 // the global candidate, which is incorrect: we should
623 // instead be mapping this to the SELECTITEM to see
624 // which is the desired candidate
625 if (ctx().schemaAlias == null)
626 setCandidate(cmd, alias);
627
628 return exp;
629 }
630
631 protected boolean isDeclaredVariable(String name) {
632 // JPQL doesn't support declaring variables
633 return false;
634 }
635
636 /**
637 * Check to see if the specific node is a path (vs. a schema name)
638 */
639 boolean isPath(JPQLNode node) {
640 if (node.getChildCount() < 2)
641 return false;
642
643 final String name = firstChild(node).text;
644 if (name == null)
645 return false;
646
647 // handle the case where the class name is the alias
648 // for the candidate (we don't use variables for this)
649 if (getMetaDataForAlias(name) != null)
650 return true;
651
652 if (!isSeenVariable(name))
653 return false;
654
655 final Value var = getVariable(name, false);
656
657 if (var != null)
658 return isBound(var);
659
660 return false;
661 }
662
663 private static ClassMetaData getFieldType(FieldMetaData fmd) {
664 if (fmd == null)
665 return null;
666
667 ClassMetaData cmd = null;
668 ValueMetaData vmd;
669
670 if ((vmd = fmd.getElement()) != null)
671 cmd = vmd.getDeclaredTypeMetaData();
672 else if ((vmd = fmd.getKey()) != null)
673 cmd = vmd.getDeclaredTypeMetaData();
674 else if ((vmd = fmd.getValue()) != null)
675 cmd = vmd.getDeclaredTypeMetaData();
676
677 if (cmd == null || cmd.getDescribedType() == Object.class)
678 cmd = fmd.getDeclaredTypeMetaData();
679
680 return cmd;
681 }
682
683 /**
684 * Identification variables in JPQL are case insensitive, so lower-case
685 * all variables we are going to bind.
686 */
687 protected Value getVariable(String id, boolean bind) {
688 if (id == null)
689 return null;
690
691 return super.getVariable(id.toLowerCase(), bind);
692 }
693
694 protected boolean isSeendVariable(String id) {
695 return id != null && super.isSeenVariable(id.toLowerCase());
696 }
697
698 /**
699 * Returns the class name using the children of the JPQLNode.
700 */
701 private String assertSchemaName(JPQLNode node) {
702 if (node.id != JJTABSTRACTSCHEMANAME)
703 throw parseException(EX_USER, "not-identifer",
704 new Object[]{ node }, null);
705
706 return assemble(node);
707 }
708
709 /**
710 * Recursive helper method to evaluate the given node.
711 */
712 private Object eval(JPQLNode node) {
713 Value val1 = null;
714 Value val2 = null;
715 Value val3 = null;
716
717 boolean not = node.not;
718
719 switch (node.id) {
720 case JJTWHERE: // top-level WHERE clause
721 return getExpression(onlyChild(node));
722
723 case JJTBOOLEANLITERAL:
724 return factory.newLiteral("true".equalsIgnoreCase
725 (node.text) ? Boolean.TRUE : Boolean.FALSE,
726 Literal.TYPE_BOOLEAN);
727
728 case JJTINTEGERLITERAL:
729 // use BigDecimal because it can handle parsing exponents
730 BigDecimal intlit = new BigDecimal
731 (node.text.endsWith("l") || node.text.endsWith("L")
732 ? node.text.substring(0, node.text.length() - 1)
733 : node.text).
734 multiply(new BigDecimal(negative(node)));
735 return factory.newLiteral(new Long(intlit.longValue()),
736 Literal.TYPE_NUMBER);
737
738 case JJTDECIMALLITERAL:
739 BigDecimal declit = new BigDecimal
740 (node.text.endsWith("d") || node.text.endsWith("D") ||
741 node.text.endsWith("f") || node.text.endsWith("F")
742 ? node.text.substring(0, node.text.length() - 1)
743 : node.text).
744 multiply(new BigDecimal(negative(node)));
745 return factory.newLiteral(declit, Literal.TYPE_NUMBER);
746
747 case JJTSTRINGLITERAL:
748 case JJTTRIMCHARACTER:
749 case JJTESCAPECHARACTER:
750 return factory.newLiteral(trimQuotes(node.text),
751 Literal.TYPE_SQ_STRING);
752
753 case JJTPATTERNVALUE:
754 return eval(firstChild(node));
755
756 case JJTNAMEDINPUTPARAMETER:
757 return getParameter(node.text, false);
758
759 case JJTPOSITIONALINPUTPARAMETER:
760 return getParameter(node.text, true);
761
762 case JJTOR: // x OR y
763 return factory.or(getExpression(left(node)),
764 getExpression(right(node)));
765
766 case JJTAND: // x AND y
767 return and(getExpression(left(node)),
768 getExpression(right(node)));
769
770 case JJTEQUALS: // x = y
771 val1 = getValue(left(node));
772 val2 = getValue(right(node));
773 setImplicitTypes(val1, val2, null);
774 return factory.equal(val1, val2);
775
776 case JJTNOTEQUALS: // x <> y
777 val1 = getValue(left(node));
778 val2 = getValue(right(node));
779 setImplicitTypes(val1, val2, null);
780 return factory.notEqual(val1, val2);
781
782 case JJTLESSTHAN: // x < y
783 val1 = getValue(left(node));
784 val2 = getValue(right(node));
785 setImplicitTypes(val1, val2, null);
786 return factory.lessThan(val1, val2);
787
788 case JJTLESSOREQUAL: // x <= y
789 val1 = getValue(left(node));
790 val2 = getValue(right(node));
791 setImplicitTypes(val1, val2, null);
792 return factory.lessThanEqual(val1, val2);
793
794 case JJTGREATERTHAN: // x > y
795 val1 = getValue(left(node));
796 val2 = getValue(right(node));
797 setImplicitTypes(val1, val2, null);
798 return factory.greaterThan(val1, val2);
799
800 case JJTGREATEROREQUAL: // x >= y
801 val1 = getValue(left(node));
802 val2 = getValue(right(node));
803 setImplicitTypes(val1, val2, null);
804 return factory.greaterThanEqual(val1, val2);
805
806 case JJTADD: // x + y
807 val1 = getValue(left(node));
808 val2 = getValue(right(node));
809 setImplicitTypes(val1, val2, TYPE_NUMBER);
810 return factory.add(val1, val2);
811
812 case JJTSUBTRACT: // x - y
813 val1 = getValue(left(node));
814 val2 = getValue(right(node));
815 setImplicitTypes(val1, val2, TYPE_NUMBER);
816 return factory.subtract(val1, val2);
817
818 case JJTMULTIPLY: // x * y
819 val1 = getValue(left(node));
820 val2 = getValue(right(node));
821 setImplicitTypes(val1, val2, TYPE_NUMBER);
822 return factory.multiply(val1, val2);
823
824 case JJTDIVIDE: // x / y
825 val1 = getValue(left(node));
826 val2 = getValue(right(node));
827 setImplicitTypes(val1, val2, TYPE_NUMBER);
828 return factory.divide(val1, val2);
829
830 case JJTBETWEEN: // x.field [NOT] BETWEEN 5 AND 10
831 val1 = getValue(child(node, 0, 3));
832 val2 = getValue(child(node, 1, 3));
833 val3 = getValue(child(node, 2, 3));
834 setImplicitTypes(val1, val2, null);
835 setImplicitTypes(val1, val3, null);
836 return evalNot(not, and(factory.greaterThanEqual(val1, val2),
837 factory.lessThanEqual(val1, val3)));
838
839 case JJTIN: // x.field [NOT] IN ('a', 'b', 'c')
840
841 Expression inExp = null;
842 Iterator inIterator = node.iterator();
843 // the first child is the path
844 val1 = getValue((JPQLNode) inIterator.next());
845
846 while (inIterator.hasNext()) {
847 val2 = getValue((JPQLNode) inIterator.next());
848
849 // special case for <value> IN (<subquery>) or
850 // <value> IN (<single value>)
851 if (!(val2 instanceof Literal) && node.getChildCount() == 2)
852 return evalNot(not, factory.contains(val2, val1));
853
854 // this is currently a sequence of OR expressions, since we
855 // do not have support for IN expressions
856 setImplicitTypes(val1, val2, null);
857 if (inExp == null)
858 inExp = factory.equal(val1, val2);
859 else
860 inExp = factory.or(inExp, factory.equal(val1, val2));
861 }
862
863 // we additionally need to add in a "NOT NULL" clause, since
864 // the IN behavior that is expected by the CTS also expects
865 // to filter our NULLs
866 return and(evalNot(not, inExp),
867 factory.notEqual(val1, factory.getNull()));
868
869 case JJTISNULL: // x.field IS [NOT] NULL
870 if (not)
871 return factory.notEqual
872 (getValue(onlyChild(node)), factory.getNull());
873 else
874 return factory.equal
875 (getValue(onlyChild(node)), factory.getNull());
876
877 case JJTPATH:
878 return getPathOrConstant(node);
879
880 case JJTIDENTIFIER:
881 case JJTIDENTIFICATIONVARIABLE:
882 return getIdentifier(node);
883
884 case JJTNOT:
885 return factory.not(getExpression(onlyChild(node)));
886
887 case JJTLIKE: // field LIKE '%someval%'
888 val1 = getValue(left(node));
889 val2 = getValue(right(node));
890
891 setImplicitType(val1, TYPE_STRING);
892 setImplicitType(val2, TYPE_STRING);
893
894 // look for an escape character beneath the node
895 String escape = null;
896 JPQLNode escapeNode = right(node).
897 findChildByID(JJTESCAPECHARACTER, true);
898 if (escapeNode != null)
899 escape = trimQuotes(onlyChild(escapeNode).text);
900
901 if (not)
902 return factory.notMatches(val1, val2, "_", "%", escape);
903 else
904 return factory.matches(val1, val2, "_", "%", escape);
905
906 case JJTISEMPTY:
907 return evalNot(not,
908 factory.isEmpty(getValue(onlyChild(node))));
909
910 case JJTSIZE:
911 return factory.size(getValue(onlyChild(node)));
912
913 case JJTUPPER:
914 val1 = getValue(onlyChild(node));
915 setImplicitType(val1, TYPE_STRING);
916 return factory.toUpperCase(val1);
917
918 case JJTLOWER:
919 return factory.toLowerCase(getStringValue(onlyChild(node)));
920
921 case JJTLENGTH:
922 return factory.stringLength(getStringValue(onlyChild(node)));
923
924 case JJTABS:
925 return factory.abs(getNumberValue(onlyChild(node)));
926
927 case JJTSQRT:
928 return factory.sqrt(getNumberValue(onlyChild(node)));
929
930 case JJTMOD:
931 val1 = getValue(left(node));
932 val2 = getValue(right(node));
933 setImplicitTypes(val1, val2, TYPE_NUMBER);
934 return factory.mod(val1, val2);
935
936 case JJTTRIM: // TRIM([[where] [char] FROM] field)
937 val1 = getValue(lastChild(node));
938 setImplicitType(val1, TYPE_STRING);
939
940 Boolean trimWhere = null;
941
942 JPQLNode firstTrimChild = firstChild(node);
943
944 if (node.getChildCount() > 1) {
945 trimWhere =
946 firstTrimChild.id == JJTTRIMLEADING ? Boolean.TRUE
947 :
948 firstTrimChild.id == JJTTRIMTRAILING ? Boolean.FALSE
949 : null;
950 }
951
952 Value trimChar;
953
954 // if there are 3 children, then we know the trim
955 // char is the second node
956 if (node.getChildCount() == 3)
957 trimChar = getValue(secondChild(node));
958 // if there are two children, then we need to check to see
959 // if the first child is a leading/trailing/both node,
960 // or the trim character node
961 else if (node.getChildCount() == 2
962 && firstTrimChild.id != JJTTRIMLEADING
963 && firstTrimChild.id != JJTTRIMTRAILING
964 && firstTrimChild.id != JJTTRIMBOTH)
965 trimChar = getValue(firstChild(node));
966 // othwerwise, we default to trimming the space character
967 else
968 trimChar = factory.newLiteral(" ", Literal.TYPE_STRING);
969
970 return factory.trim(val1, trimChar, trimWhere);
971
972 case JJTCONCAT:
973 val1 = getValue(left(node));
974 val2 = getValue(right(node));
975 setImplicitType(val1, TYPE_STRING);
976 setImplicitType(val2, TYPE_STRING);
977 return factory.concat(val1, val2);
978
979 case JJTSUBSTRING:
980 val1 = getValue(child(node, 0, 3));
981 val2 = getValue(child(node, 1, 3));
982 val3 = getValue(child(node, 2, 3));
983 setImplicitType(val1, TYPE_STRING);
984 setImplicitType(val2, Integer.TYPE);
985 setImplicitType(val3, Integer.TYPE);
986
987 // the semantics of the JPQL substring() function
988 // are that arg2 is the 1-based start index, and arg3 is
989 // the length of the string to be return; this is different
990 // than the semantics of the ExpressionFactory's substring,
991 // which matches the Java language (0-based start index,
992 // arg2 is the end index): we perform the translation by
993 // adding one to the first argument, and then adding the
994 // first argument to the second argument to get the endIndex
995 Value start;
996 Value end;
997 if (val2 instanceof Literal && val3 instanceof Literal) {
998 // optimize SQL for the common case of two literals
999 long jpqlStart = ((Number) ((Literal) val2).getValue())
1000 .longValue();
1001 long length = ((Number) ((Literal) val3).getValue())
1002 .longValue();
1003 start = factory.newLiteral(new Long(jpqlStart - 1),
1004 Literal.TYPE_NUMBER);
1005 long endIndex = length + (jpqlStart - 1);
1006 end = factory.newLiteral(new Long(endIndex),
1007 Literal.TYPE_NUMBER);
1008 } else {
1009 start = factory.subtract(val2, factory.newLiteral
1010 (Numbers.valueOf(1), Literal.TYPE_NUMBER));
1011 end = factory.add(val3,
1012 (factory.subtract(val2, factory.newLiteral
1013 (Numbers.valueOf(1), Literal.TYPE_NUMBER))));
1014 }
1015 return factory.substring(val1, factory.newArgumentList(
1016 start, end));
1017
1018 case JJTLOCATE:
1019 // as with SUBSTRING (above), the semantics for LOCATE differ
1020 // from ExpressionFactory.indexOf in that LOCATE uses a
1021 // 0-based index, and indexOf uses a 1-based index
1022 Value locatePath = getValue(firstChild(node));
1023 Value locateSearch = getValue(secondChild(node));
1024 Value locateFromIndex = null;
1025 if (node.getChildCount() > 2) // optional start index arg
1026 locateFromIndex = getValue(thirdChild(node));
1027
1028 setImplicitType(locatePath, TYPE_STRING);
1029 setImplicitType(locateSearch, TYPE_STRING);
1030
1031 if (locateFromIndex != null)
1032 setImplicitType(locateFromIndex, TYPE_STRING);
1033
1034 return factory.add(factory.indexOf(locateSearch,
1035 locateFromIndex == null ? locatePath
1036 : factory.newArgumentList(locatePath,
1037 factory.subtract(locateFromIndex,
1038 factory.newLiteral(Numbers.valueOf(1),
1039 Literal.TYPE_NUMBER)))),
1040 factory.newLiteral(Numbers.valueOf(1),
1041 Literal.TYPE_NUMBER));
1042
1043 case JJTAGGREGATE:
1044 // simply pass-through while asserting a single child
1045 return eval(onlyChild(node));
1046
1047 case JJTCOUNT:
1048 return factory.count(getValue(lastChild(node)));
1049
1050 case JJTMAX:
1051 return factory.max(getNumberValue(onlyChild(node)));
1052
1053 case JJTMIN:
1054 return factory.min(getNumberValue(onlyChild(node)));
1055
1056 case JJTSUM:
1057 return factory.sum(getNumberValue(onlyChild(node)));
1058
1059 case JJTAVERAGE:
1060 return factory.avg(getNumberValue(onlyChild(node)));
1061
1062 case JJTDISTINCTPATH:
1063 return factory.distinct(getValue(onlyChild(node)));
1064
1065 case JJTEXISTS:
1066 return factory.isNotEmpty((Value) eval(onlyChild(node)));
1067
1068 case JJTANY:
1069 return factory.any((Value) eval(onlyChild(node)));
1070
1071 case JJTALL:
1072 return factory.all((Value) eval(onlyChild(node)));
1073
1074 case JJTSUBSELECT:
1075 return getSubquery(node);
1076
1077 case JJTMEMBEROF:
1078 val1 = getValue(left(node), VAR_PATH);
1079 val2 = getValue(right(node), VAR_PATH);
1080 setImplicitContainsTypes(val2, val1, CONTAINS_TYPE_ELEMENT);
1081 return evalNot(not, factory.contains(val2, val1));
1082
1083 case JJTCURRENTDATE:
1084 return factory.getCurrentDate();
1085
1086 case JJTCURRENTTIME:
1087 return factory.getCurrentTime();
1088
1089 case JJTCURRENTTIMESTAMP:
1090 return factory.getCurrentTimestamp();
1091
1092 case JJTSELECTEXTENSION:
1093 assertQueryExtensions("SELECT");
1094 return eval(onlyChild(node));
1095
1096 case JJTGROUPBYEXTENSION:
1097 assertQueryExtensions("GROUP BY");
1098 return eval(onlyChild(node));
1099
1100 case JJTORDERBYEXTENSION:
1101 assertQueryExtensions("ORDER BY");
1102 return eval(onlyChild(node));
1103
1104 default:
1105 throw parseException(EX_FATAL, "bad-tree",
1106 new Object[]{ node }, null);
1107 }
1108 }
1109
1110 private void assertQueryExtensions(String clause) {
1111 OpenJPAConfiguration conf = resolver.getConfiguration();
1112 switch(conf.getCompatibilityInstance().getJPQL()) {
1113 case Compatibility.JPQL_WARN:
1114 // check if we've already warned for this query-factory combo
1115 StoreContext ctx = resolver.getQueryContext().getStoreContext();
1116 String query = currentQuery();
1117 if (ctx.getBroker() != null && query != null) {
1118 String key = getClass().getName() + ":" + query;
1119 BrokerFactory factory = ctx.getBroker().getBrokerFactory();
1120 Object hasWarned = factory.getUserObject(key);
1121 if (hasWarned != null)
1122 break;
1123 else
1124 factory.putUserObject(key, Boolean.TRUE);
1125 }
1126 Log log = conf.getLog(OpenJPAConfiguration.LOG_QUERY);
1127 if (log.isWarnEnabled())
1128 log.warn(_loc.get("query-extensions-warning", clause,
1129 currentQuery()));
1130 break;
1131 case Compatibility.JPQL_STRICT:
1132 throw new ParseException(_loc.get("query-extensions-error",
1133 clause, currentQuery()).getMessage());
1134 case Compatibility.JPQL_EXTENDED:
1135 break;
1136 default:
1137 throw new IllegalStateException(
1138 "Compatibility.getJPQL() == "
1139 + conf.getCompatibilityInstance().getJPQL());
1140 }
1141 }
1142
1143 protected void setImplicitTypes(Value val1, Value val2, Class expected) {
1144 super.setImplicitTypes(val1, val2, expected);
1145
1146 // as well as setting the types for conversions, we also need to
1147 // ensure that any parameters are declared with the correct type,
1148 // since the JPA spec expects that these will be validated
1149 Parameter param = val1 instanceof Parameter ? (Parameter) val1
1150 : val2 instanceof Parameter ? (Parameter) val2 : null;
1151 Path path = val1 instanceof Path ? (Path) val1
1152 : val2 instanceof Path ? (Path) val2 : null;
1153
1154 // we only check for parameter-to-path comparisons
1155 if (param == null || path == null || parameterTypes == null)
1156 return;
1157
1158 FieldMetaData fmd = path.last();
1159 if (fmd == null)
1160 return;
1161
1162 Class type = path.isXPath() ? path.getType() : fmd.getType();
1163 if (type == null)
1164 return;
1165
1166 String paramName = param.getParameterName();
1167 if (paramName == null)
1168 return;
1169
1170 // make sure we have already declared the parameter
1171 if (parameterTypes.containsKey(paramName))
1172 parameterTypes.put(paramName, type);
1173 }
1174
1175 private Value getStringValue(JPQLNode node) {
1176 return getTypeValue(node, TYPE_STRING);
1177 }
1178
1179 private Value getNumberValue(JPQLNode node) {
1180 return getTypeValue(node, TYPE_NUMBER);
1181 }
1182
1183 private Value getTypeValue(JPQLNode node, Class implicitType) {
1184 Value val = getValue(node);
1185 setImplicitType(val, implicitType);
1186 return val;
1187 }
1188
1189 private Value getSubquery(JPQLNode node) {
1190 final boolean subclasses = true;
1191 String alias = nextAlias();
1192
1193 // parse the subquery
1194 ParsedJPQL parsed = new ParsedJPQL(node.parser.jpql, node);
1195
1196 ClassMetaData candidate = getCandidateMetaData(node);
1197 Subquery subq = factory.newSubquery(candidate, subclasses, alias);
1198 subq.setMetaData(candidate);
1199
1200 contexts.push(new Context(parsed, subq));
1201
1202 try {
1203 QueryExpressions subexp = getQueryExpressions();
1204 subq.setQueryExpressions(subexp);
1205 return subq;
1206 } finally {
1207 // remove the subquery parse context
1208 contexts.pop();
1209 }
1210 }
1211
1212 /**
1213 * Record the names and order of implicit parameters.
1214 */
1215 private Parameter getParameter(String id, boolean positional) {
1216 if (parameterTypes == null)
1217 parameterTypes = new LinkedMap(6);
1218 if (!parameterTypes.containsKey(id))
1219 parameterTypes.put(id, TYPE_OBJECT);
1220
1221 Class type = Object.class;
1222 ClassMetaData meta = null;
1223 int index;
1224
1225 if (positional) {
1226 try {
1227 // indexes in JPQL are 1-based, as opposed to 0-based in
1228 // the core ExpressionFactory
1229 index = Integer.parseInt(id) - 1;
1230 } catch (NumberFormatException e) {
1231 throw parseException(EX_USER, "bad-positional-parameter",
1232 new Object[]{ id }, e);
1233 }
1234
1235 if (index < 0)
1236 throw parseException(EX_USER, "bad-positional-parameter",
1237 new Object[]{ id }, null);
1238 } else {
1239 // otherwise the index is just the current size of the params
1240 index = parameterTypes.indexOf(id);
1241 }
1242
1243 Parameter param = factory.newParameter(id, type);
1244 param.setMetaData(meta);
1245 param.setIndex(index);
1246
1247 return param;
1248 }
1249
1250 /**
1251 * Checks to see if we should evaluate for a NOT expression.
1252 */
1253 private Expression evalNot(boolean not, Expression exp) {
1254 return not ? factory.not(exp) : exp;
1255 }
1256
1257 /**
1258 * Trim off leading and trailing single-quotes, and then
1259 * replace any internal '' instances with ' (since repeating the
1260 * quote is the JPQL mechanism of escaping a single quote).
1261 */
1262 private String trimQuotes(String str) {
1263 if (str == null || str.length() <= 1)
1264 return str;
1265
1266 if (str.startsWith("'") && str.endsWith("'"))
1267 str = str.substring(1, str.length() - 1);
1268
1269 int index = -1;
1270
1271 while ((index = str.indexOf("''", index + 1)) != -1)
1272 str = str.substring(0, index + 1) + str.substring(index + 2);
1273
1274 return str;
1275 }
1276
1277 /**
1278 * An IntegerLiteral and DecimalLiteral node will
1279 * have a child node of Negative if it is negative:
1280 * if so, this method returns -1, else it returns 1.
1281 */
1282 private short negative(JPQLNode node) {
1283 if (node.children != null && node.children.length == 1
1284 && firstChild(node).id == JJTNEGATIVE)
1285 return -1;
1286 else
1287 return 1;
1288 }
1289
1290 private Value getIdentifier(JPQLNode node) {
1291 final String name = node.text;
1292 final Value val = getVariable(name, false);
1293
1294 ClassMetaData cmd = getMetaDataForAlias(name);
1295
1296 if (cmd != null) {
1297 // handle the case where the class name is the alias
1298 // for the candidate (we don't use variables for this)
1299 Value thiz = factory.getThis();
1300 thiz.setMetaData(cmd);
1301 return thiz;
1302 } else if (val instanceof Path) {
1303 return (Path) val;
1304 } else if (val instanceof Value) {
1305 return (Value) val;
1306 }
1307
1308 throw parseException(EX_USER, "unknown-identifier",
1309 new Object[]{ name }, null);
1310 }
1311
1312 private Value getPathOrConstant(JPQLNode node) {
1313 // first check to see if the path is an enum or static field, and
1314 // if so, load it
1315 String className = assemble(node, ".", 1);
1316 Class c = resolver.classForName(className, null);
1317 if (c != null) {
1318 String fieldName = lastChild(node).text;
1319
1320 try {
1321 Field field = c.getField(fieldName);
1322 Object value = field.get(null);
1323 return factory.newLiteral(value, Literal.TYPE_UNKNOWN);
1324 } catch (NoSuchFieldException nsfe) {
1325 if (node.inEnumPath)
1326 throw parseException(EX_USER, "no-field",
1327 new Object[]{ c.getName(), fieldName }, nsfe);
1328 else
1329 return getPath(node, false, true);
1330 } catch (Exception e) {
1331 throw parseException(EX_USER, "unaccessible-field",
1332 new Object[]{ className, fieldName }, e);
1333 }
1334 } else {
1335 return getPath(node, false, true);
1336 }
1337 }
1338
1339 private Path getPath(JPQLNode node) {
1340 return getPath(node, false, true);
1341 }
1342
1343 private Path getPath(JPQLNode node, boolean pcOnly, boolean inner) {
1344 // resolve the first element against the aliases map ...
1345 // i.e., the path "SELECT x.id FROM SomeClass x where x.id > 10"
1346 // will need to have "x" in the alias map in order to resolve
1347 Path path;
1348
1349 final String name = firstChild(node).text;
1350 final Value val = getVariable(name, false);
1351
1352 // handle the case where the class name is the alias
1353 // for the candidate (we don't use variables for this)
1354 if (name.equalsIgnoreCase(ctx().schemaAlias)) {
1355 if (ctx().subquery != null) {
1356 path = factory.newPath(ctx().subquery);
1357 path.setMetaData(ctx().subquery.getMetaData());
1358 } else {
1359 path = factory.newPath();
1360 path.setMetaData(ctx().meta);
1361 }
1362 } else if (getMetaDataForAlias(name) != null)
1363 path = newPath(null, getMetaDataForAlias(name));
1364 else if (val instanceof Path)
1365 path = (Path) val;
1366 else if (val.getMetaData() != null)
1367 path = newPath(val, val.getMetaData());
1368 else
1369 throw parseException(EX_USER, "path-invalid",
1370 new Object[]{ assemble(node), name }, null);
1371
1372 // walk through the children and assemble the path
1373 boolean allowNull = !inner;
1374 for (int i = 1; i < node.children.length; i++) {
1375 if (path.isXPath()) {
1376 for (int j = i; j <node.children.length; j++)
1377 path = (Path) traverseXPath(path, node.children[j].text);
1378 return path;
1379 }
1380 path = (Path) traversePath(path, node.children[i].text, pcOnly,
1381 allowNull);
1382
1383 // all traversals but the first one will always be inner joins
1384 allowNull = false;
1385 }
1386
1387 return path;
1388 }
1389
1390 protected Class getDeclaredVariableType(String name) {
1391 ClassMetaData cmd = getMetaDataForAlias(name);
1392 if (cmd != null)
1393 return cmd.getDescribedType();
1394
1395 if (name != null && name.equals(ctx().schemaAlias))
1396 return getCandidateType();
1397
1398 // JPQL has no declared variables
1399 return null;
1400 }
1401
1402 /**
1403 * Returns an Expression for the given node by eval'ing it.
1404 */
1405 private Expression getExpression(JPQLNode node) {
1406 Object exp = eval(node);
1407
1408 // check for boolean values used as expressions
1409 if (!(exp instanceof Expression))
1410 return factory.asExpression((Value) exp);
1411 return (Expression) exp;
1412 }
1413
1414 private Value getValue(JPQLNode node) {
1415 return getValue(node, VAR_PATH);
1416 }
1417
1418 private Path newPath(Value val, ClassMetaData meta) {
1419 Path path = val == null ? factory.newPath() : factory.newPath(val);
1420 if (meta != null)
1421 path.setMetaData(meta);
1422 return path;
1423 }
1424
1425 /**
1426 * Returns a Value for the given node by eval'ing it.
1427 */
1428 private Value getValue(JPQLNode node, int handleVar) {
1429 Value val = (Value) eval(node);
1430
1431 // determined how to evaluate a variable
1432 if (!val.isVariable())
1433 return val;
1434 else if (handleVar == VAR_PATH && !(val instanceof Path))
1435 return newPath(val, val.getMetaData());
1436 else if (handleVar == VAR_ERROR)
1437 throw parseException(EX_USER, "unexpected-var",
1438 new Object[]{ node.text }, null);
1439 else
1440 return val;
1441 }
1442
1443 ////////////////////////////
1444 // Parse Context Management
1445 ////////////////////////////
1446
1447 private Context ctx() {
1448 return (Context) contexts.peek();
1449 }
1450
1451 private JPQLNode root() {
1452 return ctx().parsed.root;
1453 }
1454
1455 private ClassMetaData getMetaDataForAlias(String alias) {
1456 for (int i = contexts.size() - 1; i >= 0; i--) {
1457 Context context = (Context) contexts.get(i);
1458 if (alias.equalsIgnoreCase(context.schemaAlias))
1459 return context.meta;
1460 }
1461
1462 return null;
1463 }
1464
1465 private class Context {
1466
1467 private final ParsedJPQL parsed;
1468 private ClassMetaData meta;
1469 private String schemaAlias;
1470 private Subquery subquery;
1471
1472 Context(ParsedJPQL parsed, Subquery subquery) {
1473 this.parsed = parsed;
1474 this.subquery = subquery;
1475 }
1476 }
1477
1478 ////////////////////////////
1479 // Node traversal utilities
1480 ////////////////////////////
1481
1482 private JPQLNode onlyChild(JPQLNode node)
1483 throws UserException {
1484 JPQLNode child = firstChild(node);
1485
1486 if (node.children.length > 1)
1487 throw parseException(EX_USER, "multi-children",
1488 new Object[]{ node, Arrays.asList(node.children) }, null);
1489
1490 return child;
1491 }
1492
1493 /**
1494 * Returns the left node (the first of the children), and asserts
1495 * that there are exactly two children.
1496 */
1497 private JPQLNode left(JPQLNode node) {
1498 return child(node, 0, 2);
1499 }
1500
1501 /**
1502 * Returns the right node (the second of the children), and asserts
1503 * that there are exactly two children.
1504 */
1505 private JPQLNode right(JPQLNode node) {
1506 return child(node, 1, 2);
1507 }
1508
1509 private JPQLNode child(JPQLNode node, int childNum, int assertCount) {
1510 if (node.children.length != assertCount)
1511 throw parseException(EX_USER, "wrong-child-count",
1512 new Object[]{ new Integer(assertCount), node,
1513 Arrays.asList(node.children) }, null);
1514
1515 return node.children[childNum];
1516 }
1517
1518 private JPQLNode firstChild(JPQLNode node) {
1519 if (node.children == null || node.children.length == 0)
1520 throw parseException(EX_USER, "no-children",
1521 new Object[]{ node }, null);
1522 return node.children[0];
1523 }
1524
1525 private static JPQLNode secondChild(JPQLNode node) {
1526 return node.children[1];
1527 }
1528
1529 private static JPQLNode thirdChild(JPQLNode node) {
1530 return node.children[2];
1531 }
1532
1533 private static JPQLNode lastChild(JPQLNode node) {
1534 return lastChild(node, 0);
1535 }
1536
1537 /**
1538 * The Nth from the last child. E.g.,
1539 * lastChild(1) will return the second-to-the-last child.
1540 */
1541 private static JPQLNode lastChild(JPQLNode node, int fromLast) {
1542 return node.children[node.children.length - (1 + fromLast)];
1543 }
1544
1545 /**
1546 * Base node that will be generated by the JPQLExpressionBuilder; base
1547 * class of the {@link SimpleNode} that is used by {@link JPQL}.
1548 *
1549 * @author Marc Prud'hommeaux
1550 * @see Node
1551 * @see SimpleNode
1552 */
1553 protected abstract static class JPQLNode
1554 implements Node, Serializable {
1555
1556 final int id;
1557 final JPQL parser;
1558 JPQLNode parent;
1559 JPQLNode[] children;
1560 String text;
1561 boolean not = false;
1562 boolean inEnumPath = false;
1563
1564 public JPQLNode(JPQL parser, int id) {
1565 this.id = id;
1566 this.parser = parser;
1567 this.inEnumPath = parser.inEnumPath;
1568 }
1569
1570 public void jjtOpen() {
1571 }
1572
1573 public void jjtClose() {
1574 }
1575
1576 JPQLNode[] findChildrenByID(int id) {
1577 Collection set = new HashSet();
1578 findChildrenByID(id, set);
1579 return (JPQLNode[]) set.toArray(new JPQLNode[set.size()]);
1580 }
1581
1582 private void findChildrenByID(int id, Collection set) {
1583 for (int i = 0; children != null && i < children.length; i++) {
1584 if (children[i].id == id)
1585 set.add(children[i]);
1586
1587 children[i].findChildrenByID(id, set);
1588 }
1589 }
1590
1591 boolean hasChildID(int id) {
1592 return findChildByID(id, false) != null;
1593 }
1594
1595 JPQLNode findChildByID(int id, boolean recurse) {
1596 for (int i = 0; children != null && i < children.length; i++) {
1597 JPQLNode child = children[i];
1598
1599 if (child.id == id)
1600 return children[i];
1601
1602 if (recurse) {
1603 JPQLNode found = child.findChildByID(id, recurse);
1604 if (found != null)
1605 return found;
1606 }
1607 }
1608
1609 // not found
1610 return null;
1611 }
1612
1613 public void jjtSetParent(Node parent) {
1614 this.parent = (JPQLNode) parent;
1615 }
1616
1617 public Node jjtGetParent() {
1618 return this.parent;
1619 }
1620
1621 public void jjtAddChild(Node n, int i) {
1622 if (children == null) {
1623 children = new JPQLNode[i + 1];
1624 } else if (i >= children.length) {
1625 JPQLNode c[] = new JPQLNode[i + 1];
1626 System.arraycopy(children, 0, c, 0, children.length);
1627 children = c;
1628 }
1629
1630 children[i] = (JPQLNode) n;
1631 }
1632
1633 public Node jjtGetChild(int i) {
1634 return children[i];
1635 }
1636
1637 public int getChildCount() {
1638 return jjtGetNumChildren();
1639 }
1640
1641 public JPQLNode getChild(int index) {
1642 return (JPQLNode) jjtGetChild(index);
1643 }
1644
1645 public Iterator iterator() {
1646 return Arrays.asList(children).iterator();
1647 }
1648
1649 public int jjtGetNumChildren() {
1650 return (children == null) ? 0 : children.length;
1651 }
1652
1653 void setText(String text) {
1654 this.text = text;
1655 }
1656
1657 void setToken(Token t) {
1658 setText(t.image);
1659 }
1660
1661 public String toString() {
1662 return JPQLTreeConstants.jjtNodeName[this.id];
1663 }
1664
1665 public String toString(String prefix) {
1666 return prefix + toString();
1667 }
1668
1669 /**
1670 * Debugging method.
1671 *
1672 * @see #dump(java.io.PrintStream,String)
1673 */
1674 public void dump(String prefix) {
1675 dump(System.out, prefix);
1676 }
1677
1678 public void dump() {
1679 dump(" ");
1680 }
1681
1682 /**
1683 * Debugging method to output a parse tree.
1684 *
1685 * @param out the stream to which to write the debugging info
1686 * @param prefix the prefix to write out before lines
1687 */
1688 public void dump(PrintStream out, String prefix) {
1689 dump(out, prefix, false);
1690 }
1691
1692 public void dump(PrintStream out, String prefix, boolean text) {
1693 out.println(toString(prefix)
1694 + (text && this.text != null ? " [" + this.text + "]" : ""));
1695 if (children != null) {
1696 for (int i = 0; i < children.length; ++i) {
1697 JPQLNode n = (JPQLNode) children[i];
1698 if (n != null) {
1699 n.dump(out, prefix + " ", text);
1700 }
1701 }
1702 }
1703 }
1704 }
1705
1706 /**
1707 * Public for unit testing purposes.
1708 * @nojavadoc
1709 */
1710 public static class ParsedJPQL
1711 implements Serializable {
1712
1713 // This is only ever used during parse; when ParsedJPQL instances
1714 // are serialized, they will have already been parsed.
1715 private final transient JPQLNode root;
1716
1717 private final String query;
1718
1719 // cache of candidate type data. This is stored here in case this
1720 // parse tree is reused in a context that does not know what the
1721 // candidate type is already.
1722 private Class _candidateType;
1723
1724 ParsedJPQL(String jpql) {
1725 this(jpql, parse(jpql));
1726 }
1727
1728 ParsedJPQL(String query, JPQLNode root) {
1729 this.root = root;
1730 this.query = query;
1731 }
1732
1733 private static JPQLNode parse(String jpql) {
1734 if (jpql == null)
1735 jpql = "";
1736
1737 try {
1738 return (JPQLNode) new JPQL(jpql).parseQuery();
1739 } catch (Error e) {
1740 // special handling for Error subclasses, which the
1741 // parser may sometimes (unfortunately) throw
1742 throw new UserException(_loc.get("parse-error",
1743 new Object[]{ e.toString(), jpql }));
1744 }
1745 }
1746
1747 void populate(ExpressionStoreQuery query) {
1748 QueryContext ctx = query.getContext();
1749
1750 // if the owning query's context does not have
1751 // any candidate class, then set it here
1752 if (ctx.getCandidateType() == null) {
1753 if (_candidateType == null)
1754 _candidateType = new JPQLExpressionBuilder
1755 (null, query, this).getCandidateType();
1756 ctx.setCandidateType(_candidateType, true);
1757 }
1758 }
1759
1760 /**
1761 * Public for unit testing purposes.
1762 */
1763 public Class getCandidateType() {
1764 return _candidateType;
1765 }
1766
1767 public String toString ()
1768 {
1769 return this.query;
1770 }
1771 }
1772 }
1773