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) 2006-2012 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.olap.fun;
011
012import mondrian.calc.*;
013import mondrian.calc.impl.AbstractListCalc;
014import mondrian.calc.impl.UnaryTupleList;
015import mondrian.mdx.*;
016import mondrian.olap.*;
017import mondrian.olap.type.*;
018import mondrian.resource.MondrianResource;
019import mondrian.rolap.*;
020
021import java.util.ArrayList;
022import java.util.List;
023
024/**
025 * Definition of the <code>VisualTotals</code> MDX function.
026 *
027 * @author jhyde
028 * @since Jan 16, 2006
029 */
030public 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 TupleList evaluateList(Evaluator evaluator) {
086            final List<Member> list =
087                listCalc.evaluateList(evaluator).slice(0);
088            final List<Member> resultList = new ArrayList<Member>(list);
089            final int memberCount = list.size();
090            for (int i = memberCount - 1; i >= 0; --i) {
091                Member member = list.get(i);
092                if (i + 1 < memberCount) {
093                    Member nextMember = resultList.get(i + 1);
094                    if (nextMember != member
095                        && nextMember.isChildOrEqualTo(member))
096                    {
097                        resultList.set(
098                            i,
099                            createMember(member, i, resultList, evaluator));
100                    }
101                }
102            }
103            return new UnaryTupleList(resultList);
104        }
105
106        private VisualTotalMember createMember(
107            Member member,
108            int i,
109            final List<Member> list,
110            Evaluator evaluator)
111        {
112            final String name;
113            final String caption;
114            if (stringCalc != null) {
115                final String namePattern = stringCalc.evaluateString(evaluator);
116                name = substitute(namePattern, member.getName());
117                caption = name;
118            } else {
119                name = member.getName();
120                caption = member.getCaption();
121            }
122            final List<Member> childMemberList =
123                followingDescendants(member, i + 1, list);
124            final Exp exp = makeExpr(childMemberList);
125            final Validator validator = evaluator.getQuery().createValidator();
126            final Exp validatedExp = exp.accept(validator);
127            return new VisualTotalMember(member, name, caption, validatedExp);
128        }
129
130        private List<Member> followingDescendants(
131            Member member, int i, final List<Member> list)
132        {
133            List<Member> childMemberList = new ArrayList<Member>();
134            while (i < list.size()) {
135                Member descendant = list.get(i);
136                if (descendant.equals(member)) {
137                    // strict descendants only
138                    break;
139                }
140                if (!descendant.isChildOrEqualTo(member)) {
141                    break;
142                }
143                if (descendant instanceof VisualTotalMember) {
144                    // Add the visual total member, but skip over its children.
145                    VisualTotalMember visualTotalMember =
146                            (VisualTotalMember) descendant;
147                    childMemberList.add(visualTotalMember);
148                    i = lastChildIndex(visualTotalMember.member, i, list);
149                    continue;
150                }
151                childMemberList.add(descendant);
152                ++i;
153            }
154            return childMemberList;
155        }
156
157        private int lastChildIndex(Member member, int start, List list) {
158            int i = start;
159            while (true) {
160                ++i;
161                if (i >= list.size()) {
162                    break;
163                }
164                Member descendant = (Member) list.get(i);
165                if (descendant.equals(member)) {
166                    // strict descendants only
167                    break;
168                }
169                if (!descendant.isChildOrEqualTo(member)) {
170                    break;
171                }
172            }
173            return i;
174        }
175
176        private Exp makeExpr(final List childMemberList) {
177            Exp[] memberExprs = new Exp[childMemberList.size()];
178            for (int i = 0; i < childMemberList.size(); i++) {
179                final Member childMember = (Member) childMemberList.get(i);
180                memberExprs[i] = new MemberExpr(childMember);
181            }
182            return new UnresolvedFunCall(
183                "Aggregate",
184                new Exp[] {
185                    new UnresolvedFunCall(
186                        "{}",
187                        Syntax.Braces,
188                        memberExprs)
189                });
190        }
191    }
192
193    /**
194     * Calculated member for <code>VisualTotals</code> function.
195     *
196     * <p>It corresponds to a real member, and most of its properties are
197     * similar. The main differences are:<ul>
198     * <li>its name is derived from the VisualTotals pattern, e.g.
199     *     "*Subtotal - Dairy" as opposed to "Dairy"
200     * <li>its value is a calculation computed by aggregating all of the
201     *     members which occur following it in the list</ul></p>
202     */
203    public static class VisualTotalMember extends RolapMemberBase {
204        private final Member member;
205        private Exp exp;
206        private String caption;
207
208        VisualTotalMember(
209            Member member,
210            String name,
211            String caption,
212            final Exp exp)
213        {
214            super(
215                (RolapMember) member.getParentMember(),
216                (RolapLevel) member.getLevel(),
217                RolapUtil.sqlNullValue, name, MemberType.FORMULA);
218            this.member = member;
219            this.caption = caption;
220            this.exp = exp;
221        }
222
223        @Override
224        public boolean equals(Object o) {
225            // A visual total member must compare equal to the member it wraps
226            // (for purposes of the MDX Intersect function, for instance).
227            return o instanceof VisualTotalMember
228                && this.member.equals(((VisualTotalMember) o).member)
229                && this.exp.equals(((VisualTotalMember) o).exp)
230                || o instanceof Member
231                && this.member.equals(o);
232        }
233
234        @Override
235        public int compareTo(Object o) {
236            if (o instanceof VisualTotalMember) {
237                // VisualTotals members are a special case. We have
238                // to compare the delegate member.
239                return this.getMember().compareTo(
240                    ((VisualTotalMember) o).getMember());
241            } else {
242                return super.compareTo(o);
243            }
244        }
245
246        @Override
247        public int hashCode() {
248            return member.hashCode();
249        }
250
251        @Override
252        public String getCaption() {
253            return caption;
254        }
255
256        protected boolean computeCalculated(final MemberType memberType) {
257            return true;
258        }
259
260        public int getSolveOrder() {
261            // high solve order, so it is expanded after other calculations
262            // REVIEW: 99...really?? I've seen many queries with higher SO.
263            // I don't think we should be abusing arbitrary constants
264            // like this.
265            return 99;
266        }
267
268        public Exp getExpression() {
269            return exp;
270        }
271
272        public void setExpression(Exp exp) {
273            this.exp = exp;
274        }
275
276        public void setExpression(
277            Evaluator evaluator,
278            List<Member> childMembers)
279        {
280            final Exp exp = makeExpr(childMembers);
281            final Validator validator = evaluator.getQuery().createValidator();
282            final Exp validatedExp = exp.accept(validator);
283            setExpression(validatedExp);
284        }
285
286        private Exp makeExpr(final List childMemberList) {
287            Exp[] memberExprs = new Exp[childMemberList.size()];
288            for (int i = 0; i < childMemberList.size(); i++) {
289                final Member childMember = (Member) childMemberList.get(i);
290                memberExprs[i] = new MemberExpr(childMember);
291            }
292            return new UnresolvedFunCall(
293                "Aggregate",
294                new Exp[] {
295                    new UnresolvedFunCall(
296                        "{}",
297                        Syntax.Braces,
298                        memberExprs)
299                });
300        }
301
302        public int getOrdinal() {
303            return member.getOrdinal();
304        }
305
306        public Member getDataMember() {
307            return member;
308        }
309
310        public String getQualifiedName() {
311            throw new UnsupportedOperationException();
312        }
313
314        public Member getMember() {
315            return member;
316        }
317
318        public Object getPropertyValue(String propertyName, boolean matchCase) {
319            Property property = Property.lookup(propertyName, matchCase);
320            if (property == null) {
321                return null;
322            }
323            switch (property.ordinal) {
324            case Property.CHILDREN_CARDINALITY_ORDINAL:
325                return member.getPropertyValue(propertyName, matchCase);
326            default:
327                return super.getPropertyValue(propertyName, matchCase);
328            }
329        }
330    }
331
332    /**
333     * Substitutes a name into a pattern.<p/>
334     *
335     * Asterisks are replaced with the name,
336     * double-asterisks are replaced with a single asterisk.
337     * For example,
338     *
339     * <blockquote><code>substitute("** Subtotal - *",
340     * "Dairy")</code></blockquote>
341     *
342     * returns
343     *
344     * <blockquote><code>"* Subtotal - Dairy"</code></blockquote>
345     *
346     * @param namePattern Pattern
347     * @param name Name to substitute into pattern
348     * @return Substituted pattern
349     */
350    static String substitute(String namePattern, String name) {
351        final StringBuilder buf = new StringBuilder(256);
352        final int namePatternLen = namePattern.length();
353        int startIndex = 0;
354
355        while (true) {
356            int endIndex = namePattern.indexOf('*', startIndex);
357
358            if (endIndex == -1) {
359                // No '*' left
360                // append the rest of namePattern from startIndex onwards
361                buf.append(namePattern.substring(startIndex));
362                break;
363            }
364
365            // endIndex now points to the '*'; check for '**'
366            ++endIndex;
367            if (endIndex < namePatternLen
368                && namePattern.charAt(endIndex) == '*')
369            {
370                // Found '**', replace with '*'
371                 // Include first '*'.
372                buf.append(namePattern.substring(startIndex, endIndex));
373                // Skip over 2nd '*'
374                ++endIndex;
375            } else {
376                // Found single '*' - substitute (omitting the '*')
377                // Exclude '*'
378                buf.append(namePattern.substring(startIndex, endIndex - 1));
379                buf.append(name);
380            }
381
382            startIndex = endIndex;
383        }
384
385        return buf.toString();
386    }
387
388}
389
390// End VisualTotalsFunDef.java