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) 2004-2005 Julian Hyde
008// Copyright (C) 2005-2011 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.AbstractListCalc;
015import mondrian.mdx.ResolvedFunCall;
016import mondrian.olap.*;
017
018import java.util.*;
019
020/**
021 * Definition of the <code>INTERSECT</code> MDX function.
022 *
023 * @author jhyde
024 * @since Mar 23, 2006
025 */
026class IntersectFunDef extends FunDefBase
027{
028    private static final String[] ReservedWords = new String[] {"ALL"};
029
030    static final Resolver resolver =
031        new ReflectiveMultiResolver(
032            "Intersect",
033            "Intersect(<Set1>, <Set2>[, ALL])",
034            "Returns the intersection of two input sets, optionally retaining duplicates.",
035            new String[] {"fxxxy", "fxxx"},
036            IntersectFunDef.class,
037            ReservedWords);
038
039    public IntersectFunDef(FunDef dummyFunDef)
040    {
041        super(dummyFunDef);
042    }
043
044    public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
045        final String literalArg = getLiteralArg(call, 2, "", ReservedWords);
046        final boolean all = literalArg.equalsIgnoreCase("ALL");
047        final int arity = call.getType().getArity();
048
049        final ListCalc listCalc1 = compiler.compileList(call.getArg(0));
050        final ListCalc listCalc2 = compiler.compileList(call.getArg(1));
051        return new AbstractListCalc(
052            call, new Calc[] {listCalc1, listCalc2})
053        {
054            public TupleList evaluateList(Evaluator evaluator) {
055                TupleList leftList =
056                    listCalc1.evaluateList(evaluator);
057                if (leftList.isEmpty()) {
058                    return leftList;
059                }
060                final TupleList rightList =
061                    listCalc2.evaluateList(evaluator);
062                if (rightList.isEmpty()) {
063                    return rightList;
064                }
065
066                // Set of members from the right side of the intersect.
067                // We use a RetrievableSet because distinct keys
068                // (regular members and visual totals members) compare
069                // identical using hashCode and equals, we want to retrieve
070                // the actual key, and java.util.Set only has containsKey.
071                RetrievableSet<List<Member>> rightSet =
072                    new RetrievableHashSet<List<Member>>(
073                        rightList.size() * 3 / 2);
074                for (List<Member> tuple : rightList) {
075                    rightSet.add(tuple);
076                }
077
078                final TupleList result =
079                    TupleCollections.createList(
080                        arity, Math.min(leftList.size(), rightList.size()));
081                final Set<List<Member>> resultSet =
082                    all ? null : new HashSet<List<Member>>();
083                for (List<Member> leftTuple : leftList) {
084                    List<Member> rightKey = rightSet.getKey(leftTuple);
085                    if (rightKey == null) {
086                        continue;
087                    }
088                    if (resultSet != null && !resultSet.add(leftTuple)) {
089                        continue;
090                    }
091                    result.add(
092                        copyTupleWithVisualTotalsMembersOverriding(
093                            leftTuple, rightKey));
094                }
095                return result;
096            }
097
098            /**
099             * Constructs a tuple consisting of members from
100             * {@code leftTuple}, but overridden by any corresponding
101             * members from {@code rightKey} that happen to be visual totals
102             * members.
103             *
104             * <p>Returns the original tuple if there are no visual totals
105             * members on the RHS.
106             *
107             * @param leftTuple Original tuple
108             * @param rightKey Right tuple
109             * @return Copy of original tuple, with any VisualTotalMembers
110             *   from right tuple overriding
111             */
112            private List<Member> copyTupleWithVisualTotalsMembersOverriding(
113                List<Member> leftTuple, List<Member> rightKey)
114            {
115                List<Member> tuple = leftTuple;
116                for (int i = 0; i < rightKey.size(); i++) {
117                    Member member = rightKey.get(i);
118                    if (!(tuple.get(i)
119                        instanceof VisualTotalsFunDef.VisualTotalMember)
120                        && member instanceof
121                        VisualTotalsFunDef.VisualTotalMember)
122                    {
123                        if (tuple == leftTuple) {
124                            // clone on first VisualTotalMember -- to avoid
125                            // alloc/copy in the common case where there are
126                            // no VisualTotalMembers
127                            tuple = new ArrayList<Member>(leftTuple);
128                        }
129                        tuple.set(i, member);
130                    }
131                }
132                return tuple;
133            }
134        };
135    }
136
137    /**
138     * Interface similar to the Set interface that allows key values to be
139     * returned.
140     *
141     * <p>Useful if multiple objects can compare equal (using
142     * {@link Object#equals(Object)} and {@link Object#hashCode()}, per the
143     * set contract) and you wish to distinguish them after they have been added
144     * to the set.
145     *
146     * @param <E> element type
147     */
148    private interface RetrievableSet<E> {
149        /**
150         * Returns the key in this set that compares equal to a given object,
151         * or null if there is no such key.
152         *
153         * @param e Key value
154         * @return Key in the set equal to given key value
155         */
156        E getKey(E e);
157
158        /**
159         * Analogous to {@link Set#add(Object)}.
160         *
161         * @param e element to be added to this set
162         * @return <tt>true</tt> if this set did not already contain the
163         *         specified element
164         */
165        boolean add(E e);
166    }
167
168    private static class RetrievableHashSet<E>
169        extends HashMap<E, E>
170        implements RetrievableSet<E>
171    {
172        public RetrievableHashSet(int initialCapacity) {
173            super(initialCapacity);
174        }
175
176        public E getKey(E e) {
177            return super.get(e);
178        }
179
180        public boolean add(E e) {
181            return super.put(e, e) == null;
182        }
183    }
184}
185
186// End IntersectFunDef.java