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