001 /* 002 // $Id: //open/mondrian/src/main/mondrian/olap/fun/StrToSetFunDef.java#8 $ 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) 2006-2008 Julian Hyde 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.olap.fun; 011 012 import mondrian.olap.*; 013 import mondrian.olap.type.*; 014 import mondrian.calc.Calc; 015 import mondrian.calc.ExpCompiler; 016 import mondrian.calc.StringCalc; 017 import mondrian.calc.impl.AbstractListCalc; 018 import mondrian.mdx.ResolvedFunCall; 019 import mondrian.mdx.DimensionExpr; 020 import mondrian.mdx.HierarchyExpr; 021 import mondrian.resource.MondrianResource; 022 023 import java.util.ArrayList; 024 import java.util.List; 025 026 /** 027 * Definition of the <code>StrToSet</code> MDX builtin function. 028 * 029 * @author jhyde 030 * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/StrToSetFunDef.java#8 $ 031 * @since Mar 23, 2006 032 */ 033 class StrToSetFunDef extends FunDefBase { 034 static final ResolverImpl Resolver = new ResolverImpl(); 035 036 private StrToSetFunDef(int[] parameterTypes) { 037 super("StrToSet", "<Set> StrToSet(<String>[, <Dimension>...])", 038 "Constructs a set from a string expression.", 039 Syntax.Function, Category.Set, parameterTypes); 040 } 041 042 public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { 043 final StringCalc stringCalc = compiler.compileString(call.getArg(0)); 044 SetType type = (SetType) call.getType(); 045 Type elementType = type.getElementType(); 046 if (elementType instanceof MemberType) { 047 final Hierarchy hierarchy = elementType.getHierarchy(); 048 return new AbstractListCalc(call, new Calc[] {stringCalc}) { 049 public List evaluateList(Evaluator evaluator) { 050 String string = stringCalc.evaluateString(evaluator); 051 return parseMemberList(evaluator, string, hierarchy); 052 } 053 }; 054 } else { 055 TupleType tupleType = (TupleType) elementType; 056 final Hierarchy[] hierarchies = 057 new Hierarchy[tupleType.elementTypes.length]; 058 for (int i = 0; i < tupleType.elementTypes.length; i++) { 059 hierarchies[i] = tupleType.elementTypes[i].getHierarchy(); 060 } 061 return new AbstractListCalc(call, new Calc[] {stringCalc}) { 062 public List evaluateList(Evaluator evaluator) { 063 String string = stringCalc.evaluateString(evaluator); 064 return parseTupleList(evaluator, string, hierarchies); 065 } 066 }; 067 } 068 } 069 070 private static List<Member[]> parseTupleList( 071 Evaluator evaluator, 072 String string, 073 Hierarchy[] hierarchies) 074 { 075 List<Member[]> tupleList = new ArrayList<Member[]>(); 076 Member[] members = new Member[hierarchies.length]; 077 int i = 0; 078 char c; 079 while ((c = string.charAt(i++)) == ' ') { 080 } 081 if (c != '{') { 082 throw fail(string, i, "{"); 083 } 084 while (true) { 085 i = parseTuple(evaluator, string, i, members, hierarchies); 086 tupleList.add(members.clone()); 087 while ((c = string.charAt(i++)) == ' ') { 088 } 089 if (c == ',') { 090 // fine 091 } else if (c == '}') { 092 // we're done 093 return tupleList; 094 } else { 095 throw fail(string, i, ", or }"); 096 } 097 } 098 } 099 100 /** 101 * Parses a tuple, of the form '(member, member, ...)'. 102 * There must be precisely one member for each hierarchy. 103 * 104 * @param evaluator Evaluator, provides a {@link mondrian.olap.SchemaReader} 105 * and {@link Cube} 106 * @param string String to parse 107 * @param i Position to start parsing in string 108 * @param members Output array of members 109 * @param hierarchies Hierarchies of the members 110 * @return Position where parsing ended in string 111 */ 112 static int parseTuple( 113 Evaluator evaluator, 114 String string, 115 int i, 116 Member[] members, 117 Hierarchy[] hierarchies) 118 { 119 char c; 120 while ((c = string.charAt(i++)) == ' ') { 121 } 122 if (c != '(') { 123 throw fail(string, i, "("); 124 } 125 int j = 0; 126 while (true) { 127 i = parseMember(evaluator, string, i, members, hierarchies, j); 128 while ((c = string.charAt(i++)) == ' ') { 129 } 130 ++j; 131 if (j < hierarchies.length) { 132 if (c == ',') { 133 // fine 134 } else if (c == ')') { 135 // saw ')' before we saw enough members 136 throw Util.newInternal("too few members"); 137 } else { 138 } 139 } else { 140 if (c == ')') { 141 break; 142 } else { 143 throw Util.newInternal("expected ')"); 144 } 145 } 146 } 147 return i; 148 } 149 150 private static List<Member> parseMemberList( 151 Evaluator evaluator, 152 String string, 153 Hierarchy hierarchy) 154 { 155 Hierarchy[] hierarchies = new Hierarchy[] {hierarchy}; 156 List<Member> memberList = new ArrayList<Member>(); 157 Member[] members = {null}; 158 int i = 0; 159 char c; 160 while ((c = string.charAt(i++)) == ' ') { 161 } 162 if (c != '{') { 163 throw fail(string, i, "{"); 164 } 165 while (true) { 166 i = parseMember(evaluator, string, i, members, hierarchies, 0); 167 memberList.add(members[0]); 168 while ((c = string.charAt(i++)) == ' ') { 169 } 170 if (c == ',') { 171 // fine 172 } else if (c == '}') { 173 // we're done 174 return memberList; 175 } else { 176 throw fail(string, i, ", or }"); 177 } 178 } 179 } 180 181 // State values 182 private static final int BEFORE_SEG = 0; 183 private static final int IN_BRACKET_SEG = 1; 184 private static final int AFTER_SEG = 2; 185 private static final int IN_SEG = 3; 186 187 static int parseMember( 188 Evaluator evaluator, 189 String string, 190 int i, 191 Member[] members, 192 Hierarchy[] hierarchies, 193 int j) 194 { 195 int k = string.length(); 196 List<Id.Segment> nameList = new ArrayList<Id.Segment>(); 197 int state = BEFORE_SEG; 198 int start = 0; 199 char c; 200 201 loop: 202 while (i < k) { 203 switch (state) { 204 case BEFORE_SEG: 205 c = string.charAt(i); 206 switch (c) { 207 case '[': 208 ++i; 209 start = i; 210 state = IN_BRACKET_SEG; 211 break; 212 213 case ' ': 214 // Skip whitespace, don't change state. 215 ++i; 216 break; 217 218 case ',': 219 case '}': 220 break loop; 221 222 case '.': 223 // TODO: test this, case: ".abc" 224 throw Util.newInternal("unexpected: '.'"); 225 226 default: 227 // Carry on reading. 228 state = IN_SEG; 229 start = i; 230 break; 231 } 232 break; 233 234 case IN_SEG: 235 c = string.charAt(i); 236 switch (c) { 237 case '.': 238 nameList.add( 239 new Id.Segment( 240 string.substring(start, i), 241 Id.Quoting.UNQUOTED)); 242 state = BEFORE_SEG; 243 ++i; 244 default: 245 ++i; 246 } 247 break; 248 249 case IN_BRACKET_SEG: 250 c = string.charAt(i); 251 switch (c) { 252 case ']': 253 nameList.add( 254 new Id.Segment( 255 string.substring(start, i), 256 Id.Quoting.QUOTED)); 257 ++i; 258 state = AFTER_SEG; 259 break; 260 261 default: 262 // Carry on reading. 263 ++i; 264 } 265 break; 266 267 case AFTER_SEG: 268 c = string.charAt(i); 269 switch (c) { 270 case ' ': 271 // Skip over any spaces 272 // TODO: test this case: '[foo] . [bar]' 273 ++i; 274 break; 275 case '.': 276 state = BEFORE_SEG; 277 ++i; 278 break; 279 280 default: 281 // We're not looking at the start of a segment. Parse 282 // the member we've seen so far, then return. 283 break loop; 284 } 285 break; 286 287 default: 288 throw Util.newInternal("unexpected state: " + state); 289 } 290 } 291 292 // End of member. 293 Member member = 294 (Member) 295 Util.lookupCompound( 296 evaluator.getSchemaReader(), 297 evaluator.getCube(), 298 nameList, true, Category.Member); 299 members[j] = member; 300 if (member.getHierarchy() != hierarchies[j]) { 301 // TODO: better error 302 throw Util.newInternal("member is of wrong hierarchy"); 303 } 304 return i; 305 } 306 307 private static RuntimeException fail(String string, int i, String expecting) { 308 throw Util.newInternal("expected '" + expecting + "' at position " + i + " in '" + string + "'"); 309 } 310 311 public Exp createCall(Validator validator, Exp[] args) { 312 final int argCount = args.length; 313 if (argCount <= 1) { 314 throw MondrianResource.instance().MdxFuncArgumentsNum.ex(getName()); 315 } 316 for (int i = 1; i < argCount; i++) { 317 final Exp arg = args[i]; 318 if (arg instanceof DimensionExpr) { 319 // if arg is a dimension, switch to dimension's default 320 // hierarchy 321 DimensionExpr dimensionExpr = (DimensionExpr) arg; 322 Dimension dimension = dimensionExpr.getDimension(); 323 args[i] = new HierarchyExpr(dimension.getHierarchy()); 324 } else if (arg instanceof HierarchyExpr) { 325 // nothing 326 } else { 327 throw MondrianResource.instance().MdxFuncNotHier.ex( 328 i + 1, getName()); 329 } 330 } 331 return super.createCall(validator, args); 332 } 333 334 public Type getResultType(Validator validator, Exp[] args) { 335 switch (args.length) { 336 case 1: 337 // This is a call to the standard version of StrToSet, 338 // which doesn't give us any hints about type. 339 return new SetType(null); 340 341 case 2: 342 { 343 final Type argType = args[1].getType(); 344 return new SetType( 345 new MemberType( 346 argType.getDimension(), 347 argType.getHierarchy(), 348 argType.getLevel(), 349 null)); 350 } 351 352 default: 353 { 354 // This is a call to Mondrian's extended version of 355 // StrToSet, of the form 356 // StrToSet(s, <Hier1>, ... , <HierN>) 357 // 358 // The result is a set of tuples 359 // (<Hier1>, ... , <HierN>) 360 final List<Type> list = new ArrayList<Type>(); 361 for (int i = 1; i < args.length; i++) { 362 Exp arg = args[i]; 363 final Type argType = arg.getType(); 364 list.add( 365 new MemberType( 366 argType.getDimension(), 367 argType.getHierarchy(), 368 argType.getLevel(), 369 null)); 370 } 371 final Type[] types = list.toArray(new Type[list.size()]); 372 return new SetType(new TupleType(types)); 373 } 374 } 375 } 376 377 private static class ResolverImpl extends ResolverBase { 378 ResolverImpl() { 379 super( 380 "StrToSet", 381 "StrToSet(<String Expression>)", 382 "Constructs a set from a string expression.", 383 Syntax.Function); 384 } 385 386 public FunDef resolve( 387 Exp[] args, Validator validator, int[] conversionCount) { 388 if (args.length < 1) { 389 return null; 390 } 391 Type type = args[0].getType(); 392 if (!(type instanceof StringType)) { 393 return null; 394 } 395 for (int i = 1; i < args.length; i++) { 396 Exp exp = args[i]; 397 if (!(exp instanceof DimensionExpr)) { 398 return null; 399 } 400 } 401 int[] argTypes = new int[args.length]; 402 argTypes[0] = Category.String; 403 for (int i = 1; i < argTypes.length; i++) { 404 argTypes[i] = Category.Hierarchy; 405 } 406 return new StrToSetFunDef(argTypes); 407 } 408 409 public FunDef getFunDef() { 410 return new StrToSetFunDef(new int[] {Category.String}); 411 } 412 } 413 } 414 415 // End StrToSetFunDef.java