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.calc.impl.UnaryTupleList; 016import mondrian.mdx.*; 017import mondrian.olap.*; 018import mondrian.olap.type.*; 019import mondrian.resource.MondrianResource; 020 021import java.util.*; 022 023/** 024 * Definition of the <code>Descendants</code> MDX function. 025 * 026 * @author jhyde 027 * @since Mar 23, 2006 028 */ 029class DescendantsFunDef extends FunDefBase { 030 031 static final ReflectiveMultiResolver Resolver = 032 new ReflectiveMultiResolver( 033 "Descendants", 034 "Descendants(<Member>[, <Level>[, <Desc_flag>]])", 035 "Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels.", 036 new String[]{"fxm", "fxml", "fxmly", "fxmn", "fxmny", "fxmey"}, 037 DescendantsFunDef.class, 038 Flag.getNames()); 039 040 static final ReflectiveMultiResolver Resolver2 = 041 new ReflectiveMultiResolver( 042 "Descendants", 043 "Descendants(<Set>[, <Level>[, <Desc_flag>]])", 044 "Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels.", 045 new String[]{"fxx", "fxxl", "fxxly", "fxxn", "fxxny", "fxxey"}, 046 DescendantsFunDef.class, 047 Flag.getNames()); 048 049 public DescendantsFunDef(FunDef dummyFunDef) { 050 super(dummyFunDef); 051 } 052 053 public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { 054 final Type type0 = call.getArg(0).getType(); 055 if (type0 instanceof SetType) { 056 final SetType setType = (SetType) type0; 057 if (setType.getElementType() instanceof TupleType) { 058 throw MondrianResource.instance() 059 .DescendantsAppliedToSetOfTuples.ex(); 060 } 061 062 MemberType memberType = (MemberType) setType.getElementType(); 063 final Hierarchy hierarchy = memberType.getHierarchy(); 064 if (hierarchy == null) { 065 throw MondrianResource.instance().CannotDeduceTypeOfSet.ex(); 066 } 067 // Convert 068 // Descendants(<set>, <args>) 069 // into 070 // Generate(<set>, Descendants(<dimension>.CurrentMember, <args>)) 071 Exp[] descendantsArgs = call.getArgs().clone(); 072 descendantsArgs[0] = 073 new UnresolvedFunCall( 074 "CurrentMember", 075 Syntax.Property, 076 new Exp[] { 077 new HierarchyExpr(hierarchy) 078 }); 079 final ResolvedFunCall generateCall = 080 (ResolvedFunCall) compiler.getValidator().validate( 081 new UnresolvedFunCall( 082 "Generate", 083 new Exp[] { 084 call.getArg(0), 085 new UnresolvedFunCall( 086 "Descendants", 087 descendantsArgs) 088 }), 089 false); 090 return generateCall.accept(compiler); 091 } 092 093 final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); 094 Flag flag = Flag.SELF; 095 if (call.getArgCount() == 1) { 096 flag = Flag.SELF_BEFORE_AFTER; 097 } 098 final boolean depthSpecified = 099 call.getArgCount() >= 2 100 && call.getArg(1).getType() instanceof NumericType; 101 final boolean depthEmpty = 102 call.getArgCount() >= 2 103 && call.getArg(1).getType() instanceof EmptyType; 104 if (call.getArgCount() >= 3) { 105 flag = FunUtil.getLiteralArg(call, 2, Flag.SELF, Flag.class); 106 } 107 108 if (call.getArgCount() >= 2 && depthEmpty) { 109 if (flag != Flag.LEAVES) { 110 throw Util.newError( 111 "depth must be specified unless DESC_FLAG is LEAVES"); 112 } 113 } 114 if ((depthSpecified || depthEmpty) && flag.leaves) { 115 final IntegerCalc depthCalc = 116 depthSpecified 117 ? compiler.compileInteger(call.getArg(1)) 118 : null; 119 return new AbstractListCalc( 120 call, new Calc[] {memberCalc, depthCalc}) 121 { 122 public TupleList evaluateList(Evaluator evaluator) { 123 final Member member = memberCalc.evaluateMember(evaluator); 124 List<Member> result = new ArrayList<Member>(); 125 int depth = -1; 126 if (depthCalc != null) { 127 depth = depthCalc.evaluateInteger(evaluator); 128 if (depth < 0) { 129 depth = -1; // no limit 130 } 131 } 132 final SchemaReader schemaReader = 133 evaluator.getSchemaReader(); 134 descendantsLeavesByDepth( 135 member, result, schemaReader, depth); 136 hierarchizeMemberList(result, false); 137 return new UnaryTupleList(result); 138 } 139 }; 140 } else if (depthSpecified) { 141 final IntegerCalc depthCalc = 142 compiler.compileInteger(call.getArg(1)); 143 final Flag flag1 = flag; 144 return new AbstractListCalc( 145 call, new Calc[] {memberCalc, depthCalc}) 146 { 147 public TupleList evaluateList(Evaluator evaluator) { 148 final Member member = memberCalc.evaluateMember(evaluator); 149 List<Member> result = new ArrayList<Member>(); 150 final int depth = depthCalc.evaluateInteger(evaluator); 151 final SchemaReader schemaReader = 152 evaluator.getSchemaReader(); 153 descendantsByDepth( 154 member, result, schemaReader, 155 depth, flag1.before, flag1.self, flag1.after, 156 evaluator); 157 hierarchizeMemberList(result, false); 158 return new UnaryTupleList(result); 159 } 160 }; 161 } else { 162 final LevelCalc levelCalc = 163 call.getArgCount() > 1 164 ? compiler.compileLevel(call.getArg(1)) 165 : null; 166 final Flag flag2 = flag; 167 return new AbstractListCalc( 168 call, new Calc[] {memberCalc, levelCalc}) 169 { 170 public TupleList evaluateList(Evaluator evaluator) { 171 final Evaluator context = 172 evaluator.isNonEmpty() ? evaluator : null; 173 final Member member = memberCalc.evaluateMember(evaluator); 174 List<Member> result = new ArrayList<Member>(); 175 final SchemaReader schemaReader = 176 evaluator.getSchemaReader(); 177 final Level level = 178 levelCalc != null 179 ? levelCalc.evaluateLevel(evaluator) 180 : member.getLevel(); 181 descendantsByLevel( 182 schemaReader, member, level, result, 183 flag2.before, flag2.self, 184 flag2.after, flag2.leaves, context); 185 hierarchizeMemberList(result, false); 186 return new UnaryTupleList(result); 187 } 188 }; 189 } 190 } 191 192 private static void descendantsByDepth( 193 Member member, 194 List<Member> result, 195 final SchemaReader schemaReader, 196 final int depthLimitFinal, 197 final boolean before, 198 final boolean self, 199 final boolean after, 200 final Evaluator context) 201 { 202 List<Member> children = new ArrayList<Member>(); 203 children.add(member); 204 for (int depth = 0;; ++depth) { 205 if (depth == depthLimitFinal) { 206 if (self) { 207 result.addAll(children); 208 } 209 if (!after) { 210 break; // no more results after this level 211 } 212 } else if (depth < depthLimitFinal) { 213 if (before) { 214 result.addAll(children); 215 } 216 } else { 217 if (after) { 218 result.addAll(children); 219 } else { 220 break; // no more results after this level 221 } 222 } 223 224 children = schemaReader.getMemberChildren(children, context); 225 if (children.size() == 0) { 226 break; 227 } 228 } 229 } 230 231 /** 232 * Populates 'result' with the descendants at the leaf level at depth 233 * 'depthLimit' or less. If 'depthLimit' is -1, does not apply a depth 234 * constraint. 235 */ 236 private static void descendantsLeavesByDepth( 237 final Member member, 238 final List<Member> result, 239 final SchemaReader schemaReader, 240 final int depthLimit) 241 { 242 if (!schemaReader.isDrillable(member)) { 243 if (depthLimit >= 0) { 244 result.add(member); 245 } 246 return; 247 } 248 List<Member> children = new ArrayList<Member>(); 249 children.add(member); 250 for (int depth = 0; depthLimit == -1 || depth <= depthLimit; ++depth) { 251 children = schemaReader.getMemberChildren(children); 252 if (children.size() == 0) { 253 throw Util.newInternal("drillable member must have children"); 254 } 255 List<Member> nextChildren = new ArrayList<Member>(); 256 for (Member child : children) { 257 // TODO: Implement this more efficiently. The current 258 // implementation of isDrillable for a parent-child hierarchy 259 // simply retrieves the children sees whether there are any, 260 // so we end up fetching each member's children twice. 261 if (schemaReader.isDrillable(child)) { 262 nextChildren.add(child); 263 } else { 264 result.add(child); 265 } 266 } 267 if (nextChildren.isEmpty()) { 268 return; 269 } 270 children = nextChildren; 271 } 272 } 273 274 /** 275 * Finds all descendants of a member which are before/at/after a level, 276 * and/or are leaves (have no descendants) and adds them to a result list. 277 * 278 * @param schemaReader Member reader 279 * @param ancestor Member to find descendants of 280 * @param level Level relative to which to filter, must not be null 281 * @param result Result list 282 * @param before Whether to output members above <code>level</code> 283 * @param self Whether to output members at <code>level</code> 284 * @param after Whether to output members below <code>level</code> 285 * @param leaves Whether to output members which are leaves 286 * @param context Evaluation context; determines criteria by which the 287 * result set should be filtered 288 */ 289 static void descendantsByLevel( 290 SchemaReader schemaReader, 291 Member ancestor, 292 Level level, 293 List<Member> result, 294 boolean before, 295 boolean self, 296 boolean after, 297 boolean leaves, 298 Evaluator context) 299 { 300 // We find the descendants of a member by making breadth-first passes 301 // down the hierarchy. Initially the list just contains the ancestor. 302 // Then we find its children. We add those children to the result if 303 // they fulfill the before/self/after conditions relative to the level. 304 // 305 // We add a child to the "fertileMembers" list if some of its children 306 // might be in the result. Again, this depends upon the 307 // before/self/after conditions. 308 // 309 // Note that for some member readers -- notably the 310 // RestrictedMemberReader, when it is reading a ragged hierarchy -- the 311 // children of a member do not always belong to the same level. For 312 // example, the children of USA include WA (a state) and Washington 313 // (a city). This is why we repeat the before/self/after logic for 314 // each member. 315 final int levelDepth = level.getDepth(); 316 List<Member> members = Collections.singletonList(ancestor); 317 // Each pass, "fertileMembers" has the same contents as "members", 318 // except that we omit members whose children we are not interested 319 // in. We allocate it once, and clear it each pass, to save a little 320 // memory allocation. 321 if (leaves) { 322 assert !before && !self && !after; 323 do { 324 List<Member> nextMembers = new ArrayList<Member>(); 325 for (Member member : members) { 326 final int currentDepth = member.getLevel().getDepth(); 327 List<Member> childMembers = 328 schemaReader.getMemberChildren(member, context); 329 if (childMembers.size() == 0) { 330 // this member is a leaf -- add it 331 if (currentDepth == levelDepth) { 332 result.add(member); 333 } 334 } else { 335 // this member is not a leaf -- add its children 336 // to the list to be considered next iteration 337 if (currentDepth <= levelDepth) { 338 nextMembers.addAll(childMembers); 339 } 340 } 341 } 342 members = nextMembers; 343 } while (members.size() > 0); 344 } else { 345 List<Member> fertileMembers = new ArrayList<Member>(); 346 do { 347 fertileMembers.clear(); 348 for (Member member : members) { 349 final int currentDepth = member.getLevel().getDepth(); 350 if (currentDepth == levelDepth) { 351 if (self) { 352 result.add(member); 353 } 354 if (after) { 355 // we are interested in member's children 356 fertileMembers.add(member); 357 } 358 } else if (currentDepth < levelDepth) { 359 if (before) { 360 result.add(member); 361 } 362 fertileMembers.add(member); 363 } else { 364 if (after) { 365 result.add(member); 366 fertileMembers.add(member); 367 } 368 } 369 } 370 members = 371 schemaReader.getMemberChildren(fertileMembers, context); 372 } while (members.size() > 0); 373 } 374 } 375 376 /** 377 * Enumeration of the flags allowed to the <code>DESCENDANTS</code> 378 * function. 379 */ 380 enum Flag { 381 SELF(true, false, false, false), 382 AFTER(false, true, false, false), 383 BEFORE(false, false, true, false), 384 BEFORE_AND_AFTER(false, true, true, false), 385 SELF_AND_AFTER(true, true, false, false), 386 SELF_AND_BEFORE(true, false, true, false), 387 SELF_BEFORE_AFTER(true, true, true, false), 388 LEAVES(false, false, false, true); 389 390 private final boolean self; 391 private final boolean after; 392 private final boolean before; 393 private final boolean leaves; 394 395 Flag(boolean self, boolean after, boolean before, boolean leaves) { 396 this.self = self; 397 this.after = after; 398 this.before = before; 399 this.leaves = leaves; 400 } 401 402 public static String[] getNames() { 403 List<String> names = new ArrayList<String>(); 404 for (Flag flags : Flag.class.getEnumConstants()) { 405 names.add(flags.name()); 406 } 407 return names.toArray(new String[names.size()]); 408 } 409 } 410} 411 412// End DescendantsFunDef.java