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