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) 2007-2011 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.rolap.agg;
011
012import mondrian.olap.Util;
013import mondrian.rolap.*;
014import mondrian.rolap.sql.SqlQuery;
015
016import java.util.*;
017
018/**
019 * Predicate which constrains a column to a particular member, or a range
020 * above or below a member, or a range between two members.
021 *
022 * @author jhyde
023 */
024public class MemberTuplePredicate implements StarPredicate {
025    private final Bound[] bounds;
026    private final List<RolapStar.Column> columnList;
027    private BitKey columnBitKey;
028
029    /**
030     * Creates a MemberTuplePredicate which evaluates to true for a given
031     * range of members.
032     *
033     * <p>The range can be open above or below, but at least one bound is
034     * required.
035     *
036     * @param baseCube base cube for virtual members
037     * @param lower Member which forms the lower bound, or null if range is
038     *   open below
039     * @param lowerStrict Whether lower bound of range is strict
040     * @param upper Member which forms the upper bound, or null if range is
041     *   open above
042     * @param upperStrict Whether upper bound of range is strict
043     */
044    public MemberTuplePredicate(
045        RolapCube baseCube,
046        RolapMember lower,
047        boolean lowerStrict,
048        RolapMember upper,
049        boolean upperStrict)
050    {
051        columnBitKey = null;
052        this.columnList =
053            computeColumnList(lower != null ? lower : upper, baseCube);
054
055        if (lower == null) {
056            assert upper != null;
057            bounds = new Bound[] {
058                new Bound(upper, upperStrict ? RelOp.LT : RelOp.LE)
059            };
060        } else if (upper == null) {
061            bounds = new Bound[] {
062                new Bound(lower, lowerStrict ? RelOp.GT : RelOp.GE)
063            };
064        } else {
065            bounds = new Bound[] {
066                new Bound(lower, lowerStrict ? RelOp.GT : RelOp.GE),
067                new Bound(upper, upperStrict ? RelOp.LT : RelOp.LE)
068            };
069        }
070    }
071
072    /**
073     * Creates a MemberTuplePredicate which evaluates to true for a given
074     * member.
075     *
076     * @param baseCube base cube for virtual members
077     * @param member Member
078     */
079    public MemberTuplePredicate(RolapCube baseCube, RolapCubeMember member) {
080        this.columnList = computeColumnList(member, baseCube);
081
082        this.bounds = new Bound[] {
083            new Bound(member, RelOp.EQ)
084        };
085    }
086
087    public int hashCode() {
088        return this.columnList.hashCode() * 31
089            + Arrays.hashCode(this.bounds) * 31;
090    }
091
092    public boolean equals(Object obj) {
093        if (obj instanceof MemberTuplePredicate) {
094            MemberTuplePredicate that =
095                (MemberTuplePredicate) obj;
096            return this.columnList.equals(that.columnList)
097                && Arrays.equals(this.bounds, that.bounds);
098        } else {
099            return false;
100        }
101    }
102
103    private List<RolapStar.Column> computeColumnList(
104        RolapMember member,
105        RolapCube baseCube)
106    {
107        List<RolapStar.Column> columnList = new ArrayList<RolapStar.Column>();
108        while (true) {
109            RolapLevel level = member.getLevel();
110            RolapStar.Column column = null;
111            if (level instanceof RolapCubeLevel) {
112                column = ((RolapCubeLevel)level)
113                                .getBaseStarKeyColumn(baseCube);
114            } else {
115                (new Exception()).printStackTrace();
116            }
117
118            if (columnBitKey == null) {
119                columnBitKey =
120                    BitKey.Factory.makeBitKey(
121                        column.getStar().getColumnCount());
122                columnBitKey.clear();
123            }
124            columnBitKey.set(column.getBitPosition());
125            columnList.add(0, column);
126            if (level.isUnique()) {
127                return columnList;
128            }
129            member = member.getParentMember();
130        }
131    }
132
133    /**
134     * Returns a list of constrained columns.
135     *
136     * @return List of constrained columns
137     */
138    public List<RolapStar.Column> getConstrainedColumnList() {
139        return columnList;
140    }
141
142    public BitKey getConstrainedColumnBitKey() {
143        return columnBitKey;
144    }
145
146    public boolean equalConstraint(StarPredicate that) {
147        throw new UnsupportedOperationException();
148    }
149
150    public StarPredicate minus(StarPredicate predicate) {
151        throw new UnsupportedOperationException();
152    }
153
154    public StarPredicate or(StarPredicate predicate) {
155        throw new UnsupportedOperationException();
156    }
157
158    public StarPredicate and(StarPredicate predicate) {
159        throw new UnsupportedOperationException();
160    }
161
162    /**
163     * Evaluates a constraint against a list of values.
164     *
165     * @param valueList List of values, one for each constrained column
166     * @return Whether constraint holds for given set of values
167     */
168    public boolean evaluate(List<Object> valueList) {
169        for (Bound bound : bounds) {
170            for (int k = 0; k < bound.values.length; ++k) {
171                Object value = valueList.get(k);
172                if (value == WILDCARD) {
173                    return false;
174                }
175                Object boundValue = bound.values[k];
176                RelOp relOp = bound.relOps[k];
177                int c = Util.compareKey(value, boundValue);
178                switch (relOp) {
179                case GT:
180                    if (c > 0) {
181                        break;
182                    } else {
183                        return false;
184                    }
185                case GE:
186                    if (c > 0) {
187                        return true;
188                    } else if (c == 0) {
189                        break;
190                    } else {
191                        return false;
192                    }
193                case LT:
194                    if (c < 0) {
195                        break;
196                    } else {
197                        return false;
198                    }
199                case LE:
200                    if (c < 0) {
201                        return true;
202                    } else if (c == 0) {
203                        break;
204                    } else {
205                        return false;
206                    }
207                }
208            }
209        }
210        return true;
211    }
212
213    public void describe(StringBuilder buf) {
214        int k = 0;
215        for (Bound bound : bounds) {
216            if (k++ > 0) {
217                buf.append(" AND ");
218            }
219            buf.append(bound.relOps[bound.relOps.length - 1].getOp());
220            buf.append(' ');
221            buf.append(bound.member);
222        }
223    }
224
225    private enum RelOp {
226        LT("<"),
227        LE("<="),
228        GT(">"),
229        GE(">="),
230        EQ("=");
231
232        private final String op;
233
234        RelOp(String op) {
235            this.op = op;
236        }
237
238        String getOp() {
239            return op;
240        }
241
242        /**
243         * If this is a strict operator (LT, GT) returns the non-strict
244         * equivalent (LE, GE); otherwise returns this operator.
245         *
246         * @return less strict version of this operator
247         */
248        public RelOp desctrict() {
249            switch (this) {
250            case GT:
251                return RelOp.GE;
252            case LT:
253                return RelOp.LE;
254            default:
255                return this;
256            }
257        }
258    }
259
260    private static class Bound {
261        private final RolapMember member;
262        private final Object[] values;
263        private final RelOp[] relOps;
264
265        Bound(RolapMember member, RelOp relOp) {
266            this.member = member;
267            List<Object> valueList = new ArrayList<Object>();
268            List<RelOp> relOpList = new ArrayList<RelOp>();
269            while (true) {
270                valueList.add(0, member.getKey());
271                relOpList.add(0, relOp);
272                if (member.getLevel().isUnique()) {
273                    break;
274                }
275                member = member.getParentMember();
276                relOp = relOp.desctrict();
277            }
278            this.values = valueList.toArray(new Object[valueList.size()]);
279            this.relOps = relOpList.toArray(new RelOp[relOpList.size()]);
280        }
281
282
283        public int hashCode() {
284            int h = member.hashCode();
285            h = h * 31 + Arrays.hashCode(values);
286            h = h * 31 + Arrays.hashCode(relOps);
287            return h;
288        }
289
290        public boolean equals(Object obj) {
291            if (obj instanceof Bound) {
292                Bound that = (Bound) obj;
293                return this.member.equals(that.member)
294                    && Arrays.equals(this.values, that.values)
295                    && Arrays.equals(this.relOps, that.relOps);
296            } else {
297                return false;
298            }
299        }
300    }
301
302    public void toSql(SqlQuery sqlQuery, StringBuilder buf) {
303        throw Util.needToImplement(this);
304    }
305}
306
307// End MemberTuplePredicate.java