001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/RolapResult.java#123 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2001-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2008 Julian Hyde and others
008    // All Rights Reserved.
009    // You must accept the terms of that agreement to use this software.
010    //
011    // jhyde, 10 August, 2001
012    */
013    
014    package mondrian.rolap;
015    import java.util.ArrayList;
016    import java.util.Collections;
017    import java.util.HashMap;
018    import java.util.Iterator;
019    import java.util.List;
020    import java.util.ListIterator;
021    import java.util.Locale;
022    import java.util.Map;
023    
024    import mondrian.calc.Calc;
025    import mondrian.calc.DummyExp;
026    import mondrian.calc.ParameterSlot;
027    import mondrian.calc.ResultStyle;
028    import mondrian.calc.impl.ValueCalc;
029    import mondrian.olap.Axis;
030    import mondrian.olap.Cell;
031    import mondrian.olap.CellFormatter;
032    import mondrian.olap.Cube;
033    import mondrian.olap.Dimension;
034    import mondrian.olap.DimensionType;
035    import mondrian.olap.Evaluator;
036    import mondrian.olap.Exp;
037    import mondrian.olap.Hierarchy;
038    import mondrian.olap.Member;
039    import mondrian.olap.MondrianProperties;
040    import mondrian.olap.Parameter;
041    import mondrian.olap.Position;
042    import mondrian.olap.Query;
043    import mondrian.olap.QueryAxis;
044    import mondrian.olap.ResultBase;
045    import mondrian.olap.ResultLimitExceededException;
046    import mondrian.olap.SchemaReader;
047    import mondrian.olap.Util;
048    import mondrian.olap.fun.FunUtil;
049    import mondrian.olap.fun.MondrianEvaluationException;
050    import mondrian.olap.type.ScalarType;
051    import mondrian.resource.MondrianResource;
052    import mondrian.rolap.agg.AggregationManager;
053    import mondrian.util.ConcatenableList;
054    import mondrian.util.Format;
055    import mondrian.util.ObjectPool;
056    
057    import org.apache.log4j.Logger;
058    
059    /**
060     * A <code>RolapResult</code> is the result of running a query.
061     *
062     * @author jhyde
063     * @since 10 August, 2001
064     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapResult.java#123 $
065     */
066    public class RolapResult extends ResultBase {
067    
068        private static final Logger LOGGER = Logger.getLogger(ResultBase.class);
069    
070        private RolapEvaluator evaluator;
071        private final CellKey point;
072    
073        private CellInfoContainer cellInfos;
074        private FastBatchingCellReader batchingReader;
075        private final CellReader aggregatingReader =
076            AggregationManager.instance().getCacheCellReader();
077        private Modulos modulos = null;
078        private final int maxEvalDepth =
079                MondrianProperties.instance().MaxEvalDepth.get();
080    
081        private final Map<Integer, Boolean> positionsHighCardinality =
082            new HashMap<Integer, Boolean>();
083        private final Map<Integer, Iterator<Position>> positionsIterators =
084            new HashMap<Integer, Iterator<Position>>();
085        private final Map<Integer, Integer> positionsIndexes =
086            new HashMap<Integer, Integer>();
087        private final Map<Integer, List<Position>> positionsCurrent =
088            new HashMap<Integer, List<Position>>();
089    
090        RolapResult(Query query, boolean execute) {
091            super(query, new Axis[query.axes.length]);
092    
093            this.point = CellKey.Generator.newCellKey(query.axes.length);
094            final int expDeps = MondrianProperties.instance().TestExpDependencies.get();
095            if (expDeps > 0) {
096                this.evaluator = new RolapDependencyTestingEvaluator(this, expDeps);
097            } else {
098                final RolapEvaluator.RolapEvaluatorRoot root =
099                        new RolapResultEvaluatorRoot(this);
100                this.evaluator = new RolapEvaluator(root);
101            }
102            RolapCube rcube = (RolapCube) query.getCube();
103            this.batchingReader = new FastBatchingCellReader(rcube);
104    
105            this.cellInfos = (query.axes.length > 4)
106                ?  new CellInfoMap(point) : new CellInfoPool(query.axes.length);
107    
108    
109            if (!execute) {
110                return;
111            }
112    
113            boolean normalExecution = true;
114            try {
115                // This call to clear the cube's cache only has an
116                // effect if caching has been disabled, otherwise
117                // nothing happens.
118                // Clear the local cache before a query has run
119                rcube.clearCachedAggregations();
120                // Check if there are modifications to the global aggregate cache
121                rcube.checkAggregateModifications();
122    
123    
124                /////////////////////////////////////////////////////////////////
125                //
126                // Evaluation Algorithm
127                //
128                // There are three basic steps to the evaluation algorithm:
129                // 1) Determine all Members for each axis but do not save
130                // information (do not build the RolapAxis),
131                // 2) Save all Members for each axis (build RolapAxis).
132                // 3) Evaluate and store each Cell determined by the Members
133                // of the axes.
134                // Step 1 converges on the stable set of Members pre axis.
135                // Steps 1 and 2 make sure that the data has been loaded.
136                //
137                // More detail follows.
138                //
139                // Explicit and Implicit Members:
140                // A Member is said to be 'explicit' if it appears on one of
141                // the Axes (one of the RolapAxis Position List of Members).
142                // A Member is 'implicit' if it is in the query but does not
143                // end up on any Axes (its usage, for example, is in a function).
144                // When for a Dimension none of its Members are explicit in the
145                // query, then the default Member is used which is like putting
146                // the Member in the Slicer.
147                //
148                // Special Dimensions:
149                // There are 2 special dimensions.
150                // The first is the Time dimension. If in a schema there is
151                // no ALL Member, then Whatever happens to be the default
152                // Member is used if Time Members are not explicitly set
153                // in the query.
154                // The second is the Measures dimension. This dimension
155                // NEVER has an ALL Member. A cube's default Measure is set
156                // by convention - its simply the first Measure defined in the
157                // cube.
158                //
159                // First a RolapEvaluator is created. During its creation,
160                // it gets a Member from each Hierarchy. Each Member is the
161                // default Member of the Hierarchy. For most Hierarchies this
162                // Member is the ALL Member, but there are cases where 1)
163                // a Hierarchy does not have an ALL Member or 2) the Hierarchy
164                // has an ALL Member but that Member is not the default Member.
165                // In these cases, the default Member is still used, but its
166                // use can cause evaluation issues (seemingly strange evaluation
167                // results).
168                //
169                // Next, load all root Members for Hierarchies that have no ALL
170                // Member and load ALL Members that are not the default Member.
171                //
172                // Determine the Members of the Slicer axis (Step 1 above).  Any
173                // Members found are added to the AxisMember object. If one of these
174                // Members happens to be a Measure, then the Slicer is explicitly
175                // specifying the query's Measure and this should be put into the
176                // evaluator's context (replacing the default Measure which just
177                // happens to be the first Measure defined in the cube).  Other
178                // Members found in the AxisMember object are also placed into the
179                // evaluator's context since these also are explicitly specified.
180                // Also, any other Members in the AxisMember object which have the
181                // same Hierarchy as Members in the list of root Members for
182                // Hierarchies that have no ALL Member, replace those Members - they
183                // Slicer has explicitly determined which ones to use. The
184                // AxisMember object is now cleared.
185                // The Slicer does not depend upon the other Axes, but the other
186                // Axes depend upon both the Slicer and each other.
187                //
188                // The AxisMember object also checks if the number of Members
189                // exceeds the ResultLimit property throwing a
190                // TotalMembersLimitExceeded Exception if it does.
191                //
192                // For all non-Slicer axes, the Members are determined (Step 1
193                // above). If a Measure is found in the AxisMember, then an
194                // Axis is explicitly specifying a Measure.
195                // If any Members in the AxisMember object have the same Hierarchy
196                // as a Member in the set of root Members for Hierarchies that have
197                // no ALL Member, then replace those root Members with the Member
198                // from the AxisMember object. In this case, again, a Member
199                // was explicitly specified in an Axis. If this replacement
200                // occurs, then one must redo this step with the new Members.
201                //
202                // Now Step 3 above is done. First to the Slicer Axis and then
203                // to the other Axes. Here the Axes are actually generated.
204                // If a Member of an Axis is an Calculated Member (and the
205                // Calculated Member is not a Member of the Measure Hierarchy),
206                // then find the Dimension associated with the Calculated
207                // Member and remove Members with the same Dimension in the set of
208                // root Members for Hierarchies that have no ALL Member.
209                // This is done because via the Calculated Member the Member
210                // was implicitly specified in the query. If this removal occurs,
211                // then the Axes must be re-evaluated repeating Step 3.
212                //
213                /////////////////////////////////////////////////////////////////
214    
215    
216                // The AxisMember object is used to hold Members that are found
217                // during Step 1 when the Axes are determined.
218                final AxisMember axisMembers = new AxisMember();
219    
220    
221                // list of ALL Members that are not default Members
222                final List<Member> nonDefaultAllMembers = new ArrayList<Member>();
223    
224                // List of Members of Hierarchies that do not have an ALL Member
225                List<List<Member>> nonAllMembers = new ArrayList<List<Member>>();
226    
227                // List of Measures
228                final List<Member> measureMembers = new ArrayList<Member>();
229    
230                // load all root Members for Hierarchies that have no ALL
231                // Member and load ALL Members that are not the default Member.
232                // Also, all Measures are are gathered.
233                loadSpecialMembers(nonDefaultAllMembers,
234                                    nonAllMembers, measureMembers);
235    
236                // clear evaluation cache
237                query.clearEvalCache();
238    
239                // Save, may be needed by some Expression Calc's
240                query.putEvalCache("ALL_MEMBER_LIST", nonDefaultAllMembers);
241    
242    
243                final List<List<Member>> emptyNonAllMembers = Collections.emptyList();
244    
245                /////////////////////////////////////////////////////////////////
246                //
247                // Determine Slicer
248                //
249                axisMembers.setSlicer(true);
250                loadMembers(emptyNonAllMembers, evaluator,
251                            query.slicerAxis, query.slicerCalc, axisMembers);
252                axisMembers.setSlicer(false);
253    
254                if (! axisMembers.isEmpty()) {
255                    for (Member m : axisMembers) {
256                        if (m == null) {
257                            break;
258                        }
259                        evaluator.setSlicerContext(m);
260                        if (m.isMeasure()) {
261                            // A Measure was explicitly declared in the
262                            // Slicer, don't need to worry about Measures
263                            // for this query.
264                            measureMembers.clear();
265                        }
266                    }
267                    replaceNonAllMembers(nonAllMembers, axisMembers);
268                    axisMembers.clearMembers();
269                }
270                //
271                /////////////////////////////////////////////////////////////////
272    
273    
274                /////////////////////////////////////////////////////////////////
275                //
276                // Determine Axes
277                //
278                boolean changed = false;
279    
280                // reset to total member count
281                axisMembers.clearTotalCellCount();
282    
283                for (int i = 0; i < axes.length; i++) {
284                    final QueryAxis axis = query.axes[i];
285                    final Calc calc = query.axisCalcs[i];
286                    loadMembers(emptyNonAllMembers, evaluator,
287                                axis, calc, axisMembers);
288                }
289    
290                if (! axisMembers.isEmpty()) {
291                    for (Member m : axisMembers) {
292                        if (m.isMeasure()) {
293                            // A Measure was explicitly declared on an
294                            // axis, don't need to worry about Measures
295                            // for this query.
296                            measureMembers.clear();
297                        }
298                    }
299                    changed = replaceNonAllMembers(nonAllMembers, axisMembers);
300                    axisMembers.clearMembers();
301                }
302    
303    /*
304                // This code allows replacing default Measure Member
305                // with one found in the query, an implicit Measure.
306                // Fixes some problems (RolapResultTest._testNullDefaultMeasure)
307                // but causes other ones (NamedSetTest.testNamedSetUsedInCrossJoin)
308                // so it can not be used.
309                Member previous = null;
310                if (! measureMembers.isEmpty()) {
311                     MeasureVisitor visitor = new MeasureVisitor();
312                    for (int i = 0; i < axes.length; i++) {
313                        query.axes[i].accept(visitor);
314                    }
315                    println("Measures on Axis:");
316                    for (Member m : visitor.measures) {
317                        println("   m=" +m.getUniqueName());
318                    }
319                    if (! visitor.measures.isEmpty()) {
320                        Member m = visitor.measures.get(0);
321                        previous = evaluator.setContext(m);
322                        changed |= ! m.equals(previous);
323                    }
324                }
325                // move this
326                // it should be set just before calling executeBody
327                // evaluator.setContext(previous);
328    */
329    
330    
331                if (changed) {
332                    // only count number of members, do not collect any
333                    axisMembers.countOnly(true);
334                    // reset to total member count
335                    axisMembers.clearTotalCellCount();
336    
337                    for (int i = 0; i < axes.length; i++) {
338                        final QueryAxis axis = query.axes[i];
339                        final Calc calc = query.axisCalcs[i];
340                        loadMembers(
341                            nonAllMembers,
342                            evaluator.push(),
343                            axis, calc, axisMembers);
344                    }
345                }
346    
347                // throws exception if number of members exceeds limit
348                axisMembers.checkLimit();
349    
350                //
351                /////////////////////////////////////////////////////////////////
352    
353                /////////////////////////////////////////////////////////////////
354                //
355                // Execute Slicer
356                //
357                this.slicerAxis =
358                    evalExecute(
359                        nonAllMembers,
360                        nonAllMembers.size() - 1,
361                        evaluator.push(),
362                        query.slicerAxis,
363                        query.slicerCalc);
364    
365                // Use the context created by the slicer for the other
366                // axes.  For example, "select filter([Customers], [Store
367                // Sales] > 100) on columns from Sales where
368                // ([Time].[1998])" should show customers whose 1998 (not
369                // total) purchases exceeded 100.
370    
371                // Getting the Position list's size and the Position
372                // at index == 0 will, in fact, cause an Iterable-base
373                // Axis Position List to become a List-base Axis
374                // Position List (and increae memory usage), but for
375                // the slicer axis, the number of Positions is very
376                // small, so who cares.
377                switch (this.slicerAxis.getPositions().size()) {
378                case 0:
379                    throw MondrianResource.instance().EmptySlicer.ex();
380                case 1:
381                    break;
382                default:
383                    throw MondrianResource.instance().CompoundSlicer.ex();
384                }
385                //
386                /////////////////////////////////////////////////////////////////
387    
388                /////////////////////////////////////////////////////////////////
389                //
390                // Execute Axes
391                //
392                boolean redo = true;
393                while (redo) {
394                    RolapEvaluator e = evaluator.push();
395                    redo = false;
396    
397                    for (int i = 0; i < axes.length; i++) {
398                        QueryAxis axis = query.axes[i];
399                        final Calc calc = query.axisCalcs[i];
400                        Axis axisResult =
401                            evalExecute(
402                                nonAllMembers,
403                                nonAllMembers.size() - 1, e, axis, calc);
404    
405                        if (! nonAllMembers.isEmpty()) {
406                            List<Position> pl = axisResult.getPositions();
407                            if (!pl.isEmpty()) {
408                                // Only need to process the first Position
409                                Position p = pl.get(0);
410                                for (Member m : p) {
411                                    if (m.isCalculated()) {
412                                        CalculatedMeasureVisitor visitor =
413                                            new CalculatedMeasureVisitor();
414                                        m.getExpression().accept(visitor);
415                                        Dimension dimension = visitor.dimension;
416                                        redo = removeDimension(dimension, nonAllMembers);
417                                    }
418                                }
419                            }
420                        }
421                        this.axes[i] = axisResult;
422                    }
423                }
424                //
425                /////////////////////////////////////////////////////////////////
426    
427                /////////////////////////////////////////////////////////////////
428                //
429                // Get value for each Cell
430                //
431                executeBody(this.query, new int[axes.length]);
432                //
433                /////////////////////////////////////////////////////////////////
434    
435                // If you are very close to running out of memory due to
436                // the number of CellInfo's in cellInfos, then calling this
437                // may cause the out of memory one is trying to aviod.
438                // On the other hand, calling this can reduce the size of
439                // the ObjectPool's internal storage by half (but, of course,
440                // it will not reduce the size of the stored objects themselves).
441                // Only call this if there are lots of CellInfo.
442                if (this.cellInfos.size() > 10000) {
443                    this.cellInfos.trimToSize();
444                }
445    
446            } catch (ResultLimitExceededException ex) {
447                // If one gets a ResultLimitExceededException, then
448                // don't count on anything being worth caching.
449                normalExecution = false;
450    
451                // De-reference data structures that might be holding
452                // partial results but surely are taking up memory.
453                evaluator = null;
454                cellInfos = null;
455                batchingReader = null;
456                for (int i = 0; i < axes.length; i++) {
457                    axes[i] = null;
458                }
459                slicerAxis = null;
460    
461                query.clearEvalCache();
462    
463                throw ex;
464    
465            } finally {
466                if (normalExecution) {
467                    // Push all modifications to the aggregate cache to the global
468                    // cache so each thread can start using it
469                    rcube.pushAggregateModificationsToGlobalCache();
470    
471                    // Expression cache duration is for each query. It is time to
472                    // clear out the whole expression cache at the end of a query.
473                    evaluator.clearExpResultCache(true);
474                }
475                if (LOGGER.isDebugEnabled()) {
476                    LOGGER.debug("RolapResult<init>: " + Util.printMemory());
477                }
478            }
479        }
480        protected boolean removeDimension(Dimension dimension, List<List<Member>> nonAllMembers) {
481            boolean changed = false;
482            for (ListIterator<List<Member>> it = nonAllMembers.listIterator();
483                    it.hasNext();) {
484                List<Member> ms = it.next();
485                Dimension d = ms.get(0).getHierarchy().getDimension();
486                if (d.equals(dimension)) {
487                    it.remove();
488                }
489            }
490            return changed;
491        }
492    
493        private static class CalculatedMeasureVisitor extends mondrian.mdx.MdxVisitorImpl {
494            Dimension dimension;
495            CalculatedMeasureVisitor() {
496            }
497            public Object visit(mondrian.olap.Formula formula) {
498                return null;
499            }
500            public Object visit(mondrian.mdx.ResolvedFunCall call) {
501                return null;
502            }
503            public Object visit(mondrian.olap.Id id) {
504                return null;
505            }
506            public Object visit(mondrian.mdx.ParameterExpr parameterExpr) {
507                return null;
508            }
509            public Object visit(mondrian.mdx.DimensionExpr dimensionExpr) {
510                dimension = dimensionExpr.getDimension();
511                return null;
512            }
513            public Object visit(mondrian.mdx.HierarchyExpr hierarchyExpr) {
514                Hierarchy hierarchy = hierarchyExpr.getHierarchy();
515                dimension = hierarchy.getDimension();
516                return null;
517            }
518            public Object visit(mondrian.mdx.LevelExpr levelExpr) {
519                return null;
520            }
521            public Object visit(mondrian.mdx.MemberExpr memberExpr)  {
522                Member member = memberExpr.getMember();
523                dimension = member.getHierarchy().getDimension();
524                return null;
525            }
526            public Object visit(mondrian.mdx.NamedSetExpr namedSetExpr) {
527                return null;
528            }
529            public Object visit(mondrian.olap.Literal literal) {
530                return null;
531            }
532        }
533    
534        protected boolean replaceNonAllMembers(List<List<Member>> nonAllMembers,
535                                                AxisMember axisMembers) {
536    
537            boolean changed = false;
538            List<Member> mList = new ArrayList<Member>();
539            for (ListIterator<List<Member>> it = nonAllMembers.listIterator();
540                    it.hasNext();) {
541                List<Member> ms = it.next();
542                Hierarchy h = ms.get(0).getHierarchy();
543                mList.clear();
544                for (Member m : axisMembers) {
545                    if (m.getHierarchy().equals(h)) {
546                        mList.add(m);
547                    }
548                }
549                if (! mList.isEmpty()) {
550                    changed = true;
551                    it.set(mList);
552                }
553            }
554            return changed;
555    
556        }
557    
558        protected void loadMembers(List<List<Member>> nonAllMembers,
559                        RolapEvaluator evaluator, QueryAxis axis, Calc calc,
560                        AxisMember axisMembers) {
561            int attempt = 0;
562            evaluator.setCellReader(batchingReader);
563            while (true) {
564                axisMembers.clearAxisCount();
565                evalLoad(
566                    nonAllMembers, nonAllMembers.size() - 1,
567                    evaluator, axis, calc, axisMembers);
568    
569                if (!batchingReader.loadAggregations(query)) {
570                    break;
571                } else {
572                    // Clear invalid expression result so that the next evaluation
573                    // will pick up the newly loaded aggregates.
574                    evaluator.clearExpResultCache(false);
575                }
576    
577                if (attempt++ > maxEvalDepth) {
578                    throw Util.newInternal(
579                        "Failed to load all aggregations after " +
580                        maxEvalDepth +
581                        " passes; there's probably a cycle");
582                }
583            }
584        }
585    
586        void evalLoad(List<List<Member>> nonAllMembers, int cnt,
587                Evaluator evaluator, QueryAxis axis, Calc calc,
588                AxisMember axisMembers) {
589            if (cnt < 0) {
590                executeAxis(evaluator.push(), axis, calc, false, axisMembers);
591            } else {
592                for (Member m : nonAllMembers.get(cnt)) {
593                    evaluator.setContext(m);
594                    evalLoad(nonAllMembers, cnt - 1, evaluator, axis, calc, axisMembers);
595                }
596            }
597        }
598        Axis evalExecute(List<List<Member>> nonAllMembers, int cnt,
599                RolapEvaluator evaluator, QueryAxis axis, Calc calc) {
600            Axis axisResult = null;
601            if (cnt < 0) {
602                evaluator.setCellReader(aggregatingReader);
603                axisResult =
604                        executeAxis(evaluator.push(), axis, calc, true, null);
605                // No need to clear expression cache here as no new aggregates are
606                // loaded(aggregatingReader reads from cache).
607            } else {
608                for (Member m : nonAllMembers.get(cnt)) {
609                    evaluator.setContext(m);
610                    Axis a = evalExecute(nonAllMembers, cnt - 1, evaluator, axis, calc);
611                    boolean ordered = false;
612                    if (axis != null) {
613                        ordered = axis.isOrdered();
614                    }
615                    axisResult = mergeAxes(axisResult, a, evaluator, ordered);
616                }
617            }
618            return axisResult;
619        }
620    
621        /**
622         * Finds all root Members 1) whose Hierarchy does not have an ALL
623         * Member, 2) whose default Member is not the ALL Member and 3)
624         * all Measures.
625         *
626         * @param nonDefaultAllMembers  List of all root Members for Hierarchies
627         * whose default Member is not the ALL Member.
628         * @param nonAllMembers List of root Members for Hierarchies that have no
629         * ALL Member.
630         * @param measureMembers  List all Measures
631         */
632        protected void loadSpecialMembers(
633            List<Member> nonDefaultAllMembers,
634            List<List<Member>> nonAllMembers,
635            List<Member> measureMembers)
636        {
637            SchemaReader schemaReader = evaluator.getSchemaReader();
638            Member[] evalMembers = evaluator.getMembers();
639            for (Member em : evalMembers) {
640                if (em.isCalculated()) {
641                    continue;
642                }
643                Hierarchy h = em.getHierarchy();
644                Dimension d = h.getDimension();
645                if (d.getDimensionType() == DimensionType.TimeDimension) {
646                    continue;
647                }
648                if (!em.isAll()) {
649                    List<Member> rootMembers = schemaReader.getHierarchyRootMembers(h);
650                    if (em.isMeasure()) {
651                        for (Member mm : rootMembers) {
652                            measureMembers.add(mm);
653                        }
654                    } else {
655                        if (h.hasAll()) {
656                            for (Member m : rootMembers) {
657                                if (m.isAll()) {
658                                    nonDefaultAllMembers.add(m);
659                                    break;
660                                }
661                            }
662                        } else {
663                            nonAllMembers.add(rootMembers);
664                        }
665                    }
666                }
667            }
668        }
669    
670        protected Logger getLogger() {
671            return LOGGER;
672        }
673    
674        public final RolapCube getCube() {
675            return evaluator.getCube();
676        }
677    
678        // implement Result
679        public Axis[] getAxes() {
680            return axes;
681        }
682    
683        /**
684         * Get the Cell for the given Cell position.
685         *
686         * @param pos Cell position.
687         * @return the Cell associated with the Cell position.
688         */
689        public Cell getCell(int[] pos) {
690            if (pos.length != point.size()) {
691                throw Util.newError(
692                        "coordinates should have dimension " + point.size());
693            }
694    
695            for (int i = 0; i < pos.length; i++) {
696                if (positionsHighCardinality.get(i)) {
697                    executeBody(this.query, pos);
698                    break;
699                }
700            }
701    
702            CellInfo ci = cellInfos.lookup(pos);
703            if (ci.value == null) {
704                for (int i = 0; i < pos.length; i++) {
705                    int po = pos[i];
706                    if (po < 0 || po >= axes[i].getPositions().size()) {
707                        throw Util.newError("coordinates out of range");
708                    }
709                }
710                ci.value = Util.nullValue;
711            }
712    
713            return new RolapCell(this, pos.clone(), ci);
714        }
715    
716        private Axis executeAxis(
717            Evaluator evaluator,
718            QueryAxis axis,
719            Calc axisCalc,
720            boolean construct,
721            AxisMember axisMembers)
722        {
723            Axis axisResult = null;
724            if (axis == null) {
725                // Create an axis containing one position with no members (not
726                // the same as an empty axis).
727                if (construct) {
728                    axisResult = new RolapAxis.SingleEmptyPosition();
729                }
730    
731            } else {
732                evaluator.setNonEmpty(axis.isNonEmpty());
733                evaluator.setEvalAxes(true);
734                Object value = axisCalc.evaluate(evaluator);
735                if (axisCalc.getClass().getName().indexOf("OrderFunDef") != -1) {
736                    axis.setOrdered(true);
737                }
738                evaluator.setNonEmpty(false);
739                if (value != null) {
740                    // List or Iterable of Member or Member[]
741                    if (value instanceof List) {
742                        List<Object> list = (List) value;
743                        if (construct) {
744                            if (list.isEmpty()) {
745                                // should be???
746                                axisResult = new RolapAxis.NoPosition();
747                            } else if (list.get(0) instanceof Member[]) {
748                                axisResult =
749                                    new RolapAxis.MemberArrayList((List<Member[]>)value);
750                            } else {
751                                axisResult =
752                                    new RolapAxis.MemberList((List<Member>)value);
753                            }
754                        } else if (axisMembers != null) {
755                            axisMembers.merge(list);
756                        }
757                    } else {
758                        // Iterable
759                        Iterable<Object> iter = (Iterable) value;
760                        Iterator it = iter.iterator();
761                        if (construct) {
762                            if (! it.hasNext()) {
763                                axisResult = new RolapAxis.NoPosition();
764                            } else if (it.next() instanceof Member[]) {
765                                axisResult = new RolapAxis.MemberArrayIterable(
766                                    (Iterable<Member[]>)value);
767                            } else {
768                                axisResult = new RolapAxis.MemberIterable(
769                                    (Iterable<Member>)value);
770                            }
771                        } else if (axisMembers != null) {
772                            axisMembers.merge(it);
773                        }
774                    }
775                }
776                evaluator.setEvalAxes(false);
777            }
778            return axisResult;
779        }
780    
781        private void executeBody(Query query, final int[] pos) {
782            // Compute the cells several times. The first time, use a dummy
783            // evaluator which collects requests.
784            int count = 0;
785            while (true) {
786    
787                evaluator.setCellReader(batchingReader);
788                executeStripe(query.axes.length - 1, evaluator.push(), pos);
789    
790                // Retrieve the aggregations collected.
791                //
792                if (!batchingReader.loadAggregations(query)) {
793                    // We got all of the cells we needed, so the result must be
794                    // correct.
795                    return;
796                } else {
797                    // Clear invalid expression result so that the next evaluation
798                    // will pick up the newly loaded aggregates.
799                    evaluator.clearExpResultCache(false);
800                }
801    
802                if (count++ > maxEvalDepth) {
803                    if (evaluator instanceof RolapDependencyTestingEvaluator) {
804                        // The dependency testing evaluator can trigger new
805                        // requests every cycle. So let is run as normal for
806                        // the first N times, then run it disabled.
807                        ((RolapDependencyTestingEvaluator.DteRoot)
808                            evaluator.root).disabled = true;
809                        if (count > maxEvalDepth * 2) {
810                            throw Util.newInternal("Query required more than "
811                                + count + " iterations");
812                        }
813                    } else {
814                        throw Util.newInternal("Query required more than "
815                            + count + " iterations");
816                    }
817                }
818    
819                cellInfos.clear();
820            }
821        }
822    
823        boolean isDirty() {
824            return batchingReader.isDirty();
825        }
826    
827        private Object evaluateExp(Calc calc, RolapEvaluator evaluator) {
828            int attempt = 0;
829            boolean dirty = batchingReader.isDirty();
830            while (true) {
831                RolapEvaluator ev = evaluator.push();
832    
833                ev.setCellReader(batchingReader);
834                Object preliminaryValue = calc.evaluate(ev);
835                Util.discard(preliminaryValue);
836    
837                if (!batchingReader.loadAggregations(evaluator.getQuery())) {
838                    break;
839                } else {
840                    // Clear invalid expression result so that the next evaluation
841                    // will pick up the newly loaded aggregates.
842                    ev.clearExpResultCache(false);
843                }
844    
845                if (attempt++ > maxEvalDepth) {
846                    throw Util.newInternal(
847                            "Failed to load all aggregations after " +
848                            maxEvalDepth + "passes; there's probably a cycle");
849                }
850            }
851    
852            // If there were pending reads when we entered, some of the other
853            // expressions may have been evaluated incorrectly. Set the reader's
854            // 'dirty' flag so that the caller knows that it must re-evaluate them.
855            if (dirty) {
856                batchingReader.setDirty(true);
857            }
858    
859            RolapEvaluator ev = evaluator.push();
860            ev.setCellReader(aggregatingReader);
861            return calc.evaluate(ev);
862        }
863    
864        private void executeStripe(
865            int axisOrdinal,
866            RolapEvaluator revaluator,
867            final int[] pos)
868        {
869            if (axisOrdinal < 0) {
870                Axis axis = slicerAxis;
871                List<Position> positions = axis.getPositions();
872                for (Position position : positions) {
873                    getQuery().checkCancelOrTimeout();
874                    revaluator.setContext(position);
875                    Object o;
876                    try {
877                        o = revaluator.evaluateCurrent();
878                    } catch (MondrianEvaluationException e) {
879                        LOGGER.warn("Mondrian: exception in executeStripe.", e);
880                        o = e;
881                    }
882    
883                    CellInfo ci = null;
884    
885                    // Get the Cell's format string and value formatting
886                    // Object.
887                    try {
888                        // This code is a combination of the code found in
889                        // the old RolapResult
890                        // <code>getCellNoDefaultFormatString</code> method and
891                        // the old RolapCell <code>getFormattedValue</code> method.
892    
893                        // Create a CellInfo object for the given position
894                        // integer array.
895                        ci = cellInfos.create(point.getOrdinals());
896    
897                        String cachedFormatString = null;
898                        ValueFormatter valueFormatter;
899    
900                        // Determine if there is a CellFormatter registered for
901                        // the current Cube's Measure's Dimension. If so,
902                        // then find or create a CellFormatterValueFormatter
903                        // for it. If not, then find or create a Locale based
904                        // FormatValueFormatter.
905                        final RolapCube cube = getCube();
906                        Dimension measuresDim =
907                            cube.getMeasuresHierarchy().getDimension();
908                        RolapMeasure m =
909                            (RolapMeasure) revaluator.getContext(measuresDim);
910                        CellFormatter cf = m.getFormatter();
911                        if (cf != null) {
912                            valueFormatter = cellFormatters.get(cf);
913                            if (valueFormatter == null) {
914                                valueFormatter = new CellFormatterValueFormatter(cf);
915                                cellFormatters.put(cf, valueFormatter);
916                            }
917                        } else {
918                            cachedFormatString = revaluator.getFormatString();
919                            Locale locale = query.getConnection().getLocale();
920                            valueFormatter = formatValueFormatters.get(locale);
921                            if (valueFormatter == null) {
922                                valueFormatter = new FormatValueFormatter(locale);
923                                formatValueFormatters.put(locale, valueFormatter);
924                            }
925                        }
926    
927                        ci.formatString = cachedFormatString;
928                        ci.valueFormatter = valueFormatter;
929    
930                    } catch (ResultLimitExceededException e) {
931                        // Do NOT ignore a ResultLimitExceededException!!!
932                        throw e;
933    
934                    } catch (MondrianEvaluationException e) {
935                        // ignore but warn
936                        LOGGER.warn("Mondrian: exception in executeStripe.", e);
937                     } catch (Error e) {
938                         // Errors indicate fatal JVM problems; do not discard
939                         throw e;
940                    } catch (Throwable e) {
941                        LOGGER.warn("Mondrian: exception in executeStripe.", e);
942                        Util.discard(e);
943                    }
944    
945                    if (o == RolapUtil.valueNotReadyException) {
946                        continue;
947                    }
948    
949                    ci.value = o;
950                }
951            } else {
952                Axis axis = axes[axisOrdinal];
953                List<Position> positions = axis.getPositions();
954                if (positionsHighCardinality.get(axisOrdinal) == null) {
955                    try {
956                        positionsHighCardinality.put(
957                            axisOrdinal,
958                            positions.get(0).get(0).getDimension()
959                                .isHighCardinality());
960                    } catch (IndexOutOfBoundsException ioobe) {
961                        // No elements... no problem
962                    }
963                }
964                if (positionsHighCardinality.get(axisOrdinal) != null
965                        && positionsHighCardinality.get(axisOrdinal))
966                {
967                    final int limit =
968                        MondrianProperties.instance().HighCardChunkSize.get();
969                    if (positionsIterators.get(axisOrdinal) == null) {
970                        final Iterator<Position> it = positions.iterator();
971                        positionsIterators.put(axisOrdinal, it);
972                        positionsIndexes.put(axisOrdinal, 0);
973                        final List<Position> subPositions =
974                            new ArrayList<Position>();
975                        for (int i = 0; i < limit && it.hasNext(); i++) {
976                            subPositions.add(it.next());
977                        }
978                        positionsCurrent.put(axisOrdinal, subPositions);
979                    }
980                    final Iterator<Position> it =
981                        positionsIterators.get(axisOrdinal);
982                    final int positionIndex = positionsIndexes.get(axisOrdinal);
983                    List<Position> subPositions = positionsCurrent.get(axisOrdinal);
984    
985                    if (subPositions == null) {
986                        return;
987                    }
988    
989                    int pi;
990                    if (pos[axisOrdinal] > positionIndex + subPositions.size() - 1
991                            && subPositions.size() == limit)
992                    {
993                        pi = positionIndex + subPositions.size();
994                        positionsIndexes.put(
995                            axisOrdinal, positionIndex + subPositions.size());
996                        subPositions.subList(0, subPositions.size()).clear();
997                        for (int i = 0; i < limit && it.hasNext(); i++) {
998                            subPositions.add(it.next());
999                        }
1000                        positionsCurrent.put(axisOrdinal, subPositions);
1001                    } else {
1002                        pi = positionIndex;
1003                    }
1004                    for (final Position position : subPositions) {
1005                        point.setAxis(axisOrdinal, pi);
1006                        revaluator.setContext(position);
1007                        getQuery().checkCancelOrTimeout();
1008                        executeStripe(axisOrdinal - 1, revaluator, pos);
1009                        pi++;
1010                    }
1011                } else {
1012                    int positionIndex = 0;
1013                    for (final Position position : positions) {
1014                        point.setAxis(axisOrdinal, positionIndex);
1015                        revaluator.setContext(position);
1016                        getQuery().checkCancelOrTimeout();
1017                        executeStripe(axisOrdinal - 1, revaluator, pos);
1018                        positionIndex++;
1019                    }
1020                }
1021            }
1022        }
1023    
1024        /**
1025         * Converts a cell ordinal to a set of cell coordinates. Converse of
1026         * {@link #getCellOrdinal}. For example, if this result is 10 x 10 x 10,
1027         * then cell ordinal 537 has coordinates (5, 3, 7).
1028         * <p>
1029         * This method is no longer used.
1030         */
1031        int[] getCellPos(int cellOrdinal) {
1032            if (modulos == null) {
1033                makeModulos();
1034            }
1035            return modulos.getCellPos(cellOrdinal);
1036        }
1037    
1038        /**
1039         * Converts a set of cell coordinates to a cell ordinal. Converse of
1040         * {@link #getCellPos}.
1041         * <p>
1042         * This method can be expensive, because the ordinal is computed from the
1043         * length of the axes, and therefore the axes need to be instantiated.
1044         */
1045        int getCellOrdinal(int[] pos) {
1046            if (modulos == null) {
1047                makeModulos();
1048            }
1049            return modulos.getCellOrdinal(pos);
1050        }
1051    
1052        /*
1053         * Instantiates the calculator to convert cell coordinates to a cell ordinal
1054         * and vice versa.
1055         *
1056         * <p>To create the calculator, any axis that is based upon an Iterable is
1057         * converted into a List - thus increasing memory usage.
1058         */
1059        protected void makeModulos() {
1060            modulos = Modulos.Generator.create(axes);
1061        }
1062    
1063        /**
1064         * Called only by RolapCell.
1065         *
1066         * @param pos Coordinates of cell
1067         * @return Evaluator whose context is the given cell
1068         */
1069        RolapEvaluator getCellEvaluator(int[] pos) {
1070            final RolapEvaluator cellEvaluator = evaluator.push();
1071            for (int i = 0; i < pos.length; i++) {
1072                Position position = axes[i].getPositions().get(pos[i]);
1073                cellEvaluator.setContext(position);
1074            }
1075            return cellEvaluator;
1076        }
1077    
1078        /**
1079         * Called only by RolapCell. Use this when creating an Evaluator
1080         * (using method {@link #getCellEvaluator}) is not required.
1081         *
1082         * @param pos Coordinates of cell
1083         * @return Members which form the context of the given cell
1084         */
1085        Member[] getCellMembers(int[] pos) {
1086            Member[] members = evaluator.getMembers().clone();
1087            final Cube cube = getCube();
1088            for (int i = 0; i < pos.length; i++) {
1089                Position position = axes[i].getPositions().get(pos[i]);
1090                for (Member member : position) {
1091                    RolapMember m = (RolapMember) member;
1092                    int ordinal = m.getDimension().getOrdinal(cube);
1093                    members[ordinal] = m;
1094                }
1095    
1096            }
1097            return members;
1098        }
1099    
1100        Evaluator getRootEvaluator() {
1101            return evaluator;
1102        }
1103    
1104        Evaluator getEvaluator(int[] pos) {
1105            // Set up evaluator's context, so that context-dependent format
1106            // strings work properly.
1107            Evaluator cellEvaluator = evaluator.push();
1108            for (int i = -1; i < axes.length; i++) {
1109                Axis axis;
1110                int index;
1111                if (i < 0) {
1112                    axis = slicerAxis;
1113                    index = 0;
1114                } else {
1115                    axis = axes[i];
1116                    index = pos[i];
1117                }
1118                Position position = axis.getPositions().get(index);
1119                cellEvaluator.setContext(position);
1120            }
1121            return cellEvaluator;
1122        }
1123    
1124    
1125        /**
1126         * Counts and collects Members found of the axes.
1127         * This class does two things. First it collects all Members
1128         * found during the Member-Determination phase.
1129         * Secondly, it counts how many Members are on each axis and
1130         * forms the product, the totalCellCount which is checked against
1131         * the ResultLimit property value.
1132         */
1133        private static class AxisMember implements Iterable<Member> {
1134            private final List<Member> members;
1135            private final int limit;
1136            private boolean isSlicer;
1137            private int totalCellCount;
1138            private int axisCount;
1139            private boolean countOnly;
1140    
1141            AxisMember() {
1142                this.countOnly = false;
1143                this.members = new ConcatenableList<Member>();
1144                this.totalCellCount = 1;
1145                this.axisCount = 0;
1146                // Now that the axes are evaluated, make sure that the number of
1147                // cells does not exceed the result limit.
1148                this.limit = MondrianProperties.instance().ResultLimit.get();
1149            }
1150            public Iterator<Member> iterator() {
1151                return members.iterator();
1152            }
1153            void setSlicer(final boolean isSlicer) {
1154                this.isSlicer = isSlicer;
1155            }
1156            boolean isEmpty() {
1157                return this.members.isEmpty();
1158            }
1159            void countOnly(boolean countOnly) {
1160                this.countOnly = countOnly;
1161            }
1162            void checkLimit() {
1163                if (this.limit > 0) {
1164                    this.totalCellCount *= this.axisCount;
1165                    if (this.totalCellCount > this.limit) {
1166                        throw MondrianResource.instance().
1167                            TotalMembersLimitExceeded.ex(this.totalCellCount,
1168                                                            this.limit);
1169                    }
1170                    this.axisCount = 0;
1171                }
1172            }
1173            void clearAxisCount() {
1174                this.axisCount = 0;
1175            }
1176            void clearTotalCellCount() {
1177                this.totalCellCount = 1;
1178            }
1179            void clearMembers() {
1180                this.members.clear();
1181                this.axisCount = 0;
1182                this.totalCellCount = 1;
1183            }
1184            List<Member> members() {
1185                return this.members;
1186            }
1187    
1188            void merge(List list) {
1189                if (!list.isEmpty()) {
1190                    if (list.get(0) instanceof Member[]) {
1191                        for (Member[] o : Util.<Member[]>cast(list)) {
1192                            merge(o);
1193                        }
1194                    } else {
1195                        for (Member o : Util.<Member>cast(list)) {
1196                            if (o == null) {
1197                                continue;
1198                            }
1199                            if (o.getDimension().isHighCardinality()) {
1200                                break;
1201                            }
1202                            merge(o);
1203                        }
1204                    }
1205                }
1206            }
1207    
1208            void merge(Iterator it) {
1209                if (it.hasNext()) {
1210                    Object o = it.next();
1211                    if (o instanceof Member[]) {
1212                        merge((Member[]) o);
1213                        while (it.hasNext()) {
1214                            o = it.next();
1215                            merge((Member[]) o);
1216                        }
1217                    } else {
1218                        merge((Member) o);
1219                        while (it.hasNext()) {
1220                            o = it.next();
1221                            merge((Member) o);
1222                        }
1223                    }
1224                }
1225            }
1226    
1227            private Member getTopParent(final Member m) {
1228                Member parent = m.getParentMember();
1229                return (parent == null) ? m : getTopParent(parent);
1230            }
1231    
1232            private void merge(final Member[] members) {
1233                for (Member member : members) {
1234                    merge(member);
1235                }
1236            }
1237    
1238            private void merge(final Member member) {
1239                this.axisCount++;
1240                if (! countOnly) {
1241                    if (isSlicer) {
1242                        if (! members.contains(member)) {
1243                            members.add(member);
1244                        }
1245                    } else {
1246                        if (member.isNull()) {
1247                            return;
1248                        } else if (member.isMeasure()) {
1249                            return;
1250                        } else if (member.isCalculated()) {
1251                            return;
1252                        } else if (member.isAll()) {
1253                            return;
1254                        }
1255                        Member topParent = getTopParent(member);
1256                        if (! this.members.contains(topParent)) {
1257                            this.members.add(topParent);
1258                        }
1259                    }
1260                }
1261            }
1262        }
1263    
1264    
1265        /**
1266         * Extension to {@link RolapEvaluator.RolapEvaluatorRoot} which is capable
1267         * of evaluating named sets.<p/>
1268         *
1269         * A given set is only evaluated once each time a query is executed; the
1270         * result is added to the {@link #namedSetValues} cache on first execution
1271         * and re-used.<p/>
1272         *
1273         * Named sets are always evaluated in the context of the slicer.<p/>
1274         */
1275        protected static class RolapResultEvaluatorRoot
1276                extends RolapEvaluator.RolapEvaluatorRoot {
1277            /**
1278             * Maps the names of sets to their values. Populated on demand.
1279             */
1280            private final Map<String, Object> namedSetValues =
1281                new HashMap<String, Object>();
1282    
1283            /**
1284             * Evaluator containing context resulting from evaluating the slicer.
1285             */
1286            private RolapEvaluator slicerEvaluator;
1287            private final RolapResult result;
1288            private static final Object Sentinel = new Object();
1289    
1290            public RolapResultEvaluatorRoot(RolapResult result) {
1291                super(result.query);
1292                this.result = result;
1293            }
1294    
1295            protected void init(Evaluator evaluator) {
1296                slicerEvaluator = (RolapEvaluator) evaluator;
1297            }
1298    
1299            protected Object evaluateNamedSet(String name, Exp exp) {
1300                Object value = namedSetValues.get(name);
1301                if (value == null) {
1302                    final RolapEvaluator.RolapEvaluatorRoot root =
1303                        slicerEvaluator.root;
1304                    final Calc calc = root.getCompiled(exp, false, ResultStyle.LIST);
1305                    Object o = result.evaluateExp(calc, slicerEvaluator.push());
1306                    List list;
1307                    if (o instanceof List) {
1308                        list = (List) o;
1309                    } else {
1310                        // Iterable
1311    
1312                        // TODO:
1313                        // Here, we have to convert the Iterable into a List,
1314                        // materialize it, because in the class
1315                        // mondrian.mdx.NamedSetExpr the Calc returned by the
1316                        // 'accept' method is an AbstractListCalc (hence we must
1317                        // provide a list here). It would be better if the
1318                        // NamedSetExpr class knew if it needed a ListCalc or
1319                        // an IterCalc.
1320                        Iterable iter = (Iterable) o;
1321                        list = new ArrayList();
1322                        for (Object e : iter) {
1323                            list.add(e);
1324                        }
1325                    }
1326                    // Make immutable, just in case expressions are modifying the
1327                    // results we give them.
1328                    value = Collections.unmodifiableList(list);
1329                    namedSetValues.put(name, value);
1330                }
1331                return value;
1332            }
1333    
1334            public Object getParameterValue(ParameterSlot slot) {
1335                Object value = slot.getParameterValue();
1336                if (value != null) {
1337                    return value;
1338                }
1339    
1340                // Look in other places for the value. Which places we look depends
1341                // on the scope of the parameter.
1342                Parameter.Scope scope = slot.getParameter().getScope();
1343                switch (scope) {
1344                case System:
1345                    // TODO: implement system params
1346    
1347                    // fall through
1348                case Schema:
1349                    // TODO: implement schema params
1350    
1351                    // fall through
1352                case Connection:
1353                    // if it's set in the session, return that value
1354    
1355                    // fall through
1356                case Statement:
1357                    break;
1358    
1359                default:
1360                    throw Util.badValue(scope);
1361                }
1362    
1363                // Not set in any accessible scope. Evaluate the default value,
1364                // then cache it.
1365                value = slot.getCachedDefaultValue();
1366                if (value != null) {
1367                    if (value == Sentinel) {
1368                        throw MondrianResource.instance().
1369                            CycleDuringParameterEvaluation.ex(
1370                            slot.getParameter().getName());
1371                    }
1372                    return value;
1373                }
1374                // Set value to a sentinel, so we can detect cyclic evaluation.
1375                slot.setCachedDefaultValue(Sentinel);
1376                value = result.evaluateExp(
1377                    slot.getDefaultValueCalc(), slicerEvaluator.push());
1378                slot.setCachedDefaultValue(value);
1379                return value;
1380            }
1381        }
1382    
1383        /**
1384         * Formatter to convert values into formatted strings.
1385         *
1386         * <p>Every Cell has a value, a format string (or CellFormatter) and a
1387         * formatted value string.
1388         * There are a wide range of possible values (pick a Double, any
1389         * Double - its a value). Because there are lots of possible values,
1390         * there are also lots of possible formatted value strings. On the
1391         * other hand, there are only a very small number of format strings
1392         * and CellFormatter's. These formatters are to be cached
1393         * in a synchronized HashMaps in order to limit how many copies
1394         * need to be kept around.
1395         *
1396         * <p>
1397         * There are two implementations of the ValueFormatter interface:<ul>
1398         * <li>{@link CellFormatterValueFormatter}, which formats using a
1399         * user-registered {@link CellFormatter}; and
1400         * <li> {@link FormatValueFormatter}, which takes the {@link Locale} object.
1401         * </ul>
1402         */
1403        interface ValueFormatter {
1404            String format(Object value, String formatString);
1405        }
1406    
1407        /**
1408         * A CellFormatterValueFormatter uses a user-defined {@link CellFormatter}
1409         * to format values.
1410         */
1411        static class CellFormatterValueFormatter implements ValueFormatter {
1412            final CellFormatter cf;
1413    
1414            /**
1415             * Creates a CellFormatterValueFormatter
1416             *
1417             * @param cf Cell formatter
1418             */
1419            CellFormatterValueFormatter(CellFormatter cf) {
1420                this.cf = cf;
1421            }
1422            public String format(Object value, String formatString) {
1423                return cf.formatCell(value);
1424            }
1425        }
1426    
1427        /**
1428         * A FormatValueFormatter takes a {@link Locale}
1429         * as a parameter and uses it to get the {@link mondrian.util.Format}
1430         * to be used in formatting an Object value with a
1431         * given format string.
1432         */
1433        static class FormatValueFormatter implements ValueFormatter {
1434            final Locale locale;
1435    
1436            /**
1437             * Creates a FormatValueFormatter.
1438             *
1439             * @param locale Locale
1440             */
1441            FormatValueFormatter(Locale locale) {
1442                this.locale = locale;
1443            }
1444            public String format(Object value, String formatString) {
1445                if (value == Util.nullValue) {
1446                    Format format = getFormat(formatString);
1447                    return format.format(null);
1448                } else if (value instanceof Throwable) {
1449                    return "#ERR: " + value.toString();
1450                } else if (value instanceof String) {
1451                    return (String) value;
1452                } else {
1453                    Format format = getFormat(formatString);
1454                    return format.format(value);
1455                }
1456            }
1457            private Format getFormat(String formatString) {
1458                return Format.get(formatString, locale);
1459            }
1460        }
1461    
1462        /*
1463         * Generate a long ordinal based upon the values of the integers
1464         * stored in the cell position array. With this mechanism, the
1465         * Cell information can be stored using a long key (rather than
1466         * the array integer of positions) thus saving memory. The trick
1467         * is to use a 'large number' per axis in order to convert from
1468         * position array to long key where the 'large number' is greater
1469         * than the number of members in the axis.
1470         * The largest 'long' is java.lang.Long.MAX_VALUE which is
1471         * 9,223,372,036,854,776,000. The product of the maximum number
1472         * of members per axis must be less than this maximum 'long'
1473         * value (otherwise one gets hashing collisions).
1474         * <p>
1475         * For a single axis, the maximum number of members is equal to
1476         * the max 'long' number, 9,223,372,036,854,776,000.
1477         * <p>
1478         * For two axes, the maximum number of members is the square root
1479         * of the max 'long' number, 9,223,372,036,854,776,000, which is
1480         * slightly bigger than 2,147,483,647 (which is the maximum integer).
1481         * <p>
1482         * For three axes, the maximum number of members per axis is the
1483         * cube root of the max 'long' which is about 2,000,000
1484         * <p>
1485         * For four axes the forth root is about 50,000.
1486         * <p>
1487         * For five or more axes, the maximum number of members per axis
1488         * based upon the root of the maximum 'long' number,
1489         * start getting too small to guarantee that it will be
1490         * smaller than the number of members on a given axis and so
1491         * we must resort to the Map-base Cell container.
1492         */
1493    
1494    
1495    
1496        /**
1497         * Synchronized Map from Locale to ValueFormatter. It is expected that
1498         * there will be only a small number of Locale's.
1499         * Should these be a WeakHashMap?
1500         */
1501        protected static final Map<Locale, ValueFormatter>
1502                formatValueFormatters =
1503                Collections.synchronizedMap(new HashMap<Locale, ValueFormatter>());
1504    
1505        /**
1506         * Synchronized Map from CellFormatter to ValueFormatter.
1507         * CellFormatter's are defined in schema files. It is expected
1508         * the there will only be a small number of CellFormatter's.
1509         * Should these be a WeakHashMap?
1510         */
1511        protected static final Map<CellFormatter, ValueFormatter>
1512                cellFormatters =
1513                Collections.synchronizedMap(new HashMap<CellFormatter, ValueFormatter>());
1514    
1515        /**
1516         * A CellInfo contains all of the information that a Cell requires.
1517         * It is placed in the cellInfos map during evaluation and
1518         * serves as a constructor parameter for {@link RolapCell}.
1519         *
1520         * <p>During the evaluation stage they are mutable but after evaluation has
1521         * finished they are not changed.
1522         */
1523        static class CellInfo {
1524            Object value;
1525            String formatString;
1526            ValueFormatter valueFormatter;
1527            long key;
1528    
1529            /**
1530             * Creates a CellInfo representing the position of a cell.
1531             *
1532             * @param key Ordinal representing the position of a cell
1533             */
1534            CellInfo(long key) {
1535                this(key, null, null, null);
1536            }
1537    
1538            /**
1539             * Creates a CellInfo with position, value, format string and formatter
1540             * of a cell.
1541             *
1542             * @param key Ordinal representing the position of a cell
1543             * @param value Value of cell, or null if not yet known
1544             * @param formatString Format string of cell, or null
1545             * @param valueFormatter Formatter for cell, or null
1546             */
1547            CellInfo(
1548                long key,
1549                Object value,
1550                String formatString,
1551                ValueFormatter valueFormatter)
1552            {
1553                this.key = key;
1554                this.value = value;
1555                this.formatString = formatString;
1556                this.valueFormatter = valueFormatter;
1557            }
1558    
1559            public int hashCode() {
1560                // Combine the upper 32 bits of the key with the lower 32 bits.
1561                // We used to use 'key ^ (key >>> 32)' but that was bad, because
1562                // CellKey.Two encodes (i, j) as
1563                // (i * Integer.MAX_VALUE + j), which is practically the same as
1564                // (i << 32, j). If i and j were
1565                // both k bits long, all of the hashcodes were k bits long too!
1566                return (int) (key ^ (key >>> 11) ^ (key >>> 24));
1567            }
1568    
1569            public boolean equals(Object o) {
1570                if (o instanceof CellInfo) {
1571                    CellInfo that = (CellInfo) o;
1572                    return that.key == this.key;
1573                } else {
1574                    return false;
1575                }
1576            }
1577    
1578            /**
1579             * Returns the formatted value of the Cell
1580             * @return formatted value of the Cell
1581             */
1582            String getFormatValue() {
1583                return valueFormatter.format(value, formatString);
1584            }
1585        }
1586    
1587        /**
1588         * API for the creation and
1589         * lookup of {@link CellInfo} objects. There are two implementations,
1590         * one that uses a Map for storage and the other uses an ObjectPool.
1591         */
1592        interface CellInfoContainer {
1593            /**
1594             * Returns the number of CellInfo objects in this container.
1595             * @return  the number of CellInfo objects.
1596             */
1597            int size();
1598            /**
1599             * Reduces the size of the internal data structures needed to
1600             * support the current entries. This should be called after
1601             * all CellInfo objects have been added to container.
1602             */
1603            void trimToSize();
1604            /**
1605             * Removes all CellInfo objects from container. Does not
1606             * change the size of the internal data structures.
1607             */
1608            void clear();
1609            /**
1610             * Creates a new CellInfo object, adds it to the container
1611             * a location <code>pos</code> and returns it.
1612             *
1613             * @param pos where to store CellInfo object.
1614             * @return the newly create CellInfo object.
1615             */
1616            CellInfo create(int[] pos);
1617            /**
1618             * Gets the CellInfo object at the location <code>pos</code>.
1619             *
1620             * @param pos where to find the CellInfo object.
1621             * @return the CellInfo found or null.
1622             */
1623            CellInfo lookup(int[] pos);
1624        }
1625    
1626        /**
1627         * Implementation of {@link CellInfoContainer} which uses a {@link Map} to
1628         * store CellInfo Objects.
1629         *
1630         * <p>Note that the CellKey point instance variable is the same
1631         * Object (NOT a copy) that is used and modified during
1632         * the recursive calls to executeStripe - the
1633         * <code>create</code> method relies on this fact.
1634         */
1635        static class CellInfoMap implements CellInfoContainer {
1636            private final Map<CellKey, CellInfo> cellInfoMap;
1637            private final CellKey point;
1638    
1639            /**
1640             * Creates a CellInfoMap
1641             *
1642             * @param point Cell position
1643             */
1644            CellInfoMap(CellKey point) {
1645                this.point = point;
1646                this.cellInfoMap = new HashMap<CellKey, CellInfo>();
1647            }
1648            public int size() {
1649                return this.cellInfoMap.size();
1650            }
1651            public void trimToSize() {
1652                // empty
1653            }
1654            public void clear() {
1655                this.cellInfoMap.clear();
1656            }
1657            public CellInfo create(int[] pos) {
1658                CellKey key = this.point.copy();
1659                CellInfo ci = this.cellInfoMap.get(key);
1660                if (ci == null) {
1661                    ci = new CellInfo(0);
1662                    this.cellInfoMap.put(key, ci);
1663                }
1664                return ci;
1665            }
1666            public CellInfo lookup(int[] pos) {
1667                CellKey key = CellKey.Generator.newCellKey(pos);
1668                return this.cellInfoMap.get(key);
1669            }
1670        }
1671    
1672        /**
1673         * Implementation of {@link CellInfoContainer} which uses an
1674         * {@link ObjectPool} to store {@link CellInfo} Objects.
1675         *
1676         * <p>There is an inner interface (<code>CellKeyMaker</code>) and
1677         * implementations for 0 through 4 axes that convert the Cell
1678         * position integer array into a long.
1679         *
1680         * <p>
1681         * It should be noted that there is an alternate approach.
1682         * As the <code>executeStripe</code>
1683         * method is recursively called, at each call it is known which
1684         * axis is being iterated across and it is known whether or
1685         * not the Position object for that axis is a List or just
1686         * an Iterable. It it is a List, then one knows the real
1687         * size of the axis. If it is an Iterable, then one has to
1688         * use one of the MAX_AXIS_SIZE values. Given that this information
1689         * is available when one recursives down to the next
1690         * <code>executeStripe</code> call, the Cell ordinal, the position
1691         * integer array could converted to an <code>long</code>, could
1692         * be generated on the call stack!! Just a thought for the future.
1693         */
1694        static class CellInfoPool implements CellInfoContainer {
1695            /**
1696             * The maximum number of Members, 2,147,483,647, that can be any given
1697             * Axis when the number of Axes is 2.
1698             */
1699            protected static final long MAX_AXIS_SIZE_2 = 2147483647;
1700            /**
1701             * The maximum number of Members, 2,000,000, that can be any given
1702             * Axis when the number of Axes is 3.
1703             */
1704            protected static final long MAX_AXIS_SIZE_3 = 2000000;
1705            /**
1706             * The maximum number of Members, 50,000, that can be any given
1707             * Axis when the number of Axes is 4.
1708             */
1709            protected static final long MAX_AXIS_SIZE_4 = 50000;
1710    
1711            /**
1712             * Implementations of CellKeyMaker convert the Cell
1713             * position integer array to a <code>long</code>.
1714             */
1715            interface CellKeyMaker {
1716                long generate(int[] pos);
1717            }
1718            /**
1719             * For axis of size 0.
1720             */
1721            static class Zero implements CellKeyMaker {
1722                public long generate(int[] pos) {
1723                    return 0;
1724                }
1725            }
1726            /**
1727             * For axis of size 1.
1728             */
1729            static class One implements CellKeyMaker {
1730                public long generate(int[] pos) {
1731                    return pos[0];
1732                }
1733            }
1734            /**
1735             * For axis of size 2.
1736             */
1737            static class Two implements CellKeyMaker {
1738                public long generate(int[] pos) {
1739                    long l = pos[0];
1740                    l += (MAX_AXIS_SIZE_2 * (long) pos[1]);
1741                    return l;
1742                }
1743            }
1744            /**
1745             * For axis of size 3.
1746             */
1747            static class Three implements CellKeyMaker {
1748                public long generate(int[] pos) {
1749                    long l = pos[0];
1750                    l += (MAX_AXIS_SIZE_3 * (long) pos[1]);
1751                    l += (MAX_AXIS_SIZE_3 * MAX_AXIS_SIZE_3 * (long) pos[2]);
1752                    return l;
1753                }
1754            }
1755            /**
1756             * For axis of size 4.
1757             */
1758            static class Four implements CellKeyMaker {
1759                public long generate(int[] pos) {
1760                    long l = pos[0];
1761                    l += (MAX_AXIS_SIZE_4 * (long) pos[1]);
1762                    l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * (long) pos[2]);
1763                    l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * (long) pos[3]);
1764                    return l;
1765                }
1766            }
1767    
1768            private final ObjectPool<CellInfo> cellInfoPool;
1769            private final CellKeyMaker cellKeyMaker;
1770    
1771            CellInfoPool(int axisLength) {
1772                this.cellInfoPool = new ObjectPool<CellInfo>();
1773                this.cellKeyMaker = createCellKeyMaker(axisLength);
1774            }
1775    
1776            CellInfoPool(int axisLength, int initialSize) {
1777                this.cellInfoPool = new ObjectPool<CellInfo>(initialSize);
1778                this.cellKeyMaker = createCellKeyMaker(axisLength);
1779            }
1780    
1781            private static CellKeyMaker createCellKeyMaker(int axisLength) {
1782                switch (axisLength) {
1783                case 0:
1784                    return new Zero();
1785                case 1:
1786                    return new One();
1787                case 2:
1788                    return new Two();
1789                case 3:
1790                    return new Three();
1791                case 4:
1792                    return new Four();
1793                default:
1794                    throw new RuntimeException(
1795                        "Creating CellInfoPool with axisLength=" + axisLength);
1796                }
1797            }
1798    
1799            public int size() {
1800                return this.cellInfoPool.size();
1801            }
1802            public void trimToSize() {
1803                this.cellInfoPool.trimToSize();
1804            }
1805            public void clear() {
1806                this.cellInfoPool.clear();
1807            }
1808            public CellInfo create(int[] pos) {
1809                long key = this.cellKeyMaker.generate(pos);
1810                return this.cellInfoPool.add(new CellInfo(key));
1811            }
1812            public CellInfo lookup(int[] pos) {
1813                long key = this.cellKeyMaker.generate(pos);
1814                return this.cellInfoPool.add(new CellInfo(key));
1815            }
1816        }
1817    
1818        static Axis mergeAxes(Axis axis1, Axis axis2, RolapEvaluator evaluator,
1819                                boolean ordered) {
1820            if (axis1 == null) {
1821                return axis2;
1822            }
1823            List<Position> pl1 = axis1.getPositions();
1824            List<Position> pl2 = axis2.getPositions();
1825            int arrayLen = -1;
1826            if (pl1 instanceof RolapAxis.PositionListBase) {
1827                if (pl1.isEmpty()) {
1828                    return axis2;
1829                }
1830                arrayLen = pl1.get(0).size();
1831            }
1832            if (axis1 instanceof RolapAxis.SingleEmptyPosition) {
1833                return axis2;
1834            }
1835            if (axis1 instanceof RolapAxis.NoPosition) {
1836                return axis2;
1837            }
1838            if (pl2 instanceof RolapAxis.PositionListBase) {
1839                if (pl2.isEmpty()) {
1840                    return axis1;
1841                }
1842                arrayLen = pl2.get(0).size();
1843            }
1844            if (axis2 instanceof RolapAxis.SingleEmptyPosition) {
1845                return axis1;
1846            }
1847            if (axis2 instanceof RolapAxis.NoPosition) {
1848                return axis1;
1849            }
1850            if (arrayLen == -1) {
1851                // Avoid materialization of axis
1852                arrayLen = 0;
1853                for (Position p1 : pl1) {
1854                    for (Member m1 : p1) {
1855                        arrayLen++;
1856                    }
1857                    break;
1858                }
1859                // reset to start of List
1860                pl1 = axis1.getPositions();
1861            }
1862            if (arrayLen == 1) {
1863                // single Member per position
1864                List<Member> list = new ArrayList<Member>();
1865                for (Position p1 : pl1) {
1866                    for (Member m1 : p1) {
1867                        list.add(m1);
1868                    }
1869                }
1870                for (Position p2 : pl2) {
1871                    for (Member m2 : p2) {
1872                        if (! list.contains(m2)) {
1873                            list.add(m2);
1874                        }
1875                    }
1876                }
1877                return new RolapAxis.MemberList(list);
1878            } else {
1879                // array of Members per position
1880                List<Member[]> list = new ArrayList<Member[]>();
1881                for (Position p1 : pl1) {
1882                    Member[] members = new Member[arrayLen];
1883                    int i = 0;
1884                    for (Member m1 : p1) {
1885                        members[i++] = m1;
1886                    }
1887                    list.add(members);
1888                }
1889                List<Member[]> extras = new ArrayList<Member[]>();
1890                for (Position p2 : pl2) {
1891                    int i = 0;
1892                    Member[] members = new Member[arrayLen];
1893                    for (Member m2 : p2) {
1894                        members[i++] = m2;
1895                    }
1896                    Iterator<Member[]> it1 = list.iterator();
1897                    boolean found = false;
1898                    while (it1.hasNext()) {
1899                        Member[] m1 = it1.next();
1900                        if (java.util.Arrays.equals(members, m1)) {
1901                            found = true;
1902                            break;
1903                        }
1904                    }
1905                    if (! found) {
1906                        extras.add(members);
1907                    }
1908                }
1909                list.addAll(extras);
1910    
1911                // if there are unique members on both axes and no order function,
1912                //  sort the list to ensure default order
1913                if (!list.isEmpty() && !extras.isEmpty() && ordered == false) {
1914                    Member[] membs = list.get(0);
1915                    int membsSize = membs.length;
1916                    ValueCalc valCalc = new ValueCalc(new DummyExp(new ScalarType()));
1917                    FunUtil.sortTuples(evaluator, list, valCalc,
1918                            false, false, membsSize);
1919                }
1920    
1921                return new RolapAxis.MemberArrayList(list);
1922            }
1923        }
1924    }
1925    
1926    // End RolapResult.java