001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/fun/ParameterFunDef.java#14 $
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) 2003-2006 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    //
010    // jhyde, Feb 14, 2003
011    */
012    package mondrian.olap.fun;
013    
014    import mondrian.olap.*;
015    import mondrian.olap.type.*;
016    import mondrian.mdx.*;
017    
018    /**
019     * A <code>ParameterFunDef</code> is a pseudo-function describing calls to
020     * <code>Parameter</code> and <code>ParamRef</code> functions. It exists only
021     * fleetingly, and is then converted into a {@link mondrian.olap.Parameter}.
022     * For internal use only.
023     *
024     * @author jhyde
025     * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/ParameterFunDef.java#14 $
026     * @since Feb 14, 2003
027     */
028    public class ParameterFunDef extends FunDefBase {
029        public final String parameterName;
030        private final Type type;
031        public final Exp exp;
032        public final String parameterDescription;
033    
034        ParameterFunDef(
035                FunDef funDef,
036                String parameterName,
037                Type type,
038                int returnCategory,
039                Exp exp,
040                String description) {
041            super(funDef.getName(),
042                    funDef.getSignature(),
043                    funDef.getDescription(),
044                    funDef.getSyntax(),
045                    returnCategory,
046                    funDef.getParameterCategories());
047            assertPrecondition(getName().equals("Parameter") ||
048                    getName().equals("ParamRef"));
049            this.parameterName = parameterName;
050            this.type = type;
051            this.exp = exp;
052            this.parameterDescription = description;
053        }
054    
055        public Exp createCall(Validator validator, Exp[] args) {
056            Parameter parameter = validator.createOrLookupParam(
057                this.getName().equals("Parameter"),
058                parameterName, type, exp, parameterDescription);
059            return new ParameterExpr(parameter);
060        }
061    
062        public Type getResultType(Validator validator, Exp[] args) {
063            return type;
064        }
065    
066        private static boolean isConstant(Exp typeArg) {
067            if (typeArg instanceof LevelExpr) {
068                // e.g. "[Time].[Quarter]"
069                return true;
070            }
071            if (typeArg instanceof HierarchyExpr) {
072                // e.g. "[Time].[By Week]"
073                return true;
074            }
075            if (typeArg instanceof DimensionExpr) {
076                // e.g. "[Time]"
077                return true;
078            }
079            if (typeArg instanceof FunCall) {
080                // e.g. "[Time].CurrentMember.Hierarchy". They probably wrote
081                // "[Time]", and the automatic type conversion did the rest.
082                FunCall hierarchyCall = (FunCall) typeArg;
083                if (hierarchyCall.getFunName().equals("Hierarchy") &&
084                        hierarchyCall.getArgCount() > 0 &&
085                        hierarchyCall.getArg(0) instanceof FunCall) {
086                    FunCall currentMemberCall = (FunCall) hierarchyCall.getArg(0);
087                    if (currentMemberCall.getFunName().equals("CurrentMember") &&
088                            currentMemberCall.getArgCount() > 0 &&
089                            currentMemberCall.getArg(0) instanceof DimensionExpr) {
090                        return true;
091                    }
092                }
093            }
094            return false;
095        }
096    
097        public static String getParameterName(Exp[] args) {
098            if (args[0] instanceof Literal &&
099                    args[0].getCategory() == Category.String) {
100                return (String) ((Literal) args[0]).getValue();
101            } else {
102                throw Util.newInternal("Parameter name must be a string constant");
103            }
104        }
105    
106        /**
107         * Returns an approximate type for a parameter, based upon the 1'th
108         * argument. Does not use the default value expression, so this method
109         * can safely be used before the expression has been validated.
110         */
111        public static Type getParameterType(Exp[] args) {
112            if (args[1] instanceof Id) {
113                Id id = (Id) args[1];
114                String[] names = id.toStringArray();
115                if (names.length == 1) {
116                    final String name = names[0];
117                    if (name.equals("NUMERIC")) {
118                        return new NumericType();
119                    }
120                    if (name.equals("STRING")) {
121                        return new StringType();
122                    }
123                }
124            } else if (args[1] instanceof Literal) {
125                final Literal literal = (Literal) args[1];
126                if (literal.getValue().equals("NUMERIC")) {
127                    return new NumericType();
128                } else if (literal.getValue().equals("STRING")) {
129                    return new StringType();
130                }
131            } else if (args[1] instanceof MemberExpr) {
132                return new MemberType(null,null,null,null);
133            }
134            return new StringType();
135        }
136    
137        /**
138         * Resolves calls to the <code>Parameter</code> MDX function.
139         */
140        public static class ParameterResolver extends MultiResolver {
141            private static final String[] SIGNATURES =
142                    new String[]{
143                        // Parameter(string const, symbol, string[, string const]): string
144                        "fS#yS#", "fS#yS",
145                        // Parameter(string const, symbol, numeric[, string const]): numeric
146                        "fn#yn#", "fn#yn",
147                        // Parameter(string const, hierarchy constant, member[, string const]): member
148                        "fm#hm#", "fm#hm",
149                    };
150    
151            public ParameterResolver() {
152                super("Parameter",
153                        "Parameter(<Name>, <Type>, <DefaultValue>, <Description>)",
154                        "Returns default value of parameter.", SIGNATURES);
155            }
156    
157            public String[] getReservedWords() {
158                return new String[]{"NUMERIC", "STRING"};
159            }
160    
161            protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
162                String parameterName = getParameterName(args);
163                Exp typeArg = args[1];
164                int category;
165                Type type = typeArg.getType();
166                switch (typeArg.getCategory()) {
167                case Category.Dimension:
168                case Category.Hierarchy:
169                case Category.Level:
170                    Dimension dimension = type.getDimension();
171                    if (!isConstant(typeArg)) {
172                        throw newEvalException(
173                                dummyFunDef,
174                                "Invalid parameter '" + parameterName +
175                                "'. Type must be a NUMERIC, STRING, or a dimension, hierarchy or level");
176                    }
177                    if (dimension == null) {
178                        throw newEvalException(
179                                dummyFunDef,
180                                "Invalid dimension for parameter '" +
181                                parameterName + "'");
182                    }
183                    type = new MemberType(
184                            type.getDimension(),
185                            type.getHierarchy(),
186                            type.getLevel(),
187                            null);
188                    category = Category.Member;
189                    break;
190    
191                case Category.Symbol:
192                    String s = (String) ((Literal) typeArg).getValue();
193                    if (s.equalsIgnoreCase("NUMERIC")) {
194                        category = Category.Numeric;
195                        type = new NumericType();
196                        break;
197                    } else if (s.equalsIgnoreCase("STRING")) {
198                        category = Category.String;
199                        type = new StringType();
200                        break;
201                    }
202                    // fall through and throw error
203                default:
204                    // Error is internal because the function call has already been
205                    // type-checked.
206                    throw newEvalException(
207                            dummyFunDef,
208                            "Invalid type for parameter '" + parameterName +
209                            "'; expecting NUMERIC, STRING or a hierarchy");
210                }
211                Exp exp = args[2];
212                if (exp.getCategory() != category) {
213                    String typeName =
214                            Category.instance.getName(category).toUpperCase();
215                    throw newEvalException(
216                            dummyFunDef,
217                            "Default value of parameter '" + parameterName +
218                            "' is inconsistent with its type, " + typeName);
219                }
220                if (category == Category.Member) {
221                    final Type expType = exp.getType();
222                    if (type.getDimension() != null &&
223                            expType.getDimension() != type.getDimension() ||
224                            type.getHierarchy() != null &&
225                            expType.getHierarchy() != type.getHierarchy() ||
226                            type.getLevel() != null &&
227                            expType.getLevel() != type.getLevel()) {
228                        throw newEvalException(
229                                dummyFunDef,
230                                "Default value of parameter '" + parameterName +
231                                "' is not consistent with the parameter type '" +
232                                type);
233                    }
234                }
235                String parameterDescription = null;
236                if (args.length > 3) {
237                    if (args[3] instanceof Literal &&
238                            args[3].getCategory() == Category.String) {
239                        parameterDescription =
240                                (String) ((Literal) args[3]).getValue();
241                    } else {
242                        throw newEvalException(
243                                dummyFunDef,
244                                "Description of parameter '" + parameterName +
245                                "' must be a string constant");
246                    }
247                }
248    
249                return new ParameterFunDef(
250                        dummyFunDef, parameterName, type, category,
251                        exp, parameterDescription);
252            }
253        }
254    
255        /**
256         * Resolves calls to the <code>ParamRef</code> MDX function.
257         */
258        public static class ParamRefResolver extends MultiResolver {
259            public ParamRefResolver() {
260                super(
261                        "ParamRef",
262                        "ParamRef(<Name>)",
263                        "Returns the current value of this parameter. If it is null, returns the default value.",
264                        new String[]{"fv#"});
265            }
266    
267            protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
268                String parameterName = getParameterName(args);
269                return new ParameterFunDef(
270                        dummyFunDef, parameterName, null, Category.Unknown, null,
271                        null);
272            }
273        }
274    }
275    
276    // End ParameterFunDef.java