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