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) 2002-2005 Julian Hyde
008// Copyright (C) 2005-2009 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.AbstractMemberCalc;
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
023import java.util.List;
024
025/**
026 * Definition of the <code>OpeningPeriod</code> and <code>ClosingPeriod</code>
027 * builtin functions.
028 *
029 * @author jhyde
030 * @since 2005/8/14
031 */
032class OpeningClosingPeriodFunDef extends FunDefBase {
033    private final boolean opening;
034
035    static final Resolver OpeningPeriodResolver =
036        new MultiResolver(
037            "OpeningPeriod",
038            "OpeningPeriod([<Level>[, <Member>]])",
039            "Returns the first descendant of a member at a level.",
040            new String[] {"fm", "fml", "fmlm"})
041    {
042        protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
043            return new OpeningClosingPeriodFunDef(dummyFunDef, true);
044        }
045    };
046
047    static final Resolver ClosingPeriodResolver =
048        new MultiResolver(
049            "ClosingPeriod",
050            "ClosingPeriod([<Level>[, <Member>]])",
051            "Returns the last descendant of a member at a level.",
052            new String[] {"fm", "fml", "fmlm", "fmm"})
053    {
054        protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
055            return new OpeningClosingPeriodFunDef(dummyFunDef, false);
056        }
057    };
058
059    public OpeningClosingPeriodFunDef(
060        FunDef dummyFunDef,
061        boolean opening)
062    {
063        super(dummyFunDef);
064        this.opening = opening;
065    }
066
067    public Type getResultType(Validator validator, Exp[] args) {
068        if (args.length == 0) {
069            // With no args, the default implementation cannot
070            // guess the hierarchy, so we supply the Time
071            // dimension.
072            RolapHierarchy defaultTimeHierarchy =
073                ((RolapCube) validator.getQuery().getCube()).getTimeHierarchy(
074                    getName());
075            return MemberType.forHierarchy(defaultTimeHierarchy);
076        }
077        return super.getResultType(validator, args);
078    }
079
080    public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
081        final Exp[] args = call.getArgs();
082        final LevelCalc levelCalc;
083        final MemberCalc memberCalc;
084        RolapHierarchy defaultTimeHierarchy = null;
085        switch (args.length) {
086        case 0:
087            defaultTimeHierarchy =
088                ((RolapCube) compiler.getEvaluator().getCube())
089                    .getTimeHierarchy(getName());
090            memberCalc =
091                new HierarchyCurrentMemberFunDef.FixedCalcImpl(
092                    new DummyExp(
093                        MemberType.forHierarchy(defaultTimeHierarchy)),
094                    defaultTimeHierarchy);
095            levelCalc = null;
096            break;
097        case 1:
098            defaultTimeHierarchy =
099                ((RolapCube) compiler.getEvaluator().getCube())
100                    .getTimeHierarchy(getName());
101            levelCalc = compiler.compileLevel(call.getArg(0));
102            memberCalc =
103                new HierarchyCurrentMemberFunDef.FixedCalcImpl(
104                    new DummyExp(
105                        MemberType.forHierarchy(defaultTimeHierarchy)),
106                    defaultTimeHierarchy);
107            break;
108        default:
109            levelCalc = compiler.compileLevel(call.getArg(0));
110            memberCalc = compiler.compileMember(call.getArg(1));
111            break;
112        }
113
114        // Make sure the member and the level come from the same dimension.
115        if (levelCalc != null) {
116            final Dimension memberDimension =
117                memberCalc.getType().getDimension();
118            final Dimension levelDimension = levelCalc.getType().getDimension();
119            if (!memberDimension.equals(levelDimension)) {
120                throw MondrianResource.instance()
121                    .FunctionMbrAndLevelHierarchyMismatch.ex(
122                        opening ? "OpeningPeriod" : "ClosingPeriod",
123                        levelDimension.getUniqueName(),
124                        memberDimension.getUniqueName());
125            }
126        }
127        return new AbstractMemberCalc(
128            call, new Calc[] {levelCalc, memberCalc})
129        {
130            public Member evaluateMember(Evaluator evaluator) {
131                Member member = memberCalc.evaluateMember(evaluator);
132
133                // If the level argument is present, use it. Otherwise use the
134                // level immediately after that of the member argument.
135                Level level;
136                if (levelCalc == null) {
137                    int targetDepth = member.getLevel().getDepth() + 1;
138                    Level[] levels = member.getHierarchy().getLevels();
139
140                    if (levels.length <= targetDepth) {
141                        return member.getHierarchy().getNullMember();
142                    }
143                    level = levels[targetDepth];
144                } else {
145                    level = levelCalc.evaluateLevel(evaluator);
146                }
147
148                // Shortcut if the level is above the member.
149                if (level.getDepth() < member.getLevel().getDepth()) {
150                    return member.getHierarchy().getNullMember();
151                }
152
153                // Shortcut if the level is the same as the member
154                if (level == member.getLevel()) {
155                    return member;
156                }
157
158                return getDescendant(
159                    evaluator.getSchemaReader(), member,
160                    level, opening);
161            }
162        };
163    }
164
165    /**
166     * Returns the first or last descendant of the member at the target level.
167     * This method is the implementation of both OpeningPeriod and
168     * ClosingPeriod.
169     *
170     * @param schemaReader The schema reader to use to evaluate the function.
171     * @param member The member from which the descendant is to be found.
172     * @param targetLevel The level to stop at.
173     * @param returnFirstDescendant Flag indicating whether to return the first
174     * or last descendant of the member.
175     * @return A member.
176     * @pre member.getLevel().getDepth() < level.getDepth();
177     */
178    static Member getDescendant(
179        SchemaReader schemaReader,
180        Member member,
181        Level targetLevel,
182        boolean returnFirstDescendant)
183    {
184        List<Member> children;
185
186        final int targetLevelDepth = targetLevel.getDepth();
187        assertPrecondition(member.getLevel().getDepth() < targetLevelDepth,
188                "member.getLevel().getDepth() < targetLevel.getDepth()");
189
190        for (;;) {
191            children = schemaReader.getMemberChildren(member);
192
193            if (children.size() == 0) {
194                return targetLevel.getHierarchy().getNullMember();
195            }
196
197            final int index =
198                returnFirstDescendant ? 0 : (children.size() - 1);
199            member = children.get(index);
200            if (member.getLevel().getDepth() == targetLevelDepth) {
201                if (member.isHidden()) {
202                    return member.getHierarchy().getNullMember();
203                } else {
204                    return member;
205                }
206            }
207        }
208    }
209
210}
211
212// End OpeningClosingPeriodFunDef.java