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