Package mondrian.olap

Source Code of mondrian.olap.Util$NullValuesMap

/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2001-2005 Julian Hyde
// Copyright (C) 2005-2014 Pentaho and others
// All Rights Reserved.
*/
package mondrian.olap;

import mondrian.mdx.*;
import mondrian.olap.fun.FunUtil;
import mondrian.olap.fun.Resolver;
import mondrian.olap.type.Type;
import mondrian.resource.MondrianResource;
import mondrian.rolap.*;
import mondrian.spi.UserDefinedFunction;
import mondrian.util.*;

import org.apache.commons.collections.keyvalue.AbstractMapEntry;
import org.apache.commons.io.IOUtils;
import org.apache.commons.vfs.*;
import org.apache.commons.vfs.provider.http.HttpFileObject;
import org.apache.log4j.Logger;

import org.eigenbase.xom.XOMUtil;

import org.olap4j.impl.Olap4jUtil;
import org.olap4j.mdx.*;

import java.io.*;
import java.lang.ref.Reference;
import java.lang.reflect.*;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.*;
import java.sql.Connection;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Utility functions used throughout mondrian. All methods are static.
*
* @author jhyde
* @since 6 August, 2001
*/
public class Util extends XOMUtil {

    public static final String nl = System.getProperty("line.separator");

    private static final Logger LOGGER = Logger.getLogger(Util.class);

    /**
     * Placeholder which indicates a value NULL.
     */
    public static final Object nullValue = new Double(FunUtil.DoubleNull);

    /**
     * Placeholder which indicates an EMPTY value.
     */
    public static final Object EmptyValue = new Double(FunUtil.DoubleEmpty);

    /**
     * Cumulative time spent accessing the database.
     */
    private static long databaseMillis = 0;

    /**
     * Random number generator to provide seed for other random number
     * generators.
     */
    private static final Random metaRandom =
            createRandom(MondrianProperties.instance().TestSeed.get());

    /** Unique id for this JVM instance. Part of a key that ensures that if
     * two JVMs in the same cluster have a data-source with the same
     * identity-hash-code, they will be treated as different data-sources,
     * and therefore caches will not be incorrectly shared. */
    public static final UUID JVM_INSTANCE_UUID = UUID.randomUUID();

    /**
     * Whether we are running a version of Java before 1.5.
     *
     * <p>If (but not only if) this variable is true, {@link #Retrowoven} will
     * also be true.
     */
    public static final boolean PreJdk15 =
        System.getProperty("java.version").startsWith("1.4");

    /**
     * Whether we are running a version of Java before 1.6.
     */
    public static final boolean PreJdk16 =
        PreJdk15
        || System.getProperty("java.version").startsWith("1.5");

    /**
     * Whether this is an IBM JVM.
     */
    public static final boolean IBM_JVM =
        System.getProperties().getProperty("java.vendor").equals(
            "IBM Corporation");

    /**
     * What version of JDBC?
     * Returns:<ul>
     *     <li>0x0401 in JDK 1.7 and higher</li>
     *     <li>0x0400 in JDK 1.6</li>
     *     <li>0x0300 otherwise</li>
     * </ul>
     */
    public static final int JdbcVersion =
        System.getProperty("java.version").compareTo("1.7") >= 0
            ? 0x0401
            : System.getProperty("java.version").compareTo("1.6") >= 0
            ? 0x0400
            : 0x0300;

    /**
     * Whether the code base has re-engineered using retroweaver.
     * If this is the case, some functionality is not available, but a lot of
     * things are available via {@link mondrian.util.UtilCompatible}.
     * Retroweaver has some problems involving {@link java.util.EnumSet}.
     */
    public static final boolean Retrowoven =
        Access.class.getSuperclass().getName().equals(
            "net.sourceforge.retroweaver.runtime.java.lang.Enum");

    private static final UtilCompatible compatible;

    /**
     * Flag to control expensive debugging. (More expensive than merely
     * enabling assertions: as we know, a lot of people run with assertions
     * enabled.)
     */
    public static final boolean DEBUG = false;

    static {
        String className;
        if (PreJdk15 || Retrowoven) {
            className = "mondrian.util.UtilCompatibleJdk14";
        } else if (PreJdk16) {
            className = "mondrian.util.UtilCompatibleJdk15";
        } else {
            className = "mondrian.util.UtilCompatibleJdk16";
        }
        compatible = ClassResolver.INSTANCE.instantiateSafe(className);
    }

    public static boolean isNull(Object o) {
        return o == null || o == nullValue;
    }

    /**
     * Returns whether a list is strictly sorted.
     *
     * @param list List
     * @return whether list is sorted
     */
    public static <T> boolean isSorted(List<T> list) {
        T prev = null;
        for (T t : list) {
            if (prev != null
                && ((Comparable<T>) prev).compareTo(t) >= 0)
            {
                return false;
            }
            prev = t;
        }
        return true;
    }

    /**
     * Parses a string and returns a SHA-256 checksum of it.
     *
     * @param value The source string to parse.
     * @return A checksum of the source string.
     */
    public static byte[] digestSha256(String value) {
        final MessageDigest algorithm;
        try {
            algorithm = MessageDigest.getInstance("SHA-256");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        return algorithm.digest(value.getBytes());
    }

    /**
     * Creates an MD5 hash of a String.
     *
     * @param value String to create one way hash upon.
     * @return MD5 hash.
     */
    public static byte[] digestMd5(final String value) {
        final MessageDigest algorithm;
        try {
            algorithm = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        return algorithm.digest(value.getBytes());
    }

    /**
     * Creates an {@link ExecutorService} object backed by a thread pool.
     * @param maximumPoolSize Maximum number of concurrent
     * threads.
     * @param corePoolSize Minimum number of concurrent
     * threads to maintain in the pool, even if they are
     * idle.
     * @param keepAliveTime Time, in seconds, for which to
     * keep alive unused threads.
     * @param name The name of the threads.
     * @param rejectionPolicy The rejection policy to enforce.
     * @return An executor service preconfigured.
     */
    public static ExecutorService getExecutorService(
        int maximumPoolSize,
        int corePoolSize,
        long keepAliveTime,
        final String name,
        RejectedExecutionHandler rejectionPolicy)
    {
        if (Util.PreJdk16) {
            // On JDK1.5, if you specify corePoolSize=0, nothing gets executed.
            // Bummer.
            corePoolSize = Math.max(corePoolSize, 1);
        }

        // We must create a factory where the threads
        // have the right name and are marked as daemon threads.
        final ThreadFactory factory =
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(0);
                public Thread newThread(Runnable r) {
                    final Thread t =
                        Executors.defaultThreadFactory().newThread(r);
                    t.setDaemon(true);
                    t.setName(name + '_' + counter.incrementAndGet());
                    return t;
                }
            };

        // Ok, create the executor
        final ThreadPoolExecutor executor =
            new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize > 0
                    ? maximumPoolSize
                    : Integer.MAX_VALUE,
                keepAliveTime,
                TimeUnit.SECONDS,
                // we use a sync queue. any other type of queue
                // will prevent the tasks from running concurrently
                // because the executors API requires blocking queues.
                // Important to pass true here. This makes the
                // order of tasks deterministic.
                // TODO Write a non-blocking queue which implements
                // the blocking queue API so we can pass that to the
                // executor.
                new LinkedBlockingQueue<Runnable>(),
                factory);

        // Set the rejection policy if required.
        if (rejectionPolicy != null) {
            executor.setRejectedExecutionHandler(
                rejectionPolicy);
        }

        // Done
        return executor;
    }

    /**
     * Creates an {@link ScheduledExecutorService} object backed by a
     * thread pool with a fixed number of threads..
     * @param maxNbThreads Maximum number of concurrent
     * threads.
     * @param name The name of the threads.
     * @return An scheduled executor service preconfigured.
     */
    public static ScheduledExecutorService getScheduledExecutorService(
        final int maxNbThreads,
        final String name)
    {
        return Executors.newScheduledThreadPool(
            maxNbThreads,
            new ThreadFactory() {
                final AtomicInteger counter = new AtomicInteger(0);
                public Thread newThread(Runnable r) {
                    final Thread thread =
                        Executors.defaultThreadFactory().newThread(r);
                    thread.setDaemon(true);
                    thread.setName(name + '_' + counter.incrementAndGet());
                    return thread;
                }
            }
        );
    }

    /**
     * Encodes string for MDX (escapes ] as ]] inside a name).
     *
     * @deprecated Will be removed in 4.0
     */
    public static String mdxEncodeString(String st) {
        StringBuilder retString = new StringBuilder(st.length() + 20);
        for (int i = 0; i < st.length(); i++) {
            char c = st.charAt(i);
            if ((c == ']')
                && ((i + 1) < st.length())
                && (st.charAt(i + 1) != '.'))
            {
                retString.append(']'); // escaping character
            }
            retString.append(c);
        }
        return retString.toString();
    }

    /**
     * Converts a string into a double-quoted string.
     */
    public static String quoteForMdx(String val) {
        StringBuilder buf = new StringBuilder(val.length() + 20);
        quoteForMdx(buf, val);
        return buf.toString();
    }

    /**
     * Appends a double-quoted string to a string builder.
     */
    public static StringBuilder quoteForMdx(StringBuilder buf, String val) {
        buf.append("\"");
        String s0 = replace(val, "\"", "\"\"");
        buf.append(s0);
        buf.append("\"");
        return buf;
    }

    /**
     * Return string quoted in [...].  For example, "San Francisco" becomes
     * "[San Francisco]"; "a [bracketed] string" becomes
     * "[a [bracketed]] string]".
     */
    public static String quoteMdxIdentifier(String id) {
        StringBuilder buf = new StringBuilder(id.length() + 20);
        quoteMdxIdentifier(id, buf);
        return buf.toString();
    }

    public static void quoteMdxIdentifier(String id, StringBuilder buf) {
        buf.append('[');
        int start = buf.length();
        buf.append(id);
        replace(buf, start, "]", "]]");
        buf.append(']');
    }

    /**
     * Return identifiers quoted in [...].[...].  For example, {"Store", "USA",
     * "California"} becomes "[Store].[USA].[California]".
     */
    public static String quoteMdxIdentifier(List<Id.Segment> ids) {
        StringBuilder sb = new StringBuilder(64);
        quoteMdxIdentifier(ids, sb);
        return sb.toString();
    }

    public static void quoteMdxIdentifier(
        List<Id.Segment> ids,
        StringBuilder sb)
    {
        for (int i = 0; i < ids.size(); i++) {
            if (i > 0) {
                sb.append('.');
            }
            ids.get(i).toString(sb);
        }
    }

    /**
     * Quotes a string literal for Java or JavaScript.
     *
     * @param s Unquoted literal
     * @return Quoted string literal
     */
    public static String quoteJavaString(String s) {
        return s == null
            ? "null"
            : "\""
              + s.replaceAll("\\\\", "\\\\\\\\")
                .replaceAll("\\\"", "\\\\\"")
              + "\"";
    }

    /**
     * Returns true if two objects are equal, or are both null.
     *
     * @param s First object
     * @param t Second object
     * @return Whether objects are equal or both null
     */
    public static boolean equals(Object s, Object t) {
        if (s == t) {
            return true;
        }
        if (s == null || t == null) {
            return false;
        }
        return s.equals(t);
    }

    /**
     * Returns true if two strings are equal, or are both null.
     *
     * <p>The result is not affected by
     * {@link MondrianProperties#CaseSensitive the case sensitive option}; if
     * you wish to compare names, use {@link #equalName(String, String)}.
     */
    public static boolean equals(String s, String t) {
        return equals((Object) s, (Object) t);
    }

    /**
     * Returns whether two names are equal.
     * Takes into account the
     * {@link MondrianProperties#CaseSensitive case sensitive option}.
     * Names may be null.
     */
    public static boolean equalName(String s, String t) {
        if (s == null) {
            return t == null;
        }
        boolean caseSensitive =
            MondrianProperties.instance().CaseSensitive.get();
        return caseSensitive ? s.equals(t) : s.equalsIgnoreCase(t);
    }

    /**
     * Tests two strings for equality, optionally ignoring case.
     *
     * @param s First string
     * @param t Second string
     * @param matchCase Whether to perform case-sensitive match
     * @return Whether strings are equal
     */
    public static boolean equal(String s, String t, boolean matchCase) {
        return matchCase ? s.equals(t) : s.equalsIgnoreCase(t);
    }

    /**
     * Compares two names.  if case sensitive flag is false,
     * apply finer grain difference with case sensitive
     * Takes into account the {@link MondrianProperties#CaseSensitive case
     * sensitive option}.
     * Names must not be null.
     */
    public static int caseSensitiveCompareName(String s, String t) {
        boolean caseSensitive =
            MondrianProperties.instance().CaseSensitive.get();
        if (caseSensitive) {
            return s.compareTo(t);
        } else {
            int v = s.compareToIgnoreCase(t);
            // if ignore case returns 0 compare in a case sensitive manner
            // this was introduced to solve an issue with Member.equals()
            // and Member.compareTo() not agreeing with each other
            return v == 0 ? s.compareTo(t) : v;
        }
    }

    /**
     * Compares two names.
     * Takes into account the {@link MondrianProperties#CaseSensitive case
     * sensitive option}.
     * Names must not be null.
     */
    public static int compareName(String s, String t) {
        boolean caseSensitive =
            MondrianProperties.instance().CaseSensitive.get();
        return caseSensitive ? s.compareTo(t) : s.compareToIgnoreCase(t);
    }

    /**
     * Generates a normalized form of a name, for use as a key into a map.
     * Returns the upper case name if
     * {@link MondrianProperties#CaseSensitive} is true, the name unchanged
     * otherwise.
     */
    public static String normalizeName(String s) {
        return MondrianProperties.instance().CaseSensitive.get()
            ? s
            : s.toUpperCase();
    }

    /**
     * Returns the result of ((Comparable) k1).compareTo(k2), with
     * special-casing for the fact that Boolean only became
     * comparable in JDK 1.5.
     *
     * @see Comparable#compareTo
     */
    public static int compareKey(Object k1, Object k2) {
        if (k1 instanceof Boolean) {
            // Luckily, "F" comes before "T" in the alphabet.
            k1 = k1.toString();
            k2 = k2.toString();
        }
        return ((Comparable) k1).compareTo(k2);
    }

    /**
     * Compares integer values.
     *
     * @param i0 First integer
     * @param i1 Second integer
     * @return Comparison of integers
     */
    public static int compare(int i0, int i1) {
        return i0 < i1 ? -1 : (i0 == i1 ? 0 : 1);
    }

    /**
     * Returns a string with every occurrence of a seek string replaced with
     * another.
     */
    public static String replace(String s, String find, String replace) {
        // let's be optimistic
        int found = s.indexOf(find);
        if (found == -1) {
            return s;
        }
        StringBuilder sb = new StringBuilder(s.length() + 20);
        int start = 0;
        char[] chars = s.toCharArray();
        final int step = find.length();
        if (step == 0) {
            // Special case where find is "".
            sb.append(s);
            replace(sb, 0, find, replace);
        } else {
            for (;;) {
                sb.append(chars, start, found - start);
                if (found == s.length()) {
                    break;
                }
                sb.append(replace);
                start = found + step;
                found = s.indexOf(find, start);
                if (found == -1) {
                    found = s.length();
                }
            }
        }
        return sb.toString();
    }

    /**
     * Replaces all occurrences of a string in a buffer with another.
     *
     * @param buf String buffer to act on
     * @param start Ordinal within <code>find</code> to start searching
     * @param find String to find
     * @param replace String to replace it with
     * @return The string buffer
     */
    public static StringBuilder replace(
        StringBuilder buf,
        int start,
        String find,
        String replace)
    {
        // Search and replace from the end towards the start, to avoid O(n ^ 2)
        // copying if the string occurs very commonly.
        int findLength = find.length();
        if (findLength == 0) {
            // Special case where the seek string is empty.
            for (int j = buf.length(); j >= 0; --j) {
                buf.insert(j, replace);
            }
            return buf;
        }
        int k = buf.length();
        while (k > 0) {
            int i = buf.lastIndexOf(find, k);
            if (i < start) {
                break;
            }
            buf.replace(i, i + find.length(), replace);
            // Step back far enough to ensure that the beginning of the section
            // we just replaced does not cause a match.
            k = i - findLength;
        }
        return buf;
    }

    /**
     * Parses an MDX identifier such as <code>[Foo].[Bar].Baz.&Key&Key2</code>
     * and returns the result as a list of segments.
     *
     * @param s MDX identifier
     * @return List of segments
     */
    public static List<Id.Segment> parseIdentifier(String s)  {
        return convert(
            org.olap4j.impl.IdentifierParser.parseIdentifier(s));
    }

    /**
     * Converts an array of name parts {"part1", "part2"} into a single string
     * "[part1].[part2]". If the names contain "]" they are escaped as "]]".
     */
    public static String implode(List<Id.Segment> names) {
        StringBuilder sb = new StringBuilder(64);
        for (int i = 0; i < names.size(); i++) {
            if (i > 0) {
                sb.append(".");
            }
            // FIXME: should be:
            //   names.get(i).toString(sb);
            // but that causes some tests to fail
            Id.Segment segment = names.get(i);
            switch (segment.getQuoting()) {
            case UNQUOTED:
                segment = new Id.NameSegment(((Id.NameSegment) segment).name);
            }
            segment.toString(sb);
        }
        return sb.toString();
    }

    public static String makeFqName(String name) {
        return quoteMdxIdentifier(name);
    }

    public static String makeFqName(OlapElement parent, String name) {
        if (parent == null) {
            return Util.quoteMdxIdentifier(name);
        } else {
            StringBuilder buf = new StringBuilder(64);
            buf.append(parent.getUniqueName());
            buf.append('.');
            Util.quoteMdxIdentifier(name, buf);
            return buf.toString();
        }
    }

    public static String makeFqName(String parentUniqueName, String name) {
        if (parentUniqueName == null) {
            return quoteMdxIdentifier(name);
        } else {
            StringBuilder buf = new StringBuilder(64);
            buf.append(parentUniqueName);
            buf.append('.');
            Util.quoteMdxIdentifier(name, buf);
            return buf.toString();
        }
    }

    public static OlapElement lookupCompound(
        SchemaReader schemaReader,
        OlapElement parent,
        List<Id.Segment> names,
        boolean failIfNotFound,
        int category)
    {
        return lookupCompound(
            schemaReader, parent, names, failIfNotFound, category,
            MatchType.EXACT);
    }

    /**
     * Resolves a name such as
     * '[Products]&#46;[Product Department]&#46;[Produce]' by resolving the
     * components ('Products', and so forth) one at a time.
     *
     * @param schemaReader Schema reader, supplies access-control context
     * @param parent Parent element to search in
     * @param names Exploded compound name, such as {"Products",
     *   "Product Department", "Produce"}
     * @param failIfNotFound If the element is not found, determines whether
     *   to return null or throw an error
     * @param category Type of returned element, a {@link Category} value;
     *   {@link Category#Unknown} if it doesn't matter.
     *
     * @pre parent != null
     * @post !(failIfNotFound && return == null)
     *
     * @see #parseIdentifier(String)
     */
    public static OlapElement lookupCompound(
        SchemaReader schemaReader,
        OlapElement parent,
        List<Id.Segment> names,
        boolean failIfNotFound,
        int category,
        MatchType matchType)
    {
        Util.assertPrecondition(parent != null, "parent != null");

        if (LOGGER.isDebugEnabled()) {
            StringBuilder buf = new StringBuilder(64);
            buf.append("Util.lookupCompound: ");
            buf.append("parent.name=");
            buf.append(parent.getName());
            buf.append(", category=");
            buf.append(Category.instance.getName(category));
            buf.append(", names=");
            quoteMdxIdentifier(names, buf);
            LOGGER.debug(buf.toString());
        }

        // First look up a member from the cache of calculated members
        // (cubes and queries both have them).
        switch (category) {
        case Category.Member:
        case Category.Unknown:
            Member member = schemaReader.getCalculatedMember(names);
            if (member != null) {
                return member;
            }
        }
        // Likewise named set.
        switch (category) {
        case Category.Set:
        case Category.Unknown:
            NamedSet namedSet = schemaReader.getNamedSet(names);
            if (namedSet != null) {
                return namedSet;
            }
        }

        // Now resolve the name one part at a time.
        for (int i = 0; i < names.size(); i++) {
            OlapElement child;
            Id.NameSegment name;
            if (names.get(i) instanceof Id.NameSegment) {
                name = (Id.NameSegment) names.get(i);
                child = schemaReader.getElementChild(parent, name, matchType);
            } else if (parent instanceof RolapLevel
                       && names.get(i) instanceof Id.KeySegment
                       && names.get(i).getKeyParts().size() == 1)
            {
                // The following code is for SsasCompatibleNaming=false.
                // Continues the very limited support for key segments in
                // mondrian-3.x. To be removed in mondrian-4, when
                // SsasCompatibleNaming=true is the only option.
                final Id.KeySegment keySegment = (Id.KeySegment) names.get(i);
                name = keySegment.getKeyParts().get(0);
                final List<Member> levelMembers =
                    schemaReader.getLevelMembers(
                        (Level) parent, false);
                child = null;
                for (Member member : levelMembers) {
                    if (((RolapMember) member).getKey().toString().equals(
                            name.getName()))
                    {
                        child = member;
                        break;
                    }
                }
            } else {
                name = null;
                child = schemaReader.getElementChild(parent, name, matchType);
            }
            // if we're doing a non-exact match, and we find a non-exact
            // match, then for an after match, return the first child
            // of each subsequent level; for a before match, return the
            // last child
            if (child instanceof Member
                && !matchType.isExact()
                && !Util.equalName(child.getName(), name.getName()))
            {
                Member bestChild = (Member) child;
                for (int j = i + 1; j < names.size(); j++) {
                    List<Member> childrenList =
                        schemaReader.getMemberChildren(bestChild);
                    FunUtil.hierarchizeMemberList(childrenList, false);
                    if (matchType == MatchType.AFTER) {
                        bestChild = childrenList.get(0);
                    } else {
                        bestChild =
                            childrenList.get(childrenList.size() - 1);
                    }
                    if (bestChild == null) {
                        child = null;
                        break;
                    }
                }
                parent = bestChild;
                break;
            }
            if (child == null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(
                        "Util.lookupCompound: "
                        + "parent.name="
                        + parent.getName()
                        + " has no child with name="
                        + name);
                }

                if (!failIfNotFound) {
                    return null;
                } else if (category == Category.Member) {
                    throw MondrianResource.instance().MemberNotFound.ex(
                        quoteMdxIdentifier(names));
                } else {
                    throw MondrianResource.instance().MdxChildObjectNotFound
                        .ex(name.toString(), parent.getQualifiedName());
                }
            }
            parent = child;
            if (matchType == MatchType.EXACT_SCHEMA) {
                matchType = MatchType.EXACT;
            }
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(
                "Util.lookupCompound: "
                + "found child.name="
                + parent.getName()
                + ", child.class="
                + parent.getClass().getName());
        }

        switch (category) {
        case Category.Dimension:
            if (parent instanceof Dimension) {
                return parent;
            } else if (parent instanceof Hierarchy) {
                return parent.getDimension();
            } else if (failIfNotFound) {
                throw Util.newError(
                    "Can not find dimension '" + implode(names) + "'");
            } else {
                return null;
            }
        case Category.Hierarchy:
            if (parent instanceof Hierarchy) {
                return parent;
            } else if (parent instanceof Dimension) {
                return parent.getHierarchy();
            } else if (failIfNotFound) {
                throw Util.newError(
                    "Can not find hierarchy '" + implode(names) + "'");
            } else {
                return null;
            }
        case Category.Level:
            if (parent instanceof Level) {
                return parent;
            } else if (failIfNotFound) {
                throw Util.newError(
                    "Can not find level '" + implode(names) + "'");
            } else {
                return null;
            }
        case Category.Member:
            if (parent instanceof Member) {
                return parent;
            } else if (failIfNotFound) {
                throw MondrianResource.instance().MdxCantFindMember.ex(
                    implode(names));
            } else {
                return null;
            }
        case Category.Unknown:
            assertPostcondition(parent != null, "return != null");
            return parent;
        default:
            throw newInternal("Bad switch " + category);
        }
    }

    public static OlapElement lookup(Query q, List<Id.Segment> nameParts) {
        final Exp exp = lookup(q, nameParts, false);
        if (exp instanceof MemberExpr) {
            MemberExpr memberExpr = (MemberExpr) exp;
            return memberExpr.getMember();
        } else if (exp instanceof LevelExpr) {
            LevelExpr levelExpr = (LevelExpr) exp;
            return levelExpr.getLevel();
        } else if (exp instanceof HierarchyExpr) {
            HierarchyExpr hierarchyExpr = (HierarchyExpr) exp;
            return hierarchyExpr.getHierarchy();
        } else if (exp instanceof DimensionExpr) {
            DimensionExpr dimensionExpr = (DimensionExpr) exp;
            return dimensionExpr.getDimension();
        } else {
            throw Util.newInternal("Not an olap element: " + exp);
        }
    }

    /**
     * Converts an identifier into an expression by resolving its parts into
     * an OLAP object (dimension, hierarchy, level or member) within the
     * context of a query.
     *
     * <p>If <code>allowProp</code> is true, also allows property references
     * from valid members, for example
     * <code>[Measures].[Unit Sales].FORMATTED_VALUE</code>.
     * In this case, the result will be a {@link mondrian.mdx.ResolvedFunCall}.
     *
     * @param q Query expression belongs to
     * @param nameParts Parts of the identifier
     * @param allowProp Whether to allow property references
     * @return OLAP object or property reference
     */
    public static Exp lookup(
        Query q,
        List<Id.Segment> nameParts,
        boolean allowProp)
    {
        return lookup(q, q.getSchemaReader(true), nameParts, allowProp);
    }

    /**
     * Converts an identifier into an expression by resolving its parts into
     * an OLAP object (dimension, hierarchy, level or member) within the
     * context of a query.
     *
     * <p>If <code>allowProp</code> is true, also allows property references
     * from valid members, for example
     * <code>[Measures].[Unit Sales].FORMATTED_VALUE</code>.
     * In this case, the result will be a {@link ResolvedFunCall}.
     *
     * @param q Query expression belongs to
     * @param schemaReader Schema reader
     * @param segments Parts of the identifier
     * @param allowProp Whether to allow property references
     * @return OLAP object or property reference
     */
    public static Exp lookup(
        Query q,
        SchemaReader schemaReader,
        List<Id.Segment> segments,
        boolean allowProp)
    {
        // First, look for a calculated member defined in the query.
        final String fullName = quoteMdxIdentifier(segments);
        // Look for any kind of object (member, level, hierarchy,
        // dimension) in the cube. Use a schema reader without restrictions.
        final SchemaReader schemaReaderSansAc =
            schemaReader.withoutAccessControl().withLocus();
        final Cube cube = q.getCube();
        OlapElement olapElement =
            schemaReaderSansAc.lookupCompound(
                cube, segments, false, Category.Unknown);
        if (olapElement != null) {
            Role role = schemaReader.getRole();
            if (!role.canAccess(olapElement)) {
                olapElement = null;
            }
            if (olapElement instanceof Member) {
                olapElement =
                    schemaReader.substitute((Member) olapElement);
            }
        }
        if (olapElement == null) {
            if (allowProp && segments.size() > 1) {
                List<Id.Segment> segmentsButOne =
                    segments.subList(0, segments.size() - 1);
                final Id.Segment lastSegment = last(segments);
                final String propertyName =
                    lastSegment instanceof Id.NameSegment
                        ? ((Id.NameSegment) lastSegment).getName()
                        : null;
                final Member member =
                    (Member) schemaReaderSansAc.lookupCompound(
                        cube, segmentsButOne, false, Category.Member);
                if (member != null
                    && propertyName != null
                    && isValidProperty(propertyName, member.getLevel()))
                {
                    return new UnresolvedFunCall(
                        propertyName, Syntax.Property, new Exp[] {
                            createExpr(member)});
                }
                final Level level =
                    (Level) schemaReaderSansAc.lookupCompound(
                        cube, segmentsButOne, false, Category.Level);
                if (level != null
                    && propertyName != null
                    && isValidProperty(propertyName, level))
                {
                    return new UnresolvedFunCall(
                        propertyName, Syntax.Property, new Exp[] {
                            createExpr(level)});
                }
            }
            // if we're in the middle of loading the schema, the property has
            // been set to ignore invalid members, and the member is
            // non-existent, return the null member corresponding to the
            // hierarchy of the element we're looking for; locate the
            // hierarchy by incrementally truncating the name of the element
            if (q.ignoreInvalidMembers()) {
                int nameLen = segments.size() - 1;
                olapElement = null;
                while (nameLen > 0 && olapElement == null) {
                    List<Id.Segment> partialName =
                        segments.subList(0, nameLen);
                    olapElement = schemaReaderSansAc.lookupCompound(
                        cube, partialName, false, Category.Unknown);
                    nameLen--;
                }
                if (olapElement != null) {
                    olapElement = olapElement.getHierarchy().getNullMember();
                } else {
                    throw MondrianResource.instance().MdxChildObjectNotFound.ex(
                        fullName, cube.getQualifiedName());
                }
            } else {
                throw MondrianResource.instance().MdxChildObjectNotFound.ex(
                    fullName, cube.getQualifiedName());
            }
        }
        // keep track of any measure members referenced; these will be used
        // later to determine if cross joins on virtual cubes can be
        // processed natively
        q.addMeasuresMembers(olapElement);
        return createExpr(olapElement);
    }

    /**
     * Looks up a cube in a schema reader.
     *
     * @param cubeName Cube name
     * @param fail Whether to fail if not found.
     * @return Cube, or null if not found
     */
    static Cube lookupCube(
        SchemaReader schemaReader,
        String cubeName,
        boolean fail)
    {
        for (Cube cube : schemaReader.getCubes()) {
            if (Util.compareName(cube.getName(), cubeName) == 0) {
                return cube;
            }
        }
        if (fail) {
            throw MondrianResource.instance().MdxCubeNotFound.ex(cubeName);
        }
        return null;
    }

    /**
     * Converts an olap element (dimension, hierarchy, level or member) into
     * an expression representing a usage of that element in an MDX statement.
     */
    public static Exp createExpr(OlapElement element)
    {
        if (element instanceof Member) {
            Member member = (Member) element;
            return new MemberExpr(member);
        } else if (element instanceof Level) {
            Level level = (Level) element;
            return new LevelExpr(level);
        } else if (element instanceof Hierarchy) {
            Hierarchy hierarchy = (Hierarchy) element;
            return new HierarchyExpr(hierarchy);
        } else if (element instanceof Dimension) {
            Dimension dimension = (Dimension) element;
            return new DimensionExpr(dimension);
        } else if (element instanceof NamedSet) {
            NamedSet namedSet = (NamedSet) element;
            return new NamedSetExpr(namedSet);
        } else {
            throw Util.newInternal("Unexpected element type: " + element);
        }
    }

    public static Member lookupHierarchyRootMember(
        SchemaReader reader, Hierarchy hierarchy, Id.NameSegment memberName)
    {
        return lookupHierarchyRootMember(
            reader, hierarchy, memberName, MatchType.EXACT);
    }

    /**
     * Finds a root member of a hierarchy with a given name.
     *
     * @param hierarchy Hierarchy
     * @param memberName Name of root member
     * @return Member, or null if not found
     */
    public static Member lookupHierarchyRootMember(
        SchemaReader reader,
        Hierarchy hierarchy,
        Id.NameSegment memberName,
        MatchType matchType)
    {
        // Lookup member at first level.
        //
        // Don't use access control. Suppose we cannot see the 'nation' level,
        // we still want to be able to resolve '[Customer].[USA].[CA]'.
        List<Member> rootMembers = reader.getHierarchyRootMembers(hierarchy);

        // if doing an inexact search on a non-all hierarchy, create
        // a member corresponding to the name we're searching for so
        // we can use it in a hierarchical search
        Member searchMember = null;
        if (!matchType.isExact()
            && !hierarchy.hasAll()
            && !rootMembers.isEmpty())
        {
            searchMember =
                hierarchy.createMember(
                    null,
                    rootMembers.get(0).getLevel(),
                    memberName.name,
                    null);
        }

        int bestMatch = -1;
        int k = -1;
        for (Member rootMember : rootMembers) {
            ++k;
            int rc;
            // when searching on the ALL hierarchy, match must be exact
            if (matchType.isExact() || hierarchy.hasAll()) {
                rc = rootMember.getName().compareToIgnoreCase(memberName.name);
            } else {
                rc = FunUtil.compareSiblingMembers(
                    rootMember,
                    searchMember);
            }
            if (rc == 0) {
                return rootMember;
            }
            if (!hierarchy.hasAll()) {
                if (matchType == MatchType.BEFORE) {
                    if (rc < 0
                        && (bestMatch == -1
                            || FunUtil.compareSiblingMembers(
                                rootMember,
                                rootMembers.get(bestMatch)) > 0))
                    {
                        bestMatch = k;
                    }
                } else if (matchType == MatchType.AFTER) {
                    if (rc > 0
                        && (bestMatch == -1
                            || FunUtil.compareSiblingMembers(
                                rootMember,
                                rootMembers.get(bestMatch)) < 0))
                    {
                        bestMatch = k;
                    }
                }
            }
        }

        if (matchType == MatchType.EXACT_SCHEMA) {
            return null;
        }

        if (matchType != MatchType.EXACT && bestMatch != -1) {
            return rootMembers.get(bestMatch);
        }
        // If the first level is 'all', lookup member at second level. For
        // example, they could say '[USA]' instead of '[(All
        // Customers)].[USA]'.
        return (rootMembers.size() > 0 && rootMembers.get(0).isAll())
            ? reader.lookupMemberChildByName(
                rootMembers.get(0),
                memberName,
                matchType)
            : null;
    }

    /**
     * Finds a named level in this hierarchy. Returns null if there is no
     * such level.
     */
    public static Level lookupHierarchyLevel(Hierarchy hierarchy, String s) {
        final Level[] levels = hierarchy.getLevels();
        for (Level level : levels) {
            if (level.getName().equalsIgnoreCase(s)) {
                return level;
            }
        }
        return null;
    }



    /**
     * Finds the zero based ordinal of a Member among its siblings.
     */
    public static int getMemberOrdinalInParent(
        SchemaReader reader,
        Member member)
    {
        Member parent = member.getParentMember();
        List<Member> siblings =
            (parent == null)
            ? reader.getHierarchyRootMembers(member.getHierarchy())
            : reader.getMemberChildren(parent);

        for (int i = 0; i < siblings.size(); i++) {
            if (siblings.get(i).equals(member)) {
                return i;
            }
        }
        throw Util.newInternal(
            "could not find member " + member + " amongst its siblings");
    }

    /**
     * returns the first descendant on the level underneath parent.
     * If parent = [Time].[1997] and level = [Time].[Month], then
     * the member [Time].[1997].[Q1].[1] will be returned
     */
    public static Member getFirstDescendantOnLevel(
        SchemaReader reader,
        Member parent,
        Level level)
    {
        Member m = parent;
        while (m.getLevel() != level) {
            List<Member> children = reader.getMemberChildren(m);
            m = children.get(0);
        }
        return m;
    }

    /**
     * Returns whether a string is null or empty.
     */
    public static boolean isEmpty(String s) {
        return (s == null) || (s.length() == 0);
    }

    /**
     * Encloses a value in single-quotes, to make a SQL string value. Examples:
     * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>;
     * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>.
     */
    public static String singleQuoteString(String val) {
        StringBuilder buf = new StringBuilder(64);
        singleQuoteString(val, buf);
        return buf.toString();
    }

    /**
     * Encloses a value in single-quotes, to make a SQL string value. Examples:
     * <code>singleQuoteForSql(null)</code> yields <code>NULL</code>;
     * <code>singleQuoteForSql("don't")</code> yields <code>'don''t'</code>.
     */
    public static void singleQuoteString(String val, StringBuilder buf) {
        buf.append('\'');

        String s0 = replace(val, "'", "''");
        buf.append(s0);

        buf.append('\'');
    }

    /**
     * Creates a random number generator.
     *
     * @param seed Seed for random number generator.
     *   If 0, generate a seed from the system clock and print the value
     *   chosen. (This is effectively non-deterministic.)
     *   If -1, generate a seed from an internal random number generator.
     *   (This is deterministic, but ensures that different tests have
     *   different seeds.)
     *
     * @return A random number generator.
     */
    public static Random createRandom(long seed) {
        if (seed == 0) {
            seed = new Random().nextLong();
            System.out.println("random: seed=" + seed);
        } else if (seed == -1 && metaRandom != null) {
            seed = metaRandom.nextLong();
        }
        return new Random(seed);
    }

    /**
     * Returns whether a property is valid for a member of a given level.
     * It is valid if the property is defined at the level or at
     * an ancestor level, or if the property is a standard property such as
     * "FORMATTED_VALUE".
     *
     * @param propertyName Property name
     * @param level Level
     * @return Whether property is valid
     */
    public static boolean isValidProperty(
        String propertyName,
        Level level)
    {
        return lookupProperty(level, propertyName) != null;
    }

    /**
     * Finds a member property called <code>propertyName</code> at, or above,
     * <code>level</code>.
     */
    public static Property lookupProperty(
        Level level,
        String propertyName)
    {
        do {
            Property[] properties = level.getProperties();
            for (Property property : properties) {
                if (property.getName().equals(propertyName)) {
                    return property;
                }
            }
            level = level.getParentLevel();
        } while (level != null);
        // Now try a standard property.
        boolean caseSensitive =
            MondrianProperties.instance().CaseSensitive.get();
        final Property property = Property.lookup(propertyName, caseSensitive);
        if (property != null
            && property.isMemberProperty()
            && property.isStandard())
        {
            return property;
        }
        return null;
    }

    /**
     * Insert a call to this method if you want to flag a piece of
     * undesirable code.
     *
     * @deprecated
     */
    public static <T> T deprecated(T reason) {
        throw new UnsupportedOperationException(reason.toString());
    }

    /**
     * Insert a call to this method if you want to flag a piece of
     * undesirable code.
     *
     * @deprecated
     */
    public static <T> T deprecated(T reason, boolean fail) {
        if (fail) {
            throw new UnsupportedOperationException(reason.toString());
        } else {
            return reason;
        }
    }

    public static List<Member> addLevelCalculatedMembers(
        SchemaReader reader,
        Level level,
        List<Member> members)
    {
        List<Member> calcMembers =
            reader.getCalculatedMembers(level.getHierarchy());
        List<Member> calcMembersInThisLevel = new ArrayList<Member>();
        for (Member calcMember : calcMembers) {
            if (calcMember.getLevel().equals(level)) {
                calcMembersInThisLevel.add(calcMember);
            }
        }
        if (!calcMembersInThisLevel.isEmpty()) {
            List<Member> newMemberList =
                new ConcatenableList<Member>();
            newMemberList.addAll(members);
            newMemberList.addAll(calcMembersInThisLevel);
            return newMemberList;
        }
        return members;
    }

    /**
     * Returns an exception which indicates that a particular piece of
     * functionality should work, but a developer has not implemented it yet.
     */
    public static RuntimeException needToImplement(Object o) {
        throw new UnsupportedOperationException("need to implement " + o);
    }

    /**
     * Returns an exception indicating that we didn't expect to find this value
     * here.
     */
    public static <T extends Enum<T>> RuntimeException badValue(
        Enum<T> anEnum)
    {
        return Util.newInternal(
            "Was not expecting value '" + anEnum
            + "' for enumeration '" + anEnum.getDeclaringClass().getName()
            + "' in this context");
    }

    /**
     * Converts a list of SQL-style patterns into a Java regular expression.
     *
     * <p>For example, {"Foo_", "Bar%BAZ"} becomes "Foo.|Bar.*BAZ".
     *
     * @param wildcards List of SQL-style wildcard expressions
     * @return Regular expression
     */
    public static String wildcardToRegexp(List<String> wildcards) {
        StringBuilder buf = new StringBuilder();
        for (String value : wildcards) {
            if (buf.length() > 0) {
                buf.append('|');
            }
            int i = 0;
            while (true) {
                int percent = value.indexOf('%', i);
                int underscore = value.indexOf('_', i);
                if (percent == -1 && underscore == -1) {
                    if (i < value.length()) {
                        buf.append(quotePattern(value.substring(i)));
                    }
                    break;
                }
                if (underscore >= 0 && (underscore < percent || percent < 0)) {
                    if (i < underscore) {
                        buf.append(
                            quotePattern(value.substring(i, underscore)));
                    }
                    buf.append('.');
                    i = underscore + 1;
                } else if (percent >= 0
                    && (percent < underscore || underscore < 0))
                {
                    if (i < percent) {
                    buf.append(
                        quotePattern(value.substring(i, percent)));
                    }
                    buf.append(".*");
                    i = percent + 1;
                } else {
                    throw new IllegalArgumentException();
                }
            }
        }
        return buf.toString();
    }

    /**
     * Converts a camel-case name to an upper-case name with underscores.
     *
     * <p>For example, <code>camelToUpper("FooBar")</code> returns "FOO_BAR".
     *
     * @param s Camel-case string
     * @return  Upper-case string
     */
    public static String camelToUpper(String s) {
        StringBuilder buf = new StringBuilder(s.length() + 10);
        int prevUpper = -1;
        for (int i = 0; i < s.length(); ++i) {
            char c = s.charAt(i);
            if (Character.isUpperCase(c)) {
                if (i > prevUpper + 1) {
                    buf.append('_');
                }
                prevUpper = i;
            } else {
                c = Character.toUpperCase(c);
            }
            buf.append(c);
        }
        return buf.toString();
    }

    /**
     * Parses a comma-separated list.
     *
     * <p>If a value contains a comma, escape it with a second comma. For
     * example, <code>parseCommaList("x,y,,z")</code> returns
     * <code>{"x", "y,z"}</code>.
     *
     * @param nameCommaList List of names separated by commas
     * @return List of names
     */
    public static List<String> parseCommaList(String nameCommaList) {
        if (nameCommaList.equals("")) {
            return Collections.emptyList();
        }
        if (nameCommaList.endsWith(",")) {
            // Special treatment for list ending in ",", because split ignores
            // entries after separator.
            final String zzz = "zzz";
            final List<String> list = parseCommaList(nameCommaList + zzz);
            String last = list.get(list.size() - 1);
            if (last.equals(zzz)) {
                list.remove(list.size() - 1);
            } else {
                list.set(
                    list.size() - 1,
                    last.substring(0, last.length() - zzz.length()));
            }
            return list;
        }
        List<String> names = new ArrayList<String>();
        final String[] strings = nameCommaList.split(",");
        for (String string : strings) {
            final int count = names.size();
            if (count > 0
                && names.get(count - 1).equals(""))
            {
                if (count == 1) {
                    if (string.equals("")) {
                        names.add("");
                    } else {
                        names.set(
                            0,
                            "," + string);
                    }
                } else {
                    names.set(
                        count - 2,
                        names.get(count - 2) + "," + string);
                    names.remove(count - 1);
                }
            } else {
                names.add(string);
            }
        }
        return names;
    }

    /**
     * Returns an annotation of a particular class on a method. Returns the
     * default value if the annotation is not present, or in JDK 1.4.
     *
     * @param method Method containing annotation
     * @param annotationClassName Name of annotation class to find
     * @param defaultValue Value to return if annotation is not present
     * @return value of annotation
     */
    public static <T> T getAnnotation(
        Method method,
        String annotationClassName,
        T defaultValue)
    {
        return compatible.getAnnotation(
            method, annotationClassName, defaultValue);
    }

    /**
     * Closes and cancels a {@link Statement} using the correct methods
     * available on the current Java runtime.
     * <p>If errors are encountered while canceling a statement,
     * the message is logged in {@link Util}.
     * @param stmt The statement to cancel.
     */
    public static void cancelStatement(Statement stmt) {
        compatible.cancelStatement(stmt);
    }

    public static MemoryInfo getMemoryInfo() {
        return compatible.getMemoryInfo();
    }

    /**
     * Converts a list of a string.
     *
     * For example,
     * <code>commaList("foo", Arrays.asList({"a", "b"}))</code>
     * returns "foo(a, b)".
     *
     * @param s Prefix
     * @param list List
     * @return String representation of string
     */
    public static <T> String commaList(
        String s,
        List<T> list)
    {
        final StringBuilder buf = new StringBuilder(s);
        buf.append("(");
        int k = -1;
        for (T t : list) {
            if (++k > 0) {
                buf.append(", ");
            }
            buf.append(t);
        }
        buf.append(")");
        return buf.toString();
    }

    /**
     * Makes a name distinct from other names which have already been used
     * and shorter than a length limit, adds it to the list, and returns it.
     *
     * @param name Suggested name, may not be unique
     * @param maxLength Maximum length of generated name
     * @param nameList Collection of names already used
     *
     * @return Unique name
     */
    public static String uniquify(
        String name,
        int maxLength,
        Collection<String> nameList)
    {
        assert name != null;
        if (name.length() > maxLength) {
            name = name.substring(0, maxLength);
        }
        if (nameList.contains(name)) {
            String aliasBase = name;
            int j = 0;
            while (true) {
                name = aliasBase + j;
                if (name.length() > maxLength) {
                    aliasBase = aliasBase.substring(0, aliasBase.length() - 1);
                    continue;
                }
                if (!nameList.contains(name)) {
                    break;
                }
                j++;
            }
        }
        nameList.add(name);
        return name;
    }

    /**
     * Returns whether a collection contains precisely one distinct element.
     * Returns false if the collection is empty, or if it contains elements
     * that are not the same as each other.
     *
     * @param collection Collection
     * @return boolean true if all values are same
     */
    public static <T> boolean areOccurencesEqual(
        Collection<T> collection)
    {
        Iterator<T> it = collection.iterator();
        if (!it.hasNext()) {
            // Collection is empty
            return false;
        }
        T first = it.next();
        while (it.hasNext()) {
            T t = it.next();
            if (!t.equals(first)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Creates a memory-, CPU- and cache-efficient immutable list.
     *
     * @param t Array of members of list
     * @param <T> Element type
     * @return List containing the given members
     */
    public static <T> List<T> flatList(T... t) {
        return _flatList(t, false);
    }

    /**
     * Creates a memory-, CPU- and cache-efficient immutable list,
     * always copying the contents.
     *
     * @param t Array of members of list
     * @param <T> Element type
     * @return List containing the given members
     */
    public static <T> List<T> flatListCopy(T... t) {
        return _flatList(t, true);
    }

    /**
     * Creates a memory-, CPU- and cache-efficient immutable list, optionally
     * copying the list.
     *
     * @param copy Whether to always copy the list
     * @param t Array of members of list
     * @return List containing the given members
     */
    private static <T> List<T> _flatList(T[] t, boolean copy) {
        switch (t.length) {
        case 0:
            return Collections.emptyList();
        case 1:
            return Collections.singletonList(t[0]);
        case 2:
            return new Flat2List<T>(t[0], t[1]);
        case 3:
            return new Flat3List<T>(t[0], t[1], t[2]);
        default:
            // REVIEW: AbstractList contains a modCount field; we could
            //   write our own implementation and reduce creation overhead a
            //   bit.
            if (copy) {
                return Arrays.asList(t.clone());
            } else {
                return Arrays.asList(t);
            }
        }
    }

    /**
     * Creates a memory-, CPU- and cache-efficient immutable list from an
     * existing list. The list is always copied.
     *
     * @param t Array of members of list
     * @param <T> Element type
     * @return List containing the given members
     */
    public static <T> List<T> flatList(List<T> t) {
        switch (t.size()) {
        case 0:
            return Collections.emptyList();
        case 1:
            return Collections.singletonList(t.get(0));
        case 2:
            return new Flat2List<T>(t.get(0), t.get(1));
        case 3:
            return new Flat3List<T>(t.get(0), t.get(1), t.get(2));
        default:
            // REVIEW: AbstractList contains a modCount field; we could
            //   write our own implementation and reduce creation overhead a
            //   bit.
            //noinspection unchecked
            return (List<T>) Arrays.asList(t.toArray());
        }
    }

    /**
     * Parses a locale string.
     *
     * <p>The inverse operation of {@link java.util.Locale#toString()}.
     *
     * @param localeString Locale string, e.g. "en" or "en_US"
     * @return Java locale object
     */
    public static Locale parseLocale(String localeString) {
        String[] strings = localeString.split("_");
        switch (strings.length) {
        case 1:
            return new Locale(strings[0]);
        case 2:
            return new Locale(strings[0], strings[1]);
        case 3:
            return new Locale(strings[0], strings[1], strings[2]);
        default:
            throw newInternal(
                "bad locale string '" + localeString + "'");
        }
    }

    private static final Map<String, String> TIME_UNITS =
        Olap4jUtil.mapOf(
            "ns", "NANOSECONDS",
            "us", "MICROSECONDS",
            "ms", "MILLISECONDS",
            "s", "SECONDS",
            "m", "MINUTES",
            "h", "HOURS",
            "d", "DAYS");

    /**
     * Parses an interval.
     *
     * <p>For example, "30s" becomes (30, {@link TimeUnit#SECONDS});
     * "2us" becomes (2, {@link TimeUnit#MICROSECONDS}).</p>
     *
     * <p>Units m (minutes), h (hours) and d (days) are only available
     * in JDK 1.6 or later, because the corresponding constants are missing
     * from {@link TimeUnit} in JDK 1.5.</p>
     *
     * @param s String to parse
     * @param unit Default time unit; may be null
     *
     * @return Pair of value and time unit. Neither pair or its components are
     * null
     *
     * @throws NumberFormatException if unit is not present and there is no
     * default, or if number is not valid
     */
    public static Pair<Long, TimeUnit> parseInterval(
        String s,
        TimeUnit unit)
        throws NumberFormatException
    {
        final String original = s;
        for (Map.Entry<String, String> entry : TIME_UNITS.entrySet()) {
            final String abbrev = entry.getKey();
            if (s.endsWith(abbrev)) {
                final String full = entry.getValue();
                try {
                    unit = TimeUnit.valueOf(full);
                    s = s.substring(0, s.length() - abbrev.length());
                    break;
                } catch (IllegalArgumentException e) {
                    // ignore - MINUTES, HOURS, DAYS are not defined in JDK1.5
                }
            }
        }
        if (unit == null) {
            throw new NumberFormatException(
                "Invalid time interval '" + original + "'. Does not contain a "
                + "time unit. (Suffix may be ns (nanoseconds), "
                + "us (microseconds), ms (milliseconds), s (seconds), "
                + "h (hours), d (days). For example, '20s' means 20 seconds.)");
        }
        try {
            return Pair.of(new BigDecimal(s).longValue(), unit);
        } catch (NumberFormatException e) {
            throw new NumberFormatException(
                "Invalid time interval '" + original + "'");
        }
    }

    /**
     * Converts a list of olap4j-style segments to a list of mondrian-style
     * segments.
     *
     * @param olap4jSegmentList List of olap4j segments
     * @return List of mondrian segments
     */
    public static List<Id.Segment> convert(
        List<IdentifierSegment> olap4jSegmentList)
    {
        final List<Id.Segment> list = new ArrayList<Id.Segment>();
        for (IdentifierSegment olap4jSegment : olap4jSegmentList) {
            list.add(convert(olap4jSegment));
        }
        return list;
    }

    /**
     * Converts an olap4j-style segment to a mondrian-style segment.
     *
     * @param olap4jSegment olap4j segment
     * @return mondrian segment
     */
    public static Id.Segment convert(IdentifierSegment olap4jSegment) {
        if (olap4jSegment instanceof NameSegment) {
            return convert((NameSegment) olap4jSegment);
        } else {
            return convert((KeySegment) olap4jSegment);
        }
    }

    private static Id.KeySegment convert(final KeySegment keySegment) {
        return new Id.KeySegment(
            new AbstractList<Id.NameSegment>() {
                public Id.NameSegment get(int index) {
                    return convert(keySegment.getKeyParts().get(index));
                }

                public int size() {
                    return keySegment.getKeyParts().size();
                }
            });
    }

    private static Id.NameSegment convert(NameSegment nameSegment) {
        return new Id.NameSegment(
            nameSegment.getName(),
            convert(nameSegment.getQuoting()));
    }

    private static Id.Quoting convert(Quoting quoting) {
        switch (quoting) {
        case QUOTED:
            return Id.Quoting.QUOTED;
        case UNQUOTED:
            return Id.Quoting.UNQUOTED;
        case KEY:
            return Id.Quoting.KEY;
        default:
            throw Util.unexpected(quoting);
        }
    }

    /**
     * Applies a collection of filters to an iterable.
     *
     * @param iterable Iterable
     * @param conds Zero or more conditions
     * @param <T>
     * @return Iterable that returns only members of underlying iterable for
     *     for which all conditions evaluate to true
     */
    public static <T> Iterable<T> filter(
        final Iterable<T> iterable,
        final Functor1<Boolean, T>... conds)
    {
        final Functor1<Boolean, T>[] conds2 = optimizeConditions(conds);
        if (conds2.length == 0) {
            return iterable;
        }
        return new Iterable<T>() {
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    final Iterator<T> iterator = iterable.iterator();
                    T next;
                    boolean hasNext = moveToNext();

                    private boolean moveToNext() {
                        outer:
                        while (iterator.hasNext()) {
                            next = iterator.next();
                            for (Functor1<Boolean, T> cond : conds2) {
                                if (!cond.apply(next)) {
                                    continue outer;
                                }
                            }
                            return true;
                        }
                        return false;
                    }

                    public boolean hasNext() {
                        return hasNext;
                    }

                    public T next() {
                        T t = next;
                        hasNext = moveToNext();
                        return t;
                    }

                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        };
    }

    private static <T> Functor1<Boolean, T>[] optimizeConditions(
        Functor1<Boolean, T>[] conds)
    {
        final List<Functor1<Boolean, T>> functor1List =
            new ArrayList<Functor1<Boolean, T>>(Arrays.asList(conds));
        for (Iterator<Functor1<Boolean, T>> funcIter =
            functor1List.iterator(); funcIter.hasNext();)
        {
            Functor1<Boolean, T> booleanTFunctor1 = funcIter.next();
            if (booleanTFunctor1 == trueFunctor()) {
                funcIter.remove();
            }
        }
        if (functor1List.size() < conds.length) {
            //noinspection unchecked
            return functor1List.toArray(new Functor1[functor1List.size()]);
        } else {
            return conds;
        }
    }

    /**
     * Sorts a collection of {@link Comparable} objects and returns a list.
     *
     * @param collection Collection
     * @param <T> Element type
     * @return Sorted list
     */
    public static <T extends Comparable> List<T> sort(
        Collection<T> collection)
    {
        Object[] a = collection.toArray(new Object[collection.size()]);
        Arrays.sort(a);
        return cast(Arrays.asList(a));
    }

    /**
     * Sorts a collection of objects using a {@link java.util.Comparator} and returns a
     * list.
     *
     * @param collection Collection
     * @param comparator Comparator
     * @param <T> Element type
     * @return Sorted list
     */
    public static <T> List<T> sort(
        Collection<T> collection,
        Comparator<T> comparator)
    {
        Object[] a = collection.toArray(new Object[collection.size()]);
        //noinspection unchecked
        Arrays.sort(a, (Comparator<? super Object>) comparator);
        return cast(Arrays.asList(a));
    }

    public static List<IdentifierSegment> toOlap4j(
        final List<Id.Segment> segments)
    {
        return new AbstractList<IdentifierSegment>() {
            public IdentifierSegment get(int index) {
                return toOlap4j(segments.get(index));
            }

            public int size() {
                return segments.size();
            }
        };
    }

    public static IdentifierSegment toOlap4j(Id.Segment segment) {
        switch (segment.quoting) {
        case KEY:
            return toOlap4j((Id.KeySegment) segment);
        default:
            return toOlap4j((Id.NameSegment) segment);
        }
    }

    private static KeySegment toOlap4j(final Id.KeySegment keySegment) {
        return new KeySegment(
            new AbstractList<NameSegment>() {
                public NameSegment get(int index) {
                    return toOlap4j(keySegment.subSegmentList.get(index));
                }

                public int size() {
                    return keySegment.subSegmentList.size();
                }
            });
    }

    private static NameSegment toOlap4j(Id.NameSegment nameSegment) {
        return new NameSegment(
            null,
            nameSegment.name,
            toOlap4j(nameSegment.quoting));
    }

    public static Quoting toOlap4j(Id.Quoting quoting) {
        return Quoting.valueOf(quoting.name());
    }

    // TODO: move to IdentifierSegment
    public static boolean matches(IdentifierSegment segment, String name) {
        switch (segment.getQuoting()) {
        case KEY:
            return false; // FIXME
        case QUOTED:
            return equalName(segment.getName(), name);
        case UNQUOTED:
            return segment.getName().equalsIgnoreCase(name);
        default:
            throw unexpected(segment.getQuoting());
        }
    }

    public static RuntimeException newElementNotFoundException(
        int category,
        IdentifierNode identifierNode)
    {
        String type;
        switch (category) {
        case Category.Member:
            return MondrianResource.instance().MemberNotFound.ex(
                identifierNode.toString());
        case Category.Unknown:
            type = "Element";
            break;
        default:
            type = Category.instance().getDescription(category);
        }
        return newError(type + " '" + identifierNode + "' not found");
    }

    /**
     * Calls {@link java.util.concurrent.Future#get()} and converts any
     * throwable into a non-checked exception.
     *
     * @param future Future
     * @param message Message to qualify wrapped exception
     * @param <T> Result type
     * @return Result
     */
    public static <T> T safeGet(Future<T> future, String message) {
        try {
            return future.get();
        } catch (InterruptedException e) {
            throw newError(e, message);
        } catch (ExecutionException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            } else if (cause instanceof Error) {
                throw (Error) cause;
            } else {
                throw newError(cause, message);
            }
        }
    }

    public static <T> Set<T> newIdentityHashSetFake() {
        final HashMap<T, Boolean> map = new HashMap<T, Boolean>();
        return new Set<T>() {
            public int size() {
                return map.size();
            }

            public boolean isEmpty() {
                return map.isEmpty();
            }

            public boolean contains(Object o) {
                return map.containsKey(o);
            }

            public Iterator<T> iterator() {
                return map.keySet().iterator();
            }

            public Object[] toArray() {
                return map.keySet().toArray();
            }

            public <T> T[] toArray(T[] a) {
                return map.keySet().toArray(a);
            }

            public boolean add(T t) {
                return map.put(t, Boolean.TRUE) == null;
            }

            public boolean remove(Object o) {
                return map.remove(o) == Boolean.TRUE;
            }

            public boolean containsAll(Collection<?> c) {
                return map.keySet().containsAll(c);
            }

            public boolean addAll(Collection<? extends T> c) {
                throw new UnsupportedOperationException();
            }

            public boolean retainAll(Collection<?> c) {
                throw new UnsupportedOperationException();
            }

            public boolean removeAll(Collection<?> c) {
                throw new UnsupportedOperationException();
            }

            public void clear() {
                map.clear();
            }
        };
    }

    /**
     * Equivalent to {@link Timer#Timer(String, boolean)}.
     * (Introduced in JDK 1.5.)
     *
     * @param name the name of the associated thread
     * @param isDaemon true if the associated thread should run as a daemon
     * @return timer
     */
    public static Timer newTimer(String name, boolean isDaemon) {
        return compatible.newTimer(name, isDaemon);
    }

    /**
     * As Arrays#binarySearch(Object[], int, int, Object), but
     * available pre-JDK 1.6.
     */
    public static <T extends Comparable<T>> int binarySearch(
        T[] ts, int start, int end, T t)
    {
        return compatible.binarySearch(ts, start, end, t);
    }

    /**
     * Returns the intersection of two sorted sets. Does not modify either set.
     *
     * <p>Optimized for the case that both sets are {@link ArraySortedSet}.</p>
     *
     * @param set1 First set
     * @param set2 Second set
     * @return Intersection of the sets
     */
    public static <E extends Comparable> SortedSet<E> intersect(
        SortedSet<E> set1,
        SortedSet<E> set2)
    {
        if (set1.isEmpty()) {
            return set1;
        }
        if (set2.isEmpty()) {
            return set2;
        }
        if (!(set1 instanceof ArraySortedSet)
            || !(set2 instanceof ArraySortedSet))
        {
            final TreeSet<E> set = new TreeSet<E>(set1);
            set.retainAll(set2);
            return set;
        }
        final Comparable<?>[] result =
            new Comparable[Math.min(set1.size(), set2.size())];
        final Iterator<E> it1 = set1.iterator();
        final Iterator<E> it2 = set2.iterator();
        int i = 0;
        E e1 = it1.next();
        E e2 = it2.next();
        for (;;) {
            final int compare = e1.compareTo(e2);
            if (compare == 0) {
                result[i++] = e1;
                if (!it1.hasNext() || !it2.hasNext()) {
                    break;
                }
                e1 = it1.next();
                e2 = it2.next();
            } else if (compare == 1) {
                if (!it2.hasNext()) {
                    break;
                }
                e2 = it2.next();
            } else {
                if (!it1.hasNext()) {
                    break;
                }
                e1 = it1.next();
            }
        }
        return new ArraySortedSet(result, 0, i);
    }

    /**
     * Compares two integers using the same algorithm as
     * {@link Integer#compareTo(Integer)}.
     *
     * @param i0 First integer
     * @param i1 Second integer
     * @return Comparison
     */
    public static int compareIntegers(int i0, int i1) {
        return (i0 < i1 ? -1 : (i0 == i1 ? 0 : 1));
    }

    /**
     * Returns the last item in a list.
     *
     * @param list List
     * @param <T> Element type
     * @return Last item in the list
     * @throws IndexOutOfBoundsException if list is empty
     */
    public static <T> T last(List<T> list) {
        return list.get(list.size() - 1);
    }

    /**
     * Returns the sole item in a list.
     *
     * <p>If the list has 0 or more than one element, throws.</p>
     *
     * @param list List
     * @param <T> Element type
     * @return Sole item in the list
     * @throws IndexOutOfBoundsException if list is empty or has more than 1 elt
     */
    public static <T> T only(List<T> list) {
        if (list.size() != 1) {
            throw new IndexOutOfBoundsException(
                "list " + list + " has " + list.size()
                + " elements, expected 1");
        }
        return list.get(0);
    }

    /**
     * Closes a JDBC result set, statement, and connection, ignoring any errors.
     * If any of them are null, that's fine.
     *
     * <p>If any of them throws a {@link SQLException}, returns the first
     * such exception, but always executes all closes.</p>
     *
     * @param resultSet Result set
     * @param statement Statement
     * @param connection Connection
     */
    public static SQLException close(
        ResultSet resultSet,
        Statement statement,
        Connection connection)
    {
        SQLException firstException = null;
        if (resultSet != null) {
            try {
                if (statement == null) {
                    statement = resultSet.getStatement();
                }
                resultSet.close();
            } catch (Throwable t) {
                firstException = new SQLException();
                firstException.initCause(t);
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (Throwable t) {
                if (firstException == null) {
                    firstException = new SQLException();
                    firstException.initCause(t);
                }
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (Throwable t) {
                if (firstException == null) {
                    firstException = new SQLException();
                    firstException.initCause(t);
                }
            }
        }
        return firstException;
    }

    /**
     * Creates a bitset with bits from {@code fromIndex} (inclusive) to
     * specified {@code toIndex} (exclusive) set to {@code true}.
     *
     * <p>For example, {@code bitSetBetween(0, 3)} returns a bit set with bits
     * {0, 1, 2} set.
     *
     * @param fromIndex Index of the first bit to be set.
     * @param toIndex   Index after the last bit to be set.
     * @return Bit set
     */
    public static BitSet bitSetBetween(int fromIndex, int toIndex) {
        final BitSet bitSet = new BitSet();
        if (toIndex > fromIndex) {
            // Avoid http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6222207
            // "BitSet internal invariants may be violated"
            bitSet.set(fromIndex, toIndex);
        }
        return bitSet;
    }

    public static class ErrorCellValue {
        public String toString() {
            return "#ERR";
        }
    }

    @SuppressWarnings({"unchecked"})
    public static <T> T[] genericArray(Class<T> clazz, int size) {
        return (T[]) Array.newInstance(clazz, size);
    }

    /**
     * Throws an internal error if condition is not true. It would be called
     * <code>assert</code>, but that is a keyword as of JDK 1.4.
     */
    public static void assertTrue(boolean b) {
        if (!b) {
            throw newInternal("assert failed");
        }
    }

    /**
     * Throws an internal error with the given messagee if condition is not
     * true. It would be called <code>assert</code>, but that is a keyword as
     * of JDK 1.4.
     */
    public static void assertTrue(boolean b, String message) {
        if (!b) {
            throw newInternal("assert failed: " + message);
        }
    }

    /**
     * Creates an internal error with a given message.
     */
    public static RuntimeException newInternal(String message) {
        return MondrianResource.instance().Internal.ex(message);
    }

    /**
     * Creates an internal error with a given message and cause.
     */
    public static RuntimeException newInternal(Throwable e, String message) {
        return MondrianResource.instance().Internal.ex(message, e);
    }

    /**
     * Creates a non-internal error. Currently implemented in terms of
     * internal errors, but later we will create resourced messages.
     */
    public static RuntimeException newError(String message) {
        return newInternal(message);
    }

    /**
     * Creates a non-internal error. Currently implemented in terms of
     * internal errors, but later we will create resourced messages.
     */
    public static RuntimeException newError(Throwable e, String message) {
        return newInternal(e, message);
    }

    /**
     * Returns an exception indicating that we didn't expect to find this value
     * here.
     *
     * @param value Value
     */
    public static RuntimeException unexpected(Enum value) {
        return Util.newInternal(
            "Was not expecting value '" + value
            + "' for enumeration '" + value.getClass().getName()
            + "' in this context");
    }

    /**
     * Checks that a precondition (declared using the javadoc <code>@pre</code>
     * tag) is satisfied.
     *
     * @param b The value of executing the condition
     */
    public static void assertPrecondition(boolean b) {
        assertTrue(b);
    }

    /**
     * Checks that a precondition (declared using the javadoc <code>@pre</code>
     * tag) is satisfied. For example,
     *
     * <blockquote><pre>void f(String s) {
     *    Util.assertPrecondition(s != null, "s != null");
     *    ...
     * }</pre></blockquote>
     *
     * @param b The value of executing the condition
     * @param condition The text of the condition
     */
    public static void assertPrecondition(boolean b, String condition) {
        assertTrue(b, condition);
    }

    /**
     * Checks that a postcondition (declared using the javadoc
     * <code>@post</code> tag) is satisfied.
     *
     * @param b The value of executing the condition
     */
    public static void assertPostcondition(boolean b) {
        assertTrue(b);
    }

    /**
     * Checks that a postcondition (declared using the javadoc
     * <code>@post</code> tag) is satisfied.
     *
     * @param b The value of executing the condition
     */
    public static void assertPostcondition(boolean b, String condition) {
        assertTrue(b, condition);
    }

    /**
     * Converts an error into an array of strings, the most recent error first.
     *
     * @param e the error; may be null. Errors are chained according to their
     *    {@link Throwable#getCause cause}.
     */
    public static String[] convertStackToString(Throwable e) {
        List<String> list = new ArrayList<String>();
        while (e != null) {
            String sMsg = getErrorMessage(e);
            list.add(sMsg);
            e = e.getCause();
        }
        return list.toArray(new String[list.size()]);
    }

    /**
     * Constructs the message associated with an arbitrary Java error, making
     * up one based on the stack trace if there is none. As
     * {@link #getErrorMessage(Throwable,boolean)}, but does not print the
     * class name if the exception is derived from {@link java.sql.SQLException}
     * or is exactly a {@link java.lang.Exception}.
     */
    public static String getErrorMessage(Throwable err) {
        boolean prependClassName =
            !(err instanceof java.sql.SQLException
              || err.getClass() == java.lang.Exception.class);
        return getErrorMessage(err, prependClassName);
    }

    /**
     * Constructs the message associated with an arbitrary Java error, making
     * up one based on the stack trace if there is none.
     *
     * @param err the error
     * @param prependClassName should the error be preceded by the
     *   class name of the Java exception?  defaults to false, unless the error
     *   is derived from {@link java.sql.SQLException} or is exactly a {@link
     *   java.lang.Exception}
     */
    public static String getErrorMessage(
        Throwable err,
        boolean prependClassName)
    {
        String errMsg = err.getMessage();
        if ((errMsg == null) || (err instanceof RuntimeException)) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            err.printStackTrace(pw);
            return sw.toString();
        } else {
            return (prependClassName)
                ? err.getClass().getName() + ": " + errMsg
                : errMsg;
        }
    }

    /**
     * If one of the causes of an exception is of a particular class, returns
     * that cause. Otherwise returns null.
     *
     * @param e Exception
     * @param clazz Desired class
     * @param <T> Class
     * @return Cause of given class, or null
     */
    public static <T extends Throwable>
    T getMatchingCause(Throwable e, Class<T> clazz) {
        for (;;) {
            if (clazz.isInstance(e)) {
                return clazz.cast(e);
            }
            final Throwable cause = e.getCause();
            if (cause == null || cause == e) {
                return null;
            }
            e = cause;
        }
    }

    /**
     * Converts an expression to a string.
     */
    public static String unparse(Exp exp) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        exp.unparse(pw);
        return sw.toString();
    }

    /**
     * Converts an query to a string.
     */
    public static String unparse(Query query) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new QueryPrintWriter(sw);
        query.unparse(pw);
        return sw.toString();
    }

    /**
     * Creates a file-protocol URL for the given file.
     */
    public static URL toURL(File file) throws MalformedURLException {
        String path = file.getAbsolutePath();
        // This is a bunch of weird code that is required to
        // make a valid URL on the Windows platform, due
        // to inconsistencies in what getAbsolutePath returns.
        String fs = System.getProperty("file.separator");
        if (fs.length() == 1) {
            char sep = fs.charAt(0);
            if (sep != '/') {
                path = path.replace(sep, '/');
            }
            if (path.charAt(0) != '/') {
                path = '/' + path;
            }
        }
        path = "file://" + path;
        return new URL(path);
    }

    /**
     * <code>PropertyList</code> is an order-preserving list of key-value
     * pairs. Lookup is case-insensitive, but the case of keys is preserved.
     */
    public static class PropertyList
        implements Iterable<Pair<String, String>>, Serializable
    {
        List<Pair<String, String>> list =
            new ArrayList<Pair<String, String>>();

        public PropertyList() {
            this.list = new ArrayList<Pair<String, String>>();
        }

        private PropertyList(List<Pair<String, String>> list) {
            this.list = list;
        }

        @SuppressWarnings({"CloneDoesntCallSuperClone"})
        @Override
        public PropertyList clone() {
            return new PropertyList(new ArrayList<Pair<String, String>>(list));
        }

        public String get(String key) {
            return get(key, null);
        }

        public String get(String key, String defaultValue) {
            for (int i = 0, n = list.size(); i < n; i++) {
                Pair<String, String> pair = list.get(i);
                if (pair.left.equalsIgnoreCase(key)) {
                    return pair.right;
                }
            }
            return defaultValue;
        }

        public String put(String key, String value) {
            for (int i = 0, n = list.size(); i < n; i++) {
                Pair<String, String> pair = list.get(i);
                if (pair.left.equalsIgnoreCase(key)) {
                    String old = pair.right;
                    if (key.equalsIgnoreCase("Provider")) {
                        // Unlike all other properties, later values of
                        // "Provider" do not supersede
                    } else {
                        pair.right = value;
                    }
                    return old;
                }
            }
            list.add(new Pair<String, String>(key, value));
            return null;
        }

        public boolean remove(String key) {
            boolean found = false;
            for (int i = 0; i < list.size(); i++) {
                Pair<String, String> pair = list.get(i);
                if (pair.getKey().equalsIgnoreCase(key)) {
                    list.remove(i);
                    found = true;
                    --i;
                }
            }
            return found;
        }

        public String toString() {
            StringBuilder sb = new StringBuilder(64);
            for (int i = 0, n = list.size(); i < n; i++) {
                Pair<String, String> pair = list.get(i);
                if (i > 0) {
                    sb.append("; ");
                }
                sb.append(pair.left);
                sb.append('=');

                final String right = pair.right;
                if (right == null) {
                    sb.append("'null'");
                } else {
                    // Quote a property value if is has a semi colon in it
                    // 'xxx;yyy'. Escape any single-quotes by doubling them.
                    final int needsQuote = right.indexOf(';');
                    if (needsQuote >= 0) {
                        // REVIEW: This logic leaves off the leading/trailing
                        //   quote if the property value already has a
                        //   leading/trailing quote. Doesn't seem right to me.
                        if (right.charAt(0) != '\'') {
                            sb.append("'");
                        }
                        sb.append(replace(right, "'", "''"));
                        if (right.charAt(right.length() - 1) != '\'') {
                            sb.append("'");
                        }
                    } else {
                        sb.append(right);
                    }
                }
            }
            return sb.toString();
        }

        public Iterator<Pair<String, String>> iterator() {
            return list.iterator();
        }
    }

    /**
     * Converts an OLE DB connect string into a {@link PropertyList}.
     *
     * <p> For example, <code>"Provider=MSOLAP; DataSource=LOCALHOST;"</code>
     * becomes the set of (key, value) pairs <code>{("Provider","MSOLAP"),
     * ("DataSource", "LOCALHOST")}</code>. Another example is
     * <code>Provider='sqloledb';Data Source='MySqlServer';Initial
     * Catalog='Pubs';Integrated Security='SSPI';</code>.
     *
     * <p> This method implements as much as possible of the <a
     * href="http://msdn.microsoft.com/library/en-us/oledb/htm/oledbconnectionstringsyntax.asp"
     * target="_blank">OLE DB connect string syntax
     * specification</a>. To find what it <em>actually</em> does, take
     * a look at the <code>mondrian.olap.UtilTestCase</code> test case.
     */
    public static PropertyList parseConnectString(String s) {
        return new ConnectStringParser(s).parse();
    }

    private static class ConnectStringParser {
        private final String s;
        private final int n;
        private int i;
        private final StringBuilder nameBuf;
        private final StringBuilder valueBuf;

        private ConnectStringParser(String s) {
            this.s = s;
            this.i = 0;
            this.n = s.length();
            this.nameBuf = new StringBuilder(64);
            this.valueBuf = new StringBuilder(64);
        }

        PropertyList parse() {
            PropertyList list = new PropertyList();
            while (i < n) {
                parsePair(list);
            }
            return list;
        }
        /**
         * Reads "name=value;" or "name=value<EOF>".
         */
        void parsePair(PropertyList list) {
            String name = parseName();
            if (name == null) {
                return;
            }
            String value;
            if (i >= n) {
                value = "";
            } else if (s.charAt(i) == ';') {
                i++;
                value = "";
            } else {
                value = parseValue();
            }
            list.put(name, value);
        }

        /**
         * Reads "name=". Name can contain equals sign if equals sign is
         * doubled. Returns null if there is no name to read.
         */
        String parseName() {
            nameBuf.setLength(0);
            while (true) {
                char c = s.charAt(i);
                switch (c) {
                case '=':
                    i++;
                    if (i < n && (c = s.charAt(i)) == '=') {
                        // doubled equals sign; take one of them, and carry on
                        i++;
                        nameBuf.append(c);
                        break;
                    }
                    String name = nameBuf.toString();
                    name = name.trim();
                    return name;
                case ' ':
                    if (nameBuf.length() == 0) {
                        // ignore preceding spaces
                        i++;
                        if (i >= n) {
                            // there is no name, e.g. trailing spaces after
                            // semicolon, 'x=1; y=2; '
                            return null;
                        }
                        break;
                    } else {
                        // fall through
                    }
                default:
                    nameBuf.append(c);
                    i++;
                    if (i >= n) {
                        return nameBuf.toString().trim();
                    }
                }
            }
        }

        /**
         * Reads "value;" or "value<EOF>"
         */
        String parseValue() {
            char c;
            // skip over leading white space
            while ((c = s.charAt(i)) == ' ') {
                i++;
                if (i >= n) {
                    return "";
                }
            }
            if (c == '"' || c == '\'') {
                String value = parseQuoted(c);
                // skip over trailing white space
                while (i < n && (c = s.charAt(i)) == ' ') {
                    i++;
                }
                if (i >= n) {
                    return value;
                } else if (s.charAt(i) == ';') {
                    i++;
                    return value;
                } else {
                    throw new RuntimeException(
                        "quoted value ended too soon, at position " + i
                        + " in '" + s + "'");
                }
            } else {
                String value;
                int semi = s.indexOf(';', i);
                if (semi >= 0) {
                    value = s.substring(i, semi);
                    i = semi + 1;
                } else {
                    value = s.substring(i);
                    i = n;
                }
                return value.trim();
            }
        }
        /**
         * Reads a string quoted by a given character. Occurrences of the
         * quoting character must be doubled. For example,
         * <code>parseQuoted('"')</code> reads <code>"a ""new"" string"</code>
         * and returns <code>a "new" string</code>.
         */
        String parseQuoted(char q) {
            char c = s.charAt(i++);
            Util.assertTrue(c == q);
            valueBuf.setLength(0);
            while (i < n) {
                c = s.charAt(i);
                if (c == q) {
                    i++;
                    if (i < n) {
                        c = s.charAt(i);
                        if (c == q) {
                            valueBuf.append(c);
                            i++;
                            continue;
                        }
                    }
                    return valueBuf.toString();
                } else {
                    valueBuf.append(c);
                    i++;
                }
            }
            throw new RuntimeException(
                "Connect string '" + s
                + "' contains unterminated quoted value '" + valueBuf.toString()
                + "'");
        }
    }

    /**
     * Combines two integers into a hash code.
     */
    public static int hash(int i, int j) {
        return (i << 4) ^ j;
    }

    /**
     * Computes a hash code from an existing hash code and an object (which
     * may be null).
     */
    public static int hash(int h, Object o) {
        int k = (o == null) ? 0 : o.hashCode();
        return ((h << 4) | h) ^ k;
    }

    /**
     * Computes a hash code from an existing hash code and an array of objects
     * (which may be null).
     */
    public static int hashArray(int h, Object [] a) {
        // The hashcode for a null array and an empty array should be different
        // than h, so use magic numbers.
        if (a == null) {
            return hash(h, 19690429);
        }
        if (a.length == 0) {
            return hash(h, 19690721);
        }
        for (Object anA : a) {
            h = hash(h, anA);
        }
        return h;
    }

    /**
     * Concatenates one or more arrays.
     *
     * <p>Resulting array has same element type as first array. Each arrays may
     * be empty, but must not be null.
     *
     * @param a0 First array
     * @param as Zero or more subsequent arrays
     * @return Array containing all elements
     */
    public static <T> T[] appendArrays(
        T[] a0,
        T[]... as)
    {
        int n = a0.length;
        for (T[] a : as) {
            n += a.length;
        }
        T[] copy = Util.copyOf(a0, n);
        n = a0.length;
        for (T[] a : as) {
            System.arraycopy(a, 0, copy, n, a.length);
            n += a.length;
        }
        return copy;
    }

    /**
     * Adds an object to the end of an array.  The resulting array is of the
     * same type (e.g. <code>String[]</code>) as the input array.
     *
     * @param a Array
     * @param o Element
     * @return New array containing original array plus element
     *
     * @see #appendArrays
     */
    public static <T> T[] append(T[] a, T o) {
        T[] a2 = Util.copyOf(a, a.length + 1);
        a2[a.length] = o;
        return a2;
    }

    /**
     * Like <code>{@link java.util.Arrays}.copyOf(double[], int)</code>, but
     * exists prior to JDK 1.6.
     *
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @return a copy of the original array, truncated or padded with zeros
     *     to obtain the specified length
     */
    public static double[] copyOf(double[] original, int newLength) {
        double[] copy = new double[newLength];
        System.arraycopy(
            original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }

    /**
     * Like <code>{@link java.util.Arrays}.copyOf(int[], int)</code>, but
     * exists prior to JDK 1.6.
     *
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @return a copy of the original array, truncated or padded with zeros
     *     to obtain the specified length
     */
    public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(
            original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }

    /**
     * Like <code>{@link java.util.Arrays}.copyOf(long[], int)</code>, but
     * exists prior to JDK 1.6.
     *
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @return a copy of the original array, truncated or padded with zeros
     *     to obtain the specified length
     */
    public static long[] copyOf(long[] original, int newLength) {
        long[] copy = new long[newLength];
        System.arraycopy(
            original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }

    /**
     * Like <code>{@link java.util.Arrays}.copyOf(Object[], int)</code>, but
     * exists prior to JDK 1.6.
     *
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @return a copy of the original array, truncated or padded with zeros
     *     to obtain the specified length
     */
    public static <T> T[] copyOf(T[] original, int newLength) {
        //noinspection unchecked
        return (T[]) copyOf(original, newLength, original.getClass());
    }

    /**
     * Copies the specified array.
     *
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @param newType the class of the copy to be returned
     * @return a copy of the original array, truncated or padded with nulls
     *     to obtain the specified length
     */
    public static <T, U> T[] copyOf(
        U[] original, int newLength, Class<? extends T[]> newType)
    {
        @SuppressWarnings({"unchecked", "RedundantCast"})
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        //noinspection SuspiciousSystemArraycopy
        System.arraycopy(
            original, 0, copy, 0,
            Math.min(original.length, newLength));
        return copy;
    }

    /**
     * Returns the cumulative amount of time spent accessing the database.
     *
     * @deprecated Use {@link mondrian.server.monitor.Monitor#getServer()} and
     *  {@link mondrian.server.monitor.ServerInfo#sqlStatementExecuteNanos};
     *  will be removed in 4.0.
     */
    public static long dbTimeMillis() {
        return databaseMillis;
    }

    /**
     * Adds to the cumulative amount of time spent accessing the database.
     *
     * @deprecated Will be removed in 4.0.
     */
    public static void addDatabaseTime(long millis) {
        databaseMillis += millis;
    }

    /**
     * Returns the system time less the time spent accessing the database.
     * Use this method to figure out how long an operation took: call this
     * method before an operation and after an operation, and the difference
     * is the amount of non-database time spent.
     *
     * @deprecated Will be removed in 4.0.
     */
    public static long nonDbTimeMillis() {
        final long systemMillis = System.currentTimeMillis();
        return systemMillis - databaseMillis;
    }

    /**
     * Creates a very simple implementation of {@link Validator}. (Only
     * useful for resolving trivial expressions.)
     */
    public static Validator createSimpleValidator(final FunTable funTable) {
        return new Validator() {
            public Query getQuery() {
                return null;
            }

            public SchemaReader getSchemaReader() {
                throw new UnsupportedOperationException();
            }

            public Exp validate(Exp exp, boolean scalar) {
                return exp;
            }

            public void validate(ParameterExpr parameterExpr) {
            }

            public void validate(MemberProperty memberProperty) {
            }

            public void validate(QueryAxis axis) {
            }

            public void validate(Formula formula) {
            }

            public FunDef getDef(Exp[] args, String name, Syntax syntax) {
                // Very simple resolution. Assumes that there is precisely
                // one resolver (i.e. no overloading) and no argument
                // conversions are necessary.
                List<Resolver> resolvers = funTable.getResolvers(name, syntax);
                final Resolver resolver = resolvers.get(0);
                final List<Resolver.Conversion> conversionList =
                    new ArrayList<Resolver.Conversion>();
                final FunDef def =
                    resolver.resolve(args, this, conversionList);
                assert conversionList.isEmpty();
                return def;
            }

            public boolean alwaysResolveFunDef() {
                return false;
            }

            public boolean canConvert(
                int ordinal, Exp fromExp,
                int to,
                List<Resolver.Conversion> conversions)
            {
                return true;
            }

            public boolean requiresExpression() {
                return false;
            }

            public FunTable getFunTable() {
                return funTable;
            }

            public Parameter createOrLookupParam(
                boolean definition,
                String name,
                Type type,
                Exp defaultExp,
                String description)
            {
                return null;
            }
        };
    }

    /**
     * Reads a Reader until it returns EOF and returns the contents as a String.
     *
     * @param rdr  Reader to Read.
     * @param bufferSize size of buffer to allocate for reading.
     * @return content of Reader as String
     * @throws IOException on I/O error
     */
    public static String readFully(final Reader rdr, final int bufferSize)
        throws IOException
    {
        if (bufferSize <= 0) {
            throw new IllegalArgumentException(
                "Buffer size must be greater than 0");
        }

        final char[] buffer = new char[bufferSize];
        final StringBuilder buf = new StringBuilder(bufferSize);

        int len;
        while ((len = rdr.read(buffer)) != -1) {
            buf.append(buffer, 0, len);
        }
        return buf.toString();
    }

    /**
     * Reads an input stream until it returns EOF and returns the contents as an
     * array of bytes.
     *
     * @param in  Input stream
     * @param bufferSize size of buffer to allocate for reading.
     * @return content of stream as an array of bytes
     * @throws IOException on I/O error
     */
    public static byte[] readFully(final InputStream in, final int bufferSize)
        throws IOException
    {
        if (bufferSize <= 0) {
            throw new IllegalArgumentException(
                "Buffer size must be greater than 0");
        }

        final byte[] buffer = new byte[bufferSize];
        final ByteArrayOutputStream baos =
            new ByteArrayOutputStream(bufferSize);

        int len;
        while ((len = in.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        return baos.toByteArray();
    }

    /**
     * Returns the contents of a URL, substituting tokens.
     *
     * <p>Replaces the tokens "${key}" if the map is not null and "key" occurs
     * in the key-value map.
     *
     * <p>If the URL string starts with "inline:" the contents are the
     * rest of the URL.
     *
     * @param urlStr  URL string
     * @param map Key/value map
     * @return Contents of URL with tokens substituted
     * @throws IOException on I/O error
     */
    public static String readURL(final String urlStr, Map<String, String> map)
        throws IOException
    {
        if (urlStr.startsWith("inline:")) {
            String content = urlStr.substring("inline:".length());
            if (map != null) {
                content = Util.replaceProperties(content, map);
            }
            return content;
        } else {
            final URL url = new URL(urlStr);
            return readURL(url, map);
        }
    }

    /**
     * Returns the contents of a URL.
     *
     * @param url URL
     * @return Contents of URL
     * @throws IOException on I/O error
     */
    public static String readURL(final URL url) throws IOException {
        return readURL(url, null);
    }

    /**
     * Returns the contents of a URL, substituting tokens.
     *
     * <p>Replaces the tokens "${key}" if the map is not null and "key" occurs
     * in the key-value map.
     *
     * @param url URL
     * @param map Key/value map
     * @return Contents of URL with tokens substituted
     * @throws IOException on I/O error
     */
    public static String readURL(
        final URL url,
        Map<String, String> map)
        throws IOException
    {
        final Reader r =
            new BufferedReader(new InputStreamReader(url.openStream()));
        final int BUF_SIZE = 8096;
        try {
            String xmlCatalog = readFully(r, BUF_SIZE);
            xmlCatalog = Util.replaceProperties(xmlCatalog, map);
            return xmlCatalog;
        } finally {
            r.close();
        }
    }

    /**
     * Gets content via Apache VFS. File must exist and have content
     *
     * @param url String
     * @return Apache VFS FileContent for further processing
     * @throws FileSystemException on error
     */
    public static InputStream readVirtualFile(String url)
        throws FileSystemException
    {
        // Treat catalogUrl as an Apache VFS (Virtual File System) URL.
        // VFS handles all of the usual protocols (http:, file:)
        // and then some.
        FileSystemManager fsManager = VFS.getManager();
        if (fsManager == null) {
            throw newError("Cannot get virtual file system manager");
        }

        // Workaround VFS bug.
        if (url.startsWith("file://localhost")) {
            url = url.substring("file://localhost".length());
        }
        if (url.startsWith("file:")) {
            url = url.substring("file:".length());
        }

        // work around for VFS bug not closing http sockets
        // (Mondrian-585)
        if (url.startsWith("http")) {
            try {
                return new URL(url).openStream();
            } catch (IOException e) {
                throw newError(
                    "Could not read URL: " + url);
            }
        }

        File userDir = new File("").getAbsoluteFile();
        FileObject file = fsManager.resolveFile(userDir, url);
        FileContent fileContent = null;
        try {
            // Because of VFS caching, make sure we refresh to get the latest
            // file content. This refresh may possibly solve the following
            // workaround for defect MONDRIAN-508, but cannot be tested, so we
            // will leave the work around for now.
            file.refresh();

            // Workaround to defect MONDRIAN-508. For HttpFileObjects, verifies
            // the URL of the file retrieved matches the URL passed in.  A VFS
            // cache bug can cause it to treat URLs with different parameters
            // as the same file (e.g. http://blah.com?param=A,
            // http://blah.com?param=B)
            if (file instanceof HttpFileObject
                && !file.getName().getURI().equals(url))
            {
                fsManager.getFilesCache().removeFile(
                    file.getFileSystem(),  file.getName());

                file = fsManager.resolveFile(userDir, url);
            }

            if (!file.isReadable()) {
                throw newError(
                    "Virtual file is not readable: " + url);
            }

            fileContent = file.getContent();
        } finally {
            file.close();
        }

        if (fileContent == null) {
            throw newError(
                "Cannot get virtual file content: " + url);
        }

        return fileContent.getInputStream();
    }

    public static String readVirtualFileAsString(
        String catalogUrl)
        throws IOException
    {
        InputStream in = readVirtualFile(catalogUrl);
        try {
            return IOUtils.toString(in);
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * Converts a {@link Properties} object to a string-to-string {@link Map}.
     *
     * @param properties Properties
     * @return String-to-string map
     */
    public static Map<String, String> toMap(final Properties properties) {
        return new AbstractMap<String, String>() {
            @SuppressWarnings({"unchecked"})
            public Set<Entry<String, String>> entrySet() {
                return (Set) properties.entrySet();
            }
        };
    }
    /**
     * Replaces tokens in a string.
     *
     * <p>Replaces the tokens "${key}" if "key" occurs in the key-value map.
     * Otherwise "${key}" is left in the string unchanged.
     *
     * @param text Source string
     * @param env Map of key-value pairs
     * @return String with tokens substituted
     */
    public static String replaceProperties(
        String text,
        Map<String, String> env)
    {
        // As of JDK 1.5, cannot use StringBuilder - appendReplacement requires
        // the antediluvian StringBuffer.
        StringBuffer buf = new StringBuffer(text.length() + 200);

        Pattern pattern = Pattern.compile("\\$\\{([^${}]+)\\}");
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
            String varName = matcher.group(1);
            String varValue = env.get(varName);
            if (varValue != null) {
                matcher.appendReplacement(buf, varValue);
            } else {
                matcher.appendReplacement(buf, "\\${$1}");
            }
        }
        matcher.appendTail(buf);

        return buf.toString();
    }

    public static String printMemory() {
        return printMemory(null);
    }

    public static String printMemory(String msg) {
        final Runtime rt = Runtime.getRuntime();
        final long freeMemory = rt.freeMemory();
        final long totalMemory = rt.totalMemory();
        final StringBuilder buf = new StringBuilder(64);

        buf.append("FREE_MEMORY:");
        if (msg != null) {
            buf.append(msg);
            buf.append(':');
        }
        buf.append(' ');
        buf.append(freeMemory / 1024);
        buf.append("kb ");

        long hundredths = (freeMemory * 10000) / totalMemory;

        buf.append(hundredths / 100);
        hundredths %= 100;
        if (hundredths >= 10) {
            buf.append('.');
        } else {
            buf.append(".0");
        }
        buf.append(hundredths);
        buf.append('%');

        return buf.toString();
    }

    /**
     * Casts a Set to a Set with a different element type.
     *
     * @param set Set
     * @return Set of desired type
     */
    @SuppressWarnings({"unchecked"})
    public static <T> Set<T> cast(Set<?> set) {
        return (Set<T>) set;
    }

    /**
     * Casts a List to a List with a different element type.
     *
     * @param list List
     * @return List of desired type
     */
    @SuppressWarnings({"unchecked"})
    public static <T> List<T> cast(List<?> list) {
        return (List<T>) list;
    }

    /**
     * Returns whether it is safe to cast a collection to a collection with a
     * given element type.
     *
     * @param collection Collection
     * @param clazz Target element type
     * @param <T> Element type
     * @return Whether all not-null elements of the collection are instances of
     *   element type
     */
    public static <T> boolean canCast(
        Collection<?> collection,
        Class<T> clazz)
    {
        for (Object o : collection) {
            if (o != null && !clazz.isInstance(o)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Casts a collection to iterable.
     *
     * Under JDK 1.4, {@link Collection} objects do not implement
     * {@link Iterable}, so this method inserts a casting wrapper. (Since
     * Iterable does not exist under JDK 1.4, they will have been compiled
     * under JDK 1.5 or later, then retrowoven to 1.4 class format. References
     * to Iterable will have been replaced with references to
     * <code>com.rc.retroweaver.runtime.Retroweaver_</code>.
     *
     * <p>Under later JDKs this method is trivial. This method can be deleted
     * when we discontinue support for JDK 1.4.
     *
     * @param iterable Object which ought to be iterable
     * @param <T> Element type
     * @return Object cast to Iterable
     */
    public static <T> Iterable<T> castToIterable(
        final Object iterable)
    {
        if (Util.Retrowoven
            && !(iterable instanceof Iterable))
        {
            return new Iterable<T>() {
                public Iterator<T> iterator() {
                    return ((Collection<T>) iterable).iterator();
                }
            };
        }
        return (Iterable<T>) iterable;
    }

    /**
     * Looks up an enumeration by name, returning null if null or not valid.
     *
     * @param clazz Enumerated type
     * @param name Name of constant
     */
    public static <E extends Enum<E>> E lookup(Class<E> clazz, String name) {
        return lookup(clazz, name, null);
    }

    /**
     * Looks up an enumeration by name, returning a given default value if null
     * or not valid.
     *
     * @param clazz Enumerated type
     * @param name Name of constant
     * @param defaultValue Default value if constant is not found
     * @return Value, or null if name is null or value does not exist
     */
    public static <E extends Enum<E>> E lookup(
        Class<E> clazz, String name, E defaultValue)
    {
        if (name == null) {
            return defaultValue;
        }
        try {
            return Enum.valueOf(clazz, name);
        } catch (IllegalArgumentException e) {
            return defaultValue;
        }
    }

    /**
     * Make a BigDecimal from a double. On JDK 1.5 or later, the BigDecimal
     * precision reflects the precision of the double while with JDK 1.4
     * this is not the case.
     *
     * @param d the input double
     * @return the BigDecimal
     */
    public static BigDecimal makeBigDecimalFromDouble(double d) {
        return compatible.makeBigDecimalFromDouble(d);
    }

    /**
     * Returns a literal pattern String for the specified String.
     *
     * <p>Specification as for {@link Pattern#quote(String)}, which was
     * introduced in JDK 1.5.
     *
     * @param s The string to be literalized
     * @return A literal string replacement
     */
    public static String quotePattern(String s) {
        return compatible.quotePattern(s);
    }

    /**
     * Generates a unique id.
     *
     * <p>From JDK 1.5 onwards, uses a {@code UUID}.
     *
     * @return A unique id
     */
    public static String generateUuidString() {
        return compatible.generateUuidString();
    }

    /**
     * Compiles a script to yield a Java interface.
     *
     * <p>Only valid JDK 1.6 and higher; fails on JDK 1.5 and earlier.</p>
     *
     * @param iface Interface script should implement
     * @param script Script code
     * @param engineName Name of engine (e.g. "JavaScript")
     * @param <T> Interface
     * @return Object that implements given interface
     */
    public static <T> T compileScript(
        Class<T> iface,
        String script,
        String engineName)
    {
        return compatible.compileScript(iface, script, engineName);
    }

    /**
     * Removes a thread local from the current thread.
     *
     * <p>From JDK 1.5 onwards, calls {@link ThreadLocal#remove()}; before
     * that, no-ops.</p>
     *
     * @param threadLocal Thread local
     * @param <T> Type
     */
    public static <T> void threadLocalRemove(ThreadLocal<T> threadLocal) {
        compatible.threadLocalRemove(threadLocal);
    }

    /**
     * Creates a hash set that, like {@link java.util.IdentityHashMap},
     * compares keys using identity.
     *
     * @param <T> Element type
     * @return Set
     */
    public static <T> Set<T> newIdentityHashSet() {
        return compatible.newIdentityHashSet();
    }

    /**
     * Creates a new udf instance from the given udf class.
     *
     * @param udfClass the class to create new instance for
     * @param functionName Function name, or null
     * @return an instance of UserDefinedFunction
     */
    public static UserDefinedFunction createUdf(
        Class<? extends UserDefinedFunction> udfClass,
        String functionName)
    {
        // Instantiate class with default constructor.
        UserDefinedFunction udf;
        String className = udfClass.getName();
        String functionNameOrEmpty =
            functionName == null
                ? ""
                : functionName;

        // Find a constructor.
        Constructor<?> constructor;
        Object[] args = {};

        // 0. Check that class is public and top-level or static.
        // Before JDK 1.5, inner classes are impossible; retroweaver cannot
        // handle the getEnclosingClass method, so skip the check.
        if (!Modifier.isPublic(udfClass.getModifiers())
            || (!PreJdk15
                && udfClass.getEnclosingClass() != null
                && !Modifier.isStatic(udfClass.getModifiers())))
        {
            throw MondrianResource.instance().UdfClassMustBePublicAndStatic.ex(
                functionName,
                className);
        }

        // 1. Look for a constructor "public Udf(String name)".
        try {
            constructor = udfClass.getConstructor(String.class);
            if (Modifier.isPublic(constructor.getModifiers())) {
                args = new Object[] {functionName};
            } else {
                constructor = null;
            }
        } catch (NoSuchMethodException e) {
            constructor = null;
        }
        // 2. Otherwise, look for a constructor "public Udf()".
        if (constructor == null) {
            try {
                constructor = udfClass.getConstructor();
                if (Modifier.isPublic(constructor.getModifiers())) {
                    args = new Object[] {};
                } else {
                    constructor = null;
                }
            } catch (NoSuchMethodException e) {
                constructor = null;
            }
        }
        // 3. Else, no constructor suitable.
        if (constructor == null) {
            throw MondrianResource.instance().UdfClassWrongIface.ex(
                functionNameOrEmpty,
                className,
                UserDefinedFunction.class.getName());
        }
        // Instantiate class.
        try {
            udf = (UserDefinedFunction) constructor.newInstance(args);
        } catch (InstantiationException e) {
            throw MondrianResource.instance().UdfClassWrongIface.ex(
                functionNameOrEmpty,
                className, UserDefinedFunction.class.getName());
        } catch (IllegalAccessException e) {
            throw MondrianResource.instance().UdfClassWrongIface.ex(
                functionName,
                className,
                UserDefinedFunction.class.getName());
        } catch (ClassCastException e) {
            throw MondrianResource.instance().UdfClassWrongIface.ex(
                functionNameOrEmpty,
                className,
                UserDefinedFunction.class.getName());
        } catch (InvocationTargetException e) {
            throw MondrianResource.instance().UdfClassWrongIface.ex(
                functionName,
                className,
                UserDefinedFunction.class.getName());
        }

        return udf;
    }

    /**
     * Check the resultSize against the result limit setting. Throws
     * LimitExceededDuringCrossjoin exception if limit exceeded.
     *
     * When it is called from RolapNativeSet.checkCrossJoin(), it is only
     * possible to check the known input size, because the final CJ result
     * will come from the DB(and will be checked against the limit when
     * fetching from the JDBC result set, in SqlTupleReader.prepareTuples())
     *
     * @param resultSize Result limit
     * @throws ResourceLimitExceededException
     */
    public static void checkCJResultLimit(long resultSize) {
        int resultLimit = MondrianProperties.instance().ResultLimit.get();

        // Throw an exeption, if the size of the crossjoin exceeds the result
        // limit.
        if (resultLimit > 0 && resultLimit < resultSize) {
            throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex(
                resultSize, resultLimit);
        }

        // Throw an exception if the crossjoin exceeds a reasonable limit.
        // (Yes, 4 billion is a reasonable limit.)
        if (resultSize > Integer.MAX_VALUE) {
            throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex(
                resultSize, Integer.MAX_VALUE);
        }
    }

    /**
     * Converts an olap4j connect string into a legacy mondrian connect string.
     *
     * <p>For example,
     * "jdbc:mondrian:Datasource=jdbc/SampleData;Catalog=foodmart/FoodMart.xml;"
     * becomes
     * "Provider=Mondrian;
     * Datasource=jdbc/SampleData;Catalog=foodmart/FoodMart.xml;"
     *
     * <p>This method is intended to allow legacy applications (such as JPivot
     * and Mondrian's XMLA server) to continue to create connections using
     * Mondrian's legacy connection API even when they are handed an olap4j
     * connect string.
     *
     * @param url olap4j connect string
     * @return mondrian connect string, or null if cannot be converted
     */
    public static String convertOlap4jConnectStringToNativeMondrian(
        String url)
    {
        if (url.startsWith("jdbc:mondrian:")) {
            return "Provider=Mondrian; "
                + url.substring("jdbc:mondrian:".length());
        }
        return null;
    }

    /**
     * Checks if a String is whitespace, empty ("") or null.</p>
     *
     * <pre>
     * StringUtils.isBlank(null) = true
     * StringUtils.isBlank("") = true
     * StringUtils.isBlank(" ") = true
     * StringUtils.isBlank("bob") = false
     * StringUtils.isBlank(" bob ") = false
     * </pre>
     *
     * <p>(Copied from commons-lang.)
     *
     * @param str the String to check, may be null
     * @return <code>true</code> if the String is null, empty or whitespace
     */
    public static boolean isBlank(String str) {
        final int strLen;
        if (str == null || (strLen = str.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns a role which has access to everything.
     * @param schema A schema to bind this role to.
     * @return A role with root access to the schema.
     */
    public static Role createRootRole(Schema schema) {
        RoleImpl role = new RoleImpl();
        role.grant(schema, Access.ALL);
        role.makeImmutable();
        return role;
    }

    /**
     * Tries to find the cube from which a dimension is taken.
     * It considers private dimensions, shared dimensions and virtual
     * dimensions. If it can't determine with certitude the origin
     * of the dimension, it returns null.
     */
    public static Cube getDimensionCube(Dimension dimension) {
        final Cube[] cubes = dimension.getSchema().getCubes();
        for (Cube cube : cubes) {
            for (Dimension dimension1 : cube.getDimensions()) {
                // If the dimensions have the same identity,
                // we found an access rule.
                if (dimension == dimension1) {
                    return cube;
                }
                // If the passed dimension argument is of class
                // RolapCubeDimension, we must validate the cube
                // assignment and make sure the cubes are the same.
                // If not, skip to the next grant.
                if (dimension instanceof RolapCubeDimension
                    && dimension.equals(dimension1)
                    && !((RolapCubeDimension)dimension1)
                    .getCube()
                    .equals(cube))
                {
                    continue;
                }
                // Last thing is to allow for equality correspondences
                // to work with virtual cubes.
                if (cube instanceof RolapCube
                    && ((RolapCube)cube).isVirtual()
                    && dimension.equals(dimension1))
                {
                    return cube;
                }
            }
        }
        return null;
    }

    /**
     * Similar to {@link ClassLoader#getResource(String)}, except the lookup
     *  is in reverse order.<br>
     *  i.e. returns the resource from the supplied classLoader or the
     *  one closest to it in the hierarchy, instead of the closest to the root
     *  class loader
     * @param classLoader The class loader to fetch from
     * @param name The resource name
     * @return A URL object for reading the resource, or null if the resource
     * could not be found or the invoker doesn't have adequate privileges to get
     * the resource.
     * @see ClassLoader#getResource(String)
     * @see ClassLoader#getResources(String)
     */
    public static URL getClosestResource(ClassLoader classLoader, String name) {
        URL resource = null;
        try {
            // The last resource will be from the nearest ClassLoader.
            Enumeration<URL> resourceCandidates =
                classLoader.getResources(name);
            while (resourceCandidates.hasMoreElements()) {
                resource = resourceCandidates.nextElement();
            }
        } catch (IOException ioe) {
            // ignore exception - it's OK if file is not found
            // just keep getResource contract and return null
            Util.discard(ioe);
        }
        return resource;
    }

    public static abstract class AbstractFlatList<T>
        implements List<T>, RandomAccess
    {
        protected final List<T> asArrayList() {
            //noinspection unchecked
            return Arrays.asList((T[]) toArray());
        }

        public Iterator<T> iterator() {
            return asArrayList().iterator();
        }

        public ListIterator<T> listIterator() {
            return asArrayList().listIterator();
        }

        public boolean isEmpty() {
            return false;
        }

        public boolean add(Object t) {
            throw new UnsupportedOperationException();
        }

        public boolean addAll(Collection<? extends T> c) {
            throw new UnsupportedOperationException();
        }

        public boolean addAll(int index, Collection<? extends T> c) {
            throw new UnsupportedOperationException();
        }

        public boolean removeAll(Collection<?> c) {
            throw new UnsupportedOperationException();
        }

        public boolean retainAll(Collection<?> c) {
            throw new UnsupportedOperationException();
        }

        public void clear() {
            throw new UnsupportedOperationException();
        }

        public T set(int index, Object element) {
            throw new UnsupportedOperationException();
        }

        public void add(int index, Object element) {
            throw new UnsupportedOperationException();
        }

        public T remove(int index) {
            throw new UnsupportedOperationException();
        }

        public ListIterator<T> listIterator(int index) {
            return asArrayList().listIterator(index);
        }

        public List<T> subList(int fromIndex, int toIndex) {
            return asArrayList().subList(fromIndex, toIndex);
        }

        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }

        public boolean containsAll(Collection<?> c) {
            Iterator<?> e = c.iterator();
            while (e.hasNext()) {
                if (!contains(e.next())) {
                    return false;
                }
            }
            return true;
        }

        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * List that stores its two elements in the two members of the class.
     * Unlike {@link java.util.ArrayList} or
     * {@link java.util.Arrays#asList(Object[])} there is
     * no array, only one piece of memory allocated, therefore is very compact
     * and cache and CPU efficient.
     *
     * <p>The list is read-only, cannot be modified or resized, and neither
     * of the elements can be null.
     *
     * <p>The list is created via {@link Util#flatList(Object[])}.
     *
     * @see mondrian.olap.Util.Flat3List
     * @param <T>
     */
    protected static class Flat2List<T> extends AbstractFlatList<T> {
        private final T t0;
        private final T t1;

        Flat2List(T t0, T t1) {
            this.t0 = t0;
            this.t1 = t1;
            assert t0 != null;
            assert t1 != null;
        }

        public String toString() {
            return "[" + t0 + ", " + t1 + "]";
        }

        public T get(int index) {
            switch (index) {
            case 0:
                return t0;
            case 1:
                return t1;
            default:
                throw new IndexOutOfBoundsException("index " + index);
            }
        }

        public int size() {
            return 2;
        }

        public boolean equals(Object o) {
            if (o instanceof Flat2List) {
                Flat2List that = (Flat2List) o;
                return Util.equals(this.t0, that.t0)
                    && Util.equals(this.t1, that.t1);
            }
            return Arrays.asList(t0, t1).equals(o);
        }

        public int hashCode() {
            int h = 1;
            h = h * 31 + t0.hashCode();
            h = h * 31 + t1.hashCode();
            return h;
        }

        public int indexOf(Object o) {
            if (t0.equals(o)) {
                return 0;
            }
            if (t1.equals(o)) {
                return 1;
            }
            return -1;
        }

        public int lastIndexOf(Object o) {
            if (t1.equals(o)) {
                return 1;
            }
            if (t0.equals(o)) {
                return 0;
            }
            return -1;
        }

        @SuppressWarnings({"unchecked"})
        public <T2> T2[] toArray(T2[] a) {
            a[0] = (T2) t0;
            a[1] = (T2) t1;
            return a;
        }

        public Object[] toArray() {
            return new Object[] {t0, t1};
        }
    }

    /**
     * List that stores its three elements in the three members of the class.
     * Unlike {@link java.util.ArrayList} or
     * {@link java.util.Arrays#asList(Object[])} there is
     * no array, only one piece of memory allocated, therefore is very compact
     * and cache and CPU efficient.
     *
     * <p>The list is read-only, cannot be modified or resized, and none
     * of the elements can be null.
     *
     * <p>The list is created via {@link Util#flatList(Object[])}.
     *
     * @see mondrian.olap.Util.Flat2List
     * @param <T>
     */
    protected static class Flat3List<T> extends AbstractFlatList<T> {
        private final T t0;
        private final T t1;
        private final T t2;

        Flat3List(T t0, T t1, T t2) {
            this.t0 = t0;
            this.t1 = t1;
            this.t2 = t2;
            assert t0 != null;
            assert t1 != null;
            assert t2 != null;
        }

        public String toString() {
            return "[" + t0 + ", " + t1 + ", " + t2 + "]";
        }

        public T get(int index) {
            switch (index) {
            case 0:
                return t0;
            case 1:
                return t1;
            case 2:
                return t2;
            default:
                throw new IndexOutOfBoundsException("index " + index);
            }
        }

        public int size() {
            return 3;
        }

        public boolean equals(Object o) {
            if (o instanceof Flat3List) {
                Flat3List that = (Flat3List) o;
                return Util.equals(this.t0, that.t0)
                    && Util.equals(this.t1, that.t1)
                    && Util.equals(this.t2, that.t2);
            }
            return o.equals(this);
        }

        public int hashCode() {
            int h = 1;
            h = h * 31 + t0.hashCode();
            h = h * 31 + t1.hashCode();
            h = h * 31 + t2.hashCode();
            return h;
        }

        public int indexOf(Object o) {
            if (t0.equals(o)) {
                return 0;
            }
            if (t1.equals(o)) {
                return 1;
            }
            if (t2.equals(o)) {
                return 2;
            }
            return -1;
        }

        public int lastIndexOf(Object o) {
            if (t2.equals(o)) {
                return 2;
            }
            if (t1.equals(o)) {
                return 1;
            }
            if (t0.equals(o)) {
                return 0;
            }
            return -1;
        }

        @SuppressWarnings({"unchecked"})
        public <T2> T2[] toArray(T2[] a) {
            a[0] = (T2) t0;
            a[1] = (T2) t1;
            a[2] = (T2) t2;
            return a;
        }

        public Object[] toArray() {
            return new Object[] {t0, t1, t2};
        }
    }

    /**
     * Garbage-collecting iterator. Iterates over a collection of references,
     * and if any of the references has been garbage-collected, removes it from
     * the collection.
     *
     * @param <T> Element type
     */
    public static class GcIterator<T> implements Iterator<T> {
        private final Iterator<? extends Reference<T>> iterator;
        private boolean hasNext;
        private T next;

        public GcIterator(Iterator<? extends Reference<T>> iterator) {
            this.iterator = iterator;
            this.hasNext = true;
            moveToNext();
        }

        /**
         * Creates an iterator over a collection of references.
         *
         * @param referenceIterable Collection of references
         * @param <T2> element type
         * @return iterable over collection
         */
        public static <T2> Iterable<T2> over(
            final Iterable<? extends Reference<T2>> referenceIterable)
        {
            return new Iterable<T2>() {
                public Iterator<T2> iterator() {
                    return new GcIterator<T2>(referenceIterable.iterator());
                }
            };
        }

        private void moveToNext() {
            while (iterator.hasNext()) {
                final Reference<T> ref = iterator.next();
                next = ref.get();
                if (next != null) {
                    return;
                }
                iterator.remove();
            }
            hasNext = false;
        }

        public boolean hasNext() {
            return hasNext;
        }

        public T next() {
            final T next1 = next;
            moveToNext();
            return next1;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    public static interface Functor1<RT, PT> {
        RT apply(PT param);
    }

    public static <T> Functor1<T, T> identityFunctor() {
        //noinspection unchecked
        return IDENTITY_FUNCTOR;
    }

    private static final Functor1 IDENTITY_FUNCTOR =
        new Functor1<Object, Object>() {
            public Object apply(Object param) {
                return param;
            }
        };

    public static <PT> Functor1<Boolean, PT> trueFunctor() {
        //noinspection unchecked
        return TRUE_FUNCTOR;
    }

    public static <PT> Functor1<Boolean, PT> falseFunctor() {
        //noinspection unchecked
        return FALSE_FUNCTOR;
    }

    private static final Functor1 TRUE_FUNCTOR =
        new Functor1<Boolean, Object>() {
            public Boolean apply(Object param) {
                return true;
            }
        };

    private static final Functor1 FALSE_FUNCTOR =
        new Functor1<Boolean, Object>() {
            public Boolean apply(Object param) {
                return false;
            }
        };

    /**
     * Information about memory usage.
     *
     * @see mondrian.olap.Util#getMemoryInfo()
     */
    public interface MemoryInfo {
        Usage get();

        public interface Usage {
            long getUsed();
            long getCommitted();
            long getMax();
        }
    }

    /**
     * A {@link Comparator} implementation which can deal
     * correctly with {@link RolapUtil#sqlNullValue}.
     */
    public static class SqlNullSafeComparator
        implements Comparator<Comparable>
    {
        public static final SqlNullSafeComparator instance =
            new SqlNullSafeComparator();

        private SqlNullSafeComparator() {
        }

        public int compare(Comparable o1, Comparable o2) {
            if (o1 == RolapUtil.sqlNullValue) {
                return -1;
            }
            if (o2 == RolapUtil.sqlNullValue) {
                return 1;
            }
            return o1.compareTo(o2);
        }
    }

    /**
     * This class implements the Knuth-Morris-Pratt algorithm
     * to search within a byte array for a token byte array.
     */
    public static class ByteMatcher {
        private final int[] matcher;
        public final byte[] key;
        public ByteMatcher(byte[] key) {
            this.key = key;
            this.matcher = compile(key);
        }
        /**
         * Matches the pre-compiled byte array token against a
         * byte array variable and returns the index of the key
         * within the array.
         * @param a An array of bytes to search for.
         * @return -1 if not found, or the index (0 based) of the match.
         */
        public int match(byte[] a) {
            int j = 0;
            for (int i = 0; i < a.length; i++) {
                while (j > 0 && key[j] != a[i]) {
                    j = matcher[j - 1];
                }
                if (a[i] == key[j]) {
                    j++;
                }
                if (key.length == j) {
                    return
                        i - key.length + 1;
                }
            }
            return -1;
        }
        private int[] compile(byte[] key) {
            int[] matcher = new int[key.length];
            int j = 0;
            for (int i = 1; i < key.length; i++) {
                while (j > 0 && key[j] != key[i]) {
                    j = matcher[j - 1];
                }
                if (key[i] == key[j]) {
                    j++;
                }
                matcher[i] = j;
            }
            return matcher;
        }
    }

    /**
     * Transforms a list into a map for which all the keys return
     * a null value associated to it.
     *
     * <p>The list passed as an argument will be used to back
     * the map returned and as many methods are overridden as
     * possible to make sure that we don't iterate over the backing
     * list when creating it and when performing operations like
     * .size(), entrySet() and contains().
     *
     * <p>The returned map is to be considered immutable. It will
     * throw an {@link UnsupportedOperationException} if attempts to
     * modify it are made.
     */
    public static <K, V> Map<K, V> toNullValuesMap(List<K> list) {
        return new NullValuesMap<K, V>(list);
    }

    private static class NullValuesMap<K, V> extends AbstractMap<K, V> {
        private final List<K> list;
        private NullValuesMap(List<K> list) {
            super();
            this.list = Collections.unmodifiableList(list);
        }
        public Set<Entry<K, V>> entrySet() {
            return new AbstractSet<Entry<K, V>>() {
                public Iterator<Entry<K, V>>
                    iterator()
                {
                    return new Iterator<Entry<K, V>>() {
                        private int pt = -1;
                        public void remove() {
                            throw new UnsupportedOperationException();
                        }
                        @SuppressWarnings("unchecked")
                        public Entry<K, V> next() {
                            return new AbstractMapEntry(
                                list.get(++pt), null) {};
                        }
                        public boolean hasNext() {
                            return pt < list.size();
                        }
                    };
                }
                public int size() {
                    return list.size();
                }
                public boolean contains(Object o) {
                    if (o instanceof Entry) {
                        if (list.contains(((Entry) o).getKey())) {
                            return true;
                        }
                    }
                    return false;
                }
            };
        }
        public Set<K> keySet() {
            return new AbstractSet<K>() {
                public Iterator<K> iterator() {
                    return new Iterator<K>() {
                        private int pt = -1;
                        public void remove() {
                            throw new UnsupportedOperationException();
                        }
                        public K next() {
                            return list.get(++pt);
                        }
                        public boolean hasNext() {
                            return pt < list.size();
                        }
                    };
                }
                public int size() {
                    return list.size();
                }
                public boolean contains(Object o) {
                    return list.contains(o);
                }
            };
        }
        public Collection<V> values() {
            return new AbstractList<V>() {
                public V get(int index) {
                    return null;
                }
                public int size() {
                    return list.size();
                }
                public boolean contains(Object o) {
                    if (o == null && size() > 0) {
                        return true;
                    } else {
                        return false;
                    }
                }
            };
        }
        public V get(Object key) {
            return null;
        }
        public boolean containsKey(Object key) {
            return list.contains(key);
        }
        public boolean containsValue(Object o) {
            if (o == null && size() > 0) {
                return true;
            } else {
                return false;
            }
        }
    }
}

// End Util.java
TOP

Related Classes of mondrian.olap.Util$NullValuesMap

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.