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.ResolvedFunCall;
014import mondrian.olap.*;
015import mondrian.olap.type.Type;
016import mondrian.rolap.RolapEvaluator;
017import mondrian.rolap.RolapHierarchy;
018
019import java.util.*;
020
021/**
022 * Abstract implementation of the {@link mondrian.calc.Calc} interface.
023 *
024 * @author jhyde
025 * @since Sep 27, 2005
026 */
027public abstract class AbstractCalc implements Calc {
028    private final Calc[] calcs;
029    protected final Type type;
030    protected final Exp exp;
031
032    /**
033     * Creates an AbstractCalc.
034     *
035     * @param exp Source expression
036     * @param calcs Child compiled expressions
037     */
038    protected AbstractCalc(Exp exp, Calc[] calcs) {
039        assert exp != null;
040        this.exp = exp;
041        this.calcs = calcs;
042        this.type = exp.getType();
043    }
044
045    public Type getType() {
046        return type;
047    }
048
049    /**
050     * {@inheritDoc}
051     *
052     * Default implementation just does 'instanceof TargetClass'. Subtypes that
053     * are wrappers should override.
054     */
055    public boolean isWrapperFor(Class<?> iface) {
056        return iface.isInstance(this);
057    }
058
059    /**
060     * {@inheritDoc}
061     *
062     * Default implementation just casts to TargetClass.
063     * Subtypes that are wrappers should override.
064     */
065    public <T> T unwrap(Class<T> iface) {
066        return iface.cast(this);
067    }
068
069    public void accept(CalcWriter calcWriter) {
070        calcWriter.visitCalc(this, getName(), getArguments(), getCalcs());
071    }
072
073    /**
074     * Returns the name of this expression type, used when serializing an
075     * expression to a string.
076     *
077     * <p>The default implementation tries to extract a name from a function
078     * call, if any, then prints the last part of the class name.
079     */
080    protected String getName() {
081        String name = lastSegment(getClass());
082        if (isDigits(name)
083            && exp instanceof ResolvedFunCall)
084        {
085            ResolvedFunCall funCall = (ResolvedFunCall) exp;
086            name = funCall.getFunDef().getName();
087        }
088        return name;
089    }
090
091    /**
092     * Returns the last segment of a class name.
093     *
094     * <p>Examples:
095     * lastSegment("com.acme.Foo") = "Foo"
096     * lastSegment("com.acme.Foo$Bar") = "Bar"
097     * lastSegment("com.acme.Foo$1") = "1"
098     *
099     * @param clazz Class
100     * @return Last segment of class name
101     */
102    private String lastSegment(Class clazz) {
103        final String name = clazz.getName();
104        int dot = name.lastIndexOf('.');
105        int dollar = name.lastIndexOf('$');
106        int dotDollar = Math.max(dot, dollar);
107        if (dotDollar >= 0) {
108            return name.substring(dotDollar + 1);
109        }
110        return name;
111    }
112
113    private static boolean isDigits(String name) {
114        for (int i = 0; i < name.length(); i++) {
115            char c = name.charAt(i);
116            if ("0123456789".indexOf(c) < 0) {
117                return false;
118            }
119        }
120        return true;
121    }
122
123    /**
124     * Returns this expression's child expressions.
125     */
126    public Calc[] getCalcs() {
127        return calcs;
128    }
129
130    public boolean dependsOn(Hierarchy hierarchy) {
131        return anyDepends(getCalcs(), hierarchy);
132    }
133
134    /**
135     * Returns true if one of the calcs depends on the given dimension.
136     */
137    public static boolean anyDepends(Calc[] calcs, Hierarchy hierarchy) {
138        for (Calc calc : calcs) {
139            if (calc != null && calc.dependsOn(hierarchy)) {
140                return true;
141            }
142        }
143        return false;
144    }
145
146    /**
147     * Returns true if calc[0] depends on dimension,
148     * else false if calc[0] returns dimension,
149     * else true if any of the other calcs depend on dimension.
150     *
151     * <p>Typical application: <code>Aggregate({Set}, {Value Expression})</code>
152     * depends upon everything {Value Expression} depends upon, except the
153     * dimensions of {Set}.
154     */
155    public static boolean anyDependsButFirst(
156        Calc[] calcs, Hierarchy hierarchy)
157    {
158        if (calcs.length == 0) {
159            return false;
160        }
161        if (calcs[0].dependsOn(hierarchy)) {
162            return true;
163        }
164        if (calcs[0].getType().usesHierarchy(hierarchy, true)) {
165            return false;
166        }
167        for (int i = 1; i < calcs.length; i++) {
168            Calc calc = calcs[i];
169            if (calc != null && calc.dependsOn(hierarchy)) {
170                return true;
171            }
172        }
173        return false;
174    }
175
176    /**
177     * Returns true if any of the calcs depend on dimension,
178     * else false if any of the calcs return dimension,
179     * else true.
180     */
181    public static boolean butDepends(
182        Calc[] calcs, Hierarchy hierarchy)
183    {
184        boolean result = true;
185        for (Calc calc : calcs) {
186            if (calc != null) {
187                if (calc.dependsOn(hierarchy)) {
188                    return true;
189                }
190                if (calc.getType().usesHierarchy(hierarchy, true)) {
191                    result = false;
192                }
193            }
194        }
195        return result;
196    }
197
198    /**
199     * Returns any other arguments to this calc.
200     *
201     * @return Collection of name/value pairs, represented as a map
202     */
203    protected final Map<String, Object> getArguments() {
204        final Map<String, Object> argumentMap =
205            new LinkedHashMap<String, Object>();
206        collectArguments(argumentMap);
207        return argumentMap;
208    }
209
210    /**
211     * Collects any other arguments to this calc.
212     *
213     * <p>The default implementation returns name, class, type, resultStyle.
214     * A subclass must call super, but may add other arguments.
215     *
216     * @param arguments Collection of name/value pairs, represented as a map
217     */
218    protected void collectArguments(Map<String, Object> arguments) {
219        arguments.put("name", getName());
220        arguments.put("class", getClass());
221        arguments.put("type", getType());
222        arguments.put("resultStyle", getResultStyle());
223    }
224
225    /**
226     * Returns a simplified evalator whose context is the same for every
227     * dimension which an expression depends on, and the default member for
228     * every dimension which it does not depend on.
229     *
230     * <p>The default member is often the 'all' member, so this evaluator is
231     * usually the most efficient context in which to evaluate the expression.
232     *
233     * @param calc
234     * @param evaluator
235     */
236    public static Evaluator simplifyEvaluator(Calc calc, Evaluator evaluator) {
237        if (evaluator.isNonEmpty()) {
238            // If NON EMPTY is present, we cannot simplify the context, because
239            // we have to assume that the expression depends on everything.
240            // TODO: Bug 1456418: Convert 'NON EMPTY Crossjoin' to
241            // 'NonEmptyCrossJoin'.
242            return evaluator;
243        }
244        int changeCount = 0;
245        Evaluator ev = evaluator;
246        final List<RolapHierarchy> hierarchies =
247            ((RolapEvaluator) evaluator).getCube().getHierarchies();
248        for (RolapHierarchy hierarchy : hierarchies) {
249            final Member member = ev.getContext(hierarchy);
250            if (member.isAll()) {
251                continue;
252            }
253            if (calc.dependsOn(hierarchy)) {
254                continue;
255            }
256            final Member unconstrainedMember =
257                member.getHierarchy().getDefaultMember();
258            if (member == unconstrainedMember) {
259                // This is a hierarchy without an 'all' member, and the context
260                // is already the default member.
261                continue;
262            }
263            if (changeCount++ == 0) {
264                ev = evaluator.push();
265            }
266            ev.setContext(unconstrainedMember);
267        }
268        return ev;
269    }
270
271    public ResultStyle getResultStyle() {
272        return ResultStyle.VALUE;
273    }
274}
275
276// End AbstractCalc.java