Top

Referring to Java Types Using Xbase

While the JVM types approach from the previous chapter allows to refer to any Java element, it is quite limited when it comes to generics. Usually, a type reference in Java can have type arguments which can also include wildcards, upper and lower bounds etc. A simple cross-reference using a qualified name is not enough to express neither the syntax nor the structure of such a type reference.

Xbase offers a parser rule JvmTypeReference which supports the full syntax of a Java type reference and instantiates a JVM element of type JvmTypeReference (src). So let us start by inheriting from Xbase

grammar org.eclipse.xtext.example.Domainmodel 
   with org.eclipse.xtext.xbase.Xbase

As we can express all kinds of Java type references directly now, the indirection for DataTypes is no longer necessary. To eliminate it, all cross-references to Types have to be replaced by calls to the JvmTypeReferences rule. The rules DataType, Type, and QualifiedName become obsolete (the latter is already defined in Xbase), and the Type in AbstractEntity must be changed to Entity. The whole grammar now reads as

grammar org.eclipse.xtext.example.Domainmodel with
                                      org.eclipse.xtext.xbase.Xbase

generate domainmodel "http://www.eclipse.org/xtext/example/Domainmodel"
import "http://www.eclipse.org/xtext/common/JavaVMTypes" as jvmTypes

Domainmodel:
  (elements += AbstractElement)*
;

PackageDeclaration:
  'package' name = QualifiedName '{'
    (elements += AbstractElement)*
  '}'
;

AbstractElement:
  PackageDeclaration | Entity | Import
;

Import:
  'import' importedNamespace = QualifiedNameWithWildcard
;
  
QualifiedNameWithWildcard:
  QualifiedName '.*'?
;
  
Entity:
  'entity' name = ID 
              ('extends' superType = JvmTypeReference)?
  '{'
    (features += Feature)*
  '}'
;
 
Feature:
  (many ?= 'many')? name = ID ':' type = JvmTypeReference
;

As we changed the grammar, we have to regenerate the language now.

Being able to parse a Java type reference is already nice, but we also have to write them back to their string representation when we generate Java code. Into the bargain, a generic type reference with fully qualified class names can become a bit bulky. The ImportManager (src) shortens fully qualified names, keeps track of imported namespaces, avoids name collisions, and helps to serialize JvmTypeReferences (src) by means of the TypeReferenceSerializer (src). This utility encapsulates how type references may be serialized depending on the concrete context in the output.

The following snippet shows our code generator using an ImportManager (src) in conjunction with as TypeReferenceSerializer (src). We create a new instance and pass it through the generation functions, collecting types on the way. As the import section in a Java file precedes the class body, we create the body into a String variable and assemble the whole file's content in a second step.

class DomainmodelGenerator implements IGenerator {
    
    @Inject extension IQualifiedNameProvider
    @Inject extension TypeReferenceSerializer 
    
    override void doGenerate(Resource resource, IFileSystemAccess fsa) {
        for(e: resource.allContentsIterable.filter(typeof(Entity))) {
            fsa.generateFile(
              e.fullyQualifiedName.toString.replace(".""/") + ".java",
              e.compile)
        }
    }
    
    def compile(Entity it'''
        «val importManager = new ImportManager(true)» 
        «val body = body(importManager)»
        «IF eContainer != null»
            package «eContainer.fullyQualifiedName»;
        «ENDIF»
        
        «FOR i:importManager.imports»
            import «i»;
        «ENDFOR»
        
        «body»
    '
''
    
    def body(Entity it, ImportManager importManager) '''
        public class «name» «IF superType != null»
            extends «superType.shortName(importManager)» «ENDIF»{
            «FOR f : features»
                «f.compile(importManager)»
            «ENDFOR»
        }
    '
''
        
    def compile(Feature it, ImportManager importManager) '''
        private «type.shortName(importManager)» «name»;
        
        public «type.shortName(importManager)» 
            get«name.toFirstUpper»() {
            return «name»;
        }
        
        public void set«name.toFirstUpper»(
            «type.shortName(importManager)» «name») {
            this.«name» = «name»;
        }
    '
''
    
    def shortName(JvmTypeReference ref, 
                  ImportManager importManager) {
        val result = new StringBuilderBasedAppendable(importManager)
        ref.serialize(ref.eContainer, result);
        result.toString
    }
}