();
processMemberExpr(member);
((VisualTotalMember)member).setExpression(evaluator, exprMembers);
}
return position;
}
private void processMemberExpr(Object o) {
if (o instanceof Member && o instanceof RolapCubeMember) {
exprMembers.add((Member) o);
return;
} else if (o instanceof VisualTotalMember) {
VisualTotalMember member = (VisualTotalMember) o;
Exp exp = member.getExpression();
processMemberExpr(exp);
} else if (o instanceof Exp && !(o instanceof MemberExpr)) {
Exp exp = (Exp)o;
ResolvedFunCall funCall = (ResolvedFunCall)exp;
Exp[] exps = funCall.getArgs();
processMemberExpr(exps);
} else if (o instanceof Exp[]) {
Exp[] exps = (Exp[]) o;
for (Exp exp : exps) {
processMemberExpr(exp);
}
} else if (o instanceof MemberExpr) {
MemberExpr memberExp = (MemberExpr) o;
Member member = memberExp.getMember();
processMemberExpr(member);
} else {
return;
}
}
/**
* Converts a set of cell coordinates to a cell ordinal.
*
* This method can be expensive, because the ordinal is computed from the
* length of the axes, and therefore the axes need to be instantiated.
*/
int getCellOrdinal(int[] pos) {
if (modulos == null) {
makeModulos();
}
return modulos.getCellOrdinal(pos);
}
/*
* Instantiates the calculator to convert cell coordinates to a cell ordinal
* and vice versa.
*
*
To create the calculator, any axis that is based upon an Iterable is
* converted into a List - thus increasing memory usage.
*/
protected void makeModulos() {
modulos = Modulos.Generator.create(axes);
}
/**
* Called only by RolapCell.
*
* @param pos Coordinates of cell
* @return Evaluator whose context is the given cell
*/
RolapEvaluator getCellEvaluator(int[] pos) {
final RolapEvaluator cellEvaluator = evaluator.push();
for (int i = 0; i < pos.length; i++) {
Position position = axes[i].getPositions().get(pos[i]);
cellEvaluator.setContext(position);
}
return cellEvaluator;
}
/**
* Called only by RolapCell. Use this when creating an Evaluator
* (using method {@link #getCellEvaluator}) is not required.
*
* @param pos Coordinates of cell
* @return Members which form the context of the given cell
*/
RolapMember[] getCellMembers(int[] pos) {
RolapMember[] members = (RolapMember[]) evaluator.getMembers().clone();
for (int i = 0; i < pos.length; i++) {
Position position = axes[i].getPositions().get(pos[i]);
for (Member member : position) {
RolapMember m = (RolapMember) member;
int ordinal = m.getHierarchy().getOrdinalInCube();
members[ordinal] = m;
}
}
return members;
}
Evaluator getRootEvaluator() {
return evaluator;
}
Evaluator getEvaluator(int[] pos) {
// Set up evaluator's context, so that context-dependent format
// strings work properly.
Evaluator cellEvaluator = evaluator.push();
for (int i = -1; i < axes.length; i++) {
Axis axis;
int index;
if (i < 0) {
axis = slicerAxis;
index = 0;
} else {
axis = axes[i];
index = pos[i];
}
Position position = axis.getPositions().get(index);
cellEvaluator.setContext(position);
}
return cellEvaluator;
}
/**
* Counts and collects Members found of the axes.
* This class does two things. First it collects all Members
* found during the Member-Determination phase.
* Secondly, it counts how many Members are on each axis and
* forms the product, the totalCellCount which is checked against
* the ResultLimit property value.
*/
private static class AxisMember implements Iterable {
private final List members;
private final int limit;
private boolean isSlicer;
private int totalCellCount;
private int axisCount;
private boolean countOnly;
AxisMember() {
this.countOnly = false;
this.members = new ConcatenableList();
this.totalCellCount = 1;
this.axisCount = 0;
// Now that the axes are evaluated, make sure that the number of
// cells does not exceed the result limit.
this.limit = MondrianProperties.instance().ResultLimit.get();
}
public Iterator iterator() {
return members.iterator();
}
void setSlicer(final boolean isSlicer) {
this.isSlicer = isSlicer;
}
boolean isEmpty() {
return this.members.isEmpty();
}
void countOnly(boolean countOnly) {
this.countOnly = countOnly;
}
void checkLimit() {
if (this.limit > 0) {
this.totalCellCount *= this.axisCount;
if (this.totalCellCount > this.limit) {
throw MondrianResource.instance().TotalMembersLimitExceeded
.ex(
this.totalCellCount,
this.limit);
}
this.axisCount = 0;
}
}
void clearAxisCount() {
this.axisCount = 0;
}
void clearTotalCellCount() {
this.totalCellCount = 1;
}
void clearMembers() {
this.members.clear();
this.axisCount = 0;
this.totalCellCount = 1;
}
List members() {
return this.members;
}
void mergeMemberList(List list) {
for (Member o : list) {
if (o == null) {
continue;
}
if (o.getDimension().isHighCardinality()) {
break;
}
mergeMember(o);
}
}
void mergeTupleList(List list) {
for (Member[] o : list) {
mergeTuple(o);
}
}
private void mergeMemberIter(Iterator it) {
while (it.hasNext()) {
mergeMember(it.next());
}
}
private void mergeTupleIter(Iterator it) {
while (it.hasNext()) {
mergeTuple(it.next());
}
}
private Member getTopParent(final Member m) {
Member parent = m.getParentMember();
return (parent == null) ? m : getTopParent(parent);
}
private void mergeTuple(final Member[] members) {
for (Member member : members) {
mergeMember(member);
}
}
private void mergeMember(final Member member) {
this.axisCount++;
if (! countOnly) {
if (isSlicer) {
if (! members.contains(member)) {
members.add(member);
}
} else {
if (member.isNull()) {
return;
} else if (member.isMeasure()) {
return;
} else if (member.isCalculated()) {
return;
} else if (member.isAll()) {
return;
}
Member topParent = getTopParent(member);
if (! this.members.contains(topParent)) {
this.members.add(topParent);
}
}
}
}
}
/**
* Extension to {@link RolapEvaluatorRoot} which is capable
* of evaluating named sets.
*
* A given set is only evaluated once each time a query is executed; the
* result is added to the {@link #namedSetEvaluators} cache on first execution
* and re-used.
*
* Named sets are always evaluated in the context of the slicer.
*/
protected static class RolapResultEvaluatorRoot
extends RolapEvaluatorRoot
{
/**
* Maps the names of sets to their values. Populated on demand.
*/
private final Map namedSetEvaluators =
new HashMap();
/**
* Evaluator containing context resulting from evaluating the slicer.
*/
RolapEvaluator slicerEvaluator;
final RolapResult result;
private static final Object CycleSentinel = new Object();
private static final Object NullSentinel = new Object();
public RolapResultEvaluatorRoot(RolapResult result) {
super(result.query);
this.result = result;
}
protected void init(Evaluator evaluator) {
slicerEvaluator = (RolapEvaluator) evaluator;
}
protected Evaluator.NamedSetEvaluator evaluateNamedSet(
final NamedSet namedSet,
boolean create)
{
final String name = namedSet.getNameUniqueWithinQuery();
RolapNamedSetEvaluator value;
if (namedSet.isDynamic() && !create) {
value = null;
} else {
value = namedSetEvaluators.get(name);
}
if (value == null) {
value = new RolapNamedSetEvaluator(this, namedSet);
namedSetEvaluators.put(name, value);
}
return value;
}
public Object getParameterValue(ParameterSlot slot) {
if (slot.isParameterSet()) {
return slot.getParameterValue();
}
// Look in other places for the value. Which places we look depends
// on the scope of the parameter.
Parameter.Scope scope = slot.getParameter().getScope();
switch (scope) {
case System:
// TODO: implement system params
// fall through
case Schema:
// TODO: implement schema params
// fall through
case Connection:
// if it's set in the session, return that value
// fall through
case Statement:
break;
default:
throw Util.badValue(scope);
}
// Not set in any accessible scope. Evaluate the default value,
// then cache it.
Object liftedValue = slot.getCachedDefaultValue();
Object value;
if (liftedValue != null) {
if (liftedValue == CycleSentinel) {
throw MondrianResource.instance()
.CycleDuringParameterEvaluation.ex(
slot.getParameter().getName());
}
if (liftedValue == NullSentinel) {
value = null;
} else {
value = liftedValue;
}
return value;
}
// Set value to a sentinel, so we can detect cyclic evaluation.
slot.setCachedDefaultValue(CycleSentinel);
value = result.evaluateExp(
slot.getDefaultValueCalc(), slicerEvaluator.push());
if (value == null) {
liftedValue = NullSentinel;
} else {
liftedValue = value;
}
slot.setCachedDefaultValue(liftedValue);
return value;
}
}
/**
* Formatter to convert values into formatted strings.
*
* Every Cell has a value, a format string (or CellFormatter) and a
* formatted value string.
* There are a wide range of possible values (pick a Double, any
* Double - its a value). Because there are lots of possible values,
* there are also lots of possible formatted value strings. On the
* other hand, there are only a very small number of format strings
* and CellFormatter's. These formatters are to be cached
* in a synchronized HashMaps in order to limit how many copies
* need to be kept around.
*
*
* There are two implementations of the ValueFormatter interface:
* - {@link CellFormatterValueFormatter}, which formats using a
* user-registered {@link CellFormatter}; and
*
- {@link FormatValueFormatter}, which takes the {@link Locale} object.
*
*/
interface ValueFormatter {
/**
* Formats a value according to a format string.
*
* @param value Value
* @param formatString Format string
* @return Formatted value
*/
String format(Object value, String formatString);
/**
* Formatter that always returns the empty string.
*/
public static final ValueFormatter EMPTY = new ValueFormatter() {
public String format(Object value, String formatString) {
return "";
}
};
}
/**
* A CellFormatterValueFormatter uses a user-defined {@link CellFormatter}
* to format values.
*/
static class CellFormatterValueFormatter implements ValueFormatter {
final CellFormatter cf;
/**
* Creates a CellFormatterValueFormatter
*
* @param cf Cell formatter
*/
CellFormatterValueFormatter(CellFormatter cf) {
this.cf = cf;
}
public String format(Object value, String formatString) {
return cf.formatCell(value);
}
}
/**
* A FormatValueFormatter takes a {@link Locale}
* as a parameter and uses it to get the {@link mondrian.util.Format}
* to be used in formatting an Object value with a
* given format string.
*/
static class FormatValueFormatter implements ValueFormatter {
final Locale locale;
/**
* Creates a FormatValueFormatter.
*
* @param locale Locale
*/
FormatValueFormatter(Locale locale) {
this.locale = locale;
}
public String format(Object value, String formatString) {
if (value == Util.nullValue) {
Format format = getFormat(formatString);
return format.format(null);
} else if (value instanceof Throwable) {
return "#ERR: " + value.toString();
} else if (value instanceof String) {
return (String) value;
} else {
Format format = getFormat(formatString);
return format.format(value);
}
}
private Format getFormat(String formatString) {
return Format.get(formatString, locale);
}
}
/*
* Generate a long ordinal based upon the values of the integers
* stored in the cell position array. With this mechanism, the
* Cell information can be stored using a long key (rather than
* the array integer of positions) thus saving memory. The trick
* is to use a 'large number' per axis in order to convert from
* position array to long key where the 'large number' is greater
* than the number of members in the axis.
* The largest 'long' is java.lang.Long.MAX_VALUE which is
* 9,223,372,036,854,776,000. The product of the maximum number
* of members per axis must be less than this maximum 'long'
* value (otherwise one gets hashing collisions).
*
* For a single axis, the maximum number of members is equal to
* the max 'long' number, 9,223,372,036,854,776,000.
*
* For two axes, the maximum number of members is the square root
* of the max 'long' number, 9,223,372,036,854,776,000, which is
* slightly bigger than 2,147,483,647 (which is the maximum integer).
*
* For three axes, the maximum number of members per axis is the
* cube root of the max 'long' which is about 2,000,000
*
* For four axes the forth root is about 50,000.
*
* For five or more axes, the maximum number of members per axis
* based upon the root of the maximum 'long' number,
* start getting too small to guarantee that it will be
* smaller than the number of members on a given axis and so
* we must resort to the Map-base Cell container.
*/
/**
* Synchronized Map from Locale to ValueFormatter. It is expected that
* there will be only a small number of Locale's.
* Should these be a WeakHashMap?
*/
protected static final Map
formatValueFormatters =
Collections.synchronizedMap(new HashMap());
/**
* Synchronized Map from CellFormatter to ValueFormatter.
* CellFormatter's are defined in schema files. It is expected
* the there will only be a small number of CellFormatter's.
* Should these be a WeakHashMap?
*/
protected static final Map
cellFormatters =
Collections.synchronizedMap(
new HashMap());
/**
* A CellInfo contains all of the information that a Cell requires.
* It is placed in the cellInfos map during evaluation and
* serves as a constructor parameter for {@link RolapCell}.
*
* During the evaluation stage they are mutable but after evaluation has
* finished they are not changed.
*/
static class CellInfo {
Object value;
String formatString;
ValueFormatter valueFormatter;
long key;
/**
* Creates a CellInfo representing the position of a cell.
*
* @param key Ordinal representing the position of a cell
*/
CellInfo(long key) {
this(key, null, null, ValueFormatter.EMPTY);
}
/**
* Creates a CellInfo with position, value, format string and formatter
* of a cell.
*
* @param key Ordinal representing the position of a cell
* @param value Value of cell, or null if not yet known
* @param formatString Format string of cell, or null
* @param valueFormatter Formatter for cell, or null
*/
CellInfo(
long key,
Object value,
String formatString,
ValueFormatter valueFormatter)
{
this.key = key;
this.value = value;
this.formatString = formatString;
this.valueFormatter = valueFormatter;
}
public int hashCode() {
// Combine the upper 32 bits of the key with the lower 32 bits.
// We used to use 'key ^ (key >>> 32)' but that was bad, because
// CellKey.Two encodes (i, j) as
// (i * Integer.MAX_VALUE + j), which is practically the same as
// (i << 32, j). If i and j were
// both k bits long, all of the hashcodes were k bits long too!
return (int) (key ^ (key >>> 11) ^ (key >>> 24));
}
public boolean equals(Object o) {
if (o instanceof CellInfo) {
CellInfo that = (CellInfo) o;
return that.key == this.key;
} else {
return false;
}
}
/**
* Returns the formatted value of the Cell
* @return formatted value of the Cell
*/
String getFormatValue() {
return valueFormatter.format(value, formatString);
}
}
/**
* API for the creation and
* lookup of {@link CellInfo} objects. There are two implementations,
* one that uses a Map for storage and the other uses an ObjectPool.
*/
interface CellInfoContainer {
/**
* Returns the number of CellInfo objects in this container.
* @return the number of CellInfo objects.
*/
int size();
/**
* Reduces the size of the internal data structures needed to
* support the current entries. This should be called after
* all CellInfo objects have been added to container.
*/
void trimToSize();
/**
* Removes all CellInfo objects from container. Does not
* change the size of the internal data structures.
*/
void clear();
/**
* Creates a new CellInfo object, adds it to the container
* a location pos
and returns it.
*
* @param pos where to store CellInfo object.
* @return the newly create CellInfo object.
*/
CellInfo create(int[] pos);
/**
* Gets the CellInfo object at the location pos
.
*
* @param pos where to find the CellInfo object.
* @return the CellInfo found or null.
*/
CellInfo lookup(int[] pos);
}
/**
* Implementation of {@link CellInfoContainer} which uses a {@link Map} to
* store CellInfo Objects.
*
*
Note that the CellKey point instance variable is the same
* Object (NOT a copy) that is used and modified during
* the recursive calls to executeStripe - the
* create
method relies on this fact.
*/
static class CellInfoMap implements CellInfoContainer {
private final Map cellInfoMap;
private final CellKey point;
/**
* Creates a CellInfoMap
*
* @param point Cell position
*/
CellInfoMap(CellKey point) {
this.point = point;
this.cellInfoMap = new HashMap();
}
public int size() {
return this.cellInfoMap.size();
}
public void trimToSize() {
// empty
}
public void clear() {
this.cellInfoMap.clear();
}
public CellInfo create(int[] pos) {
CellKey key = this.point.copy();
CellInfo ci = this.cellInfoMap.get(key);
if (ci == null) {
ci = new CellInfo(0);
this.cellInfoMap.put(key, ci);
}
return ci;
}
public CellInfo lookup(int[] pos) {
CellKey key = CellKey.Generator.newCellKey(pos);
return this.cellInfoMap.get(key);
}
}
/**
* Implementation of {@link CellInfoContainer} which uses an
* {@link ObjectPool} to store {@link CellInfo} Objects.
*
* There is an inner interface (CellKeyMaker
) and
* implementations for 0 through 4 axes that convert the Cell
* position integer array into a long.
*
*
* It should be noted that there is an alternate approach.
* As the executeStripe
* method is recursively called, at each call it is known which
* axis is being iterated across and it is known whether or
* not the Position object for that axis is a List or just
* an Iterable. It it is a List, then one knows the real
* size of the axis. If it is an Iterable, then one has to
* use one of the MAX_AXIS_SIZE values. Given that this information
* is available when one recursives down to the next
* executeStripe
call, the Cell ordinal, the position
* integer array could converted to an long
, could
* be generated on the call stack!! Just a thought for the future.
*/
static class CellInfoPool implements CellInfoContainer {
/**
* The maximum number of Members, 2,147,483,647, that can be any given
* Axis when the number of Axes is 2.
*/
protected static final long MAX_AXIS_SIZE_2 = 2147483647;
/**
* The maximum number of Members, 2,000,000, that can be any given
* Axis when the number of Axes is 3.
*/
protected static final long MAX_AXIS_SIZE_3 = 2000000;
/**
* The maximum number of Members, 50,000, that can be any given
* Axis when the number of Axes is 4.
*/
protected static final long MAX_AXIS_SIZE_4 = 50000;
/**
* Implementations of CellKeyMaker convert the Cell
* position integer array to a long
.
*/
interface CellKeyMaker {
long generate(int[] pos);
}
/**
* For axis of size 0.
*/
static class Zero implements CellKeyMaker {
public long generate(int[] pos) {
return 0;
}
}
/**
* For axis of size 1.
*/
static class One implements CellKeyMaker {
public long generate(int[] pos) {
return pos[0];
}
}
/**
* For axis of size 2.
*/
static class Two implements CellKeyMaker {
public long generate(int[] pos) {
long l = pos[0];
l += (MAX_AXIS_SIZE_2 * (long) pos[1]);
return l;
}
}
/**
* For axis of size 3.
*/
static class Three implements CellKeyMaker {
public long generate(int[] pos) {
long l = pos[0];
l += (MAX_AXIS_SIZE_3 * (long) pos[1]);
l += (MAX_AXIS_SIZE_3 * MAX_AXIS_SIZE_3 * (long) pos[2]);
return l;
}
}
/**
* For axis of size 4.
*/
static class Four implements CellKeyMaker {
public long generate(int[] pos) {
long l = pos[0];
l += (MAX_AXIS_SIZE_4 * (long) pos[1]);
l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * (long) pos[2]);
l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4
* (long) pos[3]);
return l;
}
}
private final ObjectPool cellInfoPool;
private final CellKeyMaker cellKeyMaker;
CellInfoPool(int axisLength) {
this.cellInfoPool = new ObjectPool();
this.cellKeyMaker = createCellKeyMaker(axisLength);
}
CellInfoPool(int axisLength, int initialSize) {
this.cellInfoPool = new ObjectPool(initialSize);
this.cellKeyMaker = createCellKeyMaker(axisLength);
}
private static CellKeyMaker createCellKeyMaker(int axisLength) {
switch (axisLength) {
case 0:
return new Zero();
case 1:
return new One();
case 2:
return new Two();
case 3:
return new Three();
case 4:
return new Four();
default:
throw new RuntimeException(
"Creating CellInfoPool with axisLength=" + axisLength);
}
}
public int size() {
return this.cellInfoPool.size();
}
public void trimToSize() {
this.cellInfoPool.trimToSize();
}
public void clear() {
this.cellInfoPool.clear();
}
public CellInfo create(int[] pos) {
long key = this.cellKeyMaker.generate(pos);
return this.cellInfoPool.add(new CellInfo(key));
}
public CellInfo lookup(int[] pos) {
long key = this.cellKeyMaker.generate(pos);
return this.cellInfoPool.add(new CellInfo(key));
}
}
static Axis mergeAxes(
Axis axis1,
Axis axis2,
RolapEvaluator evaluator,
boolean ordered)
{
if (axis1 == null) {
return axis2;
}
List posList1 = axis1.getPositions();
List posList2 = axis2.getPositions();
int arrayLen = -1;
if (posList1 instanceof RolapAxis.PositionListBase) {
if (posList1.isEmpty()) {
return axis2;
}
arrayLen = posList1.get(0).size();
}
if (axis1 instanceof RolapAxis.SingleEmptyPosition) {
return axis2;
}
if (axis1 instanceof RolapAxis.NoPosition) {
return axis2;
}
if (posList2 instanceof RolapAxis.PositionListBase) {
if (posList2.isEmpty()) {
return axis1;
}
arrayLen = posList2.get(0).size();
}
if (axis2 instanceof RolapAxis.SingleEmptyPosition) {
return axis1;
}
if (axis2 instanceof RolapAxis.NoPosition) {
return axis1;
}
if (arrayLen == -1) {
// Avoid materialization of axis
arrayLen = 0;
for (Position p1 : posList1) {
arrayLen += p1.size();
break;
}
// reset to start of List
posList1 = axis1.getPositions();
}
if (arrayLen == 1) {
// single Member per position
// LinkedHashSet gives O(n log n) additions (versus O(n ^ 2) for
// ArrayList, and preserves order (versus regular HashSet).
LinkedHashSet orderedSet = new LinkedHashSet();
for (Position p1 : posList1) {
for (Member m1 : p1) {
orderedSet.add(m1);
}
}
for (Position p2 : posList2) {
for (Member m2 : p2) {
orderedSet.add(m2);
}
}
return new RolapAxis.MemberList(
Arrays.asList(
orderedSet.toArray(new Member[orderedSet.size()])));
} else {
// array of Members per position
Set> set = new HashSet>();
List list = new ArrayList();
for (Position p1 : posList1) {
if (set.add(p1)) {
Member[] members = new Member[arrayLen];
for (int i = 0; i < p1.size(); i++) {
members[i] = p1.get(i);
}
list.add(members);
}
}
int halfWay = list.size();
for (Position p2 : posList2) {
if (set.add(p2)) {
Member[] members = new Member[arrayLen];
for (int i = 0; i < p2.size(); i++) {
Member m2 = p2.get(i);
members[i] = m2;
}
list.add(members);
}
}
// if there are unique members on both axes and no order function,
// sort the list to ensure default order
if (halfWay > 0 && halfWay < list.size() && !ordered) {
Member[] membs = list.get(0);
int membsSize = membs.length;
ValueCalc valCalc =
new ValueCalc(
new DummyExp(new ScalarType()));
FunUtil.sortTuples(
evaluator,
list,
list,
valCalc,
false,
false,
membsSize);
}
return new RolapAxis.MemberArrayList(list);
}
}
}
// End RolapResult.java