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-2009 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.olap.fun;
011
012import mondrian.calc.Calc;
013import mondrian.calc.ExpCompiler;
014import mondrian.calc.impl.GenericCalc;
015import mondrian.mdx.ResolvedFunCall;
016import mondrian.olap.*;
017import mondrian.olap.type.Type;
018import mondrian.olap.type.TypeUtil;
019import mondrian.resource.MondrianResource;
020
021import java.util.List;
022
023/**
024 * Definition of the <code>CAST</code> MDX operator.
025 *
026 * <p><code>CAST</code> is a mondrian-specific extension to MDX, because the MDX
027 * standard does not define how values are to be converted from one
028 * type to another. Microsoft Analysis Services, for Resolver, uses the Visual
029 * Basic functions <code>CStr</code>, <code>CInt</code>, etc.
030 * The syntax for this operator was inspired by the <code>CAST</code> operator
031 * in the SQL standard.
032 *
033 * <p>Examples:<ul>
034 * <li><code>CAST(1 + 2 AS STRING)</code></li>
035 * <li><code>CAST('12.' || '56' AS NUMERIC)</code></li>
036 * <li><code>CAST('tr' || 'ue' AS BOOLEAN)</code></li>
037 * </ul>
038 *
039 * @author jhyde
040 * @since Sep 3, 2006
041 */
042public class CastFunDef extends FunDefBase {
043    static final ResolverBase Resolver = new ResolverImpl();
044
045    private CastFunDef(FunDef dummyFunDef) {
046        super(dummyFunDef);
047    }
048
049    public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
050        final Type targetType = call.getType();
051        final Exp arg = call.getArg(0);
052        final Calc calc = compiler.compileScalar(arg, false);
053        return new CalcImpl(arg, calc, targetType);
054    }
055
056    private static RuntimeException cannotConvert(
057        Object o,
058        final Type targetType)
059    {
060        return Util.newInternal(
061            "cannot convert value '" + o
062            + "' to targetType '" + targetType
063            + "'");
064    }
065
066    public static int toInt(
067        Object o,
068        final Type targetType)
069    {
070        if (o == null) {
071            return FunUtil.IntegerNull;
072        }
073        if (o instanceof String) {
074            return Integer.parseInt((String) o);
075        }
076        if (o instanceof Number) {
077            return ((Number) o).intValue();
078        }
079        throw cannotConvert(o, targetType);
080    }
081
082    private static double toDouble(Object o, final Type targetType) {
083        if (o == null) {
084            return FunUtil.DoubleNull;
085        }
086        if (o instanceof String) {
087            return Double.valueOf((String) o);
088        }
089        if (o instanceof Number) {
090            return ((Number) o).doubleValue();
091        }
092        throw cannotConvert(o, targetType);
093    }
094
095    public static boolean toBoolean(Object o, final Type targetType) {
096        if (o == null) {
097            return FunUtil.BooleanNull;
098        }
099        if (o instanceof Boolean) {
100            return (Boolean) o;
101        }
102        if (o instanceof String) {
103            return Boolean.valueOf((String) o);
104        }
105        if (o instanceof Number) {
106            return ((Number) o).doubleValue() > 0;
107        }
108        throw cannotConvert(o, targetType);
109    }
110
111    /**
112     * Resolves calls to the CAST operator.
113     */
114    private static class ResolverImpl extends ResolverBase {
115
116        public ResolverImpl() {
117            super(
118                "Cast", "Cast(<Expression> AS <Type>)",
119                "Converts values to another type.", Syntax.Cast);
120        }
121
122        public FunDef resolve(
123            Exp[] args, Validator validator, List<Conversion> conversions)
124        {
125            if (args.length != 2) {
126                return null;
127            }
128            if (!(args[1] instanceof Literal)) {
129                return null;
130            }
131            Literal literal = (Literal) args[1];
132            String typeName = (String) literal.getValue();
133            int returnCategory;
134            if (typeName.equalsIgnoreCase("String")) {
135                returnCategory = Category.String;
136            } else if (typeName.equalsIgnoreCase("Numeric")) {
137                returnCategory = Category.Numeric;
138            } else if (typeName.equalsIgnoreCase("Boolean")) {
139                returnCategory = Category.Logical;
140            } else if (typeName.equalsIgnoreCase("Integer")) {
141                returnCategory = Category.Integer;
142            } else {
143                throw MondrianResource.instance().CastInvalidType.ex(typeName);
144            }
145            final FunDef dummyFunDef =
146                createDummyFunDef(this, returnCategory, args);
147            return new CastFunDef(dummyFunDef);
148        }
149    }
150
151    private static class CalcImpl extends GenericCalc {
152        private final Calc calc;
153        private final Type targetType;
154        private final int targetCategory;
155
156        public CalcImpl(Exp arg, Calc calc, Type targetType) {
157            super(arg);
158            this.calc = calc;
159            this.targetType = targetType;
160            this.targetCategory = TypeUtil.typeToCategory(targetType);
161        }
162
163        public Calc[] getCalcs() {
164            return new Calc[] {calc};
165        }
166
167        public Object evaluate(Evaluator evaluator) {
168            switch (targetCategory) {
169            case Category.String:
170                return evaluateString(evaluator);
171            case Category.Integer:
172                return FunUtil.box(evaluateInteger(evaluator));
173            case Category.Numeric:
174                return FunUtil.box(evaluateDouble(evaluator));
175            case Category.DateTime:
176                return evaluateDateTime(evaluator);
177            case Category.Logical:
178                return evaluateBoolean(evaluator);
179            default:
180                throw Util.newInternal("category " + targetCategory);
181            }
182        }
183
184        public String evaluateString(Evaluator evaluator) {
185            final Object o = calc.evaluate(evaluator);
186            if (o == null) {
187                return null;
188            }
189            return String.valueOf(o);
190        }
191
192        public int evaluateInteger(Evaluator evaluator) {
193            final Object o = calc.evaluate(evaluator);
194            return toInt(o, targetType);
195        }
196
197        public double evaluateDouble(Evaluator evaluator) {
198            final Object o = calc.evaluate(evaluator);
199            return toDouble(o, targetType);
200        }
201
202        public boolean evaluateBoolean(Evaluator evaluator) {
203            final Object o = calc.evaluate(evaluator);
204            return toBoolean(o, targetType);
205        }
206    }
207}
208
209// End CastFunDef.java