001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2006-2011 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.rolap;
011
012import mondrian.calc.*;
013import mondrian.calc.impl.*;
014import mondrian.olap.*;
015import mondrian.olap.type.SetType;
016
017import java.io.PrintWriter;
018import java.io.StringWriter;
019import java.util.*;
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 */
035public class RolapDependencyTestingEvaluator extends RolapEvaluator {
036
037    /**
038     * Creates an dependency-testing evaluator.
039     *
040     * @param result Result we are building
041     * @param expDeps Number of dependencies to check
042     */
043    RolapDependencyTestingEvaluator(RolapResult result, int expDeps) {
044        super(new DteRoot(result, expDeps));
045    }
046
047    /**
048     * Creates a child evaluator.
049     *
050     * @param root Root evaluation context
051     * @param evaluator Parent evaluator
052     */
053    private RolapDependencyTestingEvaluator(
054        RolapEvaluatorRoot root,
055        RolapDependencyTestingEvaluator evaluator,
056        List<List<Member>> aggregationList)
057    {
058        super(root, evaluator, aggregationList);
059    }
060
061    public Object evaluate(
062        Calc calc,
063        Hierarchy[] independentHierarchies,
064        String mdxString)
065    {
066        final DteRoot dteRoot =
067                (DteRoot) root;
068        if (dteRoot.faking) {
069            ++dteRoot.fakeCallCount;
070        } else {
071            ++dteRoot.callCount;
072        }
073        // Evaluate the call for real.
074        final Object result = calc.evaluate(this);
075        if (dteRoot.result.isDirty()) {
076            return result;
077        }
078
079        // If the result is a list and says that it is mutable, see whether it
080        // really is.
081        if (calc.getResultStyle() == ResultStyle.MUTABLE_LIST) {
082            List<Object> list = (List) result;
083            if (list.size() > 0) {
084                final Object zeroth = list.get(0);
085                list.set(0, zeroth);
086            }
087        }
088
089        // Change one of the allegedly independent dimensions and evaluate
090        // again.
091        //
092        // Don't do it if the faking is disabled,
093        // or if we're already faking another dimension,
094        // or if we're filtering out nonempty cells (which makes us
095        // dependent on everything),
096        // or if the ratio of fake evals to real evals is too high (which
097        // would make us too slow).
098        if (dteRoot.disabled
099            || dteRoot.faking
100            || isNonEmpty()
101            || (double) dteRoot.fakeCallCount
102            > (double) dteRoot.callCount * dteRoot.random.nextDouble()
103            * 2 * dteRoot.expDeps)
104        {
105            return result;
106        }
107        if (independentHierarchies.length == 0) {
108            return result;
109        }
110        dteRoot.faking = true;
111        ++dteRoot.fakeCount;
112        ++dteRoot.fakeCallCount;
113        final int i = dteRoot.random.nextInt(independentHierarchies.length);
114        final Member saveMember = getContext(independentHierarchies[i]);
115        final Member otherMember =
116            dteRoot.chooseOtherMember(
117                saveMember, getQuery().getSchemaReader(false));
118        setContext(otherMember);
119        final Object otherResult = calc.evaluate(this);
120        if (false) {
121            System.out.println(
122                "original=" + saveMember.getUniqueName()
123                + ", member=" + otherMember.getUniqueName()
124                + ", originalResult=" + result
125                + ", result=" + otherResult);
126        }
127        if (!equals(otherResult, result)) {
128            final Member[] members = getMembers();
129            final StringBuilder buf = new StringBuilder();
130            for (int j = 0; j < members.length; j++) {
131                if (j > 0) {
132                    buf.append(", ");
133                }
134                buf.append(members[j].getUniqueName());
135            }
136            throw Util.newInternal(
137                "Expression '" + mdxString
138                + "' claims to be independent of dimension "
139                + saveMember.getDimension() + " but is not; context is {"
140                + buf.toString() + "}; First result: "
141                + toString(result) + ", Second result: "
142                + toString(otherResult));
143        }
144        // Restore context.
145        setContext(saveMember);
146        dteRoot.faking = false;
147        return result;
148    }
149
150    public RolapEvaluator _push(List<List<Member>> aggregationList) {
151        return new RolapDependencyTestingEvaluator(root, this, aggregationList);
152    }
153
154    private boolean equals(Object o1, Object o2) {
155        if (o1 == null) {
156            return o2 == null;
157        }
158        if (o2 == null) {
159            return false;
160        }
161        if (o1 instanceof Object[]) {
162            if (o2 instanceof Object[]) {
163                Object[] a1 = (Object[]) o1;
164                Object[] a2 = (Object[]) o2;
165                if (a1.length == a2.length) {
166                    for (int i = 0; i < a1.length; i++) {
167                        if (!equals(a1[i], a2[i])) {
168                            return false;
169                        }
170                    }
171                    return true;
172                }
173            }
174            return false;
175        }
176        if (o1 instanceof List) {
177            return o2 instanceof List
178                && equals(
179                    ((List) o1).toArray(),
180                    ((List) o2).toArray());
181        }
182        if (o1 instanceof Iterable) {
183            if (o2 instanceof Iterable) {
184                return equals(toList((Iterable) o1), toList((Iterable) o2));
185            } else {
186                return false;
187            }
188        }
189        return o1.equals(o2);
190    }
191
192    private String toString(Object o) {
193        StringWriter sw = new StringWriter();
194        PrintWriter pw = new PrintWriter(sw);
195        toString(o, pw);
196        return sw.toString();
197    }
198
199    private <T> List<T> toList(Iterable<T> iterable) {
200        final ArrayList<T> list = new ArrayList<T>();
201        for (T t : iterable) {
202            list.add(t);
203        }
204        return list;
205    }
206
207    private void toString(Object o, PrintWriter pw) {
208        if (o instanceof Object[]) {
209            Object[] a = (Object[]) o;
210            pw.print("{");
211            for (int i = 0; i < a.length; i++) {
212                Object o1 = a[i];
213                if (i > 0) {
214                    pw.print(", ");
215                }
216                toString(o1, pw);
217            }
218            pw.print("}");
219        } else if (o instanceof List) {
220            List list = (List) o;
221            toString(list.toArray(), pw);
222        } else if (o instanceof Member) {
223            Member member = (Member) o;
224            pw.print(member.getUniqueName());
225        } else {
226            pw.print(o);
227        }
228    }
229
230    /**
231     * Holds context for a tree of {@link RolapDependencyTestingEvaluator}.
232     */
233    static class DteRoot extends RolapResult.RolapResultEvaluatorRoot {
234        final int expDeps;
235        int callCount;
236        int fakeCallCount;
237        int fakeCount;
238        boolean faking;
239        boolean disabled;
240        final Random random = Util.createRandom(
241            MondrianProperties.instance().TestSeed.get());
242
243        DteRoot(RolapResult result, int expDeps) {
244            super(result);
245            this.expDeps = expDeps;
246        }
247
248        /**
249         * Chooses another member of the same hierarchy.
250         * The member will come from all levels with the same probability.
251         * Calculated members are not included.
252         *
253         * @param save Previous member
254         * @param schemaReader Schema reader
255         * @return other member of same hierarchy
256         */
257        private Member chooseOtherMember(
258            final Member save,
259            SchemaReader schemaReader)
260        {
261            final Hierarchy hierarchy = save.getHierarchy();
262            int attempt = 0;
263            while (true) {
264                // Choose a random level.
265                final Level[] levels = hierarchy.getLevels();
266                final int levelDepth = random.nextInt(levels.length) + 1;
267                Member member = null;
268                for (int i = 0; i < levelDepth; i++) {
269                    List<Member> members;
270                    if (i == 0) {
271                        members =
272                            schemaReader.getLevelMembers(levels[i], false);
273                    } else {
274                        members = schemaReader.getMemberChildren(member);
275                    }
276                    if (members.size() == 0) {
277                        break;
278                    }
279                    member = members.get(random.nextInt(members.size()));
280                }
281                // If the member chosen happens to be the same as the original
282                // member, try again. Give up after 100 attempts (in case the
283                // hierarchy has only one member).
284                if (member != save || ++attempt > 100) {
285                    return member;
286                }
287            }
288        }
289    }
290
291    /**
292     * Expression which checks dependencies of an underlying scalar expression.
293     */
294    private static class DteScalarCalcImpl extends GenericCalc {
295        private final Calc calc;
296        private final Hierarchy[] independentHierarchies;
297        private final String mdxString;
298
299        DteScalarCalcImpl(
300            Calc calc,
301            Hierarchy[] independentHierarchies,
302            String mdxString)
303        {
304            super(new DummyExp(calc.getType()));
305            this.calc = calc;
306            this.independentHierarchies = independentHierarchies;
307            this.mdxString = mdxString;
308        }
309
310        public Calc[] getCalcs() {
311            return new Calc[] {calc};
312        }
313
314        public Object evaluate(Evaluator evaluator) {
315            RolapDependencyTestingEvaluator dtEval =
316                (RolapDependencyTestingEvaluator) evaluator;
317            return dtEval.evaluate(calc, independentHierarchies, mdxString);
318        }
319
320        public ResultStyle getResultStyle() {
321            return calc.getResultStyle();
322        }
323    }
324
325    /**
326     * Expression which checks dependencies and list immutability of an
327     * underlying list or iterable expression.
328     */
329    private static class DteIterCalcImpl extends GenericIterCalc {
330        private final Calc calc;
331        private final Hierarchy[] independentHierarchies;
332        private final boolean mutableList;
333        private final String mdxString;
334
335        DteIterCalcImpl(
336            Calc calc,
337            Hierarchy[] independentHierarchies,
338            boolean mutableList,
339            String mdxString)
340        {
341            super(new DummyExp(calc.getType()));
342            this.calc = calc;
343            this.independentHierarchies = independentHierarchies;
344            this.mutableList = mutableList;
345            this.mdxString = mdxString;
346        }
347
348        public Calc[] getCalcs() {
349            return new Calc[] {calc};
350        }
351
352        public Object evaluate(Evaluator evaluator) {
353            RolapDependencyTestingEvaluator dtEval =
354                    (RolapDependencyTestingEvaluator) evaluator;
355            return dtEval.evaluate(calc, independentHierarchies, mdxString);
356        }
357
358        public TupleList evaluateList(Evaluator evaluator) {
359            TupleList list = super.evaluateList(evaluator);
360            if (!mutableList) {
361                list = TupleCollections.unmodifiableList(list);
362            }
363            return list;
364        }
365
366        public ResultStyle getResultStyle() {
367            return calc.getResultStyle();
368        }
369    }
370
371    /**
372     * Expression compiler which introduces dependency testing.
373     *
374     * <p>It also checks that the caller does not modify lists unless it has
375     * explicitly asked for a mutable list.
376     */
377    static class DteCompiler extends DelegatingExpCompiler {
378        DteCompiler(ExpCompiler compiler) {
379            super(compiler);
380        }
381
382        protected Calc afterCompile(Exp exp, Calc calc, boolean mutable) {
383            Hierarchy[] dimensions = getIndependentHierarchies(calc);
384            calc = super.afterCompile(exp, calc, mutable);
385            if (calc.getType() instanceof SetType) {
386                return new DteIterCalcImpl(
387                    calc,
388                    dimensions,
389                    mutable,
390                    Util.unparse(exp));
391            } else {
392                return new DteScalarCalcImpl(
393                    calc,
394                    dimensions,
395                    Util.unparse(exp));
396            }
397        }
398
399        /**
400         * Returns the dimensions an expression does not depend on. If the
401         * current member of any of these dimensions changes, the expression
402         * will return the same result.
403         *
404         * @param calc Expression
405         * @return List of dimensions that the expression does not depend on
406         */
407        private Hierarchy[] getIndependentHierarchies(Calc calc) {
408            List<Hierarchy> list = new ArrayList<Hierarchy>();
409            final List<RolapHierarchy> hierarchies =
410                ((RolapCube) getValidator().getQuery().getCube())
411                    .getHierarchies();
412            for (Hierarchy hierarchy : hierarchies) {
413                if (!calc.dependsOn(hierarchy)) {
414                    list.add(hierarchy);
415                }
416            }
417            return list.toArray(new Hierarchy[list.size()]);
418        }
419    }
420}
421
422// End RolapDependencyTestingEvaluator.java