blob: bb729c64c56876318ec52e90ee117c01b6dd8f3e [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 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:
* <ul>
* <li>0 if the members are at the same level</li>
* <li>1 if m2 is higher in the hierarchy than m1</li>
* <li>-1 if m1 is higher in the hierarchy than m2</li>
* </ul>
*
* @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:
* <ul>
* <li>0 if the tuples are at the same level</li>
* <li>1 if t2 is higher than t1</li>
* <li>-1 if t1 is higher than t2</li>
* </ul>
*
* @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);
}
}
}
}
}