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