001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/RolapDependencyTestingEvaluator.java#11 $
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.rolap;
011    
012    import mondrian.olap.*;
013    import mondrian.calc.*;
014    import mondrian.calc.impl.DelegatingExpCompiler;
015    import mondrian.calc.impl.GenericCalc;
016    
017    import java.util.*;
018    import java.io.StringWriter;
019    import java.io.PrintWriter;
020    
021    /**
022     * Evaluator which checks dependencies of expressions.
023     *
024     * <p>For each expression evaluation, this valuator evaluates each
025     * expression more times, and makes sure that the results of the expression
026     * are independent of dimensions which the expression claims to be
027     * independent of.
028     *
029     * <p>Since it evaluates each expression twice, it also exposes function
030     * implementations which change the context of the evaluator.
031     *
032     * @author jhyde
033     * @since September, 2005
034     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapDependencyTestingEvaluator.java#11 $
035     */
036    public class RolapDependencyTestingEvaluator extends RolapEvaluator {
037    
038        /**
039         * Creates an dependency-testing evaluator.
040         *
041         * @param result Result we are building
042         * @param expDeps Number of dependencies to check
043         */
044        RolapDependencyTestingEvaluator(RolapResult result, int expDeps) {
045            super(new DteRoot(result, expDeps));
046        }
047    
048        /**
049         * Creates a child evaluator.
050         */
051        private RolapDependencyTestingEvaluator(
052            RolapEvaluatorRoot root,
053            RolapDependencyTestingEvaluator evaluator)
054        {
055            super(root, evaluator);
056        }
057    
058        public Object evaluate(
059            Calc calc,
060            Dimension[] independentDimensions,
061            String mdxString)
062        {
063            final DteRoot dteRoot =
064                    (DteRoot) root;
065            if (dteRoot.faking) {
066                ++dteRoot.fakeCallCount;
067            } else {
068                ++dteRoot.callCount;
069            }
070            // Evaluate the call for real.
071            final Object result = calc.evaluate(this);
072            if (dteRoot.result.isDirty()) {
073                return result;
074            }
075    
076            // If the result is a list and says that it is mutable, see whether it
077            // really is.
078            if (calc.getResultStyle() == ResultStyle.MUTABLE_LIST) {
079                List<Object> list = (List) result;
080                if (list.size() > 0) {
081                    final Object zeroth = list.get(0);
082                    list.set(0, zeroth);
083                }
084            }
085    
086            // Change one of the allegedly independent dimensions and evaluate
087            // again.
088            //
089            // Don't do it if the faking is disabled,
090            // or if we're already faking another dimension,
091            // or if we're filtering out nonempty cells (which makes us
092            // dependent on everything),
093            // or if the ratio of fake evals to real evals is too high (which
094            // would make us too slow).
095            if (dteRoot.disabled ||
096                    dteRoot.faking ||
097                    isNonEmpty() ||
098                    (double) dteRoot.fakeCallCount >
099                    (double) dteRoot.callCount * dteRoot.random.nextDouble() *
100                    2 * dteRoot.expDeps) {
101                return result;
102            }
103            if (independentDimensions.length == 0) {
104                return result;
105            }
106            dteRoot.faking = true;
107            ++dteRoot.fakeCount;
108            ++dteRoot.fakeCallCount;
109            final int i = dteRoot.random.nextInt(independentDimensions.length);
110            final Member saveMember = getContext(independentDimensions[i]);
111            final Member otherMember =
112                    dteRoot.chooseOtherMember(
113                            saveMember, getQuery().getSchemaReader(false));
114            setContext(otherMember);
115            final Object otherResult = calc.evaluate(this);
116            if (false) {
117                System.out.println(
118                        "original=" + saveMember.getUniqueName() +
119                        ", member=" + otherMember.getUniqueName() +
120                        ", originalResult=" + result + "" +
121                        ", result=" + otherResult);
122            }
123            if (!equals(otherResult, result)) {
124                final Member[] members = getMembers();
125                final StringBuilder buf = new StringBuilder();
126                for (int j = 0; j < members.length; j++) {
127                    if (j > 0) {
128                        buf.append(", ");
129                    }
130                    buf.append(members[j].getUniqueName());
131                }
132                throw Util.newInternal(
133                        "Expression '" + mdxString +
134                        "' claims to be independent of dimension " +
135                        saveMember.getDimension() + " but is not; context is {" +
136                        buf.toString() + "}; First result: " +
137                        toString(result) + ", Second result: " +
138                        toString(otherResult));
139            }
140            // Restore context.
141            setContext(saveMember);
142            dteRoot.faking = false;
143            return result;
144        }
145    
146        public RolapEvaluator _push() {
147            return new RolapDependencyTestingEvaluator(root, this);
148        }
149    
150        private boolean equals(Object o1, Object o2) {
151            if (o1 == null) {
152                return o2 == null;
153            }
154            if (o2 == null) {
155                return false;
156            }
157            if (o1 instanceof Object[]) {
158                if (o2 instanceof Object[]) {
159                    Object[] a1 = (Object[]) o1;
160                    Object[] a2 = (Object[]) o2;
161                    if (a1.length == a2.length) {
162                        for (int i = 0; i < a1.length; i++) {
163                            if (!equals(a1[i], a2[i])) {
164                                return false;
165                            }
166                        }
167                        return true;
168                    }
169                }
170                return false;
171            }
172            if (o1 instanceof List) {
173                return o2 instanceof List &&
174                    equals(
175                        ((List) o1).toArray(),
176                        ((List) o2).toArray());
177            }
178            return o1.equals(o2);
179        }
180    
181        private String toString(Object o) {
182            StringWriter sw = new StringWriter();
183            PrintWriter pw = new PrintWriter(sw);
184            toString(o, pw);
185            return sw.toString();
186        }
187    
188        private void toString(Object o, PrintWriter pw) {
189            if (o instanceof Object[]) {
190                Object[] a = (Object[]) o;
191                pw.print("{");
192                for (int i = 0; i < a.length; i++) {
193                    Object o1 = a[i];
194                    if (i > 0) {
195                        pw.print(", ");
196                    }
197                    toString(o1, pw);
198                }
199                pw.print("}");
200            } else if (o instanceof List) {
201                List list = (List) o;
202                toString(list.toArray(), pw);
203            } else if (o instanceof Member) {
204                Member member = (Member) o;
205                pw.print(member.getUniqueName());
206            } else {
207                pw.print(o);
208            }
209        }
210    
211        /**
212         * Holds context for a tree of {@link RolapDependencyTestingEvaluator}.
213         */
214        static class DteRoot extends RolapResult.RolapResultEvaluatorRoot {
215            final int expDeps;
216            final RolapResult result;
217            int callCount;
218            int fakeCallCount;
219            int fakeCount;
220            boolean faking;
221            boolean disabled;
222            final Random random = Util.createRandom(
223                    MondrianProperties.instance().TestSeed.get());
224    
225            DteRoot(RolapResult result, int expDeps) {
226                super(result);
227                this.expDeps = expDeps;
228                this.result = result;
229            }
230    
231            /**
232             * Chooses another member of the same hierarchy.
233             * The member will come from all levels with the same probability.
234             * Calculated members are not included.
235             *
236             * @param save Previous member
237             * @param schemaReader Schema reader
238             * @return other member of same hierarchy
239             */
240            private Member chooseOtherMember(
241                    final Member save, SchemaReader schemaReader) {
242                final Hierarchy hierarchy = save.getHierarchy();
243                int attempt = 0;
244                while (true) {
245                    // Choose a random level.
246                    final Level[] levels = hierarchy.getLevels();
247                    final int levelDepth = random.nextInt(levels.length) + 1;
248                    Member member = null;
249                    for (int i = 0; i < levelDepth; i++) {
250                        List<Member> members;
251                        if (i == 0) {
252                            members = schemaReader.getLevelMembers(levels[i], false);
253                        } else {
254                            members = schemaReader.getMemberChildren(member);
255                        }
256                        if (members.size() == 0) {
257                            break;
258                        }
259                        member = members.get(random.nextInt(members.size()));
260                    }
261                    // If the member chosen happens to be the same as the original
262                    // member, try again. Give up after 100 attempts (in case the
263                    // hierarchy has only one member).
264                    if (member != save || ++attempt > 100) {
265                        return member;
266                    }
267                }
268            }
269        }
270    
271        /**
272         * Expression which checks dependencies and list immutability.
273         */
274        private static class DteCalcImpl extends GenericCalc {
275            private final Calc calc;
276            private final Dimension[] independentDimensions;
277            private final boolean mutableList;
278            private final String mdxString;
279    
280            DteCalcImpl(
281                    Calc calc,
282                    Dimension[] independentDimensions,
283                    boolean mutableList,
284                    String mdxString) {
285                super(new DummyExp(calc.getType()));
286                this.calc = calc;
287                this.independentDimensions = independentDimensions;
288                this.mutableList = mutableList;
289                this.mdxString = mdxString;
290            }
291    
292            public Calc[] getCalcs() {
293                return new Calc[] {calc};
294            }
295    
296            public Object evaluate(Evaluator evaluator) {
297                RolapDependencyTestingEvaluator dtEval =
298                        (RolapDependencyTestingEvaluator) evaluator;
299                return dtEval.evaluate(calc, independentDimensions, mdxString);
300            }
301    
302            public List evaluateList(Evaluator evaluator) {
303                List<?> list = super.evaluateList(evaluator);
304                if (!mutableList) {
305                    list = Collections.unmodifiableList(list);
306                }
307                return list;
308            }
309    
310            public ResultStyle getResultStyle() {
311                return calc.getResultStyle();
312            }
313        }
314    
315        /**
316         * Expression compiler which introduces dependency testing.
317         *
318         * <p>It also checks that the caller does not modify lists unless it has
319         * explicitly asked for a mutable list.
320         */
321        static class DteCompiler extends DelegatingExpCompiler {
322            DteCompiler(ExpCompiler compiler) {
323                super(compiler);
324            }
325    
326            protected Calc afterCompile(Exp exp, Calc calc, boolean mutable) {
327                Dimension[] dimensions = getIndependentDimensions(calc);
328                calc = super.afterCompile(exp, calc, mutable);
329                return new DteCalcImpl(
330                        calc,
331                        dimensions,
332                        mutable,
333                        Util.unparse(exp));
334            }
335    
336            /**
337             * Returns the dimensions an expression depends on.
338             */
339            private Dimension[] getIndependentDimensions(Calc calc) {
340                List<Dimension> indDimList = new ArrayList<Dimension>();
341                final Dimension[] dims =
342                    getValidator().getQuery().getCube().getDimensions();
343                for (Dimension dim : dims) {
344                    if (!calc.dependsOn(dim)) {
345                        indDimList.add(dim);
346                    }
347                }
348                return indDimList.toArray(new Dimension[indDimList.size()]);
349            }
350        }
351    }
352    
353    // End RolapDependencyTestingEvaluator.java