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