0
votes

In an Xtext validator, I would like to assert that an Aggregate does not references another Aggregate

With this simplified grammar

grammar com.mimacom.mdd.ddd.MdDsl with org.eclipse.xtext.xbase.Xbase

generate mdDsl "http://www.mimacom.com/mdd/ddd/MdDsl"  

Domainmodel:
    elements+=Element*;

Element:
    Aggregate | ValueObject;

Aggregate:
    'aggregate' name=ValidID ('extends' superType=JvmTypeReference)? '{'
        properties+=Property*
    '}';

ValueObject:
    'valueObject' name=ValidID ('extends' superType=JvmTypeReference)? '{'
        properties+=Property*
    '}';

Property:
    name=ValidID ':' type=JvmTypeReference;

When I parse the following, I expect to be able to find out if a property is a valueObject or an aggregate

aggregate Address{
}
aggregate Person{
    p : Address
}

The validation looks like the following I was not able to extract the information from the property to find out if the type of the property is an aggregate

@Inject
IJvmModelAssociations assoc;

@Check
def aggregateDoesNotReferenceOtherAggregates(Aggregate aggregate) {
    var features = aggregate.features

    for(Feature f : features){
        println(f)
        var s = assoc.getSourceElements(f.type.type)
        var first = s.get(0)
        if(first instanceof Aggregate   ){
            warning('An aggregate is not allowed to reference another aggregate'
                , null
                , aggregate.eContainingFeature)
        }
    }
}

I add the Inferrer here:

class MdDslJvmModelInferrer extends AbstractModelInferrer {

@Inject extension JvmTypesBuilder
@Inject extension IQualifiedNameProvider

def dispatch void infer(Aggregate aggregate, IJvmDeclaredTypeAcceptor acceptor, boolean isPrelinkingPhase) {
    acceptor.accept(aggregate.toClass(aggregate.fullyQualifiedName)) [
        processAggregate(aggregate, it)
    ]
}

def dispatch void infer(ValueObject element, IJvmDeclaredTypeAcceptor acceptor, boolean isPrelinkingPhase) {
    acceptor.accept(element.toClass(element.fullyQualifiedName)) [
        processValueObject(element, it)
    ]
}

protected def void processAggregate(Aggregate aggregate, JvmGenericType it) {
    documentation = aggregate.documentation
    if (aggregate.superType !== null)
            superTypes += aggregate.superType.cloneWithProxies

    for (feature : aggregate.features) {
        switch feature {
            Property: {
                members += feature.toField(feature.name, feature.type)
                members += feature.toGetter(feature.name, feature.type)
                members += feature.toSetter(feature.name, feature.type)
            }
            Operation: {
                processOperation(it, feature)
            }
        }
    }
}

protected def void processValueObject(ValueObject element, JvmGenericType it) {
    documentation = element.documentation
    if (element.superType !== null)
            superTypes += element.superType.cloneWithProxies
    for (feature : element.features) {
        switch feature {
            Property: {
                members += feature.toField(feature.name, feature.type)
                members += feature.toGetter(feature.name, feature.type)
            }
            Operation: {
                processOperation(it, feature)
            }
        }
    }
}

protected def boolean processOperation(JvmGenericType it, Operation feature) {
    members += feature.toMethod(feature.name, feature.type) [
        documentation = feature.documentation
        for (p : feature.params) {
            parameters += p.toParameter(p.name, p.parameterType)
        }
        body = feature.body
    ]
}
}

the test I execute which gives me an empty source looks like that

@ExtendWith(InjectionExtension)
@InjectWith(MdDslInjectorProvider)
class MdDslParsingTest {
    @Inject 
    extension CompilationTestHelper

    @Test
    def void aggregateDoesNotReferenceOtherAggregate() {
        val result = parseHelper.parse('''
            aggregate Address{
            }
            aggregate Person{
                a : Address
            }
        ''')

    validationHelper.assertWarning(result,result.eClass,"failed to find the problem")
    }
}
1
am not sure if this makes sense. you seem to use xbase. but you may follow the reference and use IJvmModelAssociations to find out what the jvmType is inferred from - Christian Dietrich
var simpleName= f.type.type.simpleName, This will give me the java type, But I won't know if it is an aggregate or a value type. I can get the same information via IJvmModelAssociations, but again, I only get the target Java objects. I know it is an address, but not that it is an aggregate - fan
i mean f.type.type - Christian Dietrich
org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations.getPrimarySourceElement(EObject) - Christian Dietrich
Unfortunately, org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociations.getSourceElements() returns an empty list. thus getPrimarySourceElement() is null :( - fan

1 Answers

0
votes

i cannot reproduce this

class MyDslValidator extends AbstractMyDslValidator {

    @Inject
    private IJvmModelAssociations assoc

    @Check
    def checkAggregateReference(Aggregate aggregate) {
        var properties = aggregate.properties

        for (f : properties) {
            System.err.println(f)
            System.err.println(assoc.getSourceElements(f.type.type))
        }
    }

}

gives me the

org.xtext.example.mydsl.myDsl.impl.PropertyImpl@15c4ae78 (name: p)
[org.xtext.example.mydsl.myDsl.impl.AggregateImpl@6952cae7 (name: Address)

as expected