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) 2006-2012 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.olap.fun; 011 012import mondrian.calc.*; 013import mondrian.calc.impl.AbstractListCalc; 014import mondrian.calc.impl.UnaryTupleList; 015import mondrian.mdx.*; 016import mondrian.olap.*; 017import mondrian.olap.type.*; 018import mondrian.resource.MondrianResource; 019import mondrian.rolap.*; 020 021import java.util.ArrayList; 022import java.util.List; 023 024/** 025 * Definition of the <code>VisualTotals</code> MDX function. 026 * 027 * @author jhyde 028 * @since Jan 16, 2006 029 */ 030public class VisualTotalsFunDef extends FunDefBase { 031 static final Resolver Resolver = 032 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 { 046 final Exp validatedArg = 047 super.validateArg(validator, args, i, category); 048 if (i == 0) { 049 // The function signature guarantees that we have a set of members 050 // or a set of tuples. 051 final SetType setType = (SetType) validatedArg.getType(); 052 final Type elementType = setType.getElementType(); 053 if (!(elementType instanceof MemberType)) { 054 throw MondrianResource.instance().VisualTotalsAppliedToTuples 055 .ex(); 056 } 057 } 058 return validatedArg; 059 } 060 061 public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { 062 final ListCalc listCalc = compiler.compileList(call.getArg(0)); 063 final StringCalc stringCalc = 064 call.getArgCount() > 1 065 ? compiler.compileString(call.getArg(1)) 066 : null; 067 return new CalcImpl(call, listCalc, stringCalc); 068 } 069 070 /** 071 * Calc implementation of the <code>VisualTotals</code> function. 072 */ 073 private static class CalcImpl extends AbstractListCalc { 074 private final ListCalc listCalc; 075 private final StringCalc stringCalc; 076 077 public CalcImpl( 078 ResolvedFunCall call, ListCalc listCalc, StringCalc stringCalc) 079 { 080 super(call, new Calc[] {listCalc, stringCalc}); 081 this.listCalc = listCalc; 082 this.stringCalc = stringCalc; 083 } 084 085 public TupleList evaluateList(Evaluator evaluator) { 086 final List<Member> list = 087 listCalc.evaluateList(evaluator).slice(0); 088 final List<Member> resultList = new ArrayList<Member>(list); 089 final int memberCount = list.size(); 090 for (int i = memberCount - 1; i >= 0; --i) { 091 Member member = list.get(i); 092 if (i + 1 < memberCount) { 093 Member nextMember = resultList.get(i + 1); 094 if (nextMember != member 095 && nextMember.isChildOrEqualTo(member)) 096 { 097 resultList.set( 098 i, 099 createMember(member, i, resultList, evaluator)); 100 } 101 } 102 } 103 return new UnaryTupleList(resultList); 104 } 105 106 private VisualTotalMember createMember( 107 Member member, 108 int i, 109 final List<Member> list, 110 Evaluator evaluator) 111 { 112 final String name; 113 final String caption; 114 if (stringCalc != null) { 115 final String namePattern = stringCalc.evaluateString(evaluator); 116 name = substitute(namePattern, member.getName()); 117 caption = name; 118 } else { 119 name = member.getName(); 120 caption = member.getCaption(); 121 } 122 final List<Member> childMemberList = 123 followingDescendants(member, i + 1, list); 124 final Exp exp = makeExpr(childMemberList); 125 final Validator validator = evaluator.getQuery().createValidator(); 126 final Exp validatedExp = exp.accept(validator); 127 return new VisualTotalMember(member, name, caption, validatedExp); 128 } 129 130 private List<Member> followingDescendants( 131 Member member, int i, final List<Member> list) 132 { 133 List<Member> childMemberList = new ArrayList<Member>(); 134 while (i < list.size()) { 135 Member descendant = list.get(i); 136 if (descendant.equals(member)) { 137 // strict descendants only 138 break; 139 } 140 if (!descendant.isChildOrEqualTo(member)) { 141 break; 142 } 143 if (descendant instanceof VisualTotalMember) { 144 // Add the visual total member, but skip over its children. 145 VisualTotalMember visualTotalMember = 146 (VisualTotalMember) descendant; 147 childMemberList.add(visualTotalMember); 148 i = lastChildIndex(visualTotalMember.member, i, list); 149 continue; 150 } 151 childMemberList.add(descendant); 152 ++i; 153 } 154 return childMemberList; 155 } 156 157 private int lastChildIndex(Member member, int start, List list) { 158 int i = start; 159 while (true) { 160 ++i; 161 if (i >= list.size()) { 162 break; 163 } 164 Member descendant = (Member) list.get(i); 165 if (descendant.equals(member)) { 166 // strict descendants only 167 break; 168 } 169 if (!descendant.isChildOrEqualTo(member)) { 170 break; 171 } 172 } 173 return i; 174 } 175 176 private Exp makeExpr(final List childMemberList) { 177 Exp[] memberExprs = new Exp[childMemberList.size()]; 178 for (int i = 0; i < childMemberList.size(); i++) { 179 final Member childMember = (Member) childMemberList.get(i); 180 memberExprs[i] = new MemberExpr(childMember); 181 } 182 return new UnresolvedFunCall( 183 "Aggregate", 184 new Exp[] { 185 new UnresolvedFunCall( 186 "{}", 187 Syntax.Braces, 188 memberExprs) 189 }); 190 } 191 } 192 193 /** 194 * Calculated member for <code>VisualTotals</code> function. 195 * 196 * <p>It corresponds to a real member, and most of its properties are 197 * similar. The main differences are:<ul> 198 * <li>its name is derived from the VisualTotals pattern, e.g. 199 * "*Subtotal - Dairy" as opposed to "Dairy" 200 * <li>its value is a calculation computed by aggregating all of the 201 * members which occur following it in the list</ul></p> 202 */ 203 public static class VisualTotalMember extends RolapMemberBase { 204 private final Member member; 205 private Exp exp; 206 private String caption; 207 208 VisualTotalMember( 209 Member member, 210 String name, 211 String caption, 212 final Exp exp) 213 { 214 super( 215 (RolapMember) member.getParentMember(), 216 (RolapLevel) member.getLevel(), 217 RolapUtil.sqlNullValue, name, MemberType.FORMULA); 218 this.member = member; 219 this.caption = caption; 220 this.exp = exp; 221 } 222 223 @Override 224 public boolean equals(Object o) { 225 // A visual total member must compare equal to the member it wraps 226 // (for purposes of the MDX Intersect function, for instance). 227 return o instanceof VisualTotalMember 228 && this.member.equals(((VisualTotalMember) o).member) 229 && this.exp.equals(((VisualTotalMember) o).exp) 230 || o instanceof Member 231 && this.member.equals(o); 232 } 233 234 @Override 235 public int compareTo(Object o) { 236 if (o instanceof VisualTotalMember) { 237 // VisualTotals members are a special case. We have 238 // to compare the delegate member. 239 return this.getMember().compareTo( 240 ((VisualTotalMember) o).getMember()); 241 } else { 242 return super.compareTo(o); 243 } 244 } 245 246 @Override 247 public int hashCode() { 248 return member.hashCode(); 249 } 250 251 @Override 252 public String getCaption() { 253 return caption; 254 } 255 256 protected boolean computeCalculated(final MemberType memberType) { 257 return true; 258 } 259 260 public int getSolveOrder() { 261 // high solve order, so it is expanded after other calculations 262 // REVIEW: 99...really?? I've seen many queries with higher SO. 263 // I don't think we should be abusing arbitrary constants 264 // like this. 265 return 99; 266 } 267 268 public Exp getExpression() { 269 return exp; 270 } 271 272 public void setExpression(Exp exp) { 273 this.exp = exp; 274 } 275 276 public void setExpression( 277 Evaluator evaluator, 278 List<Member> childMembers) 279 { 280 final Exp exp = makeExpr(childMembers); 281 final Validator validator = evaluator.getQuery().createValidator(); 282 final Exp validatedExp = exp.accept(validator); 283 setExpression(validatedExp); 284 } 285 286 private Exp makeExpr(final List childMemberList) { 287 Exp[] memberExprs = new Exp[childMemberList.size()]; 288 for (int i = 0; i < childMemberList.size(); i++) { 289 final Member childMember = (Member) childMemberList.get(i); 290 memberExprs[i] = new MemberExpr(childMember); 291 } 292 return new UnresolvedFunCall( 293 "Aggregate", 294 new Exp[] { 295 new UnresolvedFunCall( 296 "{}", 297 Syntax.Braces, 298 memberExprs) 299 }); 300 } 301 302 public int getOrdinal() { 303 return member.getOrdinal(); 304 } 305 306 public Member getDataMember() { 307 return member; 308 } 309 310 public String getQualifiedName() { 311 throw new UnsupportedOperationException(); 312 } 313 314 public Member getMember() { 315 return member; 316 } 317 318 public Object getPropertyValue(String propertyName, boolean matchCase) { 319 Property property = Property.lookup(propertyName, matchCase); 320 if (property == null) { 321 return null; 322 } 323 switch (property.ordinal) { 324 case Property.CHILDREN_CARDINALITY_ORDINAL: 325 return member.getPropertyValue(propertyName, matchCase); 326 default: 327 return super.getPropertyValue(propertyName, matchCase); 328 } 329 } 330 } 331 332 /** 333 * Substitutes a name into a pattern.<p/> 334 * 335 * Asterisks are replaced with the name, 336 * double-asterisks are replaced with a single asterisk. 337 * For example, 338 * 339 * <blockquote><code>substitute("** Subtotal - *", 340 * "Dairy")</code></blockquote> 341 * 342 * returns 343 * 344 * <blockquote><code>"* Subtotal - Dairy"</code></blockquote> 345 * 346 * @param namePattern Pattern 347 * @param name Name to substitute into pattern 348 * @return Substituted pattern 349 */ 350 static String substitute(String namePattern, String name) { 351 final StringBuilder buf = new StringBuilder(256); 352 final int namePatternLen = namePattern.length(); 353 int startIndex = 0; 354 355 while (true) { 356 int endIndex = namePattern.indexOf('*', startIndex); 357 358 if (endIndex == -1) { 359 // No '*' left 360 // append the rest of namePattern from startIndex onwards 361 buf.append(namePattern.substring(startIndex)); 362 break; 363 } 364 365 // endIndex now points to the '*'; check for '**' 366 ++endIndex; 367 if (endIndex < namePatternLen 368 && namePattern.charAt(endIndex) == '*') 369 { 370 // Found '**', replace with '*' 371 // Include first '*'. 372 buf.append(namePattern.substring(startIndex, endIndex)); 373 // Skip over 2nd '*' 374 ++endIndex; 375 } else { 376 // Found single '*' - substitute (omitting the '*') 377 // Exclude '*' 378 buf.append(namePattern.substring(startIndex, endIndex - 1)); 379 buf.append(name); 380 } 381 382 startIndex = endIndex; 383 } 384 385 return buf.toString(); 386 } 387 388} 389 390// End VisualTotalsFunDef.java