Skip to content

Commit

Permalink
Story/971/5071 Fix tabulator NPE on unresolved references (finos#824)
Browse files Browse the repository at this point in the history
* task-5071: junit to reproduce error

* Add tests

* Add failing unit test

* Add multi cardinality tests

* Simplify test

* Tidy up

* Typo

* Temp changes to investigate windows issues

* Improved report gen tablulator check

* Collect types

* Add if

* Remove debug logging

* Enable tests

* Remove debug logging

* Clean up tests

* Remove unnecessary code

* Tidy up

* Remove unused code

* Rename method

* Fixed

* Cleaned

---------

Co-authored-by: James Annesley <[email protected]>
Co-authored-by: Simon Cockx <[email protected]>
  • Loading branch information
3 people authored Sep 5, 2024
1 parent 807477c commit 1fe2d32
Show file tree
Hide file tree
Showing 9 changed files with 329 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ abstract class AbstractRosettaLanguageServerTest extends AbstractLanguageServerT
}

protected override Module getServerModule() {
EPackage.Registry.INSTANCE.remove(RosettaPackage.eNS_URI);
EPackage.Registry.INSTANCE.remove(SimplePackage.eNS_URI);
EPackage.Registry.INSTANCE.remove(ExpressionPackage.eNS_URI);
EValidator.Registry.INSTANCE.clear

RosettaStandaloneSetup.doSetup
return RosettaServerModule.create
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import com.google.common.base.CaseFormat
import com.regnosys.rosetta.rosetta.RosettaEnumeration
import com.regnosys.rosetta.rosetta.RosettaExternalRuleSource
import com.regnosys.rosetta.rosetta.RosettaFeature
import com.regnosys.rosetta.rosetta.RosettaRecordType
import com.regnosys.rosetta.rosetta.RosettaReport
import com.regnosys.rosetta.rosetta.RosettaRule
import com.regnosys.rosetta.rosetta.RosettaSynonym
import com.regnosys.rosetta.rosetta.expression.ChoiceOperation
import com.regnosys.rosetta.rosetta.expression.OneOfOperation
Expand All @@ -15,28 +18,25 @@ import com.regnosys.rosetta.rosetta.simple.Attribute
import com.regnosys.rosetta.rosetta.simple.Condition
import com.regnosys.rosetta.rosetta.simple.Data
import com.regnosys.rosetta.rosetta.simple.Function
import com.regnosys.rosetta.types.RAttribute
import com.regnosys.rosetta.types.RDataType
import com.regnosys.rosetta.types.REnumType
import com.regnosys.rosetta.types.RType
import com.regnosys.rosetta.types.builtin.RBuiltinTypeService
import com.regnosys.rosetta.types.builtin.RRecordType
import com.regnosys.rosetta.utils.ExternalAnnotationUtil
import com.rosetta.model.lib.path.RosettaPath
import java.util.Collection
import java.util.List
import java.util.Map
import java.util.Optional
import java.util.Set
import javax.inject.Inject
import org.eclipse.emf.common.util.URI
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.resource.ResourceSet

import static extension com.regnosys.rosetta.generator.util.RosettaAttributeExtensions.*
import com.regnosys.rosetta.types.builtin.RRecordType
import com.regnosys.rosetta.types.builtin.RBuiltinTypeService
import org.eclipse.emf.ecore.resource.ResourceSet
import com.regnosys.rosetta.rosetta.RosettaRecordType
import java.util.Optional
import com.regnosys.rosetta.types.RAttribute
import com.regnosys.rosetta.rosetta.RosettaReport
import com.regnosys.rosetta.rosetta.RosettaRule

class RosettaExtensions {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.regnosys.rosetta.rosetta.simple.SimplePackage
import org.eclipse.emf.ecore.EPackage
import com.regnosys.rosetta.rosetta.expression.ExpressionPackage
import com.google.inject.Injector
import org.eclipse.emf.ecore.EValidator

/**
* Initialization support for running Xtext languages without Equinox extension registry.
Expand All @@ -17,6 +18,11 @@ class RosettaStandaloneSetup extends RosettaStandaloneSetupGenerated {
def static void doSetup() {
new RosettaStandaloneSetup().createInjectorAndDoEMFRegistration()
}

override Injector createInjectorAndDoEMFRegistration() {
EValidator.Registry.INSTANCE.clear // This line is to ensure tests don't use the same validator instance.
return super.createInjectorAndDoEMFRegistration()
}

/**
* Register xcore model in standalone setup.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,28 +171,28 @@ class RosettaGenerator implements IGenerator2 {
]
// new
// validatorGenerator.generate(packages, fsa, it, version)
tabulatorGenerator.generate(fsa, it, Optional.empty)
if (deepFeatureCallUtil.isEligibleForDeepFeatureCall(new RDataType(it))) {
deepPathUtilGenerator.generate(fsa, it, version)
}
tabulatorGenerator.generate(fsa, it)
tabulatorGenerator.generateTabulatorForReportData(fsa, it, Optional.empty)
tabulatorGenerator.generateTabulatorForData(fsa, it)
}
Function: {
if (!isDispatchingFunction) {
funcGenerator.generate(packages, fsa, it, version)
}
tabulatorGenerator.generate(fsa, it)
tabulatorGenerator.generateTabulatorForFunction(fsa, it)
}
RosettaRule: {
ruleGenerator.generate(packages, fsa, it, version)
}
RosettaReport: {
reportGenerator.generate(packages, fsa, it, version)
tabulatorGenerator.generate(fsa, it)
tabulatorGenerator.generateTabulatorForReport(fsa, it)
}
RosettaExternalRuleSource: {
it.externalClasses.forEach [ externalClass |
tabulatorGenerator.generate(fsa, externalClass.data, Optional.of(it))
tabulatorGenerator.generateTabulatorForReportData(fsa, externalClass.data, Optional.of(it))
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import javax.inject.Inject
import org.apache.commons.text.StringEscapeUtils
import org.eclipse.xtend2.lib.StringConcatenationClient
import org.eclipse.xtext.generator.IFileSystemAccess2
import java.util.Objects

class TabulatorGenerator {
private interface TabulatorContext {
Expand Down Expand Up @@ -167,18 +168,18 @@ class TabulatorGenerator {
@Inject extension RosettaExtensions extensions
@Inject extension JavaTypeUtil

def generate(IFileSystemAccess2 fsa, RosettaReport report) {
def generateTabulatorForReport(IFileSystemAccess2 fsa, RosettaReport report) {
val tabulatorClass = report.toReportTabulatorJavaClass
val topScope = new JavaScope(tabulatorClass.packageName)

val context = getContext(report.reportType, Optional.ofNullable(report.ruleSource))
val context = getReportTabulatorContext(report.reportType, Optional.ofNullable(report.ruleSource))
val classBody = report.reportType.mainTabulatorClassBody(context, topScope, tabulatorClass)
val content = buildClass(tabulatorClass.packageName, classBody, topScope)
fsa.generateFile(tabulatorClass.canonicalName.withForwardSlashes + ".java", content)
}

def generate(IFileSystemAccess2 fsa, Data type, Optional<RosettaExternalRuleSource> ruleSource) {
val context = getContext(type, ruleSource)
def generateTabulatorForReportData(IFileSystemAccess2 fsa, Data type, Optional<RosettaExternalRuleSource> ruleSource) {
val context = getReportTabulatorContext(type, ruleSource)
if (context.needsTabulator(type)) {
val tabulatorClass = type.toTabulatorJavaClass(ruleSource)
val topScope = new JavaScope(tabulatorClass.packageName)
Expand All @@ -189,18 +190,15 @@ class TabulatorGenerator {
}
}

def generate(IFileSystemAccess2 fsa, Data type) {
def generateTabulatorForData(IFileSystemAccess2 fsa, Data type) {
if (type.isDataTabulatable) {
val context = createDataTabulatorContext(typeTranslator)

val tabulatorClass = type.toTabulatorJavaClass
val topScope = new JavaScope(tabulatorClass.packageName)

generateTabulator(type, context, topScope, tabulatorClass, fsa)

recursivelyGenerateTabulators(fsa, type, context, newHashSet)
}
}

def generate(IFileSystemAccess2 fsa, Function func) {
def generateTabulatorForFunction(IFileSystemAccess2 fsa, Function func) {
if (func.isFunctionTabulatable) {
val tabulatorClass = func.toApplicableTabulatorClass
val topScope = new JavaScope(tabulatorClass.packageName)
Expand All @@ -209,20 +207,17 @@ class TabulatorGenerator {
if (functionOutputType instanceof RDataType) {
val context = createFunctionTabulatorContext(typeTranslator, func)

generateTabulator(functionOutputType.data, context, topScope, tabulatorClass, fsa)
val type = functionOutputType.data
val classBody = type.mainTabulatorClassBody(context, topScope, tabulatorClass)
val content = buildClass(tabulatorClass.packageName, classBody, topScope)
fsa.generateFile(tabulatorClass.canonicalName.withForwardSlashes + ".java", content)

recursivelyGenerateTabulators(fsa, type, context, newHashSet)
}
}
}

private def void generateTabulator(Data type, TabulatorContext context, JavaScope topScope, JavaClass<Tabulator<?>> tabulatorClass, IFileSystemAccess2 fsa) {
val classBody = type.mainTabulatorClassBody(context, topScope, tabulatorClass)
val content = buildClass(tabulatorClass.packageName, classBody, topScope)
fsa.generateFile(tabulatorClass.canonicalName.withForwardSlashes + ".java", content)

recursivelyGenerateFunctionTypeTabulators(fsa, type, context, newHashSet)
}

private def void recursivelyGenerateFunctionTypeTabulators(IFileSystemAccess2 fsa, Data type, TabulatorContext context, Set<Data> visited) {
private def void recursivelyGenerateTabulators(IFileSystemAccess2 fsa, Data type, TabulatorContext context, Set<Data> visited) {
if (visited.add(type)) {
val tabulatorClass = context.toTabulatorJavaClass(type)
val topScope = new JavaScope(tabulatorClass.packageName)
Expand All @@ -235,11 +230,11 @@ class TabulatorGenerator {
.allNonOverridesAttributes
.map[typeProvider.getRTypeOfSymbol(it)]
.filter(RDataType)
.forEach[recursivelyGenerateFunctionTypeTabulators(fsa, data, context, visited)]
.forEach[recursivelyGenerateTabulators(fsa, data, context, visited)]
}
}

private def ReportTabulatorContext getContext(Data type, Optional<RosettaExternalRuleSource> ruleSource) {
private def ReportTabulatorContext getReportTabulatorContext(Data type, Optional<RosettaExternalRuleSource> ruleSource) {
val ruleMap = newHashMap
type.getAllReportingRules(ruleSource).forEach[key, rule| ruleMap.put(key.attr, rule)]
new ReportTabulatorContext(extensions, typeTranslator, ruleMap, ruleSource)
Expand Down Expand Up @@ -337,6 +332,7 @@ class TabulatorGenerator {
val nestedTabulatorInstances = findNestedTabulatorsAndCreateIdentifiers(inputType, context, classScope)
val tabulateScope = classScope.methodScope("tabulate")
val inputParam = tabulateScope.createUniqueIdentifier("input")

'''
@«ImplementedBy»(«tabulatorClass».Impl.class)
public interface «tabulatorClass» extends «Tabulator»<«inputClass»> {
Expand Down Expand Up @@ -445,12 +441,17 @@ class TabulatorGenerator {
«FieldValue» «resultId» = «Optional».ofNullable(«inputParam».get«attr.name.toFirstUpper»())
«IF attr.card.isMany»
.map(«lambdaParam» -> «lambdaParam».stream()
.map(«nestedLambdaParam» -> «nestedTabulator».tabulate(«nestedLambdaParam»«IF !attr.metaAnnotations.empty».getValue()«ENDIF»))
«IF !attr.metaAnnotations.empty»
.map(«nestedLambdaParam» -> «nestedLambdaParam».getValue())
.filter(«Objects»::nonNull)
«ENDIF»
.map(«nestedLambdaParam» -> «nestedTabulator».tabulate(«nestedLambdaParam»))
.collect(«Collectors».toList()))
.map(fieldValues -> new «MultiNestedFieldValueImpl»(«scope.getIdentifierOrThrow(attr)», Optional.of(fieldValues)))
.orElse(new «MultiNestedFieldValueImpl»(«scope.getIdentifierOrThrow(attr)», Optional.empty()));
«ELSE»
.map(«lambdaParam» -> new «NestedFieldValueImpl»(«scope.getIdentifierOrThrow(attr)», Optional.of(«nestedTabulator».tabulate(«lambdaParam»«IF !attr.metaAnnotations.empty».getValue()«ENDIF»))))
«IF !attr.metaAnnotations.empty».map(«lambdaParam» -> «lambdaParam».getValue())«ENDIF»
.map(«lambdaParam» -> new «NestedFieldValueImpl»(«scope.getIdentifierOrThrow(attr)», Optional.of(«nestedTabulator».tabulate(«lambdaParam»))))
.orElse(new «NestedFieldValueImpl»(«scope.getIdentifierOrThrow(attr)», Optional.empty()));
«ENDIF»
'''
Expand All @@ -463,6 +464,7 @@ class TabulatorGenerator {
«FieldValue» «resultId» = new «FieldValueImpl»(«scope.getIdentifierOrThrow(attr)», «Optional».ofNullable(«inputParam».get«attr.name.toFirstUpper»())
.map(«lambdaParam» -> «lambdaParam».stream()
.map(«nestedLambdaParam» -> «nestedLambdaParam».getValue())
.filter(«Objects»::nonNull)
.collect(«Collectors».toList())));
«ELSE»
«FieldValue» «resultId» = new «FieldValueImpl»(«scope.getIdentifierOrThrow(attr)», «Optional».ofNullable(«inputParam».get«attr.name.toFirstUpper»())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,70 @@
package com.regnosys.rosetta.generator.java.object

import org.junit.jupiter.api.Test
import com.regnosys.rosetta.config.file.RosettaConfigurationFileProvider

import static extension com.regnosys.rosetta.tests.util.CustomConfigTestHelper.*
import java.net.URL
import static org.junit.jupiter.api.Assertions.*
import static org.hamcrest.MatcherAssert.*
import org.hamcrest.CoreMatchers
import org.junit.jupiter.api.Test

import static org.hamcrest.MatcherAssert.*
import static org.junit.jupiter.api.Assertions.*

import static extension com.regnosys.rosetta.tests.util.CustomConfigTestHelper.*

class ConfigurableTypeTabulatorTest {

@Test
def void shouldGenerateTabulatorsForTypeListedInConfig_DefaultNamespace() {
// default testing namespace is com.rosetta.test.model
val model = '''
type Foo:
bar string (1..1)
'''

val code = model.generateCodeForModel(DefaultNamespaceFileConfigProvider)
val fooTabulatorCode = code.get("com.rosetta.test.model.tabulator.FooTypeTabulator")
assertThat(fooTabulatorCode, CoreMatchers.notNullValue())
var expected = '''
package com.rosetta.test.model.tabulator;
import com.google.inject.ImplementedBy;
import com.rosetta.model.lib.reports.Tabulator;
import com.rosetta.model.lib.reports.Tabulator.Field;
import com.rosetta.model.lib.reports.Tabulator.FieldImpl;
import com.rosetta.model.lib.reports.Tabulator.FieldValue;
import com.rosetta.model.lib.reports.Tabulator.FieldValueImpl;
import com.rosetta.test.model.Foo;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@ImplementedBy(FooTypeTabulator.Impl.class)
public interface FooTypeTabulator extends Tabulator<Foo> {
public class Impl implements FooTypeTabulator {
private final Field barField;
public Impl() {
this.barField = new FieldImpl(
"bar",
false,
Optional.empty(),
Optional.empty(),
Arrays.asList()
);
}
@Override
public List<FieldValue> tabulate(Foo input) {
FieldValue bar = new FieldValueImpl(barField, Optional.ofNullable(input.getBar()));
return Arrays.asList(
bar
);
}
}
}
'''
assertEquals(expected, fooTabulatorCode)
}

@Test
def void shouldGenerateTabulatorsForTypeListedInConfig() {
Expand Down Expand Up @@ -105,6 +160,7 @@ class ConfigurableTypeTabulatorTest {
import com.rosetta.model.lib.reports.Tabulator.FieldValueImpl;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import model2.EngineSpecification;
Expand All @@ -130,6 +186,7 @@ class ConfigurableTypeTabulatorTest {
FieldValue fuel = new FieldValueImpl(fuelField, Optional.ofNullable(input.getFuel())
.map(x -> x.stream()
.map(_x -> _x.getValue())
.filter(Objects::nonNull)
.collect(Collectors.toList())));
return Arrays.asList(
fuel
Expand All @@ -152,4 +209,10 @@ class ConfigurableTypeTabulatorTest {
Thread.currentThread.contextClassLoader.getResource("rosetta-tabulator-type-config-model2.yml")
}
}

private static class DefaultNamespaceFileConfigProvider extends RosettaConfigurationFileProvider {
override URL get() {
Thread.currentThread.contextClassLoader.getResource("rosetta-tabulator-type-config-default.yml")
}
}
}
Loading

0 comments on commit 1fe2d32

Please sign in to comment.