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) 2004-2005 Julian Hyde
008// Copyright (C) 2005-2012 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.GenericCalc;
015import mondrian.mdx.ResolvedFunCall;
016import mondrian.olap.*;
017
018import java.util.List;
019
020/**
021 * Definition of the <code>Properties</code> MDX function.
022 *
023 * @author jhyde
024 * @since Mar 23, 2006
025 */
026class PropertiesFunDef extends FunDefBase {
027    static final ResolverImpl Resolver = new ResolverImpl();
028
029    public PropertiesFunDef(
030        String name,
031        String signature,
032        String description,
033        Syntax syntax,
034        int returnType,
035        int[] parameterTypes)
036    {
037        super(name, signature, description, syntax, returnType, parameterTypes);
038    }
039
040    public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
041        final MemberCalc memberCalc = compiler.compileMember(call.getArg(0));
042        final StringCalc stringCalc = compiler.compileString(call.getArg(1));
043        return new GenericCalc(call) {
044            public Object evaluate(Evaluator evaluator) {
045                return properties(
046                    memberCalc.evaluateMember(evaluator),
047                        stringCalc.evaluateString(evaluator));
048            }
049
050            public Calc[] getCalcs() {
051                return new Calc[] {memberCalc, stringCalc};
052            }
053        };
054    }
055
056    static Object properties(Member member, String s) {
057        boolean matchCase = MondrianProperties.instance().CaseSensitive.get();
058        Object o = member.getPropertyValue(s, matchCase);
059        if (o == null) {
060            if (!Util.isValidProperty(s, member.getLevel())) {
061                throw new MondrianEvaluationException(
062                    "Property '" + s
063                    + "' is not valid for member '" + member + "'");
064            }
065        }
066        return o;
067    }
068
069    /**
070     * Resolves calls to the <code>PROPERTIES</code> MDX function.
071     */
072    private static class ResolverImpl extends ResolverBase {
073        private static final int[] PARAMETER_TYPES = {
074            Category.Member, Category.String
075        };
076
077        private ResolverImpl() {
078            super(
079                "Properties",
080                "<Member>.Properties(<String Expression>)",
081                "Returns the value of a member property.",
082                Syntax.Method);
083        }
084
085        private boolean matches(
086            Exp[] args,
087            int[] parameterTypes,
088            Validator validator,
089            List<Conversion> conversions)
090        {
091            if (parameterTypes.length != args.length) {
092                return false;
093            }
094            for (int i = 0; i < args.length; i++) {
095                if (!validator.canConvert(
096                        i, args[i], parameterTypes[i], conversions))
097                {
098                    return false;
099                }
100            }
101            return true;
102        }
103
104        public FunDef resolve(
105            Exp[] args,
106            Validator validator,
107            List<Conversion> conversions)
108        {
109            if (!matches(args, PARAMETER_TYPES, validator, conversions)) {
110                return null;
111            }
112            int returnType = deducePropertyCategory(args[0], args[1]);
113            return new PropertiesFunDef(
114                getName(), getSignature(), getDescription(), getSyntax(),
115                returnType, PARAMETER_TYPES);
116        }
117
118        /**
119         * Deduces the category of a property. This is possible only if the
120         * name is a string literal, and the member's hierarchy is unambigous.
121         * If the type cannot be deduced, returns {@link Category#Value}.
122         *
123         * @param memberExp Expression for the member
124         * @param propertyNameExp Expression for the name of the property
125         * @return Category of the property
126         */
127        private int deducePropertyCategory(
128            Exp memberExp,
129            Exp propertyNameExp)
130        {
131            if (!(propertyNameExp instanceof Literal)) {
132                return Category.Value;
133            }
134            String propertyName =
135                (String) ((Literal) propertyNameExp).getValue();
136            Hierarchy hierarchy = memberExp.getType().getHierarchy();
137            if (hierarchy == null) {
138                return Category.Value;
139            }
140            Level[] levels = hierarchy.getLevels();
141            Property property = lookupProperty(
142                levels[levels.length - 1], propertyName);
143            if (property == null) {
144                // we'll likely get a runtime error
145                return Category.Value;
146            } else {
147                switch (property.getType()) {
148                case TYPE_BOOLEAN:
149                    return Category.Logical;
150                case TYPE_NUMERIC:
151                    return Category.Numeric;
152                case TYPE_STRING:
153                    return Category.String;
154                case TYPE_DATE:
155                case TYPE_TIME:
156                case TYPE_TIMESTAMP:
157                    return Category.DateTime;
158                default:
159                    throw Util.badValue(property.getType());
160                }
161            }
162        }
163
164        public boolean requiresExpression(int k) {
165            return true;
166        }
167    }
168}
169
170// End PropertiesFunDef.java