//////////////////////////////////////////////////////////////////////////////// // // 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 mx.collections.ArrayCollection; import mx.collections.IList; import mx.core.mx_internal; import mx.resources.ResourceManager; use namespace mx_internal; [ResourceBundle("olap")] /** * The OLAPSet class represents a set, * which is used to configure the axis of an OLAP query. * A set consists of zero or more tuples; * a set that does not contain any tuples is known as an empty set. * * @see mx.olap.IOLAPSet * @see mx.olap.OLAPQueryAxis * @see mx.olap.IOLAPResultAxis * @see mx.olap.OLAPResultAxis * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class OLAPSet implements IOLAPSet { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function OLAPSet() { super(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private * flag to indicate the mode in which compareMembers should * return the values. */ private var reverseCompare:Boolean = false; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // tuples //---------------------------------- private var _tuples:Array = []; /** * The tuples contained by this set instance, * as an Array of IOLAPTuple instances. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get tuples():Array { return _tuples; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function addElement(e:IOLAPElement):void { var members:IList = new ArrayCollection; if (e is IOLAPDimension) members.addItem((e as IOLAPDimension).defaultMember); if (e is IOLAPHierarchy) members.addItem((e as IOLAPHierarchy).defaultMember); if (e is IOLAPLevel) members = (e as IOLAPLevel).members; if (e is IOLAPMember) members.addItem(e); var t:OLAPTuple; if (members.length > 0) { var n:int = members.length; for (var i:int = 0; i < n; ++i) { var m:IOLAPMember = members[i]; t = new OLAPTuple(); t.addMember(m); _tuples.push(t); } } else { OLAPTrace.traceMsg("Members were not added for " + e.uniqueName, OLAPTrace.TRACE_LEVEL_1); } } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function addElements(members:IList):void { var t:OLAPTuple; var n:int = members.length; for (var i:int = 0; i < n; ++i) { var m:IOLAPMember = members[i]; t = new OLAPTuple(); t.addMember(m); _tuples.push(t); } } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function addTuple(tuple:IOLAPTuple):void { _tuples.push(tuple); } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function crossJoin(input:IOLAPSet):IOLAPSet { var join:OLAPSet = input as OLAPSet; validateCrossJoin(join); var newSet:OLAPSet = new OLAPSet(); var tuple:OLAPTuple = new OLAPTuple(); for each (var tuple1:OLAPTuple in _tuples) { for each (var tuple2:OLAPTuple in join.tuples) { tuple.addMembers(tuple1.explicitMembers); tuple.addMembers(tuple2.explicitMembers); if (tuple.isValid) { newSet.addTuple(tuple); tuple = new OLAPTuple(); } else { tuple.clear(); } } } return newSet; } /** * @private * Validates a crossjoin defined by an IOLAPSet instance. * * This method throws an error if the crossjoin is invalid. * * @param input The IOLAPSet instance that contains a crossjoin * of two IOLAPSet instances. */ protected function validateCrossJoin(input:IOLAPSet):void { var join:OLAPSet = input as OLAPSet; for each (var t1:OLAPTuple in _tuples) { for each (var t2:OLAPTuple in join.tuples) { var hierarchy:IOLAPHierarchy = findCommonHierarchy(t1, t2); if (hierarchy) { var message:String = ResourceManager.getInstance().getString( "olap", "crossJoinSameHierarchyError", [hierarchy.uniqueName]); throw Error(message); } } } } /** * Returns the common IOLAPHierarchy instance for two tuples, * or null if the tuples do not share a hierarchy. * * @param t1 The first tuple. * * @param t2 The second tuple. * * @return The common IOLAPHierarchy instance for the two tuples, * or null if the tuples do not share a hierarchy. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function findCommonHierarchy(t1:OLAPTuple, t2:OLAPTuple):IOLAPHierarchy { for each (var m1:OLAPMember in t1.explicitMembers) { for each (var m2:OLAPMember in t2.explicitMembers) { if (m1.hierarchy == m2.hierarchy) return m1.hierarchy; } } //no common hierarchy found return null; } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function hierarchize(post:Boolean=false):IOLAPSet { var newSet:OLAPSet = new OLAPSet; reverseCompare = post; // go through all the tuples of the set and arrange them in // their natural order. var arrayIndices:Array = tuples.sort(sortTuple, Array.RETURNINDEXEDARRAY); for each (var index:int in arrayIndices) newSet.addTuple(_tuples[index]); //reset the flag because union also uses compareMembers function. reverseCompare = false; return newSet; } /** * Returns information about the relative location of * two members in the set. * * @param m1 The first member. * * @param m2 The second member. * * @return The following: * * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function compareMembers(m1:IOLAPMember, m2:IOLAPMember):int { //trace("Comparing " + m1.name + " " + m2.name); if (m1 == m2) return 0; while (true) { if (m1 && !m2) return reverseCompare ? -1 : 1; if (!m1 && m2) return reverseCompare ? 1 : -1; if (m1.level.depth < m2.level.depth) { m2 = m2.parent; if (m1 == m2) return reverseCompare ? 1 : -1; } else if (m1.level.depth > m2.level.depth) { m1 = m1.parent; if (m1 == m2) return reverseCompare ? -1 : 1; } else { if (m1.parent == m2.parent) { var compResult:int = m1.name.localeCompare(m2.name); if (compResult == 0) return 0; else if (compResult > 0) return 1; else return -1; } m1 = m1.parent; m2 = m2.parent; } } return 0; } /** * Returns information about the relative location of * two tuples in the set. * * @param t1 The first tuple. * * @param t2 The second tuple. * * @return The following: * * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function sortTuple(t1:OLAPTuple, t2:OLAPTuple):int { var t1Mems:Array = t1.explicitMembers.toArray(); var t2Mems:Array = t2.explicitMembers.toArray(); var n:int = t1Mems.length; for (var i:int = 0; i < n; ++i) { var m1:IOLAPMember = t1Mems[i]; var m2:IOLAPMember = t2Mems[i]; var result:int = compareMembers(m1, m2); if (result != 0) break; } return result; } /** * @private */ private function isDuplicate(t1:OLAPTuple, t2:OLAPTuple):Boolean { var m1:IList = t1.explicitMembers; var m2:IList = t2.explicitMembers; var n:int = m1.length; for (var i:int = 0; i < n; ++i) { if (m1.getItemAt(i) != m2.getItemAt(i)) return false; } return true; } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function union(secondSet:IOLAPSet):IOLAPSet { //TODO should we handle null sets? var input:OLAPSet = secondSet as OLAPSet; var newSet:OLAPSet = new OLAPSet; //check for same hierarchy var ts1:Array = tuples; var ts2:Array = input.tuples; //create a hierarchy array which can be used for comparision var hierarchies:Array = []; var t1:IOLAPTuple = ts1[0]; var m1:IList = t1.explicitMembers; var explicitMemberLength:int = m1.length; for (var i:int = 0; i < explicitMemberLength; ++i) { hierarchies.push(m1.getItemAt(i).hierarchy); } checkTupleHierarchies(this, explicitMemberLength, hierarchies); checkTupleHierarchies(input, explicitMemberLength, hierarchies); var newTuples:Array = tuples.concat(input._tuples); //TODO remove duplicates var arrayIndices:Array = newTuples.sort(sortTuple, Array.RETURNINDEXEDARRAY); var end:int = arrayIndices.length - 1; var duplicates:Array = []; for (i = 0; i < end; ++i) { if (isDuplicate(newTuples[arrayIndices[i]], newTuples[arrayIndices[i + 1]])) { duplicates.push(arrayIndices[i]); } } //we remove items backwards as otherwise indices will change //with each item removal duplicates = duplicates.sort(Array.NUMERIC | Array.DESCENDING); for each (i in duplicates) newTuples.splice(i, 1); newSet._tuples = newTuples; return newSet; } /** * @private */ private function checkTupleHierarchies(olapSet:OLAPSet, length:int, hierarchies:Array):void { var tuples:Array = olapSet.tuples; var message:String = ResourceManager.getInstance().getString( "olap", "unionError"); //validate input set var n:int = tuples.length; for (var i:int = 0; i < n; ++i) { var tuple:OLAPTuple = tuples[i]; var members:IList = tuple.explicitMembers; if (members.length != length) throw Error(message); for (var j:int = 0; j < length; ++j) { var hierarchy:IOLAPHierarchy = members.getItemAt(j).hierarchy; if (hierarchy != hierarchies[j]) throw Error(message); } } } } }