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) 2006-2011 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.olap.fun;
011
012import mondrian.calc.*;
013import mondrian.calc.impl.AbstractMemberCalc;
014import mondrian.calc.impl.ConstantCalc;
015import mondrian.mdx.ResolvedFunCall;
016import mondrian.olap.*;
017import mondrian.olap.type.MemberType;
018import mondrian.olap.type.Type;
019import mondrian.resource.MondrianResource;
020import mondrian.rolap.RolapCube;
021import mondrian.rolap.RolapHierarchy;
022
023/**
024 * Definition of the <code>ParallelPeriod</code> MDX function.
025 *
026 * @author jhyde
027 * @since Mar 23, 2006
028 */
029class ParallelPeriodFunDef extends FunDefBase {
030    static final ReflectiveMultiResolver Resolver =
031        new ReflectiveMultiResolver(
032            "ParallelPeriod",
033            "ParallelPeriod([<Level>[, <Numeric Expression>[, <Member>]]])",
034            "Returns a member from a prior period in the same relative position as a specified member.",
035            new String[] {"fm", "fml", "fmln", "fmlnm"},
036            ParallelPeriodFunDef.class);
037
038    public ParallelPeriodFunDef(FunDef dummyFunDef) {
039        super(dummyFunDef);
040    }
041
042    public Type getResultType(Validator validator, Exp[] args) {
043        if (args.length == 0) {
044            // With no args, the default implementation cannot
045            // guess the hierarchy, so we supply the Time
046            // dimension.
047            RolapHierarchy defaultTimeHierarchy =
048                ((RolapCube) validator.getQuery().getCube()).getTimeHierarchy(
049                    getName());
050            return MemberType.forHierarchy(defaultTimeHierarchy);
051        }
052        return super.getResultType(validator, args);
053    }
054
055    public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
056        // Member defaults to [Time].currentmember
057        Exp[] args = call.getArgs();
058
059        // Numeric Expression defaults to 1.
060        final IntegerCalc lagValueCalc =
061            (args.length >= 2)
062            ? compiler.compileInteger(args[1])
063            : ConstantCalc.constantInteger(1);
064
065        // If level is not specified, we compute it from
066        // member at runtime.
067        final LevelCalc ancestorLevelCalc =
068            args.length >= 1
069            ? compiler.compileLevel(args[0])
070            : null;
071
072        final MemberCalc memberCalc;
073        switch (args.length) {
074        case 3:
075            memberCalc = compiler.compileMember(args[2]);
076            break;
077        case 1:
078            final Hierarchy hierarchy = args[0].getType().getHierarchy();
079            if (hierarchy != null) {
080                // For some functions, such as Levels(<string expression>),
081                // the dimension cannot be determined at compile time.
082                memberCalc =
083                    new HierarchyCurrentMemberFunDef.FixedCalcImpl(
084                        call, hierarchy);
085            } else {
086                memberCalc = null;
087            }
088            break;
089        default:
090            final RolapHierarchy timeHierarchy =
091                ((RolapCube) compiler.getEvaluator().getCube())
092                    .getTimeHierarchy(getName());
093            memberCalc =
094                new HierarchyCurrentMemberFunDef.FixedCalcImpl(
095                    call, timeHierarchy);
096            break;
097        }
098
099        return new AbstractMemberCalc(
100            call,
101            new Calc[] {memberCalc, lagValueCalc, ancestorLevelCalc})
102        {
103            public Member evaluateMember(Evaluator evaluator) {
104                Member member;
105                int lagValue = lagValueCalc.evaluateInteger(evaluator);
106                Level ancestorLevel;
107                if (ancestorLevelCalc != null) {
108                    ancestorLevel = ancestorLevelCalc.evaluateLevel(evaluator);
109                    if (memberCalc == null) {
110                        member =
111                            evaluator.getContext(ancestorLevel.getHierarchy());
112                    } else {
113                        member = memberCalc.evaluateMember(evaluator);
114                    }
115                } else {
116                    member = memberCalc.evaluateMember(evaluator);
117                    Member parent = member.getParentMember();
118                    if (parent == null) {
119                        // This is a root member,
120                        // so there is no parallelperiod.
121                        return member.getHierarchy().getNullMember();
122                    }
123                    ancestorLevel = parent.getLevel();
124                }
125                return parallelPeriod(
126                    member, ancestorLevel, evaluator, lagValue);
127            }
128        };
129    }
130
131    Member parallelPeriod(
132        Member member,
133        Level ancestorLevel,
134        Evaluator evaluator,
135        int lagValue)
136    {
137        // Now do some error checking.
138        // The ancestorLevel and the member must be from the
139        // same hierarchy.
140        if (member.getHierarchy() != ancestorLevel.getHierarchy()) {
141            MondrianResource.instance().FunctionMbrAndLevelHierarchyMismatch.ex(
142                "ParallelPeriod",
143                ancestorLevel.getHierarchy().getUniqueName(),
144                member.getHierarchy().getUniqueName());
145        }
146
147        if (lagValue == Integer.MIN_VALUE) {
148            // Bump up lagValue by one; otherwise -lagValue (used in
149            // the getleadMember call below) is out of range because
150            // Integer.MAX_VALUE == -(Integer.MIN_VALUE + 1)
151            lagValue +=  1;
152        }
153
154        int distance =
155            member.getLevel().getDepth()
156            - ancestorLevel.getDepth();
157        Member ancestor = FunUtil.ancestor(
158            evaluator, member, distance, ancestorLevel);
159        Member inLaw = evaluator.getSchemaReader()
160            .getLeadMember(ancestor, -lagValue);
161        return FunUtil.cousin(
162            evaluator.getSchemaReader(), member, inLaw);
163    }
164}
165
166// End ParallelPeriodFunDef.java