001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/fun/VisualTotalsFunDef.java#12 $
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) 2006-2007 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.RolapLevel;
018    import mondrian.rolap.RolapMember;
019    import mondrian.resource.MondrianResource;
020    
021    import java.util.ArrayList;
022    import java.util.List;
023    
024    /**
025     * Definition of the <code>VisualTotals</code> MDX function.
026     *
027     * @author jhyde
028     * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/VisualTotalsFunDef.java#12 $
029     * @since Jan 16, 2006
030     */
031    public class VisualTotalsFunDef extends FunDefBase {
032        static final Resolver Resolver = 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            final Exp validatedArg = super.validateArg(validator, args, i, category);
046            if (i == 0) {
047                // The function signature guarantees that we have a set of members
048                // or a set of tuples.
049                final SetType setType = (SetType) validatedArg.getType();
050                final Type elementType = setType.getElementType();
051                if (!(elementType instanceof MemberType)) {
052                    throw MondrianResource.instance().
053                            VisualTotalsAppliedToTuples.ex();
054                }
055            }
056            return validatedArg;
057        }
058    
059        public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
060            final ListCalc listCalc = compiler.compileList(call.getArg(0));
061            final StringCalc stringCalc = call.getArgCount() > 1 ?
062                    compiler.compileString(call.getArg(1)) :
063                    null;
064            return new CalcImpl(call, listCalc, stringCalc);
065        }
066    
067        /**
068         * Calc implementation of the <code>VisualTotals</code> function.
069         */
070        private static class CalcImpl extends AbstractListCalc {
071            private final ListCalc listCalc;
072            private final StringCalc stringCalc;
073    
074            public CalcImpl(
075                    ResolvedFunCall call, ListCalc listCalc, StringCalc stringCalc) {
076                super(call, new Calc[] {listCalc, stringCalc});
077                this.listCalc = listCalc;
078                this.stringCalc = stringCalc;
079            }
080    
081            public List evaluateList(Evaluator evaluator) {
082                final List<Member> list = listCalc.evaluateList(evaluator);
083                final List<Member> resultList = new ArrayList<Member>(list);
084                final int memberCount = list.size();
085                for (int i = memberCount - 1; i >= 0; --i) {
086                    Member member = list.get(i);
087                    if (i + 1 < memberCount) {
088                        Member nextMember = resultList.get(i + 1);
089                        if (nextMember != member &&
090                                nextMember.isChildOrEqualTo(member)) {
091                            resultList.set(
092                                i,
093                                createMember(member, i, resultList, evaluator));
094                        }
095                    }
096                }
097                return resultList;
098            }
099    
100            private VisualTotalMember createMember(
101                    Member member,
102                    int i,
103                    final List<Member> list,
104                    Evaluator evaluator)
105            {
106                final String name;
107                if (stringCalc != null) {
108                    final String namePattern = stringCalc.evaluateString(evaluator);
109                    name = substitute(namePattern, member.getName());
110                } else {
111                    name = member.getName();
112                }
113                final List<Member> childMemberList =
114                    followingDescendants(member, i + 1, list);
115                final Exp exp = makeExpr(childMemberList);
116                final Validator validator = evaluator.getQuery().createValidator();
117                final Exp validatedExp = exp.accept(validator);
118                return new VisualTotalMember(member, name, validatedExp);
119            }
120    
121            private List<Member> followingDescendants(
122                Member member, int i, final List<Member> list)
123            {
124                List<Member> childMemberList = new ArrayList<Member>();
125                while (i < list.size()) {
126                    Member descendant = list.get(i);
127                    if (descendant.equals(member)) {
128                        // strict descendants only
129                        break;
130                    }
131                    if (!descendant.isChildOrEqualTo(member)) {
132                        break;
133                    }
134                    if (descendant instanceof VisualTotalMember) {
135                        // Add the visual total member, but skip over its children.
136                        VisualTotalMember visualTotalMember =
137                                (VisualTotalMember) descendant;
138                        childMemberList.add(visualTotalMember);
139                        i = lastChildIndex(visualTotalMember.member, i, list);
140                        continue;
141                    }
142                    childMemberList.add(descendant);
143                    ++i;
144                }
145                return childMemberList;
146            }
147    
148            private int lastChildIndex(Member member, int start, List list) {
149                int i = start;
150                while (true) {
151                    ++i;
152                    if (i >= list.size()) {
153                        break;
154                    }
155                    Member descendant = (Member) list.get(i);
156                    if (descendant.equals(member)) {
157                        // strict descendants only
158                        break;
159                    }
160                    if (!descendant.isChildOrEqualTo(member)) {
161                        break;
162                    }
163                }
164                return i;
165            }
166    
167            private Exp makeExpr(final List childMemberList) {
168                Exp[] memberExprs = new Exp[childMemberList.size()];
169                for (int i = 0; i < childMemberList.size(); i++) {
170                    final Member childMember = (Member) childMemberList.get(i);
171                    memberExprs[i] = new MemberExpr(childMember);
172                }
173                return new UnresolvedFunCall(
174                        "Aggregate",
175                        new Exp[] {
176                            new UnresolvedFunCall(
177                                    "{}",
178                                    Syntax.Braces,
179                                    memberExprs)
180                        });
181            }
182        }
183    
184        /**
185         * Calculated member for <code>VisualTotals</code> function.
186         *
187         * It corresponds to a real member, and most of its properties are similar.
188         * The main differences are:<ul>
189         * <li>its name is derived from the VisualTotals pattern, e.g.
190         *     "*Subtotal - Dairy" as opposed to "Dairy"
191         * <li>its value is a calculation computed by aggregating all of the
192         *     members which occur following it in the list</ul></p>
193         */
194        private static class VisualTotalMember extends RolapMember {
195            private final Member member;
196            private final Exp exp;
197    
198            VisualTotalMember(
199                    Member member,
200                    String name,
201                    final Exp exp) {
202                super(
203                    (RolapMember) member.getParentMember(),
204                    (RolapLevel) member.getLevel(),
205                    null, name, MemberType.FORMULA);
206                this.member = member;
207                this.exp = exp;
208            }
209    
210            protected boolean computeCalculated(final MemberType memberType) {
211                return true;
212            }
213    
214            public int getSolveOrder() {
215                // high solve order, so it is expanded after other calculations
216                return 99;
217            }
218    
219            public Exp getExpression() {
220                return exp;
221            }
222    
223            public int getOrdinal() {
224                throw new UnsupportedOperationException();
225            }
226    
227            public Member getDataMember() {
228                return member;
229            }
230    
231            public OlapElement lookupChild(SchemaReader schemaReader, String s) {
232                throw new UnsupportedOperationException();
233            }
234    
235            public OlapElement lookupChild(
236                SchemaReader schemaReader, String s, MatchType matchType) {
237                throw new UnsupportedOperationException();
238            }
239    
240            public String getQualifiedName() {
241                throw new UnsupportedOperationException();
242            }
243        }
244    
245        /**
246         * Substitutes a name into a pattern.<p/>
247         *
248         * Asterisks are replaced with the name,
249         * double-asterisks are replaced with a single asterisk.
250         * For example,
251         * <blockquote><code>substitute("** Subtotal - *", "Dairy")</code></blockquote>
252         * returns
253         * <blockquote><code>"* Subtotal - Dairy"</code></blockquote>
254         *
255         * @param namePattern Pattern
256         * @param name Name to substitute into pattern
257         * @return Substituted pattern
258         */
259        static String substitute(String namePattern, String name) {
260            final StringBuilder buf = new StringBuilder(256);
261            final int namePatternLen = namePattern.length();
262            int startIndex = 0;
263    
264            while (true) {
265                int endIndex = namePattern.indexOf('*', startIndex);
266    
267                if (endIndex == -1) {
268                    // No '*' left
269                    // append the rest of namePattern from startIndex onwards
270                    buf.append(namePattern.substring(startIndex));
271                    break;
272                }
273    
274                // endIndex now points to the '*'; check for '**'
275                ++endIndex;
276                if (endIndex < namePatternLen
277                    && namePattern.charAt(endIndex) == '*')
278                {
279                    // Found '**', replace with '*'
280                     // Include first '*'.
281                    buf.append(namePattern.substring(startIndex, endIndex));
282                    // Skip over 2nd '*'
283                    ++endIndex;
284                } else {
285                    // Found single '*' - substitute (omitting the '*')
286                    // Exclude '*'
287                    buf.append(namePattern.substring(startIndex, endIndex - 1));
288                    buf.append(name);
289                }
290    
291                startIndex = endIndex;
292            }
293    
294            return buf.toString();
295        }
296    
297    }
298    
299    // End VisualTotalsFunDef.java