/*
// 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].[Product Department].[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