blob: 0bc5ce18c6f3b2dda72688207c91bd68b43340a4 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.olap
{
import flash.utils.Dictionary;
import mx.collections.IList;
import mx.core.mx_internal;
use namespace mx_internal;
/**
* @private
*/
public class QueryCubeBuilder
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
private var prevNodeAtLevel:Array; //of CubeNodes
private var allNodeAtLevel:Array; // of CubeNodes
private var prevValueAtLevel:Array; // type depends on data (mostly string)
// the level at which the new property is going to be placed
private var currentLevel:int;
private var prevLevel:int;
private var nextLevel:int;
//the nodes whose values are going to be aggregated as the above node
//value has changed.
private var closingNodesBelow:Array;
private var closingValues:Array;
//list of measures in the cube
private var measureMembers:IList;
// the maps holding nodes which are used to finalize the aggregation
// simple aggregation map
private var computeEndMap:Dictionary = new Dictionary(false);
//aggregation of aggregations map
private var computeObjEndMap:Dictionary = new Dictionary(false);
private var measureMap:Object = {};
private var allLevelNames:Array;
private var nodesToClose:Array = [];
private var nodesToAggregate:Array;
private var dataToAggregate:Array;
private var measureToAggregate:Array;
private var valueToAggregate:Array;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// cube
//----------------------------------
private var _cube:OLAPCube;
public function set cube(c:IOLAPCube):void
{
_cube = c as OLAPCube;
}
//----------------------------------
// rootNode
//----------------------------------
/**
* Top most node which can be used to access the query cube.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var rootNode:CubeNode;
//----------------------------------
// allNodePropertyName
//----------------------------------
/**
* The property name used to refer to the property
* pointing at the aggregation of nodes below a CubeNode
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var allNodePropertyName:String ="(QAll)";
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
public function initNodeBuilding():void
{
prevNodeAtLevel = [];
allNodeAtLevel = [];
prevValueAtLevel = [];
measureMap = {};
//compute for all the measures
measureMembers = _cube.findDimension("Measures").members;
for each (var measure:OLAPMeasure in measureMembers)
{
computeEndMap[measure] = [];
computeObjEndMap[measure] = [];
measureMap[measure.name] = measure;
}
nodesToClose = [];
nodesToAggregate = [];
dataToAggregate = [];
measureToAggregate = [];
valueToAggregate = [];
//prepare a dictionary of all level names
allLevelNames = [];
for each (var d:OLAPDimension in _cube.dimensions)
{
if (d.isMeasure)
continue;
for each (var a:OLAPAttribute in d.attributes)
{
allLevelNames.push(a.findMember(a.allMemberName).uniqueName);
}
for each (var h:OLAPHierarchy in d.hierarchies)
{
allLevelNames.push(h.findMember(h.allMemberName).uniqueName);
}
}
}
public function moveToNextRound():void
{
currentLevel = 0;
prevLevel = -1;
nextLevel = 1;
}
private function sortCubeNodes(p1:CubeNode, p2:CubeNode):int
{
if (p1.level == p2.level)
return 0;
if (p1.level < p2.level)
return 1;
return -1;
}
/**
* @private
* Check whether the cubeNode has any entry for a (All) field
* from any level.
*/
private function allPropertyPresent(node:CubeNode):Boolean
{
for (var p:String in node)
{
if (allLevelNames.indexOf(p) > -1)
return true;
}
return false;
}
public function completeNodeBuilding():void
{
//TODO initializing rootNode twice
rootNode = prevNodeAtLevel[0];
closingNodesBelow = prevNodeAtLevel.reverse();
closingValues = prevValueAtLevel.reverse();
var tempLevel:int = closingNodesBelow.length-1;
var p:String;
var n:int = closingNodesBelow.length;
for (var i:int = 0; i < n; ++i)
{
if (tempLevel < 1)
continue;
var closingValue:Object = closingValues[i];
//OLAPTrace.traceMsg("Closing value:" + closingValues[i], //OLAPTrace.TRACE_LEVEL_2);
var closingNode:Object = closingNodesBelow[i];
if (closingNode[closingValue] is CubeNode)
{
if (closingNode[allNodePropertyName] is CubeNode)
{
var index:int = nodesToClose.indexOf(closingNode);
if (index == -1)
nodesToClose.push(closingNode);
}
}
--tempLevel;
}
n = nodesToAggregate.length;
for (i = 0; i < n; ++ i)
{
var prevNode:Object = nodesToAggregate[i];
if (prevNode.numCells > 2 && allLevelNames.indexOf(valueToAggregate[i]) > -1)
continue;
var tempAllNode:Object;
if (prevNode.numCells == 2)
{
if (prevNode.hasOwnProperty(allNodePropertyName))
{
for (p in prevNode)
{
if (p == allNodePropertyName)
continue;
if (prevNode[allNodePropertyName] != prevNode[p])
prevNode[allNodePropertyName] = prevNode[p];
break;
}
continue;
}
}
if (prevNode[allNodePropertyName] is CubeNode || prevNode[allNodePropertyName] is Number)
tempAllNode = prevNode[allNodePropertyName] = new SummaryNode;
else
tempAllNode = prevNode[allNodePropertyName];
aggregateAllValue(tempAllNode, dataToAggregate[i], measureToAggregate[i]);
}
// we sort the nodes in their order of level to do aggregation
// bottom up to get correct results
nodesToClose.sort(sortCubeNodes);
// close all nodes pending
for each (var node:CubeNode in nodesToClose)
{
var allPropPresent:Boolean = allPropertyPresent(node);
for (p in node)
{
if (p == allNodePropertyName)
continue;
// if there are other paths leading to the nodes below
// do not include the all property in aggregation because that would
// lead to multiple aggregations of same values
if (node.numCells > 2 && allLevelNames.indexOf(p) > -1)
continue;
// if we have only one extra entry in the node other than allNodeProperty
// then we can optimize.
if (allPropPresent && node.numCells == 3)
node[allNodePropertyName] = node[p];
else
accumValuesFromNode(node[allNodePropertyName], node[p]);
}
}
for (p in rootNode)
{
if (p == allNodePropertyName || (rootNode.numCells > 2 && allLevelNames.indexOf(p) > -1))
continue;
// can we accumulate everything from the tree to the all tree here?
accumValuesFromNode(rootNode[allNodePropertyName], rootNode[p]);
}
var temp:Array;
var y:Object;
var measure:OLAPMeasure;
var aggregator:IOLAPCustomAggregator;
for (var x:Object in computeEndMap)
{
measure = x as OLAPMeasure;
aggregator = measure.aggregator as IOLAPCustomAggregator;
temp = computeEndMap[x];
for each (y in temp)
y[measure.name] = aggregator.computeEnd(y[measure.name], measure.dataField);
}
for (x in computeObjEndMap)
{
measure = x as OLAPMeasure;
aggregator = measure.aggregator as IOLAPCustomAggregator;
temp = computeObjEndMap[x];
for each (y in temp)
y[measure.name] = aggregator.computeObjectEnd(y[measure.name], measure.dataField);
}
}
public function addValueToNodeBuilder(value:Object, currentData:Object):void
{
// decide whether to create a new node or use the previous node
var prevNode:CubeNode = prevNodeAtLevel[currentLevel];
var prevValue:Object = prevValueAtLevel[currentLevel];
var closingValue:Object;
var closingNode:Object
if (prevValue)
{
if (prevValue == value)
{
// continue to process the same value
// update nodes
//OLAPTrace.traceMsg("Continue with value:" + prevValue, OLAPTrace.TRACE_LEVEL_2);
}
else
{
// no more processing of this value.
// time to compute update all nodes etc
// close all nodes below this node
closingNodesBelow = prevNodeAtLevel.splice(nextLevel).reverse();
closingValues = prevValueAtLevel.splice(nextLevel).reverse();
var tempLevel:int = currentLevel + closingNodesBelow.length;
var n:int = closingNodesBelow.length;
for (var i:int = 0; i < n; ++i)
{
closingValue = closingValues[i];
//OLAPTrace.traceMsg("Closing value:" + closingValues[i], OLAPTrace.TRACE_LEVEL_2);
closingNode = closingNodesBelow[i];
if (closingNode[closingValue] is CubeNode)
{
if (tempLevel != 0)
{
if (closingNode[allNodePropertyName] is CubeNode)
{
if (nodesToClose.indexOf(closingNode) == -1)
nodesToClose.push(closingNode);
}
}
}
--tempLevel;
}
//OLAPTrace.traceMsg("Closing value:" + prevValue, //OLAPTrace.TRACE_LEVEL_2);
closingValue = prevValue;
closingNode = prevNode;
if (closingNode[closingValue] is CubeNode)
{
if (currentLevel != 0)
{
if (closingNode[allNodePropertyName] is CubeNode)
{
if (nodesToClose.indexOf(closingNode) == -1)
nodesToClose.push(closingNode);
}
}
}
//OLAPTrace.traceMsg("New value:" + value, //OLAPTrace.TRACE_LEVEL_2);
}
}
var allNode:CubeNode = allNodeAtLevel[currentLevel];
if (!prevNode)
{
//OLAPTrace.traceMsg("Creating new node: " + value, //OLAPTrace.TRACE_LEVEL_2);
// - create a new node, put the pointer to the ALL node, add new key to the ALL node
var newNode:CubeNode = new CubeNode(currentLevel);
// create a all node to summaries the cells of this node
newNode[allNodePropertyName] = new CubeNode(currentLevel+1);
++newNode.numCells;
//TODO initializing rootNode twice
if (currentLevel == 0 && prevNodeAtLevel.length == 0)
rootNode = newNode;
prevNodeAtLevel[currentLevel] = newNode;
if (!allNode)
{
// for the top most level we would have only one node
// which is also the all node
// for other levels we have one node which is a all node
// containing aggr value of all cells in the nodes at that level
if (currentLevel > 0)
{
allNodeAtLevel[currentLevel] = allNode = new CubeNode(currentLevel+1);
allNode[allNodePropertyName] = {};
allNodeAtLevel[prevLevel][allNodePropertyName] = allNode;
++allNode.numCells;
}
else
{
// special case of zero level
allNodeAtLevel[currentLevel] = newNode;
}
}
prevNode = newNode;
}
else
{
// add a new cell/property to the previous node if it doesn't exist
if (!prevNode.hasOwnProperty(value))
{
//prevNode[value] = 0;
prevNodeAtLevel.splice(nextLevel);
prevValueAtLevel.splice(nextLevel);
}
else
{
if (!prevNodeAtLevel[nextLevel])
{
//assigning a summary node was causing a issue(1096)
if (prevNode[value] is CubeNode)
{
// a node already seems to be present for this value
prevNodeAtLevel[nextLevel] = prevNode[value];
// we cannot set what the previous value for next level should be
// because we have already cleared it and lost it.
}
}
}
}
// make the node at previous level point to the node
if (currentLevel > 0)
{
var prevLevelNode:Object = prevNodeAtLevel[prevLevel];
if (!prevLevelNode.hasOwnProperty(prevValueAtLevel[prevLevel]))
{
var prevLevelValue:Object = prevValueAtLevel[prevLevel];
prevLevelNode[prevLevelValue] = newNode;
++prevLevelNode.numCells;
}
}
prevValueAtLevel[currentLevel] = value;
++prevLevel;
++currentLevel;
++nextLevel;
}
public function addMeasureValueToNode(value:Object, currentData:Object, measureToUpdate:OLAPMeasure):void
{
var prevNode:CubeNode = prevNodeAtLevel[currentLevel-1];
var prevValue:Object = prevValueAtLevel[currentLevel-1];
if (!prevNode.hasOwnProperty(value))
{
prevNode[value] = new SummaryNode;
++prevNode.numCells;
}
aggregateAllValue(prevNode[value], currentData, measureToUpdate);
nodesToAggregate.push(prevNode);
dataToAggregate.push(currentData);
measureToAggregate.push(measureToUpdate);
valueToAggregate.push(value);
}
private function addValueToNode(node:Object, name:String,
value:Object, measure:OLAPMeasure):void
{
var aggregator:IOLAPCustomAggregator;
if (value is Number)
{
aggregator = measure.aggregator as IOLAPCustomAggregator;
if (!node.hasOwnProperty(name))
{
node[name] = aggregator.computeBegin(name);
computeEndMap[measure].push(node);
//if (node is SummaryNode)
// node["measures"][name] = measure;
aggregator.computeLoop(node[name], name, value);
}
else
{
aggregator.computeLoop(node[name], name, value);
}
}
else
{
var temp:Object;
if (!node.hasOwnProperty(name))
{
node[name] = temp = new SummaryNode;
++node.numCells;
for (var p:String in value)
{
if (value is SummaryNode)
measure = measureMap[p];
if (measure)
{
aggregator = measure.aggregator as IOLAPCustomAggregator;
if (value[p] is Number)
{
temp[name] = aggregator.computeBegin(p);
computeEndMap[measure].push(temp);
aggregator.computeLoop(temp[name], p, value[p]);
}
else
{
if (temp.hasOwnProperty(p))
{
aggregator.computeObjectLoop(temp[p], value[p]);
}
else
{
temp[p] = aggregator.computeObjectBegin(value[p]);
computeObjEndMap[measure].push(temp);
}
}
}
}
}
else
{
if (value is SummaryNode && !(node[name] is SummaryNode))
{
var incr:Boolean = false;
if (!node.hasOwnProperty(name))
incr = true;
temp = node[name] = new SummaryNode;
if (incr)
++node.numCells;
}
else
{
temp = node[name];
}
for (p in value)
{
if (temp.hasOwnProperty(p))
{
measure = measureMap[p];
aggregator = measure.aggregator as IOLAPCustomAggregator;
aggregator.computeObjectLoop(temp[p], value[p]);
}
else
{
if (temp is SummaryNode)
measure = measureMap[p];
if (measure)
{
aggregator = measure.aggregator as IOLAPCustomAggregator;
if (value[p] is Number)
{
temp[p] = aggregator.computeBegin(p);
aggregator.computeLoop(temp[p], p, value[p]);
}
else
{
temp[p] = aggregator.computeObjectBegin(value[p]);
computeObjEndMap[measure].push(temp);
}
}
}
}
}
}
}
private function accumValuesFromNode(target:Object,
source:Object):void
{
for (var p:String in source)
{
if (p == allNodePropertyName)
continue;
// if there are other paths leading to the nodes below
// do not include the all property in aggregation because that would
// lead to multiple aggregations of same values
if (source.numCells > 2 && allLevelNames.indexOf(p) > -1)
continue;
var value:Object = source[p];
if (value is CubeNode)
{
var newNode:CubeNode;
if (target[p] is CubeNode)
{
newNode = target[p];
}
else
{
target[p] = newNode = new CubeNode(value.level);
++target.numCells;
}
accumValuesFromNode(newNode, value);
}
else
{
addValueToNode(target, p, value, null);
}
}
if (target.numCells == 1)
{
for (p in target)
{
target[allNodePropertyName] = target[p];
break;
}
++target.numCells;
}
else if (target.numCells == 2 && target.hasOwnProperty(allNodePropertyName))
{
for (p in target)
{
if (p == allNodePropertyName)
continue;
if (target[allNodePropertyName] != target[p])
target[allNodePropertyName] = target[p];
break;
}
}
else
{
var done:Boolean = false;
var otherProperty:String;
for (p in target)
{
if (p == allNodePropertyName)
continue;
if (target[allNodePropertyName] == target[p])
{
value = target[p];
if (value is CubeNode)
{
target[allNodePropertyName] = newNode = new CubeNode(value.level);
accumValuesFromNode(newNode, value);
for (otherProperty in target)
{
if (otherProperty == allNodePropertyName ||
otherProperty == p)
continue;
//now we have the other property.
accumValuesFromNode(newNode, source[otherProperty]);
}
done = true;
}
else
{
target[allNodePropertyName] = null;
addValueToNode(target, allNodePropertyName, value, null);
for (otherProperty in target)
{
if (otherProperty == allNodePropertyName ||
otherProperty == p)
continue;
//now we have the other property.
addValueToNode(target, allNodePropertyName, target[otherProperty], null);
}
done = true;
}
break;
}
}
if (!done)
{
p = allNodePropertyName;
value = source[p];
if (value is CubeNode)
{
if (target[p] is CubeNode)
{
newNode = target[p];
}
else
{
target[p] = newNode = new CubeNode(value.level);
++target.numCells;
}
accumValuesFromNode(newNode, value);
}
else
{
addValueToNode(target, p, value, null);
}
}
}
}
public function aggregateAllValue(node:Object,/* name:String,*/
value:Object, measure:OLAPMeasure):void
{
var measureName:String = measure.name;
var aggregator:IOLAPCustomAggregator = measure.aggregator as IOLAPCustomAggregator;
var prop:String = "saved_" + measureName;
var temp:Object;
if (!node.hasOwnProperty(measureName))
{
temp = node;
if (measure)
{
if (temp.hasOwnProperty(measureName))
{
aggregator.computeObjectLoop(temp[measureName], value[prop]);
}
else
{
temp[measureName] = aggregator.computeObjectBegin(value[prop]);
computeObjEndMap[measure].push(temp);
}
}
}
else
{
temp = node;
if (temp.hasOwnProperty(measureName))
{
aggregator.computeObjectLoop(temp[measureName], value[prop]);
}
else
{
if (value[prop] is Number)
{
temp[prop] = aggregator.computeBegin(prop);
aggregator.computeLoop(temp[prop], prop, value[prop]);
}
else
{
temp[measureName] = aggregator.computeObjectBegin(value[prop]);
computeObjEndMap[measure].push(temp);
}
}
}
}
}
}