How to avoid JvmParameterizedTypeReference: java.util.List<JvmUnknownTypeReference: A> when generating methods in Xtext?

StackOverflow https://stackoverflow.com/questions/22252166

  •  11-06-2023
  •  | 
  •  

Question

In my model I have more objects from which are later generated Java classes. E.g. in one file is defined

Object A {
    operation getList B
}

And in other file:

Object B {
    operation getList A
}

1) From this 2 interfaces should be generated:

interface A {
    java.util.List<B> getListOfBs();
}

interface B {
    java.util.List<A> getListOfAs();
}

2) Instead it is newly generated like this:

interface A {
    /* java.util.List<B> */ Object getListOfBs();
}

interface B {
    /* java.util.List<A> */ Object getListOfAs();
}

This happens only when I run Project -> Clean... action in Eclipse.
After I change something in my model and save it everything is generated fine, it looks like first example. As I was debuging my inferrer I noticed that return type of created method is defined as JvmParameterizedTypeReference: java.util.List<JvmUnknownTypeReference: A>.

Is there a way to generate classes right also after clean in Eclipse?

EDIT I have now example that duplicates my problem. It is really needed to have so many model files, otherwise everything run OK. I created new Xtext project with defaults. My xtext file:

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.xbase.Xbase
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
ModelDomain:
    SubPackageDeclaration
    elements+=DataModelItem*;
SubPackageDeclaration:
    'subPackage' subPackageName=ID;
DataModelItem:
    DataType | DataObject | DataEnum;
DataType:
    'DataType' name=ID type=JvmParameterizedTypeReference;
DataObject:
    'DataObject' name=ID '{'
    ('lists' '{'
    (lists+=DataAttribute)*
    '}')?
    '}';
DataEnum:
    'DataEnum' name=ID '{'
    values+=ID (',' values+=ID)*
    '}';
DataAttribute:
    (transient?='transient')? type=[DataModelItem|QualifiedName] name=ID;

and inferrer:

package org.xtext.example.mydsl.jvmmodel
import com.google.inject.Inject
import org.eclipse.xtext.common.types.JvmTypeReference
import org.eclipse.xtext.common.types.TypesFactory
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.DataEnum
import org.xtext.example.mydsl.myDsl.DataModelItem
import org.xtext.example.mydsl.myDsl.DataObject
import org.xtext.example.mydsl.myDsl.DataType
import org.xtext.example.mydsl.myDsl.SubPackageDeclaration
class MyDslJvmModelInferrer extends AbstractModelInferrer {
    @Inject extension JvmTypesBuilder
    @Inject private TypesFactory typesFactory
    def dispatch void infer(DataEnum dataEnum, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
        var packageName = ""
        if (dataEnum.eContainer != null && dataEnum.eContainer instanceof SubPackageDeclaration) {
            var implDec = (dataEnum.eContainer as SubPackageDeclaration)
            packageName = "data.model." + implDec.subPackageName
        }
        val qualifiedName = packageName + "." + dataEnum.name.toFirstUpper
        acceptor.accept(
            dataEnum.toEnumerationType(qualifiedName) [
                for (value : dataEnum.values) {
                    val jvmLiteral = typesFactory.createJvmEnumerationLiteral
                    jvmLiteral.simpleName = value
                    jvmLiteral.^static = true
                    var t1 = typesFactory.createJvmParameterizedTypeReference
                    t1.type = it
                    jvmLiteral.type = t1
                    members += jvmLiteral
                }
            ])
    }
    def dispatch void infer(DataObject dataObject, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
        var packageName = ""
        if (dataObject.eContainer != null && dataObject.eContainer instanceof SubPackageDeclaration) {
            var implDec = (dataObject.eContainer as SubPackageDeclaration)
            packageName = "data.model." + implDec.subPackageName
        }
        val qualifiedName = packageName + "." + dataObject.name.toFirstUpper
        acceptor.accept(
            dataObject.toInterface(qualifiedName) [
                superTypes += dataObject.newTypeRef("data.model.core.IDataObject")
                for (list : dataObject.lists) {
                    val JvmTypeReference ^void = dataObject.newTypeRef(Void.TYPE)
                    val JvmTypeReference listTypeRef = convertToJvmType(list.type)
                    if (listTypeRef != null && !isPreIndexingPhase) {
                        val removeMethod = list.toMethod("remove" + list.name.toFirstUpper, ^void) [
                            parameters += list.toParameter(list.name, listTypeRef)
                        ]
                        members += removeMethod
                        removeMethod.setAbstract(true);
                        val returnType = dataObject.newTypeRef("java.util.List", listTypeRef)
                        val allMethod = list.toMethod("get" + list.name.toFirstUpper + "List", returnType)[]
                        allMethod.setAbstract(true);
                        members += allMethod
                    }
                }
                val denyLoading = dataObject.toMethod("denyLoading", dataObject.newTypeRef(Void.TYPE))[]
                denyLoading.setAbstract(true);
                members += denyLoading
            ])
    }
    def public JvmTypeReference convertToJvmType(DataModelItem modelItem) {
        var JvmTypeReference typeRef
        if (modelItem instanceof DataType) {
            var typeData = (modelItem as DataType)
            typeRef = typeData.type.cloneWithProxies
        } else if (modelItem instanceof DataEnum) {
            var typeDec = (modelItem.eContainer as SubPackageDeclaration)
            var typePackageName = "data.model." + typeDec.subPackageName
            val typeData = modelItem as DataEnum
            typeRef = typeData.newTypeRef(typePackageName + "." + typeData.name)
        } else if (modelItem.eContainer != null) {
            var typeDec = (modelItem.eContainer as SubPackageDeclaration)
            var typePackageName = "data.model." + typeDec.subPackageName
            var typeQName = typePackageName + "." + modelItem.name
            typeRef = modelItem.newTypeRef(typeQName)
        }
        return typeRef
    }
}

Then I have these 6 mydsl files:
1. address.mydsl

subPackage address DataObject PhoneNumber { lists { PhoneNumberType types } } DataObject Contact { lists { PhoneNumber phoneNumber } } DataObject Address { lists { PhoneNumber phoneNumber } } DataEnum PhoneNumberType { BUSINESS_PHONE , BUSINESS_FAX , BUSINESS_MOBILE , HOME_PHONE , HOME_MOBILE , HOME_FAX } DataEnum Gender { FEMALE , MALE }

2. asset.mydsl

subPackage asset DataObject Asset { lists { LiabilityCase liabilityCase} } DataObject VehicleEquipment { lists { Address address } } DataObject LiabilityCase { } DataObject AssetVehicle { lists { VehicleEquipment serial VehicleEquipment extra } } DataObject Tires { } DataObject RentalObject { lists { Asset vehicle Asset mob Asset prop Asset estate Asset asset } } DataEnum AssetCondition { NEW , USED , DEMO } DataEnum VehicleColorType { STANDARD , PEARL_EFFECT , METALLIC , SPECIAL } DataEnum TiresType { SUMMER , WINTER } DataEnum AssetClass { VEHICLE , MOB , PROPERTY , ESTATE }

3. businessPartner.mydsl

subPackage businesspartner DataEnum BusinessPartnerType { INDIVIDUAL , ORGANISATION } DataObject BusinessPartner { lists { Address address  Contact contact BpTransactions transaction BpAdvisorRelation advisor } } DataObject BpRiskParameters { } DataObject BpTransactions { } DataObject BalanceDetails { } DataObject BpAdvisorRelation { } DataEnum AdvisorType { ADVISOR , ADVISOR_BO , ADMINISTRATOR }

4. invoice.mydsl

subPackage invoice DataObject Invoice{ lists { Receipt receipt } } DataObject Receipt { lists { InvoiceItem item } } DataObject ReceiptFleet { } DataObject InvoiceItem{ } DataEnum InvoiceCategory { OBJECT, SERVICE ,INVOICES } DataEnum InvoiceType { INCOMING, OUTGOING } DataEnum InvoiceStatus { OPEN, PARTIALLY_COMPLETE, COMPLETE } DataEnum ItemUnit { UNIT, PALETTE, DOZEN, LITRE } DataEnum ItemIdType { LICENSE_PLATE, CARD_NUMBER, OBJECT_ID }

5. securities.mydsl

subPackage securities DataObject Securities {} DataEnum SecuritiesType {PARTNER_APPLICANT, GUARANTOR,SOMETHING_ELSE}

6. types.mydsl

subPackage types DataType String java.lang.String DataType Integer java.lang.Integer DataType Double java.lang.Double DataType Date java.util.Date DataType Boolean java.lang.Boolean DataType Void java.lang.Void

Generated interfaces extends data.model.core.IDataObject. This interface has no methods. After clean action in Eclipse I can see that getPhoneNumberList method is generated as

public abstract /* List<data.model.address.PhoneNumber> */Object getPhoneNumberList();

and also other methods, but others are OK.
I tested it with Xtext 2.5 and 2.3 with the same result.

I get error in the Console saying:

2    [Worker-4] ERROR org.eclipse.xtext.common.types.access.jdt.JdtTypeProvider  - [Working copy] PhoneNumber.java [in data.model.address [in src-gen [in test]]] does not exist
Java Model Exception: Java Model Status [[Working copy] PhoneNumber.java [in data.model.address [in src-gen [in test]]] does not exist]

but I don't know how to fix it.

Was it helpful?

Solution

It appears that you try to resolve cross references in the indexing phase. Please be careful to define the methods of your inferred types and interfaces in the initializer block of your #toClass invocations. This should work. Your code should look like this:

myObject.toClass(myObject.name) [ clazz |
  myObject.operations.forEach [ op |
    clazz.members += op.toMethod( getMethodName(op), getMethodType(op)) [
      ..
    ]
  ]
]

If your want to refer to other types from the initialization block, you'd have to split the initializer into two parts. Any code that relies on other types that will be inferred, has to go into the initialize later section, e.g.

acceptor.accept( myObject.toClass(..) [ .. ]).initializeLater [
  // more code here
]

In your particular example, the inference works for me, if I use this approach:

def dispatch void infer(DataObject dataObject, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
    var packageName = ""
    if (dataObject.eContainer != null && dataObject.eContainer instanceof SubPackageDeclaration) {
        var implDec = (dataObject.eContainer as SubPackageDeclaration)
        packageName = "data.model." + implDec.subPackageName
    }
    val qualifiedName = packageName + "." + dataObject.name.toFirstUpper
    acceptor.accept(
        dataObject.toInterface(qualifiedName) [
            superTypes += dataObject.newTypeRef("data.model.core.IDataObject")
        ]).initializeLater[
            for (list : dataObject.lists) {
                val JvmTypeReference ^void = dataObject.newTypeRef(Void.TYPE)
                if (!isPreIndexingPhase) {
                    val JvmTypeReference listTypeRef = convertToJvmType(list.type)
                    if (listTypeRef != null) {
                        val removeMethod = list.toMethod("remove" + list.name.toFirstUpper, ^void) [
                            parameters += list.toParameter(list.name, listTypeRef)
                        ]
                        members += removeMethod
                        removeMethod.setAbstract(true);
                        val returnType = dataObject.newTypeRef("java.util.List", listTypeRef)
                        val allMethod = list.toMethod("get" + list.name.toFirstUpper + "List", returnType)[]
                        allMethod.setAbstract(true);
                        members += allMethod
                    }
                }
            }
            val denyLoading = dataObject.toMethod("denyLoading", dataObject.newTypeRef(Void.TYPE))[]
            denyLoading.setAbstract(true);
            members += denyLoading
        ]
}

All this is necessary because the types have to be fully collected before they can be referenced.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top