001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/fun/FunUtil.java#111 $
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) 2002-2002 Kana Software, Inc.
007    // Copyright (C) 2002-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, 3 March, 2002
012    */
013    package mondrian.olap.fun;
014    
015    import mondrian.olap.*;
016    import mondrian.olap.type.*;
017    import mondrian.resource.MondrianResource;
018    import mondrian.calc.Calc;
019    import mondrian.calc.ResultStyle;
020    import mondrian.calc.DoubleCalc;
021    import mondrian.mdx.*;
022    import mondrian.rolap.RolapHierarchy;
023    import mondrian.util.FilteredIterableList;
024    import mondrian.util.ConcatenableList;
025    
026    import org.apache.log4j.Logger;
027    
028    import java.util.*;
029    import java.io.PrintWriter;
030    
031    /**
032     * <code>FunUtil</code> contains a set of methods useful within the
033     * <code>mondrian.olap.fun</code> package.
034     *
035     * @author jhyde
036     * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/FunUtil.java#111 $
037     * @since 1.0
038     */
039    public class FunUtil extends Util {
040    
041        static final String[] emptyStringArray = new String[0];
042        private static final boolean debug = false;
043        public static final NullMember NullMember = new NullMember();
044    
045        /**
046         * Special value which indicates that a <code>double</code> computation
047         * has returned the MDX null value. See {@link DoubleCalc}.
048         */
049        public static final double DoubleNull = 0.000000012345;
050    
051        /**
052         * Special value which indicates that a <code>double</code> computation
053         * has returned the MDX EMPTY value. See {@link DoubleCalc}.
054         */
055        public static final double DoubleEmpty = -0.000000012345;
056    
057        /**
058         * Special value which indicates that an <code>int</code> computation
059         * has returned the MDX null value. See {@link mondrian.calc.IntegerCalc}.
060         */
061        public static final int IntegerNull = Integer.MIN_VALUE + 1;
062    
063        /**
064         * Null value in three-valued boolean logic.
065         * Actually, a placeholder until we actually implement 3VL.
066         */
067        public static final boolean BooleanNull = false;
068    
069        /**
070         * Creates an exception which indicates that an error has occurred while
071         * executing a given function.
072         */
073        public static RuntimeException newEvalException(
074                FunDef funDef,
075                String message) {
076            Util.discard(funDef); // TODO: use this
077            return new MondrianEvaluationException(message);
078        }
079    
080        /**
081         * Creates an exception which indicates that an error has occurred while
082         * executing a given function.
083         */
084        public static RuntimeException newEvalException(Throwable throwable) {
085            return new MondrianEvaluationException(
086                throwable.getClass().getName() + ": " + throwable.getMessage());
087        }
088    
089        public static boolean isMemberType(Calc calc) {
090            Type type = calc.getType();
091            return (type instanceof SetType) &&
092              (((SetType) type).getElementType() instanceof MemberType);
093        }
094    
095        public static void checkIterListResultStyles(Calc calc) {
096            switch (calc.getResultStyle()) {
097            case ITERABLE:
098            case LIST:
099            case MUTABLE_LIST:
100                break;
101            default:
102                throw ResultStyleException.generateBadType(
103                    ResultStyle.ITERABLE_LIST_MUTABLELIST,
104                    calc.getResultStyle());
105            }
106        }
107    
108        public static void checkListResultStyles(Calc calc) {
109            switch (calc.getResultStyle()) {
110            case LIST:
111            case MUTABLE_LIST:
112                break;
113            default:
114                throw ResultStyleException.generateBadType(
115                    ResultStyle.LIST_MUTABLELIST,
116                    calc.getResultStyle());
117            }
118        }
119    
120        /**
121         * Returns an argument whose value is a literal.
122         */
123        static String getLiteralArg(
124                ResolvedFunCall call,
125                int i,
126                String defaultValue,
127                String[] allowedValues) {
128            if (i >= call.getArgCount()) {
129                if (defaultValue == null) {
130                    throw newEvalException(call.getFunDef(),
131                            "Required argument is missing");
132                } else {
133                    return defaultValue;
134                }
135            }
136            Exp arg = call.getArg(i);
137            if (!(arg instanceof Literal) ||
138                    arg.getCategory() != Category.Symbol) {
139                throw newEvalException(call.getFunDef(),
140                        "Expected a symbol, found '" + arg + "'");
141            }
142            String s = (String) ((Literal) arg).getValue();
143            StringBuilder sb = new StringBuilder(64);
144            for (int j = 0; j < allowedValues.length; j++) {
145                String allowedValue = allowedValues[j];
146                if (allowedValue.equalsIgnoreCase(s)) {
147                    return allowedValue;
148                }
149                if (j > 0) {
150                    sb.append(", ");
151                }
152                sb.append(allowedValue);
153            }
154            throw newEvalException(call.getFunDef(),
155                    "Allowed values are: {" + sb + "}");
156        }
157    
158        /**
159         * Returns the ordinal of a literal argument. If the argument does not
160         * belong to the supplied enumeration, returns -1.
161         */
162        static <E extends Enum<E>> E getLiteralArg(
163                ResolvedFunCall call,
164                int i,
165                E defaultValue,
166                Class<E> allowedValues) {
167            if (i >= call.getArgCount()) {
168                if (defaultValue == null) {
169                    throw newEvalException(call.getFunDef(),
170                            "Required argument is missing");
171                } else {
172                    return defaultValue;
173                }
174            }
175            Exp arg = call.getArg(i);
176            if (!(arg instanceof Literal) ||
177                    arg.getCategory() != Category.Symbol) {
178                throw newEvalException(call.getFunDef(),
179                        "Expected a symbol, found '" + arg + "'");
180            }
181            String s = (String) ((Literal) arg).getValue();
182            for (E e : allowedValues.getEnumConstants()) {
183                if (e.name().equalsIgnoreCase(s)) {
184                    return e;
185                }
186            }
187            StringBuilder buf = new StringBuilder(64);
188            int k = 0;
189            for (E e : allowedValues.getEnumConstants()) {
190                if (k++ > 0) {
191                    buf.append(", ");
192                }
193                buf.append(e.name());
194            }
195            throw newEvalException(call.getFunDef(),
196                    "Allowed values are: {" + buf + "}");
197        }
198    
199        /**
200         * Throws an error if the expressions don't have the same hierarchy.
201         * @param left
202         * @param right
203         * @throws MondrianEvaluationException if expressions don't have the same
204         *     hierarchy
205         */
206        static void checkCompatible(Exp left, Exp right, FunDef funDef) {
207            final Type leftType = TypeUtil.stripSetType(left.getType());
208            final Type rightType = TypeUtil.stripSetType(right.getType());
209            if (!TypeUtil.isUnionCompatible(leftType, rightType)) {
210                throw newEvalException(funDef, "Expressions must have the same hierarchy");
211            }
212        }
213    
214        /**
215         * Returns <code>true</code> if the mask in <code>flag</code> is set.
216         * @param value The value to check.
217         * @param mask The mask within value to look for.
218         * @param strict If <code>true</code> all the bits in mask must be set. If
219         * <code>false</code> the method will return <code>true</code> if any of the
220         * bits in <code>mask</code> are set.
221         * @return <code>true</code> if the correct bits in <code>mask</code> are set.
222         */
223        static boolean checkFlag(int value, int mask, boolean strict) {
224            return (strict)
225                ? ((value & mask) == mask)
226                : ((value & mask) != 0);
227        }
228    
229        /**
230         * Adds every element of <code>right</code> which is not in <code>set</code>
231         * to both <code>set</code> and <code>left</code>.
232         */
233        static <T> void addUnique(List<T> left, List<T> right, Set<Object> set) {
234            assert left != null;
235            assert right != null;
236            if (right.isEmpty()) {
237                return;
238            }
239            for (int i = 0, n = right.size(); i < n; i++) {
240                T o = right.get(i);
241                Object p = o;
242                if (o instanceof Object[]) {
243                    p = new ArrayHolder<Object>((Object[]) o);
244                }
245                if (set.add(p)) {
246                    left.add(o);
247                }
248            }
249        }
250    
251        static List<Member> addMembers(
252            final SchemaReader schemaReader,
253            final List<Member> members,
254            final Hierarchy hierarchy)
255        {
256            // only add accessible levels
257            for (Level level : schemaReader.getHierarchyLevels(hierarchy)) {
258                addMembers(schemaReader, members, level);
259            }
260            return members;
261        }
262    
263        static List<Member> addMembers(
264                SchemaReader schemaReader,
265                List<Member> members,
266                Level level) {
267            List<Member> levelMembers = schemaReader.getLevelMembers(level, true);
268            members.addAll(levelMembers);
269            return members;
270        }
271    
272        /**
273         * Removes every member from a list which is calculated.
274         * The list must not be null, and must consist only of members.
275         *
276         * @param memberList Member list
277         * @return List of non-calculated members
278         */
279        static List<Member> removeCalculatedMembers(List<Member> memberList)
280        {
281            return new FilteredIterableList<Member>(
282                memberList,
283                new FilteredIterableList.Filter<Member>() {
284                    public boolean accept(final Member m) {
285                        return ! m.isCalculated();
286                    }
287                }
288            );
289        }
290    
291        /**
292         * Returns whether <code>m0</code> is an ancestor of <code>m1</code>.
293         *
294         * @param strict if true, a member is not an ancestor of itself
295         */
296        static boolean isAncestorOf(Member m0, Member m1, boolean strict) {
297            if (strict) {
298                if (m1 == null) {
299                    return false;
300                }
301                m1 = m1.getParentMember();
302            }
303            while (m1 != null) {
304                if (m1.equals(m0)) {
305                    return true;
306                }
307                m1 = m1.getParentMember();
308            }
309            return false;
310        }
311    
312        /**
313         * For each member in a list, evaluate an expression and create a map
314         * from members to values.
315         *
316         * <p>If the list contains tuples, use
317         * {@link #evaluateTuples(mondrian.olap.Evaluator, mondrian.calc.Calc, java.util.List)}.
318         *
319         * @param evaluator Evaluation context
320         * @param exp Expression to evaluate
321         * @param members List of members
322         * @param parentsToo If true, evaluate the expression for all ancestors
323         *            of the members as well
324         *
325         * @pre exp != null
326         * @pre exp.getType() instanceof ScalarType
327         */
328        static Map<Member, Object> evaluateMembers(
329            Evaluator evaluator,
330            Calc exp,
331            List<Member> members,
332            boolean parentsToo)
333        {
334            // REVIEW: is this necessary?
335            evaluator = evaluator.push();
336    
337            assert exp.getType() instanceof ScalarType;
338            Map<Member, Object> mapMemberToValue = new HashMap<Member, Object>();
339            for (Member member : members) {
340                while (true) {
341                    evaluator.setContext(member);
342                    Object result = exp.evaluate(evaluator);
343                    if (result == null) {
344                        result = Util.nullValue;
345                    }
346                    mapMemberToValue.put(member, result);
347                    if (!parentsToo) {
348                        break;
349                    }
350                    member = member.getParentMember();
351                    if (member == null) {
352                        break;
353                    }
354                    if (mapMemberToValue.containsKey(member)) {
355                        break;
356                    }
357                }
358            }
359            return mapMemberToValue;
360        }
361    
362        /**
363         * For each tuple in a list, evaluates an expression and creates a map
364         * from tuples to values.
365         *
366         * @param evaluator Evaluation context
367         * @param exp Expression to evaluate
368         * @param members List of members (or List of Member[] tuples)
369         *
370         * @pre exp != null
371         * @pre exp.getType() instanceof ScalarType
372         */
373        static Map<Object, Object> evaluateTuples(
374                Evaluator evaluator,
375                Calc exp,
376                List<Member[]> members) {
377            // RME
378            evaluator = evaluator.push();
379    
380            assert exp.getType() instanceof ScalarType;
381            Map<Object, Object> mapMemberToValue = new HashMap<Object, Object>();
382            for (int i = 0, count = members.size(); i < count; i++) {
383                Member[] tuples = members.get(i);
384                evaluator.setContext(tuples);
385                Object result = exp.evaluate(evaluator);
386                if (result == null) {
387                    result = Util.nullValue;
388                }
389                mapMemberToValue.put(new ArrayHolder<Member>(tuples), result);
390            }
391            return mapMemberToValue;
392        }
393    
394        static Map<Member, Object> evaluateMembers(
395                Evaluator evaluator,
396                List<Member> members,
397                boolean parentsToo) {
398            Map<Member, Object> mapMemberToValue = new HashMap<Member, Object>();
399            for (int i = 0, count = members.size(); i < count; i++) {
400                Member member = members.get(i);
401                while (true) {
402                    evaluator.setContext(member);
403                    Object result = evaluator.evaluateCurrent();
404                    mapMemberToValue.put(member, result);
405                    if (!parentsToo) {
406                        break;
407                    }
408                    member = member.getParentMember();
409                    if (member == null) {
410                        break;
411                    }
412                    if (mapMemberToValue.containsKey(member)) {
413                        break;
414                    }
415                }
416            }
417            return mapMemberToValue;
418        }
419    
420        /**
421         * Helper function to sortMembers a list of members according to an expression.
422         *
423         * <p>NOTE: This function does not preserve the contents of the validator.
424         */
425        static void sortMembers(
426            Evaluator evaluator,
427            List<Member> members,
428            Calc exp,
429            boolean desc,
430            boolean brk)
431        {
432            if (members.isEmpty()) {
433                return;
434            }
435            Object first = members.get(0);
436            Map<Member, Object> mapMemberToValue;
437            if (first instanceof Member) {
438                final boolean parentsToo = !brk;
439                mapMemberToValue = evaluateMembers(evaluator, exp, members, parentsToo);
440                Comparator<Member> comparator;
441                if (brk) {
442                    comparator =
443                        new BreakMemberComparator(mapMemberToValue, desc).wrap();
444                } else {
445                    comparator =
446                        new HierarchicalMemberComparator(mapMemberToValue, desc)
447                            .wrap();
448                }
449                Collections.sort(members, comparator);
450            } else {
451                Util.assertTrue(first instanceof Member[]);
452                final int arity = ((Member[]) first).length;
453                Comparator<Member[]> comparator;
454                if (brk) {
455                    comparator = new BreakArrayComparator(evaluator, exp, arity)
456                        .wrap();
457                    if (desc) {
458                        comparator = new ReverseComparator<Member[]>(comparator);
459                    }
460                } else {
461                    comparator =
462                        new HierarchicalArrayComparator(
463                            evaluator, exp, arity, desc).wrap();
464                }
465                Collections.sort((List) members, comparator);
466            }
467            if (debug) {
468                final PrintWriter pw = new PrintWriter(System.out);
469                for (int i = 0; i < members.size(); i++) {
470                    Member o = members.get(i);
471                    pw.print(i);
472                    pw.print(": ");
473                    if (mapMemberToValue != null) {
474                        pw.print(mapMemberToValue.get(o));
475                        pw.print(": ");
476                    }
477                    pw.println(o);
478                }
479                pw.flush();
480            }
481        }
482    
483        /**
484         * Helper function to sortMembers a list of members according to an expression.
485         *
486         * <p>NOTE: This function does not preserve the contents of the validator.
487         */
488        public static void sortTuples(
489            Evaluator evaluator,
490            List<Member[]> tuples,
491            Calc exp,
492            boolean desc,
493            boolean brk,
494            int arity)
495        {
496            if (tuples.isEmpty()) {
497                return;
498            }
499            Comparator<Member[]> comparator;
500            if (brk) {
501                comparator =
502                    new BreakArrayComparator(evaluator, exp, arity).wrap();
503                if (desc) {
504                    comparator = new ReverseComparator<Member[]>(comparator);
505                }
506            } else {
507                comparator =
508                    new HierarchicalArrayComparator(
509                        evaluator, exp, arity, desc).wrap();
510            }
511            Collections.sort(tuples, comparator);
512            if (debug) {
513                final PrintWriter pw = new PrintWriter(System.out);
514                for (int i = 0; i < tuples.size(); i++) {
515                    Member[] o = tuples.get(i);
516                    pw.print(i);
517                    pw.print(": ");
518                    pw.println(o);
519                }
520                pw.flush();
521            }
522        }
523    
524        public static void hierarchize(List members, boolean post) {
525            if (members.isEmpty()) {
526                return;
527            }
528            final Object first = members.get(0);
529            if (first instanceof Member) {
530                if (((Member) first).getDimension().isHighCardinality()) {
531                    return;
532                }
533                List<Member> memberList = Util.cast(members);
534                Comparator<Member> comparator = new HierarchizeComparator(post);
535                members.toArray();
536                Collections.sort(memberList, comparator);
537            } else {
538                assert first instanceof Member[];
539                final int arity = ((Member[]) first).length;
540                List<Member[]> tupleList = Util.cast(members);
541                Comparator<Member[]> comparator =
542                    new HierarchizeArrayComparator(arity, post).wrap();
543                Collections.sort(tupleList, comparator);
544            }
545        }
546    
547        static int sign(double d) {
548            return (d == 0)
549                ? 0
550                : (d < 0)
551                    ? -1
552                    : 1;
553        }
554    
555        /**
556         * Compares double-precision values according to MDX semantics.
557         *
558         * <p>MDX requires a total order:
559         * <pre>
560         *    -inf &lt; NULL &lt; ... &lt; -1 &lt; ... &lt; 0 &lt; ... &lt; NaN &lt; +inf
561         * </pre>
562         * but this is different than Java semantics, specifically with regard
563         * to {@link Double#NaN}.
564         */
565        public static int compareValues(double d1, double d2) {
566            if (Double.isNaN(d1)) {
567                if (d2 == Double.POSITIVE_INFINITY) {
568                    return -1;
569                } else if (Double.isNaN(d2)) {
570                    return 0;
571                } else {
572                    return 1;
573                }
574            } else if (Double.isNaN(d2)) {
575                if (d1 == Double.POSITIVE_INFINITY) {
576                    return 1;
577                } else {
578                    return -1;
579                }
580            } else if (d1 == d2) {
581                return 0;
582            } else if (d1 == FunUtil.DoubleNull) {
583                if (d2 == Double.NEGATIVE_INFINITY) {
584                    return 1;
585                } else {
586                    return -1;
587                }
588            } else if (d2 == FunUtil.DoubleNull) {
589                if (d1 == Double.NEGATIVE_INFINITY) {
590                    return -1;
591                } else {
592                    return 1;
593                }
594            } else if (d1 < d2) {
595                return -1;
596            } else {
597                return 1;
598            }
599        }
600    
601        public static int compareValues(int i, int j) {
602            return (i == j)
603                ? 0
604                : (i < j)
605                    ? -1
606                    : 1;
607        }
608    
609        /**
610         * Compares two cell values.
611         *
612         * <p>Nulls compare last, exceptions (including the
613         * object which indicates the the cell is not in the cache yet) next,
614         * then numbers and strings are compared by value.
615         *
616         * @param value0 First cell value
617         * @param value1 Second cell value
618         * @return -1, 0, or 1, depending upon whether first cell value is less
619         *   than, equal to, or greater than the second
620         */
621        public static int compareValues(Object value0, Object value1) {
622            if (value0 == value1) {
623                return 0;
624            }
625            // null is less than anything else
626            if (value0 == null) {
627                return -1;
628            }
629            if (value1 == null) {
630                return 1;
631            }
632            if (value0 instanceof RuntimeException ||
633                value1 instanceof RuntimeException) {
634                // one of the values is not in cache; continue as best as we can
635                return 0;
636            } else if (value0 == Util.nullValue) {
637                return -1; // null == -infinity
638            } else if (value1 == Util.nullValue) {
639                return 1; // null == -infinity
640            } else if (value0 instanceof String) {
641                return ((String) value0).compareTo((String) value1);
642            } else if (value0 instanceof Number) {
643                return FunUtil.compareValues(
644                        ((Number) value0).doubleValue(),
645                        ((Number) value1).doubleValue());
646            } else {
647                throw Util.newInternal("cannot compare " + value0);
648            }
649        }
650    
651        /**
652         * Turns the mapped values into relative values (percentages) for easy
653         * use by the general topOrBottom function. This might also be a useful
654         * function in itself.
655         */
656        static void toPercent(List members, Map mapMemberToValue, boolean isMember) {
657            double total = 0;
658            int memberCount = members.size();
659            for (int i = 0; i < memberCount; i++) {
660                Object o = (isMember)
661                        ? mapMemberToValue.get(members.get(i))
662                        : mapMemberToValue.get(
663                            new ArrayHolder<Member>((Member []) members.get(i)));
664                if (o instanceof Number) {
665                    total += ((Number) o).doubleValue();
666                }
667            }
668            for (int i = 0; i < memberCount; i++) {
669                Object mo = members.get(i);
670                Object o = (isMember) ?
671                    mapMemberToValue.get(mo) :
672                    mapMemberToValue.get(new ArrayHolder<Member>((Member []) mo));
673                if (o instanceof Number) {
674                    double d = ((Number) o).doubleValue();
675                    if (isMember) {
676                        mapMemberToValue.put(
677                            mo,
678                            d / total * (double) 100);
679                    } else {
680                        mapMemberToValue.put(
681                            new ArrayHolder<Member>((Member []) mo),
682                            d / total * (double) 100);
683                    }
684                }
685            }
686        }
687    
688    
689        /**
690         * Decodes the syntactic type of an operator.
691         *
692         * @param flags A encoded string which represents an operator signature,
693         *   as used by the <code>flags</code> parameter used to construct a
694         *   {@link FunDefBase}.
695         *
696         * @return A {@link Syntax}
697         */
698        public static Syntax decodeSyntacticType(String flags) {
699            char c = flags.charAt(0);
700            switch (c) {
701            case 'p':
702                return Syntax.Property;
703            case 'f':
704                return Syntax.Function;
705            case 'm':
706                return Syntax.Method;
707            case 'i':
708                return Syntax.Infix;
709            case 'P':
710                return Syntax.Prefix;
711            case 'Q':
712                return Syntax.Postfix;
713            case 'I':
714                return Syntax.Internal;
715            default:
716                throw newInternal(
717                        "unknown syntax code '" + c + "' in string '" + flags + "'");
718            }
719        }
720    
721        /**
722         * Decodes the signature of a function into a category code which describes
723         * the return type of the operator.
724         *
725         * <p>For example, <code>decodeReturnType("fnx")</code> returns
726         * <code>{@link Category#Numeric}</code>, indicating this function has a
727         * numeric return value.
728         *
729         * @param flags The signature of an operator,
730         *   as used by the <code>flags</code> parameter used to construct a
731         *   {@link FunDefBase}.
732         *
733         * @return An array {@link Category} codes.
734         */
735        public static int decodeReturnCategory(String flags) {
736            final int returnCategory = decodeCategory(flags, 1);
737            if ((returnCategory & Category.Mask) != returnCategory) {
738                throw newInternal("bad return code flag in flags '" + flags + "'");
739            }
740            return returnCategory;
741        }
742    
743        /**
744         * Decodes the <code>offset</code>th character of an encoded method
745         * signature into a type category.
746         *
747         * <p>The codes are:
748         * <table border="1">
749         *
750         * <tr><td>a</td><td>{@link Category#Array}</td></tr>
751         *
752         * <tr><td>d</td><td>{@link Category#Dimension}</td></tr>
753         *
754         * <tr><td>h</td><td>{@link Category#Hierarchy}</td></tr>
755         *
756         * <tr><td>l</td><td>{@link Category#Level}</td></tr>
757         *
758         * <tr><td>b</td><td>{@link Category#Logical}</td></tr>
759         *
760         * <tr><td>m</td><td>{@link Category#Member}</td></tr>
761         *
762         * <tr><td>N</td><td>Constant {@link Category#Numeric}</td></tr>
763         *
764         * <tr><td>n</td><td>{@link Category#Numeric}</td></tr>
765         *
766         * <tr><td>x</td><td>{@link Category#Set}</td></tr>
767         *
768         * <tr><td>#</td><td>Constant {@link Category#String}</td></tr>
769         *
770         * <tr><td>S</td><td>{@link Category#String}</td></tr>
771         *
772         * <tr><td>t</td><td>{@link Category#Tuple}</td></tr>
773         *
774         * <tr><td>v</td><td>{@link Category#Value}</td></tr>
775         *
776         * <tr><td>y</td><td>{@link Category#Symbol}</td></tr>
777         *
778         * </table>
779         *
780         * @param flags Encoded signature string
781         * @param offset 0-based offset of character within string
782         * @return A {@link Category}
783         */
784        public static int decodeCategory(String flags, int offset) {
785            char c = flags.charAt(offset);
786            switch (c) {
787            case 'a':
788                return Category.Array;
789            case 'd':
790                return Category.Dimension;
791            case 'h':
792                return Category.Hierarchy;
793            case 'l':
794                return Category.Level;
795            case 'b':
796                return Category.Logical;
797            case 'm':
798                return Category.Member;
799            case 'N':
800                return Category.Numeric | Category.Constant;
801            case 'n':
802                return Category.Numeric;
803            case 'I':
804                return Category.Numeric | Category.Integer | Category.Constant;
805            case 'i':
806                return Category.Numeric | Category.Integer;
807            case 'x':
808                return Category.Set;
809            case '#':
810                return Category.String | Category.Constant;
811            case 'S':
812                return Category.String;
813            case 't':
814                return Category.Tuple;
815            case 'v':
816                return Category.Value;
817            case 'y':
818                return Category.Symbol;
819            case 'U':
820                return Category.Null;
821            case 'e':
822                return Category.Empty;
823            case 'D':
824                return Category.DateTime;
825            default:
826                throw newInternal(
827                        "unknown type code '" + c + "' in string '" + flags + "'");
828            }
829        }
830    
831        /**
832         * Decodes a string of parameter types into an array of type codes.
833         *
834         * <p>Each character is decoded using {@link #decodeCategory(String, int)}.
835         * For example, <code>decodeParameterTypes("nx")</code> returns
836         * <code>{{@link Category#Numeric}, {@link Category#Set}}</code>.
837         *
838         * @param flags The signature of an operator,
839         *   as used by the <code>flags</code> parameter used to construct a
840         *   {@link FunDefBase}.
841         *
842         * @return An array {@link Category} codes.
843         */
844        public static int[] decodeParameterCategories(String flags) {
845            int[] parameterCategories = new int[flags.length() - 2];
846            for (int i = 0; i < parameterCategories.length; i++) {
847                parameterCategories[i] = decodeCategory(flags, i + 2);
848            }
849            return parameterCategories;
850        }
851    
852        /**
853         * Sorts an array of values.
854         */
855        public static void sortValuesDesc(Object[] values) {
856            Arrays.sort(values, DescendingValueComparator.instance);
857        }
858    
859        /**
860         * Binary searches an array of values.
861         */
862        public static int searchValuesDesc(Object[] values, Object value) {
863            return Arrays.binarySearch(
864                    values, value, DescendingValueComparator.instance);
865        }
866    
867        static double percentile(
868            Evaluator evaluator,
869            List members,
870            Calc exp,
871            double p)
872        {
873            SetWrapper sw = evaluateSet(evaluator, members, exp);
874            if (sw.errorCount > 0) {
875                return Double.NaN;
876            } else if (sw.v.size() == 0) {
877                return FunUtil.DoubleNull;
878            }
879            double[] asArray = new double[sw.v.size()];
880            for (int i = 0; i < asArray.length; i++) {
881                asArray[i] = (Double) sw.v.get(i);
882            }
883            Arrays.sort(asArray);
884    
885            /*
886             * The median is defined as the value that has exactly the same
887             * number of entries before it in the sorted list as after.
888             * So, if the number of entries in the list is odd, the
889             * median is the entry at (length-1)/2 (using zero-based indexes).
890             * If the number of entries is even, the median is defined as the
891             * arithmetic mean of the two numbers in the middle of the list, or
892             * (entries[length/2 - 1] + entries[length/2]) / 2.
893             */
894            int length = asArray.length;
895    
896            if (p == 0.5) {
897                // Special case for median.
898                if ((length & 1) == 1) {
899                    // The length is odd. Note that length/2 is an integer
900                    // expression, and it's positive so we save ourselves a divide.
901                    return asArray[length >> 1];
902                } else {
903                    return (asArray[(length >> 1) - 1] + asArray[length >> 1])
904                        / 2.0;
905                }
906            } else if (p <= 0.0) {
907                return asArray[0];
908            } else if (p >= 1.0) {
909                return asArray[length - 1];
910            } else {
911                final double jD = Math.floor(length * p);
912                int j = (int) jD;
913                double alpha = (p - jD) * length;
914                assert alpha >= 0;
915                assert alpha <= 1;
916                return asArray[j] * (1.0 - alpha)
917                    + asArray[j + 1] * alpha;
918            }
919        }
920    
921        /**
922         * Returns the member which lies upon a particular quartile according to a
923         * given expression.
924         *
925         * @param evaluator Evaluator
926         * @param members List of members
927         * @param exp Expression to rank members
928         * @param range Quartile (1, 2 or 3)
929         *
930         * @pre range >= 1 && range <= 3
931         */
932        protected static double quartile(
933                Evaluator evaluator,
934                List members,
935                Calc exp,
936                int range) {
937            Util.assertPrecondition(range >= 1 && range <= 3, "range >= 1 && range <= 3");
938    
939            SetWrapper sw = evaluateSet(evaluator, members, exp);
940            if (sw.errorCount > 0) {
941                return Double.NaN;
942            } else if (sw.v.size() == 0) {
943                return DoubleNull;
944            }
945    
946            double[] asArray = new double[sw.v.size()];
947            for (int i = 0; i < asArray.length; i++) {
948                asArray[i] = ((Double) sw.v.get(i)).doubleValue();
949            }
950    
951            Arrays.sort(asArray);
952            // get a quartile, median is a second q
953            double dm = 0.25 * asArray.length * range;
954            int median = (int) Math.floor(dm);
955            return dm == median && median < asArray.length - 1 ?
956                    (asArray[median] + asArray[median + 1]) / 2 :
957                    asArray[median];
958        }
959    
960        public static Object min(Evaluator evaluator, List members, Calc calc) {
961            SetWrapper sw = evaluateSet(evaluator, members, calc);
962            if (sw.errorCount > 0) {
963                return Double.NaN;
964            } else {
965                final int size = sw.v.size();
966                if (size == 0) {
967                    return Util.nullValue;
968                } else {
969                    Double min = (Double) sw.v.get(0);
970                    for (int i = 1; i < size; i++) {
971                        Double iValue = (Double) sw.v.get(i);
972                        if (iValue < min) {
973                            min = iValue;
974                        }
975                    }
976                    return min;
977                }
978            }
979        }
980    
981        public static Object max(Evaluator evaluator, List members, Calc exp) {
982            SetWrapper sw = evaluateSet(evaluator, members, exp);
983            if (sw.errorCount > 0) {
984                return Double.NaN;
985            } else {
986                final int size = sw.v.size();
987                if (size == 0) {
988                    return Util.nullValue;
989                } else {
990                    Double max = (Double) sw.v.get(0);
991                    for (int i = 1; i < size; i++) {
992                        Double iValue = (Double) sw.v.get(i);
993                        if (iValue > max) {
994                            max = iValue;
995                        }
996                    }
997                    return max;
998                }
999            }
1000        }
1001    
1002        static Object var(
1003                Evaluator evaluator,
1004                List members,
1005                Calc exp,
1006                boolean biased) {
1007            SetWrapper sw = evaluateSet(evaluator, members, exp);
1008            return _var(sw, biased);
1009        }
1010    
1011        private static Object _var(SetWrapper sw, boolean biased) {
1012            if (sw.errorCount > 0) {
1013                return new Double(Double.NaN);
1014            } else if (sw.v.size() == 0) {
1015                return Util.nullValue;
1016            } else {
1017                double stdev = 0.0;
1018                double avg = _avg(sw);
1019                for (int i = 0; i < sw.v.size(); i++) {
1020                    stdev += Math.pow((((Double) sw.v.get(i)).doubleValue() - avg),2);
1021                }
1022                int n = sw.v.size();
1023                if (!biased) {
1024                    n--;
1025                }
1026                return new Double(stdev / (double) n);
1027            }
1028        }
1029    
1030        static double correlation(
1031                Evaluator evaluator,
1032                List memberList,
1033                Calc exp1,
1034                Calc exp2) {
1035            SetWrapper sw1 = evaluateSet(evaluator, memberList, exp1);
1036            SetWrapper sw2 = evaluateSet(evaluator, memberList, exp2);
1037            Object covar = _covariance(sw1, sw2, false);
1038            Object var1 = _var(sw1, false); //this should be false, yes?
1039            Object var2 = _var(sw2, false);
1040            if ((covar instanceof Double) &&
1041                (var1 instanceof Double) &&
1042                (var2 instanceof Double)) {
1043                return ((Double) covar).doubleValue() /
1044                        Math.sqrt(((Double) var1).doubleValue() *
1045                        ((Double) var2).doubleValue());
1046            } else {
1047                return DoubleNull;
1048            }
1049        }
1050    
1051        static Object covariance(Evaluator evaluator, List members,
1052                Calc exp1, Calc exp2, boolean biased) {
1053            SetWrapper sw1 = evaluateSet(evaluator.push(), members, exp1);
1054            SetWrapper sw2 = evaluateSet(evaluator.push(), members, exp2);
1055            // todo: because evaluateSet does not add nulls to the SetWrapper, this
1056            // solution may lead to mismatched lists and is therefore not robust
1057            return _covariance(sw1, sw2, biased);
1058        }
1059    
1060    
1061        private static Object _covariance(SetWrapper sw1,
1062                                          SetWrapper sw2,
1063                                          boolean biased) {
1064            if (sw1.v.size() != sw2.v.size()) {
1065                return Util.nullValue;
1066            }
1067            double avg1 = _avg(sw1);
1068            double avg2 = _avg(sw2);
1069            double covar = 0.0;
1070            for (int i = 0; i < sw1.v.size(); i++) {
1071                //all of this casting seems inefficient - can we make SetWrapper
1072                //contain an array of double instead?
1073                double diff1 = (((Double) sw1.v.get(i)).doubleValue() - avg1);
1074                double diff2 = (((Double) sw2.v.get(i)).doubleValue() - avg2);
1075                covar += (diff1 * diff2);
1076            }
1077            int n = sw1.v.size();
1078            if (!biased) {
1079                n--;
1080            }
1081            return new Double(covar / (double) n);
1082        }
1083    
1084        static Object stdev(
1085                Evaluator evaluator,
1086                List members,
1087                Calc exp,
1088                boolean biased) {
1089            Object o = var(evaluator, members, exp, biased);
1090            return (o instanceof Double)
1091                ? new Double(Math.sqrt(((Double) o).doubleValue()))
1092                : o;
1093        }
1094    
1095        public static Object avg(Evaluator evaluator, List members, Calc calc) {
1096            SetWrapper sw = evaluateSet(evaluator, members, calc);
1097            return (sw.errorCount > 0) ?
1098                    new Double(Double.NaN) :
1099                    (sw.v.size() == 0) ?
1100                    Util.nullValue :
1101                    new Double(_avg(sw));
1102        }
1103    
1104        //todo: parameterize inclusion of nulls
1105        //also, maybe make _avg a method of setwrapper, so we can cache the result (i.e. for correl)
1106        private static double _avg(SetWrapper sw) {
1107            double sum = 0.0;
1108            for (int i = 0; i < sw.v.size(); i++) {
1109                sum += ((Double) sw.v.get(i)).doubleValue();
1110            }
1111            //todo: should look at context and optionally include nulls
1112            return sum / (double) sw.v.size();
1113        }
1114    
1115        public static Object sum(Evaluator evaluator, List members, Calc exp) {
1116            double d = sumDouble(evaluator, members, exp);
1117            return d == DoubleNull ? Util.nullValue : new Double(d);
1118        }
1119    
1120        public static double sumDouble(Evaluator evaluator, List members, Calc exp) {
1121            SetWrapper sw = evaluateSet(evaluator, members, exp);
1122            if (sw.errorCount > 0) {
1123                return Double.NaN;
1124            } else if (sw.v.size() == 0) {
1125                return DoubleNull;
1126            } else {
1127                double sum = 0.0;
1128                for (int i = 0; i < sw.v.size(); i++) {
1129                    sum += ((Double) sw.v.get(i)).doubleValue();
1130                }
1131                return sum;
1132            }
1133        }
1134        public static double sumDouble(Evaluator evaluator, Iterable iterable, Calc exp) {
1135            SetWrapper sw = evaluateSet(evaluator, iterable, exp);
1136            if (sw.errorCount > 0) {
1137                return Double.NaN;
1138            } else if (sw.v.size() == 0) {
1139                return DoubleNull;
1140            } else {
1141                double sum = 0.0;
1142                for (int i = 0; i < sw.v.size(); i++) {
1143                    sum += ((Double) sw.v.get(i)).doubleValue();
1144                }
1145                return sum;
1146            }
1147        }
1148        public static int count(
1149                Evaluator evaluator,
1150                Iterable iterable,
1151                boolean includeEmpty) {
1152            if (iterable == null) {
1153                return 0;
1154            }
1155            if (includeEmpty) {
1156                if (iterable instanceof Collection) {
1157                    return ((Collection) iterable).size();
1158                } else {
1159                    int retval = 0;
1160                    Iterator it = iterable.iterator();
1161                    while (it.hasNext()) {
1162                        // must get the next one
1163                        it.next();
1164                        retval++;
1165                    }
1166                    return retval;
1167                }
1168            } else {
1169                int retval = 0;
1170                for (Object object : iterable) {
1171                    if (object instanceof Member) {
1172                        evaluator.setContext((Member) object);
1173                    } else {
1174                        evaluator.setContext((Member[]) object);
1175                    }
1176                    Object o = evaluator.evaluateCurrent();
1177                    if (o != Util.nullValue && o != null) {
1178                        retval++;
1179                    }
1180                }
1181                return retval;
1182            }
1183        }
1184    
1185    /*
1186        public static int countOld(
1187                Evaluator evaluator,
1188                List members,
1189                boolean includeEmpty) {
1190            if (members == null) {
1191    System.out.println("FunUtil.count List: null 0");
1192                return 0;
1193            }
1194            if (includeEmpty) {
1195    System.out.println("FunUtil.count List: "+members.size());
1196                return members.size();
1197            } else {
1198                int retval = 0;
1199                for (int i = 0; i < members.size(); i++) {
1200                    final Object member = members.get(i);
1201                    if (member instanceof Member) {
1202                        evaluator.setContext((Member) member);
1203                    } else {
1204                        evaluator.setContext((Member[]) member);
1205                    }
1206                    Object o = evaluator.evaluateCurrent();
1207                    if (o != Util.nullValue && o != null) {
1208                        retval++;
1209                    }
1210                }
1211    System.out.println("FunUtil.count List: "+retval);
1212                return retval;
1213            }
1214        }
1215        public static int countIterable(
1216                Evaluator evaluator,
1217                Iterable iterable,
1218                boolean includeEmpty) {
1219            if (iterable == null) {
1220    System.out.println("FunUtil.countIterable Iterable: null 0");
1221                return 0;
1222            }
1223            int retval = 0;
1224            Iterator it = iterable.iterator();
1225            while (it.hasNext()) {
1226                final Object member = it.next();
1227                if (member instanceof Member) {
1228                    evaluator.setContext((Member) member);
1229                } else if (member instanceof Member[]) {
1230                    evaluator.setContext((Member[]) member);
1231                }
1232                if (includeEmpty) {
1233                    retval++;
1234                } else {
1235                    Object o = evaluator.evaluateCurrent();
1236                    if (o != Util.nullValue && o != null) {
1237                        retval++;
1238                    }
1239                }
1240            }
1241    System.out.println("FunUtil.countIterable Iterable: "+retval);
1242            return retval;
1243        }
1244    */
1245    
1246        /**
1247         * Evaluates <code>exp</code> (if defined) over <code>members</code> to
1248         * generate a {@link List} of {@link SetWrapper} objects, which contains
1249         * a {@link Double} value and meta information, unlike
1250         * {@link #evaluateMembers}, which only produces values.
1251         *
1252         * @pre exp != null
1253         */
1254        static SetWrapper evaluateSet(
1255                Evaluator evaluator,
1256                Iterable members,
1257                Calc calc) {
1258            Util.assertPrecondition(members != null, "members != null");
1259            Util.assertPrecondition(calc != null, "calc != null");
1260            Util.assertPrecondition(calc.getType() instanceof ScalarType);
1261    
1262            // todo: treat constant exps as evaluateMembers() does
1263            SetWrapper retval = new SetWrapper();
1264            for (Object obj : members) {
1265                if (obj instanceof Member[]) {
1266                    evaluator.setContext((Member[])obj);
1267                } else {
1268                    evaluator.setContext((Member)obj);
1269                }
1270                Object o = calc.evaluate(evaluator);
1271                if (o == null || o == Util.nullValue) {
1272                    retval.nullCount++;
1273                } else if (o instanceof Throwable) {
1274                    // Carry on summing, so that if we are running in a
1275                    // BatchingCellReader, we find out all the dependent cells we
1276                    // need
1277                    retval.errorCount++;
1278                } else if (o instanceof Double) {
1279                    retval.v.add(o);
1280                } else if (o instanceof Number) {
1281                    retval.v.add(((Number) o).doubleValue());
1282                } else {
1283                    retval.v.add(o);
1284                }
1285            }
1286            return retval;
1287        }
1288    
1289        /**
1290         * Evaluates one or more expressions against the member list returning
1291         * a SetWrapper array. Where this differs very significantly from the
1292         * above evaluateSet methods is how it count null values and Throwables;
1293         * this method adds nulls to the SetWrapper Vector rather than not adding
1294         * anything - as the above method does. The impact of this is that if, for
1295         * example, one was creating a list of x,y values then each list will have
1296         * the same number of values (though some might be null) - this allows
1297         * higher level code to determine how to handle the lack of data rather than
1298         * having a non-equal number (if one is plotting x,y values it helps to
1299         * have the same number and know where a potential gap is the data is.
1300         */
1301        static SetWrapper[] evaluateSet(
1302                Evaluator evaluator,
1303                List members,
1304                DoubleCalc[] calcs,
1305                boolean isTuples) {
1306            Util.assertPrecondition(calcs != null, "calcs != null");
1307    
1308            // todo: treat constant exps as evaluateMembers() does
1309            SetWrapper[] retvals = new SetWrapper[calcs.length];
1310            for (int i = 0; i < calcs.length; i++) {
1311                retvals[i] = new SetWrapper();
1312            }
1313            for (final Object member : members) {
1314                if (isTuples) {
1315                    evaluator.setContext((List<Member>) member);
1316                } else {
1317                    evaluator.setContext((Member) member);
1318                }
1319                for (int i = 0; i < calcs.length; i++) {
1320                    DoubleCalc calc = calcs[i];
1321                    SetWrapper retval = retvals[i];
1322                    double o = calc.evaluateDouble(evaluator);
1323                    if (o == FunUtil.DoubleNull) {
1324                        retval.nullCount++;
1325                        retval.v.add(null);
1326                    } else {
1327                        retval.v.add(o);
1328                    }
1329                    // TODO: If the expression yielded an error, carry on
1330                    // summing, so that if we are running in a
1331                    // BatchingCellReader, we find out all the dependent cells
1332                    // we need
1333                }
1334            }
1335            return retvals;
1336        }
1337    
1338        static List<Member> periodsToDate(
1339                Evaluator evaluator,
1340                Level level,
1341                Member member) {
1342            if (member == null) {
1343                member = evaluator.getContext(level.getHierarchy().getDimension());
1344            }
1345            Member m = member;
1346            while (m != null) {
1347                if (m.getLevel() == level) {
1348                    break;
1349                }
1350                m = m.getParentMember();
1351            }
1352            // If m == null, then "level" was lower than member's level.
1353            // periodsToDate([Time].[Quarter], [Time].[1997] is valid,
1354            //  but will return an empty List
1355            List<Member> members = new ArrayList<Member>();
1356            if (m != null) {
1357                // e.g. m is [Time].[1997] and member is [Time].[1997].[Q1].[3]
1358                // we now have to make m to be the first member of the range,
1359                // so m becomes [Time].[1997].[Q1].[1]
1360                SchemaReader reader = evaluator.getSchemaReader();
1361                m = Util.getFirstDescendantOnLevel(reader, m, member.getLevel());
1362                reader.getMemberRange(level, m, member, members);
1363            }
1364            return members;
1365        }
1366    
1367        static List<Member> memberRange(
1368            Evaluator evaluator,
1369            Member startMember,
1370            Member endMember)
1371        {
1372            final Level level = startMember.getLevel();
1373            assertTrue(level == endMember.getLevel());
1374            List<Member> members = new ArrayList<Member>();
1375            evaluator.getSchemaReader().getMemberRange(
1376                level, startMember, endMember, members);
1377    
1378            if (members.isEmpty()) {
1379                // The result is empty, so maybe the members are reversed. This is
1380                // cheaper than comparing the members before we call getMemberRange.
1381                evaluator.getSchemaReader().getMemberRange(
1382                    level, endMember, startMember, members);
1383            }
1384            return members;
1385        }
1386    
1387        /**
1388         * Returns the member under ancestorMember having the same relative position
1389         * under member's parent.
1390         * <p>For exmaple, cousin([Feb 2001], [Q3 2001]) is [August 2001].
1391         * @param schemaReader The reader to use
1392         * @param member The member for which we'll find the cousin.
1393         * @param ancestorMember The cousin's ancestor.
1394         * @return The child of <code>ancestorMember</code> in the same position under
1395         * <code>ancestorMember</code> as <code>member</code> is under its parent.
1396         */
1397        static Member cousin(SchemaReader schemaReader,
1398                             Member member,
1399                             Member ancestorMember) {
1400            if (ancestorMember.isNull()) {
1401                return ancestorMember;
1402            }
1403            if (member.getHierarchy() != ancestorMember.getHierarchy()) {
1404                throw MondrianResource.instance().CousinHierarchyMismatch.ex(
1405                    member.getUniqueName(), ancestorMember.getUniqueName());
1406            }
1407            if (member.getLevel().getDepth() < ancestorMember.getLevel().getDepth()) {
1408                return member.getHierarchy().getNullMember();
1409            }
1410    
1411            Member cousin = cousin2(schemaReader, member, ancestorMember);
1412            if (cousin == null) {
1413                cousin = member.getHierarchy().getNullMember();
1414            }
1415    
1416            return cousin;
1417        }
1418    
1419        static private Member cousin2(SchemaReader schemaReader,
1420                                      Member member1,
1421                                      Member member2) {
1422            if (member1.getLevel() == member2.getLevel()) {
1423                return member2;
1424            }
1425            Member uncle = cousin2(schemaReader, member1.getParentMember(), member2);
1426            if (uncle == null) {
1427                return null;
1428            }
1429            int ordinal = Util.getMemberOrdinalInParent(schemaReader, member1);
1430            List<Member> cousins = schemaReader.getMemberChildren(uncle);
1431            if (cousins.size() <= ordinal) {
1432                return null;
1433            }
1434            return cousins.get(ordinal);
1435        }
1436    
1437        /**
1438         * Returns the ancestor of <code>member</code> at the given level
1439         * or distance. It is assumed that any error checking required
1440         * has been done prior to calling this function.
1441         * <p>This method takes into consideration the fact that there
1442         * may be intervening hidden members between <code>member</code>
1443         * and the ancestor. If <code>targetLevel</code> is not null, then
1444         * the method will only return a member if the level at
1445         * <code>distance</code> from the member is actually the
1446         * <code>targetLevel</code> specified.
1447         * @param evaluator The evaluation context
1448         * @param member The member for which the ancestor is to be found
1449         * @param distance The distance up the chain the ancestor is to
1450         * be found.
1451         * @param targetLevel The desired targetLevel of the ancestor. If <code>null</code>,
1452         * then the distance completely determines the desired ancestor.
1453         * @return The ancestor member, or <code>null</code> if no such
1454         * ancestor exists.
1455         */
1456        static Member ancestor(Evaluator evaluator,
1457                               Member member,
1458                               int distance,
1459                               Level targetLevel) {
1460            if ((targetLevel != null) &&
1461                (member.getHierarchy() != targetLevel.getHierarchy())) {
1462                throw MondrianResource.instance().MemberNotInLevelHierarchy.ex(
1463                    member.getUniqueName(), targetLevel.getUniqueName());
1464            }
1465    
1466            if (distance == 0) {
1467                /*
1468                * Shortcut if there's nowhere to go.
1469                */
1470                return member;
1471            } else if (distance < 0) {
1472                /*
1473                * Can't go backwards.
1474                */
1475                return member.getHierarchy().getNullMember();
1476            }
1477    
1478            List<Member> ancestors = member.getAncestorMembers();
1479            final SchemaReader schemaReader = evaluator.getSchemaReader();
1480    
1481            Member result = member.getHierarchy().getNullMember();
1482    
1483            searchLoop:
1484            for (int i = 0; i < ancestors.size(); i++) {
1485                final Member ancestorMember = ancestors.get(i);
1486    
1487                if (targetLevel != null) {
1488                    if (ancestorMember.getLevel() == targetLevel) {
1489                        if (schemaReader.isVisible(ancestorMember)) {
1490                            result = ancestorMember;
1491                            break;
1492                        } else {
1493                            result = member.getHierarchy().getNullMember();
1494                            break;
1495                        }
1496                    }
1497                } else {
1498                    if (schemaReader.isVisible(ancestorMember)) {
1499                        distance--;
1500    
1501                        //
1502                        // Make sure that this ancestor is really on the right
1503                        // targetLevel. If a targetLevel was specified and at least
1504                        // one of the ancestors was hidden, this this algorithm goes
1505                        // too far up the ancestor list. It's not a problem, except
1506                        // that we need to check if it's happened and return the
1507                        // hierarchy's null member instead.
1508                        //
1509                        // For example, consider what happens with
1510                        // Ancestor([Store].[Israel].[Haifa], [Store].[Store State]).
1511                        // The distance from [Haifa] to [Store State] is 1, but that
1512                        // lands us at the country targetLevel, which is clearly
1513                        // wrong.
1514                        //
1515                        if (distance == 0) {
1516                            result = ancestorMember;
1517                            break;
1518                        }
1519                    }
1520                }
1521            }
1522    
1523            return result;
1524        }
1525    
1526        /**
1527         * Compares a pair of members according to their positions in a
1528         * prefix-order (or postfix-order, if <code>post</code> is true) walk
1529         * over a hierarchy.
1530         *
1531         * @param m1 First member
1532         * @param m2 Second member
1533         * @param post Whether to sortMembers in postfix order. If true, a parent will
1534         *   sortMembers immediately after its last child. If false, a parent will sortMembers
1535         *   immediately before its first child.
1536         * @return -1 if m1 collates before m2,
1537         *   0 if m1 equals m2,
1538         *   1 if m1 collates after m2
1539         */
1540        public static int compareHierarchically(
1541            Member m1,
1542            Member m2,
1543            boolean post)
1544        {
1545            // Strip away the LimitedRollupMember wrapper, if it exists. The
1546            // wrapper does not implement equals and comparisons correctly. This
1547            // is safe this method has no side-effects: it just returns an int.
1548            if (m1 instanceof RolapHierarchy.LimitedRollupMember) {
1549                m1 = ((RolapHierarchy.LimitedRollupMember) m1).member;
1550            }
1551            if (m2 instanceof RolapHierarchy.LimitedRollupMember) {
1552                m2 = ((RolapHierarchy.LimitedRollupMember) m2).member;
1553            }
1554            if (equals(m1, m2)) {
1555                return 0;
1556            }
1557            while (true) {
1558                int depth1 = m1.getDepth();
1559                int depth2 = m2.getDepth();
1560                if (depth1 < depth2) {
1561                    m2 = m2.getParentMember();
1562                    if (equals(m1, m2)) {
1563                        return post ? 1 : -1;
1564                    }
1565                } else if (depth1 > depth2) {
1566                    m1 = m1.getParentMember();
1567                    if (equals(m1, m2)) {
1568                        return post ? -1 : 1;
1569                    }
1570                } else {
1571                    Member prev1 = m1;
1572                    Member prev2 = m2;
1573                    m1 = m1.getParentMember();
1574                    m2 = m2.getParentMember();
1575                    if (equals(m1, m2)) {
1576                        final int c = compareSiblingMembers(prev1, prev2);
1577                        // compareHierarchically needs to impose a total order;
1578                        // cannot return 0 for non-equal members
1579                        assert c != 0 :
1580                            "Members " + prev1 + ", " + prev2 +
1581                                " are not equal, but compare returned 0.";
1582                        return c;
1583                    }
1584                }
1585            }
1586        }
1587    
1588        /**
1589         * Compares two members which are known to have the same parent.
1590         *
1591         * First, compare by ordinal.
1592         * This is only valid now we know they're siblings, because
1593         * ordinals are only unique within a parent.
1594         * If the dimension does not use ordinals, both ordinals
1595         * will be -1.
1596         *
1597         * <p>If the ordinals do not differ, compare using regular member
1598         * comparison.
1599         *
1600         * @param m1 First member
1601         * @param m2 Second member
1602         * @return -1 if m1 collates less than m2,
1603         *   1 if m1 collates after m2,
1604         *   0 if m1 == m2.
1605         */
1606        public static int compareSiblingMembers(Member m1, Member m2) {
1607            // calculated members collate after non-calculated
1608            final boolean calculated1 = m1.isCalculatedInQuery();
1609            final boolean calculated2 = m2.isCalculatedInQuery();
1610            if (calculated1) {
1611                if (!calculated2) {
1612                    return 1;
1613                }
1614            } else {
1615                if (calculated2) {
1616                    return -1;
1617                }
1618            }
1619            final Comparable k1 = m1.getOrderKey();
1620            final Comparable k2 = m2.getOrderKey();
1621            if ((k1 != null) && (k2 != null)) {
1622                return k1.compareTo(k2);
1623            } else {
1624                final int ordinal1 = m1.getOrdinal();
1625                final int ordinal2 = m2.getOrdinal();
1626                return (ordinal1 == ordinal2) ?
1627                    m1.compareTo(m2) :
1628                    (ordinal1 < ordinal2) ?
1629                    -1 :
1630                    1;
1631            }
1632        }
1633    
1634        /**
1635         * Returns whether one of the members in a tuple is null.
1636         */
1637        static boolean tupleContainsNullMember(Member[] tuple) {
1638            for (Member member : tuple) {
1639                if (member.isNull()) {
1640                    return true;
1641                }
1642            }
1643            return false;
1644        }
1645    
1646        public static Member[] makeNullTuple(final TupleType tupleType) {
1647            Member[] members = new Member[tupleType.elementTypes.length];
1648            for (int i = 0; i < tupleType.elementTypes.length; i++) {
1649                MemberType type = (MemberType) tupleType.elementTypes[i];
1650                members[i] = makeNullMember(type);
1651            }
1652            return members;
1653        }
1654    
1655        static Member makeNullMember(
1656                MemberType memberType) {
1657            Hierarchy hierarchy = memberType.getHierarchy();
1658            if (hierarchy == null) {
1659                return NullMember;
1660            }
1661            return hierarchy.getNullMember();
1662        }
1663    
1664        /**
1665         * Validates the arguments to a function and resolves the function.
1666         *
1667         * @param validator validator used to validate function arguments and
1668         * resolve the function
1669         * @param args arguments to the function
1670         * @param newArgs returns the resolved arguments to the function
1671         * @param name function name
1672         * @param syntax syntax style used to invoke function
1673         *
1674         * @return resolved function definition
1675         */
1676        public static FunDef resolveFunArgs(
1677            Validator validator, Exp[] args, Exp[] newArgs, String name,
1678            Syntax syntax) {
1679    
1680            Query query = validator.getQuery();
1681            Cube cube = null;
1682            if (query != null) {
1683                cube = query.getCube();
1684            }
1685            for (int i = 0; i < args.length; i++) {
1686                newArgs[i] = validator.validate(args[i], false);
1687            }
1688            final FunTable funTable = validator.getFunTable();
1689            FunDef funDef = funTable.getDef(newArgs, validator, name, syntax);
1690    
1691            // If the first argument to a function is either:
1692            // 1) the measures dimension or
1693            // 2) a measures member where the function returns another member or
1694            //    a set,
1695            // then these are functions that dynamically return one or more
1696            // members ofthe measures dimension.  In that case, we cannot use
1697            // native cross joins because the functions need to be executed to
1698            // determine the resultant measures.
1699            //
1700            // As a result, we disallow functions like AllMembers applied on the
1701            // Measures dimension as well as functions like the range operator,
1702            // siblings, and lag, when the argument is a measure member.
1703            // However, we do allow functions like isEmpty, rank, and topPercent.
1704            // Also, the set function is ok since it just enumerates its
1705            // arguments.
1706            if (!(funDef instanceof SetFunDef) && query != null &&
1707                query.nativeCrossJoinVirtualCube())
1708            {
1709                int[] paramCategories = funDef.getParameterCategories();
1710                if (paramCategories.length > 0 &&
1711                    ((paramCategories[0] == Category.Dimension &&
1712                        newArgs[0] instanceof DimensionExpr &&
1713                        ((DimensionExpr) newArgs[0]).getDimension().
1714                            getOrdinal(cube) == 0) ||
1715                    (paramCategories[0] == Category.Member &&
1716                        newArgs[0] instanceof MemberExpr &&
1717                        ((MemberExpr) newArgs[0]).getMember().getDimension().
1718                            getOrdinal(cube) == 0 &&
1719                        (funDef.getReturnCategory() == Category.Member ||
1720                            funDef.getReturnCategory() == Category.Set))))
1721                {
1722                    query.setVirtualCubeNonNativeCrossJoin();
1723                }
1724            }
1725    
1726            return funDef;
1727        }
1728    
1729        static void appendTuple(StringBuilder buf, Member[] members) {
1730            buf.append("(");
1731            for (int j = 0; j < members.length; j++) {
1732                if (j > 0) {
1733                    buf.append(", ");
1734                }
1735                Member member = members[j];
1736                buf.append(member.getUniqueName());
1737            }
1738            buf.append(")");
1739        }
1740    
1741        /**
1742         * Returns whether two tuples are equal.
1743         *
1744         * <p>The members are allowed to be in different positions. For example,
1745         * <blockquote>
1746         * <code>([Gender].[M], [Store].[USA]) IS ([Store].[USA], [Gender].[M])</code>
1747         * </blockquote>
1748         * returns <code>true</code>.
1749         */
1750        static boolean equalTuple(Member[] members0, Member[] members1) {
1751            final int count = members0.length;
1752            if (count != members1.length) {
1753                return false;
1754            }
1755            outer:
1756            for (int i = 0; i < count; i++) {
1757                // First check the member at the corresponding ordinal. It is more
1758                // likely to be there.
1759                final Member member0 = members0[i];
1760                if (member0.equals(members1[i])) {
1761                    continue;
1762                }
1763                // Look for this member in other positions.
1764                // We can assume that the members in members0 are distinct (because
1765                // they belong to different dimensions), so this test is valid.
1766                for (int j = 0; j < count; j++) {
1767                    if (i != j && member0.equals(members1[j])) {
1768                        continue outer;
1769                    }
1770                }
1771                // This member of members0 does not occur in any position of
1772                // members1. The tuples are not equal.
1773                return false;
1774            }
1775            return true;
1776        }
1777    
1778        static FunDef createDummyFunDef(
1779            Resolver resolver,
1780            int returnCategory,
1781            Exp[] args)
1782        {
1783            final int[] argCategories = ExpBase.getTypes(args);
1784            return new FunDefBase(resolver, returnCategory, argCategories) {
1785            };
1786        }
1787    
1788        public static List<Member> getNonEmptyMemberChildren(
1789            Evaluator evaluator,
1790            Member member)
1791        {
1792            SchemaReader sr = evaluator.getSchemaReader();
1793            if (evaluator.isNonEmpty()) {
1794                return sr.getMemberChildren(member, evaluator);
1795            } else {
1796                return sr.getMemberChildren(member);
1797            }
1798        }
1799    
1800        /**
1801         * Returns members of a level which are not empty (according to the
1802         * criteria expressed by the evaluator).
1803         *
1804         * @param evaluator Evaluator, determines non-empty criteria
1805         * @param level Level
1806         * @param includeCalcMembers Whether to include calculated members
1807         */
1808        static List<Member> getNonEmptyLevelMembers(
1809            final Evaluator evaluator,
1810            final Level level,
1811            final boolean includeCalcMembers)
1812        {
1813            SchemaReader sr = evaluator.getSchemaReader();
1814            if (evaluator.isNonEmpty()) {
1815                List<Member> members = sr.getLevelMembers(level, evaluator);
1816                if (includeCalcMembers) {
1817                    return addLevelCalculatedMembers(sr, level, members);
1818                }
1819                return members;
1820            }
1821            return sr.getLevelMembers(level, includeCalcMembers);
1822        }
1823    
1824        static List<Member> levelMembers(
1825            final Level level,
1826            final Evaluator evaluator,
1827            final boolean includeCalcMembers)
1828        {
1829            List <Member> memberList =
1830                getNonEmptyLevelMembers(evaluator, level, includeCalcMembers);
1831            if (!includeCalcMembers) {
1832                memberList = removeCalculatedMembers(memberList);
1833            }
1834            hierarchize(memberList, false);
1835            return memberList;
1836        }
1837    
1838        static List<Member> hierarchyMembers(
1839            Hierarchy hierarchy,
1840            Evaluator evaluator,
1841            final boolean includeCalcMembers)
1842        {
1843            List<Member> memberList;
1844            if (evaluator.isNonEmpty()) {
1845                // Allow the SQL generator to generate optimized SQL since we know
1846                // we're only interested in non-empty members of this level.
1847                memberList = new ArrayList<Member>();
1848                for (Level level : hierarchy.getLevels()) {
1849                    List<Member> members =
1850                        getNonEmptyLevelMembers(
1851                            evaluator, level, includeCalcMembers);
1852                    memberList.addAll(members);
1853                }
1854            } else {
1855                memberList = addMembers(
1856                    evaluator.getSchemaReader(),
1857                    new ConcatenableList<Member>(), hierarchy);
1858                if (!includeCalcMembers && memberList != null) {
1859                    memberList = removeCalculatedMembers(memberList);
1860                }
1861            }
1862            hierarchize(memberList, false);
1863            return memberList;
1864        }
1865    
1866        static List<Member> dimensionMembers(
1867            Dimension dimension,
1868            Evaluator evaluator,
1869            final boolean includeCalcMembers)
1870        {
1871            Hierarchy hierarchy = dimension.getHierarchy();
1872            return hierarchyMembers(hierarchy, evaluator, includeCalcMembers);
1873        }
1874    
1875        // ~ Inner classes ---------------------------------------------------------
1876    
1877        private static abstract class MemberComparator implements Comparator<Member> {
1878            private static final Logger LOGGER =
1879                    Logger.getLogger(MemberComparator.class);
1880            Map<Member, Object> mapMemberToValue;
1881            private boolean desc;
1882    
1883            MemberComparator(Map<Member, Object> mapMemberToValue, boolean desc) {
1884                this.mapMemberToValue = mapMemberToValue;
1885                this.desc = desc;
1886            }
1887    
1888            Comparator<Member> wrap() {
1889                final MemberComparator comparator = this;
1890                if (LOGGER.isDebugEnabled()) {
1891                    return new Comparator<Member>() {
1892                        public int compare(Member m1, Member m2) {
1893                            final int c = comparator.compare(m1, m2);
1894                            LOGGER.debug(
1895                                    "compare " +
1896                                    m1.getUniqueName() +
1897                                    "(" + mapMemberToValue.get(m1) + "), " +
1898                                    m2.getUniqueName() +
1899                                    "(" + mapMemberToValue.get(m2) + ")" +
1900                                    " yields " + c);
1901                            return c;
1902                        }
1903                    };
1904                } else {
1905                    return this;
1906                }
1907            }
1908    
1909            protected final int compareByValue(Member m1, Member m2) {
1910                Object value1 = mapMemberToValue.get(m1),
1911                        value2 = mapMemberToValue.get(m2);
1912                final int c = FunUtil.compareValues(value1, value2);
1913                return desc ? -c : c;
1914            }
1915    
1916            protected final int compareHierarchicallyButSiblingsByValue(
1917                Member m1, Member m2)
1918            {
1919                if (FunUtil.equals(m1, m2)) {
1920                    return 0;
1921                }
1922                while (true) {
1923                    int depth1 = m1.getDepth(),
1924                            depth2 = m2.getDepth();
1925                    if (depth1 < depth2) {
1926                        m2 = m2.getParentMember();
1927                        if (Util.equals(m1, m2)) {
1928                            return -1;
1929                        }
1930                    } else if (depth1 > depth2) {
1931                        m1 = m1.getParentMember();
1932                        if (Util.equals(m1, m2)) {
1933                            return 1;
1934                        }
1935                    } else {
1936                        Member prev1 = m1, prev2 = m2;
1937                        m1 = m1.getParentMember();
1938                        m2 = m2.getParentMember();
1939                        if (Util.equals(m1, m2)) {
1940                            // including case where both parents are null
1941                            int c = compareByValue(prev1, prev2);
1942                            if (c != 0) {
1943                                return c;
1944                            }
1945                            // prev1 and prev2 are siblings.
1946                            // Order according to hierarchy, if the values do not differ.
1947                            // Needed to have a consistent sortMembers if members with equal (null!)
1948                            //  values are compared.
1949                            c = FunUtil.compareSiblingMembers(prev1, prev2);
1950                            return c;
1951                        }
1952                    }
1953                }
1954            }
1955        }
1956    
1957        private static class HierarchicalMemberComparator
1958                extends MemberComparator
1959        {
1960            HierarchicalMemberComparator(
1961                Map<Member, Object> mapMemberToValue, boolean desc)
1962            {
1963                super(mapMemberToValue, desc);
1964            }
1965    
1966            public int compare(Member m1, Member m2) {
1967                return compareHierarchicallyButSiblingsByValue(m1, m2);
1968            }
1969        }
1970    
1971        private static class BreakMemberComparator extends MemberComparator {
1972            BreakMemberComparator(Map<Member, Object> mapMemberToValue, boolean desc) {
1973                super(mapMemberToValue, desc);
1974            }
1975    
1976            public final int compare(Member m1, Member m2) {
1977                return compareByValue(m1, m2);
1978            }
1979        }
1980    
1981        /**
1982         * Compares tuples, which are represented as arrays of {@link Member}s.
1983         */
1984        private static abstract class ArrayComparator
1985            implements Comparator<Member[]>
1986        {
1987            private static final Logger LOGGER =
1988                Logger.getLogger(ArrayComparator.class);
1989    
1990            final int arity;
1991    
1992            ArrayComparator(int arity) {
1993                this.arity = arity;
1994            }
1995    
1996            Comparator<Member[]> wrap() {
1997                if (LOGGER.isDebugEnabled()) {
1998                    return new LoggingTupleComparator(this, LOGGER);
1999                } else {
2000                    return this;
2001                }
2002            }
2003        }
2004    
2005        private static class LoggingTupleComparator
2006            implements Comparator<Member[]>
2007        {
2008            private final Comparator<Member[]> comparator;
2009            private final Logger logger;
2010    
2011            LoggingTupleComparator(Comparator<Member[]> comparator, Logger logger) {
2012                this.comparator = comparator;
2013                this.logger = logger;
2014            }
2015    
2016            private static String toString(Member[] a) {
2017                StringBuilder sb = new StringBuilder();
2018                for (int i = 0; i < a.length; i++) {
2019                    Member member = a[i];
2020                    if (i > 0) {
2021                        sb.append(",");
2022                    }
2023                    sb.append(member.getUniqueName());
2024                }
2025                return sb.toString();
2026            }
2027    
2028            public int compare(Member[] a1, Member[] a2) {
2029                int c = comparator.compare(a1, a2);
2030                logger.debug(
2031                    "compare {" + toString(a1) + "}, {" + toString(a2) +
2032                        "} yields " + c);
2033                return c;
2034            }
2035        }
2036    
2037        /**
2038         * Extension to {@link ArrayComparator} which compares tuples by evaluating
2039         * an expression.
2040         */
2041        private static abstract class ArrayExpComparator
2042                extends ArrayComparator {
2043            Evaluator evaluator;
2044            final Calc calc;
2045    
2046            ArrayExpComparator(Evaluator evaluator, Calc calc, int arity) {
2047                super(arity);
2048                this.evaluator = evaluator;
2049                this.calc = calc;
2050            }
2051        }
2052    
2053        private static class HierarchicalArrayComparator
2054                extends ArrayExpComparator {
2055            private final boolean desc;
2056    
2057            HierarchicalArrayComparator(
2058                    Evaluator evaluator, Calc calc, int arity, boolean desc) {
2059                super(evaluator, calc, arity);
2060                this.desc = desc;
2061            }
2062    
2063            public int compare(Member[] a1, Member[] a2) {
2064                int c = 0;
2065                evaluator = evaluator.push();
2066                for (int i = 0; i < arity; i++) {
2067                    Member m1 = a1[i],
2068                            m2 = a2[i];
2069                    c = compareHierarchicallyButSiblingsByValue(m1, m2);
2070                    if (c != 0) {
2071                        break;
2072                    }
2073                    // compareHierarchicallyButSiblingsByValue imposes a total order
2074                    Util.assertTrue(m1.equals(m2));
2075                    evaluator.setContext(m1);
2076                }
2077                evaluator = evaluator.pop();
2078                return c;
2079            }
2080    
2081            protected int compareHierarchicallyButSiblingsByValue(
2082                    Member m1, Member m2) {
2083                if (FunUtil.equals(m1, m2)) {
2084                    return 0;
2085                }
2086                while (true) {
2087                    int depth1 = m1.getDepth(),
2088                            depth2 = m2.getDepth();
2089                    if (depth1 < depth2) {
2090                        m2 = m2.getParentMember();
2091                        if (FunUtil.equals(m1, m2)) {
2092                            return -1;
2093                        }
2094                    } else if (depth1 > depth2) {
2095                        m1 = m1.getParentMember();
2096                        if (FunUtil.equals(m1, m2)) {
2097                            return 1;
2098                        }
2099                    } else {
2100                        Member prev1 = m1, prev2 = m2;
2101                        m1 = m1.getParentMember();
2102                        m2 = m2.getParentMember();
2103                        if (FunUtil.equals(m1, m2)) {
2104                            // including case where both parents are null
2105                            int c = compareByValue(prev1, prev2);
2106                            if (c == 0) {
2107                                c = FunUtil.compareSiblingMembers(prev1, prev2);
2108                            }
2109                            return desc ? -c : c;
2110                        }
2111                    }
2112                }
2113            }
2114    
2115            private int compareByValue(Member m1, Member m2) {
2116                int c;
2117                Member old = evaluator.setContext(m1);
2118                Object v1 = calc.evaluate(evaluator);
2119                evaluator.setContext(m2);
2120                Object v2 = calc.evaluate(evaluator);
2121                // important to restore the evaluator state -- and this is faster
2122                // than calling evaluator.push()
2123                evaluator.setContext(old);
2124                c = FunUtil.compareValues(v1, v2);
2125                return c;
2126            }
2127        }
2128    
2129        private static class BreakArrayComparator extends ArrayExpComparator {
2130            BreakArrayComparator(Evaluator evaluator, Calc calc, int arity) {
2131                super(evaluator, calc, arity);
2132            }
2133    
2134            public int compare(Member[] a1, Member[] a2) {
2135                evaluator.setContext(a1);
2136                Object v1 = calc.evaluate(evaluator);
2137                evaluator.setContext(a2);
2138                Object v2 = calc.evaluate(evaluator);
2139                return FunUtil.compareValues(v1, v2);
2140            }
2141        }
2142    
2143        /**
2144         * Compares arrays of {@link Member}s so as to convert them into hierarchical
2145         * order. Applies lexicographic order to the array.
2146         */
2147        private static class HierarchizeArrayComparator extends ArrayComparator {
2148            private final boolean post;
2149    
2150            HierarchizeArrayComparator(int arity, boolean post) {
2151                super(arity);
2152                this.post = post;
2153            }
2154    
2155            public int compare(Member[] a1, Member[] a2) {
2156                for (int i = 0; i < arity; i++) {
2157                    Member m1 = a1[i],
2158                            m2 = a2[i];
2159                    int c = FunUtil.compareHierarchically(m1, m2, post);
2160                    if (c != 0) {
2161                        return c;
2162                    }
2163                }
2164                return 0;
2165            }
2166        }
2167    
2168        /**
2169         * Compares {@link Member}s so as to arrage them in prefix or postfix
2170         * hierarchical order.
2171         */
2172        private static class HierarchizeComparator implements Comparator<Member> {
2173            private final boolean post;
2174    
2175            HierarchizeComparator(boolean post) {
2176                this.post = post;
2177            }
2178            public int compare(Member m1, Member m2) {
2179                return FunUtil.compareHierarchically(m1, m2, post);
2180            }
2181        }
2182    
2183        /**
2184         * Reverses the order of a {@link Comparator}.
2185         */
2186        private static class ReverseComparator<T> implements Comparator<T> {
2187            Comparator<T> comparator;
2188            ReverseComparator(Comparator<T> comparator) {
2189                this.comparator = comparator;
2190            }
2191    
2192            public int compare(T o1, T o2) {
2193                int c = comparator.compare(o1, o2);
2194                return - c;
2195            }
2196        }
2197    
2198        static class SetWrapper {
2199            List v = new ArrayList();
2200            public int errorCount = 0, nullCount = 0;
2201    
2202            //private double avg = Double.NaN;
2203            //todo: parameterize inclusion of nulls
2204            //by making this a method of the SetWrapper, we can cache the result
2205            //this allows its reuse in Correlation
2206    //      public double getAverage() {
2207    //          if (avg == Double.NaN) {
2208    //              double sum = 0.0;
2209    //              for (int i = 0; i < resolvers.size(); i++) {
2210    //                  sum += ((Double) resolvers.elementAt(i)).doubleValue();
2211    //              }
2212    //              //todo: should look at context and optionally include nulls
2213    //              avg = sum / (double) resolvers.size();
2214    //          }
2215    //          return avg;
2216    //      }
2217        }
2218    
2219        /**
2220         * Compares cell values, so that larger values compare first.
2221         *
2222         * <p>Nulls compare last, exceptions (including the
2223         * object which indicates the the cell is not in the cache yet) next,
2224         * then numbers and strings are compared by value.
2225         */
2226        private static class DescendingValueComparator implements Comparator {
2227            /**
2228             * The singleton.
2229             */
2230            static final DescendingValueComparator instance =
2231                    new DescendingValueComparator();
2232    
2233            public int compare(Object o1, Object o2) {
2234                return - compareValues(o1, o2);
2235            }
2236        }
2237    
2238        /**
2239         * Null member of unknown hierarchy.
2240         */
2241        private static class NullMember implements Member {
2242            public Member getParentMember() {
2243                throw new UnsupportedOperationException();
2244            }
2245    
2246            public Level getLevel() {
2247                throw new UnsupportedOperationException();
2248            }
2249    
2250            public Hierarchy getHierarchy() {
2251                throw new UnsupportedOperationException();
2252            }
2253    
2254            public String getParentUniqueName() {
2255                throw new UnsupportedOperationException();
2256            }
2257    
2258            public MemberType getMemberType() {
2259                throw new UnsupportedOperationException();
2260            }
2261    
2262            public void setName(String name) {
2263                throw new UnsupportedOperationException();
2264            }
2265    
2266            public boolean isAll() {
2267                return false;
2268            }
2269    
2270            public boolean isMeasure() {
2271                throw new UnsupportedOperationException();
2272            }
2273    
2274            public boolean isNull() {
2275                return true;
2276            }
2277    
2278            public boolean isChildOrEqualTo(Member member) {
2279                throw new UnsupportedOperationException();
2280            }
2281    
2282            public boolean isCalculated() {
2283                throw new UnsupportedOperationException();
2284            }
2285    
2286            public int getSolveOrder() {
2287                throw new UnsupportedOperationException();
2288            }
2289    
2290            public Exp getExpression() {
2291                throw new UnsupportedOperationException();
2292            }
2293    
2294            public List<Member> getAncestorMembers() {
2295                throw new UnsupportedOperationException();
2296            }
2297    
2298            public boolean isCalculatedInQuery() {
2299                throw new UnsupportedOperationException();
2300            }
2301    
2302            public Object getPropertyValue(String propertyName) {
2303                throw new UnsupportedOperationException();
2304            }
2305    
2306            public Object getPropertyValue(String propertyName, boolean matchCase) {
2307                throw new UnsupportedOperationException();
2308            }
2309    
2310            public String getPropertyFormattedValue(String propertyName) {
2311                throw new UnsupportedOperationException();
2312            }
2313    
2314            public void setProperty(String name, Object value) {
2315                throw new UnsupportedOperationException();
2316            }
2317    
2318            public Property[] getProperties() {
2319                throw new UnsupportedOperationException();
2320            }
2321    
2322            public int getOrdinal() {
2323                throw new UnsupportedOperationException();
2324            }
2325    
2326            public Comparable getOrderKey() {
2327                throw new UnsupportedOperationException();
2328            }
2329    
2330            public boolean isHidden() {
2331                throw new UnsupportedOperationException();
2332            }
2333    
2334            public int getDepth() {
2335                throw new UnsupportedOperationException();
2336            }
2337    
2338            public Member getDataMember() {
2339                throw new UnsupportedOperationException();
2340            }
2341    
2342            public String getUniqueName() {
2343                throw new UnsupportedOperationException();
2344            }
2345    
2346            public String getName() {
2347                throw new UnsupportedOperationException();
2348            }
2349    
2350            public String getDescription() {
2351                throw new UnsupportedOperationException();
2352            }
2353    
2354            public OlapElement lookupChild(SchemaReader schemaReader,Id.Segment s) {
2355                throw new UnsupportedOperationException();
2356            }
2357    
2358            public OlapElement lookupChild(
2359                SchemaReader schemaReader, Id.Segment s, MatchType matchType) {
2360                throw new UnsupportedOperationException();
2361            }
2362    
2363            public String getQualifiedName() {
2364                throw new UnsupportedOperationException();
2365            }
2366    
2367            public String getCaption() {
2368                throw new UnsupportedOperationException();
2369            }
2370    
2371            public Dimension getDimension() {
2372                throw new UnsupportedOperationException();
2373            }
2374    
2375            public int compareTo(Object o) {
2376                throw new UnsupportedOperationException();
2377            }
2378    
2379            public boolean equals(Object obj) {
2380                throw new UnsupportedOperationException();
2381            }
2382    
2383            public int hashCode() {
2384                throw new UnsupportedOperationException();
2385            }
2386        }
2387    }
2388    
2389    // End FunUtil.java