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