001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2002-2005 Julian Hyde
008// Copyright (C) 2005-2011 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.AbstractListCalc;
015import mondrian.mdx.ResolvedFunCall;
016import mondrian.olap.*;
017
018import java.util.List;
019import java.util.Map;
020
021/**
022 * Definition of the <code>TopPercent</code>, <code>BottomPercent</code>,
023 * <code>TopSum</code> and <code>BottomSum</code> MDX builtin functions.
024 *
025 * @author jhyde
026 * @since Mar 23, 2006
027 */
028class TopBottomPercentSumFunDef extends FunDefBase {
029    /**
030     * Whether to calculate top (as opposed to bottom).
031     */
032    final boolean top;
033    /**
034     * Whether to calculate percent (as opposed to sum).
035     */
036    final boolean percent;
037
038    static final ResolverImpl TopPercentResolver =
039        new ResolverImpl(
040            "TopPercent",
041            "TopPercent(<Set>, <Percentage>, <Numeric Expression>)",
042            "Sorts a set and returns the top N elements whose cumulative total is at least a specified percentage.",
043            new String[]{"fxxnn"}, true, true);
044
045    static final ResolverImpl BottomPercentResolver =
046        new ResolverImpl(
047            "BottomPercent",
048            "BottomPercent(<Set>, <Percentage>, <Numeric Expression>)",
049            "Sorts a set and returns the bottom N elements whose cumulative total is at least a specified percentage.",
050            new String[]{"fxxnn"}, false, true);
051
052    static final ResolverImpl TopSumResolver =
053        new ResolverImpl(
054            "TopSum",
055            "TopSum(<Set>, <Value>, <Numeric Expression>)",
056            "Sorts a set and returns the top N elements whose cumulative total is at least a specified value.",
057            new String[]{"fxxnn"}, true, false);
058
059    static final ResolverImpl BottomSumResolver =
060        new ResolverImpl(
061            "BottomSum",
062            "BottomSum(<Set>, <Value>, <Numeric Expression>)",
063            "Sorts a set and returns the bottom N elements whose cumulative total is at least a specified value.",
064            new String[]{"fxxnn"}, false, false);
065
066    public TopBottomPercentSumFunDef(
067        FunDef dummyFunDef, boolean top, boolean percent)
068    {
069        super(dummyFunDef);
070        this.top = top;
071        this.percent = percent;
072    }
073
074    public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
075        final ListCalc listCalc =
076            (ListCalc) compiler.compileAs(
077                call.getArg(0),
078                null, ResultStyle.MUTABLELIST_ONLY);
079        final DoubleCalc doubleCalc = compiler.compileDouble(call.getArg(1));
080        final Calc calc = compiler.compileScalar(call.getArg(2), true);
081        return new CalcImpl(call, listCalc, doubleCalc, calc);
082    }
083
084    private static class ResolverImpl extends MultiResolver {
085        private final boolean top;
086        private final boolean percent;
087
088        public ResolverImpl(
089            final String name, final String signature,
090            final String description, final String[] signatures,
091            boolean top, boolean percent)
092        {
093            super(name, signature, description, signatures);
094            this.top = top;
095            this.percent = percent;
096        }
097
098        protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
099            return new TopBottomPercentSumFunDef(dummyFunDef, top, percent);
100        }
101    }
102
103    private class CalcImpl extends AbstractListCalc {
104        private final ListCalc listCalc;
105        private final DoubleCalc doubleCalc;
106        private final Calc calc;
107
108        public CalcImpl(
109            ResolvedFunCall call,
110            ListCalc listCalc,
111            DoubleCalc doubleCalc,
112            Calc calc)
113        {
114            super(call, new Calc[]{listCalc, doubleCalc, calc});
115            this.listCalc = listCalc;
116            this.doubleCalc = doubleCalc;
117            this.calc = calc;
118        }
119
120        public TupleList evaluateList(Evaluator evaluator) {
121            TupleList list = listCalc.evaluateList(evaluator);
122            double target = doubleCalc.evaluateDouble(evaluator);
123            if (list.isEmpty()) {
124                return list;
125            }
126            Map<List<Member>, Object> mapMemberToValue =
127                evaluateTuples(evaluator, calc, list);
128            final int savepoint = evaluator.savepoint();
129            try {
130                evaluator.setNonEmpty(false);
131                list = sortTuples(
132                    evaluator,
133                    list,
134                    list,
135                    calc,
136                    top,
137                    true,
138                    getType().getArity());
139            } finally {
140                evaluator.restore(savepoint);
141            }
142            if (percent) {
143                toPercent(list, mapMemberToValue);
144            }
145            double runningTotal = 0;
146            int memberCount = list.size();
147            int nullCount = 0;
148            for (int i = 0; i < memberCount; i++) {
149                if (runningTotal >= target) {
150                    list = list.subList(0, i);
151                    break;
152                }
153                final List<Member> key = list.get(i);
154                final Object o = mapMemberToValue.get(key);
155                if (o == Util.nullValue) {
156                    nullCount++;
157                } else if (o instanceof Number) {
158                    runningTotal += ((Number) o).doubleValue();
159                } else if (o instanceof Exception) {
160                    // ignore the error
161                } else {
162                    throw Util.newInternal(
163                        "got " + o + " when expecting Number");
164                }
165            }
166
167            // MSAS exhibits the following behavior. If the value of all members
168            // is null, then the first (or last) member of the set is returned
169            // for percent operations.
170            if (memberCount > 0 && percent && nullCount == memberCount) {
171                return top
172                    ? list.subList(0, 1)
173                    : list.subList(memberCount - 1, memberCount);
174            }
175            return list;
176        }
177
178        public boolean dependsOn(Hierarchy hierarchy) {
179            return anyDependsButFirst(getCalcs(), hierarchy);
180        }
181    }
182}
183
184// End TopBottomPercentSumFunDef.java