package gw.pcf.rating.flow uses gw.api.locale.DisplayKey uses gw.lang.reflect.TypeSystem uses gw.rating.flow.LocalVariable uses gw.util.Pair uses java.util.LinkedHashMap uses java.util.Map uses gw.api.util.DisplayableException uses java.util.List @Export class RateRoutineConditionalPopupHelper { private var _currentLocation : pcf.RateRoutineConditionalPopup private var _operand : CalcStepDefinitionOperand private var _routine : CalcRoutineDefinition construct(currentLocation : pcf.RateRoutineConditionalPopup, operand : CalcStepDefinitionOperand, routine: CalcRoutineDefinition) { _currentLocation = currentLocation _operand = operand _routine = routine } function commitPopup(availLocalVars : List) { removeUnusedLogicalNots(availLocalVars) clearFirstSubOperandOperator() _currentLocation.commit() } function checkForUnbalancedParentheses() { var balance = 0 for (op in _operand.ConditionalSubOperands) { balance += (op.First.LeftParenthesisGroup ?: "").countMatches("(") balance -= (op.First.RightParenthesisGroup ?: "").countMatches(")") if (balance < 0) break // always report if there's an underflow balance += (op.Second.LeftParenthesisGroup ?: "").countMatches("(") balance -= (op.Second.RightParenthesisGroup ?: "").countMatches(")") if (balance < 0) break // always report if there's an underflow } if (balance > 0) { throw new DisplayableException(DisplayKey.get("Web.Rating.Errors.MissingCloseParen")) } else if (balance < 0) { throw new DisplayableException(DisplayKey.get("Web.Rating.Errors.MissingOpenParen")) } } function maybeChangeOperandType(rhs : CalcStepDefinitionOperand) { if (rhs.OperatorType.Categories.contains(CalcStepOperatorCategory.TC_INCLUSION)) { rhs.changeToOperandType(TC_COLLECTION) } else if (rhs.OperandType == TC_COLLECTION) { rhs.changeToOperandType(TC_CONSTANT) } } function checkForIllegalTypekeyCombinations() { for (op in _operand.ConditionalSubOperands) { var expr = typekeyConstantValidationExpression(op.First, op.Second) if (expr != null) { throw new DisplayableException(expr) } } } function typekeyConstantValidationExpression(leftOp : CalcStepDefinitionOperand, rightOp: CalcStepDefinitionOperand) : String { if (rightOp.OperandType == TC_COLLECTION) { if (leftOp.IsTypeKey and rightOp.OperatorType.Categories.contains(CalcStepOperatorCategory.TC_INCLUSION)) { if (leftOp.TypeDeclaration != rightOp.TypeDeclaration) { return DisplayKey.get("Validation.Rating.RateRoutineDefinition.TypeMismatch", leftOp.TypeDeclaration, rightOp.TypeDeclaration) } } else { return DisplayKey.get("Validation.Rating.RateRoutineDefinition.OnlyTypekeysCanBeComparedToLists") } } return null } // Currently we do no deduce the types of local variables. For now, we will blindly allow the NOT operator // to be applied to local variables... this method will hopefully disappear and the caller will use // isBoolean exclusively when we keep track of var types. static function mightBeBoolean(conditionalSubOperand : CalcStepDefinitionOperand, availLocalVars : List) : Boolean { // If it's local variable but not a multifactor variable or a field from complex type, it might be a boolean if (conditionalSubOperand.OperandType == TC_LOCALVAR) { var localVar = availLocalVars.firstWhere(\lv ->conditionalSubOperand.VariableName == lv.VariableName and conditionalSubOperand.VariableFieldName == lv.FieldName) if (localVar.VarType == null) { return true } else { return gw.api.util.TypeUtil.isNominallyOrStructurallyAssignable(java.lang.Boolean.Type, localVar.VarType) or java.lang.Boolean.Type.isAssignableFrom(TypeSystem.getBoxType(localVar.VarType)) } } else { // else trust the target's IsBoolean return conditionalSubOperand.IsBoolean } } static function showFreeFormOperand(conditionalOperand : Pair) : boolean { if(conditionalOperand.Second.OperandType == null) { // nothing selected in right operand return conditionalOperand.First.isComparableToConstantInsideConditional() } else { return conditionalOperand.Second.IsEditableConstant and conditionalOperand.First.isComparableToConstantInsideConditional() } } function parseRateTables() : Map { var tabCodeNameMap = new LinkedHashMap() var rTables = _operand.CalcStep.CalcRoutineDefinition.availableTables() if (rTables.Count > 0) { rTables.each(\rt -> tabCodeNameMap.put(rt.TableCode, rt.TableName)) } return tabCodeNameMap } function getParamType(arg : CalcStepDefinitionArgument) : String { var valProvider = arg.FirstMatchingRateTableColumn.ValueProvider if (valProvider == null) { return arg.ColumnDataType.toString() } return "List" } // -- Private Helper Methods -- // // During conditional construction, we just hide the logical not if it isn't logically usable. // Before heading back to the step view, clear out the actual backing values for those conditional suboperands // for which logical not should not be allowed. private function removeUnusedLogicalNots(availLocalVars : List) { _operand.CalcStep.Operands.where(\o -> o.OperandOrder > 0 and (not mightBeBoolean(o, availLocalVars))) .each(\o -> {o.LogicalNot = false}) } // Is there a more efficient way to do this? private function clearFirstSubOperandOperator() { _operand.CalcStep.Operands.where(\op -> op.OperandOrder == 1) .each(\op -> {op.OperatorType = null}) } }