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) 2005-2005 Julian Hyde
008// Copyright (C) 2005-2012 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.rolap.agg;
012
013import mondrian.olap.Util;
014import mondrian.rolap.RolapUtil;
015import mondrian.rolap.StarColumnPredicate;
016import mondrian.util.ArraySortedSet;
017import mondrian.util.Pair;
018
019import java.util.*;
020
021/**
022 * Collection of values of one of the columns that parameterizes a
023 * {@link Segment}.
024 */
025public class SegmentAxis {
026
027    /**
028     * Constraint on the keys in this Axis. Never null.
029     */
030    final StarColumnPredicate predicate;
031
032    /**
033     * Whether predicate is always true.
034     */
035    private final boolean predicateAlwaysTrue;
036
037    private final Set<Object> predicateValues;
038
039    /**
040     * Map holding the position of each key value.
041     *
042     * <p>TODO: Hold keys in a sorted array, then deduce ordinal by doing
043     * binary search.
044     */
045    private final Map<Comparable, Integer> mapKeyToOffset;
046
047    /**
048     * Actual key values retrieved.
049     */
050    private final Comparable[] keys;
051
052    private static final Integer ZERO = Integer.valueOf(0);
053    private static final Integer ONE = Integer.valueOf(1);
054    private static final Comparable[] NO_COMPARABLES = new Comparable[0];
055
056    /**
057     * Internal constructor.
058     */
059    private SegmentAxis(
060        StarColumnPredicate predicate,
061        Comparable[] keys,
062        boolean safe)
063    {
064        this.predicate = predicate;
065        this.predicateAlwaysTrue =
066            predicate instanceof LiteralStarPredicate
067            && ((LiteralStarPredicate) predicate).getValue();
068        this.predicateValues = predicateValueSet(predicate);
069        if (keys.length == 0) {
070            // Optimize the case where axis is empty. Not that infrequent:
071            // it records that mondrian has looked in the database and found
072            // nothing.
073            this.keys = NO_COMPARABLES;
074            this.mapKeyToOffset = Collections.emptyMap();
075        } else {
076            this.keys = keys;
077            mapKeyToOffset =
078                new HashMap<Comparable, Integer>(keys.length * 3 / 2);
079            for (int i = 0; i < keys.length; i++) {
080                mapKeyToOffset.put(keys[i], i);
081            }
082        }
083        assert predicate != null;
084        assert safe || Util.isSorted(Arrays.asList(keys));
085    }
086
087    private static Set<Object> predicateValueSet(
088        StarColumnPredicate predicate)
089    {
090        if (!(predicate instanceof ListColumnPredicate)) {
091            return null;
092        }
093        ListColumnPredicate listColumnPredicate =
094            (ListColumnPredicate) predicate;
095        final List<StarColumnPredicate> predicates =
096            listColumnPredicate.getPredicates();
097        if (predicates.size() < 10) {
098            return null;
099        }
100        final HashSet<Object> set = new HashSet<Object>();
101        for (StarColumnPredicate subPredicate : predicates) {
102            if (subPredicate instanceof ValueColumnPredicate) {
103                ValueColumnPredicate valueColumnPredicate =
104                    (ValueColumnPredicate) subPredicate;
105                valueColumnPredicate.values(set);
106            } else {
107                return null;
108            }
109        }
110        return set;
111    }
112
113    /**
114     * Creates a SegmentAxis populated with an array of key values. The key
115     * values must be sorted.
116     *
117     * @param predicate Predicate defining which keys should appear on
118     *                  axis. (If a key passes the predicate but
119     *                  is not in the list, every cell with that
120     *                  key is assumed to have a null value.)
121     * @param keys      Keys
122     */
123    SegmentAxis(StarColumnPredicate predicate, Comparable[] keys) {
124        this(predicate, keys, false);
125    }
126
127    /**
128     * Creates a SegmentAxis populated with a set of key values.
129     *
130     * @param predicate Predicate defining which keys should appear on
131     *                  axis. (If a key passes the predicate but
132     *                  is not in the list, every cell with that
133     *                  key is assumed to have a null value.)
134     * @param keySet Set of distinct key values, sorted
135     * @param hasNull  Whether the axis contains the null value, in addition
136     *                 to the values in <code>valueSet</code>
137     */
138    public SegmentAxis(
139        StarColumnPredicate predicate,
140        SortedSet<Comparable> keySet,
141        boolean hasNull)
142    {
143        this(predicate, toArray(keySet, hasNull), true);
144    }
145
146    private static Comparable[] toArray(
147        SortedSet<Comparable> keySet,
148        boolean hasNull)
149    {
150        int size = keySet.size();
151        if (hasNull) {
152            size++;
153        }
154        Comparable[] keys = keySet.toArray(new Comparable[size]);
155        if (hasNull) {
156            keys[size - 1] = RolapUtil.sqlNullValue;
157        }
158        return keys;
159    }
160
161    final StarColumnPredicate getPredicate() {
162        return predicate;
163    }
164
165    final Comparable[] getKeys() {
166        return keys;
167    }
168
169    final int getOffset(Comparable key) {
170        if (keys.length == 1) {
171            return keys[0].equals(key) ? 0 : -1;
172        }
173        Integer ordinal = mapKeyToOffset.get(key);
174        if (ordinal == null) {
175            return -1;
176        }
177        return ordinal;
178    }
179
180    /**
181     * Returns whether this axis contains a given key, or would contain it
182     * if it existed.
183     *
184     * <p>For example, if this axis is unconstrained, then this method
185     * returns <code>true</code> for any value.
186     *
187     * @param key Key
188     * @return Whether this axis would contain <code>key</code>
189     */
190    public final boolean wouldContain(Object key) {
191        return predicateAlwaysTrue
192            || (predicateValues != null
193                ? predicateValues.contains(key)
194                : predicate.evaluate(key));
195    }
196
197    /**
198     * Returns how many of this SegmentAxis's keys match a given constraint.
199     *
200     * @param predicate Predicate
201     * @return How many keys match constraint
202     */
203    public int getMatchCount(StarColumnPredicate predicate) {
204        int matchCount = 0;
205        for (Object key : keys) {
206            if (predicate.evaluate(key)) {
207                ++matchCount;
208            }
209        }
210        return matchCount;
211    }
212
213    @SuppressWarnings({"unchecked"})
214    public Pair<SortedSet<Comparable>, Boolean> getValuesAndIndicator() {
215        if (keys.length > 0
216            && keys[keys.length - 1] == RolapUtil.sqlNullValue)
217        {
218            return (Pair) Pair.of(
219                new ArraySortedSet(keys, 0, keys.length - 1),
220                Boolean.TRUE);
221        } else {
222            return (Pair) Pair.of(
223                new ArraySortedSet(keys),
224                Boolean.FALSE);
225        }
226    }
227}
228
229// End SegmentAxis.java