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]&#46;[Product Department]&#46;[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&lt;String&gt; iter1;
1323         * Iterable&lt;String&gt; 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