KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > RolapEvaluator


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapEvaluator.java#59 $
3 // This software is subject to the terms of the Common Public License
4 // Agreement, available at the following URL:
5 // http://www.opensource.org/licenses/cpl.html.
6 // Copyright (C) 2001-2002 Kana Software, Inc.
7 // Copyright (C) 2001-2007 Julian Hyde and others
8 // All Rights Reserved.
9 // You must accept the terms of that agreement to use this software.
10 //
11 // jhyde, 10 August, 2001
12 */

13
14 package mondrian.rolap;
15 import mondrian.calc.Calc;
16 import mondrian.calc.ParameterSlot;
17 import mondrian.olap.*;
18 import mondrian.olap.fun.FunUtil;
19 import mondrian.resource.MondrianResource;
20 import mondrian.util.Format;
21
22 import org.apache.log4j.Logger;
23
24 import java.util.*;
25
26 /**
27  * <code>RolapEvaluator</code> evaluates expressions in a dimensional
28  * environment.
29  *
30  * <p>The context contains a member (which may be the default member)
31  * for every dimension in the current cube. Certain operations, such as
32  * evaluating a calculated member or a tuple, change the current context. The
33  * evaluator's {@link #push} method creates a clone of the current evaluator
34  * so that you can revert to the original context once the operation has
35  * completed.
36  *
37  * @author jhyde
38  * @since 10 August, 2001
39  * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapEvaluator.java#59 $
40  */

41 public class RolapEvaluator implements Evaluator {
42     private static final Logger LOGGER = Logger.getLogger(RolapEvaluator.class);
43
44     /**
45      * Dummy value to represent null results in the expression cache.
46      */

47     private static final Object JavaDoc nullResult = new Object JavaDoc();
48
49     private final Member[] currentMembers;
50     private final Evaluator parent;
51     protected CellReader cellReader;
52     private final int depth;
53
54     private Member expandingMember;
55     private boolean nonEmpty;
56     protected final RolapEvaluatorRoot root;
57     private int iterationLength;
58     private boolean evalAxes;
59
60     private final Member[] calcMembers;
61     private int calcMemberCount;
62
63
64     /**
65      * Creates an evaluator.
66      */

67     protected RolapEvaluator(
68             RolapEvaluatorRoot root,
69             RolapEvaluator parent,
70             CellReader cellReader,
71             Member[] currentMembers) {
72         this.root = root;
73         this.parent = parent;
74         if (parent == null) {
75             this.depth = 0;
76             this.nonEmpty = false;
77         } else {
78           this.depth = parent.depth + 1;
79           this.nonEmpty = parent.nonEmpty;
80         }
81         this.iterationLength = 1;
82         this.evalAxes = false;
83
84         this.cellReader = cellReader;
85         if (currentMembers == null) {
86             this.currentMembers = new Member[root.cube.getDimensions().length];
87         } else {
88             this.currentMembers = currentMembers;
89         }
90         calcMembers = new Member[this.currentMembers.length];
91         calcMemberCount = 0;
92         for (Member member : this.currentMembers) {
93             if (member != null && member.isCalculated()) {
94                 addCalcMember(member);
95             }
96         }
97     }
98
99     /**
100      * Creates an evaluator with no parent.
101      *
102      * @param root Shared context between this evaluator and its children
103      */

104     public RolapEvaluator(RolapEvaluatorRoot root) {
105         this(root, null, null, null);
106
107         // we expect client to set CellReader
108

109         SchemaReader scr = this.root.connection.getSchemaReader();
110         Dimension[] dimensions = this.root.cube.getDimensions();
111         for (final Dimension dimension : dimensions) {
112             final int ordinal = dimension.getOrdinal(this.root.cube);
113             final Hierarchy hier = dimension.getHierarchy();
114
115             Member member = scr.getHierarchyDefaultMember(hier);
116
117             // If there is no member, we cannot continue.
118
if (member == null) {
119                 throw MondrianResource.instance().InvalidHierarchyCondition
120                     .ex(hier.getUniqueName());
121             }
122
123             HierarchyUsage[] hierarchyUsages = this.root.cube.getUsages(hier);
124             if (hierarchyUsages.length != 0) {
125                 ((RolapMember) member).makeUniqueName(hierarchyUsages[0]);
126             }
127
128             currentMembers[ordinal] = member;
129             if (member.isCalculated()) {
130                 addCalcMember(member);
131             }
132         }
133
134         root.init(this);
135     }
136
137     /**
138      * Creates an evaluator.
139      */

140     public static Evaluator create(Query query) {
141         final RolapEvaluatorRoot root = new RolapEvaluatorRoot(query);
142         return new RolapEvaluator(root);
143     }
144
145     protected static class RolapEvaluatorRoot {
146         final Map<Object JavaDoc, Object JavaDoc> expResultCache =
147             new HashMap<Object JavaDoc, Object JavaDoc>();
148         final RolapCube cube;
149         final RolapConnection connection;
150         final SchemaReader schemaReader;
151         final Map<Exp, Calc> compiledExps = new HashMap<Exp, Calc>();
152         final private Query query;
153
154         public RolapEvaluatorRoot(Query query) {
155             this.query = query;
156             this.cube = (RolapCube) query.getCube();
157             this.connection = (RolapConnection) query.getConnection();
158             this.schemaReader = query.getSchemaReader(true);
159         }
160
161         /**
162          * Implements a cheap-and-cheerful mapping from expressions to compiled
163          * expressions.
164          *
165          * <p>TODO: Save compiled expressions somewhere better.
166          */

167         Calc getCompiled(Exp exp, boolean scalar) {
168             Calc calc = compiledExps.get(exp);
169             if (calc == null) {
170                 calc = query.compileExpression(exp, scalar);
171                 compiledExps.put(exp, calc);
172             }
173             return calc;
174         }
175
176         /**
177          * Evaluates a named set.
178          *
179          * <p>The default implementation throws
180          * {@link UnsupportedOperationException}.
181          */

182         protected Object JavaDoc evaluateNamedSet(String JavaDoc name, Exp exp) {
183             throw new UnsupportedOperationException JavaDoc();
184         }
185
186         /**
187          * Clears cached values for all named sets.
188          */

189         protected void clearNamedSets() {
190         }
191
192         /**
193          * First evaluator calls this method on construction.
194          */

195         protected void init(Evaluator evaluator) {
196         }
197
198         /**
199          * Returns the value of a parameter, evaluating its default expression
200          * if necessary.
201          *
202          * <p>The default implementation throws
203          * {@link UnsupportedOperationException}.
204          */

205         public Object JavaDoc getParameterValue(ParameterSlot slot) {
206             throw new UnsupportedOperationException JavaDoc();
207         }
208     }
209
210     protected Logger getLogger() {
211         return LOGGER;
212     }
213
214     public Member[] getMembers() {
215         return currentMembers;
216     }
217
218     void setCellReader(CellReader cellReader) {
219         this.cellReader = cellReader;
220     }
221
222     public Cube getCube() {
223         return root.cube;
224     }
225
226     public Query getQuery() {
227         return root.query;
228     }
229
230     public int getDepth() {
231         return depth;
232     }
233
234     public Evaluator getParent() {
235         return parent;
236     }
237
238     public SchemaReader getSchemaReader() {
239         return root.schemaReader;
240     }
241
242     public Evaluator push(Member[] members) {
243         final RolapEvaluator evaluator = _push();
244         evaluator.setContext(members);
245         return evaluator;
246     }
247
248     public Evaluator push(Member member) {
249         final RolapEvaluator evaluator = _push();
250         evaluator.setContext(member);
251         return evaluator;
252     }
253
254     public Evaluator push() {
255         return _push();
256     }
257
258     /**
259      * Creates a clone of the current validator.
260      */

261     protected RolapEvaluator _push() {
262         getQuery().checkCancelOrTimeout();
263         Member[] cloneCurrentMembers = currentMembers.clone();
264         RolapEvaluator newEvaluator =
265             new RolapEvaluator(
266                 root,
267                 this,
268                 cellReader,
269                 cloneCurrentMembers);
270         newEvaluator.setEvalAxes(evalAxes);
271         return newEvaluator;
272     }
273
274     public Evaluator pop() {
275         return parent;
276     }
277
278     /**
279      * Returns true if the other object is a {@link RolapEvaluator} with
280      * identical context.
281      */

282     public boolean equals(Object JavaDoc obj) {
283         if (!(obj instanceof RolapEvaluator)) {
284             return false;
285         }
286         RolapEvaluator that = (RolapEvaluator) obj;
287         return Arrays.equals(this.currentMembers, that.currentMembers);
288     }
289
290 /**
291  * RME remove before checkin
292  * This is for debugging only
293  * For use in debugging Checkin_7634
294  */

295 public void printCurrentMemberNames() {
296     for (int i = 0; i < currentMembers.length; i++) {
297         Member m = currentMembers[i];
298         if (m == null) {
299         System.out.println("RolapEvaluator.printCurrentMemberNames: i="+i+", member NULL");
300         } else {
301         System.out.println("RolapEvaluator.printCurrentMemberNames: i="+i+", member="+m.getUniqueName());
302         }
303     }
304 }
305
306     /**
307      * Replace the current member of a given hierarchy with member parameter
308      * if the current member is the null, all, or default member.
309      * The parameter member will never attempt to replace a current member
310      * when the current member is part of the slicer axis (this is
311      * addressed in the RolapResult purge method).
312      * <p>
313      * Currently, replacement only takes place when the current member is
314      * the default member of its hierarchy (so the checks for null, measure and
315      * all may not be needed).
316      *
317      * @param member
318      * @return Previous member
319      */

320     Member setContextConditional(Member member) {
321         RolapMember m = (RolapMember) member;
322         int ordinal = m.getDimension().getOrdinal(root.cube);
323         // should never happend
324
if (ordinal >= currentMembers.length) {
325             return null;
326         }
327         Member previous = currentMembers[ordinal];
328         if (previous.isNull()) {
329             // Is the ever possible?
330
return setContext(member);
331         } else if (previous.isMeasure()) {
332             // Is the ever possible?
333
return setContext(member);
334         } else if (previous.isAll()) {
335             // Is the ever possible?
336
return setContext(member);
337         } else {
338             Hierarchy heirarchy = m.getHierarchy();
339             Member defaultMember = heirarchy.getDefaultMember();
340             if (previous.equals(defaultMember) && ! previous.equals(member)) {
341                 return setContext(member);
342             } else {
343                 return null;
344             }
345         }
346     }
347
348     public Member setContext(Member member) {
349         RolapMember m = (RolapMember) member;
350         int ordinal = m.getDimension().getOrdinal(root.cube);
351         Member previous = currentMembers[ordinal];
352         if (previous.isCalculated()) {
353             removeCalcMember(previous);
354         }
355         currentMembers[ordinal] = m;
356         if (m.isCalculated()) {
357             addCalcMember(m);
358         }
359         return previous;
360     }
361
362     public void setContext(List<Member> memberList) {
363         int i = 0;
364         for (Member member: memberList) {
365             // more than one usage
366
if (member == null) {
367                 if (getLogger().isDebugEnabled()) {
368                     getLogger().debug(
369                         "RolapEvaluator.setContext: member == null "
370                          + " , count=" + i);
371                 }
372                 assert false;
373             } else {
374                 setContext(member);
375             }
376             i++;
377         }
378     }
379     public void setContext(Member[] members) {
380         for (int i = 0; i < members.length; i++) {
381             Member member = members[i];
382
383             // more than one usage
384
if (member == null) {
385                 if (getLogger().isDebugEnabled()) {
386                     getLogger().debug(
387                         "RolapEvaluator.setContext: member == null "
388                          + " , count=" + i);
389                 }
390                 assert false;
391                 continue;
392             }
393
394             setContext(member);
395         }
396     }
397
398     public Member getContext(Dimension dimension) {
399         return currentMembers[dimension.getOrdinal(root.cube)];
400     }
401
402     public Object JavaDoc evaluateCurrent() {
403         // Get the member in the current context which is (a) calculated, and
404
// (b) has the highest solve order; returns null if there are no
405
// calculated members.
406
Member maxSolveMember = peekCalcMember();
407         if (maxSolveMember == null) {
408             Object JavaDoc o = cellReader.get(this);
409             if (o == Util.nullValue) {
410                 o = null;
411             }
412             return o;
413         }
414         RolapMember defaultMember = (RolapMember)
415                 maxSolveMember.getHierarchy().getDefaultMember();
416         Util.assertTrue(
417                 defaultMember != maxSolveMember,
418                 "default member must not be calculated");
419         RolapEvaluator evaluator = (RolapEvaluator) push(defaultMember);
420         evaluator.setExpanding(maxSolveMember);
421         final Exp exp = maxSolveMember.getExpression();
422         Calc calc = root.getCompiled(exp, true);
423         Object JavaDoc o = calc.evaluate(evaluator);
424         if (o == Util.nullValue) {
425             o = null;
426         }
427         return o;
428     }
429
430     private void setExpanding(Member member) {
431         expandingMember = member;
432         int memberCount = currentMembers.length;
433         if (depth > memberCount) {
434             if (depth % memberCount == 0) {
435                 checkRecursion((RolapEvaluator) parent);
436             }
437         }
438     }
439
440     /**
441      * Makes sure that there is no evaluator with identical context on the
442      * stack.
443      *
444      * @throws mondrian.olap.fun.MondrianEvaluationException if there is a loop
445      */

446     private static void checkRecursion(RolapEvaluator eval) {
447         // Find the nearest ancestor which is expanding a calculated member.
448
// (The starting evaluator has just been pushed, so may not have the
449
// state it will have when recursion happens.)
450
while (true) {
451             if (eval == null) {
452                 return;
453             }
454             if (eval.expandingMember != null) {
455                 break;
456             }
457             eval = (RolapEvaluator) eval.getParent();
458         }
459
460         outer:
461         for (RolapEvaluator eval2 = (RolapEvaluator) eval.getParent();
462                  eval2 != null;
463                  eval2 = (RolapEvaluator) eval2.getParent()) {
464             if (eval2.expandingMember != eval.expandingMember) {
465                 continue;
466             }
467             for (int i = 0; i < eval.currentMembers.length; i++) {
468                 Member member = eval2.currentMembers[i];
469
470                 // more than one usage
471
if (member == null) {
472                     if (LOGGER.isDebugEnabled()) {
473                         LOGGER.debug(
474                             "RolapEvaluator.checkRecursion: member == null "
475                              + " , count=" + i);
476                     }
477                     continue;
478                 }
479
480                 Member parentMember = eval.getContext(member.getDimension());
481                 if (member != parentMember) {
482                     continue outer;
483                 }
484             }
485             throw FunUtil.newEvalException(null,
486                 "Infinite loop while evaluating calculated member '" +
487                 eval.expandingMember + "'; context stack is " +
488                 eval.getContextString());
489         }
490     }
491
492     private String JavaDoc getContextString() {
493         boolean skipDefaultMembers = true;
494         StringBuilder JavaDoc buf = new StringBuilder JavaDoc("{");
495         int frameCount = 0;
496         for (RolapEvaluator eval = this; eval != null;
497                  eval = (RolapEvaluator) eval.getParent()) {
498             if (eval.expandingMember == null) {
499                 continue;
500             }
501             if (frameCount++ > 0) {
502                 buf.append(", ");
503             }
504             buf.append("(");
505             int memberCount = 0;
506             for (Member m : eval.currentMembers) {
507                 if (skipDefaultMembers &&
508                     m == m.getHierarchy().getDefaultMember()) {
509                     continue;
510                 }
511                 if (memberCount++ > 0) {
512                     buf.append(", ");
513                 }
514                 buf.append(m.getUniqueName());
515             }
516             buf.append(")");
517         }
518         buf.append("}");
519         return buf.toString();
520     }
521
522     public Object JavaDoc getProperty(String JavaDoc name, Object JavaDoc defaultValue) {
523         Object JavaDoc o = defaultValue;
524         int maxSolve = Integer.MIN_VALUE;
525         for (int i = 0; i < currentMembers.length; i++) {
526             Member member = currentMembers[i];
527
528             // more than one usage
529
if (member == null) {
530                 if (getLogger().isDebugEnabled()) {
531                     getLogger().debug(
532                         "RolapEvaluator.getProperty: member == null "
533                          + " , count=" + i);
534                 }
535                 continue;
536             }
537
538             Object JavaDoc p = member.getPropertyValue(name);
539             if (p != null) {
540                 int solve = member.getSolveOrder();
541                 if (solve > maxSolve) {
542                     o = p;
543                     maxSolve = solve;
544                 }
545             }
546         }
547         return o;
548     }
549
550     /**
551      * Returns the format string for this cell. This is computed by evaluating
552      * the format expression in the current context, and therefore different
553      * cells may have different format strings.
554      *
555      * @post return != null
556      */

557     public String JavaDoc getFormatString() {
558         Exp formatExp = (Exp) getProperty(Property.FORMAT_EXP.name, null);
559         if (formatExp == null) {
560             return "Standard";
561         }
562         Calc formatCalc = root.getCompiled(formatExp, true);
563         Object JavaDoc o = formatCalc.evaluate(this);
564         if (o == null) {
565             return "Standard";
566         }
567         return o.toString();
568     }
569
570     private Format getFormat() {
571         String JavaDoc formatString = getFormatString();
572         return getFormat(formatString);
573     }
574
575     private Format getFormat(String JavaDoc formatString) {
576         return Format.get(formatString, root.connection.getLocale());
577     }
578
579     public Locale getConnectionLocale() {
580         return root.connection.getLocale();
581     }
582
583     /**
584      * Converts a value of this member into a string according to this member's
585      * format specification.
586      */

587     String JavaDoc format(Evaluator evaluator, Object JavaDoc o) {
588         return getFormat().format(o);
589     }
590
591     public String JavaDoc format(Object JavaDoc o) {
592         if (o == Util.nullValue) {
593             Format format = getFormat();
594             return format.format(null);
595         } else if (o instanceof Throwable JavaDoc) {
596             return "#ERR: " + o.toString();
597         } else if (o instanceof String JavaDoc) {
598             return (String JavaDoc) o;
599         } else {
600             Format format = getFormat();
601             return format.format(o);
602         }
603     }
604
605     public String JavaDoc format(Object JavaDoc o, String JavaDoc formatString) {
606         if (o == Util.nullValue) {
607             Format format = getFormat(formatString);
608             return format.format(null);
609         } else if (o instanceof Throwable JavaDoc) {
610             return "#ERR: " + o.toString();
611         } else if (o instanceof String JavaDoc) {
612             return (String JavaDoc) o;
613         } else {
614             Format format = getFormat(formatString);
615             return format.format(o);
616         }
617     }
618
619     /**
620      * Creates a key which uniquely identifes an expression and its
621      * context. The context includes members of dimensions which the
622      * expression is dependent upon.
623      */

624     private Object JavaDoc getExpResultCacheKey(ExpCacheDescriptor descriptor) {
625         List<Object JavaDoc> key = new ArrayList<Object JavaDoc>();
626         key.add(descriptor.getExp());
627         int[] dimensionOrdinals = descriptor.getDependentDimensionOrdinals();
628         for (int i = 0; i < dimensionOrdinals.length; i++) {
629             int dimensionOrdinal = dimensionOrdinals[i];
630             Member member = currentMembers[dimensionOrdinal];
631
632             // more than one usage
633
if (member == null) {
634                 getLogger().debug(
635                         "RolapEvaluator.getExpResultCacheKey: " +
636                         "member == null; dimensionOrdinal=" + i);
637                 continue;
638             }
639
640             key.add(member);
641         }
642         return key;
643     }
644
645     public Object JavaDoc getCachedResult(ExpCacheDescriptor cacheDescriptor) {
646         // Look up a cached result, and if not present, compute one and add to
647
// cache. Use a dummy value to represent nulls.
648
Object JavaDoc key = getExpResultCacheKey(cacheDescriptor);
649         Object JavaDoc result = root.expResultCache.get(key);
650         if (result == null) {
651             result = cacheDescriptor.evaluate(this);
652             root.expResultCache.put(key, result == null ? nullResult : result);
653         } else if (result == nullResult) {
654             result = null;
655         }
656         return result;
657     }
658
659     public void clearExpResultCache() {
660         root.expResultCache.clear();
661
662         // Clear cached named sets at the same time we clear other cached
663
// expressions. This may be overconservative in some cases, but
664
// without this, there can be bugs when incorrect results get cached
665
// for named sets which depend on measures which haven't been loaded
666
// yet. See mondrian.test.clearview.CVBasicTest.testLer4260 for an
667
// example.
668
root.clearNamedSets();
669     }
670
671     public boolean isNonEmpty() {
672         return nonEmpty;
673     }
674
675     public void setNonEmpty(boolean nonEmpty) {
676         this.nonEmpty = nonEmpty;
677     }
678
679     public RuntimeException JavaDoc newEvalException(Object JavaDoc context, String JavaDoc s) {
680         return FunUtil.newEvalException((FunDef) context, s);
681     }
682
683     public Object JavaDoc evaluateNamedSet(String JavaDoc name, Exp exp) {
684         return root.evaluateNamedSet(name, exp);
685     }
686
687     public int getMissCount() {
688         return cellReader.getMissCount();
689     }
690
691     public Object JavaDoc getParameterValue(ParameterSlot slot) {
692         return root.getParameterValue(slot);
693     }
694
695     void addCalcMember(Member member) {
696         assert member != null;
697         assert member.isCalculated();
698         calcMembers[calcMemberCount++] = member;
699     }
700
701     private Member peekCalcMember() {
702         switch (calcMemberCount) {
703         case 0:
704             return null;
705
706         case 1:
707             return calcMembers[0];
708
709         default:
710             // Find member with the highest solve order.
711
Member maxSolveMember = calcMembers[0];
712             int maxSolve = maxSolveMember.getSolveOrder();
713             for (int i = 1; i < calcMemberCount; i++) {
714                 Member member = calcMembers[i];
715                 int solve = member.getSolveOrder();
716                 if (solve >= maxSolve) {
717                     // If solve orders tie, the dimension with the lower
718
// ordinal wins.
719
if (solve > maxSolve ||
720                             member.getDimension().getOrdinal(root.cube) <
721                             maxSolveMember.getDimension().getOrdinal(
722                                     root.cube)) {
723                         maxSolve = solve;
724                         maxSolveMember = member;
725                     }
726                 }
727             }
728             return maxSolveMember;
729         }
730     }
731
732     private void removeCalcMember(Member previous) {
733         for (int i = 0; i < calcMemberCount; i++) {
734             Member calcMember = calcMembers[i];
735             if (calcMember == previous) {
736                 // overwrite this member with the end member
737
--calcMemberCount;
738                 calcMembers[i] = calcMembers[calcMemberCount];
739                 calcMembers[calcMemberCount] = null; // to allow gc
740
}
741         }
742     }
743
744     public int getIterationLength() {
745         return iterationLength;
746     }
747
748     public void setIterationLength(int length) {
749         iterationLength = length;
750     }
751
752     public boolean isEvalAxes() {
753         return evalAxes;
754     }
755
756     public void setEvalAxes(boolean evalAxes) {
757         this.evalAxes = evalAxes;
758     }
759 }
760
761 // End RolapEvaluator.java
762
Popular Tags