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