Home » apache-openjpa-1.1.0-source » org.apache.openjpa » kernel » jpql » [javadoc | source]
    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   

Save This Page
Home » apache-openjpa-1.1.0-source » org.apache.openjpa » kernel » jpql » [javadoc | source]