/*
* 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-2013 Pentaho Corporation.. All rights reserved.
*/
package mondrian.udf;
import mondrian.olap.*;
import mondrian.olap.type.*;
import mondrian.rolap.RolapUtil;
import mondrian.spi.UserDefinedFunction;
import java.util.List;
/**
* Definition of the user-defined function "LastNonEmpty".
*
* @author jhyde
*/
public class LastNonEmptyUdf implements UserDefinedFunction {
public String getName() {
return "LastNonEmpty";
}
public String getDescription() {
return "Returns the last member of a set whose value is not empty";
}
public Syntax getSyntax() {
return Syntax.Function;
}
public Type getReturnType(Type[] parameterTypes) {
// Return type is the same as the elements of the first parameter.
// For example,
// LastNonEmpty({[Time].[1997], [Time].[1997].[Q1]},
// [Measures].[Unit Sales])
// will return a member of the [Time] dimension.
SetType setType = (SetType) parameterTypes[0];
MemberType memberType = (MemberType) setType.getElementType();
return memberType;
}
public Type[] getParameterTypes() {
return new Type[] {
// The first argument must be a set of members (of any hierarchy).
new SetType(MemberType.Unknown),
// The second argument must be a member.
MemberType.Unknown,
};
}
public Object execute(Evaluator evaluator, Argument[] arguments) {
final Argument memberListExp = arguments[0];
final List memberList = (List) memberListExp.evaluate(evaluator);
final Argument exp = arguments[1];
int nullCount = 0;
int missCount = 0;
for (int i = memberList.size() - 1; i >= 0; --i) {
Member member = (Member) memberList.get(i);
// Create an evaluator with the member as its context.
Evaluator subEvaluator = evaluator.push();
subEvaluator.setContext(member);
int missCountBefore = subEvaluator.getMissCount();
final Object o = exp.evaluateScalar(subEvaluator);
int missCountAfter = subEvaluator.getMissCount();
if (Util.isNull(o)) {
++nullCount;
continue;
}
if (missCountAfter > missCountBefore) {
// There was a cache miss while evaluating the expression, so
// the result is bogus. It would be a mistake to give up after
// one cache miss, because then it would take us N
// evaluate/fetch passes to move back through N members, which
// is way too many.
//
// Carry on until we have seen as many misses as we have seen
// null cells. The effect of this policy is that each pass
// examines twice as many cells as the previous pass. Thus
// we can move back through N members in log2(N) passes.
++missCount;
if (missCount < 2 * nullCount + 1) {
continue;
}
}
if (o == RolapUtil.valueNotReadyException) {
// Value is not in the cache yet, so we don't know whether
// it will be empty. Carry on...
continue;
}
if (o instanceof RuntimeException) {
RuntimeException runtimeException = (RuntimeException) o;
if (o == RolapUtil.valueNotReadyException) {
// Value is not in the cache yet, so we don't know whether
// it will be empty. Carry on...
continue;
}
return runtimeException;
}
return member;
}
// Not found. Return the hierarchy's 'null member'.
// It is possible that a MemberType has a Dimension but
// no hierarchy, so we have to just get the type's Dimension's
// default hierarchy and return it's null member.
final Hierarchy hierarchy = memberListExp.getType().getHierarchy();
return (hierarchy == null)
? memberListExp.getType().getDimension()
.getHierarchies()[0].getNullMember()
: hierarchy.getNullMember();
}
public String[] getReservedWords() {
// This function does not require any reserved words.
return null;
}
}
// End LastNonEmptyUdf.java