blob: 989a4e4cb432b358c7b8b845063fe96e4fc9e068 [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;
/**
* @private
*/
public class CubeNodeBuilder
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
private var prevNodeAtLevel:Array; // of CubeNodes
private var allNodeAtLevel:Array; // of CubeNodes
private var prevValueAtLevel:Array;
// 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;
private var measuresLength:int ;
/**
* @private
* Maps containing nodes which need to be passed to the aggrgator
* to compute the final result. The measure object is used as a key.
*
*/
// 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
* An array of properites in the rootNode.
* This is used to aggregate each property asychronously
* to avoid player time out.
*
*/
private var rootCellValues:Array;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
private var _cube:OLAPCube;
public function set cube(c:IOLAPCube):void
{
_cube = c as OLAPCube;
}
/**
* The top most node of the cube.
* All navigations begin from this node.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var rootNode:CubeNode;
/**
* 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 ="(All)";
/**
* Indicates the last level of data other than measures.
* This is used to read the measure values from the data item
* when processing reaches this level.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public var levelPreviousToMeasuresIndex:int;
//total number of nodes in the cube. (used for perf improvement analysis)
//private var totalNodes:int;
//count of cubes collapsed (used for perf improvement analysis)
//private var nodesUpdated:int;
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Starts the process of building the cube by initializing
* the members.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function initNodeBuilding():void
{
prevNodeAtLevel = [];
allNodeAtLevel = [];
prevValueAtLevel = [];
//compute for all the measures
measureMembers = _cube.findDimension("Measures").members;
measuresLength = measureMembers.length;
for each (var measure:OLAPMeasure in measureMembers)
{
computeEndMap[measure] = [];
computeObjEndMap[measure] = [];
measureMap[measure.name] = measure;
}
}
/**
* Instructs the builder to start processing the next data item.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function moveToNextRound():void
{
currentLevel = 0;
prevLevel = -1;
nextLevel = 1;
}
/**
* Finalizes building the cube by computing aggregations at all nodes.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function completeNodeBuilding():Boolean
{
if (prevNodeAtLevel && prevValueAtLevel)
{
// close all nodes pending
//TODO initializing rootNode twice
rootNode = prevNodeAtLevel[0];
closingNodesBelow = prevNodeAtLevel.reverse();
closingValues = prevValueAtLevel.reverse();
var tempLevel:int = closingNodesBelow.length - 1;
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)
{
//if there are is only one property (other than (all) ) we short circuit
if (closingNode.numCells == 2)
closingNode[allNodePropertyName] = closingNode[closingValue];
else
accumValuesFromNode(closingNode[allNodePropertyName], closingNode[closingValue]);
}
}
--tempLevel;
}
prevNodeAtLevel = null;
prevValueAtLevel = null;
allNodeAtLevel = null;
rootCellValues = [];
for (var rootCell:String in rootNode)
{
if (rootCell == allNodePropertyName)
continue;
rootCellValues.push(rootCell);
}
return false;
}
//aggregate the top node
while(rootCellValues.length)
{
rootCell = rootCellValues.pop();
// can we accumulate everything from the tree to the all tree here?
accumValuesFromNode(rootNode[allNodePropertyName], rootNode[rootCell]);
return false;
}
//finalize all aggregation computation
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["saved_" + measure.name] = y[measure.name];
y[measure.name] = aggregator.computeEnd(y[measure.name], measure.dataField);
}
delete computeEndMap[x];
}
for (x in computeObjEndMap)
{
measure = x as OLAPMeasure;
aggregator = measure.aggregator as IOLAPCustomAggregator;
temp = computeObjEndMap[x];
for each (y in temp)
{
y["saved_"+measure.name] = y[measure.name];
y[measure.name] = aggregator.computeObjectEnd(y[measure.name], measure.dataField);
}
delete computeObjEndMap[x];
}
computeEndMap = new Dictionary(false);
computeObjEndMap = new Dictionary(false);
//collapse CubeNodes which have only one property
//The aggregation property would point to the single property itself
optimizeCube();
return true;
}
/**
* Adds a new value to the cube. If the value is different from the previous value
* at the current level adds the new value as a property to the CubeNode and creates
* a new CubeNode below. Also creates a CubeNode for aggregating the properties in the
* new CubeNode.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function addValueToNodeBuilder(values:Object, currentData:Object):void
{
var i:int = 0;
for each(var value:Object in values)
{
// 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 closingNodeLength:int = closingNodesBelow.length;
for (i= 0; i < closingNodeLength; ++i)
{
if (tempLevel != 0)
{
closingValue = closingValues[i];
//OLAPTrace.traceMsg("Closing value:" + closingValues[tempIndex], OLAPTrace.TRACE_LEVEL_2);
closingNode = closingNodesBelow[i];
if (closingNode[closingValue] is CubeNode)
{
if (closingNode[allNodePropertyName] is CubeNode)
{
if (tempLevel > currentLevel && closingNode.numCells == 2)
closingNode[allNodePropertyName] = closingNode[closingValue];
else
accumValuesFromNode(closingNode[allNodePropertyName], closingNode[closingValue]);
}
}
}
--tempLevel;
}
//OLAPTrace.traceMsg("Closing value:" + prevValue, //OLAPTrace.TRACE_LEVEL_2);
closingValue = prevValue;
closingNode = prevNode;
if (currentLevel != 0 && closingNode[closingValue] is CubeNode)
{
if (closingNode[allNodePropertyName] is CubeNode)
accumValuesFromNode(closingNode[allNodePropertyName], closingNode[closingValue]);
}
//OLAPTrace.traceMsg("New value:" + value, //OLAPTrace.TRACE_LEVEL_2);
}
}
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;
var allNode:CubeNode = allNodeAtLevel[currentLevel];
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);
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))
{
prevNodeAtLevel.splice(nextLevel);
prevValueAtLevel.splice(nextLevel);
}
}
// are we at the last level?
// if so we take up measures computation
if (currentLevel == levelPreviousToMeasuresIndex)
{
if (!prevNode.hasOwnProperty(value))
{
prevNode[value] = new SummaryNode;
++prevNode.numCells;
}
var temp:Object;
if (prevNode[allNodePropertyName] is CubeNode || prevNode[allNodePropertyName] is Number)
{
temp = prevNode[allNodePropertyName] = new SummaryNode;
}
else
{
temp = prevNode[allNodePropertyName];
}
for (i = 0; i < measuresLength; ++i)
{
var measureMember:OLAPMember = measureMembers.getItemAt(i) as OLAPMember;
var newDataValue:Object = currentData[measureMember.dataField];
//force convertion of measure value to a number.
//xml dataType runs into problems with value of 0
if (typeof(newDataValue) == "xml")
newDataValue = Number(newDataValue.toString());
newDataValue = Number(newDataValue);
addValueToNode(prevNode[value], measureMember.name, newDataValue, measureMember as OLAPMeasure, currentData);
addValueToNode(temp, measureMember.name, newDataValue, measureMember as OLAPMeasure, currentData);
}
}
// 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;
}
}
/**
* @private
*/
private function optimizeCube():void
{
var total:int =0;
//totalNodes = 0;
//nodesUpdated = 0;
//start from root node and count the number of nodes which have only one cell.
//skip the allValue because we are not updating the numCells there.
if (rootNode)
total = findNodeWithOneCell(rootNode);
//trace(nodesUpdated + "/" + total + "/" + totalNodes);
}
/**
* @private
* Recursively collapses CubeNodes
*
*/
private function findNodeWithOneCell(node:CubeNode):int
{
//++totalNodes;
var total:int = 0;
if (node.numCells == 2)
++total;
for (var p:String in node)
{
//if (x == allNodePropertyName)
// continue;
if (node.numCells == 2)
{
if (node[allNodePropertyName] != node[p])
{
delete node[allNodePropertyName];
node[allNodePropertyName] = node[p];
//++nodesUpdated;
}
}
var cNode:CubeNode = node[p] as CubeNode;
if (cNode)
{
total += findNodeWithOneCell(cNode);
}
}
return total;
}
/**
* @private
* Adds a measure value to the node.
* Invokes aggregator functions to compute the value.
*
*/
private function addValueToNode(node:Object, name:String,
value:Object, measure:OLAPMeasure, rowData:Object):void
{
var aggregator:IOLAPCustomAggregator;
//if the value is a number we need to compute the summary.
if (value is Number)
{
aggregator = measure.aggregator as IOLAPCustomAggregator;
if (!node.hasOwnProperty(name))
{
node[name] = aggregator.computeBegin(measure.dataField);
computeEndMap[measure].push(node);
aggregator.computeLoop(node[name], measure.dataField, /*value,*/ rowData);
}
else
{
aggregator.computeLoop(node[name], measure.dataField, /*value,*/ rowData);
}
}
else
{
var temp:Object;
if (!node.hasOwnProperty(name))
{
node[name] = temp = new SummaryNode;
++node.numCells;
for (var p:String in value)
{
measure = measureMap[p];
aggregator = measure.aggregator as IOLAPCustomAggregator;
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)
{
measure = measureMap[p];
aggregator = measure.aggregator as IOLAPCustomAggregator;
if (temp.hasOwnProperty(p))
{
aggregator.computeObjectLoop(temp[p], value[p]);
}
else
{
temp[p] = aggregator.computeObjectBegin(value[p]);
computeObjEndMap[measure].push(temp);
}
}
}
}
}
/**
* @private
* Aggregates all property values from source to target objects recursively.
*
*/
private function accumValuesFromNode(target:Object,
source:Object):void
{
for (var p:String in source)
{
if (p == allNodePropertyName)
{
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, 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 q: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 (q in target)
{
if (q == allNodePropertyName ||
q == p)
continue;
//now we have the other property.
accumValuesFromNode(newNode, source[q]);
}
done = true;
}
else
{
target[allNodePropertyName] = null;
addValueToNode(target, allNodePropertyName, value, null, null);
for (q in target)
{
if (q == allNodePropertyName ||
q == p)
continue;
//now we have the other property.
addValueToNode(target, allNodePropertyName, target[q], null, 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, null);
}
}
}
}
}
}