Package org.datanucleus.query

Source Code of org.datanucleus.query.JDOQLSingleStringParser$Parser

/**********************************************************************
Copyright (c) 2005 Andy Jefferson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


Contributors:
    ...
**********************************************************************/
package org.datanucleus.query;

import java.util.StringTokenizer;

import org.datanucleus.ObjectManager;
import org.datanucleus.ObjectManagerFactoryImpl;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.store.query.Query;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;

/**
* Parser for handling JDOQL Single-String queries.
* Takes a JDOQLQuery and the query string and parses it into its constituent parts, updating
* the JDOQLQuery accordingly with the result that after calling the parse() method the JDOQLQuery
* is populated.
* <pre>
* select [unique] [ <result> ] [into <result-class-name>]
*                              [from <candidate-class-name> [exclude subclasses] ]
*                              [where <filter>]
*                              [variables <variables-clause> ]
*                              [parameters <parameters-clause>]
*                              [<imports-clause>]
*                              [group by <grouping-clause> ]
*                              [order by <ordering-clause>]
*                              [range <from-range> ,<to-range>]                                      
* </pre>
* Note that {filter} can contain subqueries, hence containing keywords
* <pre>
* SELECT c FROM Customer c WHERE timeAvailable < (SELECT avg(hours) FROM Employee e)
* </pre>
* So the "filter" for the outer query is "timeAvailable < (SELECT avg(hours) FROM Employee e)"
*/
public class JDOQLSingleStringParser
{   
    /** Localiser for messages. */
    protected static final Localiser LOCALISER=Localiser.getInstance(
        "org.datanucleus.Localisation", ObjectManagerFactoryImpl.class.getClassLoader());

    /** The JDOQL query to populate. */
    private Query query;

    /** The single-string query string. */
    private String queryString;

    /** Record of the keyword currently being processed, so we can check for out of order keywords. */
    int keywordPosition = -1;

    /**
     * Constructor for the Single-String parser.
     * @param query The query
     * @param queryString The Single-String query
     */
    public JDOQLSingleStringParser(Query query, String queryString)
    {
        NucleusLogger.QUERY.debug(LOCALISER.msg("042010", queryString));
        this.query = query;
        this.queryString = queryString;
    }

    /**
     * Method to parse the Single-String query
     */
    public void parse()
    {
        new Compiler(new Parser(queryString)).compile();
    }

    /**
     * Compiler to process keywords contents. In the query the keywords often have
     * content values following them that represent the constituent parts of the query. This takes the keyword
     * and sets the constituent part accordingly.
     */
    private class Compiler
    {
        Parser tokenizer;

        Compiler(Parser tokenizer)
        {
            this.tokenizer = tokenizer;
        }

        private void compile()
        {
            compileSelect();
            String keyword = tokenizer.parseKeyword();
            if (keyword != null && JDOQLQueryHelper.isKeyword(keyword))
            {
                // any keyword after compiling the SELECT is an error
                throw new NucleusUserException(LOCALISER.msg("042011", keyword));
            }
        }

        private void compileSelect()
        {
            if (!tokenizer.parseKeyword("SELECT") && !tokenizer.parseKeyword("select"))
            {
                throw new NucleusUserException(LOCALISER.msg("042012"));
            }
            if (tokenizer.parseKeyword("UNIQUE") || tokenizer.parseKeyword("unique"))
            {
                compileUnique();
            }
            compileResult();
            if (tokenizer.parseKeyword("INTO") || tokenizer.parseKeyword("into"))
            {
                compileInto();
            }
            if (tokenizer.parseKeyword("FROM") || tokenizer.parseKeyword("from"))
            {
                compileFrom();
            }
            if (tokenizer.parseKeyword("WHERE") || tokenizer.parseKeyword("where"))
            {
                compileWhere();
            }
            if (tokenizer.parseKeyword("VARIABLES") || tokenizer.parseKeyword("variables"))
            {
                compileVariables();
            }
            if (tokenizer.parseKeyword("PARAMETERS") || tokenizer.parseKeyword("parameters"))
            {
                compileParameters();
            }
            if (tokenizer.parseKeyword("import"))
            {
                compileImport();
            }
            if (tokenizer.parseKeyword("GROUP") || tokenizer.parseKeyword("group"))
            {
                compileGroup();
            }
            if (tokenizer.parseKeyword("ORDER") || tokenizer.parseKeyword("order"))
            {
                compileOrder();
            }
            if (tokenizer.parseKeyword("RANGE") || tokenizer.parseKeyword("range"))
            {
                compileRange();
            }
        }
       
        private void compileUnique()
        {
            query.setUnique(true);
        }

        private void compileResult()
        {
            String content = tokenizer.parseContent(false);
            if (content.length() > 0)
            {
                query.setResult(content);
            }
        }

        private void compileInto()
        {
            String content = tokenizer.parseContent(false);
            if (content.length() == 0)
            {
                // content cannot be empty
                throw new NucleusUserException(LOCALISER.msg("042014", "INTO", "<result class>"));
            }

            query.setResultClassName(content);
        }

        private void compileFrom()
        {
            String content = tokenizer.parseContent(false);
            if (content.length() == 0)
            {
                // content cannot be empty
                throw new NucleusUserException(LOCALISER.msg("042014", "FROM", "<candidate class>"));
            }

            if (content.indexOf(' ') > 0)
            {
                // Subquery accepts "<candidate-expr> alias"
                query.setFrom(content.trim());
            }
            else
            {
                // Content is "<candidate-class-name>"
                query.setCandidateClassName(content);
            }

            if (tokenizer.parseKeyword("EXCLUDE") || tokenizer.parseKeyword("exclude"))
            {
                if (!tokenizer.parseKeyword("SUBCLASSES") && !tokenizer.parseKeyword("subclasses"))
                {
                    throw new NucleusUserException(LOCALISER.msg("042015", "SUBCLASSES", "EXCLUDE"));
                }
                content = tokenizer.parseContent(false);
                if (content.length() > 0)
                {
                    throw new NucleusUserException(LOCALISER.msg("042013", "EXCLUDE SUBCLASSES", content));
                }
                query.setSubclasses(false);
            }
        }

        private void compileWhere()
        {
            String content = tokenizer.parseContent(true);
            if (content.length() == 0)
            {
                // content cannot be empty
                throw new NucleusUserException(LOCALISER.msg("042014", "WHERE", "<filter>"));
            }

            if (content.indexOf("SELECT") > 0 || content.indexOf("select") > 0)
            {
                // Subquery (or subqueries) present
                StringBuffer contentStr = new StringBuffer(content);
                boolean parsed = false;
                int currentPosition = 0;
                int subqueryNum = 1;
                while (!parsed)
                {
                    // Find the next SELECT - subquery start
                    int selectPos1 = contentStr.indexOf("SELECT", currentPosition);
                    int selectPos2 = contentStr.indexOf("select", currentPosition);
                    if (selectPos1 < 0 && selectPos2 < 0)
                    {
                        parsed = true;
                        break;
                    }
                    else
                    {
                        if (selectPos1 > 0 && selectPos2 > 0)
                        {
                            currentPosition = Math.min(selectPos1, selectPos2);
                        }
                        else if (selectPos1 > 0 && selectPos2 < 0)
                        {
                            currentPosition = selectPos1;
                        }
                        else if (selectPos1 < 0 && selectPos2 > 0)
                        {
                            currentPosition = selectPos2;
                        }

                        // Find the opening brace
                        int startPosition = currentPosition;
                        for (int i=currentPosition-1;i>=0;i--)
                        {
                            if (contentStr.charAt(i) == '(')
                            {
                                startPosition = i;
                                break;
                            }
                        }

                        // Process subquery from this position until we find the end
                        int level = 0;
                        int index = currentPosition;
                        for (;index<content.length();index++)
                        {
                            if (contentStr.charAt(index) == '(')
                            {
                                level++;
                            }
                            else if (contentStr.charAt(index) == ')')
                            {
                                level--;
                            }
                            if (level == -1)
                            {
                                // End of subquery, so register it
                                String subqueryString = contentStr.substring(currentPosition, index);
                                String subqueryVarName = "DATANUCLEUS_SUBQUERY_" + subqueryNum;

                                Query subquery = (Query)ClassUtils.newInstance(query.getClass(),
                                    new Class[]{ObjectManager.class, String.class},
                                    new Object[] {query.getObjectManager(), subqueryString});
                                // TODO Set the type of the variable
                                query.addSubquery(subquery, "double " + subqueryVarName, null, null);

                                // Strip out the subquery from the content, replacing it with the variable name
                                contentStr.replace(startPosition, index+1, subqueryVarName);
                                subqueryNum++;
                                break;
                            }
                        }
                        // There are only 2 ways to exit the loop.  The first is if the value
                        // of 'level' drops below 0.  This value is only decremented when we
                        // encounter a close parenthesis.  The other way we can exit the loop is
                        // if we iterate all the way to the end of the string.  If this happens
                        // we know we have at least one open parenthesis without a matching close
                        // parenthesis. That's a badly constructed query, so we error.
                        if (index == content.length())
                        {
                            // missing a close parenthesis for the sub-select
                            throw new NucleusUserException(LOCALISER.msg("042017"));
                        }
                    }
                }

                query.setFilter(contentStr.toString());
            }
            else
            {
                query.setFilter(content);
            }
        }

        private void compileVariables()
        {
            String content = tokenizer.parseContent(false);
            if (content.length() == 0)
            {
                // content cannot be empty
                throw new NucleusUserException(LOCALISER.msg("042014", "VARIABLES", "<variable declarations>"));
            }
            query.declareExplicitVariables(content);
        }

        private void compileParameters()
        {
            String content = tokenizer.parseContent(false);
            if (content.length() == 0)
            {
                // content cannot be empty
                throw new NucleusUserException(LOCALISER.msg("042014", "PARAMETERS", "<parameter declarations>"));
            }
            query.declareExplicitParameters(content);
        }

        private void compileImport()
        {
            String content = "import " + tokenizer.parseContent(false);
            while (tokenizer.parseKeyword("import"))
            {
                content += "import " + tokenizer.parseContent(false);
            }
            query.declareImports(content);
        }

        private void compileGroup()
        {
            String content = tokenizer.parseContent(false);
            if (!tokenizer.parseKeyword("BY") && !tokenizer.parseKeyword("by"))
            {
                // GROUP must be followed by BY
                throw new NucleusUserException(LOCALISER.msg("042015", "BY", "GROUP"));
            }

            content = tokenizer.parseContent(false);
            if (content.length() == 0)
            {
                // content cannot be empty
                throw new NucleusUserException(LOCALISER.msg("042014", "GROUP BY", "<grouping>"));
            }
            query.setGrouping(content);
        }

        private void compileOrder()
        {
            String content = tokenizer.parseContent(false);
            if (!tokenizer.parseKeyword("BY") && !tokenizer.parseKeyword("by"))
            {
                // ORDER must be followed by BY
                throw new NucleusUserException(LOCALISER.msg("042015", "BY", "ORDER"));
            }

            content = tokenizer.parseContent(false);
            if (content.length() == 0)
            {
                // content cannot be empty
                throw new NucleusUserException(LOCALISER.msg("042014", "ORDER BY", "<ordering>"));
            }
            query.setOrdering(content);
        }

        private void compileRange()
        {
            String content = tokenizer.parseContent(false);
            if (content.length() == 0)
            {
                // content cannot be empty
                throw new NucleusUserException(LOCALISER.msg("042014", "RANGE", "<range>"));
            }
            query.setRange(content);
        }
    }

    /**
     * Tokenizer that provides access to current token.
     */
    private class Parser
    {
        final String queryString;

        int queryStringPos = 0;

        /** tokens */
        final String[] tokens;

        /** keywords */
        final String[] keywords;

        /** current token cursor position */
        int tokenIndex = -1;

        /**
         * Constructor
         * @param str String to parse
         */
        public Parser(String str)
        {
            queryString = str;

            StringTokenizer tokenizer = new StringTokenizer(str);
            tokens = new String[tokenizer.countTokens()];
            keywords = new String[tokenizer.countTokens()];
            int i = 0;
            while (tokenizer.hasMoreTokens())
            {
                tokens[i] = tokenizer.nextToken();
                if (JDOQLQueryHelper.isKeyword(tokens[i]))
                {
                    keywords[i] = tokens[i];
                }
                i++;
            }
        }

        /**
         * Parse the content until a keyword is found
         * @param allowSubentries Whether to permit subentries (in parentheses) in this next block
         * @return the content
         */
        public String parseContent(boolean allowSubentries)
        {
            String content = "";
            int level = 0;
            while (tokenIndex < tokens.length - 1)
            {
                tokenIndex++;

                if (allowSubentries)
                {
                    // Process this token to check level of parentheses.
                    // This is necessary because we want to ignore keywords if within a parentheses-block
                    // e.g SELECT ... FROM ... WHERE param1 < (SELECT ... FROM ...)
                    // and the "WHERE" part is "param1 < (SELECT ... FROM ...)"
                    // Consequently subqueries will be parsed into the relevant block correctly.
                    // Assumes that subqueries are placed in parentheses
                    for (int i=0;i<tokens[tokenIndex].length();i++)
                    {
                        char c = tokens[tokenIndex].charAt(i);
                        if (c == '(')
                        {
                            level++;
                        }
                        else if (c == ')')
                        {
                            level--;
                        }
                    }
                }

                if (level == 0 && JDOQLQueryHelper.isKeyword(tokens[tokenIndex]))
                {
                    // Keyword encountered, and not part of any subquery so end of content block
                    tokenIndex--;
                    break;
                }
                else
                {
                    // Append the content from the query string from the end of the last token to the end of this token
                    int endPos = queryString.indexOf(tokens[tokenIndex], queryStringPos) + tokens[tokenIndex].length();
                    String contentValue = queryString.substring(queryStringPos, endPos);
                    queryStringPos = endPos;

                    if (content.length() == 0)
                    {
                        content = contentValue;
                    }
                    else
                    {
                        content += contentValue;
                    }
                }
            }

            return content;
        }

        /**
         * Parse the next token looking for a keyword. The cursor position is
         * skipped in one tick if a keyword is found
         * @param keyword the searched keyword
         * @return true if the keyword
         */
        public boolean parseKeyword(String keyword)
        {
            if (tokenIndex < tokens.length - 1)
            {
                tokenIndex++;
                if (keywords[tokenIndex] != null)
                {
                    if (keywords[tokenIndex].equals(keyword))
                    {
                        // Move query position to end of last processed token
                        queryStringPos =
                            queryString.indexOf(keywords[tokenIndex], queryStringPos) +
                            keywords[tokenIndex].length()+1;
                        return true;
                    }
                }
                tokenIndex--;
            }
            return false;
        }

        /**
         * Parse the next token looking for a keyword. The cursor position is
         * skipped in one tick if a keyword is found
         * @return the parsed keyword or null
         */
        public String parseKeyword()
        {
            if (tokenIndex < tokens.length - 1)
            {
                tokenIndex++;
                if (keywords[tokenIndex] != null)
                {
                    return keywords[tokenIndex];
                }
                tokenIndex--;
            }
            return null;
        }
    }
}
TOP

Related Classes of org.datanucleus.query.JDOQLSingleStringParser$Parser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.