001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/fun/AbstractAggregateFunDef.java#19 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2005-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package mondrian.olap.fun;
011    
012    import mondrian.calc.*;
013    import mondrian.olap.*;
014    import mondrian.resource.MondrianResource;
015    import mondrian.mdx.UnresolvedFunCall;
016    import mondrian.rolap.RolapMember;
017    import mondrian.rolap.RolapCube;
018    import mondrian.rolap.RolapStoredMeasure;
019    
020    import java.util.*;
021    
022    /**
023     * Abstract base class for all aggregate functions (<code>Aggregate</code>,
024     * <code>Sum</code>, <code>Avg</code>, et cetera).
025     *
026     * @author jhyde
027     * @since 2005/8/14
028     * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/AbstractAggregateFunDef.java#19 $
029     */
030    public class AbstractAggregateFunDef extends FunDefBase {
031        public AbstractAggregateFunDef(FunDef dummyFunDef) {
032            super(dummyFunDef);
033        }
034    
035        protected Exp validateArg(
036                Validator validator, Exp[] args, int i, int category) {
037            // If expression cache is enabled, wrap first expression (the set)
038            // in a function which will use the expression cache.
039            if (i == 0) {
040                if (MondrianProperties.instance().EnableExpCache.get()) {
041                    Exp arg = args[0];
042                    final Exp cacheCall = new UnresolvedFunCall(
043                            CacheFunDef.NAME,
044                            Syntax.Function,
045                            new Exp[] {arg});
046                    return validator.validate(cacheCall, false);
047                }
048            }
049            return super.validateArg(validator, args, i, category);
050        }
051    
052        /**
053         * Evaluates the list of members or tuples used in computing the aggregate.
054         * If the measure for aggregation has to ignore unrelated dimensions
055         * this method will push unrelated dimension members to top level member.
056         * This behaviour is driven by the ignoreUnrelatedDimensions property
057         * on a base cube usage specified in the virtual cube.Keeps track of the
058         * number of iterations that will be required to iterate over the members
059         * or tuples needed to compute the aggregate within the current context.
060         * In doing so, also determines if the cross product of all iterations
061         * across all parent evaluation contexts will exceed the limit set in the
062         * properties file.
063         *
064         * @param listCalc  calculator used to evaluate the member list
065         * @param evaluator current evalutor
066         * @return list of evaluated members or tuples
067         */
068        protected static List evaluateCurrentList(
069            ListCalc listCalc,
070            Evaluator evaluator)
071        {
072            List tuples = listCalc.evaluateList(evaluator);
073    
074            int currLen = tuples.size();
075            crossProd(evaluator, currLen);
076    
077            return processUnrelatedDimensions(tuples, evaluator);
078        }
079    
080        protected Iterable evaluateCurrentIterable(
081            IterCalc iterCalc,
082            Evaluator evaluator)
083        {
084            Iterable iter = iterCalc.evaluateIterable(evaluator);
085    
086            int currLen = 0;
087            crossProd(evaluator, currLen);
088    
089            return iter;
090        }
091    
092        private static void crossProd(Evaluator evaluator, int currLen) {
093            long iterationLimit =
094                MondrianProperties.instance().IterationLimit.get();
095            if (iterationLimit > 0) {
096                int productLen = currLen;
097                Evaluator parent = evaluator.getParent();
098                while (parent != null) {
099                    productLen *= parent.getIterationLength();
100                    parent = parent.getParent();
101                }
102                if (productLen > iterationLimit) {
103                    throw MondrianResource.instance().
104                        IterationLimitExceeded.ex(iterationLimit);
105                }
106            }
107            evaluator.setIterationLength(currLen);
108        }
109    
110        /**
111         * Pushes unrelated dimensions to the top level member from the given list
112         * of tuples if the ignoreUnrelatedDimensions property is set on the base
113         * cube usage in the virtual cube.
114         *
115         * <p>If IgnoreMeasureForNonJoiningDimension is set to true and
116         * ignoreUnrelatedDimensions on CubeUsage is set to false then if a non
117         * joining dimension exists in the aggregation list then return an empty
118         * list else return the original list.
119         *
120         * @param tuplesForAggregation is a list of members or tuples used in
121         * computing the aggregate
122         * @param evaluator
123         * @return list of members or tuples
124         */
125        private static List processUnrelatedDimensions(
126            List tuplesForAggregation,
127            Evaluator evaluator)
128        {
129    
130            if (tuplesForAggregation.size() == 0) {
131                return tuplesForAggregation;
132            }
133    
134            RolapMember measure = (RolapMember) evaluator.getMembers()[0];
135    
136            if (measure.isCalculated()) {
137                return tuplesForAggregation;
138            }
139    
140            RolapCube virtualCube = (RolapCube) evaluator.getCube();
141            RolapCube baseCube = (RolapCube) evaluator.getMeasureCube();
142            if (virtualCube.isVirtual() && baseCube != null) {
143                if (virtualCube.shouldIgnoreUnrelatedDimensions(baseCube.getName())) {
144                    return ignoreUnrelatedDimensions(tuplesForAggregation, baseCube);
145                } else if (MondrianProperties.instance()
146                    .IgnoreMeasureForNonJoiningDimension.get()) {
147                    return ignoreMeasureForNonJoiningDimension(
148                            tuplesForAggregation, baseCube);
149                }
150            }
151            return tuplesForAggregation;
152        }
153    
154        /**
155         * If a non joining dimension exists in the aggregation list then return
156         * an empty list else return the original list.
157    
158         * @param tuplesForAggregation is a list of members or tuples used in
159         * computing the aggregate
160         * @param baseCube
161         * @return list of members or tuples
162         */
163        private static List ignoreMeasureForNonJoiningDimension(
164            List tuplesForAggregation,
165            RolapCube baseCube)
166        {
167            Set<Dimension> nonJoiningDimensions =
168                nonJoiningDimensions(baseCube, tuplesForAggregation);
169            if (nonJoiningDimensions.size() > 0) {
170                return new ArrayList();
171            }
172            return tuplesForAggregation;
173        }
174    
175        /**
176         * Pushes unrelated dimensions to the top level member from the given list
177         * of tuples if the ignoreUnrelatedDimensions property is set on the base
178         * cube usage in the virtual cube.
179         *
180         * @param tuplesForAggregation is a list of members or tuples used in
181         * computing the aggregate
182         * @return list of members or tuples
183         */
184        private static List ignoreUnrelatedDimensions(
185            List tuplesForAggregation,
186            RolapCube baseCube)
187        {
188            Set<Dimension> nonJoiningDimensions =
189                nonJoiningDimensions(baseCube, tuplesForAggregation);
190            Set processedTuples = new LinkedHashSet(tuplesForAggregation.size());
191            for (int i = 0; i < tuplesForAggregation.size(); i++) {
192                Member[] tuples = copy(tupleAsArray(tuplesForAggregation.get(i)));
193                for (int j = 0; j < tuples.length; j++) {
194                    if (nonJoiningDimensions.contains(
195                        tuples[j].getDimension())) {
196                        final Hierarchy hierarchy =
197                            tuples[j].getDimension().getHierarchy();
198                        if (hierarchy.hasAll()) {
199                            tuples[j] = hierarchy.getAllMember();
200                        } else {
201                            tuples[j] = hierarchy.getDefaultMember();
202                        }
203                    }
204                }
205                if (tuplesForAggregation.get(i) instanceof Member[]) {
206                    processedTuples.add(new MemberArray(tuples));
207                } else {
208                    processedTuples.add(tuples[0]);
209                }
210    
211            }
212            return tuplesAsList(processedTuples);
213        }
214    
215        private static Set<Dimension> nonJoiningDimensions(
216            RolapCube baseCube,
217            List tuplesForAggregation)
218        {
219            Member[] tuple = tupleAsArray(tuplesForAggregation.get(0));
220            return baseCube.nonJoiningDimensions(tuple);
221        }
222    
223        private static List tuplesAsList(Set tuples) {
224            List results = new ArrayList(tuples.size());
225            for (Object tuple : tuples) {
226                if (tuple instanceof MemberArray) {
227                    results.add(((MemberArray) tuple).memberArray);
228                } else {
229                    results.add(tuple);
230                }
231            }
232            return results;
233        }
234    
235        private static Member[] copy(Member[] members) {
236            Member[] result = new Member[members.length];
237            System.arraycopy(members, 0, result, 0, members.length);
238            return result;
239        }
240    
241        private static Member[] tupleAsArray(Object tuple) {
242            Member[] result;
243            if (tuple instanceof Member[]) {
244                result = ((Member[]) tuple);
245            } else {
246                result = new Member[]{((Member) tuple)};
247            }
248            return result;
249        }
250    
251        private static class MemberArray {
252            private Object[] memberArray;
253    
254            public MemberArray(Object[] memberArray) {
255                this.memberArray = memberArray;
256            }
257    
258            public int hashCode() {
259                return Arrays.hashCode(memberArray);
260            }
261    
262            public boolean equals(Object obj) {
263                return Arrays.deepEquals(
264                    memberArray,
265                    ((MemberArray) obj).memberArray);
266            }
267        }
268    }
269    
270    // End AbstractAggregateFunDef.java