//////////////////////////////////////////////////////////////////////////////// // // 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); } } } } } }