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