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