Package mondrian.rolap

Source Code of mondrian.rolap.RolapResult$CellInfoPool$Zero

/*
// $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapResult.java#4 $
// 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.
// Copyright (C) 2001-2002 Kana Software, Inc.
// Copyright (C) 2001-2009 Julian Hyde and others
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
//
// jhyde, 10 August, 2001
*/

package mondrian.rolap;

import mondrian.calc.*;
import mondrian.calc.impl.ValueCalc;
import mondrian.calc.impl.GenericCalc;
import mondrian.olap.*;
import mondrian.olap.DimensionType;
import mondrian.olap.fun.*;
import mondrian.olap.fun.VisualTotalsFunDef.VisualTotalMember;
import mondrian.olap.type.*;
import mondrian.resource.MondrianResource;
import mondrian.rolap.agg.AggregationManager;
import mondrian.util.ConcatenableList;
import mondrian.util.Format;
import mondrian.util.ObjectPool;
import mondrian.mdx.*;

import org.apache.log4j.Logger;

import java.util.*;

/**
* A <code>RolapResult</code> is the result of running a query.
*
* @author jhyde
* @since 10 August, 2001
* @version $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapResult.java#4 $
*/
public class RolapResult extends ResultBase {

    static final Logger LOGGER = Logger.getLogger(ResultBase.class);

    private RolapEvaluator evaluator;
    private final CellKey point;

    private CellInfoContainer cellInfos;
    private FastBatchingCellReader batchingReader;
    private final CellReader aggregatingReader =
        AggregationManager.instance().getCacheCellReader();
    private Modulos modulos = null;
    private final int maxEvalDepth =
            MondrianProperties.instance().MaxEvalDepth.get();

    private final Map<Integer, Boolean> positionsHighCardinality =
        new HashMap<Integer, Boolean>();
    private final Map<Integer, Iterator<Position>> positionsIterators =
        new HashMap<Integer, Iterator<Position>>();
    private final Map<Integer, Integer> positionsIndexes =
        new HashMap<Integer, Integer>();
    private final Map<Integer, List<Position>> positionsCurrent =
        new HashMap<Integer, List<Position>>();

    /**
     * Creates a RolapResult.
     *
     * @param query Query
     * @param execute Whether to execute the query
     */
    RolapResult(final Query query, boolean execute) {
        super(query, new Axis[query.axes.length]);

        this.point = CellKey.Generator.newCellKey(query.axes.length);
        final int expDeps =
            MondrianProperties.instance().TestExpDependencies.get();
        if (expDeps > 0) {
            this.evaluator = new RolapDependencyTestingEvaluator(this, expDeps);
        } else {
            final RolapEvaluatorRoot root =
                new RolapResultEvaluatorRoot(this);
            this.evaluator = new RolapEvaluator(root);
        }
        RolapCube cube = (RolapCube) query.getCube();
        this.batchingReader = new FastBatchingCellReader(cube);

        this.cellInfos =
            (query.axes.length > 4)
                ? new CellInfoMap(point)
                : new CellInfoPool(query.axes.length);

        if (!execute) {
            return;
        }

        boolean normalExecution = true;
        try {
            // This call to clear the cube's cache only has an
            // effect if caching has been disabled, otherwise
            // nothing happens.
            // Clear the local cache before a query has run
            cube.clearCachedAggregations();
            // Check if there are modifications to the global aggregate cache
            cube.checkAggregateModifications();


            /////////////////////////////////////////////////////////////////
            //
            // Evaluation Algorithm
            //
            // There are three basic steps to the evaluation algorithm:
            // 1) Determine all Members for each axis but do not save
            // information (do not build the RolapAxis),
            // 2) Save all Members for each axis (build RolapAxis).
            // 3) Evaluate and store each Cell determined by the Members
            // of the axes.
            // Step 1 converges on the stable set of Members pre axis.
            // Steps 1 and 2 make sure that the data has been loaded.
            //
            // More detail follows.
            //
            // Explicit and Implicit Members:
            // A Member is said to be 'explicit' if it appears on one of
            // the Axes (one of the RolapAxis Position List of Members).
            // A Member is 'implicit' if it is in the query but does not
            // end up on any Axes (its usage, for example, is in a function).
            // When for a Dimension none of its Members are explicit in the
            // query, then the default Member is used which is like putting
            // the Member in the Slicer.
            //
            // Special Dimensions:
            // There are 2 special dimensions.
            // The first is the Time dimension. If in a schema there is
            // no ALL Member, then Whatever happens to be the default
            // Member is used if Time Members are not explicitly set
            // in the query.
            // The second is the Measures dimension. This dimension
            // NEVER has an ALL Member. A cube's default Measure is set
            // by convention - its simply the first Measure defined in the
            // cube.
            //
            // First a RolapEvaluator is created. During its creation,
            // it gets a Member from each Hierarchy. Each Member is the
            // default Member of the Hierarchy. For most Hierarchies this
            // Member is the ALL Member, but there are cases where 1)
            // a Hierarchy does not have an ALL Member or 2) the Hierarchy
            // has an ALL Member but that Member is not the default Member.
            // In these cases, the default Member is still used, but its
            // use can cause evaluation issues (seemingly strange evaluation
            // results).
            //
            // Next, load all root Members for Hierarchies that have no ALL
            // Member and load ALL Members that are not the default Member.
            //
            // Determine the Members of the Slicer axis (Step 1 above).  Any
            // Members found are added to the AxisMember object. If one of these
            // Members happens to be a Measure, then the Slicer is explicitly
            // specifying the query's Measure and this should be put into the
            // evaluator's context (replacing the default Measure which just
            // happens to be the first Measure defined in the cube).  Other
            // Members found in the AxisMember object are also placed into the
            // evaluator's context since these also are explicitly specified.
            // Also, any other Members in the AxisMember object which have the
            // same Hierarchy as Members in the list of root Members for
            // Hierarchies that have no ALL Member, replace those Members - they
            // Slicer has explicitly determined which ones to use. The
            // AxisMember object is now cleared.
            // The Slicer does not depend upon the other Axes, but the other
            // Axes depend upon both the Slicer and each other.
            //
            // The AxisMember object also checks if the number of Members
            // exceeds the ResultLimit property throwing a
            // TotalMembersLimitExceeded Exception if it does.
            //
            // For all non-Slicer axes, the Members are determined (Step 1
            // above). If a Measure is found in the AxisMember, then an
            // Axis is explicitly specifying a Measure.
            // If any Members in the AxisMember object have the same Hierarchy
            // as a Member in the set of root Members for Hierarchies that have
            // no ALL Member, then replace those root Members with the Member
            // from the AxisMember object. In this case, again, a Member
            // was explicitly specified in an Axis. If this replacement
            // occurs, then one must redo this step with the new Members.
            //
            // Now Step 3 above is done. First to the Slicer Axis and then
            // to the other Axes. Here the Axes are actually generated.
            // If a Member of an Axis is an Calculated Member (and the
            // Calculated Member is not a Member of the Measure Hierarchy),
            // then find the Dimension associated with the Calculated
            // Member and remove Members with the same Dimension in the set of
            // root Members for Hierarchies that have no ALL Member.
            // This is done because via the Calculated Member the Member
            // was implicitly specified in the query. If this removal occurs,
            // then the Axes must be re-evaluated repeating Step 3.
            //
            /////////////////////////////////////////////////////////////////


            // The AxisMember object is used to hold Members that are found
            // during Step 1 when the Axes are determined.
            final AxisMember axisMembers = new AxisMember();


            // list of ALL Members that are not default Members
            final List<Member> nonDefaultAllMembers = new ArrayList<Member>();

            // List of Members of Hierarchies that do not have an ALL Member
            List<List<Member>> nonAllMembers = new ArrayList<List<Member>>();

            // List of Measures
            final List<Member> measureMembers = new ArrayList<Member>();

            // load all root Members for Hierarchies that have no ALL
            // Member and load ALL Members that are not the default Member.
            // Also, all Measures are are gathered.
            loadSpecialMembers(
                nonDefaultAllMembers, nonAllMembers, measureMembers);

            // clear evaluation cache
            query.clearEvalCache();

            // Save, may be needed by some Expression Calc's
            query.putEvalCache("ALL_MEMBER_LIST", nonDefaultAllMembers);


            final List<List<Member>> emptyNonAllMembers =
                Collections.emptyList();

            /////////////////////////////////////////////////////////////////
            // Determine Slicer
            //
            axisMembers.setSlicer(true);
            loadMembers(
                emptyNonAllMembers,
                evaluator,
                query.slicerAxis,
                query.slicerCalc,
                axisMembers);
            axisMembers.setSlicer(false);

            // Save unadulterated context for the next time we need to evaluate
            // the slicer.
            final RolapEvaluator savedEvaluator = evaluator.push();

            if (!axisMembers.isEmpty()) {
                for (Member m : axisMembers) {
                    if (m == null) {
                        break;
                    }
                    evaluator.setSlicerContext(m);
                    if (m.isMeasure()) {
                        // A Measure was explicitly declared in the
                        // Slicer, don't need to worry about Measures
                        // for this query.
                        measureMembers.clear();
                    }
                }
                replaceNonAllMembers(nonAllMembers, axisMembers);
                axisMembers.clearMembers();
            }

            /////////////////////////////////////////////////////////////////
            // Determine Axes
            //
            boolean changed = false;

            // reset to total member count
            axisMembers.clearTotalCellCount();

            for (int i = 0; i < axes.length; i++) {
                final QueryAxis axis = query.axes[i];
                final Calc calc = query.axisCalcs[i];
                loadMembers(
                    emptyNonAllMembers, evaluator, axis, calc, axisMembers);
            }

            if (!axisMembers.isEmpty()) {
                for (Member m : axisMembers) {
                    if (m.isMeasure()) {
                        // A Measure was explicitly declared on an
                        // axis, don't need to worry about Measures
                        // for this query.
                        measureMembers.clear();
                    }
                }
                changed = replaceNonAllMembers(nonAllMembers, axisMembers);
                axisMembers.clearMembers();
            }

            if (changed) {
                // only count number of members, do not collect any
                axisMembers.countOnly(true);
                // reset to total member count
                axisMembers.clearTotalCellCount();

                for (int i = 0; i < axes.length; i++) {
                    final QueryAxis axis = query.axes[i];
                    final Calc calc = query.axisCalcs[i];
                    loadMembers(
                        nonAllMembers,
                        evaluator.push(),
                        axis, calc, axisMembers);
                }
            }

            // throws exception if number of members exceeds limit
            axisMembers.checkLimit();

            /////////////////////////////////////////////////////////////////
            // Execute Slicer
            //
            this.slicerAxis =
                evalExecute(
                    nonAllMembers,
                    nonAllMembers.size() - 1,
                    savedEvaluator,
                    query.slicerAxis,
                    query.slicerCalc);

            // Use the context created by the slicer for the other
            // axes.  For example, "select filter([Customers], [Store
            // Sales] > 100) on columns from Sales where
            // ([Time].[1998])" should show customers whose 1998 (not
            // total) purchases exceeded 100.

            // Getting the Position list's size and the Position
            // at index == 0 will, in fact, cause an Iterable-base
            // Axis Position List to become a List-base Axis
            // Position List (and increase memory usage), but for
            // the slicer axis, the number of Positions is (generally) very
            // small, so who cares.
            final List<Position> positionList = slicerAxis.getPositions();
            RolapEvaluator evaluator = this.evaluator;
            if (positionList.size() > 1) {
                int arity = positionList.get(0).size();
                List<Member[]> tupleList =
                    new ArrayList<Member[]>(positionList.size());
                for (Position position : positionList) {
                    final Member[] members = new Member[arity];
                    for (int i = 0; i < position.size(); i++) {
                        members[i] = position.get(i);
                    }
                    tupleList.add(members);
                }
                tupleList =
                    AggregateFunDef.AggregateCalc.optimizeTupleList(
                        evaluator,
                        tupleList);

                final Calc valueCalc =
                    new ValueCalc(
                        new DummyExp(new ScalarType()));
                final List<Member[]> tupleList1 = tupleList;
                final Calc calc =
                    new GenericCalc(
                        new DummyExp(query.slicerCalc.getType()))
                    {
                        public Object evaluate(Evaluator evaluator) {
                            return AggregateFunDef.AggregateCalc.aggregate(
                                valueCalc, evaluator, tupleList1);
                        }
                    };
                final List<RolapHierarchy> hierarchyList =
                    new AbstractList<RolapHierarchy>() {
                        final Position pos0 = positionList.get(0);

                        public RolapHierarchy get(int index) {
                            return
                                ((RolapMember) pos0.get(index)).getHierarchy();
                        }

                        public int size() {
                            return 0;
                        }
                    };
                evaluator = evaluator.push(
                    new RolapTupleCalculation(hierarchyList, calc));
            }

            /////////////////////////////////////////////////////////////////
            // Execute Axes
            //
            boolean redo = true;
            while (redo) {
                RolapEvaluator e = evaluator.push();
                redo = false;

                for (int i = 0; i < axes.length; i++) {
                    QueryAxis axis = query.axes[i];
                    final Calc calc = query.axisCalcs[i];
                    Axis axisResult = evalExecute(
                        nonAllMembers, nonAllMembers.size() - 1, e, axis, calc);

                    if (!nonAllMembers.isEmpty()) {
                        List<Position> pl = axisResult.getPositions();
                        if (!pl.isEmpty()) {
                            // Only need to process the first Position
                            Position p = pl.get(0);
                            for (Member m : p) {
                                if (m.isCalculated()) {
                                    CalculatedMeasureVisitor visitor =
                                        new CalculatedMeasureVisitor();
                                    m.getExpression().accept(visitor);
                                    Dimension dimension = visitor.dimension;
                                    redo = removeDimension(
                                        dimension, nonAllMembers);
                                }
                            }
                        }
                    }
                    this.axes[i] = axisResult;
                }
            }

            // Get value for each Cell
            executeBody(evaluator, this.query, new int[axes.length]);

            // If you are very close to running out of memory due to
            // the number of CellInfo's in cellInfos, then calling this
            // may cause the out of memory one is trying to aviod.
            // On the other hand, calling this can reduce the size of
            // the ObjectPool's internal storage by half (but, of course,
            // it will not reduce the size of the stored objects themselves).
            // Only call this if there are lots of CellInfo.
            if (this.cellInfos.size() > 10000) {
                this.cellInfos.trimToSize();
            }
        } catch (ResultLimitExceededException ex) {
            // If one gets a ResultLimitExceededException, then
            // don't count on anything being worth caching.
            normalExecution = false;

            // De-reference data structures that might be holding
            // partial results but surely are taking up memory.
            evaluator = null;
            cellInfos = null;
            batchingReader = null;
            for (int i = 0; i < axes.length; i++) {
                axes[i] = null;
            }
            slicerAxis = null;

            query.clearEvalCache();

            throw ex;
        } finally {
            if (normalExecution) {
                // Push all modifications to the aggregate cache to the global
                // cache so each thread can start using it
                cube.pushAggregateModificationsToGlobalCache();

                // Expression cache duration is for each query. It is time to
                // clear out the whole expression cache at the end of a query.
                evaluator.clearExpResultCache(true);
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("RolapResult<init>: " + Util.printMemory());
            }
        }
    }

    protected boolean removeDimension(
        Dimension dimension,
        List<List<Member>> nonAllMembers)
    {
        boolean changed = false;
        for (ListIterator<List<Member>> it = nonAllMembers.listIterator();
                it.hasNext();)
        {
            List<Member> ms = it.next();
            Dimension d = ms.get(0).getHierarchy().getDimension();
            if (d.equals(dimension)) {
                it.remove();
            }
        }
        return changed;
    }

    private static class CalculatedMeasureVisitor
        extends MdxVisitorImpl
    {
        Dimension dimension;

        CalculatedMeasureVisitor() {
        }

        public Object visit(DimensionExpr dimensionExpr) {
            dimension = dimensionExpr.getDimension();
            return null;
        }

        public Object visit(HierarchyExpr hierarchyExpr) {
            Hierarchy hierarchy = hierarchyExpr.getHierarchy();
            dimension = hierarchy.getDimension();
            return null;
        }

        public Object visit(MemberExpr memberExpr)  {
            Member member = memberExpr.getMember();
            dimension = member.getHierarchy().getDimension();
            return null;
        }
    }

    protected boolean replaceNonAllMembers(
        List<List<Member>> nonAllMembers,
        AxisMember axisMembers)
    {
        boolean changed = false;
        List<Member> mList = new ArrayList<Member>();
        for (ListIterator<List<Member>> it = nonAllMembers.listIterator();
                it.hasNext();)
        {
            List<Member> ms = it.next();
            Hierarchy h = ms.get(0).getHierarchy();
            mList.clear();
            for (Member m : axisMembers) {
                if (m.getHierarchy().equals(h)) {
                    mList.add(m);
                }
            }
            if (! mList.isEmpty()) {
                changed = true;
                it.set(new ArrayList<Member>(mList));
            }
        }
        return changed;
    }

    protected void loadMembers(
        List<List<Member>> nonAllMembers,
        RolapEvaluator evaluator,
        QueryAxis axis,
        Calc calc,
        AxisMember axisMembers)
    {
        int attempt = 0;
        evaluator.setCellReader(batchingReader);
        while (true) {
            axisMembers.clearAxisCount();
            evalLoad(
                nonAllMembers,
                nonAllMembers.size() - 1,
                evaluator,
                axis,
                calc,
                axisMembers);

            if (!batchingReader.loadAggregations(query)) {
                break;
            } else {
                // Clear invalid expression result so that the next evaluation
                // will pick up the newly loaded aggregates.
                evaluator.clearExpResultCache(false);
            }

            if (attempt++ > maxEvalDepth) {
                throw Util.newInternal(
                    "Failed to load all aggregations after "
                    + maxEvalDepth
                    + " passes; there's probably a cycle");
            }
        }
    }

    void evalLoad(
        List<List<Member>> nonAllMembers,
        int cnt,
        Evaluator evaluator,
        QueryAxis axis,
        Calc calc,
        AxisMember axisMembers)
    {
        if (cnt < 0) {
            executeAxis(evaluator.push(), axis, calc, false, axisMembers);
        } else {
            for (Member m : nonAllMembers.get(cnt)) {
                evaluator.setContext(m);
                evalLoad(
                    nonAllMembers, cnt - 1, evaluator, axis, calc, axisMembers);
            }
        }
    }

    Axis evalExecute(
        List<List<Member>> nonAllMembers,
        int cnt,
        RolapEvaluator evaluator,
        QueryAxis axis,
        Calc calc)
    {
        if (cnt < 0) {
            evaluator.setCellReader(aggregatingReader);
            return executeAxis(evaluator.push(), axis, calc, true, null);
            // No need to clear expression cache here as no new aggregates are
            // loaded(aggregatingReader reads from cache).
        } else {
            Axis axisResult = null;
            for (Member m : nonAllMembers.get(cnt)) {
                evaluator.setContext(m);
                Axis a =
                    evalExecute(nonAllMembers, cnt - 1, evaluator, axis, calc);
                boolean ordered = false;
                if (axis != null) {
                    ordered = axis.isOrdered();
                }
                axisResult = mergeAxes(axisResult, a, evaluator, ordered);
            }
            return axisResult;
        }
    }

    /**
     * Finds all root Members 1) whose Hierarchy does not have an ALL
     * Member, 2) whose default Member is not the ALL Member and 3)
     * all Measures.
     *
     * @param nonDefaultAllMembers  List of all root Members for Hierarchies
     * whose default Member is not the ALL Member.
     * @param nonAllMembers List of root Members for Hierarchies that have no
     * ALL Member.
     * @param measureMembers  List all Measures
     */
    protected void loadSpecialMembers(
        List<Member> nonDefaultAllMembers,
        List<List<Member>> nonAllMembers,
        List<Member> measureMembers)
    {
        SchemaReader schemaReader = evaluator.getSchemaReader();
        Member[] evalMembers = evaluator.getMembers();
        for (Member em : evalMembers) {
            if (em.isCalculated()) {
                continue;
            }
            Hierarchy h = em.getHierarchy();
            Dimension d = h.getDimension();
            if (d.getDimensionType() == DimensionType.TimeDimension) {
                continue;
            }
            if (!em.isAll()) {
                List<Member> rootMembers =
                    schemaReader.getHierarchyRootMembers(h);
                if (em.isMeasure()) {
                    for (Member mm : rootMembers) {
                        measureMembers.add(mm);
                    }
                } else {
                    if (h.hasAll()) {
                        for (Member m : rootMembers) {
                            if (m.isAll()) {
                                nonDefaultAllMembers.add(m);
                                break;
                            }
                        }
                    } else {
                        nonAllMembers.add(rootMembers);
                    }
                }
            }
        }
    }

    protected Logger getLogger() {
        return LOGGER;
    }

    public final RolapCube getCube() {
        return evaluator.getCube();
    }

    // implement Result
    public Axis[] getAxes() {
        return axes;
    }

    /**
     * Get the Cell for the given Cell position.
     *
     * @param pos Cell position.
     * @return the Cell associated with the Cell position.
     */
    public Cell getCell(int[] pos) {
        if (pos.length != point.size()) {
            throw Util.newError(
                    "coordinates should have dimension " + point.size());
        }

        for (int i = 0; i < pos.length; i++) {
            if (positionsHighCardinality.get(i)) {
                executeBody(evaluator, this.query, pos);
                break;
            }
        }

        CellInfo ci = cellInfos.lookup(pos);
        if (ci.value == null) {
            for (int i = 0; i < pos.length; i++) {
                int po = pos[i];
                if (po < 0 || po >= axes[i].getPositions().size()) {
                    throw Util.newError("coordinates out of range");
                }
            }
            ci.value = Util.nullValue;
        }

        return new RolapCell(this, pos.clone(), ci);
    }

    private Axis executeAxis(
        Evaluator evaluator,
        QueryAxis axis,
        Calc axisCalc,
        boolean construct,
        AxisMember axisMembers)
    {
        Axis axisResult = null;
        if (axis == null) {
            // Create an axis containing one position with no members (not
            // the same as an empty axis).
            if (construct) {
                axisResult = new RolapAxis.SingleEmptyPosition();
            }
        } else {
            final int arity =
                axis.getSet().getType() instanceof SetType
                    ? ((SetType) axis.getSet().getType()).getArity()
                    : axis.getSet().getType() instanceof TupleType
                    ? ((TupleType) axis.getSet().getType()).elementTypes.length
                    : 1;
            evaluator.setNonEmpty(axis.isNonEmpty());
            evaluator.setEvalAxes(true);
            Object value = axisCalc.evaluate(evaluator);
            if (axisCalc.getClass().getName().indexOf("OrderFunDef") != -1) {
                axis.setOrdered(true);
            }
            evaluator.setNonEmpty(false);
            if (value != null) {
                // List or Iterable of Member or Member[]
                if (value instanceof List) {
                    List<Object> list = (List) value;
                    if (construct) {
                        if (list.isEmpty()) {
                            // should be???
                            axisResult = new RolapAxis.NoPosition();
                        } else if (arity == 1) {
                            axisResult =
                                new RolapAxis.MemberList((List<Member>)value);
                        } else {
                            axisResult =
                                new RolapAxis.MemberArrayList(
                                    (List<Member[]>)value);
                        }
                    } else if (axisMembers != null) {
                        if (arity == 1) {
                            axisMembers.mergeMemberList((List<Member>) value);
                        } else {
                            axisMembers.mergeTupleList((List<Member[]>) value);
                        }
                    }
                } else {
                    // Iterable
                    Iterable<Object> iter = (Iterable) value;
                    Iterator it = iter.iterator();
                    if (construct) {
                        if (! it.hasNext()) {
                            axisResult = new RolapAxis.NoPosition();
                        } else if (arity != 1) {
                            axisResult = new RolapAxis.MemberArrayIterable(
                                (Iterable<Member[]>)value);
                        } else {
                            axisResult = new RolapAxis.MemberIterable(
                                (Iterable<Member>)value);
                        }
                    } else if (axisMembers != null) {
                        if (arity == 1) {
                            axisMembers.mergeMemberIter(it);
                        } else {
                            axisMembers.mergeTupleIter(it);
                        }
                    }
                }
            }
            evaluator.setEvalAxes(false);
        }
        return axisResult;
    }

    private void executeBody(
        RolapEvaluator evaluator,
        Query query,
        final int[] pos)
    {
        // Compute the cells several times. The first time, use a dummy
        // evaluator which collects requests.
        int count = 0;
        while (true) {
            evaluator.setCellReader(batchingReader);
            executeStripe(query.axes.length - 1, evaluator.push(), pos);

            // Retrieve the aggregations collected.
            //
            if (!batchingReader.loadAggregations(query)) {
                // We got all of the cells we needed, so the result must be
                // correct.
                return;
            } else {
                // Clear invalid expression result so that the next evaluation
                // will pick up the newly loaded aggregates.
                evaluator.clearExpResultCache(false);
            }

            if (count++ > maxEvalDepth) {
                if (evaluator instanceof RolapDependencyTestingEvaluator) {
                    // The dependency testing evaluator can trigger new
                    // requests every cycle. So let is run as normal for
                    // the first N times, then run it disabled.
                    ((RolapDependencyTestingEvaluator.DteRoot)
                        evaluator.root).disabled = true;
                    if (count > maxEvalDepth * 2) {
                        throw Util.newInternal(
                            "Query required more than " + count
                            + " iterations");
                    }
                } else {
                    throw Util.newInternal(
                        "Query required more than " + count + " iterations");
                }
            }

            cellInfos.clear();
        }
    }

    boolean isDirty() {
        return batchingReader.isDirty();
    }

    /**
     * Evaluates an expression. Intended for evaluating named sets.
     *
     * @param calc Compiled expression
     * @param evaluator Evaluation context
     * @return Result
     */
    Object evaluateExp(Calc calc, RolapEvaluator evaluator) {
        int attempt = 0;
        boolean dirty = batchingReader.isDirty();
        while (true) {
            RolapEvaluator ev = evaluator.push();

            ev.setCellReader(batchingReader);
            Object preliminaryValue = calc.evaluate(ev);
            if (preliminaryValue instanceof Iterable
                && !(preliminaryValue instanceof List))
            {
                Iterable iterable = (Iterable) preliminaryValue;
                for (Object anIterable : iterable) {
                    Util.discard(anIterable);
                }
            }

            if (!batchingReader.loadAggregations(evaluator.getQuery())) {
                break;
            } else {
                // Clear invalid expression result so that the next evaluation
                // will pick up the newly loaded aggregates.
                ev.clearExpResultCache(false);
            }

            if (attempt++ > maxEvalDepth) {
                throw Util.newInternal(
                    "Failed to load all aggregations after "
                    + maxEvalDepth + "passes; there's probably a cycle");
            }
        }

        // If there were pending reads when we entered, some of the other
        // expressions may have been evaluated incorrectly. Set the reader's
        // 'dirty' flag so that the caller knows that it must re-evaluate them.
        if (dirty) {
            batchingReader.setDirty(true);
        }

        RolapEvaluator ev = evaluator.push();
        ev.setCellReader(aggregatingReader);
        return calc.evaluate(ev);
    }

    private void executeStripe(
        int axisOrdinal,
        RolapEvaluator revaluator,
        final int[] pos)
    {
        if (axisOrdinal < 0) {
            Axis axis = slicerAxis;
            List<Position> positions = axis.getPositions();
            for (Position position : positions) {
                getQuery().checkCancelOrTimeout();
                revaluator.setContext(position);
                Object o;
                try {
                    o = revaluator.evaluateCurrent();
                } catch (MondrianEvaluationException e) {
                    LOGGER.warn("Mondrian: exception in executeStripe.", e);
                    o = e;
                }

                CellInfo ci = null;

                // Get the Cell's format string and value formatting
                // Object.
                try {
                    // This code is a combination of the code found in
                    // the old RolapResult
                    // <code>getCellNoDefaultFormatString</code> method and
                    // the old RolapCell <code>getFormattedValue</code> method.

                    // Create a CellInfo object for the given position
                    // integer array.
                    ci = cellInfos.create(point.getOrdinals());

                    String cachedFormatString = null;
                    ValueFormatter valueFormatter;

                    // Determine if there is a CellFormatter registered for
                    // the current Cube's Measure's Dimension. If so,
                    // then find or create a CellFormatterValueFormatter
                    // for it. If not, then find or create a Locale based
                    // FormatValueFormatter.
                    final RolapCube cube = getCube();
                    Hierarchy measuresHierarchy =
                        cube.getMeasuresHierarchy();
                    RolapMeasure m =
                        (RolapMeasure) revaluator.getContext(measuresHierarchy);
                    CellFormatter cf = m.getFormatter();
                    if (cf != null) {
                        valueFormatter = cellFormatters.get(cf);
                        if (valueFormatter == null) {
                            valueFormatter =
                                new CellFormatterValueFormatter(cf);
                            cellFormatters.put(cf, valueFormatter);
                        }
                    } else {
                        cachedFormatString = revaluator.getFormatString();
                        Locale locale = query.getConnection().getLocale();
                        valueFormatter = formatValueFormatters.get(locale);
                        if (valueFormatter == null) {
                            valueFormatter = new FormatValueFormatter(locale);
                            formatValueFormatters.put(locale, valueFormatter);
                        }
                    }

                    ci.formatString = cachedFormatString;
                    ci.valueFormatter = valueFormatter;
                } catch (ResultLimitExceededException e) {
                    // Do NOT ignore a ResultLimitExceededException!!!
                    throw e;
                } catch (MondrianEvaluationException e) {
                    // ignore but warn
                    LOGGER.warn("Mondrian: exception in executeStripe.", e);
                } catch (Error e) {
                    // Errors indicate fatal JVM problems; do not discard
                    throw e;
                } catch (Throwable e) {
                    LOGGER.warn("Mondrian: exception in executeStripe.", e);
                    Util.discard(e);
                }

                if (o == RolapUtil.valueNotReadyException) {
                    continue;
                }

                ci.value = o;
            }
        } else {
            Axis axis = axes[axisOrdinal];
            List<Position> positions = axis.getPositions();
            if (positionsHighCardinality.get(axisOrdinal) == null
                && !positions.isEmpty()
                && !positions.get(0).isEmpty())
            {
                positionsHighCardinality.put(
                    axisOrdinal,
                    positions.get(0).get(0).getDimension()
                        .isHighCardinality());
            }
            if (positionsHighCardinality.get(axisOrdinal) != null
                && positionsHighCardinality.get(axisOrdinal))
            {
                final int limit =
                    MondrianProperties.instance().HighCardChunkSize.get();
                if (positionsIterators.get(axisOrdinal) == null) {
                    final Iterator<Position> it = positions.iterator();
                    positionsIterators.put(axisOrdinal, it);
                    positionsIndexes.put(axisOrdinal, 0);
                    final List<Position> subPositions =
                        new ArrayList<Position>();
                    for (int i = 0; i < limit && it.hasNext(); i++) {
                        subPositions.add(it.next());
                    }
                    positionsCurrent.put(axisOrdinal, subPositions);
                }
                final Iterator<Position> it =
                    positionsIterators.get(axisOrdinal);
                final int positionIndex = positionsIndexes.get(axisOrdinal);
                List<Position> subPositions = positionsCurrent.get(axisOrdinal);

                if (subPositions == null) {
                    return;
                }

                int pi;
                if (pos[axisOrdinal] > positionIndex + subPositions.size() - 1
                        && subPositions.size() == limit)
                {
                    pi = positionIndex + subPositions.size();
                    positionsIndexes.put(
                        axisOrdinal, positionIndex + subPositions.size());
                    subPositions.subList(0, subPositions.size()).clear();
                    for (int i = 0; i < limit && it.hasNext(); i++) {
                        subPositions.add(it.next());
                    }
                    positionsCurrent.put(axisOrdinal, subPositions);
                } else {
                    pi = positionIndex;
                }
                for (final Position position : subPositions) {
                    point.setAxis(axisOrdinal, pi);
                    revaluator.setContext(position);
                    getQuery().checkCancelOrTimeout();
                    executeStripe(axisOrdinal - 1, revaluator, pos);
                    pi++;
                }
            } else {
                int positionIndex = 0;
                for (Position position : positions) {
                    List<Member> measures =
                        new ArrayList<Member>(query.getMeasuresMembers());
                    for (Member measure : measures) {
                        if (measure instanceof RolapBaseCubeMeasure) {
                            RolapBaseCubeMeasure baseCubeMeasure =
                                (RolapBaseCubeMeasure) measure;
                            if (baseCubeMeasure.getAggregator()
                                == RolapAggregator.DistinctCount)
                            {
                                processDistinctMeasureExpr(
                                    position, baseCubeMeasure);
                            }
                        }
                    }
                }

                for (final Position position : positions) {
                    point.setAxis(axisOrdinal, positionIndex);
                    revaluator.setContext(position);
                    getQuery().checkCancelOrTimeout();
                    executeStripe(axisOrdinal - 1, revaluator, pos);
                    positionIndex++;
                }
            }
        }
    }

    List<Member> exprMembers = null;

    /*
     * Distinct counts are aggregated separately from other measures.
     * We need to apply filters to each level in the query.
     *
     * Replace VisualTotalMember expressions with new expressions
     * where all leaf level members are included.
     *
     * Example:
     * For MDX query:
     * WITH SET [XL_Row_Dim_0] AS
     *         VisualTotals(
     *           Distinct(
     *             Hierarchize(
     *               {Ascendants([Store].[All Stores].[USA].[CA]),
     *                Descendants([Store].[All Stores].[USA].[CA])})))
     *        select NON EMPTY
     *          Hierarchize(
     *            Intersect(
     *              {DrilldownLevel({[Store].[All Stores]})},
     *              [XL_Row_Dim_0])) ON COLUMNS
     *        from [HR]
     *        where [Measures].[Number of Employees]
     *
     * For member [Store].[All Stores]:
     * we replace aggregate expression
     *  - Aggregate({[Store].[All Stores].[USA]})
     * with
     *  - Aggregate({[Store].[All Stores].[USA].[CA].[Alameda].[HQ],
     *               [Store].[All Stores].[USA].[CA].[Beverly Hills].[Store 6],
     *               [Store].[All Stores].[USA].[CA].[Los Angeles].[Store 7],
     *               [Store].[All Stores].[USA].[CA].[San Diego].[Store 24],
     *               [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]
     *              })
     * TODO:
     * Can be optimized. For that particular query
     * we don't need to go to the lowest level.
     * We can simply replace it with:
     * - Aggregate({[Store].[All Stores].[USA].[CA]})
     * Because all children of [Store].[All Stores].[USA].[CA] are included.
     */
    private Position processDistinctMeasureExpr(
        Position position,
        RolapBaseCubeMeasure measure)
    {
        for (int i = 0; i < position.size(); i++) {
            if (!(position.get(i) instanceof VisualTotalMember)) {
                continue;
            }
            evaluator.setContext(measure);
            VisualTotalMember member = (VisualTotalMember)position.get(i);
            exprMembers = new ArrayList<Member>();
            processMemberExpr(member);
            ((VisualTotalMember)member).setExpression(evaluator, exprMembers);
        }
        return position;
    }

    private void processMemberExpr(Object o) {
        if (o instanceof Member && o instanceof RolapCubeMember) {
            exprMembers.add((Member) o);
            return;
        } else if (o instanceof VisualTotalMember) {
            VisualTotalMember member = (VisualTotalMember) o;
            Exp exp = member.getExpression();
            processMemberExpr(exp);
        } else if (o instanceof Exp && !(o instanceof MemberExpr)) {
            Exp exp = (Exp)o;
            ResolvedFunCall funCall = (ResolvedFunCall)exp;
            Exp[] exps = funCall.getArgs();
            processMemberExpr(exps);
        } else if (o instanceof Exp[]) {
            Exp[] exps = (Exp[]) o;
            for (Exp exp : exps) {
                processMemberExpr(exp);
            }
        } else if (o instanceof MemberExpr) {
            MemberExpr memberExp = (MemberExpr) o;
            Member member = memberExp.getMember();
            processMemberExpr(member);
        } else {
            return;
        }
    }

    /**
     * Converts a set of cell coordinates to a cell ordinal.
     *
     * <p>This method can be expensive, because the ordinal is computed from the
     * length of the axes, and therefore the axes need to be instantiated.
     */
    int getCellOrdinal(int[] pos) {
        if (modulos == null) {
            makeModulos();
        }
        return modulos.getCellOrdinal(pos);
    }

    /*
     * Instantiates the calculator to convert cell coordinates to a cell ordinal
     * and vice versa.
     *
     * <p>To create the calculator, any axis that is based upon an Iterable is
     * converted into a List - thus increasing memory usage.
     */
    protected void makeModulos() {
        modulos = Modulos.Generator.create(axes);
    }

    /**
     * Called only by RolapCell.
     *
     * @param pos Coordinates of cell
     * @return Evaluator whose context is the given cell
     */
    RolapEvaluator getCellEvaluator(int[] pos) {
        final RolapEvaluator cellEvaluator = evaluator.push();
        for (int i = 0; i < pos.length; i++) {
            Position position = axes[i].getPositions().get(pos[i]);
            cellEvaluator.setContext(position);
        }
        return cellEvaluator;
    }

    /**
     * Called only by RolapCell. Use this when creating an Evaluator
     * (using method {@link #getCellEvaluator}) is not required.
     *
     * @param pos Coordinates of cell
     * @return Members which form the context of the given cell
     */
    RolapMember[] getCellMembers(int[] pos) {
        RolapMember[] members = (RolapMember[]) evaluator.getMembers().clone();
        for (int i = 0; i < pos.length; i++) {
            Position position = axes[i].getPositions().get(pos[i]);
            for (Member member : position) {
                RolapMember m = (RolapMember) member;
                int ordinal = m.getHierarchy().getOrdinalInCube();
                members[ordinal] = m;
            }
        }
        return members;
    }

    Evaluator getRootEvaluator() {
        return evaluator;
    }

    Evaluator getEvaluator(int[] pos) {
        // Set up evaluator's context, so that context-dependent format
        // strings work properly.
        Evaluator cellEvaluator = evaluator.push();
        for (int i = -1; i < axes.length; i++) {
            Axis axis;
            int index;
            if (i < 0) {
                axis = slicerAxis;
                index = 0;
            } else {
                axis = axes[i];
                index = pos[i];
            }
            Position position = axis.getPositions().get(index);
            cellEvaluator.setContext(position);
        }
        return cellEvaluator;
    }


    /**
     * Counts and collects Members found of the axes.
     * This class does two things. First it collects all Members
     * found during the Member-Determination phase.
     * Secondly, it counts how many Members are on each axis and
     * forms the product, the totalCellCount which is checked against
     * the ResultLimit property value.
     */
    private static class AxisMember implements Iterable<Member> {
        private final List<Member> members;
        private final int limit;
        private boolean isSlicer;
        private int totalCellCount;
        private int axisCount;
        private boolean countOnly;

        AxisMember() {
            this.countOnly = false;
            this.members = new ConcatenableList<Member>();
            this.totalCellCount = 1;
            this.axisCount = 0;
            // Now that the axes are evaluated, make sure that the number of
            // cells does not exceed the result limit.
            this.limit = MondrianProperties.instance().ResultLimit.get();
        }

        public Iterator<Member> iterator() {
            return members.iterator();
        }

        void setSlicer(final boolean isSlicer) {
            this.isSlicer = isSlicer;
        }

        boolean isEmpty() {
            return this.members.isEmpty();
        }

        void countOnly(boolean countOnly) {
            this.countOnly = countOnly;
        }

        void checkLimit() {
            if (this.limit > 0) {
                this.totalCellCount *= this.axisCount;
                if (this.totalCellCount > this.limit) {
                    throw MondrianResource.instance().TotalMembersLimitExceeded
                        .ex(
                            this.totalCellCount,
                            this.limit);
                }
                this.axisCount = 0;
            }
        }

        void clearAxisCount() {
            this.axisCount = 0;
        }

        void clearTotalCellCount() {
            this.totalCellCount = 1;
        }

        void clearMembers() {
            this.members.clear();
            this.axisCount = 0;
            this.totalCellCount = 1;
        }

        List<Member> members() {
            return this.members;
        }

        void mergeMemberList(List<Member> list) {
            for (Member o : list) {
                if (o == null) {
                    continue;
                }
                if (o.getDimension().isHighCardinality()) {
                    break;
                }
                mergeMember(o);
            }
        }

        void mergeTupleList(List<Member[]> list) {
            for (Member[] o : list) {
                mergeTuple(o);
            }
        }

        private void mergeMemberIter(Iterator<Member> it) {
            while (it.hasNext()) {
                mergeMember(it.next());
            }
        }

        private void mergeTupleIter(Iterator<Member[]> it) {
            while (it.hasNext()) {
                mergeTuple(it.next());
            }
        }

        private Member getTopParent(final Member m) {
            Member parent = m.getParentMember();
            return (parent == null) ? m : getTopParent(parent);
        }

        private void mergeTuple(final Member[] members) {
            for (Member member : members) {
                mergeMember(member);
            }
        }

        private void mergeMember(final Member member) {
            this.axisCount++;
            if (! countOnly) {
                if (isSlicer) {
                    if (! members.contains(member)) {
                        members.add(member);
                    }
                } else {
                    if (member.isNull()) {
                        return;
                    } else if (member.isMeasure()) {
                        return;
                    } else if (member.isCalculated()) {
                        return;
                    } else if (member.isAll()) {
                        return;
                    }
                    Member topParent = getTopParent(member);
                    if (! this.members.contains(topParent)) {
                        this.members.add(topParent);
                    }
                }
            }
        }
    }

    /**
     * Extension to {@link RolapEvaluatorRoot} which is capable
     * of evaluating named sets.<p/>
     *
     * A given set is only evaluated once each time a query is executed; the
     * result is added to the {@link #namedSetEvaluators} cache on first execution
     * and re-used.<p/>
     *
     * <p>Named sets are always evaluated in the context of the slicer.<p/>
     */
    protected static class RolapResultEvaluatorRoot
        extends RolapEvaluatorRoot
    {
        /**
         * Maps the names of sets to their values. Populated on demand.
         */
        private final Map<String, RolapNamedSetEvaluator> namedSetEvaluators =
            new HashMap<String, RolapNamedSetEvaluator>();

        /**
         * Evaluator containing context resulting from evaluating the slicer.
         */
        RolapEvaluator slicerEvaluator;
        final RolapResult result;
        private static final Object CycleSentinel = new Object();
        private static final Object NullSentinel = new Object();

        public RolapResultEvaluatorRoot(RolapResult result) {
            super(result.query);
            this.result = result;
        }

        protected void init(Evaluator evaluator) {
            slicerEvaluator = (RolapEvaluator) evaluator;
        }

        protected Evaluator.NamedSetEvaluator evaluateNamedSet(
            final NamedSet namedSet,
            boolean create)
        {
            final String name = namedSet.getNameUniqueWithinQuery();
            RolapNamedSetEvaluator value;
            if (namedSet.isDynamic() && !create) {
                value = null;
            } else {
                value = namedSetEvaluators.get(name);
            }
            if (value == null) {
                value = new RolapNamedSetEvaluator(this, namedSet);
                namedSetEvaluators.put(name, value);
            }
            return value;
        }

        public Object getParameterValue(ParameterSlot slot) {
            if (slot.isParameterSet()) {
                return slot.getParameterValue();
            }

            // Look in other places for the value. Which places we look depends
            // on the scope of the parameter.
            Parameter.Scope scope = slot.getParameter().getScope();
            switch (scope) {
            case System:
                // TODO: implement system params

                // fall through
            case Schema:
                // TODO: implement schema params

                // fall through
            case Connection:
                // if it's set in the session, return that value

                // fall through
            case Statement:
                break;

            default:
                throw Util.badValue(scope);
            }

            // Not set in any accessible scope. Evaluate the default value,
            // then cache it.
            Object liftedValue = slot.getCachedDefaultValue();
            Object value;
            if (liftedValue != null) {
                if (liftedValue == CycleSentinel) {
                    throw MondrianResource.instance()
                        .CycleDuringParameterEvaluation.ex(
                            slot.getParameter().getName());
                }
                if (liftedValue == NullSentinel) {
                    value = null;
                } else {
                    value = liftedValue;
                }
                return value;
            }
            // Set value to a sentinel, so we can detect cyclic evaluation.
            slot.setCachedDefaultValue(CycleSentinel);
            value = result.evaluateExp(
                slot.getDefaultValueCalc(), slicerEvaluator.push());
            if (value == null) {
                liftedValue = NullSentinel;
            } else {
                liftedValue = value;
            }
            slot.setCachedDefaultValue(liftedValue);
            return value;
        }
    }

    /**
     * Formatter to convert values into formatted strings.
     *
     * <p>Every Cell has a value, a format string (or CellFormatter) and a
     * formatted value string.
     * There are a wide range of possible values (pick a Double, any
     * Double - its a value). Because there are lots of possible values,
     * there are also lots of possible formatted value strings. On the
     * other hand, there are only a very small number of format strings
     * and CellFormatter's. These formatters are to be cached
     * in a synchronized HashMaps in order to limit how many copies
     * need to be kept around.
     *
     * <p>
     * There are two implementations of the ValueFormatter interface:<ul>
     * <li>{@link CellFormatterValueFormatter}, which formats using a
     * user-registered {@link CellFormatter}; and
     * <li> {@link FormatValueFormatter}, which takes the {@link Locale} object.
     * </ul>
     */
    interface ValueFormatter {
        /**
         * Formats a value according to a format string.
         *
         * @param value Value
         * @param formatString Format string
         * @return Formatted value
         */
        String format(Object value, String formatString);

        /**
         * Formatter that always returns the empty string.
         */
        public static final ValueFormatter EMPTY = new ValueFormatter() {
            public String format(Object value, String formatString) {
                return "";
            }
        };
    }

    /**
     * A CellFormatterValueFormatter uses a user-defined {@link CellFormatter}
     * to format values.
     */
    static class CellFormatterValueFormatter implements ValueFormatter {
        final CellFormatter cf;

        /**
         * Creates a CellFormatterValueFormatter
         *
         * @param cf Cell formatter
         */
        CellFormatterValueFormatter(CellFormatter cf) {
            this.cf = cf;
        }
        public String format(Object value, String formatString) {
            return cf.formatCell(value);
        }
    }

    /**
     * A FormatValueFormatter takes a {@link Locale}
     * as a parameter and uses it to get the {@link mondrian.util.Format}
     * to be used in formatting an Object value with a
     * given format string.
     */
    static class FormatValueFormatter implements ValueFormatter {
        final Locale locale;

        /**
         * Creates a FormatValueFormatter.
         *
         * @param locale Locale
         */
        FormatValueFormatter(Locale locale) {
            this.locale = locale;
        }
        public String format(Object value, String formatString) {
            if (value == Util.nullValue) {
                Format format = getFormat(formatString);
                return format.format(null);
            } else if (value instanceof Throwable) {
                return "#ERR: " + value.toString();
            } else if (value instanceof String) {
                return (String) value;
            } else {
                Format format = getFormat(formatString);
                return format.format(value);
            }
        }
        private Format getFormat(String formatString) {
            return Format.get(formatString, locale);
        }
    }

    /*
     * Generate a long ordinal based upon the values of the integers
     * stored in the cell position array. With this mechanism, the
     * Cell information can be stored using a long key (rather than
     * the array integer of positions) thus saving memory. The trick
     * is to use a 'large number' per axis in order to convert from
     * position array to long key where the 'large number' is greater
     * than the number of members in the axis.
     * The largest 'long' is java.lang.Long.MAX_VALUE which is
     * 9,223,372,036,854,776,000. The product of the maximum number
     * of members per axis must be less than this maximum 'long'
     * value (otherwise one gets hashing collisions).
     * <p>
     * For a single axis, the maximum number of members is equal to
     * the max 'long' number, 9,223,372,036,854,776,000.
     * <p>
     * For two axes, the maximum number of members is the square root
     * of the max 'long' number, 9,223,372,036,854,776,000, which is
     * slightly bigger than 2,147,483,647 (which is the maximum integer).
     * <p>
     * For three axes, the maximum number of members per axis is the
     * cube root of the max 'long' which is about 2,000,000
     * <p>
     * For four axes the forth root is about 50,000.
     * <p>
     * For five or more axes, the maximum number of members per axis
     * based upon the root of the maximum 'long' number,
     * start getting too small to guarantee that it will be
     * smaller than the number of members on a given axis and so
     * we must resort to the Map-base Cell container.
     */



    /**
     * Synchronized Map from Locale to ValueFormatter. It is expected that
     * there will be only a small number of Locale's.
     * Should these be a WeakHashMap?
     */
    protected static final Map<Locale, ValueFormatter>
            formatValueFormatters =
            Collections.synchronizedMap(new HashMap<Locale, ValueFormatter>());

    /**
     * Synchronized Map from CellFormatter to ValueFormatter.
     * CellFormatter's are defined in schema files. It is expected
     * the there will only be a small number of CellFormatter's.
     * Should these be a WeakHashMap?
     */
    protected static final Map<CellFormatter, ValueFormatter>
        cellFormatters =
            Collections.synchronizedMap(
                new HashMap<CellFormatter, ValueFormatter>());

    /**
     * A CellInfo contains all of the information that a Cell requires.
     * It is placed in the cellInfos map during evaluation and
     * serves as a constructor parameter for {@link RolapCell}.
     *
     * <p>During the evaluation stage they are mutable but after evaluation has
     * finished they are not changed.
     */
    static class CellInfo {
        Object value;
        String formatString;
        ValueFormatter valueFormatter;
        long key;

        /**
         * Creates a CellInfo representing the position of a cell.
         *
         * @param key Ordinal representing the position of a cell
         */
        CellInfo(long key) {
            this(key, null, null, ValueFormatter.EMPTY);
        }

        /**
         * Creates a CellInfo with position, value, format string and formatter
         * of a cell.
         *
         * @param key Ordinal representing the position of a cell
         * @param value Value of cell, or null if not yet known
         * @param formatString Format string of cell, or null
         * @param valueFormatter Formatter for cell, or null
         */
        CellInfo(
            long key,
            Object value,
            String formatString,
            ValueFormatter valueFormatter)
        {
            this.key = key;
            this.value = value;
            this.formatString = formatString;
            this.valueFormatter = valueFormatter;
        }

        public int hashCode() {
            // Combine the upper 32 bits of the key with the lower 32 bits.
            // We used to use 'key ^ (key >>> 32)' but that was bad, because
            // CellKey.Two encodes (i, j) as
            // (i * Integer.MAX_VALUE + j), which is practically the same as
            // (i << 32, j). If i and j were
            // both k bits long, all of the hashcodes were k bits long too!
            return (int) (key ^ (key >>> 11) ^ (key >>> 24));
        }

        public boolean equals(Object o) {
            if (o instanceof CellInfo) {
                CellInfo that = (CellInfo) o;
                return that.key == this.key;
            } else {
                return false;
            }
        }

        /**
         * Returns the formatted value of the Cell
         * @return formatted value of the Cell
         */
        String getFormatValue() {
            return valueFormatter.format(value, formatString);
        }
    }

    /**
     * API for the creation and
     * lookup of {@link CellInfo} objects. There are two implementations,
     * one that uses a Map for storage and the other uses an ObjectPool.
     */
    interface CellInfoContainer {
        /**
         * Returns the number of CellInfo objects in this container.
         * @return  the number of CellInfo objects.
         */
        int size();
        /**
         * Reduces the size of the internal data structures needed to
         * support the current entries. This should be called after
         * all CellInfo objects have been added to container.
         */
        void trimToSize();
        /**
         * Removes all CellInfo objects from container. Does not
         * change the size of the internal data structures.
         */
        void clear();
        /**
         * Creates a new CellInfo object, adds it to the container
         * a location <code>pos</code> and returns it.
         *
         * @param pos where to store CellInfo object.
         * @return the newly create CellInfo object.
         */
        CellInfo create(int[] pos);
        /**
         * Gets the CellInfo object at the location <code>pos</code>.
         *
         * @param pos where to find the CellInfo object.
         * @return the CellInfo found or null.
         */
        CellInfo lookup(int[] pos);
    }

    /**
     * Implementation of {@link CellInfoContainer} which uses a {@link Map} to
     * store CellInfo Objects.
     *
     * <p>Note that the CellKey point instance variable is the same
     * Object (NOT a copy) that is used and modified during
     * the recursive calls to executeStripe - the
     * <code>create</code> method relies on this fact.
     */
    static class CellInfoMap implements CellInfoContainer {
        private final Map<CellKey, CellInfo> cellInfoMap;
        private final CellKey point;

        /**
         * Creates a CellInfoMap
         *
         * @param point Cell position
         */
        CellInfoMap(CellKey point) {
            this.point = point;
            this.cellInfoMap = new HashMap<CellKey, CellInfo>();
        }
        public int size() {
            return this.cellInfoMap.size();
        }
        public void trimToSize() {
            // empty
        }
        public void clear() {
            this.cellInfoMap.clear();
        }
        public CellInfo create(int[] pos) {
            CellKey key = this.point.copy();
            CellInfo ci = this.cellInfoMap.get(key);
            if (ci == null) {
                ci = new CellInfo(0);
                this.cellInfoMap.put(key, ci);
            }
            return ci;
        }
        public CellInfo lookup(int[] pos) {
            CellKey key = CellKey.Generator.newCellKey(pos);
            return this.cellInfoMap.get(key);
        }
    }

    /**
     * Implementation of {@link CellInfoContainer} which uses an
     * {@link ObjectPool} to store {@link CellInfo} Objects.
     *
     * <p>There is an inner interface (<code>CellKeyMaker</code>) and
     * implementations for 0 through 4 axes that convert the Cell
     * position integer array into a long.
     *
     * <p>
     * It should be noted that there is an alternate approach.
     * As the <code>executeStripe</code>
     * method is recursively called, at each call it is known which
     * axis is being iterated across and it is known whether or
     * not the Position object for that axis is a List or just
     * an Iterable. It it is a List, then one knows the real
     * size of the axis. If it is an Iterable, then one has to
     * use one of the MAX_AXIS_SIZE values. Given that this information
     * is available when one recursives down to the next
     * <code>executeStripe</code> call, the Cell ordinal, the position
     * integer array could converted to an <code>long</code>, could
     * be generated on the call stack!! Just a thought for the future.
     */
    static class CellInfoPool implements CellInfoContainer {
        /**
         * The maximum number of Members, 2,147,483,647, that can be any given
         * Axis when the number of Axes is 2.
         */
        protected static final long MAX_AXIS_SIZE_2 = 2147483647;
        /**
         * The maximum number of Members, 2,000,000, that can be any given
         * Axis when the number of Axes is 3.
         */
        protected static final long MAX_AXIS_SIZE_3 = 2000000;
        /**
         * The maximum number of Members, 50,000, that can be any given
         * Axis when the number of Axes is 4.
         */
        protected static final long MAX_AXIS_SIZE_4 = 50000;

        /**
         * Implementations of CellKeyMaker convert the Cell
         * position integer array to a <code>long</code>.
         */
        interface CellKeyMaker {
            long generate(int[] pos);
        }
        /**
         * For axis of size 0.
         */
        static class Zero implements CellKeyMaker {
            public long generate(int[] pos) {
                return 0;
            }
        }
        /**
         * For axis of size 1.
         */
        static class One implements CellKeyMaker {
            public long generate(int[] pos) {
                return pos[0];
            }
        }
        /**
         * For axis of size 2.
         */
        static class Two implements CellKeyMaker {
            public long generate(int[] pos) {
                long l = pos[0];
                l += (MAX_AXIS_SIZE_2 * (long) pos[1]);
                return l;
            }
        }
        /**
         * For axis of size 3.
         */
        static class Three implements CellKeyMaker {
            public long generate(int[] pos) {
                long l = pos[0];
                l += (MAX_AXIS_SIZE_3 * (long) pos[1]);
                l += (MAX_AXIS_SIZE_3 * MAX_AXIS_SIZE_3 * (long) pos[2]);
                return l;
            }
        }
        /**
         * For axis of size 4.
         */
        static class Four implements CellKeyMaker {
            public long generate(int[] pos) {
                long l = pos[0];
                l += (MAX_AXIS_SIZE_4 * (long) pos[1]);
                l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * (long) pos[2]);
                l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4
                      * (long) pos[3]);
                return l;
            }
        }

        private final ObjectPool<CellInfo> cellInfoPool;
        private final CellKeyMaker cellKeyMaker;

        CellInfoPool(int axisLength) {
            this.cellInfoPool = new ObjectPool<CellInfo>();
            this.cellKeyMaker = createCellKeyMaker(axisLength);
        }

        CellInfoPool(int axisLength, int initialSize) {
            this.cellInfoPool = new ObjectPool<CellInfo>(initialSize);
            this.cellKeyMaker = createCellKeyMaker(axisLength);
        }

        private static CellKeyMaker createCellKeyMaker(int axisLength) {
            switch (axisLength) {
            case 0:
                return new Zero();
            case 1:
                return new One();
            case 2:
                return new Two();
            case 3:
                return new Three();
            case 4:
                return new Four();
            default:
                throw new RuntimeException(
                    "Creating CellInfoPool with axisLength=" + axisLength);
            }
        }

        public int size() {
            return this.cellInfoPool.size();
        }
        public void trimToSize() {
            this.cellInfoPool.trimToSize();
        }
        public void clear() {
            this.cellInfoPool.clear();
        }
        public CellInfo create(int[] pos) {
            long key = this.cellKeyMaker.generate(pos);
            return this.cellInfoPool.add(new CellInfo(key));
        }
        public CellInfo lookup(int[] pos) {
            long key = this.cellKeyMaker.generate(pos);
            return this.cellInfoPool.add(new CellInfo(key));
        }
    }

    static Axis mergeAxes(
        Axis axis1,
        Axis axis2,
        RolapEvaluator evaluator,
        boolean ordered)
    {
        if (axis1 == null) {
            return axis2;
        }
        List<Position> posList1 = axis1.getPositions();
        List<Position> posList2 = axis2.getPositions();
        int arrayLen = -1;
        if (posList1 instanceof RolapAxis.PositionListBase) {
            if (posList1.isEmpty()) {
                return axis2;
            }
            arrayLen = posList1.get(0).size();
        }
        if (axis1 instanceof RolapAxis.SingleEmptyPosition) {
            return axis2;
        }
        if (axis1 instanceof RolapAxis.NoPosition) {
            return axis2;
        }
        if (posList2 instanceof RolapAxis.PositionListBase) {
            if (posList2.isEmpty()) {
                return axis1;
            }
            arrayLen = posList2.get(0).size();
        }
        if (axis2 instanceof RolapAxis.SingleEmptyPosition) {
            return axis1;
        }
        if (axis2 instanceof RolapAxis.NoPosition) {
            return axis1;
        }
        if (arrayLen == -1) {
            // Avoid materialization of axis
            arrayLen = 0;
            for (Position p1 : posList1) {
                arrayLen += p1.size();
                break;
            }
            // reset to start of List
            posList1 = axis1.getPositions();
        }
        if (arrayLen == 1) {
            // single Member per position

            // LinkedHashSet gives O(n log n) additions (versus O(n ^ 2) for
            // ArrayList, and preserves order (versus regular HashSet).
            LinkedHashSet<Member> orderedSet = new LinkedHashSet<Member>();
            for (Position p1 : posList1) {
                for (Member m1 : p1) {
                    orderedSet.add(m1);
                }
            }
            for (Position p2 : posList2) {
                for (Member m2 : p2) {
                    orderedSet.add(m2);
                }
            }
            return new RolapAxis.MemberList(
                Arrays.asList(
                    orderedSet.toArray(new Member[orderedSet.size()])));
        } else {
            // array of Members per position

            Set<List<Member>> set = new HashSet<List<Member>>();
            List<Member[]> list = new ArrayList<Member[]>();
            for (Position p1 : posList1) {
                if (set.add(p1)) {
                    Member[] members = new Member[arrayLen];
                    for (int i = 0; i < p1.size(); i++) {
                        members[i] = p1.get(i);
                    }
                    list.add(members);
                }
            }
            int halfWay = list.size();
            for (Position p2 : posList2) {
                if (set.add(p2)) {
                    Member[] members = new Member[arrayLen];
                    for (int i = 0; i < p2.size(); i++) {
                        Member m2 = p2.get(i);
                        members[i] = m2;
                    }
                    list.add(members);
                }
            }

            // if there are unique members on both axes and no order function,
            //  sort the list to ensure default order
            if (halfWay > 0 && halfWay < list.size() && !ordered) {
                Member[] membs = list.get(0);
                int membsSize = membs.length;
                ValueCalc valCalc =
                    new ValueCalc(
                        new DummyExp(new ScalarType()));
                FunUtil.sortTuples(
                    evaluator,
                    list,
                    list,
                    valCalc,
                    false,
                    false,
                    membsSize);
            }

            return new RolapAxis.MemberArrayList(list);
        }
    }

}

// End RolapResult.java
TOP

Related Classes of mondrian.rolap.RolapResult$CellInfoPool$Zero

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