【问题标题】:Xtext global auto-generationXtext 全局自动生成
【发布时间】:2019-03-04 00:19:48
【问题描述】:

在 Xtext 中,如何自动生成包含来自多个模型文件的信息的单个文件。

考虑以下简单的 Xtext 语法。

grammar org.example.people.People with org.eclipse.xtext.common.Terminals

generate people "http://www.example.org/people/People"

People:
    people+=Person*;

Person:
    'person' name=ID ';';

在启动的工作区中,我创建了一个包含两个文件的项目,friends.people

// friends
person Alice;
person Bob;

enemies.people

// enemies
person Malice;
person Rob;

当全局索引发生变化时,如何自动生成一个列出所有人的文件?

Alice
Bob
Malice
Rob

【问题讨论】:

标签: xtext


【解决方案1】:

为了方便日后参考,这里是结合Christian Dietrich给出的各种参考资料得到的解决方案。请注意,该解决方案依赖于 Eclipse。

任何发现自己有此要求的人也许应该尝试找到一种更好的方法来模拟问题。例如,一个单例模型元素 All 通过使用标准 API 查找模型中的每个人来生成所需的列表。这独立于 Eclipse,并且不需要以下复杂性。

在语法项目的生成器包中,创建一个Java接口IPeopleGenerator扩展IGenerator2

package org.example.people.generator;

import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xtext.generator.IFileSystemAccess2;
import org.eclipse.xtext.generator.IGenerator2;
import org.eclipse.xtext.generator.IGeneratorContext;

public interface IPeopleGenerator extends IGenerator2{
    public void doGenerate(ResourceSet input, IFileSystemAccess2 fsa, IGeneratorContext context);
}

并编辑现有的生成器PeopleGenerator,如下所示。

/*
 * generated by Xtext 2.14.0
 */
package org.example.people.generator

import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.emf.ecore.resource.ResourceSet
import org.eclipse.xtext.generator.IFileSystemAccess2
import org.eclipse.xtext.generator.IGeneratorContext
import org.example.people.people.Person

/**
 * Generates code from your model files on save.
 * 
 * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation
 */
class PeopleGenerator implements IPeopleGenerator {

    override doGenerate(ResourceSet rs, IFileSystemAccess2 fsa, IGeneratorContext context) {

    val people = rs.resources.map(r|r.allContents.toIterable.filter(Person)).flatten
    fsa.generateFile("all.txt", people.compile)
    }

    override afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
    }

    override beforeGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
    }

    override doGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {
    }

  def compile (Iterable<Person> entities) '''
    «FOR e : entities»
    «e.name»
    «ENDFOR»
  '''

}

并添加方法

def Class<? extends IPeopleGenerator> bindIPeopleGenerator () {
    return PeopleGenerator
}

到语法项目中现有的运行时模块PeopleRuntimeModule

工作需要在 UI 项目org.example.people.ui 中完成。因此,该解决方案依赖于 Eclipse。

如下创建一个Java类org.example.people.ui.PeopleBuilderParticipant(复杂性是需要确保全局生成的文件只创建一次)

package org.example.people.ui;

import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.builder.BuilderParticipant;
import org.eclipse.xtext.builder.EclipseResourceFileSystemAccess2;
import org.eclipse.xtext.builder.MonitorBasedCancelIndicator;
import org.eclipse.xtext.generator.GeneratorContext;
import org.eclipse.xtext.resource.IContainer;
import org.eclipse.xtext.resource.IResourceDescription;
import org.eclipse.xtext.resource.IResourceDescription.Delta;
import org.eclipse.xtext.resource.IResourceDescriptions;
import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider;
import org.example.people.generator.IPeopleGenerator;

import com.google.inject.Inject;

public class PeopleBuilderParticipant extends BuilderParticipant {

    @Inject
    private ResourceDescriptionsProvider resourceDescriptionsProvider;

    @Inject
    private IContainer.Manager containerManager;

    @Inject(optional = true)
    private IPeopleGenerator generator;

    protected ThreadLocal<Boolean> buildSemaphor = new ThreadLocal<Boolean>();

    @Override
    public void build(IBuildContext context, IProgressMonitor monitor) throws CoreException {
        buildSemaphor.set(false);
        super.build(context, monitor);
    }

    @Override
    protected void handleChangedContents(Delta delta, IBuildContext context,
            EclipseResourceFileSystemAccess2 fileSystemAccess) throws CoreException {
        super.handleChangedContents(delta, context, fileSystemAccess);
        if (!buildSemaphor.get() && generator != null) {
            invokeGenerator(delta, context, fileSystemAccess);
        }
    }
    private void invokeGenerator(Delta delta, IBuildContext context, EclipseResourceFileSystemAccess2 access) {
        buildSemaphor.set(true);
        Resource resource = context.getResourceSet().getResource(delta.getUri(), true);
        if (shouldGenerate(resource, context)) {
            IResourceDescriptions index = resourceDescriptionsProvider.createResourceDescriptions();
            IResourceDescription resDesc = index.getResourceDescription(resource.getURI());
            List<IContainer> visibleContainers = containerManager.getVisibleContainers(resDesc, index);
            for (IContainer c : visibleContainers) {
                for (IResourceDescription rd : c.getResourceDescriptions()) {
                    context.getResourceSet().getResource(rd.getURI(), true);
                }
            }

            MonitorBasedCancelIndicator cancelIndicator = new MonitorBasedCancelIndicator(
                    new NullProgressMonitor()); //maybe use reflection to read from fsa
            GeneratorContext generatorContext = new GeneratorContext();
            generatorContext.setCancelIndicator(cancelIndicator);
            generator.doGenerate(context.getResourceSet(), access, generatorContext);
        }
    }

}

并通过添加绑定此构建参与者

override  Class<? extends IXtextBuilderParticipant>  bindIXtextBuilderParticipant() {       
    return PeopleBuilderParticipant;
}

到现有的 UI 模块 org.example.people.ui.PeopleUiModule

【讨论】:

    【解决方案2】:

    我再次将验证码添加到fundamental的答案中以消除无效资源。但是,当最后修改的资源无效时,这将不起作用,因为无效时不会调用 doGenerate。当任何有效资源被保存时,无效资源将从 all.txt 中被丢弃。

    override doGenerate(ResourceSet rs, IFileSystemAccess2 fsa, IGeneratorContext context) {
        var valid_rs = new ArrayList<Resource>
        for(r : rs.resources)
            if (( r as XtextResource)
                .getResourceServiceProvider()
                .getResourceValidator()
                .validate(r,CheckMode.ALL, null)
                .map(issue | issue.severity)
                .filter[it === Severity.ERROR]
                .size == 0) 
                    valid_rs.add(r)
    
        val types = valid_rs.map(r|r.allContents.toIterable.filter(Person)).flatten
        fsa.generateFile("all.txt", people.compile)
    }
    

    【讨论】:

      猜你喜欢
      • 2018-12-20
      • 1970-01-01
      • 1970-01-01
      • 2022-08-16
      • 1970-01-01
      • 1970-01-01
      • 2016-01-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多