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