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