001    /*
002    // $Id: //open/mondrian/src/main/mondrian/udf/LastNonEmptyUdf.java#9 $
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) 2005-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package mondrian.udf;
011    
012    import mondrian.olap.*;
013    import mondrian.olap.type.*;
014    import mondrian.rolap.RolapUtil;
015    import mondrian.spi.UserDefinedFunction;
016    
017    import java.util.List;
018    
019    /**
020     * Definition of the user-defined function "LastNonEmpty".
021     *
022     * @author jhyde
023     * @version $Id: //open/mondrian/src/main/mondrian/udf/LastNonEmptyUdf.java#9 $
024     */
025    public class LastNonEmptyUdf implements UserDefinedFunction {
026    
027        public String getName() {
028            return "LastNonEmpty";
029        }
030    
031        public String getDescription() {
032            return "Returns the last member of a set whose value is not empty";
033        }
034    
035        public Syntax getSyntax() {
036            return Syntax.Function;
037        }
038    
039        public Type getReturnType(Type[] parameterTypes) {
040            // Return type is the same as the elements of the first parameter.
041            // For example,
042            //    LastNonEmpty({[Time].[1997], [Time].[1997].[Q1]},
043            //                 [Measures].[Unit Sales])
044            // will return a member of the [Time] dimension.
045            SetType setType = (SetType) parameterTypes[0];
046            MemberType memberType = (MemberType) setType.getElementType();
047            return memberType;
048        }
049    
050        public Type[] getParameterTypes() {
051            return new Type[] {
052                // The first argument must be a set of members (of any hierarchy).
053                new SetType(MemberType.Unknown),
054                // The second argument must be a member.
055                MemberType.Unknown,
056            };
057        }
058    
059        public Object execute(Evaluator evaluator, Argument[] arguments) {
060            final Argument memberListExp = arguments[0];
061            final List memberList = (List) memberListExp.evaluate(evaluator);
062            final Argument exp = arguments[1];
063            int nullCount = 0;
064            int missCount = 0;
065            for (int i = memberList.size() - 1; i >= 0; --i) {
066                Member member = (Member) memberList.get(i);
067                // Create an evaluator with the member as its context.
068                Evaluator subEvaluator = evaluator.push(member);
069                int missCountBefore = subEvaluator.getMissCount();
070                final Object o = exp.evaluateScalar(subEvaluator);
071                int missCountAfter = subEvaluator.getMissCount();
072                if (Util.isNull(o)) {
073                    ++nullCount;
074                    continue;
075                }
076                if (missCountAfter > missCountBefore) {
077                    // There was a cache miss while evaluating the expression, so
078                    // the result is bogus. It would be a mistake to give up after
079                    // one cache miss, because then it would take us N
080                    // evaluate/fetch passes to move back through N members, which
081                    // is way too many.
082                    //
083                    // Carry on until we have seen as many misses as we have seen
084                    // null cells. The effect of this policy is that each pass
085                    // examines twice as many cells as the previous pass. Thus
086                    // we can move back through N members in log2(N) passes.
087                    ++missCount;
088                    if (missCount < 2 * nullCount + 1) {
089                        continue;
090                    }
091                }
092                if (o == RolapUtil.valueNotReadyException) {
093                    // Value is not in the cache yet, so we don't know whether
094                    // it will be empty. Carry on...
095                    continue;
096                }
097                if (o instanceof RuntimeException) {
098                    RuntimeException runtimeException = (RuntimeException) o;
099                    if (o == RolapUtil.valueNotReadyException) {
100                        // Value is not in the cache yet, so we don't know whether
101                        // it will be empty. Carry on...
102                        continue;
103                    }
104                    return runtimeException;
105                }
106                return member;
107            }
108            // Not found. Return the hierarchy's 'null member'.
109            // It is possible that a MemberType has a Dimension but
110            // no hierarchy, so we have to just get the type's Dimension's
111            // default hierarchy and return it's null member.
112            final Hierarchy hierarchy = memberListExp.getType().getHierarchy();
113            return (hierarchy == null)
114                ? memberListExp.getType().getDimension().
115                    getHierarchies()[0].getNullMember()
116                : hierarchy.getNullMember();
117        }
118    
119        public String[] getReservedWords() {
120            // This function does not require any reserved words.
121            return null;
122        }
123    }
124    
125    // End LastNonEmptyUdf.java