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