001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapDependencyTestingEvaluator.java#11 $ 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-2008 Julian Hyde 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.rolap; 011 012 import mondrian.olap.*; 013 import mondrian.calc.*; 014 import mondrian.calc.impl.DelegatingExpCompiler; 015 import mondrian.calc.impl.GenericCalc; 016 017 import java.util.*; 018 import java.io.StringWriter; 019 import java.io.PrintWriter; 020 021 /** 022 * Evaluator which checks dependencies of expressions. 023 * 024 * <p>For each expression evaluation, this valuator evaluates each 025 * expression more times, and makes sure that the results of the expression 026 * are independent of dimensions which the expression claims to be 027 * independent of. 028 * 029 * <p>Since it evaluates each expression twice, it also exposes function 030 * implementations which change the context of the evaluator. 031 * 032 * @author jhyde 033 * @since September, 2005 034 * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapDependencyTestingEvaluator.java#11 $ 035 */ 036 public class RolapDependencyTestingEvaluator extends RolapEvaluator { 037 038 /** 039 * Creates an dependency-testing evaluator. 040 * 041 * @param result Result we are building 042 * @param expDeps Number of dependencies to check 043 */ 044 RolapDependencyTestingEvaluator(RolapResult result, int expDeps) { 045 super(new DteRoot(result, expDeps)); 046 } 047 048 /** 049 * Creates a child evaluator. 050 */ 051 private RolapDependencyTestingEvaluator( 052 RolapEvaluatorRoot root, 053 RolapDependencyTestingEvaluator evaluator) 054 { 055 super(root, evaluator); 056 } 057 058 public Object evaluate( 059 Calc calc, 060 Dimension[] independentDimensions, 061 String mdxString) 062 { 063 final DteRoot dteRoot = 064 (DteRoot) root; 065 if (dteRoot.faking) { 066 ++dteRoot.fakeCallCount; 067 } else { 068 ++dteRoot.callCount; 069 } 070 // Evaluate the call for real. 071 final Object result = calc.evaluate(this); 072 if (dteRoot.result.isDirty()) { 073 return result; 074 } 075 076 // If the result is a list and says that it is mutable, see whether it 077 // really is. 078 if (calc.getResultStyle() == ResultStyle.MUTABLE_LIST) { 079 List<Object> list = (List) result; 080 if (list.size() > 0) { 081 final Object zeroth = list.get(0); 082 list.set(0, zeroth); 083 } 084 } 085 086 // Change one of the allegedly independent dimensions and evaluate 087 // again. 088 // 089 // Don't do it if the faking is disabled, 090 // or if we're already faking another dimension, 091 // or if we're filtering out nonempty cells (which makes us 092 // dependent on everything), 093 // or if the ratio of fake evals to real evals is too high (which 094 // would make us too slow). 095 if (dteRoot.disabled || 096 dteRoot.faking || 097 isNonEmpty() || 098 (double) dteRoot.fakeCallCount > 099 (double) dteRoot.callCount * dteRoot.random.nextDouble() * 100 2 * dteRoot.expDeps) { 101 return result; 102 } 103 if (independentDimensions.length == 0) { 104 return result; 105 } 106 dteRoot.faking = true; 107 ++dteRoot.fakeCount; 108 ++dteRoot.fakeCallCount; 109 final int i = dteRoot.random.nextInt(independentDimensions.length); 110 final Member saveMember = getContext(independentDimensions[i]); 111 final Member otherMember = 112 dteRoot.chooseOtherMember( 113 saveMember, getQuery().getSchemaReader(false)); 114 setContext(otherMember); 115 final Object otherResult = calc.evaluate(this); 116 if (false) { 117 System.out.println( 118 "original=" + saveMember.getUniqueName() + 119 ", member=" + otherMember.getUniqueName() + 120 ", originalResult=" + result + "" + 121 ", result=" + otherResult); 122 } 123 if (!equals(otherResult, result)) { 124 final Member[] members = getMembers(); 125 final StringBuilder buf = new StringBuilder(); 126 for (int j = 0; j < members.length; j++) { 127 if (j > 0) { 128 buf.append(", "); 129 } 130 buf.append(members[j].getUniqueName()); 131 } 132 throw Util.newInternal( 133 "Expression '" + mdxString + 134 "' claims to be independent of dimension " + 135 saveMember.getDimension() + " but is not; context is {" + 136 buf.toString() + "}; First result: " + 137 toString(result) + ", Second result: " + 138 toString(otherResult)); 139 } 140 // Restore context. 141 setContext(saveMember); 142 dteRoot.faking = false; 143 return result; 144 } 145 146 public RolapEvaluator _push() { 147 return new RolapDependencyTestingEvaluator(root, this); 148 } 149 150 private boolean equals(Object o1, Object o2) { 151 if (o1 == null) { 152 return o2 == null; 153 } 154 if (o2 == null) { 155 return false; 156 } 157 if (o1 instanceof Object[]) { 158 if (o2 instanceof Object[]) { 159 Object[] a1 = (Object[]) o1; 160 Object[] a2 = (Object[]) o2; 161 if (a1.length == a2.length) { 162 for (int i = 0; i < a1.length; i++) { 163 if (!equals(a1[i], a2[i])) { 164 return false; 165 } 166 } 167 return true; 168 } 169 } 170 return false; 171 } 172 if (o1 instanceof List) { 173 return o2 instanceof List && 174 equals( 175 ((List) o1).toArray(), 176 ((List) o2).toArray()); 177 } 178 return o1.equals(o2); 179 } 180 181 private String toString(Object o) { 182 StringWriter sw = new StringWriter(); 183 PrintWriter pw = new PrintWriter(sw); 184 toString(o, pw); 185 return sw.toString(); 186 } 187 188 private void toString(Object o, PrintWriter pw) { 189 if (o instanceof Object[]) { 190 Object[] a = (Object[]) o; 191 pw.print("{"); 192 for (int i = 0; i < a.length; i++) { 193 Object o1 = a[i]; 194 if (i > 0) { 195 pw.print(", "); 196 } 197 toString(o1, pw); 198 } 199 pw.print("}"); 200 } else if (o instanceof List) { 201 List list = (List) o; 202 toString(list.toArray(), pw); 203 } else if (o instanceof Member) { 204 Member member = (Member) o; 205 pw.print(member.getUniqueName()); 206 } else { 207 pw.print(o); 208 } 209 } 210 211 /** 212 * Holds context for a tree of {@link RolapDependencyTestingEvaluator}. 213 */ 214 static class DteRoot extends RolapResult.RolapResultEvaluatorRoot { 215 final int expDeps; 216 final RolapResult result; 217 int callCount; 218 int fakeCallCount; 219 int fakeCount; 220 boolean faking; 221 boolean disabled; 222 final Random random = Util.createRandom( 223 MondrianProperties.instance().TestSeed.get()); 224 225 DteRoot(RolapResult result, int expDeps) { 226 super(result); 227 this.expDeps = expDeps; 228 this.result = result; 229 } 230 231 /** 232 * Chooses another member of the same hierarchy. 233 * The member will come from all levels with the same probability. 234 * Calculated members are not included. 235 * 236 * @param save Previous member 237 * @param schemaReader Schema reader 238 * @return other member of same hierarchy 239 */ 240 private Member chooseOtherMember( 241 final Member save, SchemaReader schemaReader) { 242 final Hierarchy hierarchy = save.getHierarchy(); 243 int attempt = 0; 244 while (true) { 245 // Choose a random level. 246 final Level[] levels = hierarchy.getLevels(); 247 final int levelDepth = random.nextInt(levels.length) + 1; 248 Member member = null; 249 for (int i = 0; i < levelDepth; i++) { 250 List<Member> members; 251 if (i == 0) { 252 members = schemaReader.getLevelMembers(levels[i], false); 253 } else { 254 members = schemaReader.getMemberChildren(member); 255 } 256 if (members.size() == 0) { 257 break; 258 } 259 member = members.get(random.nextInt(members.size())); 260 } 261 // If the member chosen happens to be the same as the original 262 // member, try again. Give up after 100 attempts (in case the 263 // hierarchy has only one member). 264 if (member != save || ++attempt > 100) { 265 return member; 266 } 267 } 268 } 269 } 270 271 /** 272 * Expression which checks dependencies and list immutability. 273 */ 274 private static class DteCalcImpl extends GenericCalc { 275 private final Calc calc; 276 private final Dimension[] independentDimensions; 277 private final boolean mutableList; 278 private final String mdxString; 279 280 DteCalcImpl( 281 Calc calc, 282 Dimension[] independentDimensions, 283 boolean mutableList, 284 String mdxString) { 285 super(new DummyExp(calc.getType())); 286 this.calc = calc; 287 this.independentDimensions = independentDimensions; 288 this.mutableList = mutableList; 289 this.mdxString = mdxString; 290 } 291 292 public Calc[] getCalcs() { 293 return new Calc[] {calc}; 294 } 295 296 public Object evaluate(Evaluator evaluator) { 297 RolapDependencyTestingEvaluator dtEval = 298 (RolapDependencyTestingEvaluator) evaluator; 299 return dtEval.evaluate(calc, independentDimensions, mdxString); 300 } 301 302 public List evaluateList(Evaluator evaluator) { 303 List<?> list = super.evaluateList(evaluator); 304 if (!mutableList) { 305 list = Collections.unmodifiableList(list); 306 } 307 return list; 308 } 309 310 public ResultStyle getResultStyle() { 311 return calc.getResultStyle(); 312 } 313 } 314 315 /** 316 * Expression compiler which introduces dependency testing. 317 * 318 * <p>It also checks that the caller does not modify lists unless it has 319 * explicitly asked for a mutable list. 320 */ 321 static class DteCompiler extends DelegatingExpCompiler { 322 DteCompiler(ExpCompiler compiler) { 323 super(compiler); 324 } 325 326 protected Calc afterCompile(Exp exp, Calc calc, boolean mutable) { 327 Dimension[] dimensions = getIndependentDimensions(calc); 328 calc = super.afterCompile(exp, calc, mutable); 329 return new DteCalcImpl( 330 calc, 331 dimensions, 332 mutable, 333 Util.unparse(exp)); 334 } 335 336 /** 337 * Returns the dimensions an expression depends on. 338 */ 339 private Dimension[] getIndependentDimensions(Calc calc) { 340 List<Dimension> indDimList = new ArrayList<Dimension>(); 341 final Dimension[] dims = 342 getValidator().getQuery().getCube().getDimensions(); 343 for (Dimension dim : dims) { 344 if (!calc.dependsOn(dim)) { 345 indDimList.add(dim); 346 } 347 } 348 return indDimList.toArray(new Dimension[indDimList.size()]); 349 } 350 } 351 } 352 353 // End RolapDependencyTestingEvaluator.java