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    */
020    package org.olap4j.query;
021    
022    import org.olap4j.Axis;
023    import org.olap4j.OlapException;
024    import org.olap4j.mdx.IdentifierSegment;
025    import org.olap4j.metadata.Measure;
026    import org.olap4j.metadata.Member;
027    
028    import 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     */
040    public 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