diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag index 66e6a5a81aff0823c07e4ec3079551c0740b4963..5659c9321a0016162ee5ca9715d6a38770c2a693 100644 --- a/ragconnect.base/src/main/jastadd/Analysis.jrag +++ b/ragconnect.base/src/main/jastadd/Analysis.jrag @@ -12,6 +12,11 @@ aspect Analysis { .filter(containingEndpointDefinition()::matchesType) .count() > 1; } + eq ContextFreeTypeEndpointTarget.isAlreadyDefined() { + return lookupContextFreeTypeEndpointDefinitions(getTypeDecl()).stream() + .filter(containingEndpointDefinition()::matchesType) + .count() > 1; + } syn boolean DependencyDefinition.isAlreadyDefined() = lookupDependencyDefinition(getSource().containingTypeDecl(), getID()) != this; // --- matchesType --- @@ -61,6 +66,7 @@ aspect Analysis { syn boolean EndpointTarget.entityIsNormalAttribute(); eq TokenEndpointTarget.entityIsNormalAttribute() = !getToken().getNTA(); eq TypeEndpointTarget.entityIsNormalAttribute() = !getType().getNTA(); + eq ContextFreeTypeEndpointTarget.entityIsNormalAttribute() = false; // --- needProxyToken --- syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() || getTokenEndpointTargetList().stream().map(EndpointTarget::containingEndpointDefinition).anyMatch(EndpointDefinition::shouldSendValue); diff --git a/ragconnect.base/src/main/jastadd/Errors.jrag b/ragconnect.base/src/main/jastadd/Errors.jrag index 61f966507a1d793ad711bb0e3dde085ee0b46ed6..2836b5f1ab6da646b18370e4d7f2ca794cfa1111 100644 --- a/ragconnect.base/src/main/jastadd/Errors.jrag +++ b/ragconnect.base/src/main/jastadd/Errors.jrag @@ -27,6 +27,10 @@ aspect Errors { token().effectiveJavaTypeUse()) to RagConnect.errors(); + ContextFreeTypeEndpointTarget contributes error("Context-Free endpoint not allowed for root nodes!") + when getTypeDecl().occurencesInProductionRules().isEmpty() + to RagConnect.errors(); + DependencyDefinition contributes error("Dependency definition already defined for " + getSource().containingTypeDecl().getName() + " with name " + getID()) when isAlreadyDefined() to RagConnect.errors(); diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd index 3729453a4123d34514ad3475b439139bd7ef7136..a391130b5dd5991f1ceba4c0d2688a8bb17015a3 100644 --- a/ragconnect.base/src/main/jastadd/Intermediate.jadd +++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd @@ -157,6 +157,14 @@ aspect MustacheMappingApplicationAndDefinition { eq MTypeSendDefinition.preemptiveExpectedValue() = lastValue(); eq MTypeSendDefinition.preemptiveReturn() = "return false;"; + eq MContextFreeTypeReceiveDefinition.firstInputVarName() = "message"; + eq MContextFreeTypeReceiveDefinition.preemptiveExpectedValue() = "this"; + eq MContextFreeTypeReceiveDefinition.preemptiveReturn() = "return;"; + + eq MContextFreeTypeSendDefinition.firstInputVarName() = null; + eq MContextFreeTypeSendDefinition.preemptiveExpectedValue() = null; + eq MContextFreeTypeSendDefinition.preemptiveReturn() = null; + syn String MEndpointDefinition.parentTypeName() = getEndpointDefinition().parentTypeName(); syn String MEndpointDefinition.getterMethodName() = getEndpointDefinition().getterMethodName(); @@ -174,9 +182,17 @@ aspect MustacheRagConnect { syn List<EndpointDefinition> RagConnect.allEndpointDefinitionList() { List<EndpointDefinition> result = new ArrayList<>(); - for (ConnectSpecification spec : getConnectSpecificationFileList()) { - spec.getEndpointDefinitionList().forEach(result::add); + // first gather all user-defined endpoint definitions, that are not context-free + for (EndpointDefinition def : givenEndpointDefinitionList()) { + if (!def.hasContextFreeTypeEndpointTarget()) { + result.add(def); + } + } + // then check for additional endpoints, and add if no conflict with existing definitions exists + for (EndpointDefinition def : givenEndpointDefinitionList()) { + def.getEndpointTarget().impliedEndpointDefinitions().iterator().forEachRemaining(result::add); } + return result; } @@ -192,8 +208,54 @@ aspect MustacheRagConnect { return result; } + syn List<TypeDecl> RagConnect.typeDeclsOfContextFreeEndpointTargets() { + List<TypeDecl> result = new ArrayList<>(); + for (EndpointTarget target : givenEndpointTargetList()) { + if (target.isContextFreeTypeEndpointTarget()) { + result.add(target.asContextFreeTypeEndpointTarget().getTypeDecl()); + } + } + return result; + } + // === MappingDefinition === syn boolean MappingDefinition.isUsed() = !effectiveUsedAt().isEmpty(); + + // === attributes needed for computing above ones === + syn List<EndpointDefinition> RagConnect.givenEndpointDefinitionList() { + List<EndpointDefinition> result = new ArrayList<>(); + for (ConnectSpecification spec : getConnectSpecificationFileList()) { + spec.getEndpointDefinitionList().forEach(result::add); + } + return result; + } + + syn nta JastAddList<EndpointDefinition> EndpointTarget.impliedEndpointDefinitions() = new JastAddList<>(); + eq ContextFreeTypeEndpointTarget.impliedEndpointDefinitions() { + JastAddList<EndpointDefinition> result = super.impliedEndpointDefinitions(); + EndpointDefinition containingDef = containingEndpointDefinition(); + for (TypeComponent typeComponent : getTypeDecl().occurencesInProductionRules()) { + List<EndpointDefinition> defsForTypeComponent = lookupTypeEndpointDefinitions(typeComponent); + if (!defsForTypeComponent.stream().anyMatch(containingDef::matchesType)) { + // there is no user-defined endpoint definition for this typeComponent yet + // -> create a new endpoint definition with the same options and mappings as the context-free def + // (except indexed-based for list-types) + EndpointDefinition newDef = new EndpointDefinition(); + newDef.setAlwaysApply(containingDef.getAlwaysApply()); + newDef.setIndexBasedListAccess(typeComponent.isListComponent()); + newDef.setSend(containingDef.getSend()); + containingDef.getMappings().forEach(newDef::addMapping); + + TypeEndpointTarget target = new TypeEndpointTarget(); + target.setType(typeComponent); + newDef.setEndpointTarget(target); + + result.add(newDef); + } + } + return result; + } + } aspect MustacheReceiveAndSendAndHandleUri { @@ -234,6 +296,10 @@ aspect MustacheReceiveAndSendAndHandleUri { eq TypeEndpointTarget.parentTypeName() = getType().containingTypeDecl().getName(); eq TypeEndpointTarget.entityName() = getType().getName() + (typeIsList() && !containingEndpointDefinition().getIndexBasedListAccess() ? "List" : ""); + eq ContextFreeTypeEndpointTarget.getterMethodName() = null; + eq ContextFreeTypeEndpointTarget.parentTypeName() = getTypeDecl().getName(); + eq ContextFreeTypeEndpointTarget.entityName() = ""; + } aspect MustacheReceiveDefinition { @@ -241,6 +307,8 @@ aspect MustacheReceiveDefinition { syn boolean RagConnect.configLoggingEnabledForReads() = getConfiguration().getLoggingEnabledForReads(); // === EndpointDefinition === + syn boolean EndpointDefinition.hasContextFreeTypeEndpointTarget() = getEndpointTarget().isContextFreeTypeEndpointTarget(); + syn boolean EndpointDefinition.hasTypeEndpointTarget() = getEndpointTarget().isTypeEndpointTarget(); syn String EndpointDefinition.idTokenName() = "InternalRagconnectTopicInList"; @@ -281,26 +349,29 @@ aspect MustacheSendDefinition { syn String EndpointTarget.senderName(); eq TokenEndpointTarget.senderName() = "_sender_" + getToken().getName(); eq TypeEndpointTarget.senderName() = "_sender_" + getType().getName(); + eq ContextFreeTypeEndpointTarget.senderName() = null; syn String MEndpointDefinition.updateMethodName(); syn String MEndpointDefinition.writeMethodName(); - // MTokenReceiveDefinition eq MTokenReceiveDefinition.updateMethodName() = null; eq MTokenReceiveDefinition.writeMethodName() = null; - // MTokenSendDefinition eq MTokenSendDefinition.updateMethodName() = "_update_" + tokenName(); eq MTokenSendDefinition.writeMethodName() = "_writeLastValue_" + tokenName(); - // MTypeReceiveDefinition eq MTypeReceiveDefinition.updateMethodName() = null; eq MTypeReceiveDefinition.writeMethodName() = null; - // MTypeSendDefinition eq MTypeSendDefinition.updateMethodName() = "_update_" + typeName(); eq MTypeSendDefinition.writeMethodName() = "_writeLastValue_" + typeName(); + eq MContextFreeTypeReceiveDefinition.updateMethodName() = null; + eq MContextFreeTypeReceiveDefinition.writeMethodName() = null; + + eq MContextFreeTypeSendDefinition.updateMethodName() = null; + eq MContextFreeTypeSendDefinition.writeMethodName() = null; + syn String EndpointDefinition.tokenName() = token().getName(); syn String MEndpointDefinition.tokenName() = getEndpointDefinition().tokenName(); @@ -350,6 +421,25 @@ aspect MustacheTokenComponent { // > see MustacheSend for updateMethodName, writeMethodName } +aspect MustacheTypeDecl { + // === TypeComponent === + syn String TypeComponent.parentTypeName() = containingTypeDecl().getName(); + + // === TypeDecl === + syn List<TypeComponent> TypeDecl.occurencesInProductionRules() { + List<TypeComponent> result = new ArrayList<>(); + for (TypeDecl typeDecl : program().typeDecls()) { + for (Component comp : typeDecl.getComponentList()) { + if (comp.isTypeComponent() && comp.asTypeComponent().getTypeDecl().equals(this)) { + result.add(comp.asTypeComponent()); + } + } + } + return result; + } + +} + aspect AttributesForMustache { syn String MEndpointDefinition.lastValue() = getEndpointDefinition().lastValue(); @@ -361,22 +451,7 @@ aspect AttributesForMustache { syn MInnerMappingDefinition MEndpointDefinition.lastDefinition() = getInnerMappingDefinition(getNumInnerMappingDefinition() - 1); syn nta MEndpointDefinition EndpointDefinition.toMustache() { - final MEndpointDefinition result; - if (getEndpointTarget().isTokenEndpointTarget()) { - if (getSend()) { - result = new MTokenSendDefinition(); - } else { - result = new MTokenReceiveDefinition(); - } - } else if (getEndpointTarget().isTypeEndpointTarget()) { - if (getSend()) { - result = new MTypeSendDefinition(); - } else { - result = new MTypeReceiveDefinition(); - } - } else { - throw new RuntimeException("Unknown endpoint target type: " + getEndpointTarget()); - } + final MEndpointDefinition result = getEndpointTarget().createMEndpointDefinition(getSend()); result.setEndpointDefinition(this); for (MappingDefinition def : effectiveMappings()) { MInnerMappingDefinition inner = new MInnerMappingDefinition(); @@ -385,6 +460,20 @@ aspect AttributesForMustache { } return result; } + abstract MEndpointDefinition EndpointTarget.createMEndpointDefinition(boolean isSend); + MEndpointDefinition TokenEndpointTarget.createMEndpointDefinition(boolean isSend) { + return isSend ? new MTokenSendDefinition() : new MTokenReceiveDefinition(); + } + MEndpointDefinition TypeEndpointTarget.createMEndpointDefinition(boolean isSend) { + return isSend ? new MTypeSendDefinition() : new MTypeReceiveDefinition(); + } + MEndpointDefinition ContextFreeTypeEndpointTarget.createMEndpointDefinition(boolean isSend) { + return isSend ? new MContextFreeTypeSendDefinition() : new MContextFreeTypeReceiveDefinition(); + } + MEndpointDefinition UntypedEndpointTarget.createMEndpointDefinition(boolean isSend) { + throw new RuntimeException("Untyped endpoint target type, typeName= " + + getTypeName() + ", childName=" + getChildName()); + } } aspect GrammarGeneration { diff --git a/ragconnect.base/src/main/jastadd/Intermediate.relast b/ragconnect.base/src/main/jastadd/Intermediate.relast index d7afd2ea942558f6999c8fa64c91bfdc480d659c..79097503ef32b417be179ccb5f679886a6297582 100644 --- a/ragconnect.base/src/main/jastadd/Intermediate.relast +++ b/ragconnect.base/src/main/jastadd/Intermediate.relast @@ -7,6 +7,9 @@ MTokenSendDefinition : MTokenEndpointDefinition; abstract MTypeEndpointDefinition : MEndpointDefinition; MTypeReceiveDefinition : MTypeEndpointDefinition; MTypeSendDefinition : MTypeEndpointDefinition; +abstract MContextFreeTypeEndpointDefinition : MEndpointDefinition; +MContextFreeTypeReceiveDefinition : MContextFreeTypeEndpointDefinition; +MContextFreeTypeSendDefinition : MContextFreeTypeEndpointDefinition; MInnerMappingDefinition; rel MInnerMappingDefinition.MappingDefinition -> MappingDefinition; diff --git a/ragconnect.base/src/main/jastadd/Mappings.jrag b/ragconnect.base/src/main/jastadd/Mappings.jrag index 093f4cc1d4f1cb0eecf1ffe7914c20e6fa715b27..45d28e8d99c02292780fed005642e72489a1c58e 100644 --- a/ragconnect.base/src/main/jastadd/Mappings.jrag +++ b/ragconnect.base/src/main/jastadd/Mappings.jrag @@ -294,6 +294,7 @@ aspect Mappings { syn String EndpointTarget.targetTypeName(); eq TokenEndpointTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName(); eq TypeEndpointTarget.targetTypeName() = getType().getTypeDecl().getName(); + eq ContextFreeTypeEndpointTarget.targetTypeName() = getTypeDecl().getName(); // eq ReceiveFromRestDefinition.suitableDefaultMapping() { // String typeName = getMappingList().isEmpty() ? diff --git a/ragconnect.base/src/main/jastadd/NameResolution.jrag b/ragconnect.base/src/main/jastadd/NameResolution.jrag index 9b37ee7f93ea9f31414f61a3a70b01a1ba5ca489..cf29d470e5734409b9cc8debd48ef70c3e281638 100644 --- a/ragconnect.base/src/main/jastadd/NameResolution.jrag +++ b/ragconnect.base/src/main/jastadd/NameResolution.jrag @@ -5,7 +5,7 @@ aspect RagConnectNameResolution { eq RagConnect.getConnectSpecificationFile().lookupTokenEndpointDefinitions(TokenComponent token) = lookupTokenEndpointDefinitions(token); syn java.util.List<EndpointDefinition> RagConnect.lookupTokenEndpointDefinitions(TokenComponent token) { java.util.List<EndpointDefinition> result = new java.util.ArrayList<>(); - for (EndpointTarget target : allEndpointTargetList()) { + for (EndpointTarget target : givenEndpointTargetList()) { if (target.isTokenEndpointTarget() && target.asTokenEndpointTarget().getToken().equals(token)) { result.add(target.containingEndpointDefinition()); } @@ -19,7 +19,7 @@ aspect RagConnectNameResolution { eq RagConnect.getConnectSpecificationFile().lookupTypeEndpointDefinitions(TypeComponent type) = lookupTypeEndpointDefinitions(type); syn java.util.List<EndpointDefinition> RagConnect.lookupTypeEndpointDefinitions(TypeComponent type) { java.util.List<EndpointDefinition> result = new java.util.ArrayList<>(); - for (EndpointTarget target : allEndpointTargetList()) { + for (EndpointTarget target : givenEndpointTargetList()) { if (target.isTypeEndpointTarget() && target.asTypeEndpointTarget().getType().equals(type)) { result.add(target.containingEndpointDefinition()); } @@ -27,6 +27,20 @@ aspect RagConnectNameResolution { return result; } + // --- lookupContextFreeTypeEndpointDefinition --- + inh java.util.List<EndpointDefinition> EndpointDefinition.lookupContextFreeTypeEndpointDefinitions(TypeDecl typeDecl); + inh java.util.List<EndpointDefinition> EndpointTarget.lookupContextFreeTypeEndpointDefinitions(TypeDecl typeDecl); + eq RagConnect.getConnectSpecificationFile().lookupContextFreeTypeEndpointDefinitions(TypeDecl typeDecl) = lookupContextFreeTypeEndpointDefinitions(typeDecl); + syn java.util.List<EndpointDefinition> RagConnect.lookupContextFreeTypeEndpointDefinitions(TypeDecl typeDecl) { + java.util.List<EndpointDefinition> result = new java.util.ArrayList<>(); + for (EndpointTarget target : givenEndpointTargetList()) { + if (target.isContextFreeTypeEndpointTarget() && target.asContextFreeTypeEndpointTarget().getTypeDecl().equals(typeDecl)) { + result.add(target.containingEndpointDefinition()); + } + } + return result; + } + // --- lookupDependencyDefinition --- inh DependencyDefinition DependencyDefinition.lookupDependencyDefinition(TypeDecl source, String id); eq RagConnect.getConnectSpecificationFile().lookupDependencyDefinition(TypeDecl source, String id) { diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag index 7419f3d880b592e28ae4a45b7a9451849b855368..a65ab72fa946bcec3351bb496f6632c012bac678 100644 --- a/ragconnect.base/src/main/jastadd/Navigation.jrag +++ b/ragconnect.base/src/main/jastadd/Navigation.jrag @@ -12,6 +12,12 @@ aspect NewStuff { syn boolean EndpointTarget.isTypeEndpointTarget() = false; eq TypeEndpointTarget.isTypeEndpointTarget() = true; + /** Tests if EndpointTarget is a ContextFreeTypeEndpointTarget. + * @return 'true' if this is a ContextFreeTypeEndpointTarget, otherwise 'false' + */ + syn boolean EndpointTarget.isContextFreeTypeEndpointTarget() = false; + eq ContextFreeTypeEndpointTarget.isContextFreeTypeEndpointTarget() = true; + /** Tests if EndpointTarget is a UntypedEndpointTarget. * @return 'true' if this is a UntypedEndpointTarget, otherwise 'false' */ @@ -32,6 +38,13 @@ aspect NewStuff { eq EndpointTarget.asTypeEndpointTarget() = null; eq TypeEndpointTarget.asTypeEndpointTarget() = this; + /** casts a EndpointTarget into a ContextFreeTypeEndpointTarget if possible. + * @return 'this' cast to a ContextFreeTypeEndpointTarget or 'null' + */ + syn ContextFreeTypeEndpointTarget EndpointTarget.asContextFreeTypeEndpointTarget(); + eq EndpointTarget.asContextFreeTypeEndpointTarget() = null; + eq ContextFreeTypeEndpointTarget.asContextFreeTypeEndpointTarget() = this; + /** casts a EndpointTarget into a UntypedEndpointTarget if possible. * @return 'this' cast to a UntypedEndpointTarget or 'null' */ @@ -73,8 +86,8 @@ aspect RagConnectNavigation { } - //--- allEndpointTargetList --- - syn List<EndpointTarget> RagConnect.allEndpointTargetList() { + //--- givenEndpointTargetList --- + syn List<EndpointTarget> RagConnect.givenEndpointTargetList() { List<EndpointTarget> result = new ArrayList<>(); for (ConnectSpecification spec : getConnectSpecificationFileList()) { spec.getEndpointDefinitionList().forEach(endpointDef -> result.add(endpointDef.getEndpointTarget())); diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast index 30e6d5ad6a034f9b35de570414e8d4e904751ca2..37ec5feaecbff82163d7ca1360c9916abfaed36e 100644 --- a/ragconnect.base/src/main/jastadd/RagConnect.relast +++ b/ragconnect.base/src/main/jastadd/RagConnect.relast @@ -11,7 +11,9 @@ TokenEndpointTarget : EndpointTarget; rel TokenEndpointTarget.Token <-> TokenComponent.TokenEndpointTarget*; TypeEndpointTarget : EndpointTarget; rel TypeEndpointTarget.Type <-> TypeComponent.TypeEndpointTarget*; -UntypedEndpointTarget : EndpointTarget ::= <TokenOrType>; // only used by parser +ContextFreeTypeEndpointTarget : EndpointTarget; +rel ContextFreeTypeEndpointTarget.TypeDecl <-> TypeDecl.ContextFreeTypeEndpointTarget?; +UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName>; // only used by parser // to be integrated: //AttributeEndpointTarget : EndpointTarget ::= <Name> ; //RelationEndpointTarget : EndpointTarget ; diff --git a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag index bbf0c11b7ba08c1f7c172cf8fd31420e7fd74561..f888758f2ca515309e639722d8ef15bd5fe7e1fc 100644 --- a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag +++ b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag @@ -1,19 +1,29 @@ aspect ParserRewrites { rewrite UntypedEndpointTarget { - when (tryGloballyResolveTypeComponentByToken(getTokenOrType()) != null) + when (getChildName() != null && tryGloballyResolveTypeComponentByToken(combinedName()) != null) to TypeEndpointTarget { TypeEndpointTarget result = new TypeEndpointTarget(); - result.setType(TypeComponent.createRef(this.getTokenOrType())); + result.setType(TypeComponent.createRef(this.combinedName())); return result; } - when (tryGloballyResolveTokenComponentByToken(getTokenOrType()) != null) + + when (getChildName() != null && tryGloballyResolveTokenComponentByToken(combinedName()) != null) to TokenEndpointTarget { TokenEndpointTarget result = new TokenEndpointTarget(); - result.setToken(TokenComponent.createRef(this.getTokenOrType())); + result.setToken(TokenComponent.createRef(this.combinedName())); + return result; + } + + when (getChildName() == "") + to ContextFreeTypeEndpointTarget { + ContextFreeTypeEndpointTarget result = new ContextFreeTypeEndpointTarget(); + result.setTypeDecl(TypeDecl.createRef(getTypeName())); return result; } } + syn String UntypedEndpointTarget.combinedName() = getTypeName() + "." + getChildName(); + eq UntypedEndpointTarget.senderName() = "<untyped.senderName>"; eq UntypedEndpointTarget.getterMethodName() = "<untyped.getterMethodName>"; eq UntypedEndpointTarget.parentTypeName() = "<untyped.parentTypeName>"; diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser index 5ddef630253006a13e64ea84cd4df30dc4b6aa2b..2c326763856f437e770e9bf5ba1566c86f2a2fdb 100644 --- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser +++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser @@ -27,13 +27,13 @@ ConnectSpecificationFile connect_specification_file // return def; // } private EndpointDefinition createEndpointDefinition( - String type_name, String child_name, boolean send, + EndpointTarget endpointTarget, boolean send, boolean indexBasedListAccess, boolean withAdd) { EndpointDefinition result = new EndpointDefinition(); result.setSend(send); result.setIndexBasedListAccess(indexBasedListAccess); result.setWithAdd(withAdd); - result.setEndpointTarget(new UntypedEndpointTarget(type_name + "." + child_name)); + result.setEndpointTarget(endpointTarget); return result; } :} ; @@ -53,30 +53,16 @@ EndpointDefinition endpoint_definition ; EndpointDefinition endpoint_definition_type - = SEND ID.type_name DOT ID.child_name - {: - return createEndpointDefinition(type_name, child_name, true, false, false); - :} -// | SEND INDEXED ID.type_name DOT ID.child_name -// {: -// return createEndpointDefinition(type_name, child_name, true, true, false); -// :} - | RECEIVE ID.type_name DOT ID.child_name - {: - return createEndpointDefinition(type_name, child_name, false, false, false); - :} - | RECEIVE INDEXED ID.type_name DOT ID.child_name - {: - return createEndpointDefinition(type_name, child_name, false, true, false); - :} - | RECEIVE WITH ADD ID.type_name DOT ID.child_name - {: - return createEndpointDefinition(type_name, child_name, false, false, true); - :} - | RECEIVE INDEXED WITH ADD ID.type_name DOT ID.child_name - {: - return createEndpointDefinition(type_name, child_name, false, true, true); - :} + = SEND endpoint_target.t {: return createEndpointDefinition(t, true, false, false); :} + | RECEIVE endpoint_target.t {: return createEndpointDefinition(t, false, false, false); :} + | RECEIVE INDEXED endpoint_target.t {: return createEndpointDefinition(t, false, true, false); :} + | RECEIVE WITH ADD endpoint_target.t {: return createEndpointDefinition(t, false, false, true ); :} + | RECEIVE INDEXED WITH ADD endpoint_target.t {: return createEndpointDefinition(t, false, true, true ); :} +; + +EndpointTarget endpoint_target + = ID.type_name DOT ID.child_name {: return new UntypedEndpointTarget(type_name, child_name); :} + | ID.type_name {: return new UntypedEndpointTarget(type_name, ""); :} ; ArrayList string_list diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache index c3b50710f38fd247aca8ca11456c23279a0bee8c..d39fe4a8686b7a6950c1bfb7128d0960f8fa8349 100644 --- a/ragconnect.base/src/main/resources/ragconnect.mustache +++ b/ragconnect.base/src/main/resources/ragconnect.mustache @@ -28,6 +28,10 @@ aspect RagConnect { {{> tokenComponent}} {{/tokenComponentsThatNeedProxy}} + {{#typeDeclsOfContextFreeEndpointTargets}} + {{> typeDecl}} + {{/typeDeclsOfContextFreeEndpointTargets}} + {{> ListAspect}} public void {{rootNodeName}}.ragconnectCheckIncremental() { diff --git a/ragconnect.base/src/main/resources/typeDecl.mustache b/ragconnect.base/src/main/resources/typeDecl.mustache new file mode 100644 index 0000000000000000000000000000000000000000..643fbc80a4eccfa50da5d48650e03d4380badb62 --- /dev/null +++ b/ragconnect.base/src/main/resources/typeDecl.mustache @@ -0,0 +1,45 @@ +uncache {{Name}}._ragconnect_myContext(); +inh String {{Name}}._ragconnect_myContext(); +inh ASTNode {{Name}}._ragconnect_myParent(); +uncache {{Name}}._ragconnect_myIndexInList(); +inh int {{Name}}._ragconnect_myIndexInList(); +{{#occurencesInProductionRules}} + eq {{parentTypeName}}.get{{Name}}()._ragconnect_myContext() = "{{parentTypeName}}.{{Name}}"; + eq {{parentTypeName}}.get{{Name}}()._ragconnect_myParent() = this; + {{^isListComponent}} + eq {{parentTypeName}}.get{{Name}}()._ragconnect_myIndexInList() = -1; + {{/isListComponent}} + {{#isListComponent}} + eq {{parentTypeName}}.get{{Name}}(int i)._ragconnect_myIndexInList() = i; + {{/isListComponent}} +{{/occurencesInProductionRules}} + +{{#ContextFreeTypeEndpointTarget}}{{#containingEndpointDefinition}} +public boolean {{Name}}.{{connectMethodName}}(String {{connectParameterName}}) throws java.io.IOException { + switch (_ragconnect_myContext()) { + {{#occurencesInProductionRules}} + {{!only using "connectMethodName" is not correct, since the actual name might be different, e.g., if both send and receive are defined. need a reference to the actual endpoint-definition here}} + {{^isListComponent}} + case "{{parentTypeName}}.{{Name}}": return (({{parentTypeName}}) _ragconnect_myParent()).{{connectMethodName}}{{Name}}({{connectParameterName}}); + {{/isListComponent}} + {{#isListComponent}} + case "{{parentTypeName}}.{{Name}}": return (({{parentTypeName}}) _ragconnect_myParent()).{{connectMethodName}}{{Name}}({{connectParameterName}}, _ragconnect_myIndexInList()); + {{/isListComponent}} + {{/occurencesInProductionRules}} + default: + System.err.println("No matching context while connecting " + this + " to " + {{connectParameterName}}); + return false; + } +} + +public boolean {{Name}}.{{disconnectMethodName}}(String {{connectParameterName}}) throws java.io.IOException { + switch (_ragconnect_myContext()) { +{{#occurencesInProductionRules}} + case "{{parentTypeName}}.{{Name}}": return (({{parentTypeName}}) _ragconnect_myParent()).{{disconnectMethodName}}{{Name}}({{connectParameterName}}); +{{/occurencesInProductionRules}} + default: + System.err.println("No matching context while disconnecting for " + this + " from " + {{connectParameterName}}); + return false; + } +} +{{/containingEndpointDefinition}}{{/ContextFreeTypeEndpointTarget}} diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle index cfeb6ae4680eca67212c256e3e39debc514609c7..7c10c5f31282114d822783ce1a04d3c543559df2 100644 --- a/ragconnect.tests/build.gradle +++ b/ragconnect.tests/build.gradle @@ -105,13 +105,22 @@ task specificTest(type: Test, dependsOn: testClasses) { description = 'Run test tagged with tag given by "-PincludeTags="' group = 'verification' String tags = project.hasProperty("includeTags") ? - project.property("includeTags") : '' + project.property("includeTags") : '' useJUnitPlatform { includeTags tags } } +task newTests(type: Test, dependsOn: testClasses) { + description = 'Run test tagged with tag "New"' + group = 'verification' + + useJUnitPlatform { + includeTags 'New' + } +} + preprocessorTesting { //noinspection GroovyAssignabilityCheck relastCompilerLocation = '../libs/relast.jar' @@ -556,21 +565,29 @@ task compileSingleListVariantIncremental(type: RagConnectTest, dependsOn: ':ragc } } -task cleanCurrentManualTest(type: Delete) { -// delete "src/test/02-after-ragconnect/singleListVariant" -// delete "src/test/03-after-relast/singleListVariant" -// delete "src/test/java-gen/singleListVariant/ast" - delete "src/test/02-after-ragconnect/singleList" - delete "src/test/03-after-relast/singleList" - delete "src/test/java-gen/singleList/ast" -} -task cleanCurrentIncrementalTest(type: Delete) { -// delete "src/test/02-after-ragconnect/singleListVariantInc" -// delete "src/test/03-after-relast/singleListVariantInc" -// delete "src/test/java-gen/singleListVariantInc/ast" - delete "src/test/02-after-ragconnect/singleListInc" - delete "src/test/03-after-relast/singleListInc" - delete "src/test/java-gen/singleListInc/ast" +// --- Test: contextFreeSimple-incremental --- +task compileContextFreeSimpleIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') { + ragconnect { + outputDir = file('src/test/02-after-ragconnect/contextFreeSimpleInc') + inputFiles = [file('src/test/01-input/contextFreeSimple/Test.relast'), + file('src/test/01-input/contextFreeSimple/Test.connect')] + rootNode = 'Root' + extraOptions = ['--experimental-jastadd-329'] + } + relast { + useJastAddNames = true + grammarName = 'src/test/03-after-relast/contextFreeSimpleInc/contextFreeSimpleInc' + serializer = 'jackson' + } + jastadd { + jastAddList = 'JastAddList' + packageName = 'contextFreeSimpleInc.ast' +// inputFiles = [file('src/test/01-input/contextFreeSimple/Test.jadd')] + extraOptions = ['--tracing=cache,flush', + '--incremental=param', + '--cache=all', + '--rewrite=cnta', + '--flush=full'] + } } -compileSingleListManual.dependsOn cleanCurrentManualTest -compileSingleListIncremental.dependsOn cleanCurrentIncrementalTest +compileContextFreeSimpleIncremental.outputs.upToDateWhen { false } diff --git a/ragconnect.tests/src/test/01-input/contextFreeSimple/README.md b/ragconnect.tests/src/test/01-input/contextFreeSimple/README.md new file mode 100644 index 0000000000000000000000000000000000000000..737ca9af132d047a1cceafee2e6b1d2a0552a6b7 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/contextFreeSimple/README.md @@ -0,0 +1,3 @@ +# ContextFree-Simple + +Idea: Use only context-free context, in simple situation (non-recursive) diff --git a/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.connect b/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.connect new file mode 100644 index 0000000000000000000000000000000000000000..95223256630de1c64de41d2c57467012e0f7d56d --- /dev/null +++ b/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.connect @@ -0,0 +1,19 @@ +receive A; + +//// implied +//receive Root.A; +receive Root.SingleA using PrependPrefix; +receive Root.OptA using AddSuffix; +//"receive Root.ListA;" would clash + +PrependPrefix maps A a to A {: + A result = new A(); + result.setValue("pre" + a.getValue()); + return result; +:} + +AddSuffix maps A a to A {: + A result = new A(); + result.setValue(a.getValue() + "post"); + return result; +:} diff --git a/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.relast b/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.relast new file mode 100644 index 0000000000000000000000000000000000000000..f5dff3f8b742bc18cfbad715b1326ce483ce8a25 --- /dev/null +++ b/ragconnect.tests/src/test/01-input/contextFreeSimple/Test.relast @@ -0,0 +1,2 @@ +Root ::= A SingleA:A [OptA:A] ListA:A* ; +A ::= <Value> ; diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ContextFreeSimpleTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ContextFreeSimpleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5e5510b63d9bb1294fcd0ade4aa4401ab18b536e --- /dev/null +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ContextFreeSimpleTest.java @@ -0,0 +1,168 @@ +package org.jastadd.ragconnect.tests; + +import contextFreeSimpleInc.ast.A; +import contextFreeSimpleInc.ast.Root; +import contextFreeSimpleInc.ast.SerializationException; +import org.junit.jupiter.api.Tag; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static org.jastadd.ragconnect.tests.TestUtils.mqttUri; +import static org.jastadd.ragconnect.tests.TestUtils.waitForMqtt; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test case "context free simple". + * + * @author rschoene - Initial contribution + */ +@Tag("New") +public class ContextFreeSimpleTest extends AbstractMqttTest { + + private static final String TOPIC_UNNAMED = "unnamed"; + private static final String TOPIC_SINGLE = "single"; + private static final String TOPIC_SINGLE_ALTERNATIVE = "double"; + private static final String TOPIC_OPT = "opt"; + private static final String TOPIC_LIST_1 = "list1"; + private static final String TOPIC_LIST_2 = "list2"; + + /** Use initially created members as values in {@link #check(String, String, String, String, String)} */ + private static final String INITIAL_VALUE = null; + + private Root model; + private A unnamedA; + private A singleA; + private A optA; + private A listA1; + private A listA2; + + @Override + protected void createModel() { + model = new Root(); + unnamedA = new A().setValue("unnamed"); + singleA = new A().setValue("single"); + optA = new A().setValue("opt"); + listA1 = new A().setValue("a1"); + listA2 = new A().setValue("a2"); + + model.setA(unnamedA); + model.setSingleA(singleA); + model.setOptA(optA); + model.addListA(listA1); + model.addListA(listA2); + } + + @Override + protected void setupReceiverAndConnect(boolean writeCurrentValue) throws IOException, InterruptedException { + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + assertTrue(unnamedA.connect(mqttUri(TOPIC_UNNAMED))); + assertTrue(singleA.connect(mqttUri(TOPIC_SINGLE))); + assertTrue(singleA.connect(mqttUri(TOPIC_SINGLE_ALTERNATIVE))); + assertTrue(optA.connect(mqttUri(TOPIC_OPT))); + assertTrue(listA1.connect(mqttUri(TOPIC_LIST_1))); + assertTrue(listA2.connect(mqttUri(TOPIC_LIST_2))); + } + + @Override + protected void communicateSendInitialValue() throws IOException, InterruptedException { + // empty + } + + @Override + protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException { + check(INITIAL_VALUE, INITIAL_VALUE, INITIAL_VALUE, INITIAL_VALUE, INITIAL_VALUE); + + send(TOPIC_UNNAMED, "1"); + check("1", INITIAL_VALUE, INITIAL_VALUE, INITIAL_VALUE, INITIAL_VALUE); + + send(TOPIC_SINGLE, "2"); + check("1", "pre2", INITIAL_VALUE, INITIAL_VALUE, INITIAL_VALUE); + + send(TOPIC_SINGLE, "2.1"); + check("1", "pre2.1", INITIAL_VALUE, INITIAL_VALUE, INITIAL_VALUE); + + send(TOPIC_SINGLE, "2.2"); + check("1", "pre2.2", INITIAL_VALUE, INITIAL_VALUE, INITIAL_VALUE); + + send(TOPIC_OPT, "3"); + check("1", "pre2.2", "3post", INITIAL_VALUE, INITIAL_VALUE); + + send(TOPIC_LIST_1, "4"); + check("1", "pre2.2", "3post", "4", INITIAL_VALUE); + + send(TOPIC_LIST_2, "5"); + check("1", "pre2.2", "3post", "4", "5"); + + send(TOPIC_SINGLE_ALTERNATIVE, "fix"); + check("1", "prefix", "3post", "4", "5"); + + assertTrue(model.getSingleA().disconnect(mqttUri(TOPIC_SINGLE))); + send(TOPIC_SINGLE, "6"); + // no change to previous check since disconnected + check("1", "prefix", "3post", "4", "5"); + + send(TOPIC_SINGLE_ALTERNATIVE, "7"); + // alternative topic is still active + check("1", "pre7", "3post", "4", "5"); + + assertTrue(model.getSingleA().disconnect(mqttUri(TOPIC_SINGLE_ALTERNATIVE))); + send(TOPIC_SINGLE_ALTERNATIVE, "8"); + // no change to previous check since alternative topic is also disconnected now + check("1", "pre7", "3post", "4", "5"); + } + + private void send(String topic, String value) throws IOException, InterruptedException { + A a = new A().setValue(value); + try { + publisher.publish(topic, TestUtils.DefaultMappings.TreeToBytes(a::serialize)); + } catch (SerializationException e) { + throw new IOException(e); + } + waitForMqtt(); + } + + @SuppressWarnings("StringEquality") + private void check(String unnamedValue, String singleValue, String optValue, String list1Value, String list2Value) { + if (INITIAL_VALUE == unnamedValue) { + assertEquals(unnamedA, model.getA()); + } else { + assertNotEquals(unnamedA, model.getA()); + assertEquals(unnamedValue, model.getA().getValue()); + } + + if (INITIAL_VALUE == singleValue) { + assertEquals(singleA, model.getSingleA()); + } else { + assertNotEquals(singleA, model.getSingleA()); + assertEquals(singleValue, model.getSingleA().getValue()); + } + + if (INITIAL_VALUE == optValue) { + assertEquals(optA, model.getOptA()); + } else { + assertNotEquals(optA, model.getOptA()); + assertEquals(optValue, model.getOptA().getValue()); + } + + if (INITIAL_VALUE == list1Value) { + assertEquals(listA1, model.getListA(0)); + } else { + assertNotEquals(listA1, model.getListA(0)); + assertEquals(list1Value, model.getListA(0).getValue()); + } + + if (INITIAL_VALUE == list2Value) { + assertEquals(listA2, model.getListA(1)); + } else { + assertNotEquals(listA2, model.getListA(1)); + assertEquals(list2Value, model.getListA(1).getValue()); + } + } + + @Override + protected void closeConnections() { + + } +} diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java index bb557f6f88783c00f3bb2115758223bae0dc6f9c..984a5b06e2bb177ae13605c58fa9a95462dd74a8 100644 --- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java +++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/TestUtils.java @@ -1,10 +1,14 @@ 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.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jastadd.ragconnect.compiler.Compiler; import org.junit.jupiter.api.Assertions; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; @@ -14,6 +18,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import static java.lang.Math.abs; @@ -214,6 +219,10 @@ public class TestUtils { return defaultOnlyWrite.ast.ASTNode._apply__DefaultStringToBytesMapping(input); } } + @FunctionalInterface + interface SerializeFunction<E extends Throwable> { + void accept(JsonGenerator g, String fieldName) throws E; + } public static boolean BytesToBool(byte[] input) { try { @@ -343,5 +352,13 @@ public class TestUtils { return null; } } + public static <E extends Throwable> byte[] TreeToBytes(SerializeFunction<E> serializeFunction) throws E, IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + JsonFactory factory = new JsonFactory(); + JsonGenerator generator = factory.createGenerator(outputStream, JsonEncoding.UTF8); + serializeFunction.accept(generator, null); + generator.flush(); + return outputStream.toString().getBytes(); + } } }