001 /* 002 // $Id: //open/mondrian/src/main/mondrian/olap/fun/VisualTotalsFunDef.java#12 $ 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) 2006-2007 Julian Hyde 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.olap.fun; 011 012 import mondrian.calc.*; 013 import mondrian.calc.impl.AbstractListCalc; 014 import mondrian.mdx.*; 015 import mondrian.olap.*; 016 import mondrian.olap.type.*; 017 import mondrian.rolap.RolapLevel; 018 import mondrian.rolap.RolapMember; 019 import mondrian.resource.MondrianResource; 020 021 import java.util.ArrayList; 022 import java.util.List; 023 024 /** 025 * Definition of the <code>VisualTotals</code> MDX function. 026 * 027 * @author jhyde 028 * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/VisualTotalsFunDef.java#12 $ 029 * @since Jan 16, 2006 030 */ 031 public class VisualTotalsFunDef extends FunDefBase { 032 static final Resolver Resolver = new ReflectiveMultiResolver( 033 "VisualTotals", 034 "VisualTotals(<Set>[, <Pattern>])", 035 "Dynamically totals child members specified in a set using a pattern for the total label in the result set.", 036 new String[] {"fxx", "fxxS"}, 037 VisualTotalsFunDef.class); 038 039 public VisualTotalsFunDef(FunDef dummyFunDef) { 040 super(dummyFunDef); 041 } 042 043 protected Exp validateArg( 044 Validator validator, Exp[] args, int i, int category) { 045 final Exp validatedArg = super.validateArg(validator, args, i, category); 046 if (i == 0) { 047 // The function signature guarantees that we have a set of members 048 // or a set of tuples. 049 final SetType setType = (SetType) validatedArg.getType(); 050 final Type elementType = setType.getElementType(); 051 if (!(elementType instanceof MemberType)) { 052 throw MondrianResource.instance(). 053 VisualTotalsAppliedToTuples.ex(); 054 } 055 } 056 return validatedArg; 057 } 058 059 public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { 060 final ListCalc listCalc = compiler.compileList(call.getArg(0)); 061 final StringCalc stringCalc = call.getArgCount() > 1 ? 062 compiler.compileString(call.getArg(1)) : 063 null; 064 return new CalcImpl(call, listCalc, stringCalc); 065 } 066 067 /** 068 * Calc implementation of the <code>VisualTotals</code> function. 069 */ 070 private static class CalcImpl extends AbstractListCalc { 071 private final ListCalc listCalc; 072 private final StringCalc stringCalc; 073 074 public CalcImpl( 075 ResolvedFunCall call, ListCalc listCalc, StringCalc stringCalc) { 076 super(call, new Calc[] {listCalc, stringCalc}); 077 this.listCalc = listCalc; 078 this.stringCalc = stringCalc; 079 } 080 081 public List evaluateList(Evaluator evaluator) { 082 final List<Member> list = listCalc.evaluateList(evaluator); 083 final List<Member> resultList = new ArrayList<Member>(list); 084 final int memberCount = list.size(); 085 for (int i = memberCount - 1; i >= 0; --i) { 086 Member member = list.get(i); 087 if (i + 1 < memberCount) { 088 Member nextMember = resultList.get(i + 1); 089 if (nextMember != member && 090 nextMember.isChildOrEqualTo(member)) { 091 resultList.set( 092 i, 093 createMember(member, i, resultList, evaluator)); 094 } 095 } 096 } 097 return resultList; 098 } 099 100 private VisualTotalMember createMember( 101 Member member, 102 int i, 103 final List<Member> list, 104 Evaluator evaluator) 105 { 106 final String name; 107 if (stringCalc != null) { 108 final String namePattern = stringCalc.evaluateString(evaluator); 109 name = substitute(namePattern, member.getName()); 110 } else { 111 name = member.getName(); 112 } 113 final List<Member> childMemberList = 114 followingDescendants(member, i + 1, list); 115 final Exp exp = makeExpr(childMemberList); 116 final Validator validator = evaluator.getQuery().createValidator(); 117 final Exp validatedExp = exp.accept(validator); 118 return new VisualTotalMember(member, name, validatedExp); 119 } 120 121 private List<Member> followingDescendants( 122 Member member, int i, final List<Member> list) 123 { 124 List<Member> childMemberList = new ArrayList<Member>(); 125 while (i < list.size()) { 126 Member descendant = list.get(i); 127 if (descendant.equals(member)) { 128 // strict descendants only 129 break; 130 } 131 if (!descendant.isChildOrEqualTo(member)) { 132 break; 133 } 134 if (descendant instanceof VisualTotalMember) { 135 // Add the visual total member, but skip over its children. 136 VisualTotalMember visualTotalMember = 137 (VisualTotalMember) descendant; 138 childMemberList.add(visualTotalMember); 139 i = lastChildIndex(visualTotalMember.member, i, list); 140 continue; 141 } 142 childMemberList.add(descendant); 143 ++i; 144 } 145 return childMemberList; 146 } 147 148 private int lastChildIndex(Member member, int start, List list) { 149 int i = start; 150 while (true) { 151 ++i; 152 if (i >= list.size()) { 153 break; 154 } 155 Member descendant = (Member) list.get(i); 156 if (descendant.equals(member)) { 157 // strict descendants only 158 break; 159 } 160 if (!descendant.isChildOrEqualTo(member)) { 161 break; 162 } 163 } 164 return i; 165 } 166 167 private Exp makeExpr(final List childMemberList) { 168 Exp[] memberExprs = new Exp[childMemberList.size()]; 169 for (int i = 0; i < childMemberList.size(); i++) { 170 final Member childMember = (Member) childMemberList.get(i); 171 memberExprs[i] = new MemberExpr(childMember); 172 } 173 return new UnresolvedFunCall( 174 "Aggregate", 175 new Exp[] { 176 new UnresolvedFunCall( 177 "{}", 178 Syntax.Braces, 179 memberExprs) 180 }); 181 } 182 } 183 184 /** 185 * Calculated member for <code>VisualTotals</code> function. 186 * 187 * It corresponds to a real member, and most of its properties are similar. 188 * The main differences are:<ul> 189 * <li>its name is derived from the VisualTotals pattern, e.g. 190 * "*Subtotal - Dairy" as opposed to "Dairy" 191 * <li>its value is a calculation computed by aggregating all of the 192 * members which occur following it in the list</ul></p> 193 */ 194 private static class VisualTotalMember extends RolapMember { 195 private final Member member; 196 private final Exp exp; 197 198 VisualTotalMember( 199 Member member, 200 String name, 201 final Exp exp) { 202 super( 203 (RolapMember) member.getParentMember(), 204 (RolapLevel) member.getLevel(), 205 null, name, MemberType.FORMULA); 206 this.member = member; 207 this.exp = exp; 208 } 209 210 protected boolean computeCalculated(final MemberType memberType) { 211 return true; 212 } 213 214 public int getSolveOrder() { 215 // high solve order, so it is expanded after other calculations 216 return 99; 217 } 218 219 public Exp getExpression() { 220 return exp; 221 } 222 223 public int getOrdinal() { 224 throw new UnsupportedOperationException(); 225 } 226 227 public Member getDataMember() { 228 return member; 229 } 230 231 public OlapElement lookupChild(SchemaReader schemaReader, String s) { 232 throw new UnsupportedOperationException(); 233 } 234 235 public OlapElement lookupChild( 236 SchemaReader schemaReader, String s, MatchType matchType) { 237 throw new UnsupportedOperationException(); 238 } 239 240 public String getQualifiedName() { 241 throw new UnsupportedOperationException(); 242 } 243 } 244 245 /** 246 * Substitutes a name into a pattern.<p/> 247 * 248 * Asterisks are replaced with the name, 249 * double-asterisks are replaced with a single asterisk. 250 * For example, 251 * <blockquote><code>substitute("** Subtotal - *", "Dairy")</code></blockquote> 252 * returns 253 * <blockquote><code>"* Subtotal - Dairy"</code></blockquote> 254 * 255 * @param namePattern Pattern 256 * @param name Name to substitute into pattern 257 * @return Substituted pattern 258 */ 259 static String substitute(String namePattern, String name) { 260 final StringBuilder buf = new StringBuilder(256); 261 final int namePatternLen = namePattern.length(); 262 int startIndex = 0; 263 264 while (true) { 265 int endIndex = namePattern.indexOf('*', startIndex); 266 267 if (endIndex == -1) { 268 // No '*' left 269 // append the rest of namePattern from startIndex onwards 270 buf.append(namePattern.substring(startIndex)); 271 break; 272 } 273 274 // endIndex now points to the '*'; check for '**' 275 ++endIndex; 276 if (endIndex < namePatternLen 277 && namePattern.charAt(endIndex) == '*') 278 { 279 // Found '**', replace with '*' 280 // Include first '*'. 281 buf.append(namePattern.substring(startIndex, endIndex)); 282 // Skip over 2nd '*' 283 ++endIndex; 284 } else { 285 // Found single '*' - substitute (omitting the '*') 286 // Exclude '*' 287 buf.append(namePattern.substring(startIndex, endIndex - 1)); 288 buf.append(name); 289 } 290 291 startIndex = endIndex; 292 } 293 294 return buf.toString(); 295 } 296 297 } 298 299 // End VisualTotalsFunDef.java