package gw.rating.worksheet uses com.google.common.collect.ImmutableList uses com.google.common.collect.ImmutableMap uses com.google.gdata.util.common.base.Pair uses gw.entity.ITypeList uses gw.entity.TypeKey uses gw.lang.reflect.IType uses gw.lang.reflect.TypeSystem uses gw.rating.RateRoutineExecutionOperator uses gw.rating.worksheet.domain.Worksheet uses gw.rating.worksheet.domain.WorksheetArgument uses gw.rating.worksheet.domain.WorksheetCalculation uses gw.rating.worksheet.domain.WorksheetConditional uses gw.rating.worksheet.domain.WorksheetConstant uses gw.rating.worksheet.domain.WorksheetEntry uses gw.rating.worksheet.domain.WorksheetEntryContainer uses gw.rating.worksheet.domain.WorksheetFunction uses gw.rating.worksheet.domain.WorksheetFunctionReturn uses gw.rating.worksheet.domain.WorksheetInstanceSubroutine uses gw.rating.worksheet.domain.WorksheetIterableElement uses gw.rating.worksheet.domain.WorksheetIteration uses gw.rating.worksheet.domain.WorksheetLoop uses gw.rating.worksheet.domain.WorksheetNegation uses gw.rating.worksheet.domain.WorksheetNewObject uses gw.rating.worksheet.domain.WorksheetNote uses gw.rating.worksheet.domain.WorksheetObjectProperty uses gw.rating.worksheet.domain.WorksheetOperand uses gw.rating.worksheet.domain.WorksheetOperandContainer uses gw.rating.worksheet.domain.WorksheetRateQuery uses gw.rating.worksheet.domain.WorksheetRateQueryParam uses gw.rating.worksheet.domain.WorksheetRoutine uses gw.rating.worksheet.domain.WorksheetStaticSubroutine uses gw.rating.worksheet.domain.WorksheetVariable uses gw.rating.worksheet.domain.WorksheetXmlSerializable uses gw.xml.date.XmlDateTime uses gw.xml.parser2.PLXMLNode uses gw.xml.parser2.PLXMLParser uses java.io.StringReader uses java.math.BigDecimal uses java.util.stream.IntStream /** * * Handy utility functions for worksheets * */ final class WorksheetUtil { public static enum WorksheetStatus { NONE, ARCHIVED, AVAILABLE } public static final var LOGGER_NAME : String = "_log" public static final var ARRAY_VALUE_TOKEN : String = "{...}" public static final var DISPLAY_HINT : String = "DisplayHint" private static final var NODES_TO_IGNORE_WHEN_DESERIALIZING = ImmutableList.of( WorksheetConditional.CONDITION_NODE_NAME, WorksheetConditional.CONDITIONALS_GROUP_NODE_NAME, DISPLAY_HINT ) // this map contains mappings that are not reversible. private static final var DOMAIN_NODE_MAP_NONUNIQUE = ImmutableMap.copyOf>({ "PropertySet" -> WorksheetCalculation, "Store" -> WorksheetCalculation, "If" -> WorksheetConditional, "Else" -> WorksheetConditional, "ElseIf" -> WorksheetConditional, "EndIf" -> WorksheetConditional, "Loop" -> WorksheetLoop, "Iteration" -> WorksheetIteration }) // This list will be used to build the reversible mapping private static final var DOMAIN_TYPES_UNIQUE = ImmutableList.copyOf>({ Worksheet, WorksheetArgument, WorksheetConstant, WorksheetFunction, WorksheetNegation, WorksheetNewObject, WorksheetNote, WorksheetObjectProperty, WorksheetRateQuery, WorksheetRateQueryParam, WorksheetRoutine, WorksheetInstanceSubroutine, WorksheetStaticSubroutine, WorksheetVariable, WorksheetFunctionReturn, WorksheetLoop, WorksheetIteration, WorksheetIterableElement }) private static final var DOMAIN_NODE_MAP_UNIQUE = ImmutableMap.copyOf>( DOMAIN_TYPES_UNIQUE .partitionUniquely(\n -> createDomainObject(n).WorksheetClass) ) // Prevent instantiation private construct() { } /** * Serialize a worksheet object to a string * @param the worksheet object * @return the worksheet object' */ static function serialize(serializable : WorksheetXmlSerializable) : String{ return serializable.toXml().asUTFString() } /** * Rehydrate a worksheet from its serialized string representation * @param the serialized string * @return the Worksheet object */ static function deserialize(str : String) : WorksheetXmlSerializable{ return deserializeXml(parseXml(str)) } /** * Rehydrate a worksheet object from an xml node * @param xml The xml node * @return the Worksheet */ static function deserializeXml(xml : PLXMLNode) : WorksheetXmlSerializable{ var domainObject = createDomainObject(xml.ElementName) domainObject.fromXml(xml) return domainObject } /** * Serialize an object to its string representation. Used only in the context of serializing * worksheet objects * * @param o Object * @return the string representation of the object */ static function serializeObject(o : Object) : String{ if (o == null) { return "" } else if (o typeis Date) { return serializeDate(o) } else if ((typeof o).Array) { return ARRAY_VALUE_TOKEN } else if (o typeis java.util.List) { return "[" + o.whereTypeIs(TypeKey).map(\ tk -> tk.Code).join(",") + "]" } else if (o typeis TypeKey) { return o.Code } else { return o.toString() } } static function deserializeTypekeyList(value : String, type : String) : List { var items = value.substring(1,value.length - 1).split(",") var typelistType = type.substring(type.indexOf("<") + 1, type.length - 1) var t = TypeSystem.getByRelativeName(typelistType) as ITypeList var l = items.map(\ i -> t.getTypeKey(i)).toList() return l } /** * Deserialize an object from its string representation. Used only in the context * of deserializing values for worksheet objects * * @param state the string representation of the object * @param type the data type of the object * @return a deserialized object */ static function deserializeObject(value: String, type : String) : Object { if (!value.HasContent) { return null } else if (type == "java.math.BigDecimal") { return new BigDecimal(value) } else if(type == "java.lang.Integer") { return Integer.valueOf(value) } else if(type == "java.lang.Boolean") { return Boolean.valueOf(value) } else if(type == "java.lang.Double") { return Double.valueOf(value) } else if(type == "java.lang.Short") { return Short.valueOf(value) } else if(type == "java.lang.Float") { return Float.valueOf(value) } else if(type == "java.lang.Long") { return Long.valueOf(value) } else if(type == "java.util.Date") { return deserializeDate(value) } else if (type?.contains("typelist.") or type?.contains("typekey")) { if (type.contains("java.util.List")) { return deserializeTypekeyList(value, type) } return getTypekeyFromName(type, value) } else if (type.HasContent and type.contains("[]")) { // Special case for arrays return value } else { return value } } /** * Serialize a date into a string * @param date Date * @return the string representation of the date */ static function serializeDate(date : Date) : String{ return date.XmlDateTime.toString() } /** * Deserialize a date from a string * @param s the serialized date * @return the date */ static function deserializeDate(s : String) : Date{ return new XmlDateTime(s).toCalendar().Time } /** * Serialize a typekey into a string * @param typeKey TypeKey * @return the string representation of a typekey * */ static function serializeTypeKey(typeKey : TypeKey) : String { return "@${typeKey.IntrinsicType.TypeInfo.Name}:${typeKey.Code}" } static function getTypekeyFromName(typeName : String, code: String): TypeKey { try { var t = TypeSystem.getByRelativeName(typeName) as ITypeList return t.getTypeKey(code) } catch (e : java.lang.ClassNotFoundException) { return null } } /** * Deserialize a typekey from a string * @param s the serialized typekey * @return the typekey * * example: @typelist.Jurisdiction:AK deserializes to Jurisdiction.TC_AK */ static function deserializeTypeKey(s : String) : TypeKey { var strs = s.substring(1).split(":") return getTypekeyFromName(strs[0], strs[1]) } /** * Parse an xml string * * @param str Xml string * @return An xml node * */ static function parseXml(str : String) : PLXMLNode{ var reader = new StringReader(str) var node = PLXMLParser.parseReader(new HashMap>(), reader, null) return node } /** * build a flat list of all worksheet domain objects in a hierarchy * * @param parent * @param list * @param includeParent include the parent in the list * */ static function recurseWorksheetHierarchy(parent : Object, list : List, includeParent : boolean = false) { if (includeParent and parent typeis WorksheetXmlSerializable) { list.add(parent) } if (parent typeis WorksheetOperandContainer) { (parent).WorksheetOperands.each(\ o -> recurseWorksheetHierarchy(o, list, true)) } if (parent typeis WorksheetEntryContainer) { (parent).WorksheetEntries.each(\ e -> recurseWorksheetHierarchy(e, list, true)) } } private static function createDomainObject(elementName : String) : WorksheetXmlSerializable { var type = worksheetClass(elementName) return createDomainObject(type) } private static function createDomainObject(type : IType) : WorksheetXmlSerializable { var constructor = type.TypeInfo.Constructors.firstWhere(\ i -> i.Parameters.IsEmpty) if (constructor == null){ throw new Exception("Worksheet object ${type.Name} lacks public no-arg constructor") } return constructor.Constructor.newInstance({}) as WorksheetXmlSerializable } /** * Helper function for writing all child operands to xml * * @param parent the container xml node * @param operands the list of worksheet operands * */ public static function writeOperandsXml(parent : PLXMLNode, operands : List) { operands.each(\ o -> parent.addChild(o.toXml())) } /** * Helper method for deserializing all child nodes of an xml node * * @param parent the parent worksheet object * @param node the xml node * */ public static function deserializeChildren(parent : WorksheetXmlSerializable, node : PLXMLNode){ node.Children.each(\c -> { if (NODES_TO_IGNORE_WHEN_DESERIALIZING.contains(c.QName.LocalPart)) { deserializeChildren(parent, c) } else { var child = WorksheetUtil.deserializeXml(c) if (child typeis WorksheetOperand) { (parent as WorksheetOperandContainer).addWorksheetOperand(child) } else { (parent as WorksheetEntryContainer).addWorksheetEntry(child as WorksheetEntry) } } }) } public static function deserializeDisplayHints(operand : WorksheetOperand, node : PLXMLNode) { var hints : List = {} node.Children.where(\c -> c.QName.LocalPart == DISPLAY_HINT).each(\c -> hints.add(c.getAttributeValue("Value"))) operand.DisplayHints = hints.toTypedArray() } /** * Helper function for writing display hints of an operand to xml * * @param parent the container xml node * @param operand the operand * */ public static function writeDisplayHintsXml(parent : PLXMLNode, operand : WorksheetOperand){ // cache the array in a local variable because each call to an array property getter creates a new array var displayHints = operand.DisplayHints if (displayHints == null) { return } Arrays.stream(displayHints).forEachOrdered(\hint -> { var node = new PLXMLNode(DISPLAY_HINT) node.setAttributeValue("Value", hint) parent.addChild(node) }) } /** * Helper function for writing all child entries to xml * * @param parent the container xml node * @param operands the list of worksheet entries */ public static function writeEntriesXml(parent : PLXMLNode, entries : List) { IntStream.range(0, entries.Count) .parallel() .mapToObj(\ i -> { var entry = entries.get(i) var conditionalsGroupNode : PLXMLNode if (entry typeis WorksheetConditional and entry.Type == IfCondition and i < entries.Count - 1) { var nextEntry = entries.get(i + 1) if (nextEntry typeis WorksheetConditional and (nextEntry.Type == ElseIfCondition or nextEntry.Type == ElseCondition)) { conditionalsGroupNode = new PLXMLNode(WorksheetConditional.CONDITIONALS_GROUP_NODE_NAME); } } return new Pair(entry.toXml(), conditionalsGroupNode) }) .forEachOrdered(\untypedPair -> { // Cast neccessary because of PL-37421 var pair = untypedPair as Pair var entryXml = pair.first var conditionalsGroupNode = pair.second // Wrap a group of conditionals in a node for easier reading if (conditionalsGroupNode != null) { parent.addChild(conditionalsGroupNode) parent = conditionalsGroupNode } parent.addChild(entryXml) }) } /** * Map a CalcStepOperatorType to a RateRoutineExecutionOperator * * @param op The calc step operator * @return the Worksheet operator * */ public static function mapOperator(op : CalcStepOperatorType) : RateRoutineExecutionOperator { return RateRoutineExecutionOperator.of(op.Code) } /** * Map a RateRoutineExecutionOperator to a CalcStepOperatorType * * @param op The worksheet operator * @return the calc step operator * */ public static function mapOperator(op : RateRoutineExecutionOperator) : CalcStepOperatorType{ return op.CalcStepOperatorType } /** * @return the worksheet class for the node name */ public static function worksheetClass(nodeName : String) : IType{ var type = DOMAIN_NODE_MAP_UNIQUE.get(nodeName) ?: DOMAIN_NODE_MAP_NONUNIQUE.get(nodeName) if (type == null) { throw new Exception("Unknown type for ${nodeName}") } return type } }