001    /*
002    // $Id: //open/mondrian-release/3.2/src/main/mondrian/olap/fun/VisualTotalsFunDef.java#6 $
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) 2006-2010 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.calc.impl.AbstractListCalc;
014    import mondrian.mdx.*;
015    import mondrian.olap.*;
016    import mondrian.olap.type.*;
017    import mondrian.rolap.*;
018    import mondrian.resource.MondrianResource;
019    
020    import java.util.ArrayList;
021    import java.util.List;
022    
023    /**
024     * Definition of the <code>VisualTotals</code> MDX function.
025     *
026     * @author jhyde
027     * @version $Id: //open/mondrian-release/3.2/src/main/mondrian/olap/fun/VisualTotalsFunDef.java#6 $
028     * @since Jan 16, 2006
029     */
030    public class VisualTotalsFunDef extends FunDefBase {
031        static final Resolver Resolver =
032            new ReflectiveMultiResolver(
033                "VisualTotals",
034                "VisualTotals(<Set>[, <Pattern>])",
035                "Dynamically totals child members specified in a set using a pattern for the total label in the result set.",
036                new String[] {"fxx", "fxxS"},
037                VisualTotalsFunDef.class);
038    
039        public VisualTotalsFunDef(FunDef dummyFunDef) {
040            super(dummyFunDef);
041        }
042    
043        protected Exp validateArg(
044            Validator validator, Exp[] args, int i, int category)
045        {
046            final Exp validatedArg =
047                super.validateArg(validator, args, i, category);
048            if (i == 0) {
049                // The function signature guarantees that we have a set of members
050                // or a set of tuples.
051                final SetType setType = (SetType) validatedArg.getType();
052                final Type elementType = setType.getElementType();
053                if (!(elementType instanceof MemberType)) {
054                    throw MondrianResource.instance().VisualTotalsAppliedToTuples
055                        .ex();
056                }
057            }
058            return validatedArg;
059        }
060    
061        public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
062            final ListCalc listCalc = compiler.compileList(call.getArg(0));
063            final StringCalc stringCalc =
064                call.getArgCount() > 1
065                ? compiler.compileString(call.getArg(1))
066                : null;
067            return new CalcImpl(call, listCalc, stringCalc);
068        }
069    
070        /**
071         * Calc implementation of the <code>VisualTotals</code> function.
072         */
073        private static class CalcImpl extends AbstractListCalc {
074            private final ListCalc listCalc;
075            private final StringCalc stringCalc;
076    
077            public CalcImpl(
078                ResolvedFunCall call, ListCalc listCalc, StringCalc stringCalc)
079            {
080                super(call, new Calc[] {listCalc, stringCalc});
081                this.listCalc = listCalc;
082                this.stringCalc = stringCalc;
083            }
084    
085            public List evaluateList(Evaluator evaluator) {
086                final List<Member> list = listCalc.evaluateList(evaluator);
087                final List<Member> resultList = new ArrayList<Member>(list);
088                final int memberCount = list.size();
089                for (int i = memberCount - 1; i >= 0; --i) {
090                    Member member = list.get(i);
091                    if (i + 1 < memberCount) {
092                        Member nextMember = resultList.get(i + 1);
093                        if (nextMember != member
094                            && nextMember.isChildOrEqualTo(member))
095                        {
096                            resultList.set(
097                                i,
098                                createMember(member, i, resultList, evaluator));
099                        }
100                    }
101                }
102                return resultList;
103            }
104    
105            private VisualTotalMember createMember(
106                    Member member,
107                    int i,
108                    final List<Member> list,
109                    Evaluator evaluator)
110            {
111                final String name;
112                if (stringCalc != null) {
113                    final String namePattern = stringCalc.evaluateString(evaluator);
114                    name = substitute(namePattern, member.getName());
115                } else {
116                    name = member.getName();
117                }
118                final List<Member> childMemberList =
119                    followingDescendants(member, i + 1, list);
120                final Exp exp = makeExpr(childMemberList);
121                final Validator validator = evaluator.getQuery().createValidator();
122                final Exp validatedExp = exp.accept(validator);
123                return new VisualTotalMember(member, name, validatedExp);
124            }
125    
126            private List<Member> followingDescendants(
127                Member member, int i, final List<Member> list)
128            {
129                List<Member> childMemberList = new ArrayList<Member>();
130                while (i < list.size()) {
131                    Member descendant = list.get(i);
132                    if (descendant.equals(member)) {
133                        // strict descendants only
134                        break;
135                    }
136                    if (!descendant.isChildOrEqualTo(member)) {
137                        break;
138                    }
139                    if (descendant instanceof VisualTotalMember) {
140                        // Add the visual total member, but skip over its children.
141                        VisualTotalMember visualTotalMember =
142                                (VisualTotalMember) descendant;
143                        childMemberList.add(visualTotalMember);
144                        i = lastChildIndex(visualTotalMember.member, i, list);
145                        continue;
146                    }
147                    childMemberList.add(descendant);
148                    ++i;
149                }
150                return childMemberList;
151            }
152    
153            private int lastChildIndex(Member member, int start, List list) {
154                int i = start;
155                while (true) {
156                    ++i;
157                    if (i >= list.size()) {
158                        break;
159                    }
160                    Member descendant = (Member) list.get(i);
161                    if (descendant.equals(member)) {
162                        // strict descendants only
163                        break;
164                    }
165                    if (!descendant.isChildOrEqualTo(member)) {
166                        break;
167                    }
168                }
169                return i;
170            }
171    
172            private Exp makeExpr(final List childMemberList) {
173                Exp[] memberExprs = new Exp[childMemberList.size()];
174                for (int i = 0; i < childMemberList.size(); i++) {
175                    final Member childMember = (Member) childMemberList.get(i);
176                    memberExprs[i] = new MemberExpr(childMember);
177                }
178                return new UnresolvedFunCall(
179                        "Aggregate",
180                        new Exp[] {
181                            new UnresolvedFunCall(
182                                    "{}",
183                                    Syntax.Braces,
184                                    memberExprs)
185                        });
186            }
187        }
188    
189        /**
190         * Calculated member for <code>VisualTotals</code> function.
191         *
192         * <p>It corresponds to a real member, and most of its properties are
193         * similar. The main differences are:<ul>
194         * <li>its name is derived from the VisualTotals pattern, e.g.
195         *     "*Subtotal - Dairy" as opposed to "Dairy"
196         * <li>its value is a calculation computed by aggregating all of the
197         *     members which occur following it in the list</ul></p>
198         */
199        public static class VisualTotalMember extends RolapMemberBase {
200            private final Member member;
201            private Exp exp;
202    
203            VisualTotalMember(
204                Member member,
205                String name,
206                final Exp exp)
207            {
208                super(
209                    (RolapMember) member.getParentMember(),
210                    (RolapLevel) member.getLevel(),
211                    null, name, MemberType.FORMULA);
212                this.member = member;
213                this.exp = exp;
214            }
215    
216            @Override
217            public boolean equals(Object o) {
218                // A visual total member must compare equal to the member it wraps
219                // (for purposes of the MDX Intersect function, for instance).
220                return o instanceof VisualTotalMember
221                    && this.member.equals(((VisualTotalMember) o).member)
222                    && this.exp.equals(((VisualTotalMember) o).exp)
223                    || o instanceof Member
224                    && this.member.equals(o);
225            }
226    
227            @Override
228            public int hashCode() {
229                return member.hashCode();
230            }
231    
232            @Override
233            public String getCaption() {
234                return member.getCaption();
235            }
236    
237            protected boolean computeCalculated(final MemberType memberType) {
238                return true;
239            }
240    
241            public int getSolveOrder() {
242                // high solve order, so it is expanded after other calculations
243                return 99;
244            }
245    
246            public Exp getExpression() {
247                return exp;
248            }
249    
250            public void setExpression(Exp exp) {
251                this.exp = exp;
252            }
253    
254            public void setExpression(
255                Evaluator evaluator,
256                List<Member> childMembers)
257            {
258                final Exp exp = makeExpr(childMembers);
259                final Validator validator = evaluator.getQuery().createValidator();
260                final Exp validatedExp = exp.accept(validator);
261                setExpression(validatedExp);
262            }
263    
264            private Exp makeExpr(final List childMemberList) {
265                Exp[] memberExprs = new Exp[childMemberList.size()];
266                for (int i = 0; i < childMemberList.size(); i++) {
267                    final Member childMember = (Member) childMemberList.get(i);
268                    memberExprs[i] = new MemberExpr(childMember);
269                }
270                return new UnresolvedFunCall(
271                        "Aggregate",
272                        new Exp[] {
273                            new UnresolvedFunCall(
274                                    "{}",
275                                    Syntax.Braces,
276                                    memberExprs)
277                        });
278            }
279    
280            public int getOrdinal() {
281                return member.getOrdinal();
282            }
283    
284            public Member getDataMember() {
285                return member;
286            }
287    
288            public OlapElement lookupChild(SchemaReader schemaReader, String s) {
289                throw new UnsupportedOperationException();
290            }
291    
292            public OlapElement lookupChild(
293                SchemaReader schemaReader, String s, MatchType matchType)
294            {
295                throw new UnsupportedOperationException();
296            }
297    
298            public String getQualifiedName() {
299                throw new UnsupportedOperationException();
300            }
301    
302            public Member getMember() {
303                return member;
304            }
305    
306            public Object getPropertyValue(String propertyName, boolean matchCase) {
307                Property property = Property.lookup(propertyName, matchCase);
308                if (property == null) {
309                    return null;
310                }
311                switch (property.ordinal) {
312                case Property.CHILDREN_CARDINALITY_ORDINAL:
313                    return member.getPropertyValue(propertyName, matchCase);
314                default:
315                    return super.getPropertyValue(propertyName, matchCase);
316                }
317            }
318        }
319    
320        /**
321         * Substitutes a name into a pattern.<p/>
322         *
323         * Asterisks are replaced with the name,
324         * double-asterisks are replaced with a single asterisk.
325         * For example,
326         *
327         * <blockquote><code>substitute("** Subtotal - *",
328         * "Dairy")</code></blockquote>
329         *
330         * returns
331         *
332         * <blockquote><code>"* Subtotal - Dairy"</code></blockquote>
333         *
334         * @param namePattern Pattern
335         * @param name Name to substitute into pattern
336         * @return Substituted pattern
337         */
338        static String substitute(String namePattern, String name) {
339            final StringBuilder buf = new StringBuilder(256);
340            final int namePatternLen = namePattern.length();
341            int startIndex = 0;
342    
343            while (true) {
344                int endIndex = namePattern.indexOf('*', startIndex);
345    
346                if (endIndex == -1) {
347                    // No '*' left
348                    // append the rest of namePattern from startIndex onwards
349                    buf.append(namePattern.substring(startIndex));
350                    break;
351                }
352    
353                // endIndex now points to the '*'; check for '**'
354                ++endIndex;
355                if (endIndex < namePatternLen
356                    && namePattern.charAt(endIndex) == '*')
357                {
358                    // Found '**', replace with '*'
359                     // Include first '*'.
360                    buf.append(namePattern.substring(startIndex, endIndex));
361                    // Skip over 2nd '*'
362                    ++endIndex;
363                } else {
364                    // Found single '*' - substitute (omitting the '*')
365                    // Exclude '*'
366                    buf.append(namePattern.substring(startIndex, endIndex - 1));
367                    buf.append(name);
368                }
369    
370                startIndex = endIndex;
371            }
372    
373            return buf.toString();
374        }
375    
376    }
377    
378    // End VisualTotalsFunDef.java