XText 对非 DSL 资源的交叉引用

XText cross-reference to an non-DSL resource

请考虑这个最小的 Xtext 语法。

Model:
  "As a" stackeholder=Stakeholder "I want" want=Want;

Stakeholder:
  'client' | 'developer' | 'manager';

Want:
  'everything' | 'cookies' | 'fame';

现在我需要做的是将利益相关者的定义(让我们忘记 want)到 SOME 外部数据源。这个 "external data source" 可能是一个 CSV 文件,可能是一个数据库,也可能是一个网络服务。但我不太可能是某个 Xtext 文件或带有 EMF 模型。但我仍然想交叉引用它,就像您可以在 DSL 中交叉引用 java 类型一样。

抛开手动解析和缓存(出于性能考虑)等问题:这是否可行?

我深入研究了范围和资源提供者的主题,但我发现的所有内容都要求外部源至少是另一个 DSL 的一部分。

我很乐意大致概述需要完成的工作。

抱歉这么久才回复。我尝试了基督徒的建议,不是很满意,然后优先事项发生了变化。现在我将再次解决这个问题,为了为其他人记录(并清醒我的头脑),我将写下我到目前为止所做的事情,因为这并不是那么简单,需要大量的实验。

我不会 post 完整 类 而是只包含相关部分。如果需要,请随时询问更多详细信息。

我的语法定义现在看起来像这样:

Model:
  stakeholders+=StakeholderDecl*
  requirements+=Requirement*;

Requirement:
  'As a' stakeholder=[Stakeholder] 'I want' want=('everything' | 'cookies' | 'results')
;

StakeholderDecl returns Stakeholder :
  'Stakeholder' Stakeholder
;

Stakeholder:
  name=ID
;

请注意,以下所有内容都需要在 .ui 包中完成。

首先我创建了StakeholdersProvider.xtend:

class StakeholdersProvider extends AbstractResourceDescription {

  // this is the dummy for an "external source". Just raw data.
  val nameList = newArrayList( "buddy", "boss" )

  val cache = nameList.map[it.toDescription]

  private val uri = org.eclipse.emf.common.util.URI.createPlatformResourceURI("neverland", true)

  def public List<IEObjectDescription> loadAdditionalStakeholders() {
        cache
  }

  def private IEObjectDescription toDescription(String name) {

    ExternalFactoryImpl.init()
    val ExternalFactory factory = new ExternalFactoryImpl()

    val Stakeholder obj = factory.createStakeholder as StakeholderImpl
    obj.setName(name)


    new StakeholderDescription(name, obj, uri)
  }

. . .

  override getURI() {
    uri
  }

  def public boolean isProvided( EObject object ) {
    if( object.eClass.classifierID != ExternalPackageImpl.STAKEHOLDER ) {
        false
    }
    else {
        val stakeholder = object as Stakeholder
        nameList.exists[it == stakeholder.name]
    }
  }

}

注意provider也是一个resourceDescription,它的uri当然是废话。

我用这个提供者写了一个 ScopeWrapper.xtend :

class ScopeWrapper implements IScope {

  private var IScope scope;
  private var StakeholdersProvider provider

  new( IScope scopeParam, StakeholdersProvider providerParam ) {
    scope=scopeParam
    provider = providerParam
  }

  override getAllElements() {
    val elements = scope.allElements.toList

    val ret = provider.loadAdditionalStakeholders()
    ret.addAll(elements)

    ret
  }

  override getSingleElement(QualifiedName name) {
      allElements.filter[it.name == name].head
  }

. . . 

}

ResourceDescriptionWrapper.xtend

class ResourceDescriptionsWrapper implements IResourceDescriptions {

  private StakeholdersProvider provider;
  private IResourceDescriptions descriptions;

  new(IResourceDescriptions descriptionsParam, StakeholdersProvider providerParam) {
    descriptions = descriptionsParam
    provider = providerParam
  }

  override getAllResourceDescriptions() {
    val resources = descriptions.allResourceDescriptions.toList
    resources.add(provider)
    resources
  }

  override getResourceDescription(URI uri) {
    if( uri == provider.URI ) provider
    else descriptions.getResourceDescription(uri)
  }
  override getExportedObjects() {
    val descriptions = descriptions.exportedObjects.toList

    descriptions.addAll(provider.exportedObjects)

    descriptions

  }

  . . . some overrides for getExportedObjects-functions

}

所有这些都连接在一起MyGlobalScopeProvider.xtend

class MyGlobalScopeProvider extends TypesAwareDefaultGlobalScopeProvider {

  val provider = new StakeholdersProvider()

  override getScope(Resource context, EReference reference, Predicate<IEObjectDescription> filter) {
    val scope = super.getScope(context, reference, filter)
    return new ScopeWrapper(scope, provider)
  }

  override public IResourceDescriptions getResourceDescriptions(Resource resource) {
    val superDescr = super.getResourceDescriptions(resource)
    return new ResourceDescriptionsWrapper(superDescr, provider)
  }

}

注册在MyDslUiModule.java

public Class<? extends IGlobalScopeProvider> bindIGlobalScopeProvider() {
    return MyGlobalScopeProvider.class;
}

到目前为止一切顺利。我现在得到 bossbuddy 作为利益相关者的建议。但是,当我使用这两个中的一个时,我在编辑器中收到一个错误,抱怨悬空引用和利益相关者 cannot be exported as the target is not contained in a resource 在控制台中记录的错误。弄清楚这两个可能是相关的我试图修复错误日志记录,创建 MyresourceDescriptionStrategy.xtend

class MyResourcesDescriptionStrategy extends DefaultResourceDescriptionStrategy {

  val provider = new StakeholdersProvider()

  override isResolvedAndExternal(EObject from, EObject to) {
    if (provider.isProvided(to)) {
        // The object is a stakeholder that was originally provided by
        // our StakeholdersProvider. So we mark it as resolved.
        true
    } else {
        super.isResolvedAndExternal(from, to)
    }
  }
}

并将其连接到 UiModule 中:

public Class<? extends IDefaultResourceDescriptionStrategy> bindDefaultResourceDescriptionStrategy() {
    return MyResourcesDescriptionStrategy.class;
}

这修复了日志记录错误,但 "dangling reference" 问题仍然存在。我为此搜索了解决方案,most prominent result 建议首先定义 IResourceServiceProvider 是解决我的问题的最佳方法。 我将在当前方法上花费更多时间,而不是使用 ResourceProvider 进行尝试。

编辑:我解决了 "dangling reference" 问题。 StakeholdersProvider.xtend 中的 loadAdditionalStakeholders() 函数现在看起来像这样:

override loadAdditionalStakeholders() {

    val injector = Guice.createInjector(new ExternalRuntimeModule());
    val rs = injector.getInstance(ResourceSet)
    val resource = rs.createResource(uri)
    nameList.map[it.toDescription(resource)]
}

def private IEObjectDescription toDescription(String name, Resource resource) {

    ExternalFactoryImpl.init()
    val ExternalFactory factory = new ExternalFactoryImpl()

    val Stakeholder obj = factory.createStakeholder as StakeholderImpl

    obj.setName(name)
    // not sure why or how but when adding the obj to the resource, the
    // the resource will be set in obj . . . thus no more dangling ref
    resource.contents += obj


    new StakeholderDescription(name, obj, uri)
}