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