Package org.jpox.store.rdbms.query

Source Code of org.jpox.store.rdbms.query.JavaQueryCompiler$AliasJoinInformation

/**********************************************************************
Copyright (c) 2008 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.jpox.store.rdbms.query;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jpox.ClassLoaderResolver;
import org.jpox.PersistenceConfiguration;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.MetaDataManager;
import org.jpox.metadata.Relation;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.mapped.MappedStoreManager;
import org.jpox.store.mapped.expression.AggregateExpression;
import org.jpox.store.mapped.expression.BooleanExpression;
import org.jpox.store.mapped.expression.ClassExpression;
import org.jpox.store.mapped.expression.CollectionExpression;
import org.jpox.store.mapped.expression.Literal;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.MapExpression;
import org.jpox.store.mapped.expression.NewObjectExpression;
import org.jpox.store.mapped.expression.NullLiteral;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.mapped.expression.UnboundVariable;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.query.Queryable;
import org.jpox.store.query.AbstractJavaQuery;
import org.jpox.store.query.JPOXResultSetMetaData;
import org.jpox.store.query.QueryCompiler;
import org.jpox.store.query.QueryCompilerSyntaxException;
import org.jpox.store.query.QueryUtils;
import org.jpox.store.rdbms.table.CollectionTable;
import org.jpox.store.rdbms.table.MapTable;
import org.jpox.util.ClassUtils;
import org.jpox.util.Imports;
import org.jpox.util.JPOXLogger;
import org.jpox.util.StringUtils;

/**
* Base definition of a query compiler for a Java query language.
* This is to be extended to provide query compilers for JDOQL, JPQL and any other java-based
* query languages.
*
* @version $Revision$
*/
public abstract class JavaQueryCompiler extends QueryCompiler implements UnboundVariable.VariableBinder
{
    /** Internal list of field expressions just being parsed. Used for checking validity of the query components. */
    List fieldExpressions = new ArrayList();

    /** ResultMetaData for the query (set in the compile process, so is null before that). */
    protected JPOXResultSetMetaData resultMetaData;

    /** QueryExpression for this query. Generated by the compile. */
    protected QueryExpression qs = null;

    /** Parent query expression (if this is a subquery). */
    protected QueryExpression parentExpr;

    protected Map expressionsByVariableName = new HashMap();

    /** ClassMetaData for the candidate. */
    protected AbstractClassMetaData candidateCmd = null;

    /** Candidates for the query. */
    protected Queryable candidates = null;

    /** Whether we should use distinct for results. */
    protected boolean distinct = false;

    /** Result class to be used. May be updated during the compilation. */
    protected Class resultClass = null;

    /** Range from position (inclusive). */
    protected long rangeFromIncl = -1;

    /** Range to position (exclusive). */
    protected long rangeToExcl = -1;

    /** Candidate expression when treating as a subquery. */
    protected String subqueryCandidateExpr;

    /** Alias join info for the subquery candidate expression root (if not using parent query candidate). */
    protected AliasJoinInformation subqueryCandidateExprRootAliasInfo = null;

    /**
     * Constructor for a compiler of java queries.
     * @param query The query to compile
     * @param imports The imports to use
     * @param parameters Any parameters
     */
    public JavaQueryCompiler(AbstractJavaQuery query, Imports imports, Map parameters)
    {
        super(query, imports, parameters);

        if (query instanceof JDOQLQuery)
        {
            this.candidates = ((JDOQLQuery)query).getCandidates();
        }
        else if (query instanceof JPQLQuery)
        {
            this.candidates = ((JPQLQuery)query).getCandidates();
        }
        this.resultClass = query.getResultClass();
        this.rangeFromIncl = query.getRangeFromIncl();
        this.rangeToExcl = query.getRangeToExcl();
    }

    /**
     * Inner class defining important information about an aliased class.
     */
    class AliasJoinInformation
    {
        String alias;
        Class cls;
        LogicSetExpression tableExpression;
        boolean candidate;
        public AliasJoinInformation(String alias, Class cls, LogicSetExpression tblExpr, boolean candidate)
        {
            this.alias = alias;
            this.cls = cls;
            this.tableExpression = tblExpr;
            this.candidate = candidate;
        }
        public String toString()
        {
            return "Alias=" + alias + " class=" + cls + " tableExpr=" + tableExpression + (candidate?"[CANDIDATE]" : "");
        }
    }

    /**
     * Method to close the Compiler.
     */
    public void close()
    {
        super.close();
        this.expressionsByVariableName = null;
        this.qs = null;
        this.fieldExpressions = null;
    }

    /**
     * Method to compile the query.
     * @param type Type of compilation
     * @return the compilation artifact (if any)
     */
    public Object compile(int type)
    {
        switch (type)
        {
            case COMPILE_SYNTAX :
                compile(COMPILE_EXPLICIT_PARAMETERS);
                compile(COMPILE_EXPLICIT_VARIABLES);
                preCompile();
                return null;

            case COMPILE_EXECUTION :
                compile(COMPILE_EXPLICIT_PARAMETERS);
                compile(COMPILE_EXPLICIT_VARIABLES);
                return executionCompile();

            default :
                return super.compile(type);
        }
    }

    /**
     * Method to provide pre-compilation checks to catch errors.
     * This is performed when calling the JDO Query.compile() method.
     */
    protected void preCompile()
    {
        executionCompile = false;

        // Compile the candidates
        compileCandidates();

        // Create a QueryStatement so that we can generate the query expressions for the parts of the query
        // The statement is thrown away after the precompile currently
        MappedStoreManager storeMgr = (MappedStoreManager)query.getObjectManager().getStoreManager();
        DatastoreIdentifier candidateAliasId = storeMgr.getIdentifierFactory().newIdentifier(
                IdentifierFactory.TABLE, candidateAlias);
        qs = candidates.newQueryStatement(candidateClass, candidateAliasId);
        if (parentExpr != null)
        {
            // Register subquery with its parent query expression
            qs.setParent(parentExpr);
        }
        qs.setCandidateInformation(candidateClass, candidateAlias);

        performCompile(qs);

        executionCompile = true;
    }

    /**
     * Method to execution-compile the query.
     * Generates the query and returns it.
     * @return the execution compiled query
     */
    protected QueryExpression executionCompile()
    {
        // Compile the candidates
        compileCandidates();
        if (candidates instanceof ResultExpressionsQueryable)
        {
            ((ResultExpressionsQueryable)candidates).setHasAggregatedExpressionsOnly(
                ((AbstractJavaQuery)query).resultHasOnlyAggregates(query.getResult()));
        }

        // Create the QueryStatement so we can generate the executable query
        MappedStoreManager storeMgr = (MappedStoreManager)query.getObjectManager().getStoreManager();
        DatastoreIdentifier candidateAliasId = storeMgr.getIdentifierFactory().newIdentifier(
                IdentifierFactory.TABLE, candidateAlias);
        qs = candidates.newQueryStatement(candidateClass, candidateAliasId);
        if (parentExpr != null)
        {
            // Register subquery with its parent query expression
            qs.setParent(parentExpr);
        }
        qs.setCandidateInformation(candidateClass, candidateAlias);

        // Apply any extensions for query generation
        PersistenceConfiguration config = query.getObjectManager().getOMFContext().getPersistenceConfiguration();
        String propName = "org.jpox.rdbms.jdoql.joinType";
        String joinType = config.getStringProperty(propName);
        if (query.getExtension(propName) != null)
        {
            String type = (String)query.getExtension(propName);
            if (type.toUpperCase().equals("INNER") || type.toUpperCase().equals("LEFT OUTER"))
            {
                joinType = type.toUpperCase();
            }
        }
        if (joinType != null)
        {
            qs.addExtension(propName, joinType);
        }

        propName = "org.jpox.rdbms.jdoql.existsIncludesConstraints";
        boolean existsIncludes = config.getBooleanProperty(propName);
        if (query.getExtension(propName) != null)
        {
            existsIncludes =
                Boolean.valueOf((String)query.getExtension(propName)).booleanValue();
            qs.addExtension(propName, "" + existsIncludes);
        }
        if (existsIncludes)
        {
            qs.addExtension(propName, "true");
        }

        propName = "org.jpox.rdbms.query.containsUsesExistsAlways";
        boolean existsAlways = config.getBooleanProperty(propName);
        if (query.getExtension(propName) != null)
        {
            existsAlways =
                Boolean.valueOf((String)query.getExtension(propName)).booleanValue();
            qs.addExtension(propName, "" + existsAlways);
        }
        if (existsAlways)
        {
            qs.addExtension(propName, "true");
        }

        performCompile(qs);

        return qs;
    }

    /**
     * Accessor for the result MetaData. Will be null until the query is compiled.
     * @return ResultMetaData for the query
     */
    public JPOXResultSetMetaData getResultMetaData()
    {
        return resultMetaData;
    }

    /**
     * Accessor for the candidates for the query.
     * @return Candidates for the query
     */
    public Queryable getCandidates()
    {
        return candidates;
    }

    /**
     * Accessor for whether the candidate result should be distinct.
     * @return Whether we should use distinct
     */
    public boolean getDistinct()
    {
        return distinct;
    }

    /**
     * Accessor for the result class. May have been updated during the compile process.
     * @return Result class
     */
    public Class getResultClass()
    {
        return resultClass;
    }

    /**
     * Accessor for the range "from" value. May have been set during compilation where the
     * "from" was an expression.
     * @return Range "from" value
     */
    public long getRangeFromIncl()
    {
        return rangeFromIncl;
    }

    /**
     * Accessor for the range "to" value. May have been set during compilation where the
     * "to" was an expression.
     * @return Range "to" value
     */
    public long getRangeToExcl()
    {
        return rangeToExcl;
    }

    /**
     * Perform the actual compilation of the query.
     * @param qs The QueryExpression to use during compilation (if required)
     */
    protected abstract void performCompile(QueryExpression qs);

    /**
     * Convenience method to process the candidates for this query.
     * Processes the "candidateClassName" and "candidateClass" and sets up "candidates".
     */
    protected abstract void compileCandidates();

    /**
     * <p>
     * Method to process any "<candidate-expression>" when this is a subquery.
     * Converts the candidate expression into a series of INNER JOINs from the subquery candidate table
     * back to the candidate table of the outer query.
     * For example, <pre>this.department.employees</pre> would result in a table expression for the
     * Department table being added to this subquery FROM, with an INNER JOIN to the subquery candidate table,
     * and an AND condition from the Department table back to the outer query candidate table.
     * </p>
     * <p>
     * The subqueryCandidateExpr should either start "this." (hence from the parent query candidate) or,
     * if not, the "subqueryCandidateExprRootAliasInfo" will be set and will define the candidate root
     * of the subquery candidate expression. This second use is used in JPQL where we have a subquery
     * using a field of another alias from the parent query e.g
     * <pre>
     * SELECT c FROM Customer c JOIN c.orders o WHERE EXISTS (SELECT o FROM o.lineItems l where l.quantity > 3)
     * </pre>
     * so "o" is the root alias for the subquery, and the "subqueryCandidateExprRootAliasInfo" will define
     * the table expression in the parent query, and the class it represents.
     * </p>
     */
    protected void compileSubqueryCandidateExpression(boolean caseSensitive)
    {
        if (subqueryCandidateExpr != null)
        {
            String[] tokens = StringUtils.split(subqueryCandidateExpr, ".");
            if (caseSensitive)
            {
                if (!tokens[0].equals(parentExpr.getCandidateAlias()))
                {
                    throw new JPOXUserException(
                        "Subquery has been specified with a candidate-expression that doesnt start with \"" +
                        parentExpr.getCandidateAlias() + "\"." +
                    " All candidate expressions must start with that to relate them back to the outer query");
                }
            }
            else
            {
            }

            Class cls = (subqueryCandidateExprRootAliasInfo != null ?
                    subqueryCandidateExprRootAliasInfo.cls : parentExpr.getCandidateClass());
            ClassLoaderResolver clr = query.getObjectManager().getClassLoaderResolver();
            MetaDataManager mmgr = query.getObjectManager().getMetaDataManager();
            MappedStoreManager storeMgr = (MappedStoreManager)query.getStoreManager();
            AbstractClassMetaData leftCmd = mmgr.getMetaDataForClass(cls, clr);
            LogicSetExpression leftTblExpr = (subqueryCandidateExprRootAliasInfo != null ?
                    subqueryCandidateExprRootAliasInfo.tableExpression : parentExpr.getMainTableExpression());
            String leftAlias = (subqueryCandidateExprRootAliasInfo != null ?
                    subqueryCandidateExprRootAliasInfo.alias : parentExpr.getCandidateAlias());
            DatastoreClass leftTable = (DatastoreClass)leftTblExpr.getMainTable();
            for (int i=1;i<tokens.length;i++)
            {
                // Process the join from the previous token to this token
                AbstractMemberMetaData leftMmd = leftCmd.getMetaDataForMember(tokens[i]);
                AbstractClassMetaData rightCmd = null;
                int relationType = leftMmd.getRelationType(clr);
                AbstractMemberMetaData rightMmd = null;
                if (relationType == Relation.ONE_TO_ONE_BI || relationType == Relation.ONE_TO_MANY_BI ||
                    relationType == Relation.MANY_TO_MANY_BI || relationType == Relation.MANY_TO_ONE_BI)
                {
                    rightMmd = leftMmd.getRelatedMemberMetaData(clr)[0]; // Take first possible
                }

                // Find class of right hand side
                if (i == tokens.length-1)
                {
                    cls = candidateClass;
                }
                else
                {
                    if (relationType == Relation.ONE_TO_ONE_BI ||
                        relationType == Relation.ONE_TO_ONE_UNI ||
                        relationType == Relation.MANY_TO_ONE_BI)
                    {
                        cls = leftMmd.getType();
                        rightCmd = mmgr.getMetaDataForClass(cls, clr);
                    }
                    else if (relationType == Relation.ONE_TO_MANY_BI ||
                        relationType == Relation.ONE_TO_MANY_UNI ||
                        relationType == Relation.MANY_TO_MANY_BI)
                    {
                        if (leftMmd.hasCollection())
                        {
                            cls = clr.classForName(leftMmd.getCollection().getElementType());
                            rightCmd = mmgr.getMetaDataForClass(cls, clr);
                        }
                        else if (leftMmd.hasMap())
                        {
                            cls = clr.classForName(leftMmd.getMap().getValueType());
                            rightCmd = mmgr.getMetaDataForClass(cls, clr);
                        }
                    }
                    else
                    {
                        throw new JPOXUserException("Subquery has been specified with a candidate-expression that" +
                            " includes \"" + tokens[i] + "\" that isnt a relation field!!");
                    }
                }

                LogicSetExpression rightTblExpr;
                String rightAlias;
                DatastoreIdentifier rightTblAlias;
                DatastoreClass rightTable;
                if (i == tokens.length-1)
                {
                    // Candidate
                    rightTblExpr = qs.getMainTableExpression();
                    rightTblAlias = qs.getMainTableAlias();
                    rightTable = (DatastoreClass)rightTblExpr.getMainTable();
                    rightAlias = candidateAlias;
                }
                else
                {
                    // Not outer candidate, nor inner candidate so add table. Alias is "T{num}"
                    // TODO Parameterise this naming of intermediate tables
                    rightTable = storeMgr.getDatastoreClass(cls.getName(), clr);
                    rightAlias = "T" + i;
                    rightTblAlias = storeMgr.getIdentifierFactory().newIdentifier(
                        IdentifierFactory.TABLE, rightAlias);
                    rightTblExpr = qs.newTableExpression(rightTable, rightTblAlias);
                }

                if (relationType == Relation.ONE_TO_ONE_UNI ||
                    (relationType == Relation.ONE_TO_ONE_BI && leftMmd.getMappedBy() == null) ||
                    (relationType == Relation.MANY_TO_ONE_BI &&
                     (leftMmd.getJoinMetaData() == null && rightMmd.getJoinMetaData() == null)))
                {
                    // 1-1/N-1 with FK here
                    ScalarExpression leftExpr = leftTblExpr.newFieldExpression(tokens[i]);
                    ScalarExpression rightExpr = rightTable.getIDMapping().newScalarExpression(qs, rightTblExpr);
                    if (i == 1)
                    {
                        // And condition to outer candidate
                        qs.andCondition(leftExpr.eq(rightExpr), true);
                    }
                    else
                    {
                        // Inner join to table
                        qs.innerJoin(rightExpr, leftExpr, leftTblExpr, true, true);
                    }
                }
                else if (relationType == Relation.ONE_TO_ONE_BI && leftMmd.getMappedBy() != null)
                {
                    // 1-1 with FK on other side
                    ScalarExpression leftExpr = leftTable.getIDMapping().newScalarExpression(qs, leftTblExpr);
                    ScalarExpression rightExpr = rightTblExpr.newFieldExpression(rightMmd.getName());
                    if (i == 1)
                    {
                        // And condition to outer candidate
                        qs.andCondition(leftExpr.eq(rightExpr), true);
                    }
                    else
                    {
                        // Inner join to table
                        qs.innerJoin(rightExpr, leftExpr, leftTblExpr, true, true);
                    }
                }
                else if ((relationType == Relation.ONE_TO_MANY_UNI && leftMmd.getJoinMetaData() == null) ||
                    (relationType == Relation.ONE_TO_MANY_BI &&
                     (leftMmd.getJoinMetaData() == null && rightMmd.getJoinMetaData() == null)))
                {
                    // 1-N FK with FK on other side
                    ScalarExpression leftExpr = leftTable.getIDMapping().newScalarExpression(qs, leftTblExpr);
                    ScalarExpression rightExpr = rightTblExpr.newFieldExpression(rightMmd.getName());
                    if (i == 1)
                    {
                        // And condition to outer candidate
                        qs.andCondition(leftExpr.eq(rightExpr), true);
                    }
                    else
                    {
                        // Inner join to table
                        qs.innerJoin(rightExpr, leftExpr, leftTblExpr, true, true);
                    }
                }
                else if (relationType == Relation.ONE_TO_MANY_UNI && leftMmd.getJoinMetaData() != null)
                {
                    // 1-N uni JoinTable
                    ScalarExpression leftExpr = leftTable.getIDMapping().newScalarExpression(qs, leftTblExpr);
                    ScalarExpression rightExpr = rightTable.getIDMapping().newScalarExpression(qs, rightTblExpr);
                    ScalarExpression leftCentreExpr = null;
                    ScalarExpression rightCentreExpr = null;
                    LogicSetExpression joinTblExpr = null;
                    if (leftMmd.hasCollection())
                    {
                        CollectionTable joinTbl = (CollectionTable)storeMgr.getDatastoreContainerObject(leftMmd);
                        DatastoreIdentifier joinTblAlias = storeMgr.getIdentifierFactory().newIdentifier(
                            IdentifierFactory.TABLE, leftAlias + "." + rightAlias);
                        joinTblExpr = qs.newTableExpression(joinTbl, joinTblAlias);
                        leftCentreExpr = joinTbl.getOwnerMapping().newScalarExpression(qs, joinTblExpr);
                        rightCentreExpr = joinTbl.getElementMapping().newScalarExpression(qs, joinTblExpr);
                    }
                    else if (leftMmd.hasMap())
                    {
                        MapTable joinTbl = (MapTable)storeMgr.getDatastoreContainerObject(leftMmd);
                        DatastoreIdentifier joinTblAlias = storeMgr.getIdentifierFactory().newIdentifier(
                            IdentifierFactory.TABLE, leftAlias + "." + rightAlias);
                        joinTblExpr = qs.newTableExpression(joinTbl, joinTblAlias);
                        leftCentreExpr = joinTbl.getOwnerMapping().newScalarExpression(qs, joinTblExpr);
                        rightCentreExpr = joinTbl.getValueMapping().newScalarExpression(qs, joinTblExpr);
                    }

                    if (i == 1)
                    {
                        // And condition to outer candidate
                        qs.andCondition(leftExpr.eq(leftCentreExpr), true);
                    }
                    else
                    {
                        // Inner join to table
                        qs.innerJoin(leftCentreExpr, leftExpr, leftTblExpr, true, true);
                    }
                    qs.innerJoin(rightExpr, rightCentreExpr, joinTblExpr, true, true);
                }
                else if ((relationType == Relation.ONE_TO_MANY_BI &&
                    (leftMmd.getJoinMetaData() != null || rightMmd.getJoinMetaData() != null)) ||
                    (relationType == Relation.MANY_TO_ONE_BI &&
                     (leftMmd.getJoinMetaData() != null || rightMmd.getJoinMetaData() != null)) ||
                     relationType == Relation.MANY_TO_MANY_BI)
                {
                    // 1-N/N-1 bi JoinTable
                    ScalarExpression leftExpr = leftTable.getIDMapping().newScalarExpression(qs, leftTblExpr);
                    ScalarExpression rightExpr = rightTable.getIDMapping().newScalarExpression(qs, rightTblExpr);
                    ScalarExpression leftCentreExpr = null;
                    ScalarExpression rightCentreExpr = null;
                    LogicSetExpression joinTblExpr = null;
                    if (leftMmd.hasCollection() || rightMmd.hasCollection())
                    {
                        CollectionTable joinTbl = (CollectionTable)storeMgr.getDatastoreContainerObject(leftMmd);
                        DatastoreIdentifier joinTblAlias = storeMgr.getIdentifierFactory().newIdentifier(
                            IdentifierFactory.TABLE, leftAlias + "." + rightAlias);
                        joinTblExpr = qs.newTableExpression(joinTbl, joinTblAlias);
                        leftCentreExpr = joinTbl.getOwnerMapping().newScalarExpression(qs, joinTblExpr);
                        rightCentreExpr = joinTbl.getElementMapping().newScalarExpression(qs, joinTblExpr);
                    }
                    else if (leftMmd.hasMap() || rightMmd.hasMap())
                    {
                        MapTable joinTbl = (MapTable)storeMgr.getDatastoreContainerObject(leftMmd);
                        DatastoreIdentifier joinTblAlias = storeMgr.getIdentifierFactory().newIdentifier(
                            IdentifierFactory.TABLE, leftAlias + "." + rightAlias);
                        joinTblExpr = qs.newTableExpression(joinTbl, joinTblAlias);
                        leftCentreExpr = joinTbl.getOwnerMapping().newScalarExpression(qs, joinTblExpr);
                        rightCentreExpr = joinTbl.getValueMapping().newScalarExpression(qs, joinTblExpr);
                    }

                    if (i == 1)
                    {
                        // And condition to outer candidate
                        qs.andCondition(leftExpr.eq(leftCentreExpr), true);
                    }
                    else
                    {
                        // Inner join to inner table
                        qs.innerJoin(leftCentreExpr, leftExpr, leftTblExpr, true, true);
                    }
                    qs.innerJoin(rightExpr, rightCentreExpr, joinTblExpr, true, true);
                }
                else
                {
                    // What relation type ???
                    throw new JPOXUserException("<candidate-expression>=" + subqueryCandidateExpr + " has token " + tokens[i] + " and relationType=" + relationType + " NOT SUPPORTED");
                }

                if (i < tokens.length-1)
                {
                    leftTblExpr = rightTblExpr;
                    leftCmd = rightCmd;
                    leftTable = rightTable;
                    leftAlias = rightAlias;
                }
            }
        }
        else if (query.getFrom() != null)
        {
            JPOXLogger.JDO.info(">> performCompile : TODO Add joins for from=" + query.getFrom() + " for real candidate of " + candidateClass.getName());
        }
    }

    /**
     * Convenience method to compile the filter.
     * Processes the filter and updates the QueryExpression accordingly.
     * @param qs The Query Expression to apply the filter to (if specified)
     * @param filter The filter specification
     */
    protected void compileFilter(QueryExpression qs, String filter)
    {
        if (filter != null && filter.length() > 0)
        {
            ScalarExpression expr = compileExpressionFromString(filter);
            if (!(expr instanceof BooleanExpression))
            {
                throw new JPOXUserException(LOCALISER.msg("021050", filter));              
            }
            if (qs != null)
            {
                qs.andCondition((BooleanExpression)expr, true);
            }
        }
    }

    /**
     * Compile the result expressions and class.
     * @param qs Query Expression to apply the result to (if required)
     * @param result Result clause to compile
     */
    protected void compileResult(QueryExpression qs, String result)
    {
        // Compile any result expressions
        ScalarExpression[] resultExprs = null;
        if (result != null)
        {
            String resultDefinition = result;
            if (result.toLowerCase().startsWith("distinct"))
            {
                // DISTINCT specified so add direct to the query statement
                if (qs != null)
                {
                    qs.setDistinctResults(true);
                }
                resultDefinition = result.substring(8);
                distinct = true;
            }
            resultExprs = compileExpressionsFromString(resultDefinition);
            ((ResultExpressionsQueryable)candidates).setResultExpressions(resultExprs);

            // Process result types
            Class[] resultTypes = new Class[resultExprs.length];
            for (int i=0; i<resultExprs.length; i++)
            {
                //TODO localise these messages
                if (resultExprs[i] instanceof CollectionExpression)
                {
                    throw new JPOXUserException(resultExprs[i].toStatementText(ScalarExpression.PROJECTION) +
                        " is of type java.util.Collection and cannot be in the result.");
                }
                else if (resultExprs[i] instanceof MapExpression)
                {
                    throw new JPOXUserException(resultExprs[i].toStatementText(ScalarExpression.PROJECTION) +
                        " is of type java.util.Map and cannot be in the result.");
                }
                //e.g.UnboundVariables have no mapping, so we ignore it
                if (resultExprs[i].getMapping() != null)
                {
                    if (resultExprs[i].getMapping().getType() != null)
                    {
                        resultTypes[i] = query.resolveClassDeclaration(resultExprs[i].getMapping().getType());
                    }
                    else
                    {
                        resultTypes[i] = resultExprs[i].getMapping().getJavaType();
                    }
                }
            }
            resultMetaData = new JPOXResultSetMetaData(resultTypes);
        }
        else
        {
            // Result must be candidate class
            resultMetaData = new JPOXResultSetMetaData(new Class[] {candidateClass} );
            if (distinct)
            {
                qs.setDistinctResults(distinct);
            }
        }

        String resultClassName = query.getResultClassName();
        if (resultClass == null && resultClassName != null)
        {
            // Resolve any result class
            ScalarExpression expr = compileExpressionFromString(resultClassName);
            resultClass = ((ClassExpression)expr).getCls();
        }

        if (resultClass != null && resultExprs != null)
        {
            // Consistency checking of result class and result expressions
            if (QueryUtils.resultClassIsSimple(resultClass.getName()))
            {
                if (resultExprs.length > 1)
                {
                    // Invalid number of result expressions
                    throw new JPOXUserException(LOCALISER.msg("021033", resultClass.getName()));
                }

                Class exprType = resultExprs[0].getMapping().getJavaType();
                boolean typeConsistent = false;
                if (exprType == resultClass)
                {
                    typeConsistent = true;
                }
                else if (exprType.isPrimitive())
                {
                    Class resultClassPrimitive = ClassUtils.getPrimitiveTypeForType(resultClass);
                    if (resultClassPrimitive == exprType)
                    {
                        typeConsistent = true;
                    }
                }
                if (!typeConsistent)
                {
                    // Inconsistent expression type not matching the result class type
                    throw new JPOXUserException(LOCALISER.msg("021034", resultClass.getName(), exprType));
                }
            }
            else if (QueryUtils.resultClassIsUserType(resultClass.getName()))
            {
                // Check for valid constructor (either using param types, or using default ctr)
                Class[] ctrTypes = new Class[resultExprs.length];
                for (int i=0;i<ctrTypes.length;i++)
                {
                    ctrTypes[i] = resultExprs[i].getMapping().getJavaType();
                }
                Constructor ctr = ClassUtils.getConstructorWithArguments(resultClass, ctrTypes);
                if (ctr == null && !ClassUtils.hasDefaultConstructor(resultClass))
                {
                    // No valid constructor found!
                    throw new JPOXUserException(LOCALISER.msg("021037", resultClass.getName()));
                }
                else if (ctr == null)
                {
                    // We are using default constructor, so check the types of the result expressions for means of input
                    for (int i=0;i<resultExprs.length;i++)
                    {
                        String fieldName = resultExprs[i].getAlias();
                        Class fieldType = resultExprs[i].getMapping().getJavaType();
                        if (fieldName == null && resultExprs[i].getMapping().getFieldMetaData() != null)
                        {
                            fieldName = resultExprs[i].getMapping().getFieldMetaData().getName();
                        }

                        if (fieldName != null)
                        {
                            // Check for the field of that name in the result class
                            Class resultFieldType = null;
                            boolean publicField = true;
                            try
                            {
                                Field fld = resultClass.getDeclaredField(fieldName);
                                resultFieldType = fld.getType();

                                // Check the type of the field
                                if (!ClassUtils.typesAreCompatible(fieldType, resultFieldType) &&
                                    !ClassUtils.typesAreCompatible(resultFieldType, fieldType))
                                {
                                    throw new JPOXUserException(LOCALISER.msg("021062",
                                        fieldName, fieldType.getName(), resultFieldType.getName()));
                                }
                                if (!Modifier.isPublic(fld.getModifiers()))
                                {
                                    publicField = false;
                                }
                            }
                            catch (NoSuchFieldException nsfe)
                            {
                                publicField = false;
                            }

                            // Check for a public set method
                            if (!publicField)
                            {
                                Method setMethod = QueryUtils.getPublicSetMethodForFieldOfResultClass(resultClass, fieldName, resultFieldType);
                                if (setMethod == null)
                                {
                                    // No setter, so check for a public put(Object, Object) method
                                    Method putMethod = QueryUtils.getPublicPutMethodForResultClass(resultClass);
                                    if (putMethod == null)
                                    {
                                        throw new JPOXUserException(LOCALISER.msg("021063",
                                            resultClass.getName(), fieldName));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Convenience method to compile the grouping.
     * Compiles the grouping definition and applies it to the passed Query Expression as appropriate.
     * @param qs The QueryExpression to update (if specified)
     * @param groupingClause The grouping clause string
     */
    protected void compileGrouping(QueryExpression qs, String groupingClause)
    {
        if (groupingClause != null && groupingClause.length() > 0)
        {
            ScalarExpression[] groupExprs = compileExpressionsFromString(groupingClause);
            if (groupExprs != null && qs != null)
            {
                for (int i=0;i<groupExprs.length;i++)
                {
                    qs.addGroupingExpression(groupExprs[i]);
                }
            }
        }
    }

    /**
     * Convenience method to compile the having clause
     * @param qs The QueryExpression to update (if specified)
     * @param havingClause The having clause string
     */
    protected void compileHaving(QueryExpression qs, String havingClause)
    {
        if (havingClause != null && havingClause.length() > 0)
        {
            ScalarExpression havingExpr = compileExpressionFromString(havingClause);
            if (qs != null)
            {
                if (!(havingExpr instanceof BooleanExpression))
                {
                    throw new JPOXUserException(LOCALISER.msg("021051", havingExpr));
                }
                qs.setHaving((BooleanExpression)havingExpr);
            }
        }
    }

    /**
     * Convenience method to compile the range.
     * Compiles any range string and extracts the fromInclNo, toExclNo as appropriate.
     * @param qs QueryExpression to apply the range to (if specified)
     */
    protected void compileRange(QueryExpression qs)
    {
        String range = query.getRange();
        if (range != null)
        {
            ScalarExpression[] exprs = compileExpressionsFromString(range);
            if (exprs.length > 0)
            {
                if (!(exprs[0] instanceof Literal))
                {
                    throw new JPOXUserException(LOCALISER.msg("021064", "FROM", exprs[0]));
                }
                if (!(((Literal)exprs[0]).getValue() instanceof Number))
                {
                    throw new JPOXUserException(LOCALISER.msg("021065", "FROM", exprs[0]));
                }
                rangeFromIncl = ((Number)((Literal)exprs[0]).getValue()).longValue();
            }
            if (exprs.length > 1)
            {
                if (!(exprs[1] instanceof Literal))
                {
                    throw new JPOXUserException(LOCALISER.msg("021064", "TO", exprs[1]));
                }
                if (!(((Literal)exprs[1]).getValue() instanceof Number))
                {
                    throw new JPOXUserException(LOCALISER.msg("021065", "TO", exprs[1]));
                }
                rangeToExcl = ((Number)((Literal)exprs[1]).getValue()).longValue();
            }
        }

        if (qs != null && (rangeFromIncl > 0 || rangeToExcl != Long.MAX_VALUE))
        {
            qs.setRangeConstraint(rangeFromIncl, rangeToExcl != Long.MAX_VALUE ? rangeToExcl-rangeFromIncl : -1);
        }
    }

    /**
     * Convenience method to check the expressions against those specified in the grouping.
     * Throws a JPOXUserException if one of the expressions is not present in the grouping expressions.
     * @param exprs The expressions to check
     * @param groupExprs The grouping expressions
     * @param localiserErrorString Name of a localiser error message to throw as the JPOXUserException message.
     */
    protected void checkExpressionsAgainstGrouping(ScalarExpression[] exprs, ScalarExpression[] groupExprs,
            String localiserErrorString)
    {
        if (exprs == null)
        {
            return;
        }
        for (int i=0; i<exprs.length; i++)
        {
            boolean exists = false;
            for (int j=0; j<groupExprs.length; j++)
            {
                if (exprs[i].equals(groupExprs[j]))
                {
                    exists = true;
                    break;
                }
            }
            if (!(exprs[i] instanceof AggregateExpression) && !exists)
            {
                throw new JPOXUserException(LOCALISER.msg(localiserErrorString, exprs[i]));
            }
        }
    }

    /**
     * Bind a variable to the query.
     * @param name Name of the variable
     * @param expr The expression
     */
    public void bindVariable(String name, ScalarExpression expr)
    {
        ScalarExpression previousExpr = (ScalarExpression)expressionsByVariableName.put(name, expr);
        if (previousExpr != null)
        {
            throw new JPOXException(LOCALISER.msg("021060",name,expr,previousExpr)).setFatal();
        }
    }

    /**
     * Convenience method to check that all variables have been bound to the query.
     * @throws JPOXUserException Thrown if a variable is found that is not bound.
     */
    protected void checkVariableBinding()
    {
        for (int i=0;i<variableNames.size();i++)
        {
            String variableName = (String)variableNames.get(i);
            if (expressionsByVariableName.get(variableName) == null)
            {
                boolean foundInResult = false;
                // we allow unbounded variables to be projected in result
                if (candidates instanceof ResultExpressionsQueryable)
                {
                    ScalarExpression[] exprs = ((ResultExpressionsQueryable) candidates).getResultExpressions();
                    for (int j = 0; j < exprs.length; j++)
                    {
                        if (exprs[j] instanceof UnboundVariable)
                        {
                            if (((UnboundVariable) exprs[j]).getVariableName().equals(variableName))
                            {
                                foundInResult = true;
                            }
                        }
                    }
                }
                if (!foundInResult)
                {
                    // throw exception if unbounded variable is not in result
                    throw new JPOXUserException(LOCALISER.msg("021061", variableName));
                }
            }
        }
    }

    /**
     * Convenience method to parse an expression string into its component query expressions.
     * This splits expressions at comma boundaries, whilst respecting that the string could include
     * expressions like "new MyClass(a, b, c)" and so keeping braced arguments together. If it wasn't
     * for this requirement we would have been able to just use a StringTokenizer.
     * @param str The string
     * @return The query expressions for the passed string
     */
    protected ScalarExpression[] compileExpressionsFromString(String str)
    {
        String[] exprList = QueryUtils.getExpressionsFromString(str);
        if (exprList != null && exprList.length > 0)
        {
            ScalarExpression[] exprs = new ScalarExpression[exprList.length];
            for (int i=0;i<exprs.length;i++)
            {
                exprs[i] = compileExpressionFromString(exprList[i]);
            }
            return exprs;
        }
        return null;
    }

    /**
     * Convenience method to parse an expression string into its query expression.
     * @param str The string
     * @return The query expression for the passed string
     */
    protected abstract ScalarExpression compileExpressionFromString(String str);

    /**
     * Principal method for compiling an expression.
     * An expression could be the filter, the range, the result, etc.
     * @return The compiled expression
     */
    protected abstract ScalarExpression compileExpression();

    protected ScalarExpression compileAdditiveExpression()
    {
        ScalarExpression expr = compileMultiplicativeExpression();

        for (;;)
        {
            if (p.parseChar('+'))
            {
                expr = expr.add(compileMultiplicativeExpression());
            }
            else if (p.parseChar('-'))
            {
                expr = expr.sub(compileMultiplicativeExpression());
            }
            else
            {
                break;
            }
        }

        return expr;
    }

    protected ScalarExpression compileMultiplicativeExpression()
    {
        ScalarExpression expr = compileUnaryExpression();

        for (;;)
        {
            if (p.parseChar('*'))
            {
                expr = expr.mul(compileUnaryExpression());
            }
            else if (p.parseChar('/'))
            {
                expr = expr.div(compileUnaryExpression());
            }
            else if (p.parseChar('%'))
            {
                expr = expr.mod(compileUnaryExpression());
            }
            else
            {
                break;
            }
        }

        return expr;
    }

    protected ScalarExpression compileUnaryExpression()
    {
        ScalarExpression expr;
        if (p.parseString("++"))
        {
            throw new JPOXUserException("Unsupported operator '++'");
        }
        else if (p.parseString("--"))
        {
            throw new JPOXUserException("Unsupported operator '--'");
        }              

        if (p.parseChar('+'))
        {
            expr = compileUnaryExpression();
        }
        else if (p.parseChar('-'))
        {
            expr = compileUnaryExpression().neg();
        }
        else
        {
            expr = compileUnaryExpressionNotPlusMinus();
        }
        return expr;
    }

    protected ScalarExpression compileUnaryExpressionNotPlusMinus()
    {
        ScalarExpression expr;

        if (p.parseChar('~'))
        {
            expr = compileUnaryExpression().com();
        }
        else if (p.parseChar('!'))
        {
            expr = compileUnaryExpression().not();
        }
        else if ((expr = compileCastExpression()) == null)
        {
            expr = compilePrimary();
        }

        return expr;
    }

    protected ScalarExpression compileCastExpression()
    {
        Class type;
        if ((type = p.parseCast(
            (qs == null ? query.getObjectManager().getClassLoaderResolver() : qs.getClassLoaderResolver()),
            (candidateClass == null ? null : candidateClass.getClassLoader()))) == null)
        {
            return null;
        }

        return compileUnaryExpression().cast(type);
    }

    /**
     * Compiles a primary. First look for a literal (e.g. "text"), then an identifier(e.g. variable).
     * In the next step, call a function, if executing a function, on the literal or the identifier found.
     *
     * @return Scalar Expression for the primary compiled expression
     */
    protected abstract ScalarExpression compilePrimary();

    /**
     * A literal is one value of any type.
     * Supported literals are of types String, Floating Point, Integer,
     * Character, Boolean and null e.g. 'J', "String", 1, 1.8, true, false, null.
     * @return The compiled literal
     */
    protected ScalarExpression compileLiteral()
    {
        Class  litType;
        Object litValue;

        String     sLiteral;
        BigDecimal fLiteral;
        BigInteger iLiteral;
        Boolean    bLiteral;

        boolean single_quote_next = p.nextIsSingleQuote();
        if ((sLiteral = p.parseStringLiteral()) != null)
        {
            // Both String and Character are allowed to use single-quotes
            // so we need to check if it was single-quoted and
            // use CharacterLiteral if length is 1.
            if (sLiteral.length() == 1 && single_quote_next)
            {
                litType = Character.class;
                litValue = new Character(sLiteral.charAt(0));
            }
            else
            {
                litType  = String.class;
                litValue = sLiteral;
            }
        }
        else if ((fLiteral = p.parseFloatingPointLiteral()) != null)
        {
            litType  = BigDecimal.class;
            litValue = fLiteral;
        }
        else if ((iLiteral = p.parseIntegerLiteral()) != null)
        {
            litType  = Long.class;
            litValue = new Long(iLiteral.longValue());
        }
        else if ((bLiteral = p.parseBooleanLiteral()) != null)
        {
            litType  = Boolean.class;
            litValue = bLiteral;
        }
        else if (p.parseNullLiteral())
        {
            return new NullLiteral(qs);
        }
        else
        {
            return null;
        }

        JavaTypeMapping m = qs.getStoreManager().getDatastoreAdapter().getMapping(litType,
            qs.getStoreManager(), qs.getClassLoaderResolver());
        return m.newLiteral(qs, litValue);
    }

    /**
     * Method to generate an expression for a new object.
     * Parser has just parsed "new" and what follows is of the form
     * <pre>
     * new MyObject(param1, param2)
     * </pre>
     * @return Expression for the new object
     */
    protected ScalarExpression compileNewObject()
    {
        // Found syntax for "new MyObject(param1, param2)" - result expression
        String newClsName = p.parseName();
        Class newCls = null;
        try
        {
            newCls = query.resolveClassDeclaration(newClsName);
        }
        catch (JPOXUserException jpue)
        {
            throw new JPOXUserException(LOCALISER.msg("021057", language, newClsName));
        }

        ArrayList args = new ArrayList();
        if (p.parseChar('('))
        {
            if (!p.parseChar(')'))
            {
                do
                {
                    ScalarExpression argExpr = compileExpression();
                    args.add(argExpr);
                    fieldExpressions.add(argExpr); // Add to field expressions list
                } while (p.parseChar(','));
                if (!p.parseChar(')'))
                {
                    throw new QueryCompilerSyntaxException("')' expected", p.getIndex(), p.getInput());
                }
            }
        }
        else
        {
            throw new JPOXUserException(LOCALISER.msg("021058", language,
                ((AbstractJavaQuery)query).getSingleStringQuery()));
        }
        return new NewObjectExpression(qs, newCls, args);
    }

    /**
     * Method to compile an explicit variable.
     * Identifier passed in is a known explicit variable name.
     * @param id Identifier of the variable
     * @return Variable expression
     */
    protected ScalarExpression compileExplicitVariable(String id)
    {
        ScalarExpression expr = (ScalarExpression)expressionsByVariableName.get(id);
        if (expr == null)
        {
            expr = new UnboundVariable(qs, id, (Class)variableTypesByName.get(id), this);
        }
        fieldExpressions.add(expr); // Add to the field expressions list
        return expr;
    }

    /**
     * Instanciate a ScalarExpression and invoke a method
     * @param method the method name prefixed by the class name (fully qualified or not)
     * @return the ScalarExpression instance
     */
    protected ScalarExpression callUserDefinedScalarExpression(String method)
    {
        String className = method.substring(0, method.lastIndexOf('.'));
        String methodName = method.substring(method.lastIndexOf('.')+1);
        if (!AbstractJavaQuery.getUserDefinedScalarExpressions().containsKey(className))
        {
            Class cls = query.resolveClassDeclaration(className);
            className = cls.getName();
        }
        if (AbstractJavaQuery.getUserDefinedScalarExpressions().containsKey(className))
        {
            ScalarExpression expr = newScalarExpression((Class)
                AbstractJavaQuery.getUserDefinedScalarExpressions().get(className));
            if (p.parseChar('('))
            {
                // Found syntax for a method, so invoke the method
                ArrayList args = new ArrayList();
   
                if (!p.parseChar(')'))
                {
                    do
                    {
                        args.add(compileExpression());
                    }
                    while (p.parseChar(','));
   
                    if (!p.parseChar(')'))
                    {
                        throw new QueryCompilerSyntaxException("')' expected", p.getIndex(), p.getInput());
                    }
                }
   
                return expr.callMethod(methodName, args);
            }
        }
        return null;
    }

    /**
     * Create a ScalarExpression instance for the given <code>cls</code>
     * @param cls the class that extends ScalarExpression
     * @return the ScalarExpression instance
     */
    private ScalarExpression newScalarExpression(Class cls)
    {
        try
        {
            return (ScalarExpression) cls.getConstructor(new Class[]{QueryExpression.class}).newInstance(
                new Object[] {qs});
        }
        catch (IllegalArgumentException e)
        {
            throw new JPOXException("Cannot create ScalarExpression for class " + cls.getName() +
                " due to " + e.getMessage(), e).setFatal();
        }
        catch (SecurityException e)
        {
            throw new JPOXException("Cannot create ScalarExpression for class " + cls.getName() +
                " due to " + e.getMessage(), e).setFatal();
        }
        catch (InstantiationException e)
        {
            throw new JPOXException("Cannot create ScalarExpression for class " + cls.getName() +
                " due to " + e.getMessage(), e).setFatal();
        }
        catch (IllegalAccessException e)
        {
            throw new JPOXException("Cannot create ScalarExpression for class " + cls.getName() +
                " due to " + e.getMessage(), e).setFatal();
        }
        catch (InvocationTargetException e)
        {
            throw new JPOXException("Cannot create ScalarExpression for class " + cls.getName() +
                " due to " + e.getMessage(), e).setFatal();
        }
        catch (NoSuchMethodException e)
        {
            throw new JPOXException("Cannot create ScalarExpression for class " + cls.getName() +
                " due to " + e.getMessage(), e).setFatal();
        }
    }
}
TOP

Related Classes of org.jpox.store.rdbms.query.JavaQueryCompiler$AliasJoinInformation

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.