Skip to content
Snippets Groups Projects
Commit ef3679c0 authored by René Schöne's avatar René Schöne
Browse files

Resolve "Feature: Send endpoint for relations"

- added support for sending relation targets
- internally, relations use forwarding
- templates: add null check before mapping application only if endpoint is not a primitive type
- generation: add default send mapping for java.util.List
- Comiler: always print stacktrace upon compiler error
- build: ragconnect.tests:classes depends on ragconnect.base:jar
- test: checking in RelationTest, should be used in all tests, see #32
parent e616deb6
No related branches found
No related tags found
3 merge requests!39Version 1.1.0,!35Version 1.0.0,!27Resolve "Feature: Send endpoint for relations"
Showing
with 1166 additions and 27 deletions
......@@ -15,6 +15,19 @@ aspect Analysis {
}
return numberOfSameDefs > 1;
}
eq RelationEndpointTarget.isAlreadyDefined() {
// define lookup here, as not used elsewhere
int numberOfSameDefs = 0;
for (EndpointTarget target : ragconnect().givenEndpointTargetList()) {
if (target.isRelationEndpointTarget()) {
RelationEndpointTarget other = target.asRelationEndpointTarget();
if (other.getRole().equals(this.getRole())) {
numberOfSameDefs += 1;
}
}
}
return numberOfSameDefs > 1;
}
eq TokenEndpointTarget.isAlreadyDefined() {
return lookupTokenEndpointDefinitions(getToken()).stream()
.filter(containingEndpointDefinition()::matchesType)
......@@ -49,7 +62,8 @@ aspect Analysis {
return target.primitivePrettyPrint().equals(this.primitivePrettyPrint());
}
syn String JavaTypeUse.primitivePrettyPrint() {
switch (getName()) {
String name = getName();
switch (name) {
case "boolean":
case "Boolean":
return "boolean";
......@@ -78,6 +92,7 @@ aspect Analysis {
syn boolean EndpointTarget.hasAttributeResetMethod();
eq AttributeEndpointTarget.hasAttributeResetMethod() = false;
eq RelationEndpointTarget.hasAttributeResetMethod() = false;
eq TokenEndpointTarget.hasAttributeResetMethod() = getToken().getNTA();
eq TypeEndpointTarget.hasAttributeResetMethod() = getType().getNTA();
eq ContextFreeTypeEndpointTarget.hasAttributeResetMethod() = false;
......
......@@ -59,6 +59,7 @@ aspect SharedMustache {
case "warn":
case "error":
case "exception":
//noinspection DuplicateBranchesInSwitch
return "ASTNode." + logConsoleErr();
default:
return "unknownLoggingLevelForConsole_" + level + "_";
......@@ -74,9 +75,8 @@ aspect SharedMustache {
}
}
syn boolean EndpointTarget.typeIsList() = false;
eq TypeEndpointTarget.typeIsList() {
return getType().isListComponent();
}
eq TypeEndpointTarget.typeIsList() = getType().isListComponent();
eq RelationEndpointTarget.typeIsList() = getRole().isListRole();
syn boolean EndpointTarget.typeIsOpt() = false;
eq TypeEndpointTarget.typeIsOpt() {
......@@ -215,6 +215,10 @@ aspect MustacheMappingApplicationAndDefinition {
eq MAttributeSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
eq MAttributeSendDefinition.preemptiveReturn() = "return false;";
eq MRelationSendDefinition.firstInputVarName() = getterMethodCall();
eq MRelationSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
eq MRelationSendDefinition.preemptiveReturn() = "return false;";
eq MTokenReceiveDefinition.firstInputVarName() = "message";
eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethodCall();
eq MTokenReceiveDefinition.preemptiveReturn() = "return;";
......@@ -425,6 +429,15 @@ aspect MustacheReceiveAndSendAndHandleUri {
eq AttributeEndpointTarget.parentTypeName() = getParentTypeDecl().getName();
eq AttributeEndpointTarget.entityName() = getName();
eq RelationEndpointTarget.getterMethodName() = forwardingNTA_Name();
eq RelationEndpointTarget.parentTypeName() = getRole().getType().getName();
eq RelationEndpointTarget.entityName() = getRole().getName();
eq RelationEndpointTarget.realGetterMethodName() = "get" + getRole().getterMethodName();
eq RelationEndpointTarget.realGetterMethodCall() = realGetterMethodName() + (containingEndpointDefinition().indexedSend() ? "(index)" : "()");
syn String NavigableRole.getterMethodName() = getName();
eq ListRole.getterMethodName() = getName() + "List";
eq TokenEndpointTarget.getterMethodName() = "get" + getToken().getName();
eq TokenEndpointTarget.parentTypeName() = getToken().containingTypeDecl().getName();
eq TokenEndpointTarget.entityName() = getToken().getName();
......@@ -481,6 +494,8 @@ aspect MustacheSendDefinition {
syn String EndpointDefinition.forwardingNTA_Name() = getEndpointTarget().forwardingNTA_Name();
syn String EndpointDefinition.forwardingNTA_Type() = getEndpointTarget().forwardingNTA_Type();
syn boolean EndpointDefinition.relationEndpointWithListRole() = getEndpointTarget().relationEndpointWithListRole();
syn String EndpointDefinition.senderName() = getEndpointTarget().senderName();
syn boolean EndpointDefinition.shouldNotResetValue() = getSend() && !getEndpointTarget().hasAttributeResetMethod();
......@@ -494,13 +509,17 @@ aspect MustacheSendDefinition {
// === attributes needed for computing above ones ===
syn boolean EndpointTarget.needForwardingNTA() = false;
eq TypeEndpointTarget.needForwardingNTA() = containingEndpointDefinition().getSend() && !getType().getNTA();
eq RelationEndpointTarget.needForwardingNTA() = containingEndpointDefinition().getSend();
syn String EndpointTarget.forwardingNTA_Name() = null;
syn String EndpointTarget.forwardingNTA_Name() = null; // only needed, if needForwardingNTA evaluates to true
eq TypeEndpointTarget.forwardingNTA_Name() = ragconnect().internalRagConnectPrefix() + getType().getName();
eq RelationEndpointTarget.forwardingNTA_Name() = ragconnect().internalRagConnectPrefix() + getRole().getName();
syn String EndpointTarget.forwardingNTA_Type() = null;
syn String EndpointTarget.forwardingNTA_Type() = null; // only needed, if needForwardingNTA evaluates to true
eq TypeEndpointTarget.forwardingNTA_Type() = getType().forwardingNTA_Type(
containingEndpointDefinition().getIndexBasedListAccess());
eq RelationEndpointTarget.forwardingNTA_Type() = getRole().forwardingNTA_Type(
containingEndpointDefinition().getIndexBasedListAccess());
syn String TypeComponent.forwardingNTA_Type(boolean indexBasedListAccess);
eq NormalComponent.forwardingNTA_Type(boolean indexBasedListAccess) = getTypeDecl().getName();
......@@ -510,6 +529,14 @@ aspect MustacheSendDefinition {
getTypeDecl().getName() :
ragconnect().configJastAddList() + "<" + getTypeDecl().getName() + ">";
syn String Role.forwardingNTA_Type(boolean indexBasedListAccess) = oppositeRole().getType().getName();
eq ListRole.forwardingNTA_Type(boolean indexBasedListAccess) = indexBasedListAccess ?
oppositeRole().getType().getName() :
"java.util.List<" + oppositeRole().getType().getName() + ">";
syn boolean EndpointTarget.relationEndpointWithListRole() = false;
eq RelationEndpointTarget.relationEndpointWithListRole() = getRole().isListRole();
syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
eq ContextFreeTypeEndpointTarget.senderName() = null;
......@@ -519,6 +546,9 @@ aspect MustacheSendDefinition {
eq MAttributeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_attr_" + getEndpointDefinition().entityName();
eq MAttributeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_attr_" + getEndpointDefinition().entityName();
eq MRelationSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_" + getEndpointDefinition().entityName();
eq MRelationSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_" + getEndpointDefinition().entityName();
eq MTokenReceiveDefinition.updateMethodName() = null;
eq MTokenReceiveDefinition.writeMethodName() = null;
......@@ -641,6 +671,12 @@ aspect AttributesForMustache {
}
return new MAttributeSendDefinition();
}
MEndpointDefinition RelationEndpointTarget.createMEndpointDefinition(boolean isSend) {
if (!isSend) {
throw new IllegalArgumentException("RelationEndpointTarget can only be sent!");
}
return new MRelationSendDefinition();
}
MEndpointDefinition TokenEndpointTarget.createMEndpointDefinition(boolean isSend) {
return isSend ? new MTokenSendDefinition() : new MTokenReceiveDefinition();
}
......
......@@ -2,6 +2,7 @@ abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*
rel MEndpointDefinition.EndpointDefinition -> EndpointDefinition;
MAttributeSendDefinition : MEndpointDefinition;
MRelationSendDefinition : MEndpointDefinition;
abstract MTokenEndpointDefinition : MEndpointDefinition;
MTokenReceiveDefinition : MTokenEndpointDefinition;
MTokenSendDefinition : MTokenEndpointDefinition;
......
aspect DefaultMappings {
private String RagConnect.baseDefaultMappingTypeNamePart(String typeName) {
if (typeName.contains(".")) {
StringBuilder sb = new StringBuilder();
for (String part : typeName.split("\\.")) {
sb.append(baseDefaultMappingTypeNamePart(part));
}
return sb.toString();
} else {
return capitalize(typeName).replace("[]", "s").replace("<", "").replace(">", "List");
}
}
private MappingDefinitionType RagConnect.baseDefaultMappingTypeFromName(String typeName) {
private MappingDefinitionType RagConnect.baseDefaultMappingTypeName(String typeName) {
return typeName.endsWith("[]") ?
new JavaArrayMappingDefinitionType(new SimpleJavaTypeUse(typeName.replace("[]", ""))) :
new JavaMappingDefinitionType(new SimpleJavaTypeUse(typeName));
......@@ -13,9 +21,9 @@ aspect DefaultMappings {
private DefaultMappingDefinition RagConnect.createDefaultMappingDefinition(String prefix, String fromTypeName, String toTypeName, String content) {
DefaultMappingDefinition result = new DefaultMappingDefinition();
result.setID(prefix + baseDefaultMappingTypeNamePart(fromTypeName) + "To" + baseDefaultMappingTypeNamePart(toTypeName) + "Mapping");
result.setFromType(baseDefaultMappingTypeFromName(fromTypeName));
result.setFromType(baseDefaultMappingTypeName(fromTypeName));
result.setFromVariableName("input");
result.setToType(baseDefaultMappingTypeFromName(toTypeName));
result.setToType(baseDefaultMappingTypeName(toTypeName));
result.setContent(content);
return result;
}
......@@ -67,7 +75,7 @@ aspect DefaultMappings {
);
}
syn nta DefaultMappingDefinition RagConnect.defaultBytesToListTreeMapping(String typeName) {
syn nta DefaultMappingDefinition RagConnect.defaultBytesToListMapping(String typeName) {
return treeDefaultMappingDefinition("byte[]", configJastAddList() + "<" + typeName + ">",
"String content = new String(input);\n" +
"com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();\n" +
......@@ -78,7 +86,7 @@ aspect DefaultMappings {
"return result;"
);
}
syn nta DefaultMappingDefinition RagConnect.defaultListTreeToBytesMapping() {
syn nta DefaultMappingDefinition RagConnect.defaultListToBytesMapping() {
return treeDefaultMappingDefinition(configJastAddList(), "byte[]",
"java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" +
"com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
......@@ -88,6 +96,16 @@ aspect DefaultMappings {
"return outputStream.toString().getBytes();"
);
}
syn nta DefaultMappingDefinition RagConnect.defaultJavaUtilListToBytesMapping() {
return treeDefaultMappingDefinition("java.util.List", "byte[]",
"java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();\n" +
"com.fasterxml.jackson.core.JsonFactory factory = new com.fasterxml.jackson.core.JsonFactory();\n" +
"com.fasterxml.jackson.core.JsonGenerator generator = factory.createGenerator(outputStream, com.fasterxml.jackson.core.JsonEncoding.UTF8);\n" +
"serializeJavaUtilList(input, generator);\n" +
"generator.flush();\n" +
"return outputStream.toString().getBytes();"
);
}
syn nta DefaultMappingDefinition RagConnect.defaultBooleanToBytesMapping() = baseDefaultMappingDefinition(
"boolean", "byte[]", "return java.nio.ByteBuffer.allocate(1).put((byte) (input ? 1 : 0)).array();");
......@@ -165,6 +183,10 @@ aspect Mappings {
}
// --- isPrimitiveType ---
syn boolean EndpointDefinition.isPrimitiveType() = getEndpointTarget().isPrimitiveType();
syn boolean EndpointTarget.isPrimitiveType() = false;
eq TokenEndpointTarget.isPrimitiveType() = getToken().isPrimitiveType();
eq AttributeEndpointTarget.isPrimitiveType() = new SimpleJavaTypeUse(getTypeName()).isPrimitiveType();
syn boolean TokenComponent.isPrimitiveType() = effectiveJavaTypeUse().isPrimitiveType();
syn boolean JavaTypeUse.isPrimitiveType() = false;
eq SimpleJavaTypeUse.isPrimitiveType() {
......@@ -218,7 +240,7 @@ aspect Mappings {
default:
try {
TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
return getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
return getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
} catch (Exception ignore) {
}
System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
......@@ -254,7 +276,10 @@ aspect Mappings {
return ragconnect().defaultStringToBytesMapping();
default:
if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) {
return ragconnect().defaultListTreeToBytesMapping();
return ragconnect().defaultListToBytesMapping();
}
if (getEndpointTarget().isRelationEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) {
return ragconnect().defaultJavaUtilListToBytesMapping();
}
try {
TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
......@@ -281,10 +306,14 @@ aspect Mappings {
}
syn String EndpointTarget.targetTypeName();
eq AttributeEndpointTarget.targetTypeName() = getTypeName();
eq RelationEndpointTarget.targetTypeName() = getRole().oppositeRole().targetTypeName();
eq TokenEndpointTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName();
eq TypeEndpointTarget.targetTypeName() = getType().getTypeDecl().getName();
eq ContextFreeTypeEndpointTarget.targetTypeName() = getTypeDecl().getName();
syn String Role.targetTypeName() = getType().getName();
eq ListRole.targetTypeName() = "java.util.List<" + getType().getName() + ">";
// eq ReceiveFromRestDefinition.suitableDefaultMapping() {
// String typeName = getMappingList().isEmpty() ?
// getToken().getJavaTypeUse().getName() :
......@@ -360,9 +389,10 @@ aspect Mappings {
for (TypeDecl typeDecl : getProgram().typeDecls()) {
result.add(defaultBytesToTreeMapping(typeDecl.getName()));
result.add(defaultTreeToBytesMapping(typeDecl.getName()));
result.add(defaultBytesToListTreeMapping(typeDecl.getName()));
result.add(defaultBytesToListMapping(typeDecl.getName()));
}
result.add(defaultListTreeToBytesMapping());
result.add(defaultListToBytesMapping());
result.add(defaultJavaUtilListToBytesMapping());
// // string conversion
// result.add(defaultStringToBooleanMapping());
// result.add(defaultStringToIntMapping());
......
......@@ -146,4 +146,39 @@ aspect RagConnectNameResolution {
return null;
}
// rel ___ -> Navigable
refine RefResolverStubs eq ASTNode.globallyResolveNavigableRoleByToken(String id) {
NavigableRole result = tryGloballyResolveNavigableRoleByToken(id);
if (result == null) {
System.err.println("Could not resolve role '" + id + "'.");
}
return result;
}
syn NavigableRole ASTNode.tryGloballyResolveNavigableRoleByToken(String id) {
// id is of the form 'type_name + "." + role_name'
int dotIndex = id.indexOf(".");
String typeName = id.substring(0, dotIndex);
String roleName = id.substring(dotIndex + 1);
for (Relation relation : program().relations()) {
if (relation.isDirectedRelation()) {
if (relation.asDirectedRelation().getSource().matches(typeName, roleName)) {
return relation.asDirectedRelation().getSource();
}
} else {
if (relation.asBidirectionalRelation().getLeft().matches(typeName, roleName)) {
return relation.asBidirectionalRelation().getLeft();
}
if (relation.asBidirectionalRelation().getRight().matches(typeName, roleName)) {
return relation.asBidirectionalRelation().getRight();
}
}
}
return null;
}
syn boolean Role.matches(String typeName, String roleName) = false;
eq NavigableRole.matches(String typeName, String roleName) {
return getType().getName().equals(typeName) && getName().equals(roleName);
}
}
aspect NewStuff {
aspect GeneratedNavigation {
/** Tests if EndpointTarget is a TokenEndpointTarget.
* @return 'true' if this is a TokenEndpointTarget, otherwise 'false'
......@@ -30,6 +30,12 @@ aspect NewStuff {
syn boolean EndpointTarget.isAttributeEndpointTarget() = false;
eq AttributeEndpointTarget.isAttributeEndpointTarget() = true;
/** Tests if EndpointTarget is a RelationEndpointTarget.
* @return 'true' if this is a RelationEndpointTarget, otherwise 'false'
*/
syn boolean EndpointTarget.isRelationEndpointTarget() = false;
eq RelationEndpointTarget.isRelationEndpointTarget() = true;
/** casts a EndpointTarget into a TokenEndpointTarget if possible.
* @return 'this' cast to a TokenEndpointTarget or 'null'
*/
......@@ -64,6 +70,13 @@ aspect NewStuff {
syn AttributeEndpointTarget EndpointTarget.asAttributeEndpointTarget();
eq EndpointTarget.asAttributeEndpointTarget() = null;
eq AttributeEndpointTarget.asAttributeEndpointTarget() = this;
/** casts a EndpointTarget into a RelationEndpointTarget if possible.
* @return 'this' cast to a RelationEndpointTarget or 'null'
*/
syn RelationEndpointTarget EndpointTarget.asRelationEndpointTarget();
eq EndpointTarget.asRelationEndpointTarget() = null;
eq RelationEndpointTarget.asRelationEndpointTarget() = this;
}
aspect RagConnectNavigation {
......@@ -121,6 +134,13 @@ aspect RagConnectNavigation {
// --- effectiveJavaTypeUse (should be in preprocessor) ---
syn lazy JavaTypeUse TokenComponent.effectiveJavaTypeUse() = hasJavaTypeUse() ? getJavaTypeUse() : new SimpleJavaTypeUse("String");
// --- oppositeRole ---
inh Role Role.oppositeRole();
eq DirectedRelation.getSource().oppositeRole() = getTarget();
eq DirectedRelation.getTarget().oppositeRole() = getSource();
eq BidirectionalRelation.getLeft().oppositeRole() = getRight();
eq BidirectionalRelation.getRight().oppositeRole() = getLeft();
// --- isDefaultMappingDefinition ---
syn boolean MappingDefinition.isDefaultMappingDefinition() = false;
eq DefaultMappingDefinition.isDefaultMappingDefinition() = true;
......
......@@ -13,12 +13,11 @@ TypeEndpointTarget : EndpointTarget;
rel TypeEndpointTarget.Type <-> TypeComponent.TypeEndpointTarget*;
ContextFreeTypeEndpointTarget : EndpointTarget;
rel ContextFreeTypeEndpointTarget.TypeDecl <-> TypeDecl.ContextFreeTypeEndpointTarget*;
UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName> <IsAttribute:boolean>; // only used by parser
// to be integrated:
AttributeEndpointTarget : EndpointTarget ::= <Name> <TypeName> ;
rel AttributeEndpointTarget.ParentTypeDecl <-> TypeDecl.AttributeEndpointTarget*;
//RelationEndpointTarget : EndpointTarget ;
//rel RelationEndpointTarget.Role <-> Role.RelationEndpointTarget* ;
RelationEndpointTarget : EndpointTarget ;
rel RelationEndpointTarget.Role <-> NavigableRole.RelationEndpointTarget* ;
UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName> <IsAttribute:boolean>; // only used by parser
DependencyDefinition ::= <ID>;
rel DependencyDefinition.Source <-> TokenComponent.DependencySourceDefinition*;
......
......@@ -16,6 +16,14 @@ aspect ParserRewrites {
return result;
}
when (getChildName() != null && tryGloballyResolveNavigableRoleByToken(combinedName()) != null)
to RelationEndpointTarget {
RelationEndpointTarget result = new RelationEndpointTarget();
result.copyOtherValuesFrom(this);
result.setRole(NavigableRole.createRef(this.combinedName()));
return result;
}
when (getChildName() == "")
to ContextFreeTypeEndpointTarget {
ContextFreeTypeEndpointTarget result = new ContextFreeTypeEndpointTarget();
......
......@@ -124,9 +124,7 @@ public class Compiler extends AbstractCompiler {
compiler.run(args);
} catch (CompilerException e) {
System.err.println(e.getMessage());
if (compiler.isVerbose()) {
e.printStackTrace();
}
System.exit(1);
}
}
......
......@@ -11,6 +11,19 @@ public void {{configJastAddList}}.serialize(com.fasterxml.jackson.core.JsonGener
}
}
protected static <T extends ASTNode> void ASTNode.serializeJavaUtilList(
java.util.List<T> input, com.fasterxml.jackson.core.JsonGenerator g) throws SerializationException {
try {
g.writeStartArray();
for (T child : input) {
child.serialize(g);
}
g.writeEndArray();
} catch (java.io.IOException e) {
throw new SerializationException("unable to serialize list", e);
}
}
{{#typesForReceivingListEndpoints}}
public static {{configJastAddList}}<{{Name}}> {{Name}}.deserializeList(com.fasterxml.jackson.databind.node.ArrayNode node) throws DeserializationException {
{{configJastAddList}}<{{Name}}> result = new {{configJastAddList}}<>();
......
{{#Send}}
{{^PrimitiveType}}
if ({{firstInputVarName}} == null) {
{{preemptiveReturn}}
}
{{/PrimitiveType}}
{{/Send}}
{{{lastDefinitionToType}}} {{lastResult}};
try {
{{#innerMappingDefinitions}}
......
......@@ -14,7 +14,13 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
{{#configLoggingEnabledForWrites}}
{{logDebug}}("[Send] {{entityName}} = {{log_}} -> {{log_}}", {{getterMethodCall}}, {{connectParameterName}});
{{/configLoggingEnabledForWrites}}
if ({{lastValueGetterCall}} != null) {
handler.publish(topic, {{lastValueGetterCall}});
{{#configLoggingEnabledForWrites}}
} else {
{{logWarn}}("[Send] {{entityName}} -> {{log_}}: can't send null.", {{connectParameterName}});
{{/configLoggingEnabledForWrites}}
}
}{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken);
{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
if (writeCurrentValue) {
......@@ -117,5 +123,20 @@ protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}i
}
{{#needForwardingNTA}}
syn {{{forwardingNTA_Type}}} {{parentTypeName}}.{{forwardingNTA_Name}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) = {{realGetterMethodCall}}.{{touchedTerminalsMethodName}}();
syn {{{forwardingNTA_Type}}} {{parentTypeName}}.{{forwardingNTA_Name}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
{{#relationEndpointWithListRole}}
// for (var element : {{realGetterMethodCall}}) {
// element.{{touchedTerminalsMethodName}}();
// }
{{realGetterMethodCall}}.stream().forEach(element -> element.{{touchedTerminalsMethodName}}());
return {{realGetterMethodCall}};
{{/relationEndpointWithListRole}}
{{^relationEndpointWithListRole}}
{{{forwardingNTA_Type}}} result = {{realGetterMethodCall}};
if (result == null) {
return null;
}
return result.{{touchedTerminalsMethodName}}();
{{/relationEndpointWithListRole}}
}
{{/needForwardingNTA}}
......@@ -151,6 +151,8 @@ def JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL = JASTADD_INCREMENTAL_OPTIONS.clone
JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL.set(0, '--tracing=all')
JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL.set(1, '--incremental=param,debug')
classes.dependsOn(':ragconnect.base:jar')
// --- Test: Example ---
task compileExampleTest(type: RagConnectTest) {
ragconnect {
......@@ -657,7 +659,30 @@ task compileAttributeIncremental(type: RagConnectTest) {
extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
}
}
compileAttributeIncremental.outputs.upToDateWhen { false }
// --- Test: relation-incremental ---
task compileRelationIncremental(type: RagConnectTest) {
ragconnect {
outputDir = file('src/test/02-after-ragconnect/relationInc')
inputFiles = [file('src/test/01-input/relation/Test.relast'),
file('src/test/01-input/relation/Test.connect')]
rootNode = 'Root'
logWrites = true
extraOptions = defaultRagConnectOptionsAnd(['--experimental-jastadd-329'])
}
relast {
useJastAddNames = true
grammarName = 'src/test/03-after-relast/relationInc/relationInc'
serializer = 'jackson'
}
jastadd {
jastAddList = 'JastAddList'
packageName = 'relationInc.ast'
inputFiles = [file('src/test/01-input/relation/Test.jadd')]
extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
}
}
compileRelationIncremental.outputs.upToDateWhen { false }
static ArrayList<String> defaultRagConnectOptionsAnd(ArrayList<String> options = []) {
if (!options.contains('--logTarget=slf4j')) {
......
# Relation
Idea: Use send definitions for relations.
send SenderRoot.MyA;
send SenderRoot.OptionalA;
send SenderRoot.ManyA;
send SenderRoot.BiMyA;
send SenderRoot.BiOptionalA;
send SenderRoot.BiManyA;
send SenderRoot.MyB using ConcatValues;
send SenderRoot.OptionalB using ConcatValues;
send SenderRoot.ManyB using ConcatValueList;
send SenderRoot.BiMyB using ConcatValues;
send SenderRoot.BiOptionalB using ConcatValues;
send SenderRoot.BiManyB using ConcatValueList;
ConcatValues maps B b to String {:
return b.getValue() + "+" + b.getInner().getInnerValue();
:}
ConcatValueList maps java.util.List<B> list to String {:
StringBuilder sb = new StringBuilder();
for (B b : list) {
sb.append(b.getValue() + "+" + b.getInner().getInnerValue()).append(";");
}
return sb.toString();
:}
receive ReceiverRoot.FromMyA;
receive ReceiverRoot.FromOptionalA;
receive ReceiverRoot.FromManyA;
receive ReceiverRoot.FromBiMyA;
receive ReceiverRoot.FromBiOptionalA;
receive ReceiverRoot.FromBiManyA;
receive ReceiverRoot.FromMyB;
receive ReceiverRoot.FromOptionalB;
receive ReceiverRoot.FromManyB;
receive ReceiverRoot.FromBiMyB;
receive ReceiverRoot.FromBiOptionalB;
receive ReceiverRoot.FromBiManyB;
aspect Computation {
}
aspect MakeCodeCompile {
}
aspect MakeCodeWork {
}
aspect NameResolution {
// overriding customID guarantees to produce the same JSON representation for equal lists
// otherwise, the value for id is different each time
@Override
protected String A.customID() {
return getClass().getSimpleName() + getValue();
}
@Override
protected String B.customID() {
return getClass().getSimpleName() + getValue();
}
@Override
protected String Inner.customID() {
return getClass().getSimpleName() + getInnerValue();
}
// override resolving of SenderRoot for As in ReceiverRoot
refine RefResolverStubs eq ASTNode.globallyResolveSenderRootByToken(String id) = getParent() != null && !getParent().isListWithoutParent() ? resolveSenderRootInh(id) : null;
syn boolean ASTNode.isList() = false;
eq JastAddList.isList() = true;
syn boolean ASTNode.isListWithoutParent() = isList() && getParent() == null;
inh SenderRoot ASTNode.resolveSenderRootInh(String id);
eq SenderRoot.getChild().resolveSenderRootInh(String id) = globallyResolveSenderRootByToken(id);
eq ReceiverRoot.getChild().resolveSenderRootInh(String id) = getFakeSenderRoot(id);
syn nta SenderRoot ReceiverRoot.getFakeSenderRoot(String id) = new SenderRoot();
}
Root ::= SenderRoot* ReceiverRoot;
SenderRoot ::= A* B* ;
rel SenderRoot.MyA -> A;
rel SenderRoot.OptionalA? -> A;
rel SenderRoot.ManyA* -> A;
rel SenderRoot.BiMyA <-> A.ToMyA?;
rel SenderRoot.BiOptionalA? <-> A.ToOptionalA?;
rel SenderRoot.BiManyA* <-> A.ToManyA?;
rel SenderRoot.MyB -> B;
rel SenderRoot.OptionalB? -> B;
rel SenderRoot.ManyB* -> B;
rel SenderRoot.BiMyB <-> B.ToMyB?;
rel SenderRoot.BiOptionalB? <-> B.ToOptionalB?;
rel SenderRoot.BiManyB* <-> B.ToManyB?;
ReceiverRoot ::=
FromMyA:A FromOptionalA:A FromManyA:A*
FromBiMyA:A FromBiOptionalA:A FromBiManyA:A*
<FromMyB:String> <FromOptionalB:String> <FromManyB:String>
<FromBiMyB:String> <FromBiOptionalB:String> <FromBiManyB:String>
;
A ::= <Value> Inner ;
B ::= <Value> Inner ;
Inner ::= <InnerValue> ;
......@@ -3,6 +3,7 @@ package org.jastadd.ragconnect.tests;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import org.assertj.core.groups.Tuple;
import org.awaitility.Awaitility;
import org.awaitility.core.ConditionFactory;
import org.jastadd.ragconnect.compiler.Compiler;
......@@ -20,6 +21,8 @@ import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
......@@ -155,6 +158,134 @@ public class TestUtils {
event, node, attribute, params, value);
}
public static class TestChecker {
static class ActualAndExpected<T> {
Callable<T> actual;
T expected;
BiConsumer<String, T> customCheck;
ActualAndExpected(String key) {
expected = null;
}
void check(String name) {
if (customCheck != null) {
customCheck.accept(name, expected);
return;
}
if (actual == null) {
fail("No actual getter defined for " + name);
}
T actualValue = null;
try {
actualValue = this.actual.call();
} catch (Exception e) {
fail(e);
}
assertThat(actualValue).as(name).isEqualTo(expected);
}
void checkAwait(String name) {
if (customCheck != null) {
logger.warn("Custom check set for {}. Can't await for that.", name);
customCheck.accept(name, expected);
return;
}
if (actual == null) {
fail("No actual getter defined for " + name);
}
awaitMqtt().alias(name).until(actual, Predicate.isEqual(expected));
}
}
private final static String NUMBER_OF_VALUES = "numberOfValues";
private final Map<String, ActualAndExpected<String>> stringValues = new HashMap<>();
private final Map<String, ActualAndExpected<Tuple>> tupleValues = new HashMap<>();
private final Map<String, ActualAndExpected<Integer>> intValues = new HashMap<>();
private boolean needManualWait = true;
public TestChecker incNumberOfValues() {
return addToNumberOfValues(1);
}
public TestChecker addToNumberOfValues(int increment) {
// if there is at least one call to this, we do not need to manually wait in the next check()
needManualWait = false;
Integer currentExpected = intValues.computeIfAbsent(NUMBER_OF_VALUES, ActualAndExpected::new).expected;
return put(NUMBER_OF_VALUES, currentExpected + increment);
}
public TestChecker setActualNumberOfValues(Callable<Integer> actual) {
setActualInteger(NUMBER_OF_VALUES, actual);
intValues.get(NUMBER_OF_VALUES).expected = 0;
return this;
}
public TestChecker setActualString(String name, Callable<String> actual) {
stringValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
return this;
}
public TestChecker setCheckForString(String name, BiConsumer<String, String> check) {
stringValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
return this;
}
public TestChecker put(String name, String expected) {
stringValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
return this;
}
public TestChecker setActualTuple(String name, Callable<Tuple> actual) {
tupleValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
return this;
}
public TestChecker setCheckForTuple(String name, BiConsumer<String, Tuple> check) {
tupleValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
return this;
}
public TestChecker put(String name, Tuple expected) {
tupleValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
return this;
}
public TestChecker setActualInteger(String name, Callable<Integer> actual) {
intValues.computeIfAbsent(name, ActualAndExpected::new).actual = actual;
return this;
}
public TestChecker setCheckForInteger(String name, BiConsumer<String, Integer> check) {
intValues.computeIfAbsent(name, ActualAndExpected::new).customCheck = check;
return this;
}
public TestChecker put(String name, Integer expected) {
intValues.computeIfAbsent(name, ActualAndExpected::new).expected = expected;
return this;
}
public void check() {
if (needManualWait) {
try {
waitForMqtt();
} catch (InterruptedException e) {
fail(e);
}
}
intValues.get(NUMBER_OF_VALUES).checkAwait(NUMBER_OF_VALUES);
stringValues.forEach((name, aae) -> aae.check(name));
tupleValues.forEach((name, aae) -> aae.check(name));
intValues.forEach((name, aae) -> {
if (!name.equals(NUMBER_OF_VALUES)) {
aae.check(name);
}
});
needManualWait = true;
}
}
public static class IntList {
private final List<Integer> integers = newArrayList();
public IntList(Integer... values) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment