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) 1998-2005 Julian Hyde
008// Copyright (C) 2005-2009 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap;
012
013import mondrian.calc.*;
014import mondrian.mdx.*;
015import mondrian.olap.type.DimensionType;
016import mondrian.olap.type.*;
017import mondrian.resource.MondrianResource;
018
019import java.io.PrintWriter;
020
021/**
022 * An axis in an MDX query. For example, the typical MDX query has two axes,
023 * which appear as the "ON COLUMNS" and "ON ROWS" clauses.
024 *
025 * @author jhyde, 20 January, 1999
026 */
027public class QueryAxis extends QueryPart {
028
029    private boolean nonEmpty;
030    private boolean ordered;
031    private Exp exp;
032    private final AxisOrdinal axisOrdinal;
033
034    /**
035     * Whether to show subtotals on this axis.
036     * The "(show\hide)Subtotals" operation changes its valud.
037     */
038    private SubtotalVisibility subtotalVisibility;
039    private final Id[] dimensionProperties;
040
041    /**
042     * Creates an axis.
043     *
044     * @param nonEmpty Whether to filter out members of this axis whose cells
045     *    are all empty
046     * @param set Expression to populate the axis
047     * @param axisOrdinal Which axis (ROWS, COLUMNS, etc.)
048     * @param subtotalVisibility Whether to show subtotals
049     * @param dimensionProperties List of dimension properties
050     */
051    public QueryAxis(
052        boolean nonEmpty,
053        Exp set,
054        AxisOrdinal axisOrdinal,
055        SubtotalVisibility subtotalVisibility,
056        Id[] dimensionProperties)
057    {
058        assert dimensionProperties != null;
059        assert axisOrdinal != null;
060        this.nonEmpty = nonEmpty
061            || (MondrianProperties.instance().EnableNonEmptyOnAllAxis.get()
062            && !axisOrdinal.isFilter());
063        this.exp = set;
064        this.axisOrdinal = axisOrdinal;
065        this.subtotalVisibility = subtotalVisibility;
066        this.dimensionProperties = dimensionProperties;
067        this.ordered = false;
068    }
069
070    /**
071     * Creates an axis with no dimension properties.
072     *
073     * @see #QueryAxis(boolean,Exp,AxisOrdinal,mondrian.olap.QueryAxis.SubtotalVisibility,Id[])
074     */
075    public QueryAxis(
076        boolean nonEmpty,
077        Exp set,
078        AxisOrdinal axisOrdinal,
079        SubtotalVisibility subtotalVisibility)
080    {
081        this(nonEmpty, set, axisOrdinal, subtotalVisibility, new Id[0]);
082    }
083
084    public Object clone() {
085        return new QueryAxis(
086            nonEmpty, exp.clone(), axisOrdinal,
087            subtotalVisibility, dimensionProperties.clone());
088    }
089
090    static QueryAxis[] cloneArray(QueryAxis[] a) {
091        QueryAxis[] a2 = new QueryAxis[a.length];
092        for (int i = 0; i < a.length; i++) {
093            a2[i] = (QueryAxis) a[i].clone();
094        }
095        return a2;
096    }
097
098    public Object accept(MdxVisitor visitor) {
099        final Object o = visitor.visit(this);
100
101        if (visitor.shouldVisitChildren()) {
102            // visit the expression which forms the axis
103            exp.accept(visitor);
104        }
105        return o;
106    }
107
108    public Calc compile(ExpCompiler compiler, ResultStyle resultStyle) {
109        Exp exp = this.exp;
110        if (axisOrdinal.isFilter()) {
111            exp = normalizeSlicerExpression(exp);
112            exp = exp.accept(compiler.getValidator());
113        }
114        switch (resultStyle) {
115        case LIST:
116            return compiler.compileList(exp, false);
117        case MUTABLE_LIST:
118            return compiler.compileList(exp, true);
119        case ITERABLE:
120            return compiler.compileIter(exp);
121        default:
122            throw Util.unexpected(resultStyle);
123        }
124    }
125
126    private static Exp normalizeSlicerExpression(Exp exp) {
127        Exp slicer = exp;
128        if (slicer instanceof LevelExpr
129            || slicer instanceof HierarchyExpr
130            || slicer instanceof DimensionExpr)
131        {
132            slicer = new UnresolvedFunCall(
133                "DefaultMember", Syntax.Property, new Exp[] {
134                    slicer});
135        }
136        if (slicer == null) {
137            ;
138        } else if (slicer instanceof FunCall
139            && ((FunCall) slicer).getSyntax() == Syntax.Parentheses)
140        {
141            slicer =
142                new UnresolvedFunCall(
143                    "{}", Syntax.Braces, new Exp[] {slicer});
144        } else {
145            slicer =
146                new UnresolvedFunCall(
147                    "{}", Syntax.Braces, new Exp[] {
148                        new UnresolvedFunCall(
149                            "()", Syntax.Parentheses, new Exp[] {
150                                slicer})});
151        }
152
153        return slicer;
154    }
155
156    public String getAxisName() {
157        return axisOrdinal.name();
158    }
159
160    /**
161     * Returns the ordinal of this axis, for example
162     * {@link mondrian.olap.AxisOrdinal.StandardAxisOrdinal#ROWS}.
163     */
164    public AxisOrdinal getAxisOrdinal() {
165        return axisOrdinal;
166    }
167
168    /**
169     * Returns whether the axis has the <code>NON EMPTY</code> property set.
170     */
171    public boolean isNonEmpty() {
172        return nonEmpty;
173    }
174
175    /**
176     * Sets whether the axis has the <code>NON EMPTY</code> property set.
177     * See {@link #isNonEmpty()}.
178     */
179    public void setNonEmpty(boolean nonEmpty) {
180        this.nonEmpty = nonEmpty;
181    }
182
183     /**
184     * Returns whether the axis has the <code>ORDER</code> property set.
185     */
186    public boolean isOrdered() {
187        return ordered;
188    }
189
190    /**
191     * Sets whether the axis has the <code>ORDER</code> property set.
192     */
193    public void setOrdered(boolean ordered) {
194        this.ordered = ordered;
195    }
196
197    /**
198     * Returns the expression which is used to compute the value of this axis.
199     */
200    public Exp getSet() {
201        return exp;
202    }
203
204    /**
205     * Sets the expression which is used to compute the value of this axis.
206     * See {@link #getSet()}.
207     */
208    public void setSet(Exp set) {
209        this.exp = set;
210    }
211
212    public void resolve(Validator validator) {
213        exp = validator.validate(exp, false);
214        final Type type = exp.getType();
215        if (!TypeUtil.isSet(type)) {
216            // If expression is a member or a tuple, implicitly convert it
217            // into a set. Dimensions and hierarchies can be converted to
218            // members, thence to sets.
219            if (type instanceof MemberType
220                || type instanceof TupleType
221                || type instanceof DimensionType
222                || type instanceof HierarchyType)
223            {
224                exp =
225                    new UnresolvedFunCall(
226                        "{}",
227                        Syntax.Braces,
228                        new Exp[] {exp});
229                exp = validator.validate(exp, false);
230            } else {
231                throw MondrianResource.instance().MdxAxisIsNotSet.ex(
232                    axisOrdinal.name());
233            }
234        }
235    }
236
237    public Object[] getChildren() {
238        return new Object[] {exp};
239    }
240
241    public void unparse(PrintWriter pw) {
242        if (nonEmpty) {
243            pw.print("NON EMPTY ");
244        }
245        if (exp != null) {
246            exp.unparse(pw);
247        }
248        if (dimensionProperties.length > 0) {
249            pw.print(" DIMENSION PROPERTIES ");
250            for (int i = 0; i < dimensionProperties.length; i++) {
251                Id dimensionProperty = dimensionProperties[i];
252                if (i > 0) {
253                    pw.print(", ");
254                }
255                dimensionProperty.unparse(pw);
256            }
257        }
258        if (!axisOrdinal.isFilter()) {
259            pw.print(" ON " + axisOrdinal.name());
260        }
261    }
262
263    public void addLevel(Level level) {
264        Util.assertTrue(level != null, "addLevel needs level");
265        exp = new UnresolvedFunCall(
266            "Crossjoin", Syntax.Function, new Exp[] {
267                exp,
268                new UnresolvedFunCall(
269                    "Members", Syntax.Property, new Exp[] {
270                        new LevelExpr(level)})});
271    }
272
273    void setSubtotalVisibility(boolean bShowSubtotals) {
274        subtotalVisibility =
275            bShowSubtotals
276            ? SubtotalVisibility.Show
277            : SubtotalVisibility.Hide;
278    }
279
280    public SubtotalVisibility getSubtotalVisibility() {
281        return subtotalVisibility;
282    }
283
284    public void resetSubtotalVisibility() {
285        this.subtotalVisibility = SubtotalVisibility.Undefined;
286    }
287
288    public void validate(Validator validator) {
289        if (axisOrdinal.isFilter()) {
290            if (exp != null) {
291                exp = validator.validate(exp, false);
292            }
293        }
294    }
295
296    public Id[] getDimensionProperties() {
297        return dimensionProperties;
298    }
299
300    /**
301     * <code>SubtotalVisibility</code> enumerates the allowed values of
302     * whether subtotals are visible.
303     */
304    public enum SubtotalVisibility {
305        Undefined,
306        Hide,
307        Show;
308    }
309
310}
311
312// End QueryAxis.java