/*
// $Id: //open/mondrian-release/3.2/src/main/mondrian/olap/type/TypeUtil.java#1 $
// 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.
// Copyright (C) 2005-2009 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
package mondrian.olap.type;
import mondrian.olap.*;
import mondrian.olap.fun.FunUtil;
import mondrian.olap.fun.Resolver;
import mondrian.resource.MondrianResource;
import mondrian.mdx.UnresolvedFunCall;
import java.util.List;
/**
* Utility methods relating to types.
*
* @author jhyde
* @since Feb 17, 2005
* @version $Id: //open/mondrian-release/3.2/src/main/mondrian/olap/type/TypeUtil.java#1 $
*/
public class TypeUtil {
public static Hierarchy typeToHierarchy(Type type) {
if (type instanceof MemberType
|| type instanceof LevelType
|| type instanceof HierarchyType
|| type instanceof DimensionType)
{
return type.getHierarchy();
} else {
throw Util.newInternal("not an mdx object");
}
}
/**
* Given a set type, returns the element type. Or its element type, if it
* is a set type. And so on.
*
* @param type Type
* @return underlying element type which is not a set type
*/
public static Type stripSetType(Type type) {
while (type instanceof SetType) {
type = ((SetType) type).getElementType();
}
return type;
}
/**
* Converts a type to a member or tuple type.
* If it cannot, returns null.
*
* @param type Type
* @return member or tuple type
*/
public static Type toMemberOrTupleType(Type type) {
type = stripSetType(type);
if (type instanceof TupleType) {
return type;
} else {
return toMemberType(type);
}
}
/**
* Converts a type to a member type.
* If it is a set, strips the set.
* If it is a member type, returns the type unchanged.
* If it is a dimension, hierarchy or level type, converts it to
* a member type.
* If it is a tuple, number, string, or boolean, returns null.
*
* @param type Type
* @return type as a member type
*/
public static MemberType toMemberType(Type type) {
type = stripSetType(type);
if (type instanceof MemberType) {
return (MemberType) type;
} else if (type instanceof DimensionType
|| type instanceof HierarchyType
|| type instanceof LevelType)
{
return MemberType.forType(type);
} else {
return null;
}
}
/**
* Returns whether this type is union-compatible with another.
* In general, to be union-compatible, types must have the same
* dimensionality.
*
* @param type1 First type
* @param type2 Second type
* @return whether types are union-compatible
*/
public static boolean isUnionCompatible(Type type1, Type type2) {
if (type1 instanceof TupleType) {
TupleType tupleType1 = (TupleType) type1;
if (type2 instanceof TupleType) {
TupleType tupleType2 = (TupleType) type2;
if (tupleType1.elementTypes.length
== tupleType2.elementTypes.length)
{
for (int i = 0; i < tupleType1.elementTypes.length; i++) {
if (!isUnionCompatible(
tupleType1.elementTypes[i],
tupleType2.elementTypes[i]))
{
return false;
}
}
return true;
}
}
return false;
} else {
final MemberType memberType1 = toMemberType(type1);
if (memberType1 == null) {
return false;
}
final MemberType memberType2 = toMemberType(type2);
if (memberType2 == null) {
return false;
}
final Hierarchy hierarchy1 = memberType1.getHierarchy();
final Hierarchy hierarchy2 = memberType2.getHierarchy();
return equal(hierarchy1, hierarchy2);
}
}
/**
* Returns whether two hierarchies are equal.
*
* @param hierarchy1 First hierarchy
* @param hierarchy2 Second hierarchy
* @return Whether hierarchies are equal
*/
private static boolean equal(
final Hierarchy hierarchy1,
final Hierarchy hierarchy2)
{
return hierarchy1 == null
|| hierarchy2 == null
|| hierarchy2.getUniqueName().equals(
hierarchy1.getUniqueName());
}
/**
* Returns whether a value of a given type can be evaluated to a scalar
* value.
*
* <p>The rules are as follows:<ul>
* <li>Clearly boolean, numeric and string expressions can be evaluated.
* <li>Member and tuple expressions can be interpreted as a scalar value.
* The expression is evaluated to establish the context where a measure
* can be evaluated.
* <li>Hierarchy and dimension expressions are implicitly
* converted into the current member, and evaluated as above.
* <li>Level expressions cannot be evaluated
* <li>Cube and Set (even sets with a single member) cannot be evaluated.
* </ul>
*
* @param type Type
* @return Whether an expression of this type can be evaluated to yield a
* scalar value.
*/
public static boolean canEvaluate(Type type) {
return ! (type instanceof SetType
|| type instanceof CubeType
|| type instanceof LevelType);
}
/**
* Returns whether a type is a set type.
*
* @param type Type
* @return Whether a value of this type can be evaluated to yield a set.
*/
public static boolean isSet(Type type) {
return type instanceof SetType;
}
public static boolean couldBeMember(Type type) {
return type instanceof MemberType
|| type instanceof HierarchyType
|| type instanceof DimensionType;
}
/**
* Converts a {@link Type} value to a {@link Category} ordinal.
*
* @param type Type
* @return category ordinal
*/
public static int typeToCategory(Type type) {
if (type instanceof NullType) {
return Category.Null;
} else if (type instanceof EmptyType) {
return Category.Empty;
} else if (type instanceof DateTimeType) {
return Category.DateTime;
} else if (type instanceof NumericType) {
return Category.Numeric;
} else if (type instanceof BooleanType) {
return Category.Logical;
} else if (type instanceof DimensionType) {
return Category.Dimension;
} else if (type instanceof HierarchyType) {
return Category.Hierarchy;
} else if (type instanceof MemberType) {
return Category.Member;
} else if (type instanceof LevelType) {
return Category.Level;
} else if (type instanceof SymbolType) {
return Category.Symbol;
} else if (type instanceof StringType) {
return Category.String;
} else if (type instanceof ScalarType) {
return Category.Value;
} else if (type instanceof SetType) {
return Category.Set;
} else if (type instanceof TupleType) {
return Category.Tuple;
} else {
throw Util.newInternal("Unknown type " + type);
}
}
/**
* Returns a type sufficiently broad to hold any value of several types,
* but as narrow as possible. If there is no such type, returns null.
*
* <p>The result is equivalent to calling
* {@link Type#computeCommonType(Type, int[])} pairwise.
*
* @param allowConversions Whether to allow implicit conversions
* @param types Array of types
* @return Most general type which encompases all types
*/
public static Type computeCommonType(
boolean allowConversions,
Type... types)
{
if (types.length == 0) {
return null;
}
Type type = types[0];
int[] conversionCount = allowConversions ? new int[] {0} : null;
for (int i = 1; i < types.length; ++i) {
if (type == null) {
return null;
}
type = type.computeCommonType(types[i], conversionCount);
}
return type;
}
/**
* Returns whether we can convert an argument of a given category to a
* given parameter category.
*
* @param ordinal argument ordinal
* @param fromType actual argument type
* @param to formal parameter category
* @param conversions list of implicit conversions required (out)
* @return whether can convert from 'from' to 'to'
*/
public static boolean canConvert(
int ordinal,
Type fromType,
int to,
List<Resolver.Conversion> conversions)
{
final int from = typeToCategory(fromType);
if (from == to) {
return true;
}
RuntimeException e = null;
switch (from) {
case Category.Array:
return false;
case Category.Dimension:
// We can go from Dimension to Hierarchy if the dimension has a
// default hierarchy. From there, we can go to Member or Tuple.
// Even if the dimension does not have a default hierarchy, we claim
// now that we can do the conversion, to prevent other overloads
// from being chosen; we will hit an error either at compile time or
// at run time.
switch (to) {
case Category.Member:
case Category.Tuple:
case Category.Hierarchy:
// It is more difficult to convert dimension->hierarchy than
// hierarchy->dimension
conversions.add(new ConversionImpl(from, to, ordinal, 2, e));
return true;
case Category.Level:
// It is more difficult to convert dimension->level than
// dimension->member or dimension->hierarchy->member.
conversions.add(new ConversionImpl(from, to, ordinal, 3, null));
return true;
default:
return false;
}
case Category.Hierarchy:
// Seems funny that you can 'downcast' from a hierarchy, doesn't
// it? But we add an implicit 'CurrentMember', for example,
// '[Product].PrevMember' actually means
// '[Product].CurrentMember.PrevMember'.
switch (to) {
case Category.Dimension:
case Category.Member:
case Category.Tuple:
conversions.add(new ConversionImpl(from, to, ordinal, 1, null));
return true;
default:
return false;
}
case Category.Level:
switch (to) {
case Category.Dimension:
// It's more difficult to convert to a dimension than a
// hierarchy. For example, we want '[Store City].CurrentMember'
// to resolve to <Hierarchy>.CurrentMember rather than
// <Dimension>.CurrentMember.
conversions.add(new ConversionImpl(from, to, ordinal, 2, null));
return true;
case Category.Hierarchy:
conversions.add(new ConversionImpl(from, to, ordinal, 1, null));
return true;
default:
return false;
}
case Category.Logical:
switch (to) {
case Category.Value:
return true;
default:
return false;
}
case Category.Member:
switch (to) {
case Category.Dimension:
case Category.Hierarchy:
case Category.Level:
case Category.Tuple:
conversions.add(new ConversionImpl(from, to, ordinal, 1, null));
return true;
case Category.Set:
// It is more expensive to convert from Member->Set (cost=2)
// than Member->Tuple (cost=1). In particular, m.Tuple(n) should
// resolve to <Tuple>.Item(<Numeric>) rather than
// <Set>.Item(<Numeric>).
conversions.add(new ConversionImpl(from, to, ordinal, 2, null));
return true;
case Category.Numeric:
// It is more expensive to convert from Member->Scalar (cost=3)
// than Member->Set (cost=2). In particular, we want 'member *
// set' to resolve to the crossjoin operator, '{m} * set'.
conversions.add(new ConversionImpl(from, to, ordinal, 3, null));
return true;
case Category.Value:
case Category.String:
// We assume that measures are numeric, so a cast to a string or
// general value expression is more expensive (cost=4) than a
// conversion to a numeric expression (cost=3).
conversions.add(new ConversionImpl(from, to, ordinal, 4, null));
return true;
default:
return false;
}
case Category.Numeric | Category.Constant:
switch (to) {
case Category.Value:
case Category.Numeric:
return true;
default:
return false;
}
case Category.Numeric:
switch (to) {
case Category.Logical:
conversions.add(new ConversionImpl(from, to, ordinal, 2, null));
return true;
case Category.Value:
case Category.Integer:
case (Category.Integer | Category.Constant):
case (Category.Numeric | Category.Constant):
return true;
default:
return false;
}
case Category.Integer:
switch (to) {
case Category.Value:
case (Category.Integer | Category.Constant):
case Category.Numeric:
case (Category.Numeric | Category.Constant):
return true;
default:
return false;
}
case Category.Set:
return false;
case Category.String | Category.Constant:
switch (to) {
case Category.Value:
case Category.String:
return true;
default:
return false;
}
case Category.String:
switch (to) {
case Category.Value:
case (Category.String | Category.Constant):
return true;
default:
return false;
}
case Category.DateTime | Category.Constant:
switch (to) {
case Category.Value:
case Category.DateTime:
return true;
default:
return false;
}
case Category.DateTime:
switch (to) {
case Category.Value:
case (Category.DateTime | Category.Constant):
return true;
default:
return false;
}
case Category.Tuple:
switch (to) {
case Category.Set:
conversions.add(new ConversionImpl(from, to, ordinal, 2, null));
return true;
case Category.Numeric:
// It is more expensive to convert from Tuple->Scalar (cost=4)
// than Tuple->Set (cost=3). In particular, we want 'tuple *
// set' to resolve to the crossjoin operator, '{tuple} * set'.
// This is analogous to Member->Numeric conversion.
conversions.add(new ConversionImpl(from, to, ordinal, 3, null));
return true;
case Category.String:
case Category.Value:
// We assume that measures are numeric, so a cast to a string or
// general value expression is more expensive (cost=4) than a
// conversion to a numeric expression (cost=3).
conversions.add(new ConversionImpl(from, to, ordinal, 4, null));
return true;
default:
return false;
}
case Category.Value:
// We can implicitly cast from value to a more specific scalar type,
// but the cost is significant.
switch (to) {
case Category.String:
case Category.Numeric:
case Category.Logical:
conversions.add(new ConversionImpl(from, to, ordinal, 2, null));
return true;
default:
return false;
}
case Category.Symbol:
return false;
case Category.Null:
// now null supports members as well as scalars; but scalar is
// preferred
if (Category.isScalar(to)) {
return true;
} else if (to == Category.Member) {
conversions.add(new ConversionImpl(from, to, ordinal, 2, null));
return true;
} else {
return false;
}
case Category.Empty:
return false;
default:
throw Util.newInternal(
"unknown category " + from + " for type " + fromType);
}
}
static <T> T neq(T t1, T t2) {
return t1 == null ? t2
: t2 == null ? t1
: t1.equals(t2) ? t1
: null;
}
/**
* Implementation of {@link mondrian.olap.fun.Resolver.Conversion}.
*/
private static class ConversionImpl implements Resolver.Conversion {
final int from;
final int to;
/**
* Which argument. Arguments are 0-based, and in particular the 'this'
* of a call of member or method call syntax is argument 0. Argument -1
* is the return.
*/
final int ordinal;
/**
* Score of the conversion. A higher value is more onerous and therefore
* a call using such a conversion is less likly to be chosen.
*/
final int cost;
final RuntimeException e;
/**
* Creates a conversion.
*
* @param from From type
* @param to To type
* @param ordinal Ordinal of argument
* @param cost Cost of conversion
* @param e Exception
*/
public ConversionImpl(
int from,
int to,
int ordinal,
int cost,
RuntimeException e)
{
this.from = from;
this.to = to;
this.ordinal = ordinal;
this.cost = cost;
this.e = e;
}
public int getCost() {
return cost;
}
public void checkValid() {
if (e != null) {
throw e;
}
}
public void apply(Validator validator, List<Exp> args) {
final Exp arg = args.get(ordinal);
switch (from) {
case Category.Member:
case Category.Tuple:
switch (to) {
case Category.Set:
final Exp newArg =
validator.validate(
new UnresolvedFunCall(
"{}", Syntax.Braces, new Exp[]{arg}), false);
args.set(ordinal, newArg);
break;
default:
// do nothing
}
default:
// do nothing
}
}
// for debug
public String toString() {
return "Conversion(from=" + Category.instance().getName(from)
+ ", to=" + Category.instance().getName(to)
+ ", ordinal="
+ ordinal + ", cost="
+ cost + ", e=" + e + ")";
}
}
}
// End TypeUtil.java