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) 2005-2005 Julian Hyde
008// Copyright (C) 2005-2012 Pentaho
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.*;
015import mondrian.mdx.ResolvedFunCall;
016import mondrian.olap.*;
017import mondrian.olap.type.*;
018import mondrian.spi.UserDefinedFunction;
019
020import java.util.*;
021
022/**
023 * Resolver for user-defined functions.
024 *
025 * @author jhyde
026 * @since 2.0
027 */
028public class UdfResolver implements Resolver {
029    private final UdfFactory factory;
030    private final UserDefinedFunction udf;
031
032    private static final String[] emptyStringArray = new String[0];
033
034    public UdfResolver(UdfFactory factory) {
035        this.factory = factory;
036        this.udf = factory.create();
037    }
038
039    public String getName() {
040        return udf.getName();
041    }
042
043    public String getDescription() {
044        return udf.getDescription();
045    }
046
047    public String getSignature() {
048        Type[] parameterTypes = udf.getParameterTypes();
049        int[] parameterCategories = new int[parameterTypes.length];
050        for (int i = 0; i < parameterCategories.length; i++) {
051            parameterCategories[i] = TypeUtil.typeToCategory(parameterTypes[i]);
052        }
053        Type returnType = udf.getReturnType(parameterTypes);
054        int returnCategory = TypeUtil.typeToCategory(returnType);
055        return getSyntax().getSignature(
056            getName(),
057            returnCategory,
058            parameterCategories);
059    }
060
061    public Syntax getSyntax() {
062        return udf.getSyntax();
063    }
064
065    public FunDef getFunDef() {
066        Type[] parameterTypes = udf.getParameterTypes();
067        int[] parameterCategories = new int[parameterTypes.length];
068        for (int i = 0; i < parameterCategories.length; i++) {
069            parameterCategories[i] = TypeUtil.typeToCategory(parameterTypes[i]);
070        }
071        Type returnType = udf.getReturnType(parameterTypes);
072        return new UdfFunDef(parameterCategories, returnType);
073    }
074
075    public FunDef resolve(
076        Exp[] args,
077        Validator validator,
078        List<Conversion> conversions)
079    {
080        final Type[] parameterTypes = udf.getParameterTypes();
081        if (args.length != parameterTypes.length) {
082            return null;
083        }
084        int[] parameterCategories = new int[parameterTypes.length];
085        Type[] castArgTypes = new Type[parameterTypes.length];
086        for (int i = 0; i < parameterTypes.length; i++) {
087            Type parameterType = parameterTypes[i];
088            final Exp arg = args[i];
089            final Type argType = arg.getType();
090            final int parameterCategory =
091                TypeUtil.typeToCategory(parameterType);
092            if (!validator.canConvert(
093                    i, arg, parameterCategory, conversions))
094            {
095                return null;
096            }
097            parameterCategories[i] = parameterCategory;
098            if (!parameterType.equals(argType)) {
099                castArgTypes[i] =
100                    FunDefBase.castType(argType, parameterCategory);
101            }
102        }
103        final Type returnType = udf.getReturnType(castArgTypes);
104        return new UdfFunDef(parameterCategories, returnType);
105    }
106
107    public boolean requiresExpression(int k) {
108        return false;
109    }
110
111    public String[] getReservedWords() {
112        final String[] reservedWords = udf.getReservedWords();
113        return reservedWords == null ? emptyStringArray : reservedWords;
114    }
115
116    /**
117     * Adapter which converts a {@link UserDefinedFunction} into a
118     * {@link FunDef}.
119     */
120    private class UdfFunDef extends FunDefBase {
121        private Type returnType;
122
123        public UdfFunDef(int[] parameterCategories, Type returnType) {
124            super(
125                UdfResolver.this,
126                TypeUtil.typeToCategory(returnType),
127                parameterCategories);
128            this.returnType = returnType;
129        }
130
131        public Type getResultType(Validator validator, Exp[] args) {
132            return returnType;
133        }
134
135        public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
136            final Exp[] args = call.getArgs();
137            Calc[] calcs = new Calc[args.length];
138            UserDefinedFunction.Argument[] expCalcs =
139                new UserDefinedFunction.Argument[args.length];
140            for (int i = 0; i < args.length; i++) {
141                Exp arg = args[i];
142                final Calc calc = calcs[i] = compiler.compileAs(
143                    arg,
144                    castType(arg.getType(), parameterCategories[i]),
145                    ResultStyle.ANY_LIST);
146                calcs[i] = calc;
147                final Calc scalarCalc = compiler.compileScalar(arg, true);
148                final ListCalc listCalc;
149                final IterCalc iterCalc;
150                if (arg.getType() instanceof SetType) {
151                    listCalc = compiler.compileList(arg, true);
152                    iterCalc = compiler.compileIter(arg);
153                } else {
154                    listCalc = null;
155                    iterCalc = null;
156                }
157                expCalcs[i] = new CalcExp(calc, scalarCalc, listCalc, iterCalc);
158            }
159
160            // Create a new instance of the UDF, because some UDFs use member
161            // variables as state.
162            UserDefinedFunction udf2 = factory.create();
163            if (call.getType() instanceof SetType) {
164                return new ListCalcImpl(call, calcs, udf2, expCalcs);
165            } else {
166                return new ScalarCalcImpl(call, calcs, udf2, expCalcs);
167            }
168        }
169    }
170
171    /**
172     * Expression that evaluates a scalar user-defined function.
173     */
174    private static class ScalarCalcImpl extends GenericCalc {
175        private final Calc[] calcs;
176        private final UserDefinedFunction udf;
177        private final UserDefinedFunction.Argument[] args;
178
179        public ScalarCalcImpl(
180            ResolvedFunCall call,
181            Calc[] calcs,
182            UserDefinedFunction udf,
183            UserDefinedFunction.Argument[] args)
184        {
185            super(call);
186            this.calcs = calcs;
187            this.udf = udf;
188            this.args = args;
189        }
190
191        public Calc[] getCalcs() {
192            return calcs;
193        }
194
195        public Object evaluate(Evaluator evaluator) {
196            try {
197                return udf.execute(evaluator, args);
198            } catch (Exception e) {
199                return FunUtil.newEvalException(
200                    "Exception while executing function " + udf.getName(),
201                    e);
202            }
203        }
204
205        public boolean dependsOn(Hierarchy hierarchy) {
206            // Be pessimistic. This effectively disables expression caching.
207            return true;
208        }
209    }
210
211    /**
212     * Expression that evaluates a list user-defined function.
213     */
214    private static class ListCalcImpl extends AbstractListCalc {
215        private final UserDefinedFunction udf;
216        private final UserDefinedFunction.Argument[] args;
217
218        public ListCalcImpl(
219            ResolvedFunCall call,
220            Calc[] calcs,
221            UserDefinedFunction udf,
222            UserDefinedFunction.Argument[] args)
223        {
224            super(call, calcs);
225            this.udf = udf;
226            this.args = args;
227        }
228
229        public TupleList evaluateList(Evaluator evaluator) {
230            final List list = (List) udf.execute(evaluator, args);
231
232            // If arity is 1, assume they have returned a list of members.
233            // For other arity, assume a list of member arrays.
234            if (getType().getArity() == 1) {
235                //noinspection unchecked
236                return new UnaryTupleList((List<Member>) list);
237            } else {
238                // Use an adapter to make a list of member arrays look like
239                // a list of members laid end-to-end.
240                final int arity = getType().getArity();
241                //noinspection unchecked
242                final List<Member[]> memberArrayList = (List<Member[]>) list;
243                return new ListTupleList(
244                    arity,
245                    new AbstractList<Member>() {
246                        @Override
247                        public Member get(int index) {
248                            return memberArrayList.get(index / arity)
249                                [index % arity];
250                        }
251
252                        @Override
253                        public int size() {
254                            return memberArrayList.size() * arity;
255                        }
256                    }
257                );
258            }
259        }
260
261        @Override
262        public boolean dependsOn(Hierarchy hierarchy) {
263            // Be pessimistic. This effectively disables expression caching.
264            return true;
265        }
266    }
267
268    /**
269     * Wrapper around a {@link Calc} to make it appear as an {@link Exp}.
270     * Only the {@link #evaluate(mondrian.olap.Evaluator)}
271     * and {@link #evaluateScalar(mondrian.olap.Evaluator)} methods are
272     * supported.
273     */
274    private static class CalcExp implements UserDefinedFunction.Argument {
275        private final Calc calc;
276        private final Calc scalarCalc;
277        private final IterCalc iterCalc;
278        private final ListCalc listCalc;
279
280        /**
281         * Creates a CalcExp.
282         *
283         * @param calc Compiled expression
284         * @param scalarCalc Compiled expression that evaluates to a scalar
285         * @param listCalc Compiled expression that evaluates an MDX set to
286         *     a java list
287         * @param iterCalc Compiled expression that evaluates an MDX set to
288         */
289        public CalcExp(
290            Calc calc,
291            Calc scalarCalc,
292            ListCalc listCalc,
293            IterCalc iterCalc)
294        {
295            this.calc = calc;
296            this.scalarCalc = scalarCalc;
297            this.listCalc = listCalc;
298            this.iterCalc = iterCalc;
299        }
300
301        public Type getType() {
302            return calc.getType();
303        }
304
305        public Object evaluate(Evaluator evaluator) {
306            return adapt(calc.evaluate(evaluator));
307        }
308
309        public Object evaluateScalar(Evaluator evaluator) {
310            return scalarCalc.evaluate(evaluator);
311        }
312
313        public List evaluateList(Evaluator eval) {
314            if (listCalc == null) {
315                throw new RuntimeException("Expression is not a set");
316            }
317            return adaptList(listCalc.evaluateList(eval));
318        }
319
320        public Iterable evaluateIterable(Evaluator eval) {
321            if (iterCalc == null) {
322                throw new RuntimeException("Expression is not a set");
323            }
324            return adaptIterable(iterCalc.evaluateIterable(eval));
325        }
326
327        /**
328         * Adapts the output of {@link TupleList} and {@link TupleIterable}
329         * calculator expressions to the old style, that returned either members
330         * or arrays of members.
331         *
332         * @param o Output of calc
333         * @return Output in new format (lists and iterables over lists of
334         *    members)
335         */
336        private Object adapt(Object o) {
337            if (o instanceof TupleIterable) {
338                return adaptIterable((TupleIterable) o);
339            }
340            return o;
341        }
342
343        private List adaptList(final TupleList tupleList) {
344            // List is required to be mutable -- so make a copy.
345            if (tupleList.getArity() == 1) {
346                return new ArrayList<Member>(tupleList.slice(0));
347            } else {
348                return new ArrayList<Member[]>(
349                    TupleCollections.asMemberArrayList(tupleList));
350            }
351        }
352
353        private Iterable adaptIterable(final TupleIterable tupleIterable) {
354            if (tupleIterable instanceof TupleList) {
355                return adaptList((TupleList) tupleIterable);
356            }
357            if (tupleIterable.getArity() == 1) {
358                return tupleIterable.slice(0);
359            } else {
360                return TupleCollections.asMemberArrayIterable(tupleIterable);
361            }
362        }
363    }
364
365    /**
366     * Factory for {@link UserDefinedFunction}.
367     *
368     * <p>This factory is required because a user-defined function is allowed
369     * to store state in itself. Therefore it is unsanitary to use the same
370     * UDF in the function table for validation and runtime. In the function
371     * table there is a factory. We use one instance of instance of the UDF to
372     * validate, and create another for the runtime plan.</p>
373     */
374    public interface UdfFactory {
375        /**
376         * Creates a UDF.
377         *
378         * @return UDF
379         */
380        UserDefinedFunction create();
381    }
382
383    /**
384     * Implementation of {@link UdfFactory} that instantiates a given class
385     * using a public default constructor.
386     */
387    public static class ClassUdfFactory implements UdfResolver.UdfFactory {
388        private final Class<? extends UserDefinedFunction> clazz;
389        private final String name;
390
391        /**
392         * Creates a ClassUdfFactory.
393         *
394         * @param clazz Class to instantiate
395         * @param name Name
396         */
397        public ClassUdfFactory(
398            Class<? extends UserDefinedFunction> clazz,
399            String name)
400        {
401            this.clazz = clazz;
402            this.name = name;
403            assert clazz != null;
404        }
405
406        public UserDefinedFunction create() {
407            return Util.createUdf(clazz, name);
408        }
409    }
410}
411
412// End UdfResolver.java