001/*
002// $Id: QueryAxis.java 482 2012-01-05 23:27:27Z jhyde $
003//
004// Licensed to Julian Hyde under one or more contributor license
005// agreements. See the NOTICE file distributed with this work for
006// additional information regarding copyright ownership.
007//
008// Julian Hyde licenses this file to you under the Apache License,
009// Version 2.0 (the "License"); you may not use this file except in
010// compliance with the License. You may obtain a copy of the License at:
011//
012// http://www.apache.org/licenses/LICENSE-2.0
013//
014// Unless required by applicable law or agreed to in writing, software
015// distributed under the License is distributed on an "AS IS" BASIS,
016// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017// See the License for the specific language governing permissions and
018// limitations under the License.
019*/
020package org.olap4j.query;
021
022import org.olap4j.Axis;
023import org.olap4j.OlapException;
024import org.olap4j.mdx.IdentifierSegment;
025import org.olap4j.metadata.Measure;
026import org.olap4j.metadata.Member;
027
028import java.util.*;
029
030/**
031 * An axis within an OLAP {@link Query}.
032 *
033 * <p>An axis has a location (columns, rows, etc) and has zero or more
034 * dimensions that are placed on it.
035 *
036 * @author jdixon, Luc Boudreau
037 * @version $Id: QueryAxis.java 482 2012-01-05 23:27:27Z jhyde $
038 * @since May 29, 2007
039 */
040public class QueryAxis extends QueryNodeImpl {
041
042    protected final List<QueryDimension> dimensions = new DimensionList();
043
044    private final Query query;
045    protected Axis location = null;
046    private boolean nonEmpty;
047    private SortOrder sortOrder = null;
048    private String sortEvaluationLiteral = null;
049
050    /**
051     * Creates a QueryAxis.
052     *
053     * @param query Query that the axis belongs to
054     * @param location Location of axis (e.g. ROWS, COLUMNS)
055     */
056    public QueryAxis(Query query, Axis location) {
057        super();
058        this.query = query;
059        this.location = location;
060    }
061
062    /**
063     * Returns the location of this <code>QueryAxis</code> in the query;
064     * <code>null</code> if unused.
065     *
066     * @return location of this axis in the query
067     */
068    public Axis getLocation() {
069        return location;
070    }
071
072    /**
073     * Returns a list of the dimensions placed on this QueryAxis.
074     *
075     * <p>Be aware that modifications to this list might
076     * have unpredictable consequences.</p>
077     *
078     * @return list of dimensions
079     */
080    public List<QueryDimension> getDimensions() {
081        return dimensions;
082    }
083
084    /**
085     * Returns the name of this QueryAxis.
086     *
087     * @return the name of this axis, for example "ROWS", "COLUMNS".
088     */
089    public String getName() {
090        return location.getCaption(query.getLocale());
091    }
092
093    /**
094     * Places a QueryDimension object one position before in the
095     * list of current dimensions. Uses a 0 based index.
096     * For example, to place the 5th dimension on the current axis
097     * one position before, one would need to call pullUp(4),
098     * so the dimension would then use axis index 4 and the previous
099     * dimension at that position gets pushed down one position.
100     * @param index The index of the dimension to move up one notch.
101     * It uses a zero based index.
102     */
103    public void pullUp(int index) {
104        Map<Integer, QueryNode> removed = new HashMap<Integer, QueryNode>();
105        removed.put(Integer.valueOf(index), this.dimensions.get(index));
106        Map<Integer, QueryNode> added = new HashMap<Integer, QueryNode>();
107        added.put(Integer.valueOf(index - 1), this.dimensions.get(index));
108        Collections.swap(this.dimensions, index, index - 1);
109        this.notifyRemove(removed);
110        this.notifyAdd(added);
111    }
112
113    /**
114     * Places a QueryDimension object one position lower in the
115     * list of current dimensions. Uses a 0 based index.
116     * For example, to place the 4th dimension on the current axis
117     * one position lower, one would need to call pushDown(3),
118     * so the dimension would then use axis index 4 and the previous
119     * dimension at that position gets pulled up one position.
120     * @param index The index of the dimension to move down one notch.
121     * It uses a zero based index.
122     */
123    public void pushDown(int index) {
124        Map<Integer, QueryNode> removed = new HashMap<Integer,  QueryNode>();
125        removed.put(Integer.valueOf(index), this.dimensions.get(index));
126        Map<Integer, QueryNode> added = new HashMap<Integer, QueryNode>();
127        added.put(Integer.valueOf(index + 1), this.dimensions.get(index));
128        Collections.swap(this.dimensions, index, index + 1);
129        this.notifyRemove(removed);
130        this.notifyAdd(added);
131    }
132
133    /**
134     * Places a {@link QueryDimension} object on this axis.
135     * @param dimension The {@link QueryDimension} object to add
136     * to this axis.
137     */
138    public void addDimension(QueryDimension dimension) {
139        this.getDimensions().add(dimension);
140        Integer index = Integer.valueOf(
141            this.getDimensions().indexOf(dimension));
142        this.notifyAdd(dimension, index);
143    }
144
145    /**
146     * Places a {@link QueryDimension} object on this axis at
147     * a specific index.
148     * @param dimension The {@link QueryDimension} object to add
149     * to this axis.
150     * @param index The position (0 based) onto which to place
151     * the QueryDimension
152     */
153    public void addDimension(int index, QueryDimension dimension) {
154        this.getDimensions().add(index, dimension);
155        this.notifyAdd(dimension, index);
156    }
157
158    /**
159     * Removes a {@link QueryDimension} object on this axis.
160     * @param dimension The {@link QueryDimension} object to remove
161     * from this axis.
162     */
163    public void removeDimension(QueryDimension dimension) {
164        Integer index = Integer.valueOf(
165            this.getDimensions().indexOf(dimension));
166        this.getDimensions().remove(dimension);
167        this.notifyRemove(dimension, index);
168    }
169
170    /**
171     * Returns whether this QueryAxis filters out empty rows.
172     * If true, axis filters out empty rows, and the MDX to evaluate the axis
173     * will be generated with the "NON EMPTY" expression.
174     *
175     * @return Whether this axis should filter out empty rows
176     *
177     * @see #setNonEmpty(boolean)
178     */
179    public boolean isNonEmpty() {
180        return nonEmpty;
181    }
182
183    /**
184     * Sets whether this QueryAxis filters out empty rows.
185     *
186     * @param nonEmpty Whether this axis should filter out empty rows
187     *
188     * @see #isNonEmpty()
189     */
190    public void setNonEmpty(boolean nonEmpty) {
191        this.nonEmpty = nonEmpty;
192    }
193
194    /**
195     * List of QueryDimension objects. The list is active: when a dimension
196     * is added to the list, it is removed from its previous axis.
197     */
198    private class DimensionList extends AbstractList<QueryDimension> {
199        private final List<QueryDimension> list =
200            new ArrayList<QueryDimension>();
201
202        public QueryDimension get(int index) {
203            return list.get(index);
204        }
205
206        public int size() {
207            return list.size();
208        }
209
210        public QueryDimension set(int index, QueryDimension dimension) {
211            if (dimension.getAxis() != null
212                && dimension.getAxis() != QueryAxis.this)
213            {
214                dimension.getAxis().getDimensions().remove(dimension);
215            }
216            dimension.setAxis(QueryAxis.this);
217            return list.set(index, dimension);
218        }
219
220        public void add(int index, QueryDimension dimension) {
221            if (this.contains(dimension)) {
222                throw new IllegalStateException(
223                    "dimension already on this axis");
224            }
225            if (dimension.getAxis() != null
226                && dimension.getAxis() != QueryAxis.this)
227            {
228                // careful! potential for loop
229                dimension.getAxis().getDimensions().remove(dimension);
230            }
231            dimension.setAxis(QueryAxis.this);
232            if (index >= list.size()) {
233                list.add(dimension);
234            } else {
235                list.add(index, dimension);
236            }
237        }
238
239        public QueryDimension remove(int index) {
240            QueryDimension dimension = list.remove(index);
241            dimension.setAxis(null);
242            return dimension;
243        }
244    }
245
246    void tearDown() {
247        for (QueryDimension node : this.getDimensions()) {
248            node.tearDown();
249        }
250        this.clearListeners();
251        this.getDimensions().clear();
252    }
253
254    /**
255     * <p>Sorts the axis according to the supplied order. The sort evaluation
256     * expression will be the default member of the default hierarchy of
257     * the dimension named "Measures".
258     * @param order The {@link SortOrder} to apply
259     * @throws OlapException If an error occurs while resolving
260     * the default measure of the underlying cube.
261     */
262    public void sort(SortOrder order) throws OlapException {
263        sort(
264            order,
265            query.getCube().getDimensions().get("Measures")
266                .getDefaultHierarchy().getDefaultMember());
267    }
268
269    /**
270     * Sorts the axis according to the supplied order
271     * and member unique name.
272     *
273     * <p>Using this method will try to resolve the supplied name
274     * parts from the underlying cube and find the corresponding
275     * member. This member will then be passed as a sort evaluation
276     * expression.
277     *
278     * @param order The {@link SortOrder} in which to
279     * sort the axis.
280     * @param nameParts The unique name parts of the sort
281     * evaluation expression.
282     * @throws OlapException If the supplied member cannot be resolved
283     *     with {@link org.olap4j.metadata.Cube#lookupMember(java.util.List)}
284     */
285    public void sort(SortOrder order, List<IdentifierSegment> nameParts)
286        throws OlapException
287    {
288        assert order != null;
289        assert nameParts != null;
290        Member member = query.getCube().lookupMember(nameParts);
291        if (member == null) {
292            throw new OlapException("Cannot find member.");
293        }
294        sort(order, member);
295    }
296
297    /**
298     * <p>Sorts the axis according to the supplied order
299     * and member.
300     * <p>This method is most commonly called by passing
301     * it a {@link Measure}.
302     * @param order The {@link SortOrder} in which to
303     * sort the axis.
304     * @param member The member that will be used as a sort
305     * evaluation expression.
306     */
307    public void sort(SortOrder order, Member member) {
308        assert order != null;
309        assert member != null;
310        sort(order, member.getUniqueName());
311    }
312
313    /**
314     * <p>Sorts the axis according to the supplied order
315     * and evaluation expression.
316     * <p>The string value passed as the sortEvaluationLitteral
317     * parameter will be used literally as a sort evaluator.
318     * @param order The {@link SortOrder} in which to
319     * sort the axis.
320     * @param sortEvaluationLiteral The literal expression that
321     * will be used to sort against.
322     */
323    public void sort(SortOrder order, String sortEvaluationLiteral) {
324        assert order != null;
325        assert sortEvaluationLiteral != null;
326        this.sortOrder = order;
327        this.sortEvaluationLiteral = sortEvaluationLiteral;
328    }
329
330    /**
331     * Clears the sort parameters from this axis.
332     */
333    public void clearSort() {
334        this.sortEvaluationLiteral = null;
335        this.sortOrder = null;
336    }
337
338    /**
339     * Returns the current sort order in which this
340     * axis will be sorted. Might return null of none
341     * is currently specified.
342     * @return The {@link SortOrder}
343     */
344    public SortOrder getSortOrder() {
345        return this.sortOrder;
346    }
347
348    /**
349     * Returns the current sort evaluation expression,
350     * or null if none are currently defined.
351     * @return The string literal that will be used in the
352     * MDX Order() function.
353     */
354    public String getSortIdentifierNodeName() {
355        return sortEvaluationLiteral;
356    }
357}
358
359// End QueryAxis.java
360
361