/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2002-2005 Julian Hyde
// Copyright (C) 2005-2011 Pentaho and others
// All Rights Reserved.
*/
package mondrian.olap.fun;
import mondrian.calc.*;
import mondrian.calc.impl.AbstractListCalc;
import mondrian.calc.impl.UnaryTupleList;
import mondrian.mdx.*;
import mondrian.olap.*;
import mondrian.olap.type.*;
import mondrian.resource.MondrianResource;
import java.util.*;
/**
* Definition of the <code>Descendants</code> MDX function.
*
* @author jhyde
* @since Mar 23, 2006
*/
class DescendantsFunDef extends FunDefBase {
static final ReflectiveMultiResolver Resolver =
new ReflectiveMultiResolver(
"Descendants",
"Descendants(<Member>[, <Level>[, <Desc_flag>]])",
"Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels.",
new String[]{"fxm", "fxml", "fxmly", "fxmn", "fxmny", "fxmey"},
DescendantsFunDef.class,
Flag.getNames());
static final ReflectiveMultiResolver Resolver2 =
new ReflectiveMultiResolver(
"Descendants",
"Descendants(<Set>[, <Level>[, <Desc_flag>]])",
"Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels.",
new String[]{"fxx", "fxxl", "fxxly", "fxxn", "fxxny", "fxxey"},
DescendantsFunDef.class,
Flag.getNames());
public DescendantsFunDef(FunDef dummyFunDef) {
super(dummyFunDef);
}
public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
final Type type0 = call.getArg(0).getType();
if (type0 instanceof SetType) {
final SetType setType = (SetType) type0;
if (setType.getElementType() instanceof TupleType) {
throw MondrianResource.instance()
.DescendantsAppliedToSetOfTuples.ex();
}
MemberType memberType = (MemberType) setType.getElementType();
final Hierarchy hierarchy = memberType.getHierarchy();
if (hierarchy == null) {
throw MondrianResource.instance().CannotDeduceTypeOfSet.ex();
}
// Convert
// Descendants(<set>, <args>)
// into
// Generate(<set>, Descendants(<dimension>.CurrentMember, <args>))
Exp[] descendantsArgs = call.getArgs().clone();
descendantsArgs[0] =
new UnresolvedFunCall(
"CurrentMember",
Syntax.Property,
new Exp[] {
new HierarchyExpr(hierarchy)
});
final ResolvedFunCall generateCall =
(ResolvedFunCall) compiler.getValidator().validate(
new UnresolvedFunCall(
"Generate",
new Exp[] {
call.getArg(0),
new UnresolvedFunCall(
"Descendants",
descendantsArgs)
}),
false);
return generateCall.accept(compiler);
}
final MemberCalc memberCalc = compiler.compileMember(call.getArg(0));
Flag flag = Flag.SELF;
if (call.getArgCount() == 1) {
flag = Flag.SELF_BEFORE_AFTER;
}
final boolean depthSpecified =
call.getArgCount() >= 2
&& call.getArg(1).getType() instanceof NumericType;
final boolean depthEmpty =
call.getArgCount() >= 2
&& call.getArg(1).getType() instanceof EmptyType;
if (call.getArgCount() >= 3) {
flag = FunUtil.getLiteralArg(call, 2, Flag.SELF, Flag.class);
}
if (call.getArgCount() >= 2 && depthEmpty) {
if (flag != Flag.LEAVES) {
throw Util.newError(
"depth must be specified unless DESC_FLAG is LEAVES");
}
}
if ((depthSpecified || depthEmpty) && flag.leaves) {
final IntegerCalc depthCalc =
depthSpecified
? compiler.compileInteger(call.getArg(1))
: null;
return new AbstractListCalc(
call, new Calc[] {memberCalc, depthCalc})
{
public TupleList evaluateList(Evaluator evaluator) {
final Member member = memberCalc.evaluateMember(evaluator);
List<Member> result = new ArrayList<Member>();
int depth = -1;
if (depthCalc != null) {
depth = depthCalc.evaluateInteger(evaluator);
if (depth < 0) {
depth = -1; // no limit
}
}
final SchemaReader schemaReader =
evaluator.getSchemaReader();
descendantsLeavesByDepth(
member, result, schemaReader, depth);
hierarchizeMemberList(result, false);
return new UnaryTupleList(result);
}
};
} else if (depthSpecified) {
final IntegerCalc depthCalc =
compiler.compileInteger(call.getArg(1));
final Flag flag1 = flag;
return new AbstractListCalc(
call, new Calc[] {memberCalc, depthCalc})
{
public TupleList evaluateList(Evaluator evaluator) {
final Member member = memberCalc.evaluateMember(evaluator);
List<Member> result = new ArrayList<Member>();
final int depth = depthCalc.evaluateInteger(evaluator);
final SchemaReader schemaReader =
evaluator.getSchemaReader();
descendantsByDepth(
member, result, schemaReader,
depth, flag1.before, flag1.self, flag1.after,
evaluator);
hierarchizeMemberList(result, false);
return new UnaryTupleList(result);
}
};
} else {
final LevelCalc levelCalc =
call.getArgCount() > 1
? compiler.compileLevel(call.getArg(1))
: null;
final Flag flag2 = flag;
return new AbstractListCalc(
call, new Calc[] {memberCalc, levelCalc})
{
public TupleList evaluateList(Evaluator evaluator) {
final Evaluator context =
evaluator.isNonEmpty() ? evaluator : null;
final Member member = memberCalc.evaluateMember(evaluator);
List<Member> result = new ArrayList<Member>();
final SchemaReader schemaReader =
evaluator.getSchemaReader();
final Level level =
levelCalc != null
? levelCalc.evaluateLevel(evaluator)
: member.getLevel();
descendantsByLevel(
schemaReader, member, level, result,
flag2.before, flag2.self,
flag2.after, flag2.leaves, context);
hierarchizeMemberList(result, false);
return new UnaryTupleList(result);
}
};
}
}
private static void descendantsByDepth(
Member member,
List<Member> result,
final SchemaReader schemaReader,
final int depthLimitFinal,
final boolean before,
final boolean self,
final boolean after,
final Evaluator context)
{
List<Member> children = new ArrayList<Member>();
children.add(member);
for (int depth = 0;; ++depth) {
if (depth == depthLimitFinal) {
if (self) {
result.addAll(children);
}
if (!after) {
break; // no more results after this level
}
} else if (depth < depthLimitFinal) {
if (before) {
result.addAll(children);
}
} else {
if (after) {
result.addAll(children);
} else {
break; // no more results after this level
}
}
children = schemaReader.getMemberChildren(children, context);
if (children.size() == 0) {
break;
}
}
}
/**
* Populates 'result' with the descendants at the leaf level at depth
* 'depthLimit' or less. If 'depthLimit' is -1, does not apply a depth
* constraint.
*/
private static void descendantsLeavesByDepth(
final Member member,
final List<Member> result,
final SchemaReader schemaReader,
final int depthLimit)
{
if (!schemaReader.isDrillable(member)) {
if (depthLimit >= 0) {
result.add(member);
}
return;
}
List<Member> children = new ArrayList<Member>();
children.add(member);
for (int depth = 0; depthLimit == -1 || depth <= depthLimit; ++depth) {
children = schemaReader.getMemberChildren(children);
if (children.size() == 0) {
throw Util.newInternal("drillable member must have children");
}
List<Member> nextChildren = new ArrayList<Member>();
for (Member child : children) {
// TODO: Implement this more efficiently. The current
// implementation of isDrillable for a parent-child hierarchy
// simply retrieves the children sees whether there are any,
// so we end up fetching each member's children twice.
if (schemaReader.isDrillable(child)) {
nextChildren.add(child);
} else {
result.add(child);
}
}
if (nextChildren.isEmpty()) {
return;
}
children = nextChildren;
}
}
/**
* Finds all descendants of a member which are before/at/after a level,
* and/or are leaves (have no descendants) and adds them to a result list.
*
* @param schemaReader Member reader
* @param ancestor Member to find descendants of
* @param level Level relative to which to filter, must not be null
* @param result Result list
* @param before Whether to output members above <code>level</code>
* @param self Whether to output members at <code>level</code>
* @param after Whether to output members below <code>level</code>
* @param leaves Whether to output members which are leaves
* @param context Evaluation context; determines criteria by which the
* result set should be filtered
*/
static void descendantsByLevel(
SchemaReader schemaReader,
Member ancestor,
Level level,
List<Member> result,
boolean before,
boolean self,
boolean after,
boolean leaves,
Evaluator context)
{
// We find the descendants of a member by making breadth-first passes
// down the hierarchy. Initially the list just contains the ancestor.
// Then we find its children. We add those children to the result if
// they fulfill the before/self/after conditions relative to the level.
//
// We add a child to the "fertileMembers" list if some of its children
// might be in the result. Again, this depends upon the
// before/self/after conditions.
//
// Note that for some member readers -- notably the
// RestrictedMemberReader, when it is reading a ragged hierarchy -- the
// children of a member do not always belong to the same level. For
// example, the children of USA include WA (a state) and Washington
// (a city). This is why we repeat the before/self/after logic for
// each member.
final int levelDepth = level.getDepth();
List<Member> members = Collections.singletonList(ancestor);
// Each pass, "fertileMembers" has the same contents as "members",
// except that we omit members whose children we are not interested
// in. We allocate it once, and clear it each pass, to save a little
// memory allocation.
if (leaves) {
assert !before && !self && !after;
do {
List<Member> nextMembers = new ArrayList<Member>();
for (Member member : members) {
final int currentDepth = member.getLevel().getDepth();
List<Member> childMembers =
schemaReader.getMemberChildren(member, context);
if (childMembers.size() == 0) {
// this member is a leaf -- add it
if (currentDepth == levelDepth) {
result.add(member);
}
} else {
// this member is not a leaf -- add its children
// to the list to be considered next iteration
if (currentDepth <= levelDepth) {
nextMembers.addAll(childMembers);
}
}
}
members = nextMembers;
} while (members.size() > 0);
} else {
List<Member> fertileMembers = new ArrayList<Member>();
do {
fertileMembers.clear();
for (Member member : members) {
final int currentDepth = member.getLevel().getDepth();
if (currentDepth == levelDepth) {
if (self) {
result.add(member);
}
if (after) {
// we are interested in member's children
fertileMembers.add(member);
}
} else if (currentDepth < levelDepth) {
if (before) {
result.add(member);
}
fertileMembers.add(member);
} else {
if (after) {
result.add(member);
fertileMembers.add(member);
}
}
}
members =
schemaReader.getMemberChildren(fertileMembers, context);
} while (members.size() > 0);
}
}
/**
* Enumeration of the flags allowed to the <code>DESCENDANTS</code>
* function.
*/
enum Flag {
SELF(true, false, false, false),
AFTER(false, true, false, false),
BEFORE(false, false, true, false),
BEFORE_AND_AFTER(false, true, true, false),
SELF_AND_AFTER(true, true, false, false),
SELF_AND_BEFORE(true, false, true, false),
SELF_BEFORE_AFTER(true, true, true, false),
LEAVES(false, false, false, true);
private final boolean self;
private final boolean after;
private final boolean before;
private final boolean leaves;
Flag(boolean self, boolean after, boolean before, boolean leaves) {
this.self = self;
this.after = after;
this.before = before;
this.leaves = leaves;
}
public static String[] getNames() {
List<String> names = new ArrayList<String>();
for (Flag flags : Flag.class.getEnumConstants()) {
names.add(flags.name());
}
return names.toArray(new String[names.size()]);
}
}
}
// End DescendantsFunDef.java