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.calc.impl;
011
012import mondrian.calc.*;
013import mondrian.mdx.MemberExpr;
014import mondrian.mdx.UnresolvedFunCall;
015import mondrian.olap.*;
016import mondrian.olap.fun.*;
017import mondrian.olap.type.*;
018import mondrian.olap.type.DimensionType;
019import mondrian.olap.type.LevelType;
020import mondrian.resource.MondrianResource;
021
022import java.util.*;
023
024/**
025 * Abstract implementation of the {@link mondrian.calc.ExpCompiler} interface.
026 *
027 * @author jhyde
028 * @since Sep 29, 2005
029 */
030public class AbstractExpCompiler implements ExpCompiler {
031    private final Evaluator evaluator;
032    private final Validator validator;
033    private final Map<Parameter, ParameterSlotImpl> parameterSlots =
034        new HashMap<Parameter, ParameterSlotImpl>();
035    private List<ResultStyle> resultStyles;
036
037    /**
038     * Creates an AbstractExpCompiler
039     *
040     * @param evaluator Evaluator
041     * @param validator Validator
042     */
043    public AbstractExpCompiler(Evaluator evaluator, Validator validator) {
044        this(evaluator, validator, ResultStyle.ANY_LIST);
045    }
046
047    /**
048     * Creates an AbstractExpCompiler which is constrained to produce one of
049     * a set of result styles.
050     *
051     * @param evaluator Evaluator
052     * @param validator Validator
053     * @param resultStyles List of result styles, preferred first, must not be
054     */
055    public AbstractExpCompiler(
056        Evaluator evaluator,
057        Validator validator,
058        List<ResultStyle> resultStyles)
059    {
060        this.evaluator = evaluator;
061        this.validator = validator;
062        this.resultStyles = (resultStyles == null)
063            ? ResultStyle.ANY_LIST : resultStyles;
064    }
065
066    public Evaluator getEvaluator() {
067        return evaluator;
068    }
069
070    public Validator getValidator() {
071        return validator;
072    }
073
074    /**
075     * {@inheritDoc}
076     *
077     * Uses the current ResultStyle to compile the expression.
078     */
079    public Calc compile(Exp exp) {
080        return exp.accept(this);
081    }
082
083    /**
084     * {@inheritDoc}
085     *
086     * Uses a new ResultStyle to compile the expression.
087     */
088    public Calc compileAs(
089        Exp exp,
090        Type resultType,
091        List<ResultStyle> preferredResultTypes)
092    {
093        assert preferredResultTypes != null;
094        int substitutions = 0;
095        if (Util.Retrowoven) {
096            // Copy and replace ITERABLE
097            // A number of functions declare that they can accept
098            // ITERABLEs so here is where that those are converted to innocent
099            // LISTs for jdk1.4 and other retrowoven code.
100            List<ResultStyle> tmp =
101                new ArrayList<ResultStyle>(preferredResultTypes.size());
102            for (ResultStyle preferredResultType : preferredResultTypes) {
103                if (preferredResultType == ResultStyle.ITERABLE) {
104                    preferredResultType = ResultStyle.LIST;
105                    ++substitutions;
106                }
107                tmp.add(preferredResultType);
108            }
109            preferredResultTypes = tmp;
110        }
111        List<ResultStyle> save = this.resultStyles;
112        try {
113            this.resultStyles = preferredResultTypes;
114            if (resultType != null && resultType != exp.getType()) {
115                if (resultType instanceof MemberType) {
116                    return compileMember(exp);
117                } else if (resultType instanceof LevelType) {
118                    return compileLevel(exp);
119                } else if (resultType instanceof HierarchyType) {
120                    return compileHierarchy(exp);
121                } else if (resultType instanceof DimensionType) {
122                    return compileDimension(exp);
123                } else if (resultType instanceof ScalarType) {
124                    return compileScalar(exp, false);
125                }
126            }
127            final Calc calc = compile(exp);
128            if (substitutions > 0) {
129                final IterCalc iterCalc = (IterCalc) calc;
130                if (iterCalc == null) {
131                    this.resultStyles =
132                        Collections.singletonList(ResultStyle.ITERABLE);
133                    return compile(exp);
134                } else {
135                    return iterCalc;
136                }
137            }
138            return calc;
139        } finally {
140            this.resultStyles = save;
141        }
142    }
143
144    public MemberCalc compileMember(Exp exp) {
145        final Type type = exp.getType();
146        if (type instanceof HierarchyType) {
147            final HierarchyCalc hierarchyCalc = compileHierarchy(exp);
148            return hierarchyToMember(hierarchyCalc);
149        } else if (type instanceof NullType) {
150            throw MondrianResource.instance().NullNotSupported.ex();
151        } else if (type instanceof DimensionType) {
152            final HierarchyCalc hierarchyCalc = compileHierarchy(exp);
153            return hierarchyToMember(hierarchyCalc);
154        }
155        assert type instanceof MemberType : type;
156        return (MemberCalc) compile(exp);
157    }
158
159    private MemberCalc hierarchyToMember(
160        HierarchyCalc hierarchyCalc)
161    {
162        final Hierarchy hierarchy = hierarchyCalc.getType().getHierarchy();
163        if (hierarchy != null) {
164            return new HierarchyCurrentMemberFunDef.FixedCalcImpl(
165                new DummyExp(TypeUtil.toMemberType(hierarchyCalc.getType())),
166                hierarchy);
167        } else {
168            return new HierarchyCurrentMemberFunDef.CalcImpl(
169                new DummyExp(TypeUtil.toMemberType(hierarchyCalc.getType())),
170                hierarchyCalc);
171        }
172    }
173
174    public LevelCalc compileLevel(Exp exp) {
175        final Type type = exp.getType();
176        if (type instanceof MemberType) {
177            // <Member> --> <Member>.Level
178            final MemberCalc memberCalc = compileMember(exp);
179            return new MemberLevelFunDef.CalcImpl(
180                new DummyExp(LevelType.forType(type)),
181                memberCalc);
182        }
183        assert type instanceof LevelType;
184        return (LevelCalc) compile(exp);
185    }
186
187    public DimensionCalc compileDimension(Exp exp) {
188        final Type type = exp.getType();
189        if (type instanceof HierarchyType) {
190            final HierarchyCalc hierarchyCalc = compileHierarchy(exp);
191            return new HierarchyDimensionFunDef.CalcImpl(
192                new DummyExp(new DimensionType(type.getDimension())),
193                hierarchyCalc);
194        }
195        assert type instanceof DimensionType : type;
196        return (DimensionCalc) compile(exp);
197    }
198
199    public HierarchyCalc compileHierarchy(Exp exp) {
200        final Type type = exp.getType();
201        if (type instanceof DimensionType) {
202            // <Dimension> --> unique Hierarchy else error
203            // Resolve at compile time if constant
204            final Dimension dimension = type.getDimension();
205            if (dimension != null) {
206                final Hierarchy hierarchy =
207                    FunUtil.getDimensionDefaultHierarchy(dimension);
208                if (hierarchy != null) {
209                    return (HierarchyCalc) ConstantCalc.constantHierarchy(
210                        hierarchy);
211                } else {
212                    // SSAS gives error at run time (often as an error in a
213                    // cell) but we prefer to give an error at validate time.
214                    throw MondrianResource.instance()
215                        .CannotImplicitlyConvertDimensionToHierarchy.ex(
216                            dimension.getName());
217                }
218            }
219            final DimensionCalc dimensionCalc = compileDimension(exp);
220            return new DimensionHierarchyCalc(
221                new DummyExp(HierarchyType.forType(type)),
222                dimensionCalc);
223        }
224        if (type instanceof MemberType) {
225            // <Member> --> <Member>.Hierarchy
226            final MemberCalc memberCalc = compileMember(exp);
227            return new MemberHierarchyFunDef.CalcImpl(
228                new DummyExp(HierarchyType.forType(type)),
229                memberCalc);
230        }
231        if (type instanceof LevelType) {
232            // <Level> --> <Level>.Hierarchy
233            final LevelCalc levelCalc = compileLevel(exp);
234            return new LevelHierarchyFunDef.CalcImpl(
235                new DummyExp(HierarchyType.forType(type)),
236                levelCalc);
237        }
238        assert type instanceof HierarchyType;
239        return (HierarchyCalc) compile(exp);
240    }
241
242    public IntegerCalc compileInteger(Exp exp) {
243        final Calc calc = compileScalar(exp, false);
244        final Type type = calc.getType();
245        if (type instanceof DecimalType
246            && ((DecimalType) type).getScale() == 0)
247        {
248            return (IntegerCalc) calc;
249        } else if (type instanceof NumericType) {
250            if (calc instanceof ConstantCalc) {
251                ConstantCalc constantCalc = (ConstantCalc) calc;
252                return new ConstantCalc(
253                    new DecimalType(Integer.MAX_VALUE, 0),
254                    constantCalc.evaluateInteger(null));
255            } else if (calc instanceof DoubleCalc) {
256                final DoubleCalc doubleCalc = (DoubleCalc) calc;
257                return new AbstractIntegerCalc(exp, new Calc[] {doubleCalc}) {
258                    public int evaluateInteger(Evaluator evaluator) {
259                        return (int) doubleCalc.evaluateDouble(evaluator);
260                    }
261                };
262            }
263        }
264        return (IntegerCalc) calc;
265    }
266
267    public StringCalc compileString(Exp exp) {
268        return (StringCalc) compileScalar(exp, false);
269    }
270
271    public DateTimeCalc compileDateTime(Exp exp) {
272        return (DateTimeCalc) compileScalar(exp, false);
273    }
274
275    public ListCalc compileList(Exp exp) {
276        return compileList(exp, false);
277    }
278
279    public ListCalc compileList(Exp exp, boolean mutable) {
280        assert exp.getType() instanceof SetType : "must be a set: " + exp;
281        final List<ResultStyle> resultStyleList;
282        if (mutable) {
283            resultStyleList = ResultStyle.MUTABLELIST_ONLY;
284        } else {
285            resultStyleList = ResultStyle.LIST_ONLY;
286        }
287        Calc calc = compileAs(exp, null, resultStyleList);
288        if (calc instanceof ListCalc) {
289            return (ListCalc) calc;
290        }
291        if (calc == null) {
292            calc = compileAs(exp, null, ResultStyle.ITERABLE_ANY);
293            assert calc != null;
294        }
295        // If expression is an iterator, convert it to a list. Don't check
296        // 'calc instanceof IterCalc' because some generic calcs implement both
297        // ListCalc and IterCalc.
298        if (!(calc instanceof ListCalc)) {
299            return toList((IterCalc) calc);
300        } else {
301            // A set can only be implemented as a list or an iterable.
302            throw Util.newInternal("Cannot convert calc to list: " + calc);
303        }
304    }
305
306    /**
307     * Converts an iterable over tuples to a list of tuples.
308     *
309     * @param calc Calc
310     * @return List calculation.
311     */
312    public ListCalc toList(IterCalc calc) {
313        return new IterableListCalc(calc);
314    }
315
316    public IterCalc compileIter(Exp exp) {
317        IterCalc calc =
318            (IterCalc) compileAs(exp, null, ResultStyle.ITERABLE_ONLY);
319        if (calc == null) {
320            calc = (IterCalc) compileAs(exp, null, ResultStyle.ANY_ONLY);
321            assert calc != null;
322        }
323        return calc;
324    }
325
326    public BooleanCalc compileBoolean(Exp exp) {
327        final Calc calc = compileScalar(exp, false);
328        if (calc instanceof BooleanCalc) {
329            if (calc instanceof ConstantCalc) {
330                final Object o = calc.evaluate(null);
331                if (!(o instanceof Boolean)) {
332                    return ConstantCalc.constantBoolean(
333                        CastFunDef.toBoolean(o, new BooleanType()));
334                }
335            }
336            return (BooleanCalc) calc;
337        } else if (calc instanceof DoubleCalc) {
338            final DoubleCalc doubleCalc = (DoubleCalc) calc;
339            return new AbstractBooleanCalc(exp, new Calc[] {doubleCalc}) {
340                public boolean evaluateBoolean(Evaluator evaluator) {
341                    return doubleCalc.evaluateDouble(evaluator) != 0;
342                }
343            };
344        } else if (calc instanceof IntegerCalc) {
345            final IntegerCalc integerCalc = (IntegerCalc) calc;
346            return new AbstractBooleanCalc(exp, new Calc[] {integerCalc}) {
347                public boolean evaluateBoolean(Evaluator evaluator) {
348                    return integerCalc.evaluateInteger(evaluator) != 0;
349                }
350            };
351        } else {
352            return (BooleanCalc) calc;
353        }
354    }
355
356    public DoubleCalc compileDouble(Exp exp) {
357        final Calc calc = compileScalar(exp, false);
358        if (calc instanceof ConstantCalc
359            && !(calc.evaluate(null) instanceof Double))
360        {
361            return ConstantCalc.constantDouble(
362                ((ConstantCalc) calc).evaluateDouble(null));
363        }
364        if (calc instanceof DoubleCalc) {
365            return (DoubleCalc) calc;
366        }
367        if (calc instanceof IntegerCalc) {
368            final IntegerCalc integerCalc = (IntegerCalc) calc;
369            return new AbstractDoubleCalc(exp, new Calc[] {integerCalc}) {
370                public double evaluateDouble(Evaluator evaluator) {
371                    final int result = integerCalc.evaluateInteger(evaluator);
372                    return (double) result;
373                }
374            };
375        }
376        throw Util.newInternal("cannot cast " + exp);
377    }
378
379    public TupleCalc compileTuple(Exp exp) {
380        return (TupleCalc) compile(exp);
381    }
382
383    public Calc compileScalar(Exp exp, boolean specific) {
384        final Type type = exp.getType();
385        if (type instanceof MemberType) {
386            MemberCalc calc = compileMember(exp);
387            return memberToScalar(calc);
388        } else if (type instanceof DimensionType) {
389            HierarchyCalc hierarchyCalc = compileHierarchy(exp);
390            return hierarchyToScalar(hierarchyCalc);
391        } else if (type instanceof HierarchyType) {
392            final HierarchyCalc hierarchyCalc = compileHierarchy(exp);
393            return hierarchyToScalar(hierarchyCalc);
394        } else if (type instanceof TupleType) {
395            TupleType tupleType = (TupleType) type;
396            TupleCalc tupleCalc = compileTuple(exp);
397            final TupleValueCalc scalarCalc =
398                new TupleValueCalc(
399                    new DummyExp(tupleType.getValueType()),
400                    tupleCalc,
401                    getEvaluator().mightReturnNullForUnrelatedDimension());
402            return scalarCalc.optimize();
403        } else if (type instanceof ScalarType) {
404            if (specific) {
405                if (type instanceof BooleanType) {
406                    return compileBoolean(exp);
407                } else if (type instanceof NumericType) {
408                    return compileDouble(exp);
409                } else if (type instanceof StringType) {
410                    return compileString(exp);
411                } else {
412                    return compile(exp);
413                }
414            } else {
415                return compile(exp);
416            }
417        } else {
418            return compile(exp);
419        }
420    }
421
422    private Calc hierarchyToScalar(HierarchyCalc hierarchyCalc) {
423        final MemberCalc memberCalc = hierarchyToMember(hierarchyCalc);
424        return memberToScalar(memberCalc);
425    }
426
427    private Calc memberToScalar(MemberCalc memberCalc) {
428        MemberType memberType = (MemberType) memberCalc.getType();
429        return MemberValueCalc.create(
430            new DummyExp(memberType.getValueType()),
431            new MemberCalc[] {memberCalc},
432            getEvaluator().mightReturnNullForUnrelatedDimension());
433    }
434
435    public ParameterSlot registerParameter(Parameter parameter) {
436        ParameterSlot slot = parameterSlots.get(parameter);
437        if (slot != null) {
438            return slot;
439        }
440        int index = parameterSlots.size();
441        ParameterSlotImpl slot2 = new ParameterSlotImpl(parameter, index);
442        parameterSlots.put(parameter, slot2);
443        slot2.value = parameter.getValue();
444
445        // Compile the expression only AFTER the parameter has been
446        // registered with a slot. Otherwise a cycle is possible.
447        final Type type = parameter.getType();
448        Exp defaultExp = parameter.getDefaultExp();
449        Calc calc;
450        if (type instanceof ScalarType) {
451            if (!defaultExp.getType().equals(type)) {
452                defaultExp =
453                    new UnresolvedFunCall(
454                        "Cast",
455                        Syntax.Cast,
456                        new Exp[] {
457                            defaultExp,
458                            Literal.createSymbol(
459                                Category.instance.getName(
460                                    TypeUtil.typeToCategory(type)))});
461                defaultExp = getValidator().validate(defaultExp, true);
462            }
463            calc = compileScalar(defaultExp, true);
464        } else {
465            calc = compileAs(defaultExp, type, resultStyles);
466        }
467        slot2.setDefaultValueCalc(calc);
468        return slot2;
469    }
470
471    public List<ResultStyle> getAcceptableResultStyles() {
472        return resultStyles;
473    }
474
475    /**
476     * Implementation of {@link ParameterSlot}.
477     */
478    private static class ParameterSlotImpl implements ParameterSlot {
479        private final Parameter parameter;
480        private final int index;
481        private Calc defaultValueCalc;
482        private Object value;
483        private boolean assigned;
484        private Object cachedDefaultValue;
485
486        /**
487         * Creates a ParameterSlotImpl.
488         *
489         * @param parameter Parameter
490         * @param index Unique index of the slot
491         */
492        public ParameterSlotImpl(
493            Parameter parameter, int index)
494        {
495            this.parameter = parameter;
496            this.index = index;
497        }
498
499        public int getIndex() {
500            return index;
501        }
502
503        public Calc getDefaultValueCalc() {
504            return defaultValueCalc;
505        }
506
507        public Parameter getParameter() {
508            return parameter;
509        }
510
511        /**
512         * Sets a compiled expression to compute the default value of the
513         * parameter.
514         *
515         * @param calc Compiled expression to compute default value of
516         * parameter
517         *
518         * @see #getDefaultValueCalc()
519         */
520        private void setDefaultValueCalc(Calc calc) {
521            this.defaultValueCalc = calc;
522        }
523
524        public void setParameterValue(Object value, boolean assigned) {
525            this.value = value;
526            this.assigned = assigned;
527
528            // make sure caller called convert first
529            assert !(value instanceof List && !(value instanceof TupleList));
530            assert !(value instanceof MemberExpr);
531            assert !(value instanceof Literal);
532        }
533
534        public Object getParameterValue() {
535            return value;
536        }
537
538        public boolean isParameterSet() {
539            return assigned;
540        }
541
542        public void unsetParameterValue() {
543            this.value = null;
544            this.assigned = false;
545        }
546
547        public void setCachedDefaultValue(Object value) {
548            this.cachedDefaultValue = value;
549        }
550
551        public Object getCachedDefaultValue() {
552            return cachedDefaultValue;
553        }
554    }
555
556    /**
557     * Computes the hierarchy of a dimension.
558     */
559    private static class DimensionHierarchyCalc extends AbstractHierarchyCalc {
560        private final DimensionCalc dimensionCalc;
561
562        protected DimensionHierarchyCalc(Exp exp, DimensionCalc dimensionCalc) {
563            super(exp, new Calc[] {dimensionCalc});
564            this.dimensionCalc = dimensionCalc;
565        }
566
567        public Hierarchy evaluateHierarchy(Evaluator evaluator) {
568            Dimension dimension =
569                dimensionCalc.evaluateDimension(evaluator);
570            final Hierarchy hierarchy =
571                FunUtil.getDimensionDefaultHierarchy(dimension);
572            if (hierarchy != null) {
573                return hierarchy;
574            }
575            throw FunUtil.newEvalException(
576                MondrianResource.instance()
577                    .CannotImplicitlyConvertDimensionToHierarchy.ex(
578                        dimension.getName()));
579        }
580    }
581}
582
583// End AbstractExpCompiler.java