org.babyfish.hibernate.hql.XQueryTranslatorImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.babyfish.hibernate.hql.XQueryTranslatorImpl.java

Source

/*
 * BabyFish, Object Model Framework for Java and JPA.
 * https://github.com/babyfish-ct/babyfish
 *
 * Copyright (c) 2008-2015, Tao Chen
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * Please visit "http://opensource.org/licenses/LGPL-3.0" to know more.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 */
package org.babyfish.hibernate.hql;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.Tuple;

import org.babyfish.collection.ArrayList;
import org.babyfish.collection.HashMap;
import org.babyfish.collection.HashSet;
import org.babyfish.collection.LinkedHashMap;
import org.babyfish.collection.LinkedHashSet;
import org.babyfish.collection.ReferenceEqualityComparator;
import org.babyfish.collection.XOrderedMap;
import org.babyfish.collection.XOrderedSet;
import org.babyfish.hibernate.cfg.SettingsFactory;
import org.babyfish.hibernate.dialect.DistinctLimitDialect;
import org.babyfish.hibernate.dialect.Oracle10gDialect;
import org.babyfish.hibernate.hql.HqlASTHelper.AliasGenerator;
import org.babyfish.hibernate.internal.AbstractHibernatePathPlanFactory;
import org.babyfish.hibernate.loader.DistinctLimitQueryLoader;
import org.babyfish.hibernate.loader.UnlimitedCountLoader;
import org.babyfish.hibernate.model.loader.HibernateObjectModelScalarLoader;
import org.babyfish.lang.IllegalProgramException;
import org.babyfish.lang.UncheckedException;
import org.babyfish.lang.reflect.asm.ASM;
import org.babyfish.lang.reflect.asm.ClassEnhancer;
import org.babyfish.model.ObjectModel;
import org.babyfish.model.ScalarBatchLoader;
import org.babyfish.org.objectweb.asm.Label;
import org.babyfish.org.objectweb.asm.Opcodes;
import org.babyfish.org.objectweb.asm.tree.AbstractInsnNode;
import org.babyfish.org.objectweb.asm.tree.FieldInsnNode;
import org.babyfish.org.objectweb.asm.tree.InsnList;
import org.babyfish.org.objectweb.asm.tree.InsnNode;
import org.babyfish.org.objectweb.asm.tree.JumpInsnNode;
import org.babyfish.org.objectweb.asm.tree.LabelNode;
import org.babyfish.org.objectweb.asm.tree.LdcInsnNode;
import org.babyfish.org.objectweb.asm.tree.MethodInsnNode;
import org.babyfish.org.objectweb.asm.tree.MethodNode;
import org.babyfish.org.objectweb.asm.tree.TypeInsnNode;
import org.babyfish.org.objectweb.asm.tree.VarInsnNode;
import org.babyfish.persistence.Constants;
import org.babyfish.persistence.QueryType;
import org.babyfish.persistence.model.metadata.JPAObjectModelMetadata;
import org.babyfish.persistence.path.spi.JoinNode;
import org.babyfish.persistence.path.spi.OrderNode;
import org.babyfish.persistence.path.spi.PathPlan;
import org.babyfish.persistence.path.spi.PathPlanKey;
import org.babyfish.persistence.path.spi.SubPlan;
import org.babyfish.util.LazyResource;
import org.hibernate.Filter;
import org.hibernate.QueryException;
import org.hibernate.bytecode.instrumentation.internal.FieldInterceptionHelper;
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.hql.internal.antlr.HqlTokenTypes;
import org.hibernate.hql.internal.ast.HqlParser;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.internal.ast.QueryTranslatorImpl;
import org.hibernate.hql.internal.ast.SqlGenerator;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.hql.internal.ast.tree.QueryNode;
import org.hibernate.hql.internal.ast.tree.SelectClause;
import org.hibernate.hql.internal.ast.util.ASTPrinter;
import org.hibernate.loader.hql.QueryLoader;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.type.AssociationType;
import org.hibernate.type.ManyToOneType;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import antlr.collections.AST;

/**
 * @author Tao Chen
 */
public abstract class XQueryTranslatorImpl extends QueryTranslatorImpl
        implements XQueryTranslator, XFilterTranslator {

    private static final Logger LOGGER = LoggerFactory.getLogger(XQueryTranslatorImpl.class);

    private static final LazyResource<Resource> LAZY_RESOURCE = LazyResource.of(Resource.class);

    private static final Constructor<XQueryTranslatorImpl> CONSTRUCTOR;

    private static final Method PARSE_METHOD;

    /*
     * These fields is not private because they may be access by the dynamic derived class.
     */
    SessionFactoryImplementor factory;

    String hql;

    PathPlanKey pathPlanKey;

    PathPlan pathPlan;

    int queryType; //Hibernate's queryType: SELECT, INSERT, UPDATE, DELETE; not babyfish's queryType: DISTINCT, RESULT

    Map<String, AST> startFromElementASTMap;

    Map<String, Integer> returnedEntityColumns;

    String countSql;

    AST countSqlAst;

    String distinctCountSql;

    AST distinctCountSqlAst;

    QueryLoader queryLoader;

    DistinctLimitQueryLoader distinctLimitQueryLoader;

    UnlimitedCountLoader countLoader;

    UnlimitedCountLoader distinctCountLoader;

    ExceptionCreator unlimitedCountExceptionCreator;

    boolean usingQueryPaths;

    protected XQueryTranslatorImpl(String queryIdentifier, String query, PathPlanKey pathPlanKey,
            Map<String, Filter> enabledFilters, SessionFactoryImplementor factory,
            EntityGraphQueryHint entityGraphQueryHint) {
        super(queryIdentifier, query, enabledFilters, factory);
        this.factory = factory;
        this.hql = query;
        this.pathPlanKey = pathPlanKey;
        this.usingQueryPaths = pathPlanKey != null;
    }

    public static XQueryTranslatorImpl newInstance(String queryIdentifier, String queryString,
            PathPlanKey pathPlanKey, Map<String, Filter> filters, SessionFactoryImplementor factory,
            EntityGraphQueryHint entityGraphQueryHint) {
        try {
            return CONSTRUCTOR.newInstance(new Object[] { queryIdentifier, queryString, pathPlanKey, filters,
                    factory, entityGraphQueryHint });
        } catch (InstantiationException ex) {
            throw new AssertionError();
        } catch (IllegalAccessException ex) {
            throw UncheckedException.rethrow(ex);
        } catch (InvocationTargetException ex) {
            throw UncheckedException.rethrow(ex.getTargetException());
        }
    }

    @Override
    public String getCountSQLString() {
        return this.countSql;
    }

    @Override
    public String getDistinctCountSQLString() {
        return this.distinctCountSql;
    }

    @Override
    public final PathPlan getPathPlan() {
        return this.pathPlan;
    }

    @Override
    public long unlimitedCount(SessionImplementor session, QueryParameters queryParameters, QueryType queryType) {
        if (this.unlimitedCountExceptionCreator != null) {
            throw this.unlimitedCountExceptionCreator.create();
        }
        UnlimitedCountLoader loader;
        if (queryType == QueryType.DISTINCT && this.distinctCountLoader != null) {
            loader = this.distinctCountLoader;
        } else {
            loader = this.countLoader;
        }
        if (loader == null) {
            throw new QueryException(LAZY_RESOURCE.get().operationRequiresQuery("unlimitedCount"));
        }
        List<?> list = loader.list(session, queryParameters);
        Object o = list.iterator().next();
        if (o.getClass().isArray()) {
            o = ((Object[]) o)[0];
        }
        if (o instanceof Long) {
            return (Long) o;
        }
        if (o instanceof Integer) {
            return (Integer) o;
        }
        return Long.parseLong(o.toString());
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public <T> List<T> list(SessionImplementor session, QueryParameters queryParameters, QueryType queryType) {
        if (queryType == QueryType.DISTINCT) {
            List<T> tmp;
            if (this.distinctLimitQueryLoader != null) {
                tmp = (List) this.distinctLimitQueryLoader.list(session, queryParameters);
            } else {
                boolean hasLimit = queryParameters.getRowSelection() != null
                        && queryParameters.getRowSelection().definesLimits();
                if (hasLimit && this.containsCollectionFetches()) {
                    if (!SettingsFactory.isLimitInMemoryEnabled(session.getFactory().getProperties())) {
                        throw new QueryException(LAZY_RESOURCE.get()
                                .hibernateLimitInMemoryForCollectionFetchIsNotEnabled(DistinctLimitDialect.class,
                                        Oracle10gDialect.class, SettingsFactory.ENABLE_LIMIT_IN_MEMORY));
                    }
                }
                tmp = this.list(session, queryParameters);
            }
            Set<T> distinction = new LinkedHashSet<T>(ReferenceEqualityComparator.getInstance());
            distinction.addAll(tmp);
            List<T> results = new ArrayList<T>(distinction.size());
            results.addAll(distinction);
            this.applyScalarEagerness(results, session);
            return results;
        } else {
            return this.queryLoader.list(session, queryParameters);
        }
    }

    protected final SessionFactoryImplementor getFactory() {
        return this.factory;
    }

    protected final boolean isUsingQueryPaths() {
        return this.usingQueryPaths;
    }

    //It is invoked by the byte code generated runtime.
    //It is always called before 
    //applyRootJoinNodeForCount and applyRootJoinNodeForDistinctCount
    final void applyRootJoinNode(AST ast) {
        this.queryType = ast.getType();
        this.initialize(ast);
        if (ast.getType() == HqlTokenTypes.QUERY) {
            logAST(ast, "Before apply query paths for generic query, the HQL-AST is");
            this.onApplyRootJoinNode(ast);
            this.preApplyScalarEagerness(ast);
            logAST(ast, "After apply query paths for generic query, the HQL-AST is");
        }
    }

    //It is invoked by the byte code generated runtime.
    final void applyRootJoinNodeForCount(AST ast) {
        this.initialize(ast);
        if (ast.getType() == HqlTokenTypes.QUERY && this.unlimitedCountExceptionCreator == null) {
            this.unlimitedCountExceptionCreator = this.getUnlimitedCountExceptionCreator(ast);
            if (this.unlimitedCountExceptionCreator == null) {
                logAST(ast, "Before apply query paths for count query, the HQL-AST is");
                this.onApplyRootJoinNodeForCount(ast, false);
                logAST(ast, "After apply query paths for count query, the HQL-AST is");
            }
        }
    }

    //It is invoked by the byte code generated runtime.
    final void applyRootJoinNodeForDistinctCount(AST ast) {
        this.initialize(ast);
        if (ast.getType() == HqlTokenTypes.QUERY && this.unlimitedCountExceptionCreator == null) {
            this.unlimitedCountExceptionCreator = this.getUnlimitedCountExceptionCreator(ast);
            if (this.unlimitedCountExceptionCreator == null) {
                logAST(ast, "Before apply query paths for distinct count query, the HQL-AST is");
                this.onApplyRootJoinNodeForCount(ast, true);
                logAST(ast, "After apply query paths for distinct count query, the HQL-AST is");
            }
        }
    }

    //It is invoked by the byte code generated runtime.
    final void processCountSqlAst(AST countSqlAST) {
        logAST(countSqlAST, "Before SQL-AST processing for count query, the SQL-AST is");
        this.onProcessCountSqlAST(countSqlAST, false);
        logAST(countSqlAST, "After SQL-AST processing for count query, the SQL-AST is");
    }

    //It is invoked by the byte code generated runtime.
    final void processDistinctCountSqlAst(AST distinctCountSqlAST) {
        logAST(distinctCountSqlAST, "Before SQL-AST processing for distinct count query, the SQL-AST is");
        this.onProcessCountSqlAST(distinctCountSqlAST, true);
        logAST(distinctCountSqlAST, "Before SQL-AST processing for distinct count query, the SQL-AST is");
    }

    @SuppressWarnings("unchecked")
    protected boolean shouldUseDistinctQuery() {
        if (this.pathPlan.containsCollectionJoins()) {
            return true;
        }
        QueryNode queryNode = (QueryNode) this.getSqlAST();
        boolean foundFrom = false;
        for (FromElement fromElement : (List<FromElement>) queryNode.getFromClause().getFromElements()) {
            if (fromElement.getClass() == FromElement.class) {
                if (!fromElement.getText().contains(" join ")) {
                    if (foundFrom) {
                        return true;
                    }
                    foundFrom = true;
                } else if (fromElement.getText().startsWith("cross ")) {
                    return true;
                } else if (fromElement.getQueryableCollection() != null) {
                    return true;
                }
            }
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    protected boolean shouldUseDistinctCount() {
        if (this.pathPlan.containsCollectionInnerJoins()) {
            return true;
        }
        QueryNode queryNode = (QueryNode) this.getSqlAST();
        Set<FromElement> collectionLeftJoins = new HashSet<>(ReferenceEqualityComparator.getInstance());
        boolean foundFrom = false;
        for (FromElement fromElement : (List<FromElement>) queryNode.getFromClause().getFromElements()) {
            if (fromElement.getClass() == FromElement.class) {
                if (!fromElement.getText().contains(" join ")) {
                    if (foundFrom) {
                        return true;
                    }
                    foundFrom = true;
                } else if (fromElement.getText().startsWith("cross ")) {
                    return true;
                } else if (fromElement.getQueryableCollection() != null) {
                    if (!fromElement.getText().startsWith("left ")) {
                        return true;
                    }
                    if (fromElement.getWithClauseFragment() != null) {
                        return true;
                    }
                    collectionLeftJoins.add(fromElement);
                }
            }
        }
        return SqlASTHelper.findJoinReferenceInWhereCaluse(queryNode, collectionLeftJoins);
    }

    protected void onApplyRootJoinNode(AST ast) {
        AliasGenerator aliasGenerator = new AliasGenerator();
        AST selectFromAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.SELECT_FROM);
        AST fromAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.FROM);

        for (SubPlan subPlan : this.pathPlan.getSubPlans().values()) {
            Map<JoinNode, AST> fromElementASTMap = new HashMap<>(
                    ReferenceEqualityComparator.<JoinNode>getInstance(),
                    ReferenceEqualityComparator.<AST>getInstance());
            AST startFromElementAst = this.startFromElementASTMap.get(subPlan.getAlias());
            fromElementASTMap.put(subPlan.getJoinNode(), startFromElementAst);
            if (!subPlan.getJoinNode().getChildNodes().isEmpty()) {
                for (JoinNode joinNode : subPlan.getJoinNode().getChildNodes().values()) {
                    HqlASTHelper.addJoinAST(fromAst, startFromElementAst, joinNode, aliasGenerator, true, false,
                            false, fromElementASTMap);
                }
            }
            List<OrderNode> preOrderNodes = subPlan.getPreOrderNodes();
            List<OrderNode> postOrderNodes = subPlan.getPostOrderNodes();
            if (!preOrderNodes.isEmpty() || !postOrderNodes.isEmpty()) {
                AST orderAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.ORDER);
                if (orderAst == null) {
                    orderAst = HqlASTHelper.createAST(HqlTokenTypes.ORDER, "order");
                    ast.addChild(orderAst);
                } else {
                    HqlASTHelper.removeChildOrdersInToppestQuery(orderAst, preOrderNodes, fromElementASTMap);
                    HqlASTHelper.removeChildOrdersInToppestQuery(orderAst, postOrderNodes, fromElementASTMap);
                }
                for (int i = preOrderNodes.size() - 1; i >= 0; i--) {
                    OrderNode preOrderNode = preOrderNodes.get(i);
                    JoinNode joinNode = preOrderNode.getParentNode();
                    AST fromElementAST = fromElementASTMap.get(joinNode);
                    String alias = HqlASTHelper.getAlias(fromElementAST);
                    if (alias == null) {
                        alias = aliasGenerator.generateAlias();
                        HqlASTHelper.setAlias(fromElementAST, alias);
                    }
                    AST orderFieldAST = HqlASTHelper.createOrderFieldAST(alias, preOrderNode);
                    AST orderTypeAST = preOrderNode.isDesc()
                            ? HqlASTHelper.createAST(HqlTokenTypes.DESCENDING, "desc")
                            : HqlASTHelper.createAST(HqlTokenTypes.ASCENDING, "asc");
                    orderTypeAST.setNextSibling(orderAst.getFirstChild());
                    orderFieldAST.setNextSibling(orderTypeAST);
                    orderAst.setFirstChild(orderFieldAST);
                }
                for (OrderNode postOrderNode : postOrderNodes) {
                    JoinNode joinNode = postOrderNode.getParentNode();
                    AST fromElementAST = fromElementASTMap.get(joinNode);
                    String alias = HqlASTHelper.getAlias(fromElementAST);
                    if (alias == null) {
                        alias = aliasGenerator.generateAlias();
                        HqlASTHelper.setAlias(fromElementAST, alias);
                    }
                    AST orderFieldAST = HqlASTHelper.createOrderFieldAST(alias, postOrderNode);
                    AST orderTypeAST = postOrderNode.isDesc()
                            ? HqlASTHelper.createAST(HqlTokenTypes.DESCENDING, "desc")
                            : HqlASTHelper.createAST(HqlTokenTypes.ASCENDING, "asc");
                    orderAst.addChild(orderFieldAST);
                    orderAst.addChild(orderTypeAST);
                }
            }
        }
    }

    protected void onApplyRootJoinNodeForCount(AST ast, boolean distinct) {
        AST orderAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.ORDER);
        if (orderAst != null) {
            HqlASTHelper.removeOrderByInToppestQuery(ast);
            orderAst = null;
        }
        AST selectFromAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.SELECT_FROM);
        AST fromAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.FROM);
        AST selectAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.SELECT);
        AST defaultRangeAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.RANGE);
        AST defaultRangeAliasAst = HqlASTHelper.findFirstChildInToppestQuery(defaultRangeAst, HqlTokenTypes.ALIAS);
        AliasGenerator aliasGenerator = new AliasGenerator();
        if (defaultRangeAliasAst == null) {
            defaultRangeAliasAst = HqlASTHelper.createAST(HqlTokenTypes.ALIAS, aliasGenerator.generateAlias());
            defaultRangeAst.addChild(defaultRangeAliasAst);
        }
        AST countAst = HqlASTHelper.createAST(HqlTokenTypes.COUNT, "count",
                distinct && this.shouldUseDistinctCount()
                        ? HqlASTHelper.createAST(HqlTokenTypes.DISTINCT, "distinct")
                        : null,
                HqlASTHelper.createAST(HqlTokenTypes.IDENT, defaultRangeAliasAst.getText()));
        if (selectAst == null) {
            selectAst = HqlASTHelper.createAST(HqlTokenTypes.SELECT, "select", countAst);
            selectFromAst.addChild(selectAst);
        } else {
            selectAst.setFirstChild(countAst);
        }

        for (SubPlan subPlan : this.pathPlan.getSubPlans().values()) {
            AST startFromElementAst = this.startFromElementASTMap.get(subPlan.getAlias());
            //Must add join ASTs before unset fetch, because whether the original join ASTs
            //is with fetches is very important to find out how to add new join ASTs.
            for (JoinNode joinNode : subPlan.getJoinNode().getChildNodes().values()) {
                HqlASTHelper.addJoinAST(fromAst, startFromElementAst, joinNode, aliasGenerator, false, true,
                        distinct, null);
            }
        }

        //After join ASTs, unset fetch of all the joinAST
        for (AST fromElementAst = fromAst.getFirstChild(); fromElementAst != null; fromElementAst = fromElementAst
                .getNextSibling()) {
            if (fromElementAst.getType() == HqlTokenTypes.JOIN) {
                HqlASTHelper.unsetFetch(fromElementAst);
            }
        }
    }

    @SuppressWarnings("unchecked")
    protected void onProcessCountSqlAST(AST sqlAst, boolean distinct) {
        QueryNode queryNode = (QueryNode) sqlAst;
        Set<FromElement> usedFromElements = new HashSet<>(ReferenceEqualityComparator.getInstance());
        for (FromElement fromElement : (List<FromElement>) queryNode.getFromClause().getFromElements()) {
            if (fromElement.getClass() == FromElement.class && fromElement.getWithClauseFragment() == null) {
                if (distinct || fromElement.getQueryableCollection() == null) {
                    if (fromElement.getText().startsWith("left ")) {
                        continue;
                    } else if (fromElement.getRole() != null && fromElement.getQueryableCollection() == null
                            && SettingsFactory.isDbSchemaStrict(this.factory.getProperties())) {
                        String role = fromElement.getRole();
                        int lastDotIndex = role.lastIndexOf('.');
                        String entityName = role.substring(0, lastDotIndex);
                        String propertyName = role.substring(lastDotIndex + 1);
                        EntityMetamodel entityMetamodel = this.factory.getEntityPersister(entityName)
                                .getEntityMetamodel();
                        int propertyIndex = entityMetamodel.getPropertyIndex(propertyName);
                        if (entityMetamodel.getPropertyTypes()[propertyIndex] instanceof ManyToOneType) {
                            boolean nullable = entityMetamodel.getPropertyNullability()[propertyIndex];
                            if (!nullable) {
                                continue;
                            }
                        }
                    }
                }
            }
            SqlASTHelper.addFromElementAndancestors(usedFromElements, fromElement);
        }
        SqlASTHelper.removeFromElementsExcept(queryNode, usedFromElements);
    }

    private void initialize(AST ast) {
        if (this.pathPlan == null) {
            if (ast.getType() != HqlTokenTypes.QUERY) {
                if (this.pathPlanKey != null) {
                    throw new QueryException(LAZY_RESOURCE.get().queryPathsCanNotBeApplyToNonQuery(this.hql));
                }
            } else {
                this.pathPlan = new PathPlanFactoryImpl(this.factory, ast).create(this.pathPlanKey);
                if (HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.SELECT) == null
                        && this.pathPlan.containsNoFetchJoins()) {
                    throw new QueryException(LAZY_RESOURCE.get()
                            .hqlMustContainsSelectCaluseWhenThereIsNoFetchJoinInQueryPaths(this.hql));
                }
                this.startFromElementASTMap = this.createStartFromElementASTMap(ast);
            }
        }
    }

    private ExceptionCreator getUnlimitedCountExceptionCreator(AST ast) {
        final String hql = this.hql;
        AST groupAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.GROUP);
        if (groupAst != null) {
            return new ExceptionCreator() {
                @Override
                public RuntimeException create() {
                    return new QueryException(LAZY_RESOURCE.get().unlimitedCountIsUnsupportedBecauseOfGroupBy(hql));
                }
            };
        }
        AST selectFromAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.SELECT_FROM);
        AST selectAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.SELECT);
        if (selectAst != null) {
            AST selectionAst = selectAst.getFirstChild();
            if (selectionAst.getNextSibling() != null) {
                return new ExceptionCreator() {
                    @Override
                    public RuntimeException create() {
                        return new QueryException(
                                LAZY_RESOURCE.get().unlimitedCountIsUnsupportedBecauseOfTooManySelections(hql));
                    }
                };
            }
            boolean selectRootEntity = false;
            if (selectionAst.getType() == HqlTokenTypes.IDENT) {
                AST firstRangeAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.RANGE);
                String alias = HqlASTHelper.getAlias(firstRangeAst);
                selectRootEntity = selectionAst.getText().equals(alias);
            }
            if (!selectRootEntity) {
                return new ExceptionCreator() {
                    @Override
                    public RuntimeException create() {
                        return new QueryException(LAZY_RESOURCE.get()
                                .unlimitedCountIsUnsupportedBecauseSelectionIsNotRootEntity(hql));
                    }
                };
            }
        } else {
            boolean metRange = false;
            AST fromAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.FROM);
            for (AST fromElementAst = fromAst
                    .getFirstChild(); fromElementAst != null; fromElementAst = fromElementAst.getNextSibling()) {
                if (fromElementAst.getType() == HqlTokenTypes.RANGE) {
                    if (metRange) {
                        return new ExceptionCreator() {
                            @Override
                            public RuntimeException create() {
                                return new QueryException(LAZY_RESOURCE.get()
                                        .unlimitedCountIsUnsupportedBecauseOfTooManyRangeAndNoSelection(hql));
                            }
                        };
                    }
                    metRange = true;
                } else {
                    AST fetchAst = HqlASTHelper.findFirstChildInToppestQuery(fromElementAst, HqlTokenTypes.FETCH);
                    if (fetchAst == null) {
                        return new ExceptionCreator() {
                            @Override
                            public RuntimeException create() {
                                return new QueryException(LAZY_RESOURCE.get()
                                        .unlimitedCountIsUnsupportedBecauseOfNonFetchJoinsAndNoSelection(hql));
                            }
                        };
                    }
                }
            }
        }
        return null;
    }

    private void preApplyScalarEagerness(AST ast) {
        if (!this.pathPlan.containsScalarEagerness()) {
            return;
        }
        this.returnedEntityColumns = new LinkedHashMap<>();
        AST selectAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.SELECT);
        if (selectAst != null) {
            for (SubPlan subPlan : this.pathPlan.getSubPlans().values()) {
                if (subPlan.getJoinNode().containsScalarEagerness()) {
                    String subPlanAlias = subPlan.getAlias();
                    int column = -1;
                    int index = 0;
                    for (AST childAst = selectAst.getFirstChild(); childAst != null; childAst = childAst
                            .getNextSibling()) {
                        if (subPlanAlias == null) {
                            column = index;
                            break;
                        }
                        if (childAst.getType() == HqlTokenTypes.IDENT
                                && (subPlanAlias == null || childAst.getText().equals(subPlanAlias))) {
                            column = index;
                            break;
                        }
                        index++;
                    }
                    if (column == -1) {
                        throw new IllegalArgumentException();
                    }
                    this.returnedEntityColumns.put(subPlanAlias, column);
                }
            }
        } else {
            AST fromAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.FROM);
            XOrderedMap<String, Integer> allEntityMap = new LinkedHashMap<>();
            int index = 0;
            for (AST fromElementAst = fromAst
                    .getFirstChild(); fromElementAst != null; fromElementAst = fromElementAst.getNextSibling()) {
                int type = fromElementAst.getType();
                if (type == HqlTokenTypes.JOIN) {
                    if (HqlASTHelper.findFirstChildInToppestQuery(fromElementAst, HqlTokenTypes.FETCH) != null) {
                        index++;
                        continue;
                    }
                }
                String fromElementAlias = HqlASTHelper.getAlias(fromElementAst);
                if (!allEntityMap.containsKey(fromElementAlias)) {
                    allEntityMap.put(fromElementAlias, index);
                }
                index++;
            }
            for (SubPlan subPlan : this.pathPlan.getSubPlans().values()) {
                if (subPlan.getJoinNode().containsScalarEagerness()) {
                    String subPlanAlias = subPlan.getAlias();
                    Integer column;
                    if (subPlanAlias != null) {
                        column = allEntityMap.get(subPlanAlias);
                        if (column == null) {
                            throw new IllegalArgumentException();
                        }
                    } else {
                        column = allEntityMap.firstEntry().getValue();
                    }
                    this.returnedEntityColumns.put(subPlanAlias, column);
                }
            }
        }
    }

    private void applyScalarEagerness(List<?> results, SessionImplementor session) {
        if (this.returnedEntityColumns == null) {
            return;
        }
        ScalarEagerness scalarEagerness = new ScalarEagerness(session);
        for (Entry<String, Integer> entry : this.returnedEntityColumns.entrySet()) {
            for (Object result : results) {
                if (result == null) {
                    continue;
                }
                Object entity;
                if (result.getClass().isArray()) {
                    Object[] arr = (Object[]) result;
                    entity = arr[entry.getValue()];
                } else if (result instanceof Tuple) {
                    Tuple tuple = (Tuple) result;
                    entity = tuple.get(entry.getValue());
                } else {
                    if (entry.getValue() != 0) {
                        throw new AssertionError();
                    }
                    entity = result;
                }
                JoinNode joinNode = this.pathPlan.getSubPlans().get(entry.getKey()).getJoinNode();
                scalarEagerness.prepareApply(entity, joinNode);
            }
        }
        scalarEagerness.apply();
    }

    private Map<String, AST> createStartFromElementASTMap(AST ast) {
        AST selectFromAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.SELECT_FROM);
        AST fromAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.FROM);
        AST defaultRangeAst = HqlASTHelper.findFirstChildInToppestQuery(fromAst, HqlTokenTypes.RANGE);

        Map<String, AST> map = new HashMap<>();
        map.put(null, defaultRangeAst);

        for (SubPlan subPlan : this.pathPlan.getSubPlans().values()) {
            String alias = subPlan.getAlias();
            if (alias != null) {
                AST matchedFromElementAst = null;
                for (AST fromElementAst = fromAst
                        .getFirstChild(); fromElementAst != null; fromElementAst = fromElementAst
                                .getNextSibling()) {
                    AST feaAst = HqlASTHelper.findFirstChildInToppestQuery(fromElementAst, HqlTokenTypes.ALIAS);
                    String feaAlias = feaAst != null ? feaAst.getText() : null;
                    if (feaAlias.startsWith(Constants.NOT_SHARED_JOIN_ALIAS_PREFIX)) {
                        feaAlias = feaAlias.substring(Constants.NOT_SHARED_JOIN_ALIAS_PREFIX.length());
                    }
                    if (alias.equals(feaAlias)) {
                        matchedFromElementAst = fromElementAst;
                        break;
                    }
                }
                if (matchedFromElementAst == null) {
                    throw new IllegalArgumentException(LAZY_RESOURCE.get().illegalSubPathAlias(alias,
                            Constants.NOT_SHARED_JOIN_ALIAS_PREFIX + alias, hql));
                }
                map.put(alias, matchedFromElementAst);
            }
        }
        return map;
    }

    private static void logAST(AST ast, String message) {
        if (LOGGER.isDebugEnabled()) {
            ASTPrinter printer = new ASTPrinter(HqlTokenTypes.class);
            String astText = printer.showAsString(ast, message);
            LOGGER.debug(astText);
        }
    }

    private static class Enhancer extends ClassEnhancer {

        private static final Enhancer INSTANCE = getInstance(Enhancer.class);

        private int walkerVarInDoCompile;

        private Enhancer() {
            super(XQueryTranslatorImpl.class);
        }

        static Class<XQueryTranslatorImpl> getEhancedClass() {
            return INSTANCE.getResultClass();
        }

        @Override
        protected void doMethodFilter(MethodSource methodSource) {
            Method method = methodSource.getMethod();
            if (method.getDeclaringClass() == QueryTranslatorImpl.class && method.getName().equals("parse")
                    && method.getReturnType() == HqlParser.class) {
                InsnList instructions = methodSource.getInstructions();
                for (AbstractInsnNode abstractInsnNode = instructions
                        .getFirst(); abstractInsnNode != null; abstractInsnNode = abstractInsnNode.getNext()) {
                    if (abstractInsnNode.getOpcode() == Opcodes.ARETURN) {
                        InsnList tmpInstructions = new InsnList();
                        tmpInstructions.add(new InsnNode(Opcodes.DUP));
                        tmpInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                        tmpInstructions.add(new InsnNode(Opcodes.SWAP));
                        tmpInstructions
                                .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, ASM.getInternalName(HqlParser.class),
                                        "getAST", "()" + ASM.getDescriptor(AST.class), false));
                        tmpInstructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, this.getResultInternalName(),
                                "applyRootJoinNode", "(" + ASM.getDescriptor(AST.class) + ")V", false));
                        instructions.insertBefore(abstractInsnNode, tmpInstructions);
                    }
                }
            } else if (method.getDeclaringClass() == QueryTranslatorImpl.class
                    && method.getName().equals("doCompile") && Arrays.equals(method.getParameterTypes(),
                            new Class[] { Map.class, boolean.class, String.class })) {
                InsnList instructions = methodSource.getInstructions();
                for (AbstractInsnNode abstractInsnNode = instructions
                        .getFirst(); abstractInsnNode != null; abstractInsnNode = abstractInsnNode.getNext()) {
                    if (abstractInsnNode.getOpcode() == Opcodes.PUTFIELD) {
                        FieldInsnNode fieldInsnNode = (FieldInsnNode) abstractInsnNode;
                        if (fieldInsnNode.name.equals("queryLoader")
                                && fieldInsnNode.owner.equals(ASM.getInternalName(QueryTranslatorImpl.class))) {

                            InsnList beforeInstructions = new InsnList();
                            beforeInstructions.add(new InsnNode(Opcodes.DUP));
                            beforeInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            beforeInstructions.add(new InsnNode(Opcodes.SWAP));
                            beforeInstructions.add(new FieldInsnNode(Opcodes.PUTFIELD,
                                    ASM.getInternalName(XQueryTranslatorImpl.class), "queryLoader",
                                    ASM.getDescriptor(QueryLoader.class)));
                            instructions.insertBefore(fieldInsnNode, beforeInstructions);

                            InsnList afterInstructions = new InsnList();
                            /*
                             * if (this.queryType == QUERY) {
                             */
                            LabelNode isNotQueryLabelNode = new LabelNode();
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            afterInstructions.add(new FieldInsnNode(Opcodes.GETFIELD,
                                    ASM.getInternalName(XQueryTranslatorImpl.class), "queryType", "I"));
                            afterInstructions.add(new LdcInsnNode(HqlTokenTypes.QUERY));
                            afterInstructions.add(new JumpInsnNode(Opcodes.IF_ICMPNE, isNotQueryLabelNode));

                            /*
                             * this.compileForCount();
                             */
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 3));
                            afterInstructions
                                    .add(new MethodInsnNode(Opcodes.INVOKESPECIAL, this.getResultInternalName(),
                                            "compileForCount", "(Ljava/lang/String;)V", false));

                            /*
                             * this.compileForDistinctCount();
                             */
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 3));
                            afterInstructions
                                    .add(new MethodInsnNode(Opcodes.INVOKESPECIAL, this.getResultInternalName(),
                                            "compileForDistinctCount", "(Ljava/lang/String;)V", false));

                            /*
                             * if (this.shouldUseDistinctQuery() && this.factory.getDialect() instanceof DistinctLimitDialect) {
                             *      this.distinctQueryLoader = new DistinctLimitQueryLoader(this, this.factory, walker.getSelectClause());
                             * }
                             */
                            LabelNode endIfNode = new LabelNode(new Label());
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            afterInstructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                    this.getResultInternalName(), "shouldUseDistinctQuery", "()Z", false));
                            afterInstructions.add(new JumpInsnNode(Opcodes.IFEQ, endIfNode));
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            afterInstructions.add(new FieldInsnNode(Opcodes.GETFIELD,
                                    ASM.getInternalName(XQueryTranslatorImpl.class), "factory",
                                    ASM.getDescriptor(SessionFactoryImplementor.class)));
                            afterInstructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                    ASM.getInternalName(SessionFactoryImplementor.class), "getDialect",
                                    "()" + ASM.getDescriptor(Dialect.class), true));
                            afterInstructions.add(new TypeInsnNode(Opcodes.INSTANCEOF,
                                    ASM.getInternalName(DistinctLimitDialect.class)));
                            afterInstructions.add(new JumpInsnNode(Opcodes.IFEQ, endIfNode));
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            afterInstructions.add(new TypeInsnNode(Opcodes.NEW,
                                    ASM.getInternalName(DistinctLimitQueryLoader.class)));
                            afterInstructions.add(new InsnNode(Opcodes.DUP));
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                            afterInstructions.add(new FieldInsnNode(Opcodes.GETFIELD, this.getResultInternalName(),
                                    "factory", ASM.getDescriptor(SessionFactoryImplementor.class)));
                            if (this.walkerVarInDoCompile == 0) {
                                throw new AssertionError();
                            }
                            afterInstructions.add(new VarInsnNode(Opcodes.ALOAD, this.walkerVarInDoCompile));
                            afterInstructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                    ASM.getInternalName(HqlSqlWalker.class), "getSelectClause",
                                    "()" + ASM.getDescriptor(SelectClause.class), false));
                            afterInstructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                                    ASM.getInternalName(DistinctLimitQueryLoader.class), "<init>",
                                    "(" + ASM.getDescriptor(XQueryTranslatorImpl.class)
                                            + ASM.getDescriptor(SessionFactoryImplementor.class)
                                            + ASM.getDescriptor(SelectClause.class) + ")V",
                                    false));
                            afterInstructions.add(new FieldInsnNode(Opcodes.PUTFIELD, this.getResultInternalName(),
                                    "distinctLimitQueryLoader", ASM.getDescriptor(DistinctLimitQueryLoader.class)));
                            afterInstructions.add(endIfNode);

                            /*
                             * } //end if (this.queryType == QUERY)
                             */
                            afterInstructions.add(isNotQueryLabelNode);

                            instructions.insert(abstractInsnNode, afterInstructions);
                        }
                    } else if (abstractInsnNode.getOpcode() == Opcodes.ASTORE
                            && abstractInsnNode.getPrevious() instanceof MethodInsnNode) {
                        MethodInsnNode prevMethodInsnNode = (MethodInsnNode) abstractInsnNode.getPrevious();
                        if (prevMethodInsnNode.name.equals("analyze") && prevMethodInsnNode.owner
                                .equals(ASM.getInternalName(QueryTranslatorImpl.class))) {
                            this.walkerVarInDoCompile = ((VarInsnNode) abstractInsnNode).var;
                        }
                    }
                }
            }
        }

        @Override
        protected Collection<MethodNode> getMoreMethodNodes(MethodSourceFactory methodSourceFactory) {
            List<MethodNode> methodNodes = new ArrayList<MethodNode>();
            methodNodes.add(this.createCompileForCount(false));
            methodNodes.add(this.createCompileForCount(true));
            methodNodes.add(this.createParseForCount(false, methodSourceFactory));
            methodNodes.add(this.createParseForCount(true, methodSourceFactory));
            return methodNodes;
        }

        private MethodNode createCompileForCount(boolean distinct) {

            /*
             * private void compileForCount(String collectionRole) {
             *      HqlSqlWalker walker = super.analyze(this.parseForCount(true), collectionRole);
             *      this.countSqlAst = walker.getAST();
             *      SqlGenerator sqlGenerator = new SqlGenerator(this.factory);
             *      sqlGenerator.statement(this.countSqlAst);
             *      this.countSql = sqlGenerator.getSQL();
             *      this.countLoader = new UnlimitedCountLoader(
             *          this,
             *          this.factory,
             *          waler.getSelectClause(),
             *          false
             *      );
             * }
             * 
             * private void compileForDistinctCount(String role) {
             *      if (!this.shouldUseDistinctQuery()) {
             *          return;
             *      }
             *      HqlSqlWalker walker = super.analyze(this.parseForDistinctCount(true), role);
             *      this.distinctCountSqlAst = walker.getAST();
             *      SqlGenerator sqlGenerator = new SqlGenerator(this.factory);
             *      sqlGenerator.statement(this.distinctCountSqlAst);
             *      this.distinctCountSql = sqlGenerator.getSQL();
             *      this.distinctCountLoader = new UnlimitedCountLoader(
             *          this,
             *          this.factory,
             *          walker.getSelectClause(),
             *          true
             *      );
             * }
             */
            MethodNode methodNode = createMethodNode(Opcodes.ACC_PRIVATE,
                    distinct ? "compileForDistinctCount" : "compileForCount", "(Ljava/lang/String;)V", null);

            InsnList instructions = new InsnList();
            final int walkerIndex = 2;
            final int sqlAstIndex = walkerIndex + 1;
            final int sqlGeneratorIndex = sqlAstIndex + 1;

            if (distinct) {
                LabelNode useDistinctLabelNode = new LabelNode();
                instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, this.getResultInternalName(),
                        "shouldUseDistinctQuery", "()Z", false));
                instructions.add(new JumpInsnNode(Opcodes.IFNE, useDistinctLabelNode));
                instructions.add(new InsnNode(Opcodes.RETURN));
                instructions.add(useDistinctLabelNode);
            }
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new LdcInsnNode(true));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, this.getResultInternalName(),
                    distinct ? "parseForDistinctCount" : "parseForCount",
                    "(Z)" + ASM.getDescriptor(HqlParser.class), false));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 1));
            instructions
                    .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, ASM.getInternalName(QueryTranslatorImpl.class),
                            "analyze", "(" + ASM.getDescriptor(HqlParser.class) + "Ljava/lang/String;)"
                                    + ASM.getDescriptor(HqlSqlWalker.class),
                            false));
            instructions.add(new VarInsnNode(Opcodes.ASTORE, walkerIndex));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, walkerIndex));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, ASM.getInternalName(HqlSqlWalker.class),
                    "getAST", "()" + ASM.getDescriptor(AST.class), false));
            instructions.add(new VarInsnNode(Opcodes.ASTORE, sqlAstIndex));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, sqlAstIndex));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, this.getResultInternalName(),
                    distinct ? "processDistinctCountSqlAst" : "processCountSqlAst",
                    "(" + ASM.getDescriptor(AST.class) + ")V", false));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, sqlAstIndex));
            instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, this.getResultInternalName(),
                    distinct ? "distinctCountSqlAst" : "countSqlAst", ASM.getDescriptor(AST.class)));

            instructions.add(new TypeInsnNode(Opcodes.NEW, ASM.getInternalName(SqlGenerator.class)));
            instructions.add(new InsnNode(Opcodes.DUP));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new FieldInsnNode(Opcodes.GETFIELD, this.getResultInternalName(), "factory",
                    ASM.getDescriptor(SessionFactoryImplementor.class)));
            instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, ASM.getInternalName(SqlGenerator.class),
                    "<init>", "(" + ASM.getDescriptor(SessionFactoryImplementor.class) + ")V", false));
            instructions.add(new VarInsnNode(Opcodes.ASTORE, sqlGeneratorIndex));

            instructions.add(new VarInsnNode(Opcodes.ALOAD, sqlGeneratorIndex));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new FieldInsnNode(Opcodes.GETFIELD, this.getResultInternalName(),
                    distinct ? "distinctCountSqlAst" : "countSqlAst", ASM.getDescriptor(AST.class)));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, ASM.getInternalName(SqlGenerator.class),
                    "statement", "(" + ASM.getDescriptor(AST.class) + ")V", false));

            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, sqlGeneratorIndex));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, ASM.getInternalName(SqlGenerator.class),
                    "getSQL", "()Ljava/lang/String;", false));
            instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, this.getResultInternalName(),
                    distinct ? "distinctCountSql" : "countSql", "Ljava/lang/String;"));

            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new TypeInsnNode(Opcodes.NEW, ASM.getInternalName(UnlimitedCountLoader.class)));
            instructions.add(new InsnNode(Opcodes.DUP));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
            instructions.add(new FieldInsnNode(Opcodes.GETFIELD, this.getResultInternalName(), "factory",
                    ASM.getDescriptor(SessionFactoryImplementor.class)));
            instructions.add(new VarInsnNode(Opcodes.ALOAD, walkerIndex));
            instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, ASM.getInternalName(HqlSqlWalker.class),
                    "getSelectClause", "()" + ASM.getDescriptor(SelectClause.class), false));
            instructions.add(new InsnNode(distinct ? Opcodes.ICONST_1 : Opcodes.ICONST_0));
            instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
                    ASM.getInternalName(UnlimitedCountLoader.class), "<init>",
                    "(" + ASM.getDescriptor(XQueryTranslatorImpl.class)
                            + ASM.getDescriptor(SessionFactoryImplementor.class)
                            + ASM.getDescriptor(SelectClause.class) + "Z)V",
                    false));
            instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, this.getResultInternalName(),
                    distinct ? "distinctCountLoader" : "countLoader",
                    ASM.getDescriptor(UnlimitedCountLoader.class)));

            instructions.add(new InsnNode(Opcodes.RETURN));

            methodNode.instructions = instructions;
            return methodNode;
        }

        private MethodNode createParseForCount(boolean distinct, MethodSourceFactory methodSourceFactory) {
            MethodNode methodNode = createMethodNode(Opcodes.ACC_PRIVATE,
                    distinct ? "parseForDistinctCount" : "parseForCount",
                    "(Z)" + ASM.getDescriptor(HqlParser.class), null);
            MethodSource parseSource = methodSourceFactory.getMethodSource(PARSE_METHOD);
            methodNode.tryCatchBlocks = cloneTryCatchBlocks(parseSource.getOldTryCatchBlocks());
            InsnList instructions = cloneInsnList(parseSource.getOldInstructions());
            for (AbstractInsnNode abstractInsnNode = instructions
                    .getFirst(); abstractInsnNode != null; abstractInsnNode = abstractInsnNode.getNext()) {
                if (abstractInsnNode.getOpcode() == Opcodes.ARETURN) {
                    InsnList tmpInstructions = new InsnList();
                    tmpInstructions.add(new InsnNode(Opcodes.DUP));
                    tmpInstructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
                    tmpInstructions.add(new InsnNode(Opcodes.SWAP));
                    tmpInstructions
                            .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, ASM.getInternalName(HqlParser.class),
                                    "getAST", "()" + ASM.getDescriptor(AST.class), false));
                    tmpInstructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, this.getResultInternalName(),
                            distinct ? "applyRootJoinNodeForDistinctCount" : "applyRootJoinNodeForCount",
                            "(" + ASM.getDescriptor(AST.class) + ")V", false));
                    instructions.insertBefore(abstractInsnNode, tmpInstructions);
                }
            }
            methodNode.instructions = instructions;
            return methodNode;
        }
    }

    private static class PathPlanFactoryImpl extends AbstractHibernatePathPlanFactory {

        private Map<String, EntityPersister> entityPersisters;

        @SuppressWarnings({ "unchecked", "rawtypes" })
        public PathPlanFactoryImpl(SessionFactoryImplementor sfi, AST ast) {
            Map<String, Object> map = new HashMap<>();
            AST selectFromAst = HqlASTHelper.findFirstChildInToppestQuery(ast, HqlTokenTypes.SELECT_FROM);
            AST fromAst = HqlASTHelper.findFirstChildInToppestQuery(selectFromAst, HqlTokenTypes.FROM);
            boolean metFirstRange = false;
            for (AST fromElementAst = fromAst
                    .getFirstChild(); fromElementAst != null; fromElementAst = fromElementAst.getNextSibling()) {
                if (fromElementAst.getType() == HqlTokenTypes.RANGE) {
                    AST aliasAst = HqlASTHelper.findFirstChildInToppestQuery(fromElementAst, HqlTokenTypes.ALIAS);
                    if (!metFirstRange || aliasAst != null) {
                        String alias = aliasAst != null ? aliasAst.getText() : null;
                        String entityName = HqlASTHelper.getComplexIdentifier(fromElementAst.getFirstChild());
                        EntityPersister entityPersister = sfi.getEntityPersister(entityName);
                        if (alias != null) {
                            map.put(alias, entityPersister);
                        }
                        if (!metFirstRange) {
                            metFirstRange = true;
                            map.put(null, entityPersister);
                        }
                    }
                }
                if (fromElementAst.getType() == HqlTokenTypes.JOIN) {
                    AST aliasAst = HqlASTHelper.findFirstChildInToppestQuery(fromElementAst, HqlTokenTypes.ALIAS);
                    if (aliasAst != null) {
                        AST dotAST = HqlASTHelper.findFirstChildInToppestQuery(fromElementAst, HqlTokenTypes.DOT);
                        if (dotAST == null) {
                            throw new AssertionError();
                        }
                        AST parentAliasAst = dotAST.getFirstChild();
                        AST associationNameAst = parentAliasAst.getNextSibling();
                        JoinSource joinSource = new JoinSource(parentAliasAst.getText(),
                                associationNameAst.getText());
                        map.put(aliasAst.getText(), joinSource);
                    }
                }
            }
            while (true) {
                boolean continue_ = false;
                for (Entry<String, Object> e1 : map.entrySet()) {
                    if (e1.getValue() instanceof EntityPersister) {
                        EntityPersister parentEntityPersister = (EntityPersister) e1.getValue();
                        for (Entry<String, Object> e2 : map.entrySet()) {
                            if (e2.getValue() instanceof JoinSource) {
                                JoinSource joinSource = (JoinSource) e2.getValue();
                                if (joinSource.parentAlias.equals(e1.getKey())) {
                                    String entityName = ((AssociationType) parentEntityPersister
                                            .getPropertyType(joinSource.assocationName))
                                                    .getAssociatedEntityName(sfi);
                                    EntityPersister entityPersister = sfi.getEntityPersister(entityName);
                                    e2.setValue(entityPersister);
                                    continue_ = true;
                                    break;
                                }
                            }
                        }
                    }
                }
                if (!continue_) {
                    break;
                }
            }
            this.entityPersisters = (Map) map;
        }

        @Override
        protected EntityPersister getEntityPersister(String alias) {
            return this.entityPersisters.get(alias);
        }

        private static class JoinSource {

            String parentAlias;

            String assocationName;

            public JoinSource(String parentAlias, String assocationName) {
                this.parentAlias = parentAlias;
                this.assocationName = assocationName;
            }
        }
    }

    private static class ScalarEagerness {

        private SessionImplementor session;

        private Map<Class<?>, ClassMetadata> classMetadatas = new HashMap<>();

        private ScalarBatchLoader scalarBatchLoader = new ScalarBatchLoader();

        ScalarEagerness(SessionImplementor session) {
            this.session = session;
        }

        @SuppressWarnings("unchecked")
        void prepareApply(Object entity, JoinNode joinNode) {

            if (entity == null || !joinNode.containsScalarEagerness()) {
                return;
            }

            ClassMetadata classMetadata = this.getClassMetadata(entity.getClass());
            EntityPersister entityPersister = this.session.getEntityPersister(classMetadata.getEntityName(),
                    entity);

            XOrderedSet<String> loadedScalarNames = joinNode.getLoadedScalarNames();
            String[] names = entityPersister.getPropertyNames();
            Type[] types = entityPersister.getPropertyTypes();
            boolean[] laziness = entityPersister.getPropertyLaziness();
            XOrderedSet<String> requiredPropertyNames = new LinkedHashSet<>((loadedScalarNames.size() * 4 + 2) / 3);
            for (int i = names.length - 1; i >= 0; i--) {
                if (!types[i].isAssociationType()) {
                    if (laziness[i] && loadedScalarNames.contains(names[i])) {
                        requiredPropertyNames.add(names[i]);
                    }
                }
            }
            if (!requiredPropertyNames.isEmpty()) {
                this.parepareLoadScalars(entity, entityPersister, requiredPropertyNames);
            }

            for (int i = names.length - 1; i >= 0; i--) {
                if (types[i].isAssociationType()) {
                    JoinNode childJoinNode = joinNode.getChildNodes().get(names[i]);
                    if (childJoinNode != null && childJoinNode.isFetch()) {
                        Object value = entityPersister.getPropertyValue(entity, i);
                        if (value != null) {
                            if (value.getClass().isArray()) {
                                Object[] arr = (Object[]) value;
                                for (Object childEntity : arr) {
                                    this.prepareApply(childEntity, childJoinNode);
                                }
                            } else if (value instanceof Collection<?>) {
                                Collection<Object> c = (Collection<Object>) value;
                                for (Object childEntity : c) {
                                    this.prepareApply(childEntity, childJoinNode);
                                }
                            } else if (value instanceof Map<?, ?>) {
                                Map<?, Object> m = (Map<?, Object>) value;
                                for (Object childEntity : m.values()) {
                                    this.prepareApply(childEntity, childJoinNode);
                                }
                            } else {
                                this.prepareApply(value, childJoinNode);
                            }
                        }
                    }
                }
            }
        }

        void apply() {
            this.scalarBatchLoader.flush();
        }

        private void parepareLoadScalars(Object entity, EntityPersister entityPersister,
                Set<String> propertyNames) {
            FieldInterceptor fieldInterceptor = FieldInterceptionHelper.extractFieldInterceptor(entity);
            if (fieldInterceptor instanceof HibernateObjectModelScalarLoader) {
                HibernateObjectModelScalarLoader hibernateObjectModelScalarLoader = (HibernateObjectModelScalarLoader) fieldInterceptor;
                ObjectModel objectModel = hibernateObjectModelScalarLoader.getObjectModel();
                JPAObjectModelMetadata jpaMetadata = objectModel.getObjectModelMetadata();
                int[] scalarPropertyIds = new int[propertyNames.size()];
                int index = 0;
                for (String propertyName : propertyNames) {
                    scalarPropertyIds[index++] = jpaMetadata.getMappingSources().get(propertyName).getId();
                }
                this.scalarBatchLoader.prepareLoad(objectModel, scalarPropertyIds);
            } else {
                throw new IllegalProgramException(LAZY_RESOURCE.get()
                        .scalarFetchCanOnlyBeAppliedOnJAPObjectModel(entityPersister.getEntityName()));
            }
        }

        private ClassMetadata getClassMetadata(Class<?> clazz) {
            ClassMetadata classMetadata = this.classMetadatas.get(clazz);
            if (classMetadata == null) {
                if (clazz == Object.class) {
                    throw new AssertionError();
                }
                SessionFactoryImplementor factory = session.getFactory();
                classMetadata = factory.getClassMetadata(clazz);
                if (classMetadata == null) {
                    classMetadata = this.getClassMetadata(clazz.getSuperclass());
                }
                this.classMetadatas.put(clazz, classMetadata);
            }
            return classMetadata;
        }
    }

    static {

        try {
            /*
             * PARSE_METHOD must be initialized at first,
             * because Ehancer.<clinit> depends on it.
             */
            PARSE_METHOD = QueryTranslatorImpl.class.getDeclaredMethod("parse", boolean.class);
        } catch (NoSuchMethodException ex) {
            throw new AssertionError("No method \"parse(boolean)\"");
        }

        Constructor<XQueryTranslatorImpl> constructor;
        try {
            constructor = Enhancer.getEhancedClass().getDeclaredConstructor(String.class, String.class,
                    PathPlanKey.class, Map.class, SessionFactoryImplementor.class, EntityGraphQueryHint.class);
        } catch (NoSuchMethodException ex) {
            throw new AssertionError("No constructor");
        }
        constructor.setAccessible(true);
        CONSTRUCTOR = constructor;
    }

    private interface ExceptionCreator {
        RuntimeException create();
    };

    private interface Resource {

        String queryPathsCanNotBeApplyToNonQuery(String hql);

        String hqlMustContainsSelectCaluseWhenThereIsNoFetchJoinInQueryPaths(String hql);

        String operationRequiresQuery(String operataion);

        String illegalSubPathAlias(String alias, String notSharedAlias, String hql);

        String hibernateLimitInMemoryForCollectionFetchIsNotEnabled(
                Class<DistinctLimitDialect> distinctLimitDialectType,
                Class<? extends DistinctLimitDialect> exampleDistinctLimitDialectType,
                String configurationPropertyName);

        String scalarFetchCanOnlyBeAppliedOnJAPObjectModel(String entityName);

        String unlimitedCountIsUnsupportedBecauseOfGroupBy(String hql);

        String unlimitedCountIsUnsupportedBecauseOfTooManySelections(String hql);

        String unlimitedCountIsUnsupportedBecauseSelectionIsNotRootEntity(String hql);

        String unlimitedCountIsUnsupportedBecauseOfTooManyRangeAndNoSelection(String hql);

        String unlimitedCountIsUnsupportedBecauseOfNonFetchJoinsAndNoSelection(String hql);
    }
}