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