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