I am having a problem with referencing the container class from an inner class with Xtext, Xbase and the Java model inferrer. To highlight my problem, let me first demonstrate a working example (derived from Implementing Domain-Specific Languages with Xtext and Xtend by Bettini) , which I call the loose-grammar. It is loose in the sense that Entities can inherit any Java class (rather that only Entities) and attributes are typed by any Java class (rather than just entities).
This question concerns tightening this grammar to Entities only.
Thanks to the answers to Grammatically restricted JVMModelInferrer inheritence with Xtext and XBase and Xbase: Fields in generated inner class not recognized, no problems as class in own file, I can perfectly achieve this, except in the case of inner-classes referencing containing classes. This question is only about the inner-class case.
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.xbase.Xbase
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
ModelDeclaration:
importSection=XImportSection?
packageDeclaration=PackageDeclaration;
PackageDeclaration:
'package' name=QualifiedName ';'?
model=Model;
Model:
'model' name=ID '{'
(
entities+=Entity
)+
'}';
Entity:
'entity' name=ID ('extends' superType=JvmParameterizedTypeReference)? '{'
(attributes+=Attribute
|
entities+=Entity
)*
'}';
Attribute:
'attr' (type=JvmTypeReference) name=ID ';';
The following JvmModelInferrer perfectly forges a single class for the model, with the entities as inner classes, themselves possibly containing inner classes. All generated inner-classes are static.
package org.xtext.example.mydsl.jvmmodel
import com.google.inject.Inject
import org.eclipse.xtext.common.types.JvmGenericType
import org.eclipse.xtext.naming.IQualifiedNameProvider
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.xtext.example.mydsl.myDsl.Entity
import org.xtext.example.mydsl.myDsl.Model
class MyDslJvmModelInferrer extends AbstractModelInferrer {
@Inject extension JvmTypesBuilder
@Inject extension IQualifiedNameProvider
def dispatch void infer(Model model, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(
model.toClass(model.fullyQualifiedName)
) [
model.entities.forEach [ entity |
members += entity.toClass(entity.fullyQualifiedName) [ jEntity |
forgeEntities(model, jEntity, entity)
]
]
]
}
protected def void forgeEntities(Model model, JvmGenericType it, Entity entity) {
static=true
documentation = entity.documentation
if (entity.superType !== null) {
superTypes += entity.superType
}
entity.attributes.forEach [ a |
val type = a.type
members += a.toField(a.name, type) [
documentation = a.documentation
]
members += a.toGetter(a.name, type)
members += a.toSetter(a.name, type)
]
entity.entities.forEach [ innerEntity |
members += innerEntity.toClass(innerEntity.fullyQualifiedName) [ jInnerEntity |
forgeEntities(model, jInnerEntity, innerEntity)
]
]
}
}
For example, the instance
package test
model Test {
entity A {}
entity B {
attr Test.A myA;
}
entity C extends test.Test.A {}
entity D {
attr Test.A myA;
entity I {}
entity J extends test.Test.D {}
entity K {
attr Test.D myD;
}
}
}
correctly infers
package test;
@SuppressWarnings("all")
public class Test {
public static class A {
}
public static class B {
private Test.A myA;
public Test.A getMyA() {
return this.myA;
}
public void setMyA(final Test.A myA) {
this.myA = myA;
}
}
public static class C extends Test.A {
}
public static class D {
private Test.A myA;
public Test.A getMyA() {
return this.myA;
}
public void setMyA(final Test.A myA) {
this.myA = myA;
}
public static class I {
}
public static class J extends Test.D {
}
public static class K {
private Test.D myD;
public Test.D getMyD() {
return this.myD;
}
public void setMyD(final Test.D myD) {
this.myD = myD;
}
}
}
}
Now consider the following tightening of the grammar.
...
Entity:
'entity' name=ID ('extends' superType=[Entity|QualifiedName])? '{'
(attributes+=Attribute
|
entities+=Entity
)*
'}';
Attribute:
'attr' (type=[Entity|QualifiedName]) name=ID ';';
To the model inferrer I add, based on Xbase: Fields in generated inner class not recognized, no problems as class in own file, the helper methods
def String getJavaTypeName(Entity entity) {
val parent = entity.eContainer
if (parent instanceof Model) {
return (parent.fullyQualifiedName.toString+"$"+entity.name)
}
if (parent instanceof Entity) {
return getJavaTypeName(parent)+"$"+entity.name
}
throw new RuntimeException("Impossible")
}
def JvmTypeReference getJavaTypeRef(Entity entity) {
getJavaTypeName(entity).typeRef
}
and change the forgeEntities method to
protected def void forgeEntities(Model model, JvmGenericType it, Entity entity) {
static=true
documentation = entity.documentation
println("### FORGING [" + entity.name + "]")
if (entity.superType !== null) {
superTypes += entity.superType.javaTypeRef
}
entity.attributes.forEach [ a |
val type = a.type.javaTypeRef
members += a.toField(a.name, type) [
documentation = a.documentation
]
members += a.toGetter(a.name, type)
members += a.toSetter(a.name, type)
]
entity.entities.forEach [ innerEntity |
members += innerEntity.toClass(innerEntity.fullyQualifiedName) [ jInnerEntity |
forgeEntities(model, jInnerEntity, innerEntity)
]
]
}
For the same model (although now references can be tightened)
package test
model Test {
entity A {}
entity B {
attr A myA;
}
entity C extends A {}
entity D {
attr A myA;
entity I {}
entity J extends D {}
entity K {
attr D myD;
}
}
}
the following is generated.
package test;
@SuppressWarnings("all")
public class Test {
public static class A {
}
public static class B {
private Test.A myA;
public Test.A getMyA() {
return this.myA;
}
public void setMyA(final Test.A myA) {
this.myA = myA;
}
}
public static class C extends Test.A {
}
public static class D {
private Test.A myA;
public Test.A getMyA() {
return this.myA;
}
public void setMyA(final Test.A myA) {
this.myA = myA;
}
public static class I {
}
public static class J implements test.Test$D {
}
public static class K {
private test.Test$D myD;
public test.Test$D getMyD() {
return this.myD;
}
public void setMyD(final test.Test$D myD) {
this.myD = myD;
}
}
}
}
While everything works as expected for A, B and C, demonstrating that the "$" is working, class D fails. The difference is that an inner class is referring to it's containing class. This is when the process fails. Both the extension of J by D and the reference from myD to D in K are not working.
While I can work around this, explicitly externalizing the inner classes with a naming convention and changing the name/scope? provider, I am wondering if there is a simple solution.