package com.guidewire.rating.flow.domain.util uses gw.rating.flow.domain.CalcRoutine uses gw.rating.flow.domain.CompoundStatement uses gw.rating.flow.domain.GenerationContext uses gw.rating.flow.domain.IfStatement uses gw.rating.flow.domain.LoopStatement uses gw.rating.flow.domain.Statement uses gw.validation.PCValidationContext internal final class CalcRoutineMethodSequenceCodeGenerator extends CalcRoutineCodeGenerator { static final var LOOP_RUNNABLE_CODESIZE = 1 static final var IF_RUNNABLE_CODESIZE = 1 static final var NEW_METHOD_CODESIZE = 1 var _methodCount = 0 final var _sequenceStack = new ArrayDeque>() construct(definition : CalcRoutineDefinition, orderedSteps : List, validationContext : PCValidationContext, worksheets : boolean, useUniquePkgNameSuffix : boolean) { super(definition, orderedSteps, validationContext, worksheets, useUniquePkgNameSuffix) } override protected function generate() : String { var statements: List try { statements = Statements // Has the side effect, if there are errors, of throwing or altering validation context } catch (e) { // Validation context must be null. throw new IllegalStateException(e.Message, e) // Because our tests want only IllegalStateException-s } if (ValidationContext?.Result?.hasErrors()) { return null } return generate(statements) } internal function generate(statements: List): String { var context = new GenerationContext(ValidationContext, GosuShop.createSymbolTable(true), GosuShop.createTypeUsesMap({}), 1, PolicyLinePatternCode, WorksheetsEnabled) context.AddLocalVarDecls = false context.CodegenStrategy = SEQUENCE generate(context, statements, "The start") if (ValidationContext?.Result?.hasErrors()) { return null } // Must do this after generating: need context.VarMap updated with local variables. Prepends. var prologLineCount = prolog(context) addExecuteMethod(context) epilog(context) context.replace(CalcRoutineCodeGeneratorUtils.prettyPrint(context.GeneratedCode)) var headerLineCount = addHeaderComments(context) // Prepends var prependLineCount = prologLineCount + headerLineCount var sourceCode = context.GeneratedCode fixStatementLineAndColumnNumbers(statements, sourceCode, prependLineCount) return sourceCode } private function generate(context: GenerationContext, statements: List, comment: String) { context.appendNewlineIfNeeded() _methodCount = _methodCount + 1 var sequence = new ArrayList() sequence.add(_methodCount) _sequenceStack.push(sequence) var runningCodeSize = generateMethodStart(context, _methodCount, comment) context.appendNewlineIfNeeded() generate(context, statements, runningCodeSize) // Close the last method. This method may be empty. context.appendNewlineIfNeeded() context.append('}') } private function generate(context: GenerationContext, statements: List, runningCodeSize: int): int { for (statement in statements) { context.appendNewlineIfNeeded() runningCodeSize = generate(context, statement, runningCodeSize) } return runningCodeSize } private function generate(context: GenerationContext, statement: Statement, runningCodeSize: int): int { var start = context.beginCodeGen() /** * Do *NOT* turn this into switch (typeof statement) {...}! Need it to be this way for testing purposes. For * example, we mock LoopStatement and need the mocks to be treated the same. */ if (statement typeis LoopStatement) { runningCodeSize = generateLoop(context, statement, runningCodeSize) } else if (statement typeis IfStatement) { runningCodeSize = generateIf(context, statement, runningCodeSize) } else if (statement typeis CompoundStatement) { runningCodeSize = generate(context, statement.Statements, runningCodeSize) } else { runningCodeSize = generateSimple(context, statement, runningCodeSize) } var end = context.endCodeGen() statement.setStartAndEnd(start, end) return runningCodeSize } private function generateLoop(context: GenerationContext, loop: LoopStatement, runningCodeSize: int): int { loop.addLocalVariableDeclarations(context) if ((runningCodeSize + loop.CodeSize) <= MAX_METHOD_CODESIZE) { // It'll fit into the current method. context.appendNewlineIfNeeded() loop.generate(context) runningCodeSize += loop.CodeSize } else { runningCodeSize = generateMethodSequence(context, runningCodeSize, "Loop statement: ${runningCodeSize} + ${loop.CodeSize} > ${MAX_METHOD_CODESIZE}") if ((NEW_METHOD_CODESIZE + loop.CodeSize) <= MAX_METHOD_CODESIZE) { // It'll fit into a new method. runningCodeSize = generateLoop(context, loop, runningCodeSize) } else { // It won't fit, even in a new method. runningCodeSize = generateLoopRunnable(context, loop, runningCodeSize, "Loop Runnable: ${loop.CodeSize} > ${MAX_METHOD_CODESIZE}") } } return runningCodeSize } private function generateIf(context: GenerationContext, ifStatement: IfStatement, runningCodeSize: int): int { ifStatement.addLocalVariableDeclarations(context) if ((runningCodeSize + ifStatement.CodeSize) <= MAX_METHOD_CODESIZE) { // It'll fit into the current method. context.appendNewlineIfNeeded() ifStatement.generate(context) runningCodeSize += ifStatement.CodeSize } else { runningCodeSize = generateMethodSequence(context, runningCodeSize, "If statment: ${runningCodeSize} + ${ifStatement.CodeSize} > ${MAX_METHOD_CODESIZE}") if ((NEW_METHOD_CODESIZE + ifStatement.CodeSize) <= MAX_METHOD_CODESIZE) { // It'll fit into a new method. runningCodeSize = generateIf(context, ifStatement, runningCodeSize) } else { // It won't fit, even in a new method. runningCodeSize = generateIfRunnable(context, ifStatement, runningCodeSize, "If Runnable: ${ifStatement.CodeSize} > ${MAX_METHOD_CODESIZE}") } } return runningCodeSize } private function generateSimple(context: GenerationContext, simple: Statement, runningCodeSize: int): int { // The size of simple statements should never exceed the maximum method code size, but ... if ((simple.CodeSize > MAX_METHOD_CODESIZE or (runningCodeSize + simple.CodeSize) > MAX_METHOD_CODESIZE)) { runningCodeSize = generateMethodSequence(context, runningCodeSize, "Simple: > ${MAX_METHOD_CODESIZE}") } context.appendNewlineIfNeeded() simple.generate(context) return runningCodeSize + simple.CodeSize } private function generateMethodSequence(context: GenerationContext, runningCodeSize: int, comment: String = null): int { if (runningCodeSize > NEW_METHOD_CODESIZE) { context.appendNewlineIfNeeded().append("}\n") _methodCount = _methodCount + 1 _sequenceStack.peek().add(_methodCount) runningCodeSize = generateMethodStart(context, _methodCount, comment) } return runningCodeSize } private function generateMethodStart(context: GenerationContext, methodNumber: int, comment: String = null): int { if (comment?.HasContent) { context.append("\n// ${comment}") } context.append("\nprivate function _method${methodNumber}() {") return NEW_METHOD_CODESIZE } /** * Generated code is along the lines of:
* *    (new java.lang.Runnable() {
*      private function _method10() {
*        ...
*      }
*
*      private function _method11() {
*        ...
*      }
*
*      var $loopVar: LoopVarType
*      override function run() {
*        for (_$loopVar in $iterable index $loopIndex) {
*          $loopVar = _$loopVar
*          _method10()
*          _method11()
*        }
*      }
*    }).run() *
*

* where _method10 and _method11 implement the loop body. We need to do the magic with the * loop variable so that it is accessible to the loop body methods. * * @param context the generation context * @param loop the loop statement * @param runningCodeSize current method size * * @return size of last open method */ private function generateLoopRunnable(context: GenerationContext, loop: LoopStatement, runningCodeSize: int, comment: String = null): int { /** * Fixup: need to stick the loop variable and loop index variable into the context VarMap * We are now generating the loop body *before* the loop proper. So the context's VarMap doesn't have the loop * variable or the loop index variable. * TODO: All of this code generation belongs in LoopStatement and not here. */ context.VarMap.put(loop.LoopVariable.VariableKey, loop.LoopVariable.ReferenceType) context.VarMap.put(loop.LoopIndexVariableKey, LoopStatement.INT_ITYPE) try { context.appendNewlineIfNeeded() if (comment?.HasContent) { context.append("// ${comment}\n") } context.append("(new java.lang.Runnable() {") generate(context, loop.LoopBody.Statements, "Loop body start") context.append("\n\nvar ${loop.LoopVariable.VariableReference}: ${loop.LoopVariable.ReferenceTypeName}") context.append("\n\noverride function run() {\n") context.WorksheetHelper.wrapStatement(loop, \-> { context.append("\nfor (_${loop.LoopVariable.VariableReference} in ${loop.IterableOperand.VariableReference} ") .append("index ${loop.LoopIndexVariableReference}) {") context.append("\n${loop.LoopVariable.VariableReference} = _${loop.LoopVariable.VariableReference}") // Create the iteration record and the iteration itself. context.append('\n') context.WorksheetHelper.wrapStatement(loop.LoopBody, \-> {}) var sequence = _sequenceStack.pop() for (methodNumber in sequence) { context.append("\n_method${methodNumber}()") } context.append("\n}") // close for () { }) context.append("\n}") // close function run() { context.append("\n}).run()") // close (new java.lang.Runnable() { } finally { context.VarMap.remove(loop.LoopVariable.VariableKey) context.VarMap.remove(loop.LoopIndexVariableKey) } return runningCodeSize + LOOP_RUNNABLE_CODESIZE } /** * Generates code along the lines of:
* *    (new java.lang.Runnable() {
*      private function _method10() {
*        ...
*      }
*
*      private function _method11() {
*        ...
*      }
*
*      private function _method12() {
*        ...
*      }
*
*      override function run() {
*        if (...) {
*          _method10()
*        } else if (...) {
*          _method11()
*        } else {
*          _method12()
*        }
*      }
*      }).run() *
*

Of course, _method10, _methdo11, _method12 may instead be a sequence of * methods. * * @param context the generation context * @param ifStatement the if statement * @param runningCodeSize size of current method thus far * * @return size of last open method */ private function generateIfRunnable(context: GenerationContext, ifStatement: IfStatement, runningCodeSize: int, comment: String = null): int { context.appendNewlineIfNeeded() if (comment?.HasContent) { context.append("// ${comment}\n") } context.append("(new java.lang.Runnable() {") _methodCount = _methodCount + 1 var ifBodyMethodNumber = _methodCount generateIfBranches(context, ifStatement, ifBodyMethodNumber) context.appendNewlineIfNeeded() context.appendNewlineIfNeeded() context.append("override function run() {") context.append("\n_method${ifBodyMethodNumber}()") context.append("\n}") context.append("\n}).run()") return runningCodeSize + IF_RUNNABLE_CODESIZE } private function generateIfBranches(context: GenerationContext, ifStatement: IfStatement, ifBodyMethodNumber: int) { // Make each branch a method var elseSeen = false // Gather all the branches var branches = new ArrayList() for (cond in ifStatement.Conditions) { if (elseSeen) { throw new IllegalStateException("Cannot have if, elseif or else after else") } if (cond.First == null) { elseSeen = true } branches.add(cond.Second) } // Gnerate code for each branch. This adds elements to the sequence stack. for (branch in branches) { generate(context, Collections.singletonList(branch), "If branch start") } /** * The sequences on the stack are in reverse order now, i.e., top of stack is last branch, etc. * Need them in the order of the branches. */ var n = branches.size() var sequences = new List[n] for (i in 1..n) { sequences[n - i] = _sequenceStack.pop() } /** * Generate the body of if statements. sequences[] has things in order of the branches. * For each branch, generate the sequence of calls for that branch. */ context.append("\n\n// If statement: body method") generateMethodStart(context, ifBodyMethodNumber) context.appendNewlineIfNeeded() new IfStatementMethodCodeGenerator(ifStatement, sequences).generate(context) context.appendNewlineIfNeeded().append("}") // Close the body method } // ---------------------------------------------------------------------------------------// // Methods for the various boilerplate elements in the generated code // // ---------------------------------------------------------------------------------------// private function prolog(context: GenerationContext): int { var sb = new StringBuilder() sb.append("package ${PackageName}\n\n") addImports(sb) sb.append("final class ${RoutineName} implements ").append(RATE_ROUTINE_INTERFACE).append(" {\n") addIdentification(sb) addRatingFunctionSources(sb) addExecutionLoggerSymbols(sb) addParameterDeclarations(sb) addLocalVariableDeclarations(sb, context) addProperties(sb) var prolog = sb.toString() var lineCount = prolog.countMatches("\n") context.prepend(prolog) return lineCount } private function addExecuteMethod(context: GenerationContext) { context.append("\n\noverride function execute(") .append("${CalcRoutine.RATE_BOOK}: entity.RateBook, ") .append("${COST_DATA_WRAPPER}: ${COST_DATA_WRAPPER_TYPE_NAME}, ") .append("${PARAMETER_SET_ARGUMENT_NAME}: java.util.Map) {\n") addParameterInitializations(context) context.appendNewlineIfNeeded() for (methodNumber in _sequenceStack.pop()) { // Intentionally *not* logging to worksheet, so that worksheets remain same as for the single method case. context.append("\n_method${methodNumber}()") } context.append('\n}') } private function epilog(context: GenerationContext) { context.append('\n}') // close the opening class ... { } }