001 /* 002 // $Id: //open/mondrian/src/main/mondrian/olap/Util.java#121 $ 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) 2001-2002 Kana Software, Inc. 007 // Copyright (C) 2001-2008 Julian Hyde and others 008 // All Rights Reserved. 009 // You must accept the terms of that agreement to use this software. 010 // 011 // jhyde, 6 August, 2001 012 */ 013 014 package mondrian.olap; 015 016 import org.apache.log4j.Logger; 017 import org.eigenbase.xom.XOMUtil; 018 019 import java.io.File; 020 import java.io.PrintWriter; 021 import java.io.BufferedReader; 022 import java.io.Reader; 023 import java.io.IOException; 024 import java.io.InputStreamReader; 025 import java.io.StringWriter; 026 import java.net.MalformedURLException; 027 import java.net.URL; 028 import java.util.*; 029 import java.util.regex.Matcher; 030 import java.util.regex.Pattern; 031 import java.math.BigDecimal; 032 import java.lang.reflect.Method; 033 034 import mondrian.olap.fun.*; 035 import mondrian.olap.type.Type; 036 import mondrian.resource.MondrianResource; 037 import mondrian.spi.UserDefinedFunction; 038 import mondrian.mdx.*; 039 import mondrian.util.*; 040 041 /** 042 * Utility functions used throughout mondrian. All methods are static. 043 * 044 * @author jhyde 045 * @since 6 August, 2001 046 * @version $Id: //open/mondrian/src/main/mondrian/olap/Util.java#121 $ 047 */ 048 public class Util extends XOMUtil { 049 050 public static final String nl = System.getProperty("line.separator"); 051 052 private static final Logger LOGGER = Logger.getLogger(Util.class); 053 054 /** 055 * Placeholder which indicates a value NULL. 056 */ 057 public static final Object nullValue = new Double(FunUtil.DoubleNull); 058 059 /** 060 * Placeholder which indicates an EMPTY value. 061 */ 062 public static final Object EmptyValue = new Double(FunUtil.DoubleEmpty); 063 064 /** 065 * Cumulative time spent accessing the database. 066 */ 067 private static long databaseMillis = 0; 068 069 /** 070 * Random number generator to provide seed for other random number 071 * generators. 072 */ 073 private static final Random metaRandom = 074 createRandom(MondrianProperties.instance().TestSeed.get()); 075 076 /** 077 * Whether we are running a version of Java before 1.5. 078 * <p/> 079 * <p>If this variable is true, we will be running retroweaver. Retroweaver 080 * has some problems involving {@link java.util.EnumSet}. 081 */ 082 public static final boolean PreJdk15 = 083 System.getProperty("java.version").startsWith("1.4"); 084 085 /** 086 * What version of JDBC? Returns 4 in JDK 1.6 and higher, 3 otherwise. 087 */ 088 public static final int JdbcVersion = 089 System.getProperty("java.version").compareTo("1.6") >= 0 090 ? 4 091 : 3; 092 093 /** 094 * Whether the code base has re-engineered using retroweaver. 095 * If this is the case, some functionality is not available. 096 */ 097 public static final boolean Retrowoven = 098 Access.class.getSuperclass().getName().equals( 099 "com.rc.retroweaver.runtime.Enum_"); 100 101 private static final UtilCompatible compatible; 102 103 static { 104 String className; 105 if (PreJdk15 || Retrowoven) { 106 className = "mondrian.util.UtilCompatibleJdk14"; 107 } else { 108 className = "mondrian.util.UtilCompatibleJdk15"; 109 } 110 try { 111 Class<UtilCompatible> clazz = 112 (Class<UtilCompatible>) Class.forName(className); 113 compatible = clazz.newInstance(); 114 } catch (ClassNotFoundException e) { 115 throw Util.newInternal(e, "Could not load '" + className + "'"); 116 } catch (InstantiationException e) { 117 throw Util.newInternal(e, "Could not load '" + className + "'"); 118 } catch (IllegalAccessException e) { 119 throw Util.newInternal(e, "Could not load '" + className + "'"); 120 } 121 } 122 123 public static boolean isNull(Object o) { 124 return o == null || o == nullValue; 125 } 126 127 /** 128 * Returns whether a list is strictly sorted. 129 * 130 * @param list List 131 * @return whether list is sorted 132 */ 133 public static <T> boolean isSorted(List<T> list) { 134 T prev = null; 135 for (T t : list) { 136 if (prev != null && 137 ((Comparable<T>) prev).compareTo(t) >= 0) { 138 return false; 139 } 140 prev = t; 141 } 142 return true; 143 } 144 145 /** 146 * Encodes string for MDX (escapes ] as ]] inside a name). 147 */ 148 public static String mdxEncodeString(String st) { 149 StringBuilder retString = new StringBuilder(st.length() + 20); 150 for (int i = 0; i < st.length(); i++) { 151 char c = st.charAt(i); 152 if ((c == ']') && 153 ((i + 1) < st.length()) && 154 (st.charAt(i + 1) != '.')) { 155 156 retString.append(']'); //escaping character 157 } 158 retString.append(c); 159 } 160 return retString.toString(); 161 } 162 163 /** 164 * Converts a string into a double-quoted string. 165 */ 166 public static String quoteForMdx(String val) { 167 StringBuilder buf = new StringBuilder(val.length() + 20); 168 buf.append("\""); 169 170 String s0 = replace(val, "\"", "\"\""); 171 buf.append(s0); 172 173 buf.append("\""); 174 return buf.toString(); 175 } 176 177 /** 178 * Return string quoted in [...]. For example, "San Francisco" becomes 179 * "[San Francisco]"; "a [bracketed] string" becomes 180 * "[a [bracketed]] string]". 181 */ 182 public static String quoteMdxIdentifier(String id) { 183 StringBuilder buf = new StringBuilder(id.length() + 20); 184 quoteMdxIdentifier(id, buf); 185 return buf.toString(); 186 } 187 188 public static void quoteMdxIdentifier(String id, StringBuilder buf) { 189 buf.append('['); 190 int start = buf.length(); 191 buf.append(id); 192 replace(buf, start, "]", "]]"); 193 buf.append(']'); 194 } 195 196 /** 197 * Return identifiers quoted in [...].[...]. For example, {"Store", "USA", 198 * "California"} becomes "[Store].[USA].[California]". 199 */ 200 public static String quoteMdxIdentifier(List<Id.Segment> ids) { 201 StringBuilder sb = new StringBuilder(64); 202 quoteMdxIdentifier(ids, sb); 203 return sb.toString(); 204 } 205 206 public static void quoteMdxIdentifier( 207 List<Id.Segment> ids, 208 StringBuilder sb) 209 { 210 for (int i = 0; i < ids.size(); i++) { 211 if (i > 0) { 212 sb.append('.'); 213 } 214 sb.append(ids.get(i).toString()); 215 } 216 } 217 218 /** 219 * Returns true if two objects are equal, or are both null. 220 */ 221 public static boolean equals(Object s, Object t) { 222 return (s == null) ? (t == null) : s.equals(t); 223 } 224 225 /** 226 * Returns true if two strings are equal, or are both null. 227 * 228 * <p>The result is not affected by 229 * {@link MondrianProperties#CaseSensitive the case sensitive option}; if 230 * you wish to compare names, use {@link #equalName(String, String)}. 231 */ 232 public static boolean equals(String s, String t) { 233 return equals((Object) s, (Object) t); 234 } 235 236 /** 237 * Returns whether two names are equal. 238 * Takes into account the 239 * {@link MondrianProperties#CaseSensitive case sensitive option}. 240 * Names may be null. 241 */ 242 public static boolean equalName(String s, String t) { 243 if (s == null) { 244 return t == null; 245 } 246 boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get(); 247 return caseSensitive ? s.equals(t) : s.equalsIgnoreCase(t); 248 } 249 250 /** 251 * Tests two strings for equality, optionally ignoring case. 252 * 253 * @param s First string 254 * @param t Second string 255 * @param matchCase Whether to perform case-sensitive match 256 * @return Whether strings are equal 257 */ 258 public static boolean equal(String s, String t, boolean matchCase) { 259 return matchCase ? s.equals(t) : s.equalsIgnoreCase(t); 260 } 261 262 /** 263 * Compares two names. if case sensitive flag is false, 264 * apply finer grain difference with case sensitive 265 * Takes into account the {@link MondrianProperties#CaseSensitive case 266 * sensitive option}. 267 * Names must not be null. 268 */ 269 public static int caseSensitiveCompareName(String s, String t) { 270 boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get(); 271 if (caseSensitive) { 272 return s.compareTo(t); 273 } else { 274 int v = s.compareToIgnoreCase(t); 275 // if ignore case returns 0 compare in a case sensitive manner 276 // this was introduced to solve an issue with Member.equals() 277 // and Member.compareTo() not agreeing with each other 278 return v == 0 ? s.compareTo(t) : v; 279 } 280 } 281 282 /** 283 * Compares two names. 284 * Takes into account the {@link MondrianProperties#CaseSensitive case 285 * sensitive option}. 286 * Names must not be null. 287 */ 288 public static int compareName(String s, String t) { 289 boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get(); 290 return caseSensitive ? s.compareTo(t) : s.compareToIgnoreCase(t); 291 } 292 293 /** 294 * Generates a normalized form of a name, for use as a key into a map. 295 * Returns the upper case name if 296 * {@link MondrianProperties#CaseSensitive} is true, the name unchanged 297 * otherwise. 298 */ 299 public static String normalizeName(String s) { 300 return MondrianProperties.instance().CaseSensitive.get() ? 301 s : 302 s.toUpperCase(); 303 } 304 305 /** 306 * Returns the result of ((Comparable) k1).compareTo(k2), with 307 * special-casing for the fact that Boolean only became 308 * comparable in JDK 1.5. 309 * 310 * @see Comparable#compareTo 311 */ 312 public static int compareKey(Object k1, Object k2) { 313 if (k1 instanceof Boolean) { 314 // Luckily, "F" comes before "T" in the alphabet. 315 k1 = k1.toString(); 316 k2 = k2.toString(); 317 } 318 return ((Comparable) k1).compareTo(k2); 319 } 320 321 /** 322 * Returns a string with every occurrence of a seek string replaced with 323 * another. 324 */ 325 public static String replace(String s, String find, String replace) { 326 // let's be optimistic 327 int found = s.indexOf(find); 328 if (found == -1) { 329 return s; 330 } 331 StringBuilder sb = new StringBuilder(s.length() + 20); 332 int start = 0; 333 char[] chars = s.toCharArray(); 334 final int step = find.length(); 335 if (step == 0) { 336 // Special case where find is "". 337 sb.append(s); 338 replace(sb, 0, find, replace); 339 } else { 340 for (;;) { 341 sb.append(chars, start, found - start); 342 if (found == s.length()) { 343 break; 344 } 345 sb.append(replace); 346 start = found + step; 347 found = s.indexOf(find, start); 348 if (found == -1) { 349 found = s.length(); 350 } 351 } 352 } 353 return sb.toString(); 354 } 355 356 /** 357 * Replaces all occurrences of a string in a buffer with another. 358 * 359 * @param buf String buffer to act on 360 * @param start Ordinal within <code>find</code> to start searching 361 * @param find String to find 362 * @param replace String to replace it with 363 * @return The string buffer 364 */ 365 public static StringBuilder replace( 366 StringBuilder buf, 367 int start, 368 String find, String replace) { 369 // Search and replace from the end towards the start, to avoid O(n ^ 2) 370 // copying if the string occurs very commonly. 371 int findLength = find.length(); 372 if (findLength == 0) { 373 // Special case where the seek string is empty. 374 for (int j = buf.length(); j >= 0; --j) { 375 buf.insert(j, replace); 376 } 377 return buf; 378 } 379 int k = buf.length(); 380 while (k > 0) { 381 int i = buf.lastIndexOf(find, k); 382 if (i < start) { 383 break; 384 } 385 buf.replace(i, i + find.length(), replace); 386 // Step back far enough to ensure that the beginning of the section 387 // we just replaced does not cause a match. 388 k = i - findLength; 389 } 390 return buf; 391 } 392 393 public static List<Id.Segment> parseIdentifier(String s) { 394 if (!s.startsWith("[")) { 395 return Collections.singletonList( 396 new Id.Segment(s, Id.Quoting.UNQUOTED)); 397 } 398 399 List<Id.Segment> list = new ArrayList<Id.Segment>(); 400 int i = 0; 401 Id.Quoting type; 402 while (i < s.length()) { 403 if (s.charAt(i) != '&' && s.charAt(i) != '[') { 404 throw MondrianResource.instance().MdxInvalidMember.ex(s); 405 } 406 407 if (s.charAt(i) == '&') { 408 i++; 409 type = Id.Quoting.KEY; 410 } else { 411 type = Id.Quoting.QUOTED; 412 } 413 414 if (s.charAt(i) != '[') { 415 throw MondrianResource.instance().MdxInvalidMember.ex(s); 416 } 417 418 int j = getEndIndex(s, i + 1); 419 if (j == -1) { 420 throw MondrianResource.instance().MdxInvalidMember.ex(s); 421 } 422 423 list.add( 424 new Id.Segment( 425 replace(s.substring(i + 1, j), "]]", "]"), 426 type)); 427 428 i = j + 2; 429 } 430 return list; 431 } 432 433 private static int getEndIndex(String s, int i) { 434 while (i < s.length()) { 435 char ch = s.charAt(i); 436 if (ch == ']') { 437 if (i + 1 < s.length() && s.charAt(i + 1) == ']') { // found ]] => skip 438 i += 2; 439 } else { 440 return i; 441 } 442 } else { 443 i++; 444 } 445 } 446 return -1; 447 } 448 449 /** 450 * Converts an array of name parts {"part1", "part2"} into a single string 451 * "[part1].[part2]". If the names contain "]" they are escaped as "]]". 452 */ 453 public static String implode(List<Id.Segment> names) { 454 StringBuilder sb = new StringBuilder(64); 455 for (int i = 0; i < names.size(); i++) { 456 if (i > 0) { 457 sb.append("."); 458 } 459 // FIXME: should be: 460 // names.get(i).toString(sb); 461 // but that causes some tests to fail 462 quoteMdxIdentifier(names.get(i).name, sb); 463 } 464 return sb.toString(); 465 } 466 467 public static String makeFqName(String name) { 468 return quoteMdxIdentifier(name); 469 } 470 471 public static String makeFqName(OlapElement parent, String name) { 472 if (parent == null) { 473 return Util.quoteMdxIdentifier(name); 474 } else { 475 StringBuilder buf = new StringBuilder(64); 476 buf.append(parent.getUniqueName()); 477 buf.append('.'); 478 Util.quoteMdxIdentifier(name, buf); 479 return buf.toString(); 480 } 481 } 482 483 public static String makeFqName(String parentUniqueName, String name) { 484 if (parentUniqueName == null) { 485 return quoteMdxIdentifier(name); 486 } else { 487 StringBuilder buf = new StringBuilder(64); 488 buf.append(parentUniqueName); 489 buf.append('.'); 490 Util.quoteMdxIdentifier(name, buf); 491 return buf.toString(); 492 } 493 } 494 495 public static OlapElement lookupCompound( 496 SchemaReader schemaReader, 497 OlapElement parent, 498 List<Id.Segment> names, 499 boolean failIfNotFound, 500 int category) 501 { 502 return lookupCompound( 503 schemaReader, parent, names, failIfNotFound, category, 504 MatchType.EXACT); 505 } 506 507 /** 508 * Resolves a name such as 509 * '[Products].[Product Department].[Produce]' by resolving the 510 * components ('Products', and so forth) one at a time. 511 * 512 * @param schemaReader Schema reader, supplies access-control context 513 * @param parent Parent element to search in 514 * @param names Exploded compound name, such as {"Products", 515 * "Product Department", "Produce"} 516 * @param failIfNotFound If the element is not found, determines whether 517 * to return null or throw an error 518 * @param category Type of returned element, a {@link Category} value; 519 * {@link Category#Unknown} if it doesn't matter. 520 * 521 * @pre parent != null 522 * @post !(failIfNotFound && return == null) 523 * 524 * @see #parseIdentifier(String) 525 */ 526 public static OlapElement lookupCompound( 527 SchemaReader schemaReader, 528 OlapElement parent, 529 List<Id.Segment> names, 530 boolean failIfNotFound, 531 int category, 532 MatchType matchType) 533 { 534 Util.assertPrecondition(parent != null, "parent != null"); 535 536 if (LOGGER.isDebugEnabled()) { 537 StringBuilder buf = new StringBuilder(64); 538 buf.append("Util.lookupCompound: "); 539 buf.append("parent.name="); 540 buf.append(parent.getName()); 541 buf.append(", category="); 542 buf.append(Category.instance.getName(category)); 543 buf.append(", names="); 544 quoteMdxIdentifier(names, buf); 545 LOGGER.debug(buf.toString()); 546 } 547 548 // First look up a member from the cache of calculated members 549 // (cubes and queries both have them). 550 switch (category) { 551 case Category.Member: 552 case Category.Unknown: 553 Member member = schemaReader.getCalculatedMember(names); 554 if (member != null) { 555 return member; 556 } 557 } 558 // Likewise named set. 559 switch (category) { 560 case Category.Set: 561 case Category.Unknown: 562 NamedSet namedSet = schemaReader.getNamedSet(names); 563 if (namedSet != null) { 564 return namedSet; 565 } 566 } 567 568 // Now resolve the name one part at a time. 569 for (int i = 0; i < names.size(); i++) { 570 Id.Segment name = names.get(i); 571 OlapElement child = 572 schemaReader.getElementChild(parent, name, matchType); 573 // if we're doing a non-exact match, and we find a non-exact 574 // match, then for an after match, return the first child 575 // of each subsequent level; for a before match, return the 576 // last child 577 if (child != null && matchType != MatchType.EXACT && 578 !Util.equalName(child.getName(), name.name)) 579 { 580 Util.assertPrecondition(child instanceof Member); 581 Member bestChild = (Member) child; 582 for (int j = i + 1; j < names.size(); j++) { 583 List<Member> childrenList = 584 schemaReader.getMemberChildren(bestChild); 585 FunUtil.hierarchize(childrenList, false); 586 if (matchType == MatchType.AFTER) { 587 bestChild = childrenList.get(0); 588 } else { 589 bestChild = 590 childrenList.get(childrenList.size() - 1); 591 } 592 if (bestChild == null) { 593 child = null; 594 break; 595 } 596 } 597 parent = bestChild; 598 break; 599 } 600 if (child == null) { 601 if (LOGGER.isDebugEnabled()) { 602 StringBuilder buf = new StringBuilder(64); 603 buf.append("Util.lookupCompound: "); 604 buf.append("parent.name="); 605 buf.append(parent.getName()); 606 buf.append(" has no child with name="); 607 buf.append(name); 608 LOGGER.debug(buf.toString()); 609 } 610 611 if (!failIfNotFound) { 612 return null; 613 } else if (category == Category.Member) { 614 throw MondrianResource.instance().MemberNotFound.ex( 615 quoteMdxIdentifier(names)); 616 } else { 617 throw MondrianResource.instance().MdxChildObjectNotFound 618 .ex(name.name, parent.getQualifiedName()); 619 } 620 } 621 parent = child; 622 } 623 if (LOGGER.isDebugEnabled()) { 624 StringBuilder buf = new StringBuilder(64); 625 buf.append("Util.lookupCompound: "); 626 buf.append("found child.name="); 627 buf.append(parent.getName()); 628 buf.append(", child.class="); 629 buf.append(parent.getClass().getName()); 630 LOGGER.debug(buf.toString()); 631 } 632 633 switch (category) { 634 case Category.Dimension: 635 if (parent instanceof Dimension) { 636 return parent; 637 } else if (parent instanceof Hierarchy) { 638 return parent.getDimension(); 639 } else if (failIfNotFound) { 640 throw Util.newError("Can not find dimension '" + implode(names) + "'"); 641 } else { 642 return null; 643 } 644 case Category.Hierarchy: 645 if (parent instanceof Hierarchy) { 646 return parent; 647 } else if (parent instanceof Dimension) { 648 return parent.getHierarchy(); 649 } else if (failIfNotFound) { 650 throw Util.newError("Can not find hierarchy '" + implode(names) + "'"); 651 } else { 652 return null; 653 } 654 case Category.Level: 655 if (parent instanceof Level) { 656 return parent; 657 } else if (failIfNotFound) { 658 throw Util.newError("Can not find level '" + implode(names) + "'"); 659 } else { 660 return null; 661 } 662 case Category.Member: 663 if (parent instanceof Member) { 664 return parent; 665 } else if (failIfNotFound) { 666 throw MondrianResource.instance().MdxCantFindMember.ex(implode(names)); 667 } else { 668 return null; 669 } 670 case Category.Unknown: 671 assertPostcondition(parent != null, "return != null"); 672 return parent; 673 default: 674 throw newInternal("Bad switch " + category); 675 } 676 } 677 678 public static OlapElement lookup(Query q, List<Id.Segment> nameParts) { 679 final Exp exp = lookup(q, nameParts, false); 680 if (exp instanceof MemberExpr) { 681 MemberExpr memberExpr = (MemberExpr) exp; 682 return memberExpr.getMember(); 683 } else if (exp instanceof LevelExpr) { 684 LevelExpr levelExpr = (LevelExpr) exp; 685 return levelExpr.getLevel(); 686 } else if (exp instanceof HierarchyExpr) { 687 HierarchyExpr hierarchyExpr = (HierarchyExpr) exp; 688 return hierarchyExpr.getHierarchy(); 689 } else if (exp instanceof DimensionExpr) { 690 DimensionExpr dimensionExpr = (DimensionExpr) exp; 691 return dimensionExpr.getDimension(); 692 } else { 693 throw Util.newInternal("Not an olap element: " + exp); 694 } 695 } 696 697 /** 698 * Converts an identifier into an expression by resolving its parts into 699 * an OLAP object (dimension, hierarchy, level or member) within the 700 * context of a query. 701 * 702 * <p>If <code>allowProp</code> is true, also allows property references 703 * from valid members, for example 704 * <code>[Measures].[Unit Sales].FORMATTED_VALUE</code>. 705 * In this case, the result will be a {@link ResolvedFunCall}. 706 * 707 * @param q Query expression belongs to 708 * @param nameParts Parts of the identifier 709 * @param allowProp Whether to allow property references 710 * @return OLAP object or property reference 711 */ 712 public static Exp lookup( 713 Query q, 714 List<Id.Segment> nameParts, 715 boolean allowProp) 716 { 717 // First, look for a calculated member defined in the query. 718 final String fullName = quoteMdxIdentifier(nameParts); 719 // Look for any kind of object (member, level, hierarchy, 720 // dimension) in the cube. Use a schema reader without restrictions. 721 final SchemaReader schemaReader = q.getSchemaReader(false); 722 OlapElement olapElement = 723 schemaReader.lookupCompound( 724 q.getCube(), nameParts, false, Category.Unknown); 725 if (olapElement != null) { 726 final SchemaReader accessConSchemaReader = q.getSchemaReader(true); 727 Role role = accessConSchemaReader.getRole(); 728 if (!role.canAccess(olapElement)) { 729 olapElement = null; 730 } 731 if (olapElement instanceof Member) { 732 olapElement = 733 accessConSchemaReader.substitute((Member) olapElement); 734 } 735 } 736 if (olapElement == null) { 737 if (allowProp && 738 nameParts.size() > 1) { 739 List<Id.Segment> namePartsButOne = 740 nameParts.subList(0, nameParts.size() - 1); 741 final String propertyName = 742 nameParts.get(nameParts.size() - 1).name; 743 olapElement = 744 schemaReader.lookupCompound( 745 q.getCube(), namePartsButOne, false, Category.Member); 746 if (olapElement != null && 747 isValidProperty((Member) olapElement, propertyName)) { 748 return new UnresolvedFunCall( 749 propertyName, Syntax.Property, new Exp[] { 750 createExpr(olapElement)}); 751 } 752 } 753 // if we're in the middle of loading the schema, the property has 754 // been set to ignore invalid members, and the member is 755 // non-existent, return the null member corresponding to the 756 // hierarchy of the element we're looking for; locate the 757 // hierarchy by incrementally truncating the name of the element 758 if (q.ignoreInvalidMembers()) { 759 int nameLen = nameParts.size() - 1; 760 olapElement = null; 761 while (nameLen > 0 && olapElement == null) { 762 List<Id.Segment> partialName = nameParts.subList(0, nameLen); 763 olapElement = schemaReader.lookupCompound( 764 q.getCube(), partialName, false, Category.Unknown); 765 nameLen--; 766 } 767 if (olapElement != null) { 768 olapElement = olapElement.getHierarchy().getNullMember(); 769 } else { 770 throw MondrianResource.instance().MdxChildObjectNotFound.ex( 771 fullName, q.getCube().getQualifiedName()); 772 } 773 } else { 774 throw MondrianResource.instance().MdxChildObjectNotFound.ex( 775 fullName, q.getCube().getQualifiedName()); 776 } 777 } 778 // keep track of any measure members referenced; these will be used 779 // later to determine if cross joins on virtual cubes can be 780 // processed natively 781 q.addMeasuresMembers(olapElement); 782 return createExpr(olapElement); 783 } 784 785 /** 786 * Looks up a cube in a schema reader. 787 * 788 * @param cubeName Cube name 789 * @param fail Whether to fail if not found. 790 * @return Cube, or null if not found 791 */ 792 static Cube lookupCube(SchemaReader schemaReader, String cubeName, boolean fail) { 793 for (Cube cube : schemaReader.getCubes()) { 794 if (Util.compareName(cube.getName(), cubeName) == 0) { 795 return cube; 796 } 797 } 798 if (fail) { 799 throw MondrianResource.instance().MdxCubeNotFound.ex(cubeName); 800 } 801 return null; 802 } 803 804 /** 805 * Converts an olap element (dimension, hierarchy, level or member) into 806 * an expression representing a usage of that element in an MDX statement. 807 */ 808 public static Exp createExpr(OlapElement element) 809 { 810 if (element instanceof Member) { 811 Member member = (Member) element; 812 return new MemberExpr(member); 813 } else if (element instanceof Level) { 814 Level level = (Level) element; 815 return new LevelExpr(level); 816 } else if (element instanceof Hierarchy) { 817 Hierarchy hierarchy = (Hierarchy) element; 818 return new HierarchyExpr(hierarchy); 819 } else if (element instanceof Dimension) { 820 Dimension dimension = (Dimension) element; 821 return new DimensionExpr(dimension); 822 } else if (element instanceof NamedSet) { 823 NamedSet namedSet = (NamedSet) element; 824 return new NamedSetExpr(namedSet); 825 } else { 826 throw Util.newInternal("Unexpected element type: " + element); 827 } 828 } 829 830 public static Member lookupHierarchyRootMember( 831 SchemaReader reader, Hierarchy hierarchy, Id.Segment memberName) 832 { 833 return lookupHierarchyRootMember( 834 reader, hierarchy, memberName, MatchType.EXACT); 835 } 836 837 /** 838 * Finds a root member of a hierarchy with a given name. 839 * 840 * @param hierarchy 841 * @param memberName 842 * @return Member, or null if not found 843 */ 844 public static Member lookupHierarchyRootMember( 845 SchemaReader reader, 846 Hierarchy hierarchy, 847 Id.Segment memberName, 848 MatchType matchType) 849 { 850 // Lookup member at first level. 851 // 852 // Don't use access control. Suppose we cannot see the 'nation' level, 853 // we still want to be able to resolve '[Customer].[USA].[CA]'. 854 List<Member> rootMembers = reader.getHierarchyRootMembers(hierarchy); 855 856 // if doing an inexact search on a non-all hieararchy, create 857 // a member corresponding to the name we're searching for so 858 // we can use it in a hierarchical search 859 Member searchMember = null; 860 if (matchType != MatchType.EXACT && !hierarchy.hasAll() && 861 ! rootMembers.isEmpty()) 862 { 863 searchMember = 864 hierarchy.createMember( 865 null, 866 rootMembers.get(0).getLevel(), 867 memberName.name, 868 null); 869 } 870 871 int bestMatch = -1; 872 int k = -1; 873 for (Member rootMember : rootMembers) { 874 ++k; 875 int rc; 876 // when searching on the ALL hierarchy, match must be exact 877 if (matchType == MatchType.EXACT || hierarchy.hasAll()) { 878 rc = rootMember.getName() 879 .compareToIgnoreCase(memberName.name); 880 } else { 881 rc = FunUtil.compareSiblingMembers( 882 rootMember, 883 searchMember); 884 } 885 if (rc == 0) { 886 return rootMember; 887 } 888 if (!hierarchy.hasAll()) { 889 if (matchType == MatchType.BEFORE) { 890 if (rc < 0 && 891 (bestMatch == -1 || 892 FunUtil.compareSiblingMembers( 893 rootMember, 894 rootMembers.get(bestMatch)) > 0)) 895 { 896 bestMatch = k; 897 } 898 } else if (matchType == MatchType.AFTER) { 899 if (rc > 0 && 900 (bestMatch == -1 || 901 FunUtil.compareSiblingMembers( 902 rootMember, 903 rootMembers.get(bestMatch)) < 0)) 904 { 905 bestMatch = k; 906 } 907 } 908 } 909 } 910 if (matchType != MatchType.EXACT && bestMatch != -1) { 911 return rootMembers.get(bestMatch); 912 } 913 // If the first level is 'all', lookup member at second level. For 914 // example, they could say '[USA]' instead of '[(All 915 // Customers)].[USA]'. 916 return (rootMembers.size() == 1 && rootMembers.get(0).isAll()) 917 ? reader.lookupMemberChildByName( 918 rootMembers.get(0), 919 memberName, 920 matchType) 921 : null; 922 } 923 924 /** 925 * Finds a named level in this hierarchy. Returns null if there is no 926 * such level. 927 */ 928 public static Level lookupHierarchyLevel(Hierarchy hierarchy, String s) { 929 final Level[] levels = hierarchy.getLevels(); 930 for (Level level : levels) { 931 if (level.getName().equalsIgnoreCase(s)) { 932 return level; 933 } 934 } 935 return null; 936 } 937 938 939 940 /** 941 * Finds the zero based ordinal of a Member among its siblings. 942 */ 943 public static int getMemberOrdinalInParent( 944 SchemaReader reader, 945 Member member) 946 { 947 Member parent = member.getParentMember(); 948 List<Member> siblings = 949 (parent == null) 950 ? reader.getHierarchyRootMembers(member.getHierarchy()) 951 : reader.getMemberChildren(parent); 952 953 for (int i = 0; i < siblings.size(); i++) { 954 if (siblings.get(i).equals(member)) { 955 return i; 956 } 957 } 958 throw Util.newInternal( 959 "could not find member " + member + " amongst its siblings"); 960 } 961 962 /** 963 * returns the first descendant on the level underneath parent. 964 * If parent = [Time].[1997] and level = [Time].[Month], then 965 * the member [Time].[1997].[Q1].[1] will be returned 966 */ 967 public static Member getFirstDescendantOnLevel( 968 SchemaReader reader, 969 Member parent, 970 Level level) 971 { 972 Member m = parent; 973 while (m.getLevel() != level) { 974 List<Member> children = reader.getMemberChildren(m); 975 m = children.get(0); 976 } 977 return m; 978 } 979 980 /** 981 * Returns whether a string is null or empty. 982 */ 983 public static boolean isEmpty(String s) { 984 return (s == null) || (s.length() == 0); 985 } 986 987 /** 988 * Encloses a value in single-quotes, to make a SQL string value. Examples: 989 * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>; 990 * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>. 991 */ 992 public static String singleQuoteString(String val) { 993 StringBuilder buf = new StringBuilder(64); 994 singleQuoteString(val, buf); 995 return buf.toString(); 996 } 997 998 /** 999 * Encloses a value in single-quotes, to make a SQL string value. Examples: 1000 * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>; 1001 * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>. 1002 */ 1003 public static void singleQuoteString(String val, StringBuilder buf) { 1004 buf.append('\''); 1005 1006 String s0 = replace(val, "'", "''"); 1007 buf.append(s0); 1008 1009 buf.append('\''); 1010 } 1011 1012 /** 1013 * Creates a random number generator. 1014 * 1015 * @param seed Seed for random number generator. 1016 * If 0, generate a seed from the system clock and print the value 1017 * chosen. (This is effectively non-deterministic.) 1018 * If -1, generate a seed from an internal random number generator. 1019 * (This is deterministic, but ensures that different tests have 1020 * different seeds.) 1021 * 1022 * @return A random number generator. 1023 */ 1024 public static Random createRandom(long seed) { 1025 if (seed == 0) { 1026 seed = new Random().nextLong(); 1027 System.out.println("random: seed=" + seed); 1028 } else if (seed == -1 && metaRandom != null) { 1029 seed = metaRandom.nextLong(); 1030 } 1031 return new Random(seed); 1032 } 1033 1034 /** 1035 * Returns whether a property is valid for a given member. 1036 * It is valid if the property is defined at the member's level or at 1037 * an ancestor level, or if the property is a standard property such as 1038 * "FORMATTED_VALUE". 1039 * 1040 * @param member Member 1041 * @param propertyName Property name 1042 * @return Whether property is valid 1043 */ 1044 public static boolean isValidProperty( 1045 Member member, String propertyName) { 1046 return lookupProperty(member.getLevel(), propertyName) != null; 1047 } 1048 1049 /** 1050 * Finds a member property called <code>propertyName</code> at, or above, 1051 * <code>level</code>. 1052 */ 1053 protected static Property lookupProperty(Level level, String propertyName) { 1054 do { 1055 Property[] properties = level.getProperties(); 1056 for (Property property : properties) { 1057 if (property.getName().equals(propertyName)) { 1058 return property; 1059 } 1060 } 1061 level = level.getParentLevel(); 1062 } while (level != null); 1063 // Now try a standard property. 1064 boolean caseSensitive = 1065 MondrianProperties.instance().CaseSensitive.get(); 1066 final Property property = Property.lookup(propertyName, caseSensitive); 1067 if (property != null && 1068 property.isMemberProperty() && 1069 property.isStandard()) { 1070 return property; 1071 } 1072 return null; 1073 } 1074 1075 /** 1076 * Insert a call to this method if you want to flag a piece of 1077 * undesirable code. 1078 * 1079 * @deprecated 1080 */ 1081 public static void deprecated(String reason) { 1082 throw new UnsupportedOperationException(reason); 1083 } 1084 1085 public static List<Member> addLevelCalculatedMembers( 1086 SchemaReader reader, 1087 Level level, 1088 List<Member> members) 1089 { 1090 List<Member> calcMembers = 1091 reader.getCalculatedMembers(level.getHierarchy()); 1092 List<Member> calcMembersInThisLevel = new ArrayList<Member>(); 1093 for (Member calcMember : calcMembers) { 1094 if (calcMember.getLevel().equals(level)) { 1095 calcMembersInThisLevel.add(calcMember); 1096 } 1097 } 1098 if (!calcMembersInThisLevel.isEmpty()) { 1099 List<Member> newMemberList = 1100 new ConcatenableList<Member>(); 1101 newMemberList.addAll(members); 1102 newMemberList.addAll(calcMembersInThisLevel); 1103 return newMemberList; 1104 } 1105 return members; 1106 } 1107 1108 /** 1109 * Returns an exception which indicates that a particular piece of 1110 * functionality should work, but a developer has not implemented it yet. 1111 */ 1112 public static RuntimeException needToImplement(Object o) { 1113 throw new UnsupportedOperationException("need to implement " + o); 1114 } 1115 1116 /** 1117 * Returns an exception indicating that we didn't expect to find this value 1118 * here. 1119 */ 1120 public static <T extends Enum<T>> RuntimeException badValue( 1121 Enum<T> anEnum) 1122 { 1123 return Util.newInternal("Was not expecting value '" + anEnum + 1124 "' for enumeration '" + anEnum.getDeclaringClass().getName() + 1125 "' in this context"); 1126 } 1127 1128 /** 1129 * Masks Mondrian's version number from a string. 1130 * 1131 * @param str String 1132 * @return String with each occurrence of mondrian's version number 1133 * (e.g. "2.3.0.0") replaced with "${mondrianVersion}" 1134 */ 1135 public static String maskVersion(String str) { 1136 MondrianServer.MondrianVersion mondrianVersion = 1137 MondrianServer.forConnection(null).getVersion(); 1138 String versionString = mondrianVersion.getVersionString(); 1139 return replace(str, versionString, "${mondrianVersion}"); 1140 } 1141 1142 /** 1143 * Converts a list of SQL-style patterns into a Java regular expression. 1144 * 1145 * <p>For example, {"Foo_", "Bar%BAZ"} becomes "Foo.|Bar.*BAZ". 1146 * 1147 * @param wildcards List of SQL-style wildcard expressions 1148 * @return Regular expression 1149 */ 1150 public static String wildcardToRegexp(List<String> wildcards) { 1151 StringBuilder buf = new StringBuilder(); 1152 for (String value : wildcards) { 1153 if (buf.length() > 0) { 1154 buf.append('|'); 1155 } 1156 int i = 0; 1157 while (true) { 1158 int percent = value.indexOf('%', i); 1159 int underscore = value.indexOf('_', i); 1160 if (percent == -1 && underscore == -1) { 1161 if (i < value.length()) { 1162 buf.append(quotePattern(value.substring(i))); 1163 } 1164 break; 1165 } 1166 if (underscore >= 0 && (underscore < percent || percent < 0)) { 1167 if (i < underscore) { 1168 buf.append( 1169 quotePattern(value.substring(i, underscore))); 1170 } 1171 buf.append('.'); 1172 i = underscore + 1; 1173 } else if (percent >= 0 && (percent < underscore || underscore < 0)) { 1174 if (i < percent) { 1175 buf.append( 1176 quotePattern(value.substring(i, percent))); 1177 } 1178 buf.append(".*"); 1179 i = percent + 1; 1180 } else { 1181 throw new IllegalArgumentException(); 1182 } 1183 } 1184 } 1185 return buf.toString(); 1186 } 1187 1188 /** 1189 * Converts a camel-case name to an upper-case name with underscores. 1190 * 1191 * <p>For example, <code>camelToUpper("FooBar")</code> returns "FOO_BAR". 1192 * 1193 * @param s Camel-case string 1194 * @return Upper-case string 1195 */ 1196 public static String camelToUpper(String s) { 1197 StringBuilder buf = new StringBuilder(s.length() + 10); 1198 int prevUpper = -1; 1199 for (int i = 0; i < s.length(); ++i) { 1200 char c = s.charAt(i); 1201 if (Character.isUpperCase(c)) { 1202 if (i > prevUpper + 1) { 1203 buf.append('_'); 1204 } 1205 prevUpper = i; 1206 } else { 1207 c = Character.toUpperCase(c); 1208 } 1209 buf.append(c); 1210 } 1211 return buf.toString(); 1212 } 1213 1214 /** 1215 * Parses a comma-separated list. 1216 * 1217 * <p>If a value contains a comma, escape it with a second comma. For 1218 * example, <code>parseCommaList("x,y,,z")</code> returns 1219 * <code>{"x", "y,z"}</code>. 1220 * 1221 * @param nameCommaList List of names separated by commas 1222 * @return List of names 1223 */ 1224 public static List<String> parseCommaList(String nameCommaList) { 1225 if (nameCommaList.equals("")) { 1226 return Collections.emptyList(); 1227 } 1228 if (nameCommaList.endsWith(",")) { 1229 // Special treatment for list ending in ",", because split ignores 1230 // entries after separator. 1231 final String zzz = "zzz"; 1232 final List<String> list = parseCommaList(nameCommaList + zzz); 1233 String last = list.get(list.size() - 1); 1234 if (last.equals(zzz)) { 1235 list.remove(list.size() - 1); 1236 } else { 1237 list.set( 1238 list.size() - 1, 1239 last.substring(0, last.length() - zzz.length())); 1240 } 1241 return list; 1242 } 1243 List<String> names = new ArrayList<String>(); 1244 final String[] strings = nameCommaList.split(","); 1245 for (String string : strings) { 1246 final int count = names.size(); 1247 if (count > 0 1248 && names.get(count - 1).equals("")) 1249 { 1250 if (count == 1) { 1251 if (string.equals("")) { 1252 names.add(""); 1253 } else { 1254 names.set( 1255 0, 1256 "," + string); 1257 } 1258 } else { 1259 names.set( 1260 count - 2, 1261 names.get(count - 2) + "," + string); 1262 names.remove(count - 1); 1263 } 1264 } else { 1265 names.add(string); 1266 } 1267 } 1268 return names; 1269 } 1270 1271 /** 1272 * Returns an annotation of a particular class on a method. Returns the 1273 * default value if the annotation is not present, or in JDK 1.4. 1274 * 1275 * @param method Method containing annotation 1276 * @param annotationClassName Name of annotation class to find 1277 * @param defaultValue Value to return if annotation is not present 1278 * @return value of annotation 1279 */ 1280 public static <T> T getAnnotation( 1281 Method method, 1282 String annotationClassName, 1283 T defaultValue) 1284 { 1285 return compatible.getAnnotation( 1286 method, annotationClassName, defaultValue); 1287 } 1288 1289 /** 1290 * Converts a list of a string. 1291 * 1292 * For example, 1293 * <code>commaList("foo", Arrays.asList({"a", "b"}))</code> 1294 * returns "foo(a, b)". 1295 * 1296 * @param s Prefix 1297 * @param list List 1298 * @return String representation of string 1299 */ 1300 public static <T> String commaList( 1301 String s, 1302 List<T> list) 1303 { 1304 final StringBuilder buf = new StringBuilder(s); 1305 buf.append("("); 1306 int k = -1; 1307 for (T t : list) { 1308 if (++k > 0) { 1309 buf.append(", "); 1310 } 1311 buf.append(t); 1312 } 1313 buf.append(")"); 1314 return buf.toString(); 1315 } 1316 1317 /** 1318 * Returns the union of a list of iterables. 1319 * 1320 * <p>You can use it like this: 1321 * <blockquote><pre> 1322 * Iterable<String> iter1; 1323 * Iterable<String> iter2; 1324 * for (String s : union(iter1, iter2)) { 1325 * print(s); 1326 * }</pre></blockquote> 1327 * 1328 * @param iterables Array of one or more iterables 1329 * @return iterable over the union of the iterables 1330 */ 1331 public static <T> Iterable<T> union( 1332 final Iterable<? extends T>... iterables) { 1333 return new Iterable<T>() { 1334 public Iterator<T> iterator() { 1335 return new UnionIterator<T>(iterables); 1336 } 1337 }; 1338 } 1339 1340 /** 1341 * Returns the union of a list of collections. 1342 * 1343 * <p>This method exists for code that will be retrowoven to run on JDK 1.4. 1344 * Retroweaver has its own version of the {@link Iterable} interface, which 1345 * is problematic since the {@link Collection} classes don't implement it. 1346 * This method solves some of these problems by working in terms of 1347 * collections; retroweaver deals with these correctly. 1348 * 1349 * @see #union(Iterable[]) 1350 * 1351 * @param collections Array of one or more collections 1352 * @return iterable over the union of the collections 1353 */ 1354 public static <T> Iterable<T> union( 1355 final Collection<? extends T>... collections) { 1356 return new Iterable<T>() { 1357 public Iterator<T> iterator() { 1358 return new UnionIterator<T>(collections); 1359 } 1360 }; 1361 } 1362 1363 /** 1364 * Makes a name distinct from other names which have already been used 1365 * and shorter than a length limit, adds it to the list, and returns it. 1366 * 1367 * @param name Suggested name, may not be unique 1368 * @param maxLength Maximum length of generated name 1369 * @param nameList Collection of names already used 1370 * 1371 * @return Unique name 1372 */ 1373 public static String uniquify( 1374 String name, 1375 int maxLength, 1376 Collection<String> nameList) 1377 { 1378 assert name != null; 1379 if (name.length() > maxLength) { 1380 name = name.substring(0, maxLength); 1381 } 1382 if (nameList.contains(name)) { 1383 String aliasBase = name; 1384 int j = 0; 1385 while (true) { 1386 name = aliasBase + j; 1387 if (name.length() > maxLength) { 1388 aliasBase = aliasBase.substring(0, aliasBase.length() - 1); 1389 continue; 1390 } 1391 if (!nameList.contains(name)) { 1392 break; 1393 } 1394 j++; 1395 } 1396 } 1397 nameList.add(name); 1398 return name; 1399 } 1400 1401 /** 1402 * Returns whether a collection contains precisely one distinct element. 1403 * Returns false if the collection is empty, or if it contains elements 1404 * that are not the same as each other. 1405 * 1406 * @param collection Collection 1407 * @return boolean true if all values are same 1408 */ 1409 public static <T> boolean areOccurencesEqual( 1410 Collection<T> collection) 1411 { 1412 Iterator<T> it = collection.iterator(); 1413 if (!it.hasNext()) { 1414 // Collection is empty 1415 return false; 1416 } 1417 T first = it.next(); 1418 while (it.hasNext()) { 1419 T t = it.next(); 1420 if (!t.equals(first)) { 1421 return false; 1422 } 1423 } 1424 return true; 1425 } 1426 1427 public static class ErrorCellValue { 1428 public String toString() { 1429 return "#ERR"; 1430 } 1431 } 1432 1433 /** 1434 * Throws an internal error if condition is not true. It would be called 1435 * <code>assert</code>, but that is a keyword as of JDK 1.4. 1436 */ 1437 public static void assertTrue(boolean b) { 1438 if (!b) { 1439 throw newInternal("assert failed"); 1440 } 1441 } 1442 1443 /** 1444 * Throws an internal error with the given messagee if condition is not 1445 * true. It would be called <code>assert</code>, but that is a keyword as 1446 * of JDK 1.4. 1447 */ 1448 public static void assertTrue(boolean b, String message) { 1449 if (!b) { 1450 throw newInternal("assert failed: " + message); 1451 } 1452 } 1453 1454 /** 1455 * Creates an internal error with a given message. 1456 */ 1457 public static RuntimeException newInternal(String message) { 1458 return MondrianResource.instance().Internal.ex(message); 1459 } 1460 1461 /** 1462 * Creates an internal error with a given message and cause. 1463 */ 1464 public static RuntimeException newInternal(Throwable e, String message) { 1465 return MondrianResource.instance().Internal.ex(message, e); 1466 } 1467 1468 /** 1469 * Creates a non-internal error. Currently implemented in terms of 1470 * internal errors, but later we will create resourced messages. 1471 */ 1472 public static RuntimeException newError(String message) { 1473 return newInternal(message); 1474 } 1475 1476 /** 1477 * Creates a non-internal error. Currently implemented in terms of 1478 * internal errors, but later we will create resourced messages. 1479 */ 1480 public static RuntimeException newError(Throwable e, String message) { 1481 return newInternal(e, message); 1482 } 1483 1484 /** 1485 * Returns an exception indicating that we didn't expect to find this value 1486 * here. 1487 * 1488 * @param value Value 1489 */ 1490 public static RuntimeException unexpected(Enum value) { 1491 return Util.newInternal( 1492 "Was not expecting value '" + value + 1493 "' for enumeration '" + value.getClass().getName() + 1494 "' in this context"); 1495 } 1496 1497 /** 1498 * Checks that a precondition (declared using the javadoc <code>@pre</code> 1499 * tag) is satisfied. 1500 * 1501 * @param b The value of executing the condition 1502 */ 1503 public static void assertPrecondition(boolean b) { 1504 assertTrue(b); 1505 } 1506 1507 /** 1508 * Checks that a precondition (declared using the javadoc <code>@pre</code> 1509 * tag) is satisfied. For example, 1510 * 1511 * <blockquote><pre>void f(String s) { 1512 * Util.assertPrecondition(s != null, "s != null"); 1513 * ... 1514 * }</pre></blockquote> 1515 * 1516 * @param b The value of executing the condition 1517 * @param condition The text of the condition 1518 */ 1519 public static void assertPrecondition(boolean b, String condition) { 1520 assertTrue(b, condition); 1521 } 1522 1523 /** 1524 * Checks that a postcondition (declared using the javadoc 1525 * <code>@post</code> tag) is satisfied. 1526 * 1527 * @param b The value of executing the condition 1528 */ 1529 public static void assertPostcondition(boolean b) { 1530 assertTrue(b); 1531 } 1532 1533 /** 1534 * Checks that a postcondition (declared using the javadoc 1535 * <code>@post</code> tag) is satisfied. 1536 * 1537 * @param b The value of executing the condition 1538 */ 1539 public static void assertPostcondition(boolean b, String condition) { 1540 assertTrue(b, condition); 1541 } 1542 1543 /** 1544 * Converts an error into an array of strings, the most recent error first. 1545 * 1546 * @param e the error; may be null. Errors are chained according to their 1547 * {@link Throwable#getCause cause}. 1548 */ 1549 public static String[] convertStackToString(Throwable e) { 1550 List<String> list = new ArrayList<String>(); 1551 while (e != null) { 1552 String sMsg = getErrorMessage(e); 1553 list.add(sMsg); 1554 e = e.getCause(); 1555 } 1556 return list.toArray(new String[list.size()]); 1557 } 1558 1559 /** 1560 * Constructs the message associated with an arbitrary Java error, making 1561 * up one based on the stack trace if there is none. As 1562 * {@link #getErrorMessage(Throwable,boolean)}, but does not print the 1563 * class name if the exception is derived from {@link java.sql.SQLException} 1564 * or is exactly a {@link java.lang.Exception}. 1565 */ 1566 public static String getErrorMessage(Throwable err) { 1567 boolean prependClassName = 1568 !(err instanceof java.sql.SQLException || 1569 err.getClass() == java.lang.Exception.class); 1570 return getErrorMessage(err, prependClassName); 1571 } 1572 1573 /** 1574 * Constructs the message associated with an arbitrary Java error, making 1575 * up one based on the stack trace if there is none. 1576 * 1577 * @param err the error 1578 * @param prependClassName should the error be preceded by the 1579 * class name of the Java exception? defaults to false, unless the error 1580 * is derived from {@link java.sql.SQLException} or is exactly a {@link 1581 * java.lang.Exception} 1582 */ 1583 public static String getErrorMessage( 1584 Throwable err, 1585 boolean prependClassName) 1586 { 1587 String errMsg = err.getMessage(); 1588 if ((errMsg == null) || (err instanceof RuntimeException)) { 1589 StringWriter sw = new StringWriter(); 1590 PrintWriter pw = new PrintWriter(sw); 1591 err.printStackTrace(pw); 1592 return sw.toString(); 1593 } else { 1594 return (prependClassName) 1595 ? err.getClass().getName() + ": " + errMsg 1596 : errMsg; 1597 1598 } 1599 } 1600 1601 /** 1602 * Converts an expression to a string. 1603 */ 1604 public static String unparse(Exp exp) { 1605 StringWriter sw = new StringWriter(); 1606 PrintWriter pw = new PrintWriter(sw); 1607 exp.unparse(pw); 1608 return sw.toString(); 1609 } 1610 1611 /** 1612 * Converts an query to a string. 1613 */ 1614 public static String unparse(Query query) { 1615 StringWriter sw = new StringWriter(); 1616 PrintWriter pw = new QueryPrintWriter(sw); 1617 query.unparse(pw); 1618 return sw.toString(); 1619 } 1620 1621 /** 1622 * Creates a file-protocol URL for the given file. 1623 */ 1624 public static URL toURL(File file) throws MalformedURLException { 1625 String path = file.getAbsolutePath(); 1626 // This is a bunch of weird code that is required to 1627 // make a valid URL on the Windows platform, due 1628 // to inconsistencies in what getAbsolutePath returns. 1629 String fs = System.getProperty("file.separator"); 1630 if (fs.length() == 1) { 1631 char sep = fs.charAt(0); 1632 if (sep != '/') { 1633 path = path.replace(sep, '/'); 1634 } 1635 if (path.charAt(0) != '/') { 1636 path = '/' + path; 1637 } 1638 } 1639 path = "file://" + path; 1640 return new URL(path); 1641 } 1642 1643 /** 1644 * <code>PropertyList</code> is an order-preserving list of key-value 1645 * pairs. Lookup is case-insensitive, but the case of keys is preserved. 1646 */ 1647 public static class PropertyList implements Iterable<Pair<String, String>> { 1648 List<Pair<String, String>> list = new ArrayList<Pair<String, String>>(); 1649 1650 public String get(String key) { 1651 return get(key, null); 1652 } 1653 1654 public String get(String key, String defaultValue) { 1655 for (int i = 0, n = list.size(); i < n; i++) { 1656 Pair<String, String> pair = list.get(i); 1657 if (pair.left.equalsIgnoreCase(key)) { 1658 return pair.right; 1659 } 1660 } 1661 return defaultValue; 1662 } 1663 1664 public String put(String key, String value) { 1665 for (int i = 0, n = list.size(); i < n; i++) { 1666 Pair<String, String> pair = list.get(i); 1667 if (pair.left.equalsIgnoreCase(key)) { 1668 String old = pair.right; 1669 if (key.equalsIgnoreCase("Provider")) { 1670 // Unlike all other properties, later values of 1671 // "Provider" do not supersede 1672 } else { 1673 pair.right = value; 1674 } 1675 return old; 1676 } 1677 } 1678 list.add(new Pair<String, String>(key, value)); 1679 return null; 1680 } 1681 1682 public boolean remove(String key) { 1683 boolean found = false; 1684 for (int i = 0; i < list.size(); i++) { 1685 Pair<String, String> pair = list.get(i); 1686 if (pair.getKey().equalsIgnoreCase(key)) { 1687 list.remove(i); 1688 found = true; 1689 --i; 1690 } 1691 } 1692 return found; 1693 } 1694 1695 public String toString() { 1696 StringBuilder sb = new StringBuilder(64); 1697 for (int i = 0, n = list.size(); i < n; i++) { 1698 Pair<String, String> pair = list.get(i); 1699 if (i > 0) { 1700 sb.append("; "); 1701 } 1702 sb.append(pair.left); 1703 sb.append('='); 1704 1705 final String right = pair.right; 1706 if (right == null) { 1707 sb.append("'null'"); 1708 } else { 1709 // Quote a property value if is has a semi colon in it 1710 // 'xxx;yyy'. Escape any single-quotes by doubling them. 1711 final int needsQuote = right.indexOf(';'); 1712 if (needsQuote >= 0) { 1713 // REVIEW: This logic leaves off the leading/trailing 1714 // quote if the property value already has a 1715 // leading/trailing quote. Doesn't seem right to me. 1716 if (right.charAt(0) != '\'') { 1717 sb.append("'"); 1718 } 1719 sb.append(replace(right, "'", "''")); 1720 if (right.charAt(right.length() - 1) != '\'') { 1721 sb.append("'"); 1722 } 1723 } else { 1724 sb.append(right); 1725 } 1726 } 1727 } 1728 return sb.toString(); 1729 } 1730 1731 public Iterator<Pair<String, String>> iterator() { 1732 return list.iterator(); 1733 } 1734 } 1735 1736 /** 1737 * Converts an OLE DB connect string into a {@link PropertyList}. 1738 * 1739 * <p> For example, <code>"Provider=MSOLAP; DataSource=LOCALHOST;"</code> 1740 * becomes the set of (key, value) pairs <code>{("Provider","MSOLAP"), 1741 * ("DataSource", "LOCALHOST")}</code>. Another example is 1742 * <code>Provider='sqloledb';Data Source='MySqlServer';Initial 1743 * Catalog='Pubs';Integrated Security='SSPI';</code>. 1744 * 1745 * <p> This method implements as much as possible of the <a 1746 * href="http://msdn.microsoft.com/library/en-us/oledb/htm/oledbconnectionstringsyntax.asp" 1747 * target="_blank">OLE DB connect string syntax 1748 * specification</a>. To find what it <em>actually</em> does, take 1749 * a look at the <code>mondrian.olap.UtilTestCase</code> test case. 1750 */ 1751 public static PropertyList parseConnectString(String s) { 1752 return new ConnectStringParser(s).parse(); 1753 } 1754 1755 private static class ConnectStringParser { 1756 private final String s; 1757 private final int n; 1758 private int i; 1759 private final StringBuilder nameBuf; 1760 private final StringBuilder valueBuf; 1761 1762 private ConnectStringParser(String s) { 1763 this.s = s; 1764 this.i = 0; 1765 this.n = s.length(); 1766 this.nameBuf = new StringBuilder(64); 1767 this.valueBuf = new StringBuilder(64); 1768 } 1769 1770 PropertyList parse() { 1771 PropertyList list = new PropertyList(); 1772 while (i < n) { 1773 parsePair(list); 1774 } 1775 return list; 1776 } 1777 /** 1778 * Reads "name=value;" or "name=value<EOF>". 1779 */ 1780 void parsePair(PropertyList list) { 1781 String name = parseName(); 1782 if (name == null) { 1783 return; 1784 } 1785 String value; 1786 if (i >= n) { 1787 value = ""; 1788 } else if (s.charAt(i) == ';') { 1789 i++; 1790 value = ""; 1791 } else { 1792 value = parseValue(); 1793 } 1794 list.put(name, value); 1795 } 1796 1797 /** 1798 * Reads "name=". Name can contain equals sign if equals sign is 1799 * doubled. Returns null if there is no name to read. 1800 */ 1801 String parseName() { 1802 nameBuf.setLength(0); 1803 while (true) { 1804 char c = s.charAt(i); 1805 switch (c) { 1806 case '=': 1807 i++; 1808 if (i < n && (c = s.charAt(i)) == '=') { 1809 // doubled equals sign; take one of them, and carry on 1810 i++; 1811 nameBuf.append(c); 1812 break; 1813 } 1814 String name = nameBuf.toString(); 1815 name = name.trim(); 1816 return name; 1817 case ' ': 1818 if (nameBuf.length() == 0) { 1819 // ignore preceding spaces 1820 i++; 1821 if (i >= n) { 1822 // there is no name, e.g. trailing spaces after 1823 // semicolon, 'x=1; y=2; ' 1824 return null; 1825 } 1826 break; 1827 } else { 1828 // fall through 1829 } 1830 default: 1831 nameBuf.append(c); 1832 i++; 1833 if (i >= n) { 1834 return nameBuf.toString().trim(); 1835 } 1836 } 1837 } 1838 } 1839 /** 1840 * Reads "value;" or "value<EOF>" 1841 */ 1842 String parseValue() { 1843 char c; 1844 // skip over leading white space 1845 while ((c = s.charAt(i)) == ' ') { 1846 i++; 1847 if (i >= n) { 1848 return ""; 1849 } 1850 } 1851 if (c == '"' || c == '\'') { 1852 String value = parseQuoted(c); 1853 // skip over trailing white space 1854 while (i < n && (c = s.charAt(i)) == ' ') { 1855 i++; 1856 } 1857 if (i >= n) { 1858 return value; 1859 } else if (s.charAt(i) == ';') { 1860 i++; 1861 return value; 1862 } else { 1863 throw new RuntimeException( 1864 "quoted value ended too soon, at position " + i + 1865 " in '" + s + "'"); 1866 } 1867 } else { 1868 String value; 1869 int semi = s.indexOf(';', i); 1870 if (semi >= 0) { 1871 value = s.substring(i, semi); 1872 i = semi + 1; 1873 } else { 1874 value = s.substring(i); 1875 i = n; 1876 } 1877 return value.trim(); 1878 } 1879 } 1880 /** 1881 * Reads a string quoted by a given character. Occurrences of the 1882 * quoting character must be doubled. For example, 1883 * <code>parseQuoted('"')</code> reads <code>"a ""new"" string"</code> 1884 * and returns <code>a "new" string</code>. 1885 */ 1886 String parseQuoted(char q) { 1887 char c = s.charAt(i++); 1888 Util.assertTrue(c == q); 1889 valueBuf.setLength(0); 1890 while (i < n) { 1891 c = s.charAt(i); 1892 if (c == q) { 1893 i++; 1894 if (i < n) { 1895 c = s.charAt(i); 1896 if (c == q) { 1897 valueBuf.append(c); 1898 i++; 1899 continue; 1900 } 1901 } 1902 return valueBuf.toString(); 1903 } else { 1904 valueBuf.append(c); 1905 i++; 1906 } 1907 } 1908 throw new RuntimeException( 1909 "Connect string '" + s + 1910 "' contains unterminated quoted value '" + 1911 valueBuf.toString() + "'"); 1912 } 1913 } 1914 1915 /** 1916 * Combines two integers into a hash code. 1917 */ 1918 public static int hash(int i, int j) { 1919 return (i << 4) ^ j; 1920 } 1921 1922 /** 1923 * Computes a hash code from an existing hash code and an object (which 1924 * may be null). 1925 */ 1926 public static int hash(int h, Object o) { 1927 int k = (o == null) ? 0 : o.hashCode(); 1928 return ((h << 4) | h) ^ k; 1929 } 1930 1931 /** 1932 * Computes a hash code from an existing hash code and an array of objects 1933 * (which may be null). 1934 */ 1935 public static int hashArray(int h, Object [] a) { 1936 // The hashcode for a null array and an empty array should be different 1937 // than h, so use magic numbers. 1938 if (a == null) { 1939 return hash(h, 19690429); 1940 } 1941 if (a.length == 0) { 1942 return hash(h, 19690721); 1943 } 1944 for (Object anA : a) { 1945 h = hash(h, anA); 1946 } 1947 return h; 1948 } 1949 1950 /** 1951 * Returns the cumulative amount of time spent accessing the database. 1952 */ 1953 public static long dbTimeMillis() { 1954 return databaseMillis; 1955 } 1956 1957 /** 1958 * Adds to the cumulative amount of time spent accessing the database. 1959 */ 1960 public static void addDatabaseTime(long millis) { 1961 databaseMillis += millis; 1962 } 1963 1964 /** 1965 * Returns the system time less the time spent accessing the database. 1966 * Use this method to figure out how long an operation took: call this 1967 * method before an operation and after an operation, and the difference 1968 * is the amount of non-database time spent. 1969 */ 1970 public static long nonDbTimeMillis() { 1971 final long systemMillis = System.currentTimeMillis(); 1972 return systemMillis - databaseMillis; 1973 } 1974 1975 /** 1976 * Creates a very simple implementation of {@link Validator}. (Only 1977 * useful for resolving trivial expressions.) 1978 */ 1979 public static Validator createSimpleValidator(final FunTable funTable) { 1980 return new Validator() { 1981 public Query getQuery() { 1982 return null; 1983 } 1984 1985 public Exp validate(Exp exp, boolean scalar) { 1986 return exp; 1987 } 1988 1989 public void validate(ParameterExpr parameterExpr) { 1990 } 1991 1992 public void validate(MemberProperty memberProperty) { 1993 } 1994 1995 public void validate(QueryAxis axis) { 1996 } 1997 1998 public void validate(Formula formula) { 1999 } 2000 2001 public boolean canConvert(Exp fromExp, int to, int[] conversionCount) { 2002 return true; 2003 } 2004 2005 public boolean requiresExpression() { 2006 return false; 2007 } 2008 2009 public FunTable getFunTable() { 2010 return funTable; 2011 } 2012 2013 public Parameter createOrLookupParam( 2014 boolean definition, 2015 String name, 2016 Type type, 2017 Exp defaultExp, 2018 String description) { 2019 return null; 2020 } 2021 }; 2022 } 2023 2024 /** 2025 * Read a Reader until EOF and return as String. 2026 * Note: this ought to be in a Utility class. 2027 * 2028 * @param rdr Reader to Read. 2029 * @param bufferSize size of buffer to allocate for reading. 2030 * @return content of Reader as String or null if Reader was empty. 2031 * @throws IOException 2032 */ 2033 public static String readFully(final Reader rdr, final int bufferSize) 2034 throws IOException { 2035 2036 if (bufferSize <= 0) { 2037 throw new IllegalArgumentException( 2038 "Buffer size must be greater than 0"); 2039 } 2040 2041 final char[] buffer = new char[bufferSize]; 2042 final StringBuilder buf = new StringBuilder(bufferSize); 2043 2044 int len = rdr.read(buffer); 2045 while (len != -1) { 2046 buf.append(buffer, 0, len); 2047 len = rdr.read(buffer); 2048 } 2049 2050 final String s = buf.toString(); 2051 return (s.length() == 0) ? null : s; 2052 } 2053 2054 2055 /** 2056 * Read URL and return String containing content. 2057 * 2058 * @param urlStr actually a catalog URL 2059 * @return String containing content of catalog. 2060 * @throws MalformedURLException 2061 * @throws IOException 2062 */ 2063 public static String readURL(final String urlStr) 2064 throws MalformedURLException, IOException { 2065 return readURL(urlStr, null); 2066 } 2067 2068 /** 2069 * Returns the contents of a URL, substituting tokens. 2070 * 2071 * <p>Replaces the tokens "${key}" if "key" occurs in the key-value map. 2072 * 2073 * @param urlStr URL string 2074 * @param map Key/value map 2075 * @return Contents of URL with tokens substituted 2076 * @throws MalformedURLException 2077 * @throws IOException 2078 */ 2079 public static String readURL(final String urlStr, Map map) 2080 throws MalformedURLException, IOException { 2081 final URL url = new URL(urlStr); 2082 return readURL(url, map); 2083 } 2084 2085 /** 2086 * Returns the contents of a URL. 2087 * 2088 * @param url URL 2089 * @return Contents of URL 2090 * @throws IOException 2091 */ 2092 public static String readURL(final URL url) throws IOException { 2093 return readURL(url, null); 2094 } 2095 2096 /** 2097 * Returns the contents of a URL, substituting tokens. 2098 * 2099 * <p>Replaces the tokens "${key}" if "key" occurs in the key-value map. 2100 * 2101 * @param url URL 2102 * @param map Key/value map 2103 * @return Contents of URL with tokens substituted 2104 * @throws IOException 2105 */ 2106 public static String readURL(final URL url, Map<String, String> map) throws IOException { 2107 final Reader r = 2108 new BufferedReader(new InputStreamReader(url.openStream())); 2109 final int BUF_SIZE = 8096; 2110 try { 2111 String xmlCatalog = readFully(r, BUF_SIZE); 2112 if (map != null) { 2113 xmlCatalog = Util.replaceProperties(xmlCatalog, map); 2114 } 2115 return xmlCatalog; 2116 } finally { 2117 r.close(); 2118 } 2119 } 2120 2121 public static Map<String, String> toMap(final Properties properties) { 2122 return new AbstractMap<String, String>() { 2123 public Set<Entry<String, String>> entrySet() { 2124 return (Set) properties.entrySet(); 2125 } 2126 }; 2127 } 2128 /** 2129 * Replaces tokens in a string. 2130 * 2131 * <p>Replaces the tokens "${key}" if "key" occurs in the key-value map. 2132 * Otherwise "${key}" is left in the string unchanged. 2133 * 2134 * @param text Source string 2135 * @param env Map of key-value pairs 2136 * @return String with tokens substituted 2137 */ 2138 public static String replaceProperties( 2139 String text, 2140 Map<String, String> env) 2141 { 2142 // As of JDK 1.5, cannot use StringBuilder - appendReplacement requires 2143 // the antediluvian StringBuffer. 2144 StringBuffer buf = new StringBuffer(text.length() + 200); 2145 2146 Pattern pattern = Pattern.compile("\\$\\{([^${}]+)\\}"); 2147 Matcher matcher = pattern.matcher(text); 2148 while (matcher.find()) { 2149 String varName = matcher.group(1); 2150 String varValue = env.get(varName); 2151 if (varValue != null) { 2152 matcher.appendReplacement(buf, varValue); 2153 } else { 2154 matcher.appendReplacement(buf, "\\${$1}"); 2155 } 2156 } 2157 matcher.appendTail(buf); 2158 2159 return buf.toString(); 2160 } 2161 2162 public static String printMemory() { 2163 return printMemory(null); 2164 } 2165 2166 public static String printMemory(String msg) { 2167 final Runtime rt = Runtime.getRuntime(); 2168 final long freeMemory = rt.freeMemory(); 2169 final long totalMemory = rt.totalMemory(); 2170 final StringBuilder buf = new StringBuilder(64); 2171 2172 buf.append("FREE_MEMORY:"); 2173 if (msg != null) { 2174 buf.append(msg); 2175 buf.append(':'); 2176 } 2177 buf.append(' '); 2178 buf.append(freeMemory / 1024); 2179 buf.append("kb "); 2180 2181 long hundredths = (freeMemory * 10000) / totalMemory; 2182 2183 buf.append(hundredths / 100); 2184 hundredths %= 100; 2185 if (hundredths >= 10) { 2186 buf.append('.'); 2187 } else { 2188 buf.append(".0"); 2189 } 2190 buf.append(hundredths); 2191 buf.append('%'); 2192 2193 return buf.toString(); 2194 } 2195 2196 /** 2197 * Casts a Set to a Set with a different element type. 2198 * 2199 * @param set Set 2200 * @return Set of desired type 2201 */ 2202 @SuppressWarnings({"unchecked"}) 2203 public static <T> Set<T> cast(Set<?> set) { 2204 return (Set<T>) set; 2205 } 2206 2207 /** 2208 * Casts a List to a List with a different element type. 2209 * 2210 * @param list List 2211 * @return List of desired type 2212 */ 2213 @SuppressWarnings({"unchecked"}) 2214 public static <T> List<T> cast(List<?> list) { 2215 return (List<T>) list; 2216 } 2217 2218 /** 2219 * Looks up an enumeration by name, returning null if not valid. 2220 */ 2221 public static <E extends Enum<E>> E lookup(Class<E> clazz, String name) { 2222 return lookup(clazz, name, null); 2223 } 2224 2225 /** 2226 * Looks up an enumeration by name, returning a given default value if not 2227 * valid. 2228 */ 2229 public static <E extends Enum<E>> E lookup( 2230 Class<E> clazz, String name, E defaultValue) { 2231 try { 2232 return Enum.valueOf(clazz, name); 2233 } catch (IllegalArgumentException e) { 2234 return defaultValue; 2235 } 2236 } 2237 2238 /** 2239 * Equivalent to {@link java.util.EnumSet#of(Enum, Enum[])} on JDK 1.5 or 2240 * later. Otherwise, returns an ordinary set. 2241 * 2242 * @param first an element that the set is to contain initially 2243 * @param rest the remaining elements the set is to contain initially 2244 * @throws NullPointerException if any of the specified elements are null, 2245 * or if <tt>rest</tt> is null 2246 * @return an enum set initially containing the specified elements 2247 */ 2248 public static <E extends Enum<E>> Set<E> enumSetOf(E first, E... rest) { 2249 return compatible.enumSetOf(first, rest); 2250 } 2251 2252 /** 2253 * Equivalent to {@link java.util.EnumSet#noneOf(Class)} on JDK 1.5 or later. 2254 * Otherwise, returns an ordinary set. 2255 * 2256 * @param elementType the class object of the element type for this enum 2257 * set 2258 */ 2259 public static <E extends Enum<E>> Set<E> enumSetNoneOf(Class<E> elementType) { 2260 return compatible.enumSetNoneOf(elementType); 2261 } 2262 2263 /** 2264 * Equivalent to {@link java.util.EnumSet#allOf(Class)} on JDK 1.5 or later. 2265 * Otherwise, returns an ordinary set. 2266 2267 * @param elementType the class object of the element type for this enum 2268 * set 2269 */ 2270 public static <E extends Enum<E>> Set<E> enumSetAllOf(Class<E> elementType) { 2271 return compatible.enumSetAllOf(elementType); 2272 } 2273 2274 /** 2275 * Make a BigDecimal from a double. On JDK 1.5 or later, the BigDecimal 2276 * precision reflects the precision of the double while with JDK 1.4 2277 * this is not the case. 2278 * 2279 * @param d the input double 2280 * @return the BigDecimal 2281 */ 2282 public static BigDecimal makeBigDecimalFromDouble(double d) { 2283 return compatible.makeBigDecimalFromDouble(d); 2284 } 2285 2286 /** 2287 * Returns a literal pattern String for the specified String. 2288 * 2289 * <p>Specification as for {@link Pattern#quote(String)}, which was 2290 * introduced in JDK 1.5. 2291 * 2292 * @param s The string to be literalized 2293 * @return A literal string replacement 2294 */ 2295 public static String quotePattern(String s) { 2296 return compatible.quotePattern(s); 2297 } 2298 2299 /** 2300 * Creates a new udf instance from the given udf class. 2301 * 2302 * @param udfClass the class to create new instance for 2303 * @return an instance of UserDefinedFunction 2304 */ 2305 public static UserDefinedFunction createUdf(Class<?> udfClass) { 2306 // Instantiate class with default constructor. 2307 UserDefinedFunction udf; 2308 String className = udfClass.getName(); 2309 2310 try { 2311 udf = (UserDefinedFunction) udfClass.newInstance(); 2312 } catch (InstantiationException e) { 2313 throw MondrianResource.instance().UdfClassWrongIface.ex("", 2314 className, UserDefinedFunction.class.getName()); 2315 } catch (IllegalAccessException e) { 2316 throw MondrianResource.instance().UdfClassWrongIface.ex("", 2317 className, UserDefinedFunction.class.getName()); 2318 } catch (ClassCastException e) { 2319 throw MondrianResource.instance().UdfClassWrongIface.ex("", 2320 className, UserDefinedFunction.class.getName()); 2321 } 2322 2323 return udf; 2324 } 2325 2326 /** 2327 * Check the resultSize against the result limit setting. Throws 2328 * LimitExceededDuringCrossjoin exception if limit exceeded. 2329 * 2330 * When it is called from RolapNativeSet.checkCrossJoin(), it is only 2331 * possible to check the known input size, because the final CJ result 2332 * will come from the DB(and will be checked against the limit when 2333 * fetching from the JDBC result set, in SqlTupleReader.prepareTuples()) 2334 * 2335 * @param resultSize 2336 * @throws ResourceLimitExceededException 2337 */ 2338 public static void checkCJResultLimit(long resultSize) { 2339 int resultLimit = MondrianProperties.instance().ResultLimit.get(); 2340 2341 // Throw an exeption, if the size of the crossjoin exceeds the result 2342 // limit. 2343 // 2344 if (resultLimit > 0 && resultLimit < resultSize) { 2345 throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex( 2346 resultSize, resultLimit); 2347 } 2348 2349 // Throw an exception if the crossjoin exceeds a reasonable limit. 2350 // (Yes, 4 billion is a reasonable limit.) 2351 if (resultSize > Integer.MAX_VALUE) { 2352 throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex( 2353 resultSize, Integer.MAX_VALUE); 2354 } 2355 } 2356 } 2357 2358 // End Util.java