/*
// 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.*;
import mondrian.mdx.ResolvedFunCall;
import mondrian.olap.*;
import mondrian.olap.type.*;
import mondrian.resource.MondrianResource;
import java.io.PrintWriter;
import java.util.*;
/**
* <code>SetFunDef</code> implements the 'set' function (whose syntax is the
* brace operator, <code>{ ... }</code>).
*
* @author jhyde
* @since 3 March, 2002
*/
public class SetFunDef extends FunDefBase {
static final ResolverImpl Resolver = new ResolverImpl();
SetFunDef(Resolver resolver, int[] argTypes) {
super(resolver, Category.Set, argTypes);
}
public void unparse(Exp[] args, PrintWriter pw) {
ExpBase.unparseList(pw, args, "{", ", ", "}");
}
public Type getResultType(Validator validator, Exp[] args) {
// All of the members in {<Member1>[,<MemberI>]...} must have the same
// Hierarchy. But if there are no members, we can't derive a
// hierarchy.
Type type0 = null;
if (args.length == 0) {
// No members to go on, so we can't guess the hierarchy.
type0 = MemberType.Unknown;
} else {
for (int i = 0; i < args.length; i++) {
Exp arg = args[i];
Type type = arg.getType();
type = TypeUtil.toMemberOrTupleType(type);
if (i == 0) {
type0 = type;
} else {
if (!TypeUtil.isUnionCompatible(type0, type)) {
throw MondrianResource.instance()
.ArgsMustHaveSameHierarchy.ex(getName());
}
}
}
}
return new SetType(type0);
}
public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
final Exp[] args = call.getArgs();
if (args.length == 0) {
// Special treatment for empty set, because we don't know whether it
// is a set of members or tuples, and so we need it to implement
// both MemberListCalc and ListCalc.
return new EmptyListCalc(call);
}
if (args.length == 1
&& args[0].getType() instanceof SetType)
{
// Optimized case when there is only one argument. This occurs quite
// often, because people write '{Foo.Children} on 1' when they could
// write 'Foo.Children on 1'.
return args[0].accept(compiler);
}
return new SetListCalc(
call, args, compiler, ResultStyle.LIST_MUTABLELIST);
}
/**
* Compiled expression to implement the MDX set function, <code>{ ...
* }</code>, applied to a set of tuples, as a list.
*
* <p>The set function can contain expressions which yield sets together
* with expressions which yield individual tuples, provided that
* they all have the same type. It automatically removes null
* or partially-null tuples from the list.
*
* <p>Also, does not process high-cardinality dimensions specially.
*/
public static class SetListCalc extends AbstractListCalc {
private TupleList result;
private final VoidCalc[] voidCalcs;
public SetListCalc(
Exp exp,
Exp[] args,
ExpCompiler compiler,
List<ResultStyle> resultStyles)
{
super(exp, null);
voidCalcs = compileSelf(args, compiler, resultStyles);
result = TupleCollections.createList(getType().getArity());
}
public Calc[] getCalcs() {
return voidCalcs;
}
private VoidCalc[] compileSelf(
Exp[] args,
ExpCompiler compiler,
List<ResultStyle> resultStyles)
{
VoidCalc[] voidCalcs = new VoidCalc[args.length];
for (int i = 0; i < args.length; i++) {
voidCalcs[i] = createCalc(args[i], compiler, resultStyles);
}
return voidCalcs;
}
private VoidCalc createCalc(
Exp arg,
ExpCompiler compiler,
List<ResultStyle> resultStyles)
{
final Type type = arg.getType();
if (type instanceof SetType) {
// TODO use resultStyles
final ListCalc listCalc = compiler.compileList(arg);
return new AbstractVoidCalc(arg, new Calc[] {listCalc}) {
public void evaluateVoid(Evaluator evaluator) {
TupleList list =
listCalc.evaluateList(evaluator);
// Add only tuples which are not null. Tuples with
// any null members are considered null.
outer:
for (List<Member> members : list) {
for (Member member : members) {
if (member == null || member.isNull()) {
continue outer;
}
}
result.add(members);
}
}
protected String getName() {
return "Sublist";
}
};
} else if (type.getArity() == 1) {
final MemberCalc memberCalc = compiler.compileMember(arg);
return new AbstractVoidCalc(arg, new Calc[]{memberCalc}) {
final Member[] members = {null};
public void evaluateVoid(Evaluator evaluator) {
// Don't add null or partially null tuple to result.
Member member = memberCalc.evaluateMember(evaluator);
if (member == null || member.isNull()) {
return;
}
members[0] = member;
result.addTuple(members);
}
};
} else {
final TupleCalc tupleCalc = compiler.compileTuple(arg);
return new AbstractVoidCalc(arg, new Calc[]{tupleCalc}) {
public void evaluateVoid(Evaluator evaluator) {
// Don't add null or partially null tuple to result.
Member[] members = tupleCalc.evaluateTuple(evaluator);
if (members == null
|| tupleContainsNullMember(members))
{
return;
}
result.addTuple(members);
}
};
}
}
public TupleList evaluateList(final Evaluator evaluator) {
result.clear();
for (VoidCalc voidCalc : voidCalcs) {
voidCalc.evaluateVoid(evaluator);
}
return result.cloneList(-1);
}
}
private static List<Calc> compileSelf(
Exp[] args,
ExpCompiler compiler,
List<ResultStyle> resultStyles)
{
List<Calc> calcs = new ArrayList<Calc>(args.length);
for (Exp arg : args) {
calcs.add(createCalc(arg, compiler, resultStyles));
}
return calcs;
}
private static IterCalc createCalc(
Exp arg,
ExpCompiler compiler,
List<ResultStyle> resultStyles)
{
final Type type = arg.getType();
if (type instanceof SetType) {
final Calc calc = compiler.compileAs(arg, null, resultStyles);
switch (calc.getResultStyle()) {
case ITERABLE:
final IterCalc iterCalc = (IterCalc) calc;
return new AbstractIterCalc(arg, new Calc[]{calc}) {
public TupleIterable evaluateIterable(
Evaluator evaluator)
{
return iterCalc.evaluateIterable(evaluator);
}
protected String getName() {
return "Sublist";
}
};
case LIST:
case MUTABLE_LIST:
final ListCalc listCalc = (ListCalc) calc;
return new AbstractIterCalc(arg, new Calc[]{calc}) {
public TupleIterable evaluateIterable(
Evaluator evaluator)
{
TupleList list = listCalc.evaluateList(
evaluator);
TupleList result = list.cloneList(list.size());
// Add only tuples which are not null. Tuples with
// any null members are considered null.
list:
for (List<Member> members : list) {
for (Member member : members) {
if (member == null || member.isNull()) {
continue list;
}
}
result.add(members);
}
return result;
}
protected String getName() {
return "Sublist";
}
};
}
throw ResultStyleException.generateBadType(
ResultStyle.ITERABLE_LIST_MUTABLELIST,
calc.getResultStyle());
} else if (TypeUtil.couldBeMember(type)) {
final MemberCalc memberCalc = compiler.compileMember(arg);
final ResolvedFunCall call = wrapAsSet(arg);
return new AbstractIterCalc(call, new Calc[] {memberCalc}) {
public TupleIterable evaluateIterable(
Evaluator evaluator)
{
final Member member =
memberCalc.evaluateMember(evaluator);
return member == null
? TupleCollections.createList(1)
: new UnaryTupleList(Collections.singletonList(member));
}
protected String getName() {
return "Sublist";
}
};
} else {
final TupleCalc tupleCalc = compiler.compileTuple(arg);
final ResolvedFunCall call = wrapAsSet(arg);
return new AbstractIterCalc(call, new Calc[] {tupleCalc}) {
public TupleIterable evaluateIterable(
Evaluator evaluator)
{
final Member[] members = tupleCalc.evaluateTuple(evaluator);
return new ListTupleList(
tupleCalc.getType().getArity(),
Arrays.asList(members));
}
protected String getName() {
return "Sublist";
}
};
}
}
/**
* Creates a call to the set operator with a given collection of
* expressions.
*
* <p>There must be at least one expression. Each expression may be a set of
* members/tuples, or may be a member/tuple, but method assumes that
* expressions have compatible types.
*
* @param args Expressions
* @return Call to set operator
*/
public static ResolvedFunCall wrapAsSet(Exp... args) {
assert args.length > 0;
final int[] categories = new int[args.length];
Type type = null;
for (int i = 0; i < args.length; i++) {
final Exp arg = args[i];
categories[i] = arg.getCategory();
final Type argType = arg.getType();
if (argType instanceof SetType) {
type = ((SetType) argType).getElementType();
} else {
type = argType;
}
}
return new ResolvedFunCall(
new SetFunDef(Resolver, categories),
args,
new SetType(type));
}
/**
* Compiled expression that evaluates one or more expressions, each of which
* yields a tuple or a set of tuples, and returns the result as a tuple
* iterator.
*/
public static class ExprIterCalc extends AbstractIterCalc {
private final IterCalc[] iterCalcs;
public ExprIterCalc(
Exp exp,
Exp[] args,
ExpCompiler compiler,
List<ResultStyle> resultStyles)
{
super(exp, null);
final List<Calc> calcList =
compileSelf(args, compiler, resultStyles);
iterCalcs = calcList.toArray(new IterCalc[calcList.size()]);
}
// override return type
public IterCalc[] getCalcs() {
return iterCalcs;
}
public TupleIterable evaluateIterable(
final Evaluator evaluator)
{
return new AbstractTupleIterable(getType().getArity()) {
public TupleCursor tupleCursor() {
return new AbstractTupleCursor(arity) {
Iterator<IterCalc> calcIterator =
Arrays.asList(iterCalcs).iterator();
TupleCursor currentCursor =
TupleCollections.emptyList(1).tupleCursor();
public boolean forward() {
while (true) {
if (currentCursor.forward()) {
return true;
}
if (!calcIterator.hasNext()) {
return false;
}
currentCursor =
calcIterator.next()
.evaluateIterable(evaluator)
.tupleCursor();
}
}
public List<Member> current() {
return currentCursor.current();
}
@Override
public void setContext(Evaluator evaluator) {
currentCursor.setContext(evaluator);
}
@Override
public void currentToArray(
Member[] members, int offset)
{
currentCursor.currentToArray(members, offset);
}
@Override
public Member member(int column) {
return currentCursor.member(column);
}
};
}
};
}
}
private static class ResolverImpl extends ResolverBase {
public ResolverImpl() {
super(
"{}",
"{<Member> [, <Member>...]}",
"Brace operator constructs a set.",
Syntax.Braces);
}
public FunDef resolve(
Exp[] args,
Validator validator,
List<Conversion> conversions)
{
int[] parameterTypes = new int[args.length];
for (int i = 0; i < args.length; i++) {
if (validator.canConvert(
i, args[i], Category.Member, conversions))
{
parameterTypes[i] = Category.Member;
continue;
}
if (validator.canConvert(
i, args[i], Category.Tuple, conversions))
{
parameterTypes[i] = Category.Tuple;
continue;
}
if (validator.canConvert(
i, args[i], Category.Set, conversions))
{
parameterTypes[i] = Category.Set;
continue;
}
return null;
}
return new SetFunDef(this, parameterTypes);
}
}
/**
* Compiled expression that returns an empty list of members or tuples.
*/
private static class EmptyListCalc extends AbstractListCalc {
private final TupleList list;
/**
* Creates an EmptyListCalc.
*
* @param call Expression which was compiled
*/
EmptyListCalc(ResolvedFunCall call) {
super(call, new Calc[0]);
list = TupleCollections.emptyList(call.getType().getArity());
}
public TupleList evaluateList(Evaluator evaluator) {
return list;
}
}
}
// End SetFunDef.java