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