001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2010-2012 Pentaho and others 008// All Rights Reserved. 009*/ 010package mondrian.rolap.agg; 011 012import mondrian.olap.Util; 013import mondrian.rolap.*; 014 015import java.util.*; 016 017/** 018 * Extension to {@link Segment} with a data set. 019 * 020 * @author jhyde 021 */ 022public class SegmentWithData extends Segment { 023 /** 024 * An array of axes, one for each constraining column, containing the values 025 * returned for that constraining column. 026 */ 027 final SegmentAxis[] axes; 028 029 /** 030 * <p><code>data</code> holds a reference to the <code>SegmentDataset</code> 031 * that contains the underlying cell values.</p> 032 * 033 * <p>Since the <code>SegmentDataset</code> is loaded and assigned after 034 * <code>Segment</code> is constructed, threadsafe access to it is only 035 * guaranteed if the access is guarded.<p/> 036 * 037 * <p>Access which does not depend on <code>data</code> already having been 038 * loaded should be guarded by obtaining either a read or write lock on 039 * <code>stateLock</code>, as appropriate.</p> 040 * 041 * <p>Access that should not proceed until the <code>data</code> reference 042 * has been loaded should be guarded using the <code>dataGate</code> latch. 043 * This is typically accomplished by calling <code>waitUntilLoaded()</code>, 044 * which will block until the latch is released and throw an error if 045 * <code>data</code> failed to load.</p> 046 * 047 * <p>Once set, the value of <code>data</code> is presumed to be invariant 048 * and should never be reset, nor should the contents be modified. Thus, 049 * for a given thread, any read access to data which comes after 050 * <code>dataGate.await()</code> (or, by extension, 051 * <code>waitUntilLoaded</code> will be threadsafe.</p> 052 */ 053 private final SegmentDataset data; 054 055 /** 056 * Creates a SegmentWithData from an existing Segment. 057 * 058 * @param segment Segment (without data) 059 * @param data Data set 060 */ 061 public SegmentWithData( 062 Segment segment, 063 SegmentDataset data, 064 SegmentAxis[] axes) 065 { 066 this( 067 segment.getStar(), 068 segment.getConstrainedColumnsBitKey(), 069 segment.getColumns(), 070 segment.measure, 071 segment.predicates, 072 segment.getExcludedRegions(), 073 segment.compoundPredicateList, 074 data, 075 axes); 076 if (segment instanceof SegmentWithData) { 077 throw new AssertionError(); 078 } 079 } 080 081 /** 082 * Creates a SegmentWithData. 083 * 084 * @param star Star that this Segment belongs to 085 * @param measure Measure whose values this Segment contains 086 * @param predicates List of axes; each is a constraint plus a list of 087 * values. 088 * @param excludedRegions List of regions which are not in this segment. 089 */ 090 private SegmentWithData( 091 RolapStar star, 092 BitKey constrainedColumnsBitKey, 093 RolapStar.Column[] columns, 094 RolapStar.Measure measure, 095 StarColumnPredicate[] predicates, 096 List<ExcludedRegion> excludedRegions, 097 final List<StarPredicate> compoundPredicateList, 098 SegmentDataset data, 099 SegmentAxis[] axes) 100 { 101 super( 102 star, 103 constrainedColumnsBitKey, 104 columns, 105 measure, 106 predicates, 107 excludedRegions, 108 compoundPredicateList); 109 this.axes = axes; 110 this.data = data; 111 } 112 113 @Override 114 protected void describeAxes(StringBuilder buf, int i, boolean values) { 115 super.describeAxes(buf, i, values); 116 if (!values) { 117 return; 118 } 119 Object[] keys = axes[i].getKeys(); 120 buf.append(", values={"); 121 for (int j = 0; j < keys.length; j++) { 122 if (j > 0) { 123 buf.append(", "); 124 } 125 Object key = keys[j]; 126 buf.append(key); 127 } 128 buf.append("}"); 129 } 130 131 /** 132 * Retrieves the value at the location identified by 133 * <code>keys</code>. 134 * 135 * <p>Returns<ul> 136 * 137 * <li>{@link mondrian.olap.Util#nullValue} if the cell value 138 * is null (because no fact table rows met those criteria);</li> 139 * 140 * <li><code>null</code> if the value is not supposed to be in this segment 141 * (because one or more of the keys do not pass the axis criteria);</li> 142 * 143 * <li>the data value otherwise</li> 144 * 145 * </ul></p> 146 * 147 * @see mondrian.olap.Util#deprecated(Object) make package-private? 148 */ 149 public Object getCellValue(Object[] keys) { 150 assert keys.length == axes.length; 151 int missed = 0; 152 CellKey cellKey = CellKey.Generator.newCellKey(axes.length); 153 for (int i = 0; i < keys.length; i++) { 154 Comparable key = (Comparable) keys[i]; 155 int offset = axes[i].getOffset(key); 156 if (offset < 0) { 157 if (axes[i].wouldContain(key)) { 158 // see whether this segment should contain this value 159 missed++; 160 continue; 161 } else { 162 // this value should not appear in this segment; we 163 // should be looking in a different segment 164 return null; 165 } 166 } 167 cellKey.setAxis(i, offset); 168 } 169 if (isExcluded(keys)) { 170 // this value should not appear in this segment; we 171 // should be looking in a different segment 172 return null; 173 } 174 if (missed > 0) { 175 // the value should be in this segment, but isn't, because one 176 // or more of its keys does have any values 177 return Util.nullValue; 178 } else { 179 Object o = data.getObject(cellKey); 180 if (o == null) { 181 o = Util.nullValue; 182 } 183 return o; 184 } 185 } 186 187 /** 188 * Returns whether the given set of key values will be in this segment 189 * when it finishes loading. 190 */ 191 boolean wouldContain(Object[] keys) { 192 Util.assertTrue(keys.length == axes.length); 193 for (int i = 0; i < keys.length; i++) { 194 Object key = keys[i]; 195 if (!axes[i].wouldContain(key)) { 196 return false; 197 } 198 } 199 return !isExcluded(keys); 200 } 201 202 /** 203 * Returns the number of cells in this Segment, deducting cells in 204 * excluded regions. 205 * 206 * <p>This method may return a value which is slightly too low, or 207 * occasionally even negative. This occurs when a Segment has more than one 208 * excluded region, and those regions overlap. Cells which are in both 209 * regions will be counted twice. 210 * 211 * @return Number of cells in this Segment 212 */ 213 public int getCellCount() { 214 int cellCount = 1; 215 for (SegmentAxis axis : axes) { 216 cellCount *= axis.getKeys().length; 217 } 218 for (ExcludedRegion excludedRegion : excludedRegions) { 219 cellCount -= excludedRegion.getCellCount(); 220 } 221 return cellCount; 222 } 223 224 /** 225 * Creates a Segment which has the same dimensionality as this Segment and a 226 * subset of the values. 227 * 228 * <p>If <code>bestColumn</code> is not -1, the <code>bestColumn</code>th 229 * column's predicate should be replaced by <code>bestPredicate</code>. 230 * 231 * @param axisKeepBitSets For each axis, a bitmap of the axis values to 232 * keep; each axis must have at least one bit set 233 * @param bestColumn The column that retains most of its values 234 * @param bestPredicate 235 * @param excludedRegions List of regions to exclude from segment 236 * @return Segment containing a subset of the values 237 */ 238 SegmentWithData createSubSegment( 239 BitSet[] axisKeepBitSets, 240 int bestColumn, 241 StarColumnPredicate bestPredicate, 242 List<ExcludedRegion> excludedRegions) 243 { 244 assert axisKeepBitSets.length == axes.length; 245 246 // Create a new segment with a subset of the values. If only one 247 // of the axes is restricted, restrict just that axis. If more than 248 // one of the axis is restricted, add a negation to the segment. 249 final SegmentAxis[] newAxes = axes.clone(); 250 final StarColumnPredicate[] newPredicates = predicates.clone(); 251 252 // For each axis, map from old position to new position. 253 final Map<Integer, Integer>[] axisPosMaps = new Map[axes.length]; 254 255 int valueCount = 1; 256 for (int j = 0; j < axes.length; j++) { 257 SegmentAxis axis = axes[j]; 258 StarColumnPredicate newPredicate = axis.getPredicate(); 259 if (j == bestColumn) { 260 newPredicate = bestPredicate; 261 } 262 final Comparable[] axisKeys = axis.getKeys(); 263 BitSet keepBitSet = axisKeepBitSets[j]; 264 int firstClearBit = keepBitSet.nextClearBit(0); 265 Comparable[] newAxisKeys; 266 if (firstClearBit >= axisKeys.length) { 267 // Keep everything 268 newAxisKeys = axisKeys; 269 axisPosMaps[j] = null; // identity map 270 } else { 271 List<Object> newAxisKeyList = new ArrayList<Object>(); 272 Map<Integer, Integer> map = 273 axisPosMaps[j] = 274 new HashMap<Integer, Integer>(); 275 for (int bit = keepBitSet.nextSetBit(0); 276 bit >= 0; 277 bit = keepBitSet.nextSetBit(bit + 1)) 278 { 279 map.put(bit, newAxisKeyList.size()); 280 newAxisKeyList.add(axisKeys[bit]); 281 } 282 newAxisKeys = 283 newAxisKeyList.toArray( 284 new Comparable[newAxisKeyList.size()]); 285 assert newAxisKeys.length > 0; 286 } 287 final SegmentAxis newAxis = 288 new SegmentAxis(newPredicate, newAxisKeys); 289 newAxes[j] = newAxis; 290 newPredicates[j] = newPredicate; 291 valueCount *= newAxisKeys.length; 292 } 293 294 // Create a dataset containing a subset of the current dataset. 295 // Keep the same representation as the current dataset. 296 // (We could be smarter - sometimes a subset of a sparse dataset will 297 // be dense and VERY occasionally a subset of a relatively dense dataset 298 // will be sparse.) 299 SegmentDataset newData = 300 createDataset( 301 axes, 302 data instanceof SparseSegmentDataset, 303 data.getType(), 304 valueCount); 305 306 // If the source is sparse, it is more efficient to iterate over the 307 // values we need. If it's dense, it doesn't matter too much. 308 int[] pos = new int[axes.length]; 309 data: 310 for (Map.Entry<CellKey, Object> entry : data) { 311 CellKey key = entry.getKey(); 312 313 // Map each of the source coordinates to the target coordinate. 314 // If any of the coordinates maps to null, it means that the 315 // cell falls outside the subset. 316 for (int i = 0; i < pos.length; i++) { 317 int ordinal = key.getAxis(i); 318 319 Map<Integer, Integer> axisPosMap = axisPosMaps[i]; 320 if (axisPosMap == null) { 321 pos[i] = ordinal; 322 } else { 323 Integer integer = axisPosMap.get(ordinal); 324 if (integer == null) { 325 continue data; 326 } 327 pos[i] = integer; 328 } 329 } 330 newData.populateFrom(pos, data, key); 331 } 332 333 // Create a segment with the new data set. 334 return new SegmentWithData( 335 star, constrainedColumnsBitKey, columns, measure, 336 newPredicates, excludedRegions, compoundPredicateList, 337 newData, newAxes); 338 } 339 340 /** 341 * <p>Returns the data set.</p> 342 * 343 * <p>WARNING: the returned SegmentDataset reference should not be modified; 344 * it is assumed to be invariant.</p> 345 * 346 * @return The <code>data</code> reference 347 */ 348 public final SegmentDataset getData() { 349 return data; 350 } 351} 352 353// End SegmentWithData.java