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