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