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