001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/fun/RankFunDef.java#16 $
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) 2005-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    
011    package mondrian.olap.fun;
012    
013    import mondrian.olap.*;
014    import mondrian.olap.type.*;
015    import mondrian.calc.*;
016    import mondrian.calc.impl.*;
017    import mondrian.rolap.RolapUtil;
018    import mondrian.mdx.ResolvedFunCall;
019    
020    import java.util.*;
021    import java.io.PrintWriter;
022    
023    /**
024     * Definition of the <code>RANK</code> MDX function.
025     *
026     * @author Richard Emberson
027     * @since 17 January, 2005
028     * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/RankFunDef.java#16 $
029     */
030    public class RankFunDef extends FunDefBase {
031        static final boolean debug = false;
032        static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver(
033                "Rank",
034                "Rank(<Tuple>, <Set> [, <Calc Expression>])",
035                "Returns the one-based rank of a tuple in a set.",
036                new String[]{"fitx", "fitxn", "fimx", "fimxn"},
037                RankFunDef.class);
038    
039        public RankFunDef(FunDef dummyFunDef) {
040            super(dummyFunDef);
041        }
042    
043        public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
044            switch (call.getArgCount()) {
045            case 2:
046                return compileCall2(call, compiler);
047            case 3:
048                return compileCall3(call, compiler);
049            default:
050                throw Util.newInternal("invalid arg count " + call.getArgCount());
051            }
052        }
053    
054        public Calc compileCall3(ResolvedFunCall call, ExpCompiler compiler) {
055            final Type type0 = call.getArg(0).getType();
056            if (type0 instanceof TupleType) {
057                final TupleCalc tupleCalc =
058                        compiler.compileTuple(call.getArg(0));
059                final ListCalc listCalc =
060                        compiler.compileList(call.getArg(1));
061                final Calc sortCalc =
062                        compiler.compileScalar(call.getArg(2), true);
063                Calc sortedListCalc =
064                        new SortCalc(call, listCalc, sortCalc);
065                final ExpCacheDescriptor cacheDescriptor =
066                        new ExpCacheDescriptor(
067                                call, sortedListCalc, compiler.getEvaluator());
068                return new Rank3TupleCalc(call, tupleCalc, sortCalc, cacheDescriptor);
069            } else {
070                final MemberCalc memberCalc =
071                        compiler.compileMember(call.getArg(0));
072                final ListCalc listCalc = compiler.compileList(call.getArg(1));
073                final Calc sortCalc = compiler.compileScalar(call.getArg(2), true);
074                Calc sortedListCalc =
075                        new SortCalc(call, listCalc, sortCalc);
076                final ExpCacheDescriptor cacheDescriptor =
077                        new ExpCacheDescriptor(
078                                call, sortedListCalc, compiler.getEvaluator());
079                return new Rank3MemberCalc(call, memberCalc, sortCalc, cacheDescriptor);
080            }
081        }
082    
083        public Calc compileCall2(ResolvedFunCall call, ExpCompiler compiler) {
084            final boolean tuple = call.getArg(0).getType() instanceof TupleType;
085            final Exp listExp = call.getArg(1);
086            ListCalc listCalc0 = compiler.compileList(listExp);
087            Calc listCalc1 = new RankedListCalc(listCalc0, tuple);
088            final Calc listCalc;
089            if (MondrianProperties.instance().EnableExpCache.get()) {
090                final ExpCacheDescriptor key = new ExpCacheDescriptor(
091                        listExp, listCalc1, compiler.getEvaluator());
092                listCalc = new CacheCalc(listExp, key);
093            } else {
094                listCalc = listCalc1;
095            }
096            if (tuple) {
097                final TupleCalc tupleCalc =
098                        compiler.compileTuple(call.getArg(0));
099                return new Rank2TupleCalc(call, tupleCalc, listCalc);
100            } else {
101                final MemberCalc memberCalc =
102                        compiler.compileMember(call.getArg(0));
103                return new Rank2MemberCalc(call, memberCalc, listCalc);
104            }
105        }
106    
107        private static class Rank2TupleCalc extends AbstractIntegerCalc {
108            private final TupleCalc tupleCalc;
109            private final Calc listCalc;
110    
111            public Rank2TupleCalc(ResolvedFunCall call, TupleCalc tupleCalc, Calc listCalc) {
112                super(call, new Calc[] {tupleCalc, listCalc});
113                this.tupleCalc = tupleCalc;
114                this.listCalc = listCalc;
115            }
116    
117            public int evaluateInteger(Evaluator evaluator) {
118                // Get member or tuple.
119                // If the member is null (or the tuple contains a null member)
120                // the result is null (even if the list is null).
121                final Member[] members = tupleCalc.evaluateTuple(evaluator);
122                if (members == null) {
123                    return IntegerNull;
124                }
125                assert !tupleContainsNullMember(members);
126    
127                // Get the set of members/tuples.
128                // If the list is empty, MSAS cannot figure out the type of the
129                // list, so returns an error "Formula error - dimension count is
130                // not valid - in the Rank function". We will naturally return 0,
131                // which I think is better.
132                RankedTupleList rankedList = (RankedTupleList) listCalc.evaluate(evaluator);
133                if (rankedList == null) {
134                    return 0;
135                }
136    
137                // Find position of member in list. -1 signifies not found.
138                final int i = rankedList.indexOf(members);
139                // Return 1-based rank. 0 signifies not found.
140                return i + 1;
141            }
142        }
143    
144        private static class Rank2MemberCalc extends AbstractIntegerCalc {
145            private final MemberCalc memberCalc;
146            private final Calc listCalc;
147    
148            public Rank2MemberCalc(
149                ResolvedFunCall call, MemberCalc memberCalc, Calc listCalc)
150            {
151                super(call, new Calc[] {memberCalc, listCalc});
152                this.memberCalc = memberCalc;
153                this.listCalc = listCalc;
154            }
155    
156            public int evaluateInteger(Evaluator evaluator) {
157                // Get member or tuple.
158                // If the member is null (or the tuple contains a null member)
159                // the result is null (even if the list is null).
160                final Member member = memberCalc.evaluateMember(evaluator);
161                if (member == null ||
162                    member.isNull())
163                {
164                    return IntegerNull;
165                }
166                // Get the set of members/tuples.
167                // If the list is empty, MSAS cannot figure out the type of the
168                // list, so returns an error "Formula error - dimension count is
169                // not valid - in the Rank function". We will naturally return 0,
170                // which I think is better.
171                RankedMemberList rankedList = (RankedMemberList) listCalc.evaluate(evaluator);
172                if (rankedList == null) {
173                    return 0;
174                }
175    
176                // Find position of member in list. -1 signifies not found.
177                final int i = rankedList.indexOf(member);
178                // Return 1-based rank. 0 signifies not found.
179                return i + 1;
180            }
181        }
182    
183        private static class Rank3TupleCalc extends AbstractIntegerCalc {
184            private final TupleCalc tupleCalc;
185            private final Calc sortCalc;
186            private final ExpCacheDescriptor cacheDescriptor;
187    
188            public Rank3TupleCalc(
189                ResolvedFunCall call,
190                TupleCalc tupleCalc,
191                Calc sortCalc,
192                ExpCacheDescriptor cacheDescriptor)
193            {
194                super(call, new Calc[] {tupleCalc, sortCalc});
195                this.tupleCalc = tupleCalc;
196                this.sortCalc = sortCalc;
197                this.cacheDescriptor = cacheDescriptor;
198            }
199    
200            public int evaluateInteger(Evaluator evaluator) {
201                Member[] members = tupleCalc.evaluateTuple(evaluator);
202                if (members == null) {
203                    return IntegerNull;
204                }
205                assert !tupleContainsNullMember(members);
206    
207                // Compute the value of the tuple.
208                final Evaluator evaluator2 = evaluator.push(members);
209                Object value = sortCalc.evaluate(evaluator2);
210                if (value instanceof RuntimeException) {
211                    // The value wasn't ready, so quit now... we'll be back.
212                    return 0;
213                }
214    
215                // Evaluate the list (or retrieve from cache).
216                // If there was an exception while calculating the
217                // list, propagate it up.
218                final SortResult sortResult = (SortResult)
219                        evaluator.getCachedResult(cacheDescriptor);
220                if (debug) {
221                    sortResult.print(new PrintWriter(System.out));
222                }
223                if (sortResult.empty) {
224                    // If list is empty, the rank is null.
225                    return IntegerNull;
226                }
227    
228                // If value is null, it won't be in the values array.
229                if (value == Util.nullValue) {
230                    return sortResult.values.length + 1;
231                }
232                // Look for the ranked value in the array.
233                int j = FunUtil.searchValuesDesc(sortResult.values, value);
234                if (j < 0) {
235                    // Value not found. Flip the result to find the
236                    // insertion point.
237                    j = -(j + 1);
238                    return j + 1; // 1-based
239                }
240                if (j <= sortResult.values.length) {
241                    // If the values preceding are equal, increase the rank.
242                    while (j > 0 && sortResult.values[j - 1].equals(value)) {
243                        --j;
244                    }
245                }
246                return j + 1; // 1-based
247            }
248        }
249    
250        private static class Rank3MemberCalc extends AbstractDoubleCalc {
251            private final MemberCalc memberCalc;
252            private final Calc sortCalc;
253            private final ExpCacheDescriptor cacheDescriptor;
254    
255            public Rank3MemberCalc(
256                    ResolvedFunCall call,
257                    MemberCalc memberCalc,
258                    Calc sortCalc,
259                    ExpCacheDescriptor cacheDescriptor) {
260                super(call, new Calc[] {memberCalc, sortCalc});
261                this.memberCalc = memberCalc;
262                this.sortCalc = sortCalc;
263                this.cacheDescriptor = cacheDescriptor;
264            }
265    
266            public double evaluateDouble(Evaluator evaluator) {
267                Member member = memberCalc.evaluateMember(evaluator);
268                if (member == null || member.isNull()) {
269                    return DoubleNull;
270                }
271                // Compute the value of the tuple.
272                final Evaluator evaluator2 = evaluator.push(member);
273                Object value = sortCalc.evaluate(evaluator2);
274                if (value == RolapUtil.valueNotReadyException) {
275                    // The value wasn't ready, so quit now... we'll be back.
276                    return 0;
277                }
278    
279                // Evaluate the list (or retrieve from cache).
280                // If there was an exception while calculating the
281                // list, propagate it up.
282                final SortResult sortResult = (SortResult)
283                        evaluator.getCachedResult(cacheDescriptor);
284                if (debug) {
285                    sortResult.print(new PrintWriter(System.out));
286                }
287                if (sortResult.empty) {
288                    // If list is empty, the rank is null.
289                    return DoubleNull;
290                }
291    
292                // If value is null, it won't be in the values array.
293                if (value == Util.nullValue) {
294                    return sortResult.values.length + 1;
295                }
296                // Look for the ranked value in the array.
297                int j = FunUtil.searchValuesDesc(sortResult.values, value);
298                if (j < 0) {
299                    // Value not found. Flip the result to find the
300                    // insertion point.
301                    j = -(j + 1);
302                    return j + 1; // 1-based
303                }
304                if (j <= sortResult.values.length) {
305                    // If the values preceding are equal, increase the rank.
306                    while (j > 0 && sortResult.values[j - 1].equals(value)) {
307                        --j;
308                    }
309                }
310                return j + 1; // 1-based
311            }
312        }
313    
314        /**
315         * Expression which evaluates an expression to form a list of tuples,
316         * evaluates a scalar expression at each tuple, then sorts the list of
317         * values. The result is a value of type {@link SortResult}, and can be
318         * used to implement the <code>Rank</code> function efficiently.
319         */
320        private static class SortCalc extends AbstractCalc {
321            private final ListCalc listCalc;
322            private final Calc sortCalc;
323    
324            public SortCalc(Exp exp, ListCalc listExp, Calc sortExp) {
325                super(exp);
326                this.listCalc = listExp;
327                this.sortCalc = sortExp;
328            }
329    
330            public Calc[] getCalcs() {
331                return new Calc[] {listCalc, sortCalc};
332            }
333    
334            public boolean dependsOn(Dimension dimension) {
335                return anyDependsButFirst(getCalcs(), dimension);
336            }
337    
338            public Object evaluate(Evaluator evaluator) {
339                // Create a new evaluator so we don't corrupt the given one.
340                final Evaluator evaluator2 = evaluator.push();
341                // Construct an array containing the value of the expression
342                // for each member.
343                List members = (List) listCalc.evaluate(evaluator2);
344                assert members != null;
345                if (members.isEmpty()) {
346                    return new SortResult(true, new Object[0]);
347                }
348                RuntimeException exception = null;
349                Object[] values = new Object[members.size()];
350                int j = 0;
351                for (Object o : members) {
352                    if (o instanceof Member) {
353                        Member member = (Member) o;
354                        evaluator2.setContext(member);
355                    } else {
356                        evaluator2.setContext((Member[]) o);
357                    }
358                    final Object value = sortCalc.evaluate(evaluator2);
359                    if (value instanceof RuntimeException) {
360                        if (exception == null) {
361                            exception = (RuntimeException) value;
362                        }
363                    } else if (Util.isNull(value)) {
364                        // nothing to do
365                    } else {
366                        values[j++] = value;
367                    }
368                }
369                // If there were exceptions, quit now... we'll be back.
370                if (exception != null) {
371                    return exception;
372                }
373                // If the array is shorter than we expected (because of null
374                // values) truncate it.
375                if (j < members.size()) {
376                    final Object[] oldValues = values;
377                    values = new Object[j];
378                    System.arraycopy(oldValues, 0, values, 0, j);
379                }
380                // Sort the array.
381                FunUtil.sortValuesDesc(values);
382                return new SortResult(false, values);
383            }
384        }
385    
386        /**
387         * Holder for the result of sorting a set of values.
388         *
389         * <p>todo: More optimal representation if a lot of the values are the
390         * same.
391         */
392        private static class SortResult {
393            /**
394             * Whether the list of tuples was empty.
395             * If this is the case, the rank will always be null.
396             *
397             * <p>It's possible for there to be a positive number of tuples, all
398             * of whose values are null, in which case, empty will be false but
399             * values will be empty.
400             */
401            final boolean empty;
402            /**
403             * Values in sorted order. Null values are not present: they would
404             * be at the end, anyway.
405             */
406            final Object[] values;
407    
408            public SortResult(boolean empty, Object[] values) {
409                this.empty = empty;
410                this.values = values;
411                assert values != null;
412                assert !empty || values.length == 0;
413            }
414    
415            public void print(PrintWriter pw) {
416                if (empty) {
417                    pw.println("SortResult: empty");
418                } else {
419                    pw.println("SortResult {");
420                    for (int i = 0; i < values.length; i++) {
421                        if (i > 0) {
422                            pw.println(",");
423                        }
424                        Object value = values[i];
425                        pw.print(value);
426                    }
427                    pw.println("}");
428                }
429                pw.flush();
430            }
431        }
432    
433        /**
434         * Expression which evaluates an expression to form a list of tuples.
435         * The result is a value of type
436         * {@link mondrian.olap.fun.RankFunDef.RankedMemberList} or
437         * {@link mondrian.olap.fun.RankFunDef.RankedTupleList}, or
438         * null if the list is empty.
439         */
440        private static class RankedListCalc extends AbstractCalc {
441            private final ListCalc listCalc;
442            private final boolean tuple;
443    
444            public RankedListCalc(ListCalc listCalc, boolean tuple) {
445                super(new DummyExp(listCalc.getType()));
446                this.listCalc = listCalc;
447                this.tuple = tuple;
448            }
449    
450            public Calc[] getCalcs() {
451                return new Calc[] {listCalc};
452            }
453    
454            public Object evaluate(Evaluator evaluator) {
455                // Construct an array containing the value of the expression
456                // for each member.
457                if (tuple) {
458                    List<Member[]> tupleList =
459                        ((TupleListCalc) listCalc).evaluateTupleList(evaluator);
460                    assert tupleList != null;
461                    return new RankedTupleList(tupleList);
462                } else {
463                    List<Member> memberList =
464                        ((MemberListCalc) listCalc).evaluateMemberList(evaluator);
465                    assert memberList != null;
466                    return new RankedMemberList(memberList);
467                }
468            }
469        }
470    
471        /**
472         * Data structure which contains a list and can return the position of an
473         * element in the list in O(log N).
474         */
475        static class RankedMemberList {
476            Map<Member, Integer> map = new HashMap<Member, Integer>();
477    
478            RankedMemberList(List<Member> members) {
479                int i = -1;
480                for (final Member member : members) {
481                    ++i;
482                    final Integer value = map.put(member, i);
483                    if (value != null) {
484                        // The list already contained a value for this key -- put
485                        // it back.
486                        map.put(member, value);
487                    }
488                }
489            }
490    
491            int indexOf(Member m) {
492                Integer integer = map.get(m);
493                if (integer == null) {
494                    return -1;
495                } else {
496                    return integer;
497                }
498            }
499        }
500        /**
501         * Data structure which contains a list and can return the position of an
502         * element in the list in O(log N).
503         */
504        static class RankedTupleList {
505            final Map<List<Member>, Integer> map =
506                new HashMap<List<Member>, Integer>();
507    
508            RankedTupleList(List<Member[]> tupleList) {
509                int i = -1;
510                for (final Member[] tupleMembers : tupleList) {
511                    ++i;
512                    final List<Member> key = Arrays.asList(tupleMembers);
513                    final Integer value = map.put(key, i);
514                    if (value != null) {
515                        // The list already contained a value for this key -- put
516                        // it back.
517                        map.put(key, value);
518                    }
519                }
520            }
521    
522            int indexOf(Member[] tupleMembers) {
523                final List<Member> key = Arrays.asList(tupleMembers);
524                Integer integer = map.get(key);
525                if (integer == null) {
526                    return -1;
527                } else {
528                    return integer;
529                }
530            }
531        }
532    }
533    
534    // End RankFunDef.java