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