/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2001-2005 Julian Hyde
// Copyright (C) 2005-2015 Pentaho and others
// All Rights Reserved.
//
// jhyde, 30 August, 2001
*/
package mondrian.rolap;

import mondrian.olap.*;
import mondrian.olap.fun.VisualTotalsFunDef.VisualTotalMember;
import mondrian.resource.MondrianResource;
import mondrian.rolap.agg.*;

import org.apache.commons.collections.*;

import java.util.*;

/**
 * <code>RolapAggregationManager</code> manages all
 * {@link mondrian.rolap.agg.Segment}s in the system.
 *
 * <p> The bits of the implementation which depend upon dimensional concepts
 * <code>RolapMember</code>, etc.) live in this class, and the other bits live
 * in the derived class, {@link mondrian.rolap.agg.AggregationManager}.
 *
 * @author jhyde
 * @since 30 August, 2001
 */
public abstract class RolapAggregationManager {

    /**
     * Creates the RolapAggregationManager.
     */
    protected RolapAggregationManager() {
    }

    /**
     * Creates a request to evaluate the cell identified by
     * <code>members</code>.
     *
     * <p>If any of the members is the null member, returns
     * null, since there is no cell. If the measure is calculated, returns
     * null.
     *
     * @param members Set of members which constrain the cell
     * @return Cell request, or null if the requst is unsatisfiable
     */
    public static CellRequest makeRequest(final Member[] members) {
        return makeCellRequest(
            members, false, false, null, null, null,
            Collections.<OlapElement>emptyList());
    }

    /**
     * Creates a request for the fact-table rows underlying the cell identified
     * by <code>members</code>.
     *
     * <p>If any of the members is the null member, returns null, since there
     * is no cell. If the measure is calculated, returns null.
     *
     * @param members           Set of members which constrain the cell
     *
     * @param extendedContext   If true, add non-constraining columns to the
     *                          query for levels below each current member.
     *                          This additional context makes the drill-through
     *                          queries easier for humans to understand.
     *
     * @param cube              Cube
     * @return Cell request, or null if the requst is unsatisfiable
     */
    public static DrillThroughCellRequest makeDrillThroughRequest(
        final Member[] members,
        final boolean extendedContext,
        RolapCube cube,
        List<OlapElement> returnClauseMembers)
    {
        assert cube != null;

        List<OlapElement> applicableMembers = getApplicableReturnClauseMembers(
            cube, members, returnClauseMembers, extendedContext);
        List<OlapElement> nonApplicableMembers =
            new ArrayList<OlapElement>(
                (List<OlapElement>)CollectionUtils
                    .subtract(returnClauseMembers, applicableMembers));

        return (DrillThroughCellRequest) makeCellRequest(
            members, true, extendedContext, cube, null, applicableMembers,
            nonApplicableMembers);
    }

    /**
     * Returns a List of OlapElement consisting of elements present on
     * the base cube in context.  If drillthrough is happening on a virtual
     * cube this may exclude non-conforming dimensions, for example.
     */
    private static List<OlapElement> getApplicableReturnClauseMembers(
        RolapCube cube, Member[] members,
        List<OlapElement> returnClauseMembers, boolean extendedContext)
    {
        if (!extendedContext || returnClauseMembers == null) {
            return Collections.emptyList();
        }
        List<OlapElement> applicableReturnClauseMembers =
            new ArrayList<OlapElement>();
        final RolapCube drillthroughCube = getDrillthroughCube(cube, members);
        final Map<String, OlapElement> measureUniqueNameMap =
            getMeasureUniqueNameMap(drillthroughCube);

        for (OlapElement olapElement : returnClauseMembers) {
            if (isClosureFor(olapElement)) {
                continue;
            }
            if (hierarchyPresentOnCube(drillthroughCube, olapElement)) {
                applicableReturnClauseMembers.add(olapElement);
            } else if (measureUniqueNameMap
                .containsKey(olapElement.getUniqueName()))
            {
                // add the drillthrough cubes measure, not the one
                // potentially from a virtual cube
                applicableReturnClauseMembers.add(
                    measureUniqueNameMap.get(olapElement.getUniqueName()));
            }
        }
        return applicableReturnClauseMembers;
    }

    private static Map<String, OlapElement> getMeasureUniqueNameMap(
        RolapCube drillthroughCube)
    {
        final Map<String, OlapElement> measureUniqueNameMap =
            new HashMap<String, OlapElement>();
        for (Member measureMember : drillthroughCube.getMeasures())  {
            measureUniqueNameMap
                .put(measureMember.getUniqueName(), measureMember);
        }
        return measureUniqueNameMap;
    }

    private static boolean hierarchyPresentOnCube(
        RolapCube cube, OlapElement olapElement)
    {
        return cube.getHierarchies().contains(olapElement.getHierarchy());
    }

    private static RolapCube getDrillthroughCube(
        RolapCube cube, Member[] members)
    {
        RolapCube drillthroughCube =
            RolapCell.chooseDrillThroughCube(members, cube);
        if (drillthroughCube == null) {
            drillthroughCube = cube;
        }
        return drillthroughCube;
    }

    private static boolean isClosureFor(OlapElement olapElement) {
        return olapElement.getHierarchy() instanceof RolapCubeHierarchy
            && ((RolapCubeHierarchy)olapElement.getHierarchy())
            .getRolapHierarchy().closureFor != null;
    }

    /**
     * Creates a request to evaluate the cell identified by the context
     * specified in <code>evaluator</code>.
     *
     * <p>If any of the members from the context is the null member, returns
     * null, since there is no cell. If the measure is calculated, returns
     * null.
     *
     * @param evaluator the cell specified by the evaluator context
     * @return Cell request, or null if the requst is unsatisfiable
     */
    public static CellRequest makeRequest(
        RolapEvaluator evaluator)
    {
        final CellRequest request =
            makeCellRequest(
                evaluator.getNonAllMembers(),
                false,
                false,
                null,
                evaluator, null,
                Collections.<OlapElement>emptyList());
        if (request == null) {
            // Current request cannot be processed. Per API, return null.
            return null;
        }
        if (CollectionUtils.isEmpty(evaluator.getAggregationLists())) {
            return request;
        }
        // convert aggregation lists to compound predicates
        if (!applyCompoundPredicates(evaluator, request)) {
            // unsatisfiable
            return null;
        }
        return request;
    }

    /**
     * Retrieves CompoundPredicateInfo for each tuple in the evaluator's
     * aggregationLists.  Uses this to add the predicate (and its string
     * representation) to the cell request.
     * Returns false if any predicate results in an unsatisfiable request.
     */
    private static boolean applyCompoundPredicates(
        RolapEvaluator evaluator, CellRequest request)
    {
        final Member[] currentMembers = evaluator.getNonAllMembers();
        final RolapStoredMeasure measure =
            (RolapStoredMeasure)currentMembers[0];
        for (List<List<Member>> aggregationList
            : evaluator.getAggregationLists())
        {
            CompoundPredicateInfo predicateInfo =
                    getCompoundPredicateInfo(
                        evaluator, measure, aggregationList);
            if (!predicateInfo.isSatisfiable()) {
                return false;
            }
            if (predicateInfo.getPredicate() != null) {
                request.addAggregateList(
                    predicateInfo.getBitKey(), predicateInfo.getPredicate());
                request.addPredicateString(predicateInfo.getPredicateString());
            }
        }
        return true;
    }

    /**
     * Retrieves CompoundPredicateInfo for the aggregationList.
     * If the aggregationList is the slicer, will attempt to retrieve the
     * pre-built slicer predicate info from the evaluator.  This avoids the
     * overhead of building the slicer predicates for every cell request.
     */
    private static CompoundPredicateInfo getCompoundPredicateInfo(
        RolapEvaluator evaluator,
        RolapStoredMeasure measure,
        List<List<Member>> aggregationList)
    {
        CompoundPredicateInfo predicateInfo;
        if (aggregationList.equals(evaluator.getSlicerTuples())) {
            // slicer predicate is built once in the evaluator to
            // avoid unnecessary duplicate effort
            predicateInfo = evaluator.getSlicerPredicateInfo();
            if (!measure.getCube().equals(predicateInfo.getCube())) {
                predicateInfo = new CompoundPredicateInfo(
                    aggregationList, measure);
                evaluator.slicerPredicateInfo = predicateInfo;
            }
        } else {
            predicateInfo =  new CompoundPredicateInfo(
                aggregationList, measure);
        }
        return predicateInfo;
    }

    private static CellRequest makeCellRequest(
        final Member[] members,
        boolean drillThrough,
        final boolean extendedContext,
        RolapCube cube,
        Evaluator evaluator, List<OlapElement> fieldsList,
        List<OlapElement> nonApplicableFields)
    {
        // Need cube for drill-through requests
        assert drillThrough == (cube != null);

        if (extendedContext) {
            assert (drillThrough);
        }

        final RolapStoredMeasure measure;
        if (drillThrough) {
            cube = RolapCell.chooseDrillThroughCube(members, cube);
            if (cube == null) {
                return null;
            }
            if (members.length > 0
                && members[0] instanceof RolapStoredMeasure)
            {
                measure = (RolapStoredMeasure) members[0];
            } else {
                measure = (RolapStoredMeasure) cube.getMeasures().get(0);
            }
        } else {
            if (members.length > 0
                && members[0] instanceof RolapStoredMeasure)
            {
                measure = (RolapStoredMeasure) members[0];
            } else {
                return null;
            }
        }

        final RolapStar.Measure starMeasure =
            (RolapStar.Measure) measure.getStarMeasure();
        assert starMeasure != null;
        final CellRequest request;
        if (drillThrough) {
            request =
                new DrillThroughCellRequest(
                    starMeasure, extendedContext, nonApplicableFields);
        } else {
            request =
                new CellRequest(starMeasure, extendedContext, drillThrough);
        }

        // Since 'request.extendedContext == false' is a well-worn code path,
        // we have moved the test outside the loop.
        if (extendedContext) {
            if (fieldsList != null) {
                // If a field list was specified, there will be some columns
                // to include in the result set, other that we don't. This
                // happens when the MDX is a DRILLTHROUGH operation and
                // includes a RETURN clause.
                for (OlapElement member : fieldsList) {
                    addNonConstrainingColumns(member, cube, request);
                }
            }
            for (int i = 1; i < members.length; i++) {
                final RolapCubeMember member = (RolapCubeMember) members[i];
                if (member.getHierarchy().getRolapHierarchy().closureFor
                    != null)
                {
                    continue;
                }

                addNonConstrainingColumns(member, cube, request);

                final RolapCubeLevel level = member.getLevel();
                final boolean needToReturnNull =
                    level.getLevelReader().constrainRequest(
                        member, measure.getCube(), request);
                if (needToReturnNull) {
                    return null;
                }
            }

        } else {
            for (int i = 1; i < members.length; i++) {
                if (!(members[i] instanceof RolapCubeMember)) {
                    continue;
                }
                RolapCubeMember member = (RolapCubeMember) members[i];
                final RolapCubeLevel level = member.getLevel();
                final boolean needToReturnNull =
                    level.getLevelReader().constrainRequest(
                        member, measure.getCube(), request);
                if (needToReturnNull) {
                    // check to see if the current member is part of an ignored
                    // unrelated dimension
                    if (evaluator == null
                        || !evaluator.mightReturnNullForUnrelatedDimension()
                        || evaluator.needToReturnNullForUnrelatedDimension(
                            new Member[] {member})) {
                        return null;
                    }
                }
            }
        }
        return request;
    }

    /**
     * Adds the key columns as non-constraining columns. For
     * example, if they asked for [Gender].[M], [Store].[USA].[CA]
     * then the following levels are in play:<ul>
     *   <li>Gender = 'M'
     *   <li>Marital Status not constraining
     *   <li>Nation = 'USA'
     *   <li>State = 'CA'
     *   <li>City not constraining
     * </ul>
     *
     * <p>Note that [Marital Status] column is present by virtue of
     * the implicit [Marital Status].[All] member. Hence the SQL
     *
     *   <blockquote><pre>
     *   select [Marital Status], [City]
     *   from [Star]
     *   where [Gender] = 'M'
     *   and [Nation] = 'USA'
     *   and [State] = 'CA'
     *   </pre></blockquote>
     *
     * @param member Member to constraint
     * @param baseCube base cube if virtual
     * @param request Cell request
     */
    private static void addNonConstrainingColumns(
        final RolapCubeMember member,
        final RolapCube baseCube,
        final CellRequest request)
    {
        final RolapCubeHierarchy hierarchy = member.getHierarchy();
        final RolapCubeLevel[] levels = hierarchy.getLevels();
        for (int j = levels.length - 1, depth = member.getLevel().getDepth();
             j > depth; j--)
        {
            final RolapCubeLevel level = levels[j];
            RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
            if (column != null) {
                request.addConstrainedColumn(column, null);
                if (request.extendedContext
                    && level.getNameExp() != null)
                {
                    final RolapStar.Column nameColumn = column.getNameColumn();
                    Util.assertTrue(nameColumn != null);
                    request.addConstrainedColumn(nameColumn, null);
                }
            }
        }
    }

    private static void addNonConstrainingColumns(
        final OlapElement member,
        final RolapCube baseCube,
        final CellRequest request)
    {
        RolapCubeLevel level;
        if (member instanceof RolapCubeLevel) {
            level = (RolapCubeLevel) member;
        } else if (member instanceof RolapCubeHierarchy
            || member instanceof RolapCubeDimension)
        {
            level = (RolapCubeLevel) member.getHierarchy().getLevels()[0];
            if (level.isAll()) {
                level = level.getChildLevel();
            }
        } else if (member instanceof RolapStar.Measure) {
            ((DrillThroughCellRequest)request)
                .addDrillThroughMeasure((RolapStar.Measure)member);
            return;
        } else if (member instanceof RolapBaseCubeMeasure) {
            ((DrillThroughCellRequest)request)
                .addDrillThroughMeasure(
                    (RolapStar.Measure)
                        ((RolapBaseCubeMeasure)member).getStarMeasure());
            return;
        } else if (member instanceof RolapHierarchy.RolapCalculatedMeasure) {
            throw MondrianResource.instance().DrillthroughCalculatedMember
                .ex(member.getUniqueName());
        } else {
            throw new MondrianException(
                "Unknown member type in DRILLTHROUGH operation.");
        }
        RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
        if (column != null) {
            request.addConstrainedColumn(column, null);
            ((DrillThroughCellRequest)request).addDrillThroughColumn(column);
            if (request.extendedContext
                && level.getNameExp() != null)
            {
                final RolapStar.Column nameColumn = column.getNameColumn();
                Util.assertTrue(nameColumn != null);
                request.addConstrainedColumn(nameColumn, null);
            }
        }
    }

    /**
     * Groups members (or tuples) from the same compound (i.e. hierarchy) into
     * groups that are constrained by the same set of columns.
     *
     * <p>E.g.
     *
     * <pre>Members
     *     [USA].[CA],
     *     [Canada].[BC],
     *     [USA].[CA].[San Francisco],
     *     [USA].[OR].[Portland]</pre>
     *
     * will be grouped into
     *
     * <pre>Group 1:
     *     {[USA].[CA], [Canada].[BC]}
     * Group 2:
     *     {[USA].[CA].[San Francisco], [USA].[OR].[Portland]}</pre>
     *
     * <p>This helps with generating optimal form of sql.
     *
     * <p>In case of aggregating over a list of tuples, similar logic also
     * applies.
     *
     * <p>For example:
     *
     * <pre>Tuples:
     *     ([Gender].[M], [Store].[USA].[CA])
     *     ([Gender].[F], [Store].[USA].[CA])
     *     ([Gender].[M], [Store].[USA])
     *     ([Gender].[F], [Store].[Canada])</pre>
     *
     * will be grouped into
     *
     * <pre>Group 1:
     *     {([Gender].[M], [Store].[USA].[CA]),
     *      ([Gender].[F], [Store].[USA].[CA])}
     * Group 2:
     *     {([Gender].[M], [Store].[USA]),
     *      ([Gender].[F], [Store].[Canada])}</pre>
     *
     * <p>This function returns a boolean value indicating if any constraint
     * can be created from the aggregationList. It is possible that only part
     * of the aggregationList can be applied, which still leads to a (partial)
     * constraint that is represented by the compoundGroupMap.
     */
    private static boolean makeCompoundGroup(
        int starColumnCount,
        RolapCube baseCube,
        List<List<RolapMember>> aggregationList,
        Map<BitKey, List<RolapCubeMember[]>> compoundGroupMap)
    {
        // The more generalized aggregation as aggregating over tuples.
        // The special case is a tuple defined by only one member.
        int unsatisfiableTupleCount = 0;
        for (List<RolapMember> aggregation : aggregationList) {
            boolean isTuple;
            if (aggregation.size() > 0
                && (aggregation.get(0) instanceof RolapCubeMember
                    || aggregation.get(0) instanceof VisualTotalMember))
            {
                isTuple = true;
            } else {
                ++unsatisfiableTupleCount;
                continue;
            }

            BitKey bitKey = BitKey.Factory.makeBitKey(starColumnCount);
            RolapCubeMember[] tuple;

            tuple = new RolapCubeMember[aggregation.size()];
            int i = 0;
            for (Member member : aggregation) {
                if (member instanceof VisualTotalMember) {
                    tuple[i] = (RolapCubeMember)
                        ((VisualTotalMember) member).getMember();
                } else {
                    tuple[i] = (RolapCubeMember)member;
                }
                i++;
            }

            boolean tupleUnsatisfiable = false;
            for (RolapCubeMember member : tuple) {
                // Tuple cannot be constrained if any of the member cannot be.
                tupleUnsatisfiable =
                    makeCompoundGroupForMember(member, baseCube, bitKey);
                if (tupleUnsatisfiable) {
                    // If this tuple is unsatisfiable, skip it and try to
                    // constrain the next tuple.
                    unsatisfiableTupleCount ++;
                    break;
                }
            }

            if (!tupleUnsatisfiable && !bitKey.isEmpty()) {
                // Found tuple(columns) to constrain,
                // now add it to the compoundGroupMap
                addTupleToCompoundGroupMap(tuple, bitKey, compoundGroupMap);
            }
        }

        return (unsatisfiableTupleCount == aggregationList.size());
    }

    private static void addTupleToCompoundGroupMap(
        RolapCubeMember[] tuple,
        BitKey bitKey,
        Map<BitKey, List<RolapCubeMember[]>> compoundGroupMap)
    {
        List<RolapCubeMember[]> compoundGroup = compoundGroupMap.get(bitKey);
        if (compoundGroup == null) {
            compoundGroup = new ArrayList<RolapCubeMember[]>();
            compoundGroupMap.put(bitKey, compoundGroup);
        }
        compoundGroup.add(tuple);
    }

    private static boolean makeCompoundGroupForMember(
        RolapCubeMember member,
        RolapCube baseCube,
        BitKey bitKey)
    {
        RolapCubeMember levelMember = member;
        boolean memberUnsatisfiable = false;
        while (levelMember != null) {
            RolapCubeLevel level = levelMember.getLevel();
            // Only need to constrain the nonAll levels
            if (!level.isAll()) {
                RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
                if (column != null) {
                    bitKey.set(column.getBitPosition());
                } else {
                    // One level in a member causes the member to be
                    // unsatisfiable.
                    memberUnsatisfiable = true;
                    break;
                }
            }

            levelMember = levelMember.getParentMember();
        }
        return memberUnsatisfiable;
    }

    /**
     * Translates a Map&lt;BitKey, List&lt;RolapMember&gt;&gt; of the same
     * compound member into {@link ListPredicate} by traversing a list of
     * members or tuples.
     *
     * <p>1. The example below is for list of tuples
     *
     * <blockquote>
     * group 1: [Gender].[M], [Store].[USA].[CA]<br/>
     * group 2: [Gender].[F], [Store].[USA].[CA]
     * </blockquote>
     *
     * is translated into
     *
     * <blockquote>
     * (Gender=M AND Store_State=CA AND Store_Country=USA)<br/>
     * OR<br/>
     * (Gender=F AND Store_State=CA AND Store_Country=USA)
     * </blockquote>
     *
     * <p>The caller of this method will translate this representation into
     * appropriate SQL form as
     * <blockquote>
     * where (gender = 'M'<br/>
     *        and Store_State = 'CA'<br/>
     *        AND Store_Country = 'USA')<br/>
     *     OR (Gender = 'F'<br/>
     *         and Store_State = 'CA'<br/>
     *         AND Store_Country = 'USA')
     * </blockquote>
     *
     * <p>2. The example below for a list of members
     * <blockquote>
     * group 1: [USA].[CA], [Canada].[BC]<br/>
     * group 2: [USA].[CA].[San Francisco], [USA].[OR].[Portland]
     * </blockquote>
     *
     * is translated into:
     *
     * <blockquote>
     * (Country=USA AND State=CA)<br/>
     * OR (Country=Canada AND State=BC)<br/>
     * OR (Country=USA AND State=CA AND City=San Francisco)<br/>
     * OR (Country=USA AND State=OR AND City=Portland)
     * </blockquote>
     *
     * <p>The caller of this method will translate this representation into
     * appropriate SQL form. For exmaple, if the underlying DB supports multi
     * value IN-list, the second group will turn into this predicate:
     *
     * <blockquote>
     * where (country, state, city) IN ((USA, CA, San Francisco),
     *                                      (USA, OR, Portland))
     * </blockquote>
     *
     * or, if the DB does not support multi-value IN list:
     *
     * <blockquote>
     * where country=USA AND
     *           ((state=CA AND city = San Francisco) OR
     *            (state=OR AND city=Portland))
     * </blockquote>
     *
     * @param compoundGroupMap Map from dimensionality to groups
     * @param baseCube base cube if virtual
     * @return compound predicate for a tuple or a member
     */
    private static StarPredicate makeCompoundPredicate(
        Map<BitKey, List<RolapCubeMember[]>> compoundGroupMap,
        RolapCube baseCube)
    {
        List<StarPredicate> compoundPredicateList =
            new ArrayList<StarPredicate> ();
        for (List<RolapCubeMember[]> group : compoundGroupMap.values()) {
             // e.g {[USA].[CA], [Canada].[BC]}
            StarPredicate compoundGroupPredicate = null;
            for (RolapCubeMember[] tuple : group) {
                // [USA].[CA]
                StarPredicate tuplePredicate = null;

                for (RolapCubeMember member : tuple) {
                    tuplePredicate = makeCompoundPredicateForMember(
                        member, baseCube, tuplePredicate);
                }
                if (tuplePredicate != null) {
                    if (compoundGroupPredicate == null) {
                        compoundGroupPredicate = tuplePredicate;
                    } else {
                        compoundGroupPredicate =
                            compoundGroupPredicate.or(tuplePredicate);
                    }
                }
            }

            if (compoundGroupPredicate != null) {
                // Sometimes the compound member list does not constrain any
                // columns; for example, if only AllLevel is present.
                compoundPredicateList.add(compoundGroupPredicate);
            }
        }

        StarPredicate compoundPredicate = null;

        if (compoundPredicateList.size() > 1) {
            compoundPredicate = new OrPredicate(compoundPredicateList);
        } else if (compoundPredicateList.size() == 1) {
            compoundPredicate = compoundPredicateList.get(0);
        }

        return compoundPredicate;
    }

    private static StarPredicate makeCompoundPredicateForMember(
        RolapCubeMember member,
        RolapCube baseCube,
        StarPredicate memberPredicate)
    {
        while (member != null) {
            RolapCubeLevel level = member.getLevel();
            if (!level.isAll()) {
                RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
                if (memberPredicate == null) {
                    memberPredicate =
                        new ValueColumnPredicate(column, member.getKey());
                } else {
                    memberPredicate =
                        memberPredicate.and(
                            new ValueColumnPredicate(column, member.getKey()));
                }
            }
            // Don't need to constrain USA if CA is unique
            if (member.getLevel().isUnique()) {
                break;
            }
            member = member.getParentMember();
        }
        return memberPredicate;
    }

    /**
     * Retrieves the value of a cell from the cache.
     *
     * @param request Cell request
     * @pre request != null && !request.isUnsatisfiable()
     * @return Cell value, or null if cell is not in any aggregation in cache,
     *   or {@link Util#nullValue} if cell's value is null
     */
    public abstract Object getCellFromCache(CellRequest request);

    public abstract Object getCellFromCache(
        CellRequest request,
        PinSet pinSet);

    /**
     * Generates a SQL statement which will return the rows which contribute to
     * this request.
     *
     * @param request Cell request
     * @param countOnly If true, return a statment which returns only the count
     * @param starPredicateSlicer A StarPredicate representing slicer positions
     * that could not be represented by the CellRequest, or
     * <code>null</code> if no additional predicate is necessary.
     * @return SQL statement
     */
    public abstract String getDrillThroughSql(
        DrillThroughCellRequest request,
        StarPredicate starPredicateSlicer,
        List<OlapElement> fields,
        boolean countOnly);

    public static RolapCacheRegion makeCacheRegion(
        final RolapStar star,
        final CacheControl.CellRegion region)
    {
        final List<Member> measureList = CacheControlImpl.findMeasures(region);
        final List<RolapStar.Measure> starMeasureList =
            new ArrayList<RolapStar.Measure>();
        RolapCube baseCube = null;
        for (Member measure : measureList) {
            if (!(measure instanceof RolapStoredMeasure)) {
                continue;
            }
            final RolapStoredMeasure storedMeasure =
                (RolapStoredMeasure) measure;
            final RolapStar.Measure starMeasure =
                (RolapStar.Measure) storedMeasure.getStarMeasure();
            assert starMeasure != null;
            if (star != starMeasure.getStar()) {
                continue;
            }
            // TODO: each time this code executes, baseCube is set.
            // Should there be a 'break' here? Are all of the
            // storedMeasure cubes the same cube? Is the measureList always
            // non-empty so that baseCube is always set?
            baseCube = storedMeasure.getCube();
            starMeasureList.add(starMeasure);
        }
        final RolapCacheRegion cacheRegion =
            new RolapCacheRegion(star, starMeasureList);
        if (region instanceof CacheControlImpl.CrossjoinCellRegion) {
            final CacheControlImpl.CrossjoinCellRegion crossjoin =
                (CacheControlImpl.CrossjoinCellRegion) region;
            for (CacheControl.CellRegion component
                : crossjoin.getComponents())
            {
                constrainCacheRegion(cacheRegion, baseCube, component);
            }
        } else {
            constrainCacheRegion(cacheRegion, baseCube, region);
        }
        return cacheRegion;
    }

    private static void constrainCacheRegion(
        final RolapCacheRegion cacheRegion,
        final RolapCube baseCube,
        final CacheControl.CellRegion region)
    {
        if (region instanceof CacheControlImpl.MemberCellRegion) {
            final CacheControlImpl.MemberCellRegion memberCellRegion =
                (CacheControlImpl.MemberCellRegion) region;
            final List<Member> memberList = memberCellRegion.getMemberList();
            for (Member member : memberList) {
                if (member.isMeasure()) {
                    continue;
                }
                final RolapCubeMember rolapMember;
                if (member instanceof RolapCubeMember) {
                    rolapMember = (RolapCubeMember) member;
                } else {
                    rolapMember =
                        (RolapCubeMember) baseCube.getSchemaReader()
                            .getMemberByUniqueName(
                                Util.parseIdentifier(member.getUniqueName()),
                                true);
                }
                final RolapCubeLevel level = rolapMember.getLevel();
                RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);

                level.getLevelReader().constrainRegion(
                    new MemberColumnPredicate(column, rolapMember),
                    baseCube,
                    cacheRegion);
            }
        } else if (region instanceof CacheControlImpl.MemberRangeCellRegion) {
            final CacheControlImpl.MemberRangeCellRegion rangeRegion =
                (CacheControlImpl.MemberRangeCellRegion) region;
            final RolapCubeLevel level = (RolapCubeLevel)rangeRegion.getLevel();
            RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);

            level.getLevelReader().constrainRegion(
                new RangeColumnPredicate(
                    column,
                    rangeRegion.getLowerInclusive(),
                    (rangeRegion.getLowerBound() == null
                     ? null
                     : new MemberColumnPredicate(
                         column, rangeRegion.getLowerBound())),
                    rangeRegion.getUpperInclusive(),
                    (rangeRegion.getUpperBound() == null
                     ? null
                     : new MemberColumnPredicate(
                         column, rangeRegion.getUpperBound()))),
                baseCube,
                cacheRegion);
        } else {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * Returns a {@link mondrian.rolap.CellReader} which reads cells from cache.
     */
    public CellReader getCacheCellReader() {
        return new CellReader() {
            // implement CellReader
            public Object get(RolapEvaluator evaluator) {
                CellRequest request = makeRequest(evaluator);
                if (request == null || request.isUnsatisfiable()) {
                    // request out of bounds
                    return Util.nullValue;
                }
                return getCellFromCache(request);
            }

            public int getMissCount() {
                return 0; // RolapAggregationManager never lies
            }

            public boolean isDirty() {
                return false;
            }
        };
    }

    /**
     * Creates a {@link PinSet}.
     *
     * @return a new PinSet
     */
    public abstract PinSet createPinSet();

    /**
     * A set of segments which are pinned (prevented from garbage collection)
     * for a short duration as a result of a cache inquiry.
     */
    public interface PinSet {
    }
}

// End RolapAggregationManager.java
