Package mondrian.olap.fun

Source Code of mondrian.olap.fun.FunUtil

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

import mondrian.calc.*;
import mondrian.calc.impl.*;
import mondrian.mdx.*;
import mondrian.olap.*;
import mondrian.olap.type.*;
import mondrian.resource.MondrianResource;
import mondrian.rolap.*;
import mondrian.util.*;

import org.apache.commons.collections.ComparatorUtils;
import org.apache.commons.collections.comparators.ComparatorChain;
import org.apache.log4j.Logger;

import java.util.*;

/**
* {@code FunUtil} contains a set of methods useful within the {@code
* mondrian.olap.fun} package.
*
* @author jhyde
* @since 1.0
*/
public class FunUtil extends Util {
    private static final Logger LOGGER =
        Logger.getLogger(FunUtil.class);
    private static final String SORT_TIMING_NAME = "Sort";
    private static final String SORT_EVAL_TIMING_NAME = "EvalForSort";

    static final String[] emptyStringArray = new String[0];
    private static final boolean debug = false;
    public static final NullMember NullMember = new NullMember();

    /**
     * Special value which indicates that a {@code double} computation
     * has returned the MDX null value. See {@link DoubleCalc}.
     */
    public static final double DoubleNull = 0.000000012345;

    /**
     * Special value which indicates that a {@code double} computation
     * has returned the MDX EMPTY value. See {@link DoubleCalc}.
     */
    public static final double DoubleEmpty = -0.000000012345;

    /**
     * Special value which indicates that an {@code int} computation
     * has returned the MDX null value. See {@link mondrian.calc.IntegerCalc}.
     */
    public static final int IntegerNull = Integer.MIN_VALUE + 1;

    /**
     * Null value in three-valued boolean logic.
     * Actually, a placeholder until we actually implement 3VL.
     */
    public static final boolean BooleanNull = false;

    /**
     * Creates an exception which indicates that an error has occurred while
     * executing a given function.
     *
     * @param funDef Function being executed
     * @param message Explanatory message
     * @return Exception that can be used as a cell result
     */
    public static RuntimeException newEvalException(
        FunDef funDef,
        String message)
    {
        Util.discard(funDef); // TODO: use this
        return new MondrianEvaluationException(message);
    }

    /**
     * Creates an exception which indicates that an error has occurred while
     * executing a given function.
     *
     * @param throwable Exception
     * @return Exception that can be used as a cell result
     */
    public static RuntimeException newEvalException(Throwable throwable) {
        return new MondrianEvaluationException(
            throwable.getClass().getName() + ": " + throwable.getMessage());
    }

    /**
     * Creates an exception which indicates that an error has occurred while
     * executing a given function.
     *
     * @param message Explanatory message
     * @param throwable Exception
     * @return Exception that can be used as a cell result
     */
    public static RuntimeException newEvalException(
        String message,
        Throwable throwable)
    {
        return new MondrianEvaluationException(
            message
            + ": " + Util.getErrorMessage(throwable));
    }

    public static void checkIterListResultStyles(Calc calc) {
        switch (calc.getResultStyle()) {
        case ITERABLE:
        case LIST:
        case MUTABLE_LIST:
            break;
        default:
            throw ResultStyleException.generateBadType(
                ResultStyle.ITERABLE_LIST_MUTABLELIST,
                calc.getResultStyle());
        }
    }

    public static void checkListResultStyles(Calc calc) {
        switch (calc.getResultStyle()) {
        case LIST:
        case MUTABLE_LIST:
            break;
        default:
            throw ResultStyleException.generateBadType(
                ResultStyle.LIST_MUTABLELIST,
                calc.getResultStyle());
        }
    }

    /**
     * Returns an argument whose value is a literal.
     */
    static String getLiteralArg(
        ResolvedFunCall call,
        int i,
        String defaultValue,
        String[] allowedValues)
    {
        if (i >= call.getArgCount()) {
            if (defaultValue == null) {
                throw newEvalException(
                    call.getFunDef(),
                    "Required argument is missing");
            } else {
                return defaultValue;
            }
        }
        Exp arg = call.getArg(i);
        if (!(arg instanceof Literal)
            || arg.getCategory() != Category.Symbol)
        {
            throw newEvalException(
                call.getFunDef(),
                "Expected a symbol, found '" + arg + "'");
        }
        String s = (String) ((Literal) arg).getValue();
        StringBuilder sb = new StringBuilder(64);
        for (int j = 0; j < allowedValues.length; j++) {
            String allowedValue = allowedValues[j];
            if (allowedValue.equalsIgnoreCase(s)) {
                return allowedValue;
            }
            if (j > 0) {
                sb.append(", ");
            }
            sb.append(allowedValue);
        }
        throw newEvalException(
            call.getFunDef(),
            "Allowed values are: {" + sb + "}");
    }

    /**
     * Returns the ordinal of a literal argument. If the argument does not
     * belong to the supplied enumeration, returns -1.
     */
    static <E extends Enum<E>> E getLiteralArg(
        ResolvedFunCall call,
        int i,
        E defaultValue,
        Class<E> allowedValues)
    {
        if (i >= call.getArgCount()) {
            if (defaultValue == null) {
                throw newEvalException(
                    call.getFunDef(),
                    "Required argument is missing");
            } else {
                return defaultValue;
            }
        }
        Exp arg = call.getArg(i);
        if (!(arg instanceof Literal)
            || arg.getCategory() != Category.Symbol)
        {
            throw newEvalException(
                call.getFunDef(),
                "Expected a symbol, found '" + arg + "'");
        }
        String s = (String) ((Literal) arg).getValue();
        for (E e : allowedValues.getEnumConstants()) {
            if (e.name().equalsIgnoreCase(s)) {
                return e;
            }
        }
        StringBuilder buf = new StringBuilder(64);
        int k = 0;
        for (E e : allowedValues.getEnumConstants()) {
            if (k++ > 0) {
                buf.append(", ");
            }
            buf.append(e.name());
        }
        throw newEvalException(
            call.getFunDef(),
            "Allowed values are: {" + buf + "}");
    }

    /**
     * Throws an error if the expressions don't have the same hierarchy.
     * @throws MondrianEvaluationException if expressions don't have the same
     *     hierarchy
     */
    static void checkCompatible(Exp left, Exp right, FunDef funDef) {
        final Type leftType = TypeUtil.stripSetType(left.getType());
        final Type rightType = TypeUtil.stripSetType(right.getType());
        if (!TypeUtil.isUnionCompatible(leftType, rightType)) {
            throw newEvalException(
                funDef, "Expressions must have the same hierarchy");
        }
    }

    /**
     * Adds every element of {@code right} which is not in {@code set}
     * to both {@code set} and {@code left}.
     */
    static void addUnique(
        TupleList left,
        TupleList right,
        Set<List<Member>> set)
    {
        assert left != null;
        assert right != null;
        if (right.isEmpty()) {
            return;
        }
        for (int i = 0, n = right.size(); i < n; i++) {
            List<Member> o = right.get(i);
            if (set.add(o)) {
                left.add(o);
            }
        }
    }

    /**
     * Returns the default hierarchy of a dimension, or null if there is no
     * default.
     *
     * @see MondrianResource#CannotImplicitlyConvertDimensionToHierarchy
     *
     * @param dimension Dimension
     * @return Default hierarchy, or null
     */
    public static Hierarchy getDimensionDefaultHierarchy(Dimension dimension) {
        final Hierarchy[] hierarchies = dimension.getHierarchies();
        if (hierarchies.length == 1) {
            return hierarchies[0];
        }
        if (MondrianProperties.instance().SsasCompatibleNaming.get()) {
            // In SSAS 2005, dimensions with more than one hierarchy do not have
            // a default hierarchy.
            return null;
        }
        for (Hierarchy hierarchy : hierarchies) {
            if (hierarchy.getName() == null
                || hierarchy.getUniqueName().equals(dimension.getUniqueName()))
            {
                return hierarchy;
            }
        }
        return null;
    }

    static List<Member> addMembers(
        final SchemaReader schemaReader,
        final List<Member> members,
        final Hierarchy hierarchy)
    {
        // only add accessible levels
        for (Level level : schemaReader.getHierarchyLevels(hierarchy)) {
            addMembers(schemaReader, members, level);
        }
        return members;
    }

    static List<Member> addMembers(
        SchemaReader schemaReader,
        List<Member> members,
        Level level)
    {
        List<Member> levelMembers = schemaReader.getLevelMembers(level, true);
        members.addAll(levelMembers);
        return members;
    }

    /**
     * Removes every member from a list which is calculated.
     * The list must not be null, and must consist only of members.
     *
     * @param memberList Member list
     * @return List of non-calculated members
     */
    static List<Member> removeCalculatedMembers(List<Member> memberList)
    {
        List<Member> clone = new ArrayList<Member>();
        for (Member member : memberList) {
            if (member.isCalculated()) {
                continue;
            }
            clone.add(member);
        }
        return clone;
    }

    /**
     * Removes every tuple from a list which is calculated.
     * The list must not be null, and must consist only of members.
     *
     * @param memberList Member list
     * @return List of non-calculated members
     */
    static TupleList removeCalculatedMembers(TupleList memberList)
    {
        if (memberList.getArity() == 1) {
            return new UnaryTupleList(
                removeCalculatedMembers(
                    memberList.slice(0)));
        } else {
            final TupleList clone = memberList.cloneList(memberList.size());
            outer:
            for (List<Member> members : memberList) {
                for (Member member : members) {
                    if (member.isCalculated()) {
                        continue outer;
                    }
                }
                clone.add(members);
            }
            return clone;
        }
    }

    /**
     * Returns whether {@code m0} is an ancestor of {@code m1}.
     *
     * @param strict if true, a member is not an ancestor of itself
     */
    static boolean isAncestorOf(Member m0, Member m1, boolean strict) {
        if (strict) {
            if (m1 == null) {
                return false;
            }
            m1 = m1.getParentMember();
        }
        while (m1 != null) {
            if (m1.equals(m0)) {
                return true;
            }
            m1 = m1.getParentMember();
        }
        return false;
    }

    /**
     * For each member in a list, evaluates an expression and creates a map
     * from members to values.
     *
     * <p>If the list contains tuples, use
     * {@link #evaluateTuples(mondrian.olap.Evaluator, mondrian.calc.Calc, mondrian.calc.TupleList)}.
     *
     * @param evaluator Evaluation context
     * @param exp Expression to evaluate
     * @param memberIter Iterable over the collection of members
     * @param memberList List to be populated with members, or null
     * @param parentsToo If true, evaluate the expression for all ancestors
     *            of the members as well
     *
     * @pre exp != null
     * @pre exp.getType() instanceof ScalarType
     */
    static Map<Member, Object> evaluateMembers(
        Evaluator evaluator,
        Calc exp,
        Iterable<Member> memberIter,
        List<Member> memberList,
        boolean parentsToo)
    {
        final int savepoint = evaluator.savepoint();
        try {
            assert exp.getType() instanceof ScalarType;
            Map<Member, Object> mapMemberToValue =
                new HashMap<Member, Object>();
            for (Member member : memberIter) {
                if (memberList != null) {
                    memberList.add(member);
                }
                while (true) {
                    evaluator.setContext(member);
                    Object result = exp.evaluate(evaluator);
                    if (result == null) {
                        result = Util.nullValue;
                    }
                    mapMemberToValue.put(member, result);
                    if (!parentsToo) {
                        break;
                    }
                    member = member.getParentMember();
                    if (member == null) {
                        break;
                    }
                    if (mapMemberToValue.containsKey(member)) {
                        break;
                    }
                }
            }
            return mapMemberToValue;
        } finally {
            evaluator.restore(savepoint);
        }
    }

    /**
     * For each tuple in a list, evaluates an expression and creates a map
     * from tuples to values.
     *
     * @param evaluator Evaluation context
     * @param exp Expression to evaluate
     * @param tuples List of tuples
     *
     * @pre exp != null
     * @pre exp.getType() instanceof ScalarType
     */
    static Map<List<Member>, Object> evaluateTuples(
        Evaluator evaluator,
        Calc exp,
        TupleList tuples)
    {
        final int savepoint = evaluator.savepoint();

        try {
            assert exp.getType() instanceof ScalarType;
            final Map<List<Member>, Object> mapMemberToValue =
                    new HashMap<List<Member>, Object>();
            for (int i = 0, count = tuples.size(); i < count; i++) {
                List<Member> tuple = tuples.get(i);
                evaluator.setContext(tuple);
                Object result = exp.evaluate(evaluator);
                if (result == null) {
                    result = Util.nullValue;
                }
                mapMemberToValue.put(tuple, result);
            }
            return mapMemberToValue;
        } finally {
            evaluator.restore(savepoint);
        }
    }

    /**
     * Helper function to sort a list of members according to an expression.
     *
     * <p>NOTE: This function does not preserve the contents of the validator.
     *
     * <p>If you do not specify {@code memberList}, the method
     * will build its own member list as it iterates over {@code memberIter}.
     * It is acceptable if {@code memberList} and {@code memberIter} are the
     * same list object.
     *
     * <p>If you specify {@code memberList}, the list is sorted in place, and
     * memberList is returned.
     *
     * @param evaluator Evaluator
     * @param memberIter Iterable over members
     * @param memberList List of members
     * @param exp Expression to sort on
     * @param desc Whether to sort descending
     * @param brk Whether to break
     * @return sorted list (never null)
     */
    static List<Member> sortMembers(
        Evaluator evaluator,
        Iterable<Member> memberIter,
        List<Member> memberList,
        Calc exp,
        boolean desc,
        boolean brk)
    {
        if ((memberList != null) && (memberList.size() <= 1)) {
            return memberList;
        }

        evaluator.getTiming().markStart(SORT_EVAL_TIMING_NAME);
        boolean timingEval = true;
        boolean timingSort = false;
        try {
            // REVIEW mberkowitz 1/09: test whether precomputing
            // values saves time.
            Map<Member, Object> mapMemberToValue;
            final boolean parentsToo = !brk;
            if (memberList == null) {
                memberList = new ArrayList<Member>();
                mapMemberToValue = evaluateMembers(
                    evaluator, exp, memberIter, memberList, parentsToo);
            } else {
                mapMemberToValue = evaluateMembers(
                    evaluator, exp, memberIter, null, parentsToo);
            }

            MemberComparator comp;
            if (brk) {
                comp = new BreakMemberComparator(evaluator, exp, desc);
            } else {
                comp = new HierarchicalMemberComparator(evaluator, exp, desc);
            }
            comp.preloadValues(mapMemberToValue);
            evaluator.getTiming().markEnd(SORT_EVAL_TIMING_NAME);
            timingEval = false;
            evaluator.getTiming().markStart(SORT_TIMING_NAME);
            timingSort = true;
            Collections.sort(memberList, comp.wrap());
            return memberList;
        } finally {
            if (timingEval) {
                evaluator.getTiming().markEnd(SORT_EVAL_TIMING_NAME);
            } else if (timingSort) {
                evaluator.getTiming().markEnd(SORT_TIMING_NAME);
            }
        }
    }

    /**
     * Sorts a list of members according to a list of SortKeySpecs.
     * An in-place, Stable sort.
     * Helper function for MDX OrderSet function.
     *
     * <p>NOTE: Does not preserve the contents of the validator.
     */
    static List<Member> sortMembers(
        Evaluator evaluator,
        Iterable<Member> memberIter,
        List<Member> memberList,
        List<SortKeySpec> keySpecList)
    {
        if ((memberList != null) && (memberList.size() <= 1)) {
            return memberList;
        }
        if (memberList == null) {
            memberList = new ArrayList<Member>();
            for (Member member : memberIter) {
                memberList.add(member);
            }
            if (memberList.size() <= 1) {
                return memberList;
            }
        }

        ComparatorChain chain = new ComparatorChain();
        for (SortKeySpec key : keySpecList) {
            boolean brk = key.direction.brk;
            MemberComparator comp;
            if (brk) {
                comp = new BreakMemberComparator(
                    evaluator, key.key, key.direction.descending);
            } else {
                comp = new HierarchicalMemberComparator(
                    evaluator, key.key, key.direction.descending);
            }
            comp.preloadValues(memberList);
            chain.addComparator(comp.wrap(), false);
        }

        Collections.sort(memberList, chain);
        return memberList;
    }

    /**
     * Sorts a list of Tuples by the value of an applied expression. Stable
     * sort.
     *
     * <p>Helper function for MDX functions TopCount, TopSum, TopPercent,
     * BottomCount, BottomSum, BottomPercent, but not the MDX function Order.
     *
     * <p>NOTE: This function does not preserve the contents of the validator.
     *
     * <p>If you specify {@code tupleList}, the list is sorted in place, and
     * tupleList is returned.
     *
     * @param evaluator Evaluator
     * @param tupleIterable Iterator over tuples
     * @param tupleList List of tuples, if known, otherwise null
     * @param exp Expression to sort on
     * @param desc Whether to sort descending
     * @param brk Whether to break
     * @param arity Number of members in each tuple
     * @return sorted list (never null)
     */
    public static TupleList sortTuples(
        Evaluator evaluator,
        TupleIterable tupleIterable,
        TupleList tupleList,
        Calc exp,
        boolean desc,
        boolean brk,
        int arity)
    {
        // NOTE: This method does not implement the iterable/list concept
        // as fully as sortMembers. This is because sortMembers evaluates all
        // sort expressions up front. There, it is efficient to unravel the
        // iterator and evaluate the sort expressions at the same time.
        List<List<Member>> tupleArrayList;
        if (tupleList == null) {
            tupleArrayList = new ArrayList<List<Member>>();
            final TupleCursor cursor = tupleIterable.tupleCursor();
            while (cursor.forward()) {
                tupleArrayList.add(cursor.current());
            }
            if (tupleArrayList.size() <= 1) {
                return new DelegatingTupleList(
                    tupleIterable.getArity(),
                    tupleArrayList);
            }
        } else {
            if (tupleList.size() <= 1) {
                return tupleList;
            }
            tupleArrayList = tupleList;
        }

        @SuppressWarnings({"unchecked"})
        List<Member>[] tuples =
            tupleArrayList.toArray(new List[tupleArrayList.size()]);
        final DelegatingTupleList result =
            new DelegatingTupleList(
                tupleIterable.getArity(),
                Arrays.asList(tuples));

        Comparator<List<Member>> comparator;
        if (brk) {
            comparator =
                new BreakTupleComparator(evaluator, exp, arity);
            if (desc) {
                comparator = Collections.reverseOrder(comparator);
            }
        } else {
            comparator =
                new HierarchicalTupleComparator(evaluator, exp, arity, desc);
        }

        Arrays.sort(tuples, comparator);

        if (LOGGER.isDebugEnabled()) {
            StringBuilder sb =
                new StringBuilder("FunUtil.sortTuples returned:");
            for (List<Member> tuple : tuples) {
                sb.append("\n");
                sb.append(tuple.toString());
            }
            LOGGER.debug(sb.toString());
        }

        return result;
    }

    /**
     * Partially sorts a list of Members by the value of an applied expression.
     *
     * <p>Avoids sorting the whole list, finds only the <i>n</i>top (or bottom)
     * valued Members, and returns them as a new List. Helper function for MDX
     * functions TopCount and BottomCount.</p>
     *
     * <p>NOTE: Does not preserve the contents of the validator.</p>
     *
     * @param list a list of members
     * @param exp a Calc applied to each member to find its sort-key
     * @param evaluator Evaluator
     * @param limit maximum count of members to return.
     * @param desc true to sort descending (and find TopCount), false to sort
     *   ascending (and find BottomCount).
     * @return the top or bottom members, as a new list.
     */
    public static  List<Member> partiallySortMembers(
        Evaluator evaluator,
        List<Member> list,
        Calc exp,
        int limit,
        boolean desc)
    {
        assert list.size() > 0;
        assert limit <= list.size();
        evaluator.getTiming().markStart(SORT_EVAL_TIMING_NAME);
        boolean timingEval = true;
        boolean timingSort = false;
        try {
            MemberComparator comp =
                new BreakMemberComparator(evaluator, exp, desc);
            Map<Member, Object> valueMap =
                evaluateMembers(evaluator, exp, list, null, false);
            evaluator.getTiming().markEnd(SORT_EVAL_TIMING_NAME);
            timingEval = false;
            evaluator.getTiming().markStart(SORT_TIMING_NAME);
            timingSort = true;
            comp.preloadValues(valueMap);
            return stablePartialSort(list, comp.wrap(), limit);
        } finally {
            if (timingEval) {
                evaluator.getTiming().markEnd(SORT_EVAL_TIMING_NAME);
            } else if (timingSort) {
                evaluator.getTiming().markEnd(SORT_TIMING_NAME);
            }
        }
    }

    /**
     * Helper function to sort a list of tuples according to a list
     * of expressions and a list of sorting flags.
     *
     * <p>NOTE: This function does not preserve the contents of the validator.
     */
    static TupleList sortTuples(
        Evaluator evaluator,
        TupleIterable tupleIter,
        TupleList tupleList,
        List<SortKeySpec> keySpecList,
        int arity)
    {
        if (tupleList == null) {
            tupleList = TupleCollections.createList(arity);
            TupleCursor cursor = tupleIter.tupleCursor();
            while (cursor.forward()) {
                tupleList.addCurrent(cursor);
            }
        }
        if (tupleList.size() <= 1) {
            return tupleList;
        }

        ComparatorChain chain = new ComparatorChain();
        for (SortKeySpec key : keySpecList) {
            boolean brk = key.direction.brk;
            boolean orderByKey =
                key.key.isWrapperFor(MemberOrderKeyFunDef.CalcImpl.class);
            if (brk) {
                TupleExpMemoComparator comp =
                    new BreakTupleComparator(evaluator, key.key, arity);
                comp.preloadValues(tupleList);
                chain.addComparator(comp, key.direction.descending);
            } else if (orderByKey) {
                TupleExpMemoComparator comp =
                    new HierarchicalTupleKeyComparator(
                        evaluator, key.key, arity);
                comp.preloadValues(tupleList);
                chain.addComparator(comp, key.direction.descending);
            } else {
                TupleExpComparator comp =
                    new HierarchicalTupleComparator(
                        evaluator, key.key, arity, key.direction.descending);
                chain.addComparator(comp, false);
            }
        }

        Collections.sort(tupleList, chain);

        if (LOGGER.isDebugEnabled()) {
            StringBuilder sb =
                new StringBuilder("FunUtil.sortTuples returned:");
            for (List<Member> tuple : tupleList) {
                sb.append("\n");
                sb.append(tuple.toString());
            }
            LOGGER.debug(sb.toString());
        }

        return tupleList;
    }

    /**
     * Partially sorts a list of Tuples by the value of an applied expression.
     *
     * <p>Avoids sorting the whole list, finds only the <i>n</i> top (or bottom)
     * valued Tuples, and returns them as a new List. Helper function for MDX
     * functions TopCount and BottomCount.
     *
     * <p>NOTE: Does not preserve the contents of the validator. The returned
     * list is immutable.
     *
     * @param evaluator Evaluator
     * @param list a list of tuples
     * @param exp a Calc applied to each tuple to find its sort-key
     * @param limit maximum count of tuples to return.
     * @param desc true to sort descending (and find TopCount),
     *  false to sort ascending (and find BottomCount).
     * @return the top or bottom tuples, as a new list.
     */
    public static List<List<Member>> partiallySortTuples(
        Evaluator evaluator,
        TupleList list,
        Calc exp,
        int limit,
        boolean desc)
    {
        assert list.size() > 0;
        assert limit <= list.size();
        Comparator<List<Member>> comp =
            new BreakTupleComparator(evaluator, exp, list.getArity());
        if (desc) {
            comp = Collections.reverseOrder(comp);
        }
        return stablePartialSort(list, comp, limit);
    }

    /**
     * Sorts a list of members into hierarchical order. The members must belong
     * to the same dimension.
     *
     * @param memberList List of members
     * @param post Whether to sort in post order; if false, sorts in pre order
     *
     * @see #hierarchizeTupleList(mondrian.calc.TupleList, boolean)
     */
    public static void hierarchizeMemberList(
        List<Member> memberList,
        boolean post)
    {
        if (memberList.size() <= 1) {
            return;
        }
        if (memberList.get(0).getDimension().isHighCardinality()) {
            return;
        }
        Comparator<Member> comparator = new HierarchizeComparator(post);
        Collections.sort(memberList, comparator);
    }

    /**
     * Sorts a list of tuples into hierarchical order.
     *
     * @param tupleList List of tuples
     * @param post Whether to sort in post order; if false, sorts in pre order
     *
     * @see #hierarchizeMemberList(java.util.List, boolean)
     */
    public static TupleList hierarchizeTupleList(
        TupleList tupleList,
        boolean post)
    {
        if (tupleList.isEmpty()) {
            TupleCollections.emptyList(tupleList.getArity());
        }
        final TupleList fixedList = tupleList.fix();
        if (tupleList.getArity() == 1) {
            hierarchizeMemberList(fixedList.slice(0), post);
            return fixedList;
        }
        Comparator<List<Member>> comparator =
            new HierarchizeTupleComparator(fixedList.getArity(), post);

        Collections.sort(fixedList, comparator);

        if (LOGGER.isDebugEnabled()) {
            StringBuilder sb =
                new StringBuilder("FunUtil.hierarchizeTupleList returned:");
            for (List<Member> tuple : fixedList) {
                sb.append("\n");
                sb.append(tuple.toString());
            }
        }

        return fixedList;
    }

    /**
     * Compares double-precision values according to MDX semantics.
     *
     * <p>MDX requires a total order:
     * <blockquote>
     *    -inf &lt; NULL &lt; ... &lt; -1 &lt; ... &lt; 0 &lt; ... &lt; NaN &lt;
     * +inf
     * </blockquote>
     * but this is different than Java semantics, specifically with regard
     * to {@link Double#NaN}.
     */
    public static int compareValues(double d1, double d2) {
        if (Double.isNaN(d1)) {
            if (d2 == Double.POSITIVE_INFINITY) {
                return -1;
            } else if (Double.isNaN(d2)) {
                return 0;
            } else {
                return 1;
            }
        } else if (Double.isNaN(d2)) {
            if (d1 == Double.POSITIVE_INFINITY) {
                return 1;
            } else {
                return -1;
            }
        } else if (d1 == d2) {
            return 0;
        } else if (d1 == FunUtil.DoubleNull) {
            if (d2 == Double.NEGATIVE_INFINITY) {
                return 1;
            } else {
                return -1;
            }
        } else if (d2 == FunUtil.DoubleNull) {
            if (d1 == Double.NEGATIVE_INFINITY) {
                return -1;
            } else {
                return 1;
            }
        } else if (d1 < d2) {
            return -1;
        } else {
            return 1;
        }
    }

    /**
     * Compares two cell values.
     *
     * <p>Nulls compare last, exceptions (including the
     * object which indicates the the cell is not in the cache yet) next,
     * then numbers and strings are compared by value.
     *
     * @param value0 First cell value
     * @param value1 Second cell value
     * @return -1, 0, or 1, depending upon whether first cell value is less
     *   than, equal to, or greater than the second
     */
    public static int compareValues(Object value0, Object value1) {
        if (value0 == value1) {
            return 0;
        }
        // null is less than anything else
        if (value0 == null) {
            return -1;
        }
        if (value1 == null) {
            return 1;
        }

        if (value0 == RolapUtil.valueNotReadyException) {
            // the left value is not in cache; continue as best as we can
             return -1;
        } else if (value1 == RolapUtil.valueNotReadyException) {
            // the right value is not in cache; continue as best as we can
            return 1;
        } else if (value0 == Util.nullValue) {
            return -1; // null == -infinity
        } else if (value1 == Util.nullValue) {
            return 1; // null == -infinity
        } else if (value0 instanceof String) {
            return ((String) value0).compareToIgnoreCase((String) value1);
        } else if (value0 instanceof Number) {
            return FunUtil.compareValues(
                ((Number) value0).doubleValue(),
                ((Number) value1).doubleValue());
        } else if (value0 instanceof Date) {
            return ((Date) value0).compareTo((Date) value1);
        } else if (value0 instanceof OrderKey) {
            return ((OrderKey) value0).compareTo(value1);
        } else {
            throw Util.newInternal("cannot compare " + value0);
        }
    }

    /**
     * Turns the mapped values into relative values (percentages) for easy
     * use by the general topOrBottom function. This might also be a useful
     * function in itself.
     */
    static void toPercent(
        TupleList members,
        Map<List<Member>, Object> mapMemberToValue)
    {
        double total = 0;
        int memberCount = members.size();
        for (int i = 0; i < memberCount; i++) {
            final List<Member> key = members.get(i);
            final Object o = mapMemberToValue.get(key);
            if (o instanceof Number) {
                total += ((Number) o).doubleValue();
            }
        }
        for (int i = 0; i < memberCount; i++) {
            final List<Member> key = members.get(i);
            final Object o = mapMemberToValue.get(key);
            if (o instanceof Number) {
                double d = ((Number) o).doubleValue();
                mapMemberToValue.put(
                    key,
                    d / total * (double) 100);
            }
        }
    }


    /**
     * Decodes the syntactic type of an operator.
     *
     * @param flags A encoded string which represents an operator signature,
     *   as used by the {@code flags} parameter used to construct a
     *   {@link FunDefBase}.
     *
     * @return A {@link Syntax}
     */
    public static Syntax decodeSyntacticType(String flags) {
        char c = flags.charAt(0);
        switch (c) {
        case 'p':
            return Syntax.Property;
        case 'f':
            return Syntax.Function;
        case 'm':
            return Syntax.Method;
        case 'i':
            return Syntax.Infix;
        case 'P':
            return Syntax.Prefix;
        case 'Q':
            return Syntax.Postfix;
        case 'I':
            return Syntax.Internal;
        default:
            throw newInternal(
                "unknown syntax code '" + c + "' in string '" + flags + "'");
        }
    }

    /**
     * Decodes the signature of a function into a category code which describes
     * the return type of the operator.
     *
     * <p>For example, <code>decodeReturnType("fnx")</code> returns
     * <code>{@link Category#Numeric}</code>, indicating this function has a
     * numeric return value.
     *
     * @param flags The signature of an operator,
     *   as used by the {@code flags} parameter used to construct a
     *   {@link FunDefBase}.
     *
     * @return An array {@link Category} codes.
     */
    public static int decodeReturnCategory(String flags) {
        final int returnCategory = decodeCategory(flags, 1);
        if ((returnCategory & Category.Mask) != returnCategory) {
            throw newInternal("bad return code flag in flags '" + flags + "'");
        }
        return returnCategory;
    }

    /**
     * Decodes the {@code offset}th character of an encoded method
     * signature into a type category.
     *
     * <p>The codes are:
     * <table border="1">
     *
     * <tr><td>a</td><td>{@link Category#Array}</td></tr>
     *
     * <tr><td>d</td><td>{@link Category#Dimension}</td></tr>
     *
     * <tr><td>h</td><td>{@link Category#Hierarchy}</td></tr>
     *
     * <tr><td>l</td><td>{@link Category#Level}</td></tr>
     *
     * <tr><td>b</td><td>{@link Category#Logical}</td></tr>
     *
     * <tr><td>m</td><td>{@link Category#Member}</td></tr>
     *
     * <tr><td>N</td><td>Constant {@link Category#Numeric}</td></tr>
     *
     * <tr><td>n</td><td>{@link Category#Numeric}</td></tr>
     *
     * <tr><td>x</td><td>{@link Category#Set}</td></tr>
     *
     * <tr><td>#</td><td>Constant {@link Category#String}</td></tr>
     *
     * <tr><td>S</td><td>{@link Category#String}</td></tr>
     *
     * <tr><td>t</td><td>{@link Category#Tuple}</td></tr>
     *
     * <tr><td>v</td><td>{@link Category#Value}</td></tr>
     *
     * <tr><td>y</td><td>{@link Category#Symbol}</td></tr>
     *
     * </table>
     *
     * @param flags Encoded signature string
     * @param offset 0-based offset of character within string
     * @return A {@link Category}
     */
    public static int decodeCategory(String flags, int offset) {
        char c = flags.charAt(offset);
        switch (c) {
        case 'a':
            return Category.Array;
        case 'd':
            return Category.Dimension;
        case 'h':
            return Category.Hierarchy;
        case 'l':
            return Category.Level;
        case 'b':
            return Category.Logical;
        case 'm':
            return Category.Member;
        case 'N':
            return Category.Numeric | Category.Constant;
        case 'n':
            return Category.Numeric;
        case 'I':
            return Category.Numeric | Category.Integer | Category.Constant;
        case 'i':
            return Category.Numeric | Category.Integer;
        case 'x':
            return Category.Set;
        case '#':
            return Category.String | Category.Constant;
        case 'S':
            return Category.String;
        case 't':
            return Category.Tuple;
        case 'v':
            return Category.Value;
        case 'y':
            return Category.Symbol;
        case 'U':
            return Category.Null;
        case 'e':
            return Category.Empty;
        case 'D':
            return Category.DateTime;
        default:
            throw newInternal(
                "unknown type code '" + c + "' in string '" + flags + "'");
        }
    }

    /**
     * Decodes a string of parameter types into an array of type codes.
     *
     * <p>Each character is decoded using {@link #decodeCategory(String, int)}.
     * For example, <code>decodeParameterTypes("nx")</code> returns
     * <code>{{@link Category#Numeric}, {@link Category#Set}}</code>.
     *
     * @param flags The signature of an operator,
     *   as used by the {@code flags} parameter used to construct a
     *   {@link FunDefBase}.
     *
     * @return An array {@link Category} codes.
     */
    public static int[] decodeParameterCategories(String flags) {
        int[] parameterCategories = new int[flags.length() - 2];
        for (int i = 0; i < parameterCategories.length; i++) {
            parameterCategories[i] = decodeCategory(flags, i + 2);
        }
        return parameterCategories;
    }

    /**
     * Converts a double (primitive) value to a Double. {@link #DoubleNull}
     * becomes null.
     */
    public static Double box(double d) {
        return d == DoubleNull
            ? null
            : d;
    }

    /**
     * Converts an int (primitive) value to an Integer. {@link #IntegerNull}
     * becomes null.
     */
    public static Integer box(int n) {
        return n == IntegerNull
            ? null
            : n;
    }

    static double percentile(
        Evaluator evaluator,
        TupleList members,
        Calc exp,
        double p)
    {
        SetWrapper sw = evaluateSet(evaluator, members, exp);
        if (sw.errorCount > 0) {
            return Double.NaN;
        } else if (sw.v.size() == 0) {
            return FunUtil.DoubleNull;
        }
        double[] asArray = new double[sw.v.size()];
        for (int i = 0; i < asArray.length; i++) {
            asArray[i] = (Double) sw.v.get(i);
        }
        Arrays.sort(asArray);

        // The median is defined as the value that has exactly the same
        // number of entries before it in the sorted list as after.
        // So, if the number of entries in the list is odd, the
        // median is the entry at (length-1)/2 (using zero-based indexes).
        // If the number of entries is even, the median is defined as the
        // arithmetic mean of the two numbers in the middle of the list, or
        // (entries[length/2 - 1] + entries[length/2]) / 2.
        int length = asArray.length;
        if (p <= 0.0) {
            return asArray[0];
        } else if (p >= 1.0) {
            return asArray[length - 1];
        } else if (length == 1) {
            return asArray[0];
        } else if (p == 0.5) {
            // Special case for median.
            if ((length & 1) == 1) {
                // The length is odd. Note that length/2 is an integer
                // expression, and it's positive so we save ourselves a divide.
                return asArray[length >> 1];
            } else {
                return (asArray[(length >> 1) - 1] + asArray[length >> 1])
                    / 2.0;
            }
        } else {
            final double jD = Math.floor(length * p);
            int j = jD > 0 ? (int) jD - 1 : (int) jD;
            double alpha = (p * length) - jD;
            assert alpha >= 0;
            assert alpha <= 1;
            return asArray[j] + ((asArray[j + 1] - asArray[j]) * alpha);
        }
    }

    /**
     * Returns the member which lies upon a particular quartile according to a
     * given expression.
     *
     * @param evaluator Evaluator
     * @param members List of members
     * @param exp Expression to rank members
     * @param range Quartile (1, 2 or 3)
     *
     * @pre range >= 1 && range <= 3
     */
    protected static double quartile(
        Evaluator evaluator,
        TupleList members,
        Calc exp,
        int range)
    {
        assert range >= 1 && range <= 3;

        SetWrapper sw = evaluateSet(evaluator, members, exp);
        if (sw.errorCount > 0) {
            return Double.NaN;
        } else if (sw.v.size() == 0) {
            return DoubleNull;
        }

        double[] asArray = new double[sw.v.size()];
        for (int i = 0; i < asArray.length; i++) {
            asArray[i] = ((Double) sw.v.get(i)).doubleValue();
        }

        Arrays.sort(asArray);
        // get a quartile, median is a second q
        double dm = 0.25 * asArray.length * range;
        int median = (int) Math.floor(dm);
        return dm == median && median < asArray.length - 1
            ? (asArray[median] + asArray[median + 1]) / 2
            : asArray[median];
    }

    public static Object min(
        Evaluator evaluator,
        TupleList members,
        Calc calc)
    {
        SetWrapper sw = evaluateSet(evaluator, members, calc);
        if (sw.errorCount > 0) {
            return Double.NaN;
        } else {
            final int size = sw.v.size();
            if (size == 0) {
                return Util.nullValue;
            } else {
                Double min = ((Number) sw.v.get(0)).doubleValue();
                for (int i = 1; i < size; i++) {
                    Double iValue = ((Number) sw.v.get(i)).doubleValue();
                    if (iValue < min) {
                        min = iValue;
                    }
                }
                return min;
            }
        }
    }

    public static Object max(
        Evaluator evaluator,
        TupleList members,
        Calc exp)
    {
        SetWrapper sw = evaluateSet(evaluator, members, exp);
        if (sw.errorCount > 0) {
            return Double.NaN;
        } else {
            final int size = sw.v.size();
            if (size == 0) {
                return Util.nullValue;
            } else {
                Double max = ((Number) sw.v.get(0)).doubleValue();
                for (int i = 1; i < size; i++) {
                    Double iValue = ((Number) sw.v.get(i)).doubleValue();
                    if (iValue > max) {
                        max = iValue;
                    }
                }
                return max;
            }
        }
    }

    static Object var(
        Evaluator evaluator,
        TupleList members,
        Calc exp,
        boolean biased)
    {
        SetWrapper sw = evaluateSet(evaluator, members, exp);
        return _var(sw, biased);
    }

    private static Object _var(SetWrapper sw, boolean biased) {
        if (sw.errorCount > 0) {
            return new Double(Double.NaN);
        } else if (sw.v.size() == 0) {
            return Util.nullValue;
        } else {
            double stdev = 0.0;
            double avg = _avg(sw);
            for (int i = 0; i < sw.v.size(); i++) {
                stdev +=
                    Math.pow((((Number) sw.v.get(i)).doubleValue() - avg), 2);
            }
            int n = sw.v.size();
            if (!biased) {
                n--;
            }
            return new Double(stdev / (double) n);
        }
    }

    static double correlation(
        Evaluator evaluator,
        TupleList memberList,
        Calc exp1,
        Calc exp2)
    {
        SetWrapper sw1 = evaluateSet(evaluator, memberList, exp1);
        SetWrapper sw2 = evaluateSet(evaluator, memberList, exp2);
        Object covar = _covariance(sw1, sw2, false);
        Object var1 = _var(sw1, false); // this should be false, yes?
        Object var2 = _var(sw2, false);

        return ((Number) covar).doubleValue()
            / Math.sqrt(
                ((Number) var1).doubleValue()
                * ((Number) var2).doubleValue());
    }

    static Object covariance(
        Evaluator evaluator,
        TupleList members,
        Calc exp1,
        Calc exp2,
        boolean biased)
    {
        final int savepoint = evaluator.savepoint();
        SetWrapper sw1;
        try {
            sw1 = evaluateSet(evaluator, members, exp1);
        } finally {
            evaluator.restore(savepoint);
        }
        SetWrapper sw2;
        try {
            sw2 = evaluateSet(evaluator, members, exp2);
        } finally {
            evaluator.restore(savepoint);
        }
        // todo: because evaluateSet does not add nulls to the SetWrapper, this
        // solution may lead to mismatched lists and is therefore not robust
        return _covariance(sw1, sw2, biased);
    }


    private static Object _covariance(
        SetWrapper sw1,
        SetWrapper sw2,
        boolean biased)
    {
        if (sw1.v.size() != sw2.v.size()) {
            return Util.nullValue;
        }
        double avg1 = _avg(sw1);
        double avg2 = _avg(sw2);
        double covar = 0.0;
        for (int i = 0; i < sw1.v.size(); i++) {
            // all of this casting seems inefficient - can we make SetWrapper
            // contain an array of double instead?
            double diff1 = (((Number) sw1.v.get(i)).doubleValue() - avg1);
            double diff2 = (((Number) sw2.v.get(i)).doubleValue() - avg2);
            covar += (diff1 * diff2);
        }
        int n = sw1.v.size();
        if (!biased) {
            n--;
        }
        return new Double(covar / (double) n);
    }

    static Object stdev(
        Evaluator evaluator,
        TupleList members,
        Calc exp,
        boolean biased)
    {
        Object o = var(evaluator, members, exp, biased);
        return (o instanceof Double)
            ? new Double(Math.sqrt(((Number) o).doubleValue()))
            : o;
    }

    public static Object avg(
        Evaluator evaluator,
        TupleList members,
        Calc calc)
    {
        SetWrapper sw = evaluateSet(evaluator, members, calc);
        return (sw.errorCount > 0)
            ? new Double(Double.NaN)
            : (sw.v.size() == 0)
            ? Util.nullValue
            : new Double(_avg(sw));
    }

    // TODO: parameterize inclusion of nulls; also, maybe make _avg a method of
    // setwrapper, so we can cache the result (i.e. for correl)
    private static double _avg(SetWrapper sw) {
        double sum = 0.0;
        for (int i = 0; i < sw.v.size(); i++) {
            sum += ((Number) sw.v.get(i)).doubleValue();
        }
        // TODO: should look at context and optionally include nulls
        return sum / (double) sw.v.size();
    }

    public static Object sum(
        Evaluator evaluator,
        TupleList members,
        Calc exp)
    {
        double d = sumDouble(evaluator, members, exp);
        return d == DoubleNull ? Util.nullValue : new Double(d);
    }

    public static double sumDouble(
        Evaluator evaluator,
        TupleList members,
        Calc exp)
    {
        SetWrapper sw = evaluateSet(evaluator, members, exp);
        if (sw.errorCount > 0) {
            return Double.NaN;
        } else if (sw.v.size() == 0) {
            return DoubleNull;
        } else {
            double sum = 0.0;
            for (int i = 0; i < sw.v.size(); i++) {
                sum += ((Number) sw.v.get(i)).doubleValue();
            }
            return sum;
        }
    }

    public static double sumDouble(
        Evaluator evaluator,
        TupleIterable iterable,
        Calc exp)
    {
        SetWrapper sw = evaluateSet(evaluator, iterable, exp);
        if (sw.errorCount > 0) {
            return Double.NaN;
        } else if (sw.v.size() == 0) {
            return DoubleNull;
        } else {
            double sum = 0.0;
            for (int i = 0; i < sw.v.size(); i++) {
                sum += ((Number) sw.v.get(i)).doubleValue();
            }
            return sum;
        }
    }

    public static int count(
        Evaluator evaluator,
        TupleIterable iterable,
        boolean includeEmpty)
    {
        if (iterable == null) {
            return 0;
        }
        if (includeEmpty) {
            if (iterable instanceof TupleList) {
                return ((TupleList) iterable).size();
            } else {
                int retval = 0;
                TupleCursor cursor = iterable.tupleCursor();
                while (cursor.forward()) {
                    retval++;
                }
                return retval;
            }
        } else {
            int retval = 0;
            TupleCursor cursor = iterable.tupleCursor();
            while (cursor.forward()) {
                cursor.setContext(evaluator);
                if (!evaluator.currentIsEmpty()) {
                    retval++;
                }
            }
            return retval;
        }
    }

    /**
     * Evaluates {@code exp} (if defined) over {@code members} to
     * generate a {@link List} of {@link SetWrapper} objects, which contains
     * a {@link Double} value and meta information, unlike
     * {@link #evaluateMembers}, which only produces values.
     *
     * @pre exp != null
     */
    static SetWrapper evaluateSet(
        Evaluator evaluator,
        TupleIterable members,
        Calc calc)
    {
        assert members != null;
        assert calc != null;
        assert calc.getType() instanceof ScalarType;

        // todo: treat constant exps as evaluateMembers() does
        SetWrapper retval = new SetWrapper();
        final TupleCursor cursor = members.tupleCursor();
        while (cursor.forward()) {
            cursor.setContext(evaluator);
            Object o = calc.evaluate(evaluator);
            if (o == null || o == Util.nullValue) {
                retval.nullCount++;
            } else if (o == RolapUtil.valueNotReadyException) {
                // Carry on summing, so that if we are running in a
                // BatchingCellReader, we find out all the dependent cells we
                // need
                retval.errorCount++;
            } else if (o instanceof Number) {
                retval.v.add(((Number) o).doubleValue());
            } else {
                retval.v.add(o);
            }
        }
        return retval;
    }

    /**
     * Evaluates one or more expressions against the member list returning
     * a SetWrapper array. Where this differs very significantly from the
     * above evaluateSet methods is how it count null values and Throwables;
     * this method adds nulls to the SetWrapper Vector rather than not adding
     * anything - as the above method does. The impact of this is that if, for
     * example, one was creating a list of x,y values then each list will have
     * the same number of values (though some might be null) - this allows
     * higher level code to determine how to handle the lack of data rather than
     * having a non-equal number (if one is plotting x,y values it helps to
     * have the same number and know where a potential gap is the data is.
     */
    static SetWrapper[] evaluateSet(
        Evaluator evaluator,
        TupleList list,
        DoubleCalc[] calcs)
    {
        Util.assertPrecondition(calcs != null, "calcs != null");

        // todo: treat constant exps as evaluateMembers() does
        SetWrapper[] retvals = new SetWrapper[calcs.length];
        for (int i = 0; i < calcs.length; i++) {
            retvals[i] = new SetWrapper();
        }
        final TupleCursor cursor = list.tupleCursor();
        while (cursor.forward()) {
            cursor.setContext(evaluator);
            for (int i = 0; i < calcs.length; i++) {
                DoubleCalc calc = calcs[i];
                SetWrapper retval = retvals[i];
                double o = calc.evaluateDouble(evaluator);
                if (o == FunUtil.DoubleNull) {
                    retval.nullCount++;
                    retval.v.add(null);
                } else {
                    retval.v.add(o);
                }
                // TODO: If the expression yielded an error, carry on
                // summing, so that if we are running in a
                // BatchingCellReader, we find out all the dependent cells
                // we need
            }
        }
        return retvals;
    }

    static List<Member> periodsToDate(
        Evaluator evaluator,
        Level level,
        Member member)
    {
        if (member == null) {
            member = evaluator.getContext(level.getHierarchy());
        }
        Member m = member;
        while (m != null) {
            if (m.getLevel() == level) {
                break;
            }
            m = m.getParentMember();
        }
        // If m == null, then "level" was lower than member's level.
        // periodsToDate([Time].[Quarter], [Time].[1997] is valid,
        //  but will return an empty List
        List<Member> members = new ArrayList<Member>();
        if (m != null) {
            // e.g. m is [Time].[1997] and member is [Time].[1997].[Q1].[3]
            // we now have to make m to be the first member of the range,
            // so m becomes [Time].[1997].[Q1].[1]
            SchemaReader reader = evaluator.getSchemaReader();
            m = Util.getFirstDescendantOnLevel(reader, m, member.getLevel());
            reader.getMemberRange(level, m, member, members);
        }
        return members;
    }

    static List<Member> memberRange(
        Evaluator evaluator,
        Member startMember,
        Member endMember)
    {
        final Level level = startMember.getLevel();
        assertTrue(level == endMember.getLevel());
        List<Member> members = new ArrayList<Member>();
        evaluator.getSchemaReader().getMemberRange(
            level, startMember, endMember, members);

        if (members.isEmpty()) {
            // The result is empty, so maybe the members are reversed. This is
            // cheaper than comparing the members before we call getMemberRange.
            evaluator.getSchemaReader().getMemberRange(
                level, endMember, startMember, members);
        }
        return members;
    }

    /**
     * Returns the member under ancestorMember having the same relative position
     * under member's parent.
     * <p>For exmaple, cousin([Feb 2001], [Q3 2001]) is [August 2001].
     * @param schemaReader The reader to use
     * @param member The member for which we'll find the cousin.
     * @param ancestorMember The cousin's ancestor.
     *
     * @return The child of {@code ancestorMember} in the same position
     * under {@code ancestorMember} as {@code member} is under its
     * parent.
     */
    static Member cousin(
        SchemaReader schemaReader,
        Member member,
        Member ancestorMember)
    {
        if (ancestorMember.isNull()) {
            return ancestorMember;
        }
        if (member.getHierarchy() != ancestorMember.getHierarchy()) {
            throw MondrianResource.instance().CousinHierarchyMismatch.ex(
                member.getUniqueName(), ancestorMember.getUniqueName());
        }
        if (member.getLevel().getDepth()
            < ancestorMember.getLevel().getDepth())
        {
            return member.getHierarchy().getNullMember();
        }

        Member cousin = cousin2(schemaReader, member, ancestorMember);
        if (cousin == null) {
            cousin = member.getHierarchy().getNullMember();
        }

        return cousin;
    }

    private static Member cousin2(
        SchemaReader schemaReader,
        Member member1,
        Member member2)
    {
        if (member1.getLevel() == member2.getLevel()) {
            return member2;
        }
        Member uncle =
            cousin2(schemaReader, member1.getParentMember(), member2);
        if (uncle == null) {
            return null;
        }
        int ordinal = Util.getMemberOrdinalInParent(schemaReader, member1);
        List<Member> cousins = schemaReader.getMemberChildren(uncle);
        if (cousins.size() <= ordinal) {
            return null;
        }
        return cousins.get(ordinal);
    }

    /**
     * Returns the ancestor of {@code member} at the given level
     * or distance. It is assumed that any error checking required
     * has been done prior to calling this function.
     *
     * <p>This method takes into consideration the fact that there
     * may be intervening hidden members between {@code member}
     * and the ancestor. If {@code targetLevel} is not null, then
     * the method will only return a member if the level at
     * {@code distance} from the member is actually the
     * {@code targetLevel} specified.
     *
     * @param evaluator The evaluation context
     * @param member The member for which the ancestor is to be found
     * @param distance The distance up the chain the ancestor is to
     * be found.
     *
     * @param targetLevel The desired targetLevel of the ancestor. If
     * {@code null}, then the distance completely determines the desired
     * ancestor.
     *
     * @return The ancestor member, or {@code null} if no such
     * ancestor exists.
     */
    static Member ancestor(
        Evaluator evaluator,
        Member member,
        int distance,
        Level targetLevel)
    {
        if ((targetLevel != null)
            && (member.getHierarchy() != targetLevel.getHierarchy()))
        {
            throw MondrianResource.instance().MemberNotInLevelHierarchy.ex(
                member.getUniqueName(), targetLevel.getUniqueName());
        }

        if (distance == 0) {
            // Shortcut if there's nowhere to go.
            return member;
        } else if (distance < 0) {
            // Can't go backwards.
            return member.getHierarchy().getNullMember();
        }

        final List<Member> ancestors = new ArrayList<Member>();
        final SchemaReader schemaReader = evaluator.getSchemaReader();
        schemaReader.getMemberAncestors(member, ancestors);

        Member result = member.getHierarchy().getNullMember();

        searchLoop:
        for (int i = 0; i < ancestors.size(); i++) {
            final Member ancestorMember = ancestors.get(i);

            if (targetLevel != null) {
                if (ancestorMember.getLevel() == targetLevel) {
                    if (schemaReader.isVisible(ancestorMember)) {
                        result = ancestorMember;
                        break;
                    } else {
                        result = member.getHierarchy().getNullMember();
                        break;
                    }
                }
            } else {
                if (schemaReader.isVisible(ancestorMember)) {
                    distance--;

                    // Make sure that this ancestor is really on the right
                    // targetLevel. If a targetLevel was specified and at least
                    // one of the ancestors was hidden, this this algorithm goes
                    // too far up the ancestor list. It's not a problem, except
                    // that we need to check if it's happened and return the
                    // hierarchy's null member instead.
                    //
                    // For example, consider what happens with
                    // Ancestor([Store].[Israel].[Haifa], [Store].[Store
                    // State]).  The distance from [Haifa] to [Store State] is
                    // 1, but that lands us at the country targetLevel, which is
                    // clearly wrong.
                    if (distance == 0) {
                        result = ancestorMember;
                        break;
                    }
                }
            }
        }

        return result;
    }

    /**
     * Compares a pair of members according to their positions in a
     * prefix-order (or postfix-order, if {@code post} is true) walk
     * over a hierarchy.
     *
     * @param m1 First member
     * @param m2 Second member
     *
     * @param post Whether to sortMembers in postfix order. If true, a parent
     *   will sortMembers immediately after its last child. If false, a parent
     *   will sortMembers immediately before its first child.
     *
     * @return -1 if m1 collates before m2,
     *   0 if m1 equals m2,
     *   1 if m1 collates after m2
     */
    public static int compareHierarchically(
        Member m1,
        Member m2,
        boolean post)
    {
        // Strip away the LimitedRollupMember wrapper, if it exists. The
        // wrapper does not implement equals and comparisons correctly. This
        // is safe this method has no side-effects: it just returns an int.
        m1 = unwrapLimitedRollupMember(m1);
        m2 = unwrapLimitedRollupMember(m2);

        if (equals(m1, m2)) {
            return 0;
        }

        while (true) {
            int depth1 = m1.getDepth();
            int depth2 = m2.getDepth();
            if (depth1 < depth2) {
                m2 = m2.getParentMember();
                if (equals(m1, m2)) {
                    return post ? 1 : -1;
                }
            } else if (depth1 > depth2) {
                m1 = m1.getParentMember();
                if (equals(m1, m2)) {
                    return post ? -1 : 1;
                }
            } else {
                Member prev1 = m1;
                Member prev2 = m2;
                m1 = unwrapLimitedRollupMember(m1.getParentMember());
                m2 = unwrapLimitedRollupMember(m2.getParentMember());
                if (equals(m1, m2)) {
                    final int c = compareSiblingMembers(prev1, prev2);
                    // compareHierarchically needs to impose a total order;
                    // cannot return 0 for non-equal members
                    assert c != 0
                        : "Members " + prev1 + ", " + prev2
                        + " are not equal, but compare returned 0.";
                    return c;
                }
            }
        }
    }

    private static Member unwrapLimitedRollupMember(Member m) {
        if (m instanceof RolapHierarchy.LimitedRollupMember) {
            return ((RolapHierarchy.LimitedRollupMember) m).member;
        }
        return m;
    }
    /**
     * Compares two members which are known to have the same parent.
     *
     * First, compare by ordinal.
     * This is only valid now we know they're siblings, because
     * ordinals are only unique within a parent.
     * If the dimension does not use ordinals, both ordinals
     * will be -1.
     *
     * <p>If the ordinals do not differ, compare using regular member
     * comparison.
     *
     * @param m1 First member
     * @param m2 Second member
     * @return -1 if m1 collates less than m2,
     *   1 if m1 collates after m2,
     *   0 if m1 == m2.
     */
    public static int compareSiblingMembers(Member m1, Member m2) {
        // calculated members collate after non-calculated
        final boolean calculated1 = m1.isCalculatedInQuery();
        final boolean calculated2 = m2.isCalculatedInQuery();
        if (calculated1) {
            if (!calculated2) {
                return 1;
            }
        } else {
            if (calculated2) {
                return -1;
            }
        }
        final Comparable k1 = m1.getOrderKey();
        final Comparable k2 = m2.getOrderKey();
        if ((k1 != null) && (k2 != null)) {
            return k1.compareTo(k2);
        } else {
            final int ordinal1 = m1.getOrdinal();
            final int ordinal2 = m2.getOrdinal();
            return (ordinal1 == ordinal2)
                ? m1.compareTo(m2)
                : (ordinal1 < ordinal2)
                ? -1
                : 1;
        }
    }

    /**
     * Returns whether one of the members in a tuple is null.
     */
    public static boolean tupleContainsNullMember(Member[] tuple) {
        for (Member member : tuple) {
            if (member.isNull()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns whether one of the members in a tuple is null.
     */
    public static boolean tupleContainsNullMember(List<Member> tuple) {
        for (Member member : tuple) {
            if (member.isNull()) {
                return true;
            }
        }
        return false;
    }

    public static Member[] makeNullTuple(final TupleType tupleType) {
        final Type[] elementTypes = tupleType.elementTypes;
        Member[] members = new Member[elementTypes.length];
        for (int i = 0; i < elementTypes.length; i++) {
            MemberType type = (MemberType) elementTypes[i];
            members[i] = makeNullMember(type);
        }
        return members;
    }

    static Member makeNullMember(MemberType memberType) {
        Hierarchy hierarchy = memberType.getHierarchy();
        if (hierarchy == null) {
            return NullMember;
        }
        return hierarchy.getNullMember();
    }

    /**
     * Validates the arguments to a function and resolves the function.
     *
     * @param validator Validator used to validate function arguments and
     *           resolve the function
     * @param funDef Function definition, or null to deduce definition from
     *           name, syntax and argument types
     * @param args    Arguments to the function
     * @param newArgs Output parameter for the resolved arguments
     * @param name    Function name
     * @param syntax Syntax style used to invoke function
     * @return resolved function definition
     */
    public static FunDef resolveFunArgs(
        Validator validator,
        FunDef funDef,
        Exp[] args,
        Exp[] newArgs,
        String name,
        Syntax syntax)
    {
        for (int i = 0; i < args.length; i++) {
            newArgs[i] = validator.validate(args[i], false);
        }
        if (funDef == null || validator.alwaysResolveFunDef()) {
            funDef = validator.getDef(newArgs, name, syntax);
        }
        checkNativeCompatible(validator, funDef, newArgs);
        return funDef;
    }

    /**
     * Functions that dynamically return one or more members of the measures
     * dimension prevent us from using native evaluation.
     *
     * @param validator Validator used to validate function arguments and
     *           resolve the function
     * @param funDef Function definition, or null to deduce definition from
     *           name, syntax and argument types
     * @param args    Arguments to the function
     */
    private static void checkNativeCompatible(
        Validator validator,
        FunDef funDef,
        Exp[] args)
    {
        // If the first argument to a function is either:
        // 1) the measures dimension or
        // 2) a measures member where the function returns another member or
        //    a set,
        // then these are functions that dynamically return one or more
        // members of the measures dimension.  In that case, we cannot use
        // native cross joins because the functions need to be executed to
        // determine the resultant measures.
        //
        // As a result, we disallow functions like AllMembers applied on the
        // Measures dimension as well as functions like the range operator,
        // siblings, and lag, when the argument is a measure member.
        // However, we do allow functions like isEmpty, rank, and topPercent.
        //
        // Also, the Set and Parentheses functions are ok since they're
        // essentially just containers.
        Query query = validator.getQuery();
        if (!(funDef instanceof SetFunDef)
            && !(funDef instanceof ParenthesesFunDef)
            && query != null
            && query.nativeCrossJoinVirtualCube())
        {
            int[] paramCategories = funDef.getParameterCategories();
            if (paramCategories.length > 0) {
                final int cat0 = paramCategories[0];
                final Exp arg0 = args[0];
                switch (cat0) {
                case Category.Dimension:
                case Category.Hierarchy:
                    if (arg0 instanceof DimensionExpr
                        && ((DimensionExpr) arg0).getDimension().isMeasures()
                        && !(funDef instanceof HierarchyCurrentMemberFunDef))
                    {
                        query.setVirtualCubeNonNativeCrossJoin();
                    }
                    break;
                case Category.Member:
                    if (arg0 instanceof MemberExpr
                        && ((MemberExpr) arg0).getMember().isMeasure()
                        && isMemberOrSet(funDef.getReturnCategory()))
                    {
                        query.setVirtualCubeNonNativeCrossJoin();
                    }
                    break;
                }
            }
        }
    }

    private static boolean isMemberOrSet(int category) {
        return category == Category.Member || category == Category.Set;
    }

    static void appendTuple(StringBuilder buf, Member[] members) {
        buf.append("(");
        for (int j = 0; j < members.length; j++) {
            if (j > 0) {
                buf.append(", ");
            }
            Member member = members[j];
            buf.append(member.getUniqueName());
        }
        buf.append(")");
    }

    /**
     * Returns whether two tuples are equal.
     *
     * <p>The members are allowed to be in different positions. For example,
     * <code>([Gender].[M], [Store].[USA]) IS ([Store].[USA],
     * [Gender].[M])</code> returns {@code true}.
     */
    static boolean equalTuple(Member[] members0, Member[] members1) {
        final int count = members0.length;
        if (count != members1.length) {
            return false;
        }
        outer:
        for (int i = 0; i < count; i++) {
            // First check the member at the corresponding ordinal. It is more
            // likely to be there.
            final Member member0 = members0[i];
            if (member0.equals(members1[i])) {
                continue;
            }
            // Look for this member in other positions.
            // We can assume that the members in members0 are distinct (because
            // they belong to different dimensions), so this test is valid.
            for (int j = 0; j < count; j++) {
                if (i != j && member0.equals(members1[j])) {
                    continue outer;
                }
            }
            // This member of members0 does not occur in any position of
            // members1. The tuples are not equal.
            return false;
        }
        return true;
    }

    static FunDef createDummyFunDef(
        Resolver resolver,
        int returnCategory,
        Exp[] args)
    {
        final int[] argCategories = ExpBase.getTypes(args);
        return new FunDefBase(resolver, returnCategory, argCategories) {
        };
    }

    public static List<Member> getNonEmptyMemberChildren(
        Evaluator evaluator,
        Member member)
    {
        SchemaReader sr = evaluator.getSchemaReader();
        if (evaluator.isNonEmpty()) {
            return sr.getMemberChildren(member, evaluator);
        } else {
            return sr.getMemberChildren(member);
        }
    }

    public static Map<Member, Access>
        getNonEmptyMemberChildrenWithDetails(
            Evaluator evaluator, Member member)
    {
        SchemaReader sr = evaluator.getSchemaReader();
        if (evaluator.isNonEmpty()) {
            return (Map<Member, Access>)
                sr.getMemberChildrenWithDetails(member, evaluator);
        } else {
            return (Map<Member, Access>)
                sr.getMemberChildrenWithDetails(member, null);
        }
    }

    /**
     * Returns members of a level which are not empty (according to the
     * criteria expressed by the evaluator).
     *
     * @param evaluator Evaluator, determines non-empty criteria
     * @param level Level
     * @param includeCalcMembers Whether to include calculated members
     */
    static List<Member> getNonEmptyLevelMembers(
        final Evaluator evaluator,
        final Level level,
        final boolean includeCalcMembers)
    {
        SchemaReader sr = evaluator.getSchemaReader();
        if (evaluator.isNonEmpty()) {
            List<Member> members = sr.getLevelMembers(level, evaluator);
            if (includeCalcMembers) {
                return addLevelCalculatedMembers(sr, level, members);
            }
            return members;
        }
        return sr.getLevelMembers(level, includeCalcMembers);
    }

    static TupleList levelMembers(
        final Level level,
        final Evaluator evaluator,
        final boolean includeCalcMembers)
    {
        List<Member> memberList =
            getNonEmptyLevelMembers(evaluator, level, includeCalcMembers);
        TupleList tupleList;
        if (!includeCalcMembers) {
            memberList = removeCalculatedMembers(memberList);
        }
        final List<Member> memberListClone = new ArrayList<Member>(memberList);
        tupleList = new UnaryTupleList(memberListClone);
        return hierarchizeTupleList(tupleList, false);
    }

    static TupleList hierarchyMembers(
        Hierarchy hierarchy,
        Evaluator evaluator,
        final boolean includeCalcMembers)
    {
        TupleList tupleList = new UnaryTupleList();
        final List<Member> memberList = tupleList.slice(0);
        if (evaluator.isNonEmpty()) {
            // Allow the SQL generator to generate optimized SQL since we know
            // we're only interested in non-empty members of this level.
            for (Level level : hierarchy.getLevels()) {
                List<Member> members =
                    getNonEmptyLevelMembers(
                        evaluator, level, includeCalcMembers);
                memberList.addAll(members);
            }
        } else {
            final List<Member> memberList1 = addMembers(
                evaluator.getSchemaReader(),
                new ConcatenableList<Member>(),
                hierarchy);
            if (includeCalcMembers) {
                memberList.addAll(memberList1);
            } else {
                // Same effect as calling removeCalculatedMembers(tupleList)
                // but one fewer copy of the list.
                for (Member member1 : memberList1) {
                    if (!member1.isCalculated()) {
                        memberList.add(member1);
                    }
                }
            }
        }
        return hierarchizeTupleList(tupleList, false);
    }

   /**
    * Partial Sort: sorts in place an array of Objects using a given Comparator,
    * but only enough so that the N biggest (or smallest) items are at the start
    * of the array. Not a stable sort, unless the Comparator is so contrived.
    *
    * @param items will be partially-sorted in place
    * @param comp a Comparator; null means use natural comparison
    */
    static <T> void partialSort(T[] items, Comparator<T> comp, int limit)
    {
        if (comp == null) {
            //noinspection unchecked
            comp = (Comparator<T>) ComparatorUtils.naturalComparator();
        }
        new Quicksorter<T>(items, comp).partialSort(limit);
    }

    /**
     * Stable partial sort of a list. Returns the desired head of the list.
     */
    public static <T> List<T> stablePartialSort(
        final List<T> list, final Comparator<T> comp, int limit)
    {
        return stablePartialSort(list, comp, limit, 0);
    }

    /**
     * Stable partial sort of a list, using a specified algorithm.
     */
    public static <T> List<T> stablePartialSort(
        final List<T> list, final Comparator<T> comp, int limit, int algorithm)
    {
        assert limit <= list.size();
        assert list.size() > 0;
        for (;;) {
            switch (algorithm) {
            case 0:
                float ratio = (float) limit / (float) list.size();
                if (ratio <= .05) {
                    algorithm = 4; // julian's algorithm
                } else if (ratio <= .35) {
                    algorithm = 2; // marc's algorithm
                } else {
                    algorithm = 1; // array sort
                }
                break;
            case 1:
                return stablePartialSortArray(list, comp, limit);
            case 2:
                return stablePartialSortMarc(list, comp, limit);
            case 3:
                return stablePartialSortPedro(list, comp, limit);
            case 4:
                return stablePartialSortJulian(list, comp, limit);
            default:
                throw new RuntimeException();
            }
        }
    }

    /**
     * Partial sort an array by sorting it and returning the first {@code limit}
     * elements. Fastest approach if limit is a significant fraction of the
     * list.
     */
    public static <T> List<T> stablePartialSortArray(
        final List<T> list, final Comparator<T> comp, int limit)
    {
        ArrayList<T> list2 = new ArrayList<T>(list);
        Collections.sort(list2, comp);
        return list2.subList(0, limit);
    }

    /**
     * Marc's original algorithm for stable partial sort of a list.
     * Now superseded by {@link #stablePartialSortJulian}.
     */
    public static <T> List<T> stablePartialSortMarc(
        final List<T> list, final Comparator<T> comp, int limit)
    {
        assert limit >= 0;

        // Load an array of pairs {list-item, list-index}.
        // List-index is a secondary sort key, to give a stable sort.
        // REVIEW Can we use a simple T[], with the index implied?
        // REVIEW When limit is big relative to list size, faster to
        // mergesort. Test for this.
        int n = list.size();            // O(n) to scan list
        @SuppressWarnings({"unchecked"})
        final ObjIntPair<T>[] pairs = new ObjIntPair[n];

        int i = 0;
        for (T item : list) {           // O(n) to scan list
            pairs[i] = new ObjIntPair<T>(item, i);
            ++i;
        }

        Comparator<ObjIntPair<T>> pairComp =
            new Comparator<ObjIntPair<T>>()
        {
            public int compare(ObjIntPair<T> x, ObjIntPair<T> y) {
                int val = comp.compare(x.t, y.t);
                if (val == 0) {
                    val = x.i - y.i;
                }
                return val;
            }
        };

        final int length = Math.min(limit, n);
        // O(n + limit * log(limit)) to quicksort
        partialSort(pairs, pairComp, length);

        // Use an abstract list to avoid doing a copy. The result is immutable.
        return new AbstractList<T>() {
            @Override
            public T get(int index) {
                return pairs[index].t;
            }

            @Override
            public int size() {
                return length;
            }
        };
    }

    /**
     * Pedro's algorithm for stably sorting the top {@code limit} items in
     * a list.
     */
    public static <T> List<T> stablePartialSortPedro(
        final List<T> list, final Comparator<T> comp, int limit)
    {
        final ObjIntPair<T>[] pairs = new ObjIntPair[limit];
        Comparator<ObjIntPair<T>> pairComp =
            new Comparator<ObjIntPair<T>>() {
                public int compare(ObjIntPair<T> x, ObjIntPair<T> y) {
                    int val = comp.compare(x.t, y.t);
                    if (val == 0) {
                        val = x.i - y.i;
                    }
                    return val;
                }
            };

        int filled = 0;
        T maximum = null;
        int maximumIndex = 0;
        int originalIndex = 0;
        for (T item : list) { // O(n) to scan list
            switch (filled) {
            case 0:
                maximum = item;
                pairs[0] = new ObjIntPair<T>(item, originalIndex);
                filled++;
                break;
            default:
                if (filled < limit) {
                    pairs[filled] = new ObjIntPair<T>(item, originalIndex);

                    if (comp.compare(item, maximum) > 0) {
                        maximum = item;
                        maximumIndex = filled;
                    }
                    filled++;
                } else {
                    if (comp.compare(item, maximum) < 0) {
                        pairs[maximumIndex] =
                            new ObjIntPair<T>(item, originalIndex);
                        maximum = pairs[0].t;
                        maximumIndex = 0;
                        for (int i = 0; i < filled; i++) {
                            if (comp.compare(pairs[i].t, maximum) > 0) {
                                maximum = pairs[i].t;
                                maximumIndex = i;
                            }
                        }
                    }
                }
            }
            originalIndex++;
        }

        Arrays.sort(pairs, pairComp);

        if (false)
        for (int i = 0; i < limit; i++) {
            T item = pairs[i].t;
            T originalItem = list.get(i);
            int itemIndex = pairs[i].i;
            if (itemIndex < i) {
                if (pairs[itemIndex].i > i) {
                    list.set(pairs[itemIndex].i, originalItem);
                }
            } else {
                list.set(itemIndex, originalItem);
            }
            list.set(i, item);
        }

        List<T> result = new ArrayList<T>(limit);
        for (int i = 0; i < limit; i++) {
            result.add(list.get(pairs[i].i));
        }
        return result;
    }

    /**
     * Julian's algorithm for stable partial sort. Improves Pedro's algorithm
     * by using a heap (priority queue) for the top {@code limit} items seen.
     * The items on the priority queue have an ordinal field, so the queue
     * can be used to generate a list of stably sorted items. (Heap sort is
     * not normally stable.)
     *
     * @param list List to sort
     * @param comp Comparator
     * @param limit Maximum number of items to return
     * @param <T> Element type
     * @return Sorted list, containing at most limit items
     */
    public static <T> List<T> stablePartialSortJulian(
        final List<T> list, final Comparator<T> comp, int limit)
    {
        final Comparator<ObjIntPair<T>> comp2 =
            new Comparator<ObjIntPair<T>>() {
                public int compare(ObjIntPair<T> o1, ObjIntPair<T> o2) {
                    int c = comp.compare(o1.t, o2.t);
                    if (c == 0) {
                        c = Util.compare(o1.i, o2.i);
                    }
                    return -c;
                }
            };
        int filled = 0;
        final PriorityQueue<ObjIntPair<T>> queue =
            new PriorityQueue<ObjIntPair<T>>(limit, comp2);
        for (T element : list) {
            if (filled < limit) {
                queue.offer(new ObjIntPair<T>(element, filled++));
            } else {
                ObjIntPair<T> head = queue.element();
                if (comp.compare(element, head.t) <= 0) {
                    ObjIntPair<T> item = new ObjIntPair<T>(element, filled++);
                    if (comp2.compare(item, head) >= 0) {
                        ObjIntPair poll = queue.remove();
                        Util.discard(poll);
                        queue.offer(item);
                    }
                }
            }
        }

        int n = queue.size();
        final Object[] elements = new Object[n];
        while (n > 0) {
            elements[--n] = queue.poll().t;
        }
        assert queue.isEmpty();
        //noinspection unchecked
        return Arrays.asList((T[]) elements);
    }

    static TupleList parseTupleList(
        Evaluator evaluator,
        String string,
        List<Hierarchy> hierarchies)
    {
        final IdentifierParser.TupleListBuilder builder =
            new IdentifierParser.TupleListBuilder(
                evaluator.getSchemaReader(),
                evaluator.getCube(),
                hierarchies);
        IdentifierParser.parseTupleList(builder, string);
        return builder.tupleList;
    }

    /**
     * Parses a tuple, of the form '(member, member, ...)'.
     * There must be precisely one member for each hierarchy.
     *
     *
     * @param evaluator Evaluator, provides a {@link mondrian.olap.SchemaReader}
     *   and {@link mondrian.olap.Cube}
     * @param string String to parse
     * @param i Position to start parsing in string
     * @param members Output array of members
     * @param hierarchies Hierarchies of the members
     * @return Position where parsing ended in string
     */
    private static int parseTuple(
        final Evaluator evaluator,
        String string,
        int i,
        final Member[] members,
        List<Hierarchy> hierarchies)
    {
        final IdentifierParser.Builder builder =
            new IdentifierParser.TupleBuilder(
                evaluator.getSchemaReader(),
                evaluator.getCube(),
                hierarchies)
            {
                public void tupleComplete() {
                    super.tupleComplete();
                    memberList.toArray(members);
                }
            };
        return IdentifierParser.parseTuple(builder, string, i);
    }

    /**
     * Parses a tuple, such as "([Gender].[M], [Marital Status].[S])".
     *
     * @param evaluator Evaluator, provides a {@link mondrian.olap.SchemaReader}
     *   and {@link mondrian.olap.Cube}
     * @param string String to parse
     * @param hierarchies Hierarchies of the members
     * @return Tuple represented as array of members
     */
    static Member[] parseTuple(
        Evaluator evaluator,
        String string,
        List<Hierarchy> hierarchies)
    {
        final Member[] members = new Member[hierarchies.size()];
        int i = parseTuple(evaluator, string, 0, members, hierarchies);
        // todo: check for garbage at end of string
        if (FunUtil.tupleContainsNullMember(members)) {
            return null;
        }
        return members;
    }

    static List<Member> parseMemberList(
        Evaluator evaluator,
        String string,
        Hierarchy hierarchy)
    {
        IdentifierParser.MemberListBuilder builder =
            new IdentifierParser.MemberListBuilder(
                evaluator.getSchemaReader(),
                evaluator.getCube(),
                hierarchy);
        IdentifierParser.parseMemberList(builder, string);
        return builder.memberList;
    }

    private static int parseMember(
        Evaluator evaluator,
        String string,
        int i,
        final Member[] members,
        Hierarchy hierarchy)
    {
        IdentifierParser.MemberListBuilder builder =
            new IdentifierParser.MemberListBuilder(
                evaluator.getSchemaReader(), evaluator.getCube(), hierarchy)
            {
                @Override
                public void memberComplete() {
                    members[0] = resolveMember(hierarchyList.get(0));
                    segmentList.clear();
                }
            };
        return IdentifierParser.parseMember(builder, string, i);
    }

    static Member parseMember(
        Evaluator evaluator, String string, Hierarchy hierarchy)
    {
        Member[] members = {null};
        int i = parseMember(evaluator, string, 0, members, hierarchy);
        // todo: check for garbage at end of string
        final Member member = members[0];
        if (member == null) {
            throw MondrianResource.instance().MdxChildObjectNotFound.ex(
                string, evaluator.getCube().getQualifiedName());
        }
        return member;
    }

    /**
     * Returns whether an expression is worth wrapping in "Cache( ... )".
     *
     * @param exp Expression
     * @return Whether worth caching
     */
    public static boolean worthCaching(Exp exp) {
        // Literal is not worth caching.
        if (exp instanceof Literal) {
            return false;
        }
        // Member, hierarchy, level, or dimension expression is not worth
        // caching.
        if (exp instanceof MemberExpr
            || exp instanceof LevelExpr
            || exp instanceof HierarchyExpr
            || exp instanceof DimensionExpr)
        {
            return false;
        }
        if (exp instanceof ResolvedFunCall) {
            ResolvedFunCall call = (ResolvedFunCall) exp;

            // A set of literals is not worth caching.
            if (call.getFunDef() instanceof SetFunDef) {
                for (Exp setArg : call.getArgs()) {
                    if (worthCaching(setArg)) {
                        return true;
                    }
                }
                return false;
            }
        }
        return true;
    }

    // ~ Inner classes ---------------------------------------------------------

    /**
     * A functional for {@link FunUtil#partialSort}.
     * Sorts or partially sorts an array in ascending order, using a Comparator.
     *
     * <p>Algorithm: quicksort, or partial quicksort (alias
     * "quickselect"), Hoare/Singleton.  Partial quicksort is
     * quicksort that recurs only on one side, which is thus
     * tail-recursion.  Picks pivot as median of three; falls back on
     * insertion sort for small "subfiles".  Partial quicksort is O(n
     * + m log m), instead of O(n log n), where n is the input size,
     * and m is the desired output size.
     *
     * <p>See D Knuth, Art of Computer Programming, 5.2.2 (Algorithm
     * Q); R. Sedgewick, Algorithms in C, ch 5.  Good summary in
     * http://en.wikipedia.org/wiki/Selection_algorithm
     *
     * <P>TODO: What is the time-cost of this functor and of the nested
     * Comparators?
     */
    static class Quicksorter<T> {
        // size of smallest set worth a quicksort
        public final int TOO_SMALL = 8;

        private static final Logger LOGGER =
            Logger.getLogger(Quicksorter.class);
        private final T[] vec;
        private final Comparator<T> comp;
        private final boolean traced;
        private long partitions, comparisons, exchanges; // stats

        public Quicksorter(T[] vec, Comparator<T> comp) {
            this.vec = vec;
            this.comp = comp;
            partitions = comparisons = exchanges = 0;
            traced = LOGGER.isDebugEnabled();
        }

        private void traceStats(String prefix) {
            StringBuilder sb = new StringBuilder(prefix);
            sb.append(": ");
            sb.append(partitions).append(" partitions, ");
            sb.append(comparisons).append(" comparisons, ");
            sb.append(exchanges).append(" exchanges.");
            LOGGER.debug(sb.toString());
        }

        // equivalent to operator <
        private boolean less(T x, T y) {
            comparisons++;
            return comp.compare(x, y) < 0;
        }

        // equivalent to operator >
        private boolean more(T x, T y) {
            comparisons++;
            return comp.compare(x, y) > 0;
        }
        // equivalent to operator >
        private boolean equal(T x, T y) {
            comparisons++;
            return comp.compare(x, y) == 0;
        }

        // swaps two items (identified by index in vec[])
        private void swap(int i, int j) {
            exchanges++;
            T temp = vec[i];
            vec[i] = vec[j];
            vec[j] = temp;
        }

        // puts into ascending order three items
        // (identified by index in vec[])
        // REVIEW: use only 2 comparisons??
        private void order3(int i, int j, int k) {
            if (more(vec[i], vec[j])) {
                swap(i, j);
            }
            if (more(vec[i], vec[k])) {
                swap(i, k);
            }
            if (more(vec[j], vec[k])) {
                swap(j, k);
            }
        }

        // runs a selection sort on the array segment VEC[START .. END]
        private void selectionSort(int start, int end) {
            for (int i = start; i < end; ++i) {
                // pick the min of vec[i, end]
                int pmin = i;
                for (int j = i + 1; j <= end; ++j) {
                    if (less(vec[j], vec[pmin])) {
                        pmin = j;
                    }
                }
                if (pmin != i) {
                    swap(i, pmin);
                }
            }
        }

        /**
         * Runs one pass of quicksort on array segment VEC[START .. END],
         * dividing it into two parts, the left side VEC[START .. P] none
         * greater than the pivot value VEC[P], and the right side VEC[P+1
         * .. END] none less than the pivot value. Returns P, the index of the
         * pivot element in VEC[].
         */
        private int partition(int start, int end) {
            partitions++;
            assert start <= end;

            // Find median of three (both ends and the middle).
            // TODO: use pseudo-median of nine when array segment is big enough.
            int mid = (start + end) / 2;
            order3(start, mid, end);
            if (end - start <= 2) {
                return mid;        // sorted!
            }

            // Now the left and right ends are in place (ie in the correct
            // partition), and will serve as sentinels for scanning. Pick middle
            // as pivot and set it aside, in penultimate position.
            final T pivot = vec[mid];
            swap(mid, end - 1);

            // Scan inward from both ends, swapping misplaced items.
            int left = start + 1;       // vec[start] is in place
            int right = end - 2;        // vec[end - 1] is pivot
            while (left < right) {
                // scan past items in correct place, but stop at a pivot value
                // (Sedgewick's idea).
                while (less(vec[left], pivot)) {
                    ++left;
                }
                while (less(pivot, vec[right])) {
                    --right;
                }
                if (debug) {
                    assert (left <= end) && (right >= start);
                }
                if (left < right) {     // found a misplaced pair
                    swap(left, right);
                    ++left; --right;
                }
            }
            if ((left == right) && less(vec[left], pivot)) {
                ++left;
            }

            // All scanned. Restore pivot to its rightful place.
            swap(left, end - 1);

            if (debug) {
                for (int i = start; i < left; i++) {
                    assert !more(vec[i], pivot);
                }
                assert equal(vec[left], pivot);
                for (int i = left + 1;  i <= end;  i++) {
                    assert !less(vec[i], pivot);
                }
            }
            return left;
        }


        // Runs quicksort on VEC[START, END]. Recursive version,
        // TODO: exploit tail recursion
        private void sort(int start, int end) {
            if (end - start < TOO_SMALL) {
                selectionSort(start, end);
                return;
            }

            // Split data, so that left side dominates the right side
            // (but neither is sorted):
            int mid = partition(start, end);
            sort(start, mid - 1);
            sort(mid + 1, end);
        }

        // Runs quickselect(LIMIT) on VEC[START, END]. Recursive version,
        // TODO: exploit tail recursion, unfold.
        private void select(int limit, int start, int end) {
            if (limit == 0) {
                return;
            }
            if (end - start < TOO_SMALL) {
                selectionSort(start, end);
                return;
            }
            int mid = partition(start, end);
            int leftSize = mid - start + 1;
            if (limit < leftSize) {
                // work on the left side, and ignore the right side
                select(limit, start, mid);
            } else {
                limit -= leftSize;
                // work on the right side, but keep the left side
                select(limit, mid + 1, end);
            }
        }

        public void sort() {
            int n = vec.length - 1;
            sort(0, n);
            if (traced) {
                traceStats("quicksort on " + n + "items");
            }
        }

        /** puts the LIMIT biggest items at the head, not sorted */
        public void select(int limit) {
            int n = vec.length - 1;
            select(limit, 0, n);
            if (traced) {
                traceStats("quickselect for " + limit + " from" + n + "items");
            }
        }

        public void partialSort(int limit) {
            int n = vec.length - 1;
            select(limit, 0, n);
            if (traced) {
                traceStats(
                    "partial sort: quickselect phase for "
                    + limit + "from " + n + "items");
            }
            sort(0, limit - 1);
            if (traced) {
                traceStats("partial sort: quicksort phase on " + n + "items");
            }
        }
    }

    /**
     * Comparator for members.
     *
     * <p>Could genericize this to <code>class&lt;T&gt; MemorizingComparator
     * implements Comparator&lt;T&gt;</code>, but not if it adds a run time
     * cost, since the comparator is at the heart of the sort algorithms.
     */
    private static abstract class MemberComparator implements Comparator<Member>
    {
        private static final Logger LOGGER =
            Logger.getLogger(MemberComparator.class);
        final Evaluator evaluator;
        final Calc exp;

        private final int descMask;
        private final Map<Member, Object> valueMap;

        MemberComparator(Evaluator evaluator, Calc exp, boolean desc)
        {
            this.evaluator = evaluator;
            this.exp = exp;
            this.descMask = desc ? -1 : 1;
            this.valueMap = new HashMap<Member, Object>();
        }

        private int maybeNegate(int c) {
            return descMask * c;
        }

        // applies the Calc to a member, memorizing results
        protected Object eval(Member m)
        {
            Object val = valueMap.get(m);
            if (val == null) {
                evaluator.setContext(m);
                val = exp.evaluate(evaluator);
                if (val == null) {
                    val = Util.nullValue;
                }
                valueMap.put(m, val);
            }
            return val;
        }

        // wraps comparison with tracing
        Comparator<Member> wrap() {
            final MemberComparator comparator = this;
            if (LOGGER.isDebugEnabled()) {
                return new Comparator<Member>() {
                    public int compare(Member m1, Member m2) {
                        final int c = comparator.compare(m1, m2);
                        // here guaranteed that eval(m) finds a memorized value
                        LOGGER.debug(
                            "compare "
                            + m1.getUniqueName() + "(" + eval(m1) + "), "
                            + m2.getUniqueName() + "(" + eval(m2) + ")"
                            + " yields " + c);
                        return c;
                    }
                };
            } else {
                return this;
            }
        }

        // Preloads the value map with precomputed members (supplied as a map).
        void preloadValues(Map<Member, Object> map) {
            valueMap.putAll(map);
        }

        // Preloads the value map by applying the expression to a Collection of
        // members.
        void preloadValues(Collection<Member> members) {
            for (Member m : members) {
                eval(m);
            }
        }

        protected final int compareByValue(Member m1, Member m2) {
            final int c = FunUtil.compareValues(eval(m1), eval(m2));
            return maybeNegate(c);
        }

        protected final int compareHierarchicallyButSiblingsByValue(
            Member m1, Member m2)
        {
            if (FunUtil.equals(m1, m2)) {
                return 0;
            }
            while (true) {
                int depth1 = m1.getDepth(),
                    depth2 = m2.getDepth();
                if (depth1 < depth2) {
                    m2 = m2.getParentMember();
                    if (Util.equals(m1, m2)) {
                        return -1;
                    }
                } else if (depth1 > depth2) {
                    m1 = m1.getParentMember();
                    if (Util.equals(m1, m2)) {
                        return 1;
                    }
                } else {
                    Member prev1 = m1, prev2 = m2;
                    m1 = m1.getParentMember();
                    m2 = m2.getParentMember();
                    if (Util.equals(m1, m2)) {
                        // including case where both parents are null
                        int c = compareByValue(prev1, prev2);
                        if (c != 0) {
                            return c;
                        }
                        // prev1 and prev2 are siblings.  Order according to
                        // hierarchy, if the values do not differ.  Needed to
                        // have a consistent sortMembers if members with equal
                        // (null!)  values are compared.
                        c = FunUtil.compareSiblingMembers(prev1, prev2);

                        // Do not negate c, even if we are sorting descending.
                        // This comparison is to achieve the 'natural order'.
                        return c;
                    }
                }
            }
        }
    }

    private static class HierarchicalMemberComparator
        extends MemberComparator
    {
        HierarchicalMemberComparator(
            Evaluator evaluator, Calc exp, boolean desc)
        {
            super(evaluator, exp, desc);
        }

        public int compare(Member m1, Member m2) {
            return compareHierarchicallyButSiblingsByValue(m1, m2);
        }
    }

    private static class BreakMemberComparator extends MemberComparator {
        BreakMemberComparator(Evaluator evaluator, Calc exp, boolean desc)
        {
            super(evaluator, exp, desc);
        }

        public final int compare(Member m1, Member m2) {
            return compareByValue(m1, m2);
        }
    }

    /**
     * Compares tuples, which are represented as lists of {@link Member}s.
     */
    private static abstract class TupleComparator
        implements Comparator<List<Member>>
    {
        final int arity;
        TupleComparator(int arity) {
            this.arity = arity;
        }
    }

    /**
     * Extension to {@link TupleComparator} which compares tuples by evaluating
     * an expression.
     */
    private static abstract class TupleExpComparator extends TupleComparator {
        Evaluator evaluator;
        final Calc calc;

        TupleExpComparator(Evaluator evaluator, Calc calc, int arity) {
            super(arity);
            this.evaluator = evaluator;
            this.calc = calc;
        }
    }

    private static class HierarchicalTupleComparator
        extends TupleExpComparator
    {
        private final boolean desc;

        HierarchicalTupleComparator(
            Evaluator evaluator, Calc calc, int arity, boolean desc)
        {
            super(evaluator, calc, arity);
            this.desc = desc;
        }

        public int compare(List<Member> a1, List<Member> a2) {
            int c = 0;
            final int savepoint = evaluator.savepoint();
            try {
                for (int i = 0; i < arity; i++) {
                    Member m1 = a1.get(i), m2 = a2.get(i);
                    c = compareHierarchicallyButSiblingsByValue(m1, m2);
                    if (c != 0) {
                        break;
                    }
                    // compareHierarchicallyButSiblingsByValue imposes a
                    // total order
                    assert m1.equals(m2);
                    evaluator.setContext(m1);
                }
            } finally {
                evaluator.restore(savepoint);
            }
            return c;
        }

        protected int compareHierarchicallyButSiblingsByValue(
            Member m1,
            Member m2)
        {
            if (FunUtil.equals(m1, m2)) {
                return 0;
            }
            while (true) {
                int depth1 = m1.getDepth(),
                        depth2 = m2.getDepth();
                if (depth1 < depth2) {
                    m2 = m2.getParentMember();
                    if (FunUtil.equals(m1, m2)) {
                        return -1;
                    }
                } else if (depth1 > depth2) {
                    m1 = m1.getParentMember();
                    if (FunUtil.equals(m1, m2)) {
                        return 1;
                    }
                } else {
                    Member prev1 = m1, prev2 = m2;
                    m1 = m1.getParentMember();
                    m2 = m2.getParentMember();
                    if (FunUtil.equals(m1, m2)) {
                        // including case where both parents are null
                        int c = compareByValue(prev1, prev2);
                        if (c == 0) {
                            c = FunUtil.compareSiblingMembers(prev1, prev2);
                        }
                        return desc ? -c : c;
                    }
                }
            }
        }

        private int compareByValue(Member m1, Member m2) {
            int c;
            final int savepoint = evaluator.savepoint();
            try {
                evaluator.setContext(m1);
                Object v1 = calc.evaluate(evaluator);
                evaluator.setContext(m2);
                Object v2 = calc.evaluate(evaluator);
                c = FunUtil.compareValues(v1, v2);
                return c;
            } finally {
                // important to restore the evaluator state
                evaluator.restore(savepoint);
            }
        }
    }

    // almost the same as MemberComparator
    static abstract class TupleExpMemoComparator extends TupleExpComparator {
        private final Map<List<Member>, Object> valueMap =
            new HashMap<List<Member>, Object>();

        TupleExpMemoComparator(Evaluator e, Calc calc, int arity)
        {
            super(e, calc, arity);
        }

        // applies the Calc to a tuple, memorizing results
        protected Object eval(List<Member> t)
        {
            Object val = valueMap.get(t);
            if (val != null) {
                return val;
            }
            return compute(t);
        }

        private Object compute(List<Member> key) {
            evaluator.setContext(key);
            Object val = calc.evaluate(evaluator);
            if (val == null) {
                val = Util.nullValue;
            }
            valueMap.put(key, val);
            return val;
        }

        // Preloads the value map by applying the expression to a Collection of
        // members.
        void preloadValues(TupleList tuples) {
            for (List<Member> t : tuples) {
                compute(t);
            }
        }
    }

    private static class BreakTupleComparator extends TupleExpMemoComparator {
        BreakTupleComparator(Evaluator e, Calc calc, int arity) {
            super(e, calc, arity);
        }

        public int compare(List<Member> a1, List<Member> a2) {
            return FunUtil.compareValues(eval(a1), eval(a2));
        }
    }

    private static class HierarchicalTupleKeyComparator
        extends TupleExpMemoComparator
    {

        HierarchicalTupleKeyComparator(Evaluator e, Calc calc, int arity) {
            super(e, calc, arity);
        }

        public int compare(List<Member> a1, List<Member> a2) {
            OrderKey k1 = (OrderKey) eval(a1);
            OrderKey k2 = (OrderKey) eval(a2);
            return compareMemberOrderKeysHierarchically(k1, k2);
        }

        private int compareMemberOrderKeysHierarchically(
            OrderKey k1, OrderKey k2)
        {
            // null is less than anything else
            if (k1 == Util.nullValue) {
                return -1;
            }
            if (k2 == Util.nullValue) {
                return 1;
            }
            Member m1 = k1.member;
            Member m2 = k2.member;
            if (FunUtil.equals(m1, m2)) {
                return 0;
            }
            while (true) {
                int depth1 = m1.getDepth(),
                        depth2 = m2.getDepth();
                if (depth1 < depth2) {
                    m2 = m2.getParentMember();
                    if (FunUtil.equals(m1, m2)) {
                        return -1;
                    }
                } else if (depth1 > depth2) {
                    m1 = m1.getParentMember();
                    if (FunUtil.equals(m1, m2)) {
                        return 1;
                    }
                } else {
                    Member prev1 = m1, prev2 = m2;
                    m1 = m1.getParentMember();
                    m2 = m2.getParentMember();
                    if (FunUtil.equals(m1, m2)) {
                        OrderKey pk1 = new OrderKey(prev1);
                        OrderKey pk2 = new OrderKey(prev2);
                        return FunUtil.compareValues(pk1, pk2);
                    }
                }
            }
        }
    }

    /**
     * Compares lists of {@link Member}s so as to convert them into hierarchical
     * order. Applies lexicographic order to the array.
     */
    private static class HierarchizeTupleComparator extends TupleComparator {
        private final boolean post;

        HierarchizeTupleComparator(int arity, boolean post) {
            super(arity);
            this.post = post;
        }

        public int compare(List<Member> a1, List<Member> a2) {
            for (int i = 0; i < arity; i++) {
                Member m1 = a1.get(i), m2 = a2.get(i);
                int c = FunUtil.compareHierarchically(m1, m2, post);
                if (c != 0) {
                    return c;
                }
            }
            return 0;
        }
    }

    /**
     * Compares {@link Member}s so as to arrage them in prefix or postfix
     * hierarchical order.
     */
    private static class HierarchizeComparator implements Comparator<Member> {
        private final boolean post;

        HierarchizeComparator(boolean post) {
            this.post = post;
        }
        public int compare(Member m1, Member m2) {
            return FunUtil.compareHierarchically(m1, m2, post);
        }
    }

    static class SetWrapper {
        List v = new ArrayList();
        public int errorCount = 0, nullCount = 0;

        // private double avg = Double.NaN;
        // TODO: parameterize inclusion of nulls
        // by making this a method of the SetWrapper, we can cache the result
        // this allows its reuse in Correlation
        // public double getAverage() {
        //     if (avg == Double.NaN) {
        //         double sum = 0.0;
        //         for (int i = 0; i < resolvers.size(); i++) {
        //             sum += ((Number) resolvers.elementAt(i)).doubleValue();
        //         }
        //         // TODO: should look at context and optionally include nulls
        //         avg = sum / (double) resolvers.size();
        //     }
        //     return avg;
        // }
    }

    /**
     * Compares cell values, so that larger values compare first.
     *
     * <p>Nulls compare last, exceptions (including the
     * object which indicates the the cell is not in the cache yet) next,
     * then numbers and strings are compared by value.
     */
    public static class DescendingValueComparator implements Comparator {
        /**
         * The singleton.
         */
        static final DescendingValueComparator instance =
                new DescendingValueComparator();

        public int compare(Object o1, Object o2) {
            return - compareValues(o1, o2);
        }
    }

    /**
     * Null member of unknown hierarchy.
     */
    private static class NullMember implements Member {
        public Member getParentMember() {
            throw new UnsupportedOperationException();
        }

        public Level getLevel() {
            throw new UnsupportedOperationException();
        }

        public Hierarchy getHierarchy() {
            throw new UnsupportedOperationException();
        }

        public String getParentUniqueName() {
            throw new UnsupportedOperationException();
        }

        public MemberType getMemberType() {
            throw new UnsupportedOperationException();
        }

        public boolean isParentChildLeaf() {
            return false;
        }

        public void setName(String name) {
            throw new UnsupportedOperationException();
        }

        public boolean isAll() {
            return false;
        }

        public boolean isMeasure() {
            throw new UnsupportedOperationException();
        }

        public boolean isNull() {
            return true;
        }

        public boolean isChildOrEqualTo(Member member) {
            throw new UnsupportedOperationException();
        }

        public boolean isCalculated() {
            throw new UnsupportedOperationException();
        }

        public boolean isEvaluated() {
            throw new UnsupportedOperationException();
        }

        public int getSolveOrder() {
            throw new UnsupportedOperationException();
        }

        public Exp getExpression() {
            throw new UnsupportedOperationException();
        }

        public List<Member> getAncestorMembers() {
            throw new UnsupportedOperationException();
        }

        public boolean isCalculatedInQuery() {
            throw new UnsupportedOperationException();
        }

        public Object getPropertyValue(String propertyName) {
            throw new UnsupportedOperationException();
        }

        public Object getPropertyValue(String propertyName, boolean matchCase) {
            throw new UnsupportedOperationException();
        }

        public String getPropertyFormattedValue(String propertyName) {
            throw new UnsupportedOperationException();
        }

        public void setProperty(String name, Object value) {
            throw new UnsupportedOperationException();
        }

        public Property[] getProperties() {
            throw new UnsupportedOperationException();
        }

        public int getOrdinal() {
            throw new UnsupportedOperationException();
        }

        public Comparable getOrderKey() {
            throw new UnsupportedOperationException();
        }

        public boolean isHidden() {
            throw new UnsupportedOperationException();
        }

        public int getDepth() {
            throw new UnsupportedOperationException();
        }

        public Member getDataMember() {
            throw new UnsupportedOperationException();
        }

        public String getUniqueName() {
            throw new UnsupportedOperationException();
        }

        public String getName() {
            throw new UnsupportedOperationException();
        }

        public String getDescription() {
            throw new UnsupportedOperationException();
        }

        public OlapElement lookupChild(
            SchemaReader schemaReader, Id.Segment s, MatchType matchType)
        {
            throw new UnsupportedOperationException();
        }

        public String getQualifiedName() {
            throw new UnsupportedOperationException();
        }

        public String getCaption() {
            throw new UnsupportedOperationException();
        }

        public String getLocalized(LocalizedProperty prop, Locale locale) {
            throw new UnsupportedOperationException();
        }

        public boolean isVisible() {
            throw new UnsupportedOperationException();
        }

        public Dimension getDimension() {
            throw new UnsupportedOperationException();
        }

        public Map<String, Annotation> getAnnotationMap() {
            throw new UnsupportedOperationException();
        }

        public int compareTo(Object o) {
            throw new UnsupportedOperationException();
        }

        public boolean equals(Object obj) {
            throw new UnsupportedOperationException();
        }

        public int hashCode() {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * Enumeration of the flags allowed to the {@code ORDER} MDX function.
     */
    enum Flag {
        ASC(false, false),
        DESC(true, false),
        BASC(false, true),
        BDESC(true, true);

        final boolean descending;
        final boolean brk;

        Flag(boolean descending, boolean brk) {
            this.descending = descending;
            this.brk = brk;
        }

        public static String[] getNames() {
            List<String> names = new ArrayList<String>();
            for (Flag flags : Flag.class.getEnumConstants()) {
                names.add(flags.name());
            }
            return names.toArray(new String[names.size()]);
        }
    }

    static class SortKeySpec {
        private final Calc key;
        private final Flag direction;

        SortKeySpec(Calc key, Flag dir) {
            this.key = key;
            this.direction = dir;
        }

        Calc getKey() {
            return this.key;
        }

        Flag getDirection() {
            return this.direction;
        }
    }

    public static class OrderKey implements Comparable {
        private final Member member;

        public OrderKey(Member member) {
            super();
            this.member = member;
        }

        public int compareTo(Object o) {
            assert o instanceof OrderKey;
            Member otherMember = ((OrderKey) o).member;
            final boolean thisCalculated = this.member.isCalculatedInQuery();
            final boolean otherCalculated = otherMember.isCalculatedInQuery();
            if (thisCalculated) {
                if (!otherCalculated) {
                    return 1;
                }
            } else {
                if (otherCalculated) {
                    return -1;
                }
            }
            final Comparable thisKey = this.member.getOrderKey();
            final Comparable otherKey = otherMember.getOrderKey();
            if ((thisKey != null) && (otherKey != null)) {
                return thisKey.compareTo(otherKey);
            } else {
                return this.member.compareTo(otherMember);
            }
        }
    }

    /**
     * Tuple consisting of an object and an integer.
     *
     * <p>Similar to {@link Pair}, but saves boxing overhead of converting
     * {@code int} to {@link Integer}.
     */
    public static class ObjIntPair<T> {
        final T t;
        final int i;

        public ObjIntPair(T t, int i) {
            this.t = t;
            this.i = i;
        }

        public int hashCode() {
            return Util.hash(i, t);
        }

        public boolean equals(Object obj) {
            return this == obj
                || obj instanceof ObjIntPair
                && this.i == ((ObjIntPair) obj).i
                && Util.equals(this.t, ((ObjIntPair) obj).t);
        }

        public String toString() {
            return "<" + t + ", " + i + ">";
        }
    }
}

// End FunUtil.java
TOP

Related Classes of mondrian.olap.fun.FunUtil

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