Skip to content

Commit

Permalink
Condition support in typeAlias (#909)
Browse files Browse the repository at this point in the history
* Added support for conditions in typeAliases. See rosetta-testing/.../RosettaParsingTest.java for syntax examples

* Added new typealias test with sample condition

* RAliasType conditions used in type-format validation static context. Mock functions proposed in ExpressionOperators.java

* Mock type format validators generator for typeAliases with conditions specific for CDM reference-data project

* Update static method and class references in ValidatorsGenerator.xtend

* Updated typeAlias join() logic and improved ValidatorsGenerator

* Removed development code comments

* Resolves compilation errors if IsValidCodingScheme condition is not present

* Removes unnecessary comments
  • Loading branch information
manel-martos authored Feb 21, 2025
1 parent a27a430 commit 7d29fee
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,20 @@ class RosettaParsingTest {
'''.parseRosettaWithNoIssues
}

@Test
def void testTypeAliasesWithConditions() {
'''
func Foo:
inputs:
code DomainCodeList (1..1)
domain string (1..1)
output: result boolean (1..1)
set result: True
typeAlias DomainCodeList (domain string): string
condition IsValidCode: Foo(item, domain)
'''.parseRosettaWithNoIssues
}

@Test
def void testParametrizedBasicTypes() {
'''
Expand Down
2 changes: 2 additions & 0 deletions rosetta-lang/model/Rosetta.xcore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.regnosys.rosetta.rosetta.simple.Data
import com.regnosys.rosetta.rosetta.simple.Attribute
import com.regnosys.rosetta.rosetta.simple.RosettaRuleReference
import com.regnosys.rosetta.rosetta.expression.RosettaExpression
import com.regnosys.rosetta.rosetta.simple.Condition

class RosettaModel extends RosettaDefinable {
String name
Expand Down Expand Up @@ -119,6 +120,7 @@ class RosettaParameter extends RosettaTyped, RosettaSymbol {
* Data model
*/
class RosettaTypeAlias extends RosettaRootElement, RosettaType, RosettaTyped, RosettaDefinable, ParametrizedRosettaType {
contains Condition[] conditions
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ RosettaCalculationType returns RosettaBasicType:
*/
RosettaTypeAlias:
'typeAlias' RosettaNamed TypeParameters?
':' RosettaDefinable? RosettaTyped
':' RosettaDefinable? RosettaTyped
conditions += Condition*
;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ import com.regnosys.rosetta.types.RCardinality
import com.regnosys.rosetta.generator.java.types.JavaPojoInterface
import com.regnosys.rosetta.generator.java.statement.builder.JavaExpression

import com.regnosys.rosetta.rosetta.simple.Condition;
import com.regnosys.rosetta.types.RAliasType
import java.util.Collections
import java.util.ArrayList

class ValidatorsGenerator {

@Inject extension ImportManagerExtension
Expand Down Expand Up @@ -113,14 +118,25 @@ class ValidatorsGenerator {
}
'''
def private StringConcatenationClient typeFormatClassBody(JavaPojoInterface javaType, String version, Iterable<RAttribute> attributes) '''
def private StringConcatenationClient typeFormatClassBody(JavaPojoInterface javaType, String version, Iterable<RAttribute> attributes) {
val conditions = attributes.map[it.RMetaAnnotatedType.RType.collectConditionsFromTypeAliases].flatten
'''
public class «javaType.toTypeFormatValidatorClass» implements «Validator»<«javaType»> {
«IF conditions.size() > 0»
«IF conditions.map[it.name].filter[it.equalsIgnoreCase("IsValidCodingScheme")].size > 0»
protected cdm.base.staticdata.codelist.functions.ValidateFpMLCodingSchemeDomain func = new cdm.base.staticdata.codelist.ValidateFpMLCodingSchemeImpl();
«ENDIF»
«ENDIF»

private «List»<«ComparisonResult»> getComparisonResults(«javaType» o) {
return «Lists».<«ComparisonResult»>newArrayList(
«FOR attrCheck : attributes.map[checkTypeFormat(javaType, it)].filter[it !== null] SEPARATOR ", "»
«attrCheck»
«ENDFOR»
«FOR condCheck : attributes.map[checkTypeAliasFormat(javaType, it)].filter[it !== null] SEPARATOR ", "»
«condCheck»
«ENDFOR»
);
}

Expand Down Expand Up @@ -152,7 +168,8 @@ class ValidatorsGenerator {
}

}
'''
'''
}
def private StringConcatenationClient onlyExistsClassBody(JavaPojoInterface javaType, String version, Iterable<RAttribute> attributes) {
Expand Down Expand Up @@ -202,9 +219,50 @@ class ValidatorsGenerator {
'''
}
}
private def StringConcatenationClient checkTypeAliasFormat(JavaPojoInterface javaType, RAttribute attr) {
val conditions = attr.RMetaAnnotatedType.RType.collectConditionsFromTypeAliases
val args = attr.RMetaAnnotatedType.RType.collectArgumentsFromTypeAliases
if (conditions.isEmpty() || (!conditions.map[it.name].exists[it.equalsIgnoreCase("IsValidCodingScheme")] && !args.containsKey("domain"))) {
null
} else {
val prop = javaType.findProperty(attr.name)
val propCode = prop.applyGetter(JavaExpression.from('''o''', javaType));
'''
«FOR cond : conditions»
«IF cond.getName().equalsIgnoreCase("IsValidCodingScheme") && args.get("domain").getSingle().isPresent()»
«IF attr.isMulti»
«IF !attr.RMetaAnnotatedType.hasMeta»
ComparisonResult») «method(Optional, "ofNullable")»(«propCode»).orElse(«method(Collections, "emptyList")»())
.stream()
.filter(it -> !func.evaluate(it, "«StringEscapeUtils.escapeJava(args.get("domain").getSingle().orElse(null) as String)»"))
.collect(«Collectors».collectingAndThen(
«Collectors».joining(", "),
it -> it.isEmpty() ? «method(ComparisonResult, "success")»() : «method(ComparisonResult, "failure")»(it + " code not found in domain '«StringEscapeUtils.escapeJava(args.get("domain").getSingle().orElse(null) as String)»'")
))
«ELSE»
ComparisonResult») «method(Optional, "ofNullable")»(«propCode»).orElse(«method(Collections, "emptyList")»())
.stream().map(«prop.type.itemType»::getValue)
.filter(it -> !func.evaluate(it, "«StringEscapeUtils.escapeJava(args.get("domain").getSingle().orElse(null) as String)»"))
.collect(«Collectors».collectingAndThen(
«Collectors».joining(", "),
it -> it.isEmpty() ? «method(ComparisonResult, "success")»() : «method(ComparisonResult, "failure")»(it + " code not found in domain '«StringEscapeUtils.escapeJava(args.get("domain").getSingle().orElse(null) as String)»'")
))
«ENDIF»
«ELSE»
func.evaluate(«javaType.getAttributeValue(attr)», "«StringEscapeUtils.escapeJava(args.get("domain").getSingle().orElse(null) as String)»")?
«method(ComparisonResult, "success")»() : «method(ComparisonResult, "failure")»(«javaType.getAttributeValue(attr)» + " code not found in domain '«StringEscapeUtils.escapeJava(args.get("domain").getSingle().orElse(null) as String)»'")
«ENDIF»
«ENDIF»
«ENDFOR»
'''
}
}
private def StringConcatenationClient checkTypeFormat(JavaPojoInterface javaType, RAttribute attr) {
val t = attr.RMetaAnnotatedType.RType.stripFromTypeAliases
if (t instanceof RStringType) {
if (t != UNCONSTRAINED_STRING) {
val min = t.interval.minBound
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,23 @@
package com.regnosys.rosetta.types;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;

import com.regnosys.rosetta.interpreter.RosettaValue;
import com.regnosys.rosetta.rosetta.simple.Condition;
import com.rosetta.model.lib.ModelSymbolId;


public class RAliasType extends RParametrizedType {
private final RTypeFunction typeFunction;
private final RType refersTo;
private final List<Condition> conditions;

public RAliasType(RTypeFunction typeFunction, LinkedHashMap<String, RosettaValue> params, RType refersTo) {
public RAliasType(RTypeFunction typeFunction, LinkedHashMap<String, RosettaValue> params, RType refersTo, List<Condition> conditions) {
super(params);
this.typeFunction = typeFunction;
this.refersTo = refersTo;
this.conditions = conditions;
}

@Override
Expand All @@ -46,6 +49,10 @@ public RType getRefersTo() {
return refersTo;
}

public List<Condition> getConditions() {
return conditions;
}

@Override
public boolean hasNaturalOrder() {
return refersTo.hasNaturalOrder();
Expand All @@ -64,6 +71,7 @@ public boolean equals(final Object object) {
RAliasType other = (RAliasType) object;
return Objects.equals(typeFunction, other.typeFunction)
&& Objects.equals(getArguments(), other.getArguments())
&& Objects.equals(refersTo, other.refersTo);
&& Objects.equals(refersTo, other.refersTo)
&& Objects.equals(conditions, other.conditions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.util.List;
import java.util.Optional;
import java.util.Stack;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.inject.Inject;

Expand All @@ -15,6 +17,8 @@
import com.regnosys.rosetta.types.builtin.RNumberType;
import com.regnosys.rosetta.types.builtin.RStringType;

import com.regnosys.rosetta.rosetta.simple.Condition;

/**
* An implementation of Rune's subtype relation. This class
* allows you to check whether one type is a subtype of the other,
Expand Down Expand Up @@ -159,7 +163,8 @@ public RType join(RAliasType t1, RAliasType t2) {
RTypeFunction typeFunc = t1.getTypeFunction();
RType underlyingJoin = join(t1.getRefersTo(), t2.getRefersTo());
Optional<LinkedHashMap<String, RosettaValue>> aliasParams = typeFunc.reverse(underlyingJoin);
return aliasParams.<RType>map(p -> new RAliasType(typeFunc, p, underlyingJoin))
//Condition intersection should be considered in the long term. Picked one set of conditions since are tightly coupled with typeFunctions
return aliasParams.<RType>map(p -> new RAliasType(typeFunc, p, underlyingJoin, t1.getConditions()))
.orElse(underlyingJoin);
} else {
return joinByTraversingAncestorsAndAliases(t1, t2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -53,7 +54,7 @@ public RAliasType constrainedInt(Optional<Integer> digits, Optional<BigInteger>
LinkedHashMap<String, RosettaValue> args = new LinkedHashMap<>(refersTo.getArguments());
args.remove(RNumberType.FRACTIONAL_DIGITS_PARAM_NAME);
args.remove(RNumberType.SCALE_PARAM_NAME);
return new RAliasType(builtinTypes.INT_FUNCTION, args, refersTo);
return new RAliasType(builtinTypes.INT_FUNCTION, args, refersTo, new ArrayList<>());
}
public RAliasType constrainedInt(int digits, BigInteger min, BigInteger max) {
return constrainedInt(Optional.of(digits), Optional.of(min), Optional.of(max));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.regnosys.rosetta.types;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
Expand All @@ -26,6 +27,7 @@
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.inject.Inject;
import javax.inject.Provider;
Expand Down Expand Up @@ -58,6 +60,8 @@

import org.eclipse.xtext.xbase.lib.Pair;

import com.regnosys.rosetta.rosetta.simple.Condition;

public class TypeSystem {
public static String RULE_INPUT_TYPE_CACHE_KEY = TypeSystem.class.getCanonicalName() + ".RULE_INPUT_TYPE";

Expand Down Expand Up @@ -242,7 +246,8 @@ public RType typeCallToRType(TypeCall typeCall, RosettaInterpreterContext contex
});
absentParameters.forEach(p -> args.put(p.getName(), RosettaValue.empty()));
RType refersTo = typeCallToRType(alias.getTypeCall(), RosettaInterpreterContext.of(args));
return new RAliasType(typeFunctionOfTypeAlias(alias), args, refersTo);
List<Condition> conditions = alias.getConditions();
return new RAliasType(typeFunctionOfTypeAlias(alias), args, refersTo, conditions);
}
return builtins.NOTHING;
}
Expand Down Expand Up @@ -299,8 +304,9 @@ public RType keepTypeAliasIfPossible(RType t1, RType t2, BiFunction<RType, RType
if (alias1.getTypeFunction().equals(alias2.getTypeFunction())) {
RTypeFunction typeFunc = alias1.getTypeFunction();
RType underlier = keepTypeAliasIfPossible(alias1.getRefersTo(), alias2.getRefersTo(), combineUnderlyingTypes);
//Condition intersection should be considered in the long term. Picked one set of conditions since are tightly coupled with typeFunctions
return typeFunc.reverse(underlier)
.<RType>map(args -> new RAliasType(typeFunc, args, underlier))
.<RType>map(args -> new RAliasType(typeFunc, args, underlier, alias1.getConditions()))
.orElse(underlier);
} else {
List<RAliasType> superAliases = new ArrayList<>();
Expand Down Expand Up @@ -333,11 +339,29 @@ public RType keepTypeAliasIfPossible(RType t1, RType t2, BiFunction<RType, RType
}
return combineUnderlyingTypes.apply(t1, t2);
}

public RType stripFromTypeAliases(RType t) {
while (t instanceof RAliasType) {
t = ((RAliasType)t).getRefersTo();
}
return t;
}

public List<Condition> collectConditionsFromTypeAliases(RType t) {
List<Condition> conditions = new ArrayList<>();
while (t instanceof RAliasType) {
conditions.addAll(((RAliasType)t).getConditions());
t = ((RAliasType)t).getRefersTo();
}
return conditions;
}

public Map<String, RosettaValue> collectArgumentsFromTypeAliases(RType t) {
Map<String, RosettaValue> args = new HashMap<>();
while (t instanceof RAliasType) {
args.putAll(((RAliasType)t).getArguments());
t = ((RAliasType)t).getRefersTo();
}
return args;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.regnosys.rosetta.types.builtin;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
Expand Down Expand Up @@ -83,7 +84,7 @@ public Optional<LinkedHashMap<String, RosettaValue>> reverse(RType type) {
public final RMetaAnnotatedType NOTHING_WITH_NO_META = RMetaAnnotatedType.withNoMeta(NOTHING);
public final RBasicType ANY = registerConstantType(new RBasicType("any", false));
public final RMetaAnnotatedType ANY_WITH_NO_META = RMetaAnnotatedType.withNoMeta(ANY);
public final RAliasType UNCONSTRAINED_INT = new RAliasType(INT_FUNCTION, new LinkedHashMap<>(Map.of(RNumberType.DIGITS_PARAM_NAME, RosettaValue.empty(), RNumberType.MIN_PARAM_NAME, RosettaValue.empty(), RNumberType.MAX_PARAM_NAME, RosettaValue.empty())), new RNumberType(Optional.empty(), Optional.of(0), Optional.empty(), Optional.empty(), Optional.empty()));
public final RAliasType UNCONSTRAINED_INT = new RAliasType(INT_FUNCTION, new LinkedHashMap<>(Map.of(RNumberType.DIGITS_PARAM_NAME, RosettaValue.empty(), RNumberType.MIN_PARAM_NAME, RosettaValue.empty(), RNumberType.MAX_PARAM_NAME, RosettaValue.empty())), new RNumberType(Optional.empty(), Optional.of(0), Optional.empty(), Optional.empty(), Optional.empty()), new ArrayList<>());
public final RMetaAnnotatedType UNCONSTRAINED_INT_WITH_NO_META = RMetaAnnotatedType.withNoMeta(UNCONSTRAINED_INT);
public final RNumberType UNCONSTRAINED_NUMBER = new RNumberType(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
public final RMetaAnnotatedType UNCONSTRAINED_NUMBER_WITH_NO_META = RMetaAnnotatedType.withNoMeta(UNCONSTRAINED_NUMBER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.regnosys.rosetta.rosetta.expression.RosettaImplicitVariable;
import com.regnosys.rosetta.rosetta.expression.SwitchCaseOrDefault;
import com.regnosys.rosetta.rosetta.simple.Data;
import com.regnosys.rosetta.rosetta.RosettaTypeAlias;

/**
* A tool for finding information about implicit variables, often called
Expand All @@ -51,6 +52,8 @@ public Optional<? extends EObject> findContainerDefiningImplicitVariable(EObject
for (EObject container: containers) {
if (container instanceof Data) {
return Optional.of(container);
} else if (container instanceof RosettaTypeAlias) {
return Optional.of(container);
} else if (container instanceof RosettaFunctionalOperation) {
RosettaFunctionalOperation op = (RosettaFunctionalOperation)container;
InlineFunction f = op.getFunction();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@

import com.rosetta.model.lib.RosettaModelObject;
import com.rosetta.model.lib.mapper.Mapper;
import com.rosetta.model.lib.mapper.MapperC;
import com.rosetta.model.lib.mapper.Mapper.Path;
import com.rosetta.model.lib.mapper.MapperC;
import com.rosetta.model.lib.mapper.MapperS;
import com.rosetta.model.lib.meta.RosettaMetaData;
import com.rosetta.model.lib.validation.ChoiceRuleValidationMethod;
Expand Down

0 comments on commit 7d29fee

Please sign in to comment.