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-2011 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.calc.impl; 011 012import mondrian.calc.*; 013import mondrian.mdx.ResolvedFunCall; 014import mondrian.olap.*; 015import mondrian.olap.type.Type; 016import mondrian.rolap.RolapEvaluator; 017import mondrian.rolap.RolapHierarchy; 018 019import java.util.*; 020 021/** 022 * Abstract implementation of the {@link mondrian.calc.Calc} interface. 023 * 024 * @author jhyde 025 * @since Sep 27, 2005 026 */ 027public abstract class AbstractCalc implements Calc { 028 private final Calc[] calcs; 029 protected final Type type; 030 protected final Exp exp; 031 032 /** 033 * Creates an AbstractCalc. 034 * 035 * @param exp Source expression 036 * @param calcs Child compiled expressions 037 */ 038 protected AbstractCalc(Exp exp, Calc[] calcs) { 039 assert exp != null; 040 this.exp = exp; 041 this.calcs = calcs; 042 this.type = exp.getType(); 043 } 044 045 public Type getType() { 046 return type; 047 } 048 049 /** 050 * {@inheritDoc} 051 * 052 * Default implementation just does 'instanceof TargetClass'. Subtypes that 053 * are wrappers should override. 054 */ 055 public boolean isWrapperFor(Class<?> iface) { 056 return iface.isInstance(this); 057 } 058 059 /** 060 * {@inheritDoc} 061 * 062 * Default implementation just casts to TargetClass. 063 * Subtypes that are wrappers should override. 064 */ 065 public <T> T unwrap(Class<T> iface) { 066 return iface.cast(this); 067 } 068 069 public void accept(CalcWriter calcWriter) { 070 calcWriter.visitCalc(this, getName(), getArguments(), getCalcs()); 071 } 072 073 /** 074 * Returns the name of this expression type, used when serializing an 075 * expression to a string. 076 * 077 * <p>The default implementation tries to extract a name from a function 078 * call, if any, then prints the last part of the class name. 079 */ 080 protected String getName() { 081 String name = lastSegment(getClass()); 082 if (isDigits(name) 083 && exp instanceof ResolvedFunCall) 084 { 085 ResolvedFunCall funCall = (ResolvedFunCall) exp; 086 name = funCall.getFunDef().getName(); 087 } 088 return name; 089 } 090 091 /** 092 * Returns the last segment of a class name. 093 * 094 * <p>Examples: 095 * lastSegment("com.acme.Foo") = "Foo" 096 * lastSegment("com.acme.Foo$Bar") = "Bar" 097 * lastSegment("com.acme.Foo$1") = "1" 098 * 099 * @param clazz Class 100 * @return Last segment of class name 101 */ 102 private String lastSegment(Class clazz) { 103 final String name = clazz.getName(); 104 int dot = name.lastIndexOf('.'); 105 int dollar = name.lastIndexOf('$'); 106 int dotDollar = Math.max(dot, dollar); 107 if (dotDollar >= 0) { 108 return name.substring(dotDollar + 1); 109 } 110 return name; 111 } 112 113 private static boolean isDigits(String name) { 114 for (int i = 0; i < name.length(); i++) { 115 char c = name.charAt(i); 116 if ("0123456789".indexOf(c) < 0) { 117 return false; 118 } 119 } 120 return true; 121 } 122 123 /** 124 * Returns this expression's child expressions. 125 */ 126 public Calc[] getCalcs() { 127 return calcs; 128 } 129 130 public boolean dependsOn(Hierarchy hierarchy) { 131 return anyDepends(getCalcs(), hierarchy); 132 } 133 134 /** 135 * Returns true if one of the calcs depends on the given dimension. 136 */ 137 public static boolean anyDepends(Calc[] calcs, Hierarchy hierarchy) { 138 for (Calc calc : calcs) { 139 if (calc != null && calc.dependsOn(hierarchy)) { 140 return true; 141 } 142 } 143 return false; 144 } 145 146 /** 147 * Returns true if calc[0] depends on dimension, 148 * else false if calc[0] returns dimension, 149 * else true if any of the other calcs depend on dimension. 150 * 151 * <p>Typical application: <code>Aggregate({Set}, {Value Expression})</code> 152 * depends upon everything {Value Expression} depends upon, except the 153 * dimensions of {Set}. 154 */ 155 public static boolean anyDependsButFirst( 156 Calc[] calcs, Hierarchy hierarchy) 157 { 158 if (calcs.length == 0) { 159 return false; 160 } 161 if (calcs[0].dependsOn(hierarchy)) { 162 return true; 163 } 164 if (calcs[0].getType().usesHierarchy(hierarchy, true)) { 165 return false; 166 } 167 for (int i = 1; i < calcs.length; i++) { 168 Calc calc = calcs[i]; 169 if (calc != null && calc.dependsOn(hierarchy)) { 170 return true; 171 } 172 } 173 return false; 174 } 175 176 /** 177 * Returns true if any of the calcs depend on dimension, 178 * else false if any of the calcs return dimension, 179 * else true. 180 */ 181 public static boolean butDepends( 182 Calc[] calcs, Hierarchy hierarchy) 183 { 184 boolean result = true; 185 for (Calc calc : calcs) { 186 if (calc != null) { 187 if (calc.dependsOn(hierarchy)) { 188 return true; 189 } 190 if (calc.getType().usesHierarchy(hierarchy, true)) { 191 result = false; 192 } 193 } 194 } 195 return result; 196 } 197 198 /** 199 * Returns any other arguments to this calc. 200 * 201 * @return Collection of name/value pairs, represented as a map 202 */ 203 protected final Map<String, Object> getArguments() { 204 final Map<String, Object> argumentMap = 205 new LinkedHashMap<String, Object>(); 206 collectArguments(argumentMap); 207 return argumentMap; 208 } 209 210 /** 211 * Collects any other arguments to this calc. 212 * 213 * <p>The default implementation returns name, class, type, resultStyle. 214 * A subclass must call super, but may add other arguments. 215 * 216 * @param arguments Collection of name/value pairs, represented as a map 217 */ 218 protected void collectArguments(Map<String, Object> arguments) { 219 arguments.put("name", getName()); 220 arguments.put("class", getClass()); 221 arguments.put("type", getType()); 222 arguments.put("resultStyle", getResultStyle()); 223 } 224 225 /** 226 * Returns a simplified evalator whose context is the same for every 227 * dimension which an expression depends on, and the default member for 228 * every dimension which it does not depend on. 229 * 230 * <p>The default member is often the 'all' member, so this evaluator is 231 * usually the most efficient context in which to evaluate the expression. 232 * 233 * @param calc 234 * @param evaluator 235 */ 236 public static Evaluator simplifyEvaluator(Calc calc, Evaluator evaluator) { 237 if (evaluator.isNonEmpty()) { 238 // If NON EMPTY is present, we cannot simplify the context, because 239 // we have to assume that the expression depends on everything. 240 // TODO: Bug 1456418: Convert 'NON EMPTY Crossjoin' to 241 // 'NonEmptyCrossJoin'. 242 return evaluator; 243 } 244 int changeCount = 0; 245 Evaluator ev = evaluator; 246 final List<RolapHierarchy> hierarchies = 247 ((RolapEvaluator) evaluator).getCube().getHierarchies(); 248 for (RolapHierarchy hierarchy : hierarchies) { 249 final Member member = ev.getContext(hierarchy); 250 if (member.isAll()) { 251 continue; 252 } 253 if (calc.dependsOn(hierarchy)) { 254 continue; 255 } 256 final Member unconstrainedMember = 257 member.getHierarchy().getDefaultMember(); 258 if (member == unconstrainedMember) { 259 // This is a hierarchy without an 'all' member, and the context 260 // is already the default member. 261 continue; 262 } 263 if (changeCount++ == 0) { 264 ev = evaluator.push(); 265 } 266 ev.setContext(unconstrainedMember); 267 } 268 return ev; 269 } 270 271 public ResultStyle getResultStyle() { 272 return ResultStyle.VALUE; 273 } 274} 275 276// End AbstractCalc.java