/**
  * Recursively check the validity of the expression with regard to the data model
  * and the query in which it is used
  * 	 	 
  * @param ModelReflection $oModelReflection MetaModel to consider	 	
  * @throws OqlNormalizeException
  */
 public function Check(ModelReflection $oModelReflection, $sSourceQuery)
 {
     $sClass = $this->GetClass();
     $sClassAlias = $this->GetClassAlias();
     if (!$oModelReflection->IsValidClass($sClass)) {
         throw new UnknownClassOqlException($sSourceQuery, $this->GetClassDetails(), $oModelReflection->GetClasses());
     }
     $aAliases = array($sClassAlias => $sClass);
     $aJoinSpecs = $this->GetJoins();
     if (is_array($aJoinSpecs)) {
         foreach ($aJoinSpecs as $oJoinSpec) {
             $sJoinClass = $oJoinSpec->GetClass();
             $sJoinClassAlias = $oJoinSpec->GetClassAlias();
             if (!$oModelReflection->IsValidClass($sJoinClass)) {
                 throw new UnknownClassOqlException($sSourceQuery, $oJoinSpec->GetClassDetails(), $oModelReflection->GetClasses());
             }
             if (array_key_exists($sJoinClassAlias, $aAliases)) {
                 if ($sJoinClassAlias != $sJoinClass) {
                     throw new OqlNormalizeException('Duplicate class alias', $sSourceQuery, $oJoinSpec->GetClassAliasDetails());
                 } else {
                     throw new OqlNormalizeException('Duplicate class name', $sSourceQuery, $oJoinSpec->GetClassDetails());
                 }
             }
             // Assumption: ext key on the left only !!!
             // normalization should take care of this
             $oLeftField = $oJoinSpec->GetLeftField();
             $sFromClass = $oLeftField->GetParent();
             $sExtKeyAttCode = $oLeftField->GetName();
             $oRightField = $oJoinSpec->GetRightField();
             $sToClass = $oRightField->GetParent();
             $sPKeyDescriptor = $oRightField->GetName();
             if ($sPKeyDescriptor != 'id') {
                 throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sSourceQuery, $oRightField->GetNameDetails(), array('id'));
             }
             $aAliases[$sJoinClassAlias] = $sJoinClass;
             if (!array_key_exists($sFromClass, $aAliases)) {
                 throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sSourceQuery, $oLeftField->GetParentDetails(), array_keys($aAliases));
             }
             if (!array_key_exists($sToClass, $aAliases)) {
                 throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sSourceQuery, $oRightField->GetParentDetails(), array_keys($aAliases));
             }
             $aExtKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeExternalKey');
             $aObjKeys = $oModelReflection->ListAttributes($aAliases[$sFromClass], 'AttributeObjectKey');
             $aAllKeys = array_merge($aExtKeys, $aObjKeys);
             if (!array_key_exists($sExtKeyAttCode, $aAllKeys)) {
                 throw new OqlNormalizeException('Unknown key in join condition (left expression)', $sSourceQuery, $oLeftField->GetNameDetails(), array_keys($aAllKeys));
             }
             if ($sFromClass == $sJoinClassAlias) {
                 if (array_key_exists($sExtKeyAttCode, $aExtKeys)) {
                     $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
                     if (!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) {
                         throw new OqlNormalizeException("The joined class ({$aAliases[$sFromClass]}) is not compatible with the external key, which is pointing to {$sTargetClass}", $sSourceQuery, $oLeftField->GetNameDetails());
                     }
                 }
             } else {
                 $sOperator = $oJoinSpec->GetOperator();
                 switch ($sOperator) {
                     case '=':
                         $iOperatorCode = TREE_OPERATOR_EQUALS;
                         break;
                     case 'BELOW':
                         $iOperatorCode = TREE_OPERATOR_BELOW;
                         break;
                     case 'BELOW_STRICT':
                         $iOperatorCode = TREE_OPERATOR_BELOW_STRICT;
                         break;
                     case 'NOT_BELOW':
                         $iOperatorCode = TREE_OPERATOR_NOT_BELOW;
                         break;
                     case 'NOT_BELOW_STRICT':
                         $iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT;
                         break;
                     case 'ABOVE':
                         $iOperatorCode = TREE_OPERATOR_ABOVE;
                         break;
                     case 'ABOVE_STRICT':
                         $iOperatorCode = TREE_OPERATOR_ABOVE_STRICT;
                         break;
                     case 'NOT_ABOVE':
                         $iOperatorCode = TREE_OPERATOR_NOT_ABOVE;
                         break;
                     case 'NOT_ABOVE_STRICT':
                         $iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT;
                         break;
                 }
                 if (array_key_exists($sExtKeyAttCode, $aExtKeys)) {
                     $sTargetClass = $oModelReflection->GetAttributeProperty($aAliases[$sFromClass], $sExtKeyAttCode, 'targetclass');
                     if (!$oModelReflection->IsSameFamilyBranch($aAliases[$sToClass], $sTargetClass)) {
                         throw new OqlNormalizeException("The joined class ({$aAliases[$sToClass]}) is not compatible with the external key, which is pointing to {$sTargetClass}", $sSourceQuery, $oLeftField->GetNameDetails());
                     }
                 }
                 $aAttList = $oModelReflection->ListAttributes($aAliases[$sFromClass]);
                 $sAttType = $aAttList[$sExtKeyAttCode];
                 if ($iOperatorCode != TREE_OPERATOR_EQUALS && !is_subclass_of($sAttType, 'AttributeHierarchicalKey') && $sAttType != 'AttributeHierarchicalKey') {
                     throw new OqlNormalizeException("The specified tree operator {$sOperator} is not applicable to the key", $sSourceQuery, $oLeftField->GetNameDetails());
                 }
             }
         }
     }
     // Check the select information
     //
     foreach ($this->GetSelectedClasses() as $oClassDetails) {
         $sClassToSelect = $oClassDetails->GetValue();
         if (!array_key_exists($sClassToSelect, $aAliases)) {
             throw new OqlNormalizeException('Unknown class [alias]', $sSourceQuery, $oClassDetails, array_keys($aAliases));
         }
     }
     // Check the condition tree
     //
     if ($this->m_oCondition instanceof Expression) {
         $this->m_oCondition->Check($oModelReflection, $aAliases, $sSourceQuery);
     }
 }
 /**
  * Note: assumes that class A and B have a common ancestor
  */
 protected static function LowestCommonAncestor(ModelReflection $oModelReflection, $sClassA, $sClassB)
 {
     if ($sClassA == $sClassB) {
         $sRet = $sClassA;
     } elseif (in_array($sClassA, $oModelReflection->EnumChildClasses($sClassB))) {
         $sRet = $sClassB;
     } elseif (in_array($sClassB, $oModelReflection->EnumChildClasses($sClassA))) {
         $sRet = $sClassA;
     } else {
         // Recurse
         $sRet = self::LowestCommonAncestor($oModelReflection, $sClassA, $oModelReflection->GetParentClass($sClassB));
     }
     return $sRet;
 }