You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过EMFTVM编程执行ATL转换时获取跟踪模型相对路径

问题背景

以基础ATL转换Families2Persons为例,其ATL代码如下:

-- @atlcompiler emftvm
-- @path Families=/Test/Families.ecore
-- @path Persons=/Test/Persons.ecore

module Families2Persons;

create OUT: Persons, trace: Trace from IN: Families;

rule Member2Male {
    from
        s: Families!Member (not s.isFemale())
    to
        t: Persons!Male (
            fullName <- s.firstName + ' ' + s.familyName
        )
}

rule Member2Female {
    from
        s: Families!Member (s.isFemale())
    to
        t: Persons!Female (
            fullName <- s.firstName + ' ' + s.familyName
        )
}

(注:为节省篇幅省略了辅助函数,与官方教程示例一致)

在Eclipse环境中通过EMFTVM运行该转换时,生成的trace.xmi片段引用路径为相对路径,符合预期:

<links>
      <sourceElements name="s" defaultFor="/">
        <object href="../sample-Families.xmi#/0/@father"/>
      </sourceElements>
      <targetElements name="t">
        <object href="../person-emftvm.xmi#/0"/>
      </targetElements>
    </links>

但通过Java代码调用EMFTVM API执行转换时,输出模型内容正确,但跟踪模型中的引用路径变成了绝对路径:

public class RunTestTransformation {

    public static String here = new File(".").getAbsolutePath();

    public static URI resourceURI(String relativePath) {
        return URI.createFileURI(here + relativePath);
    }

    public static void main(String[] args) throws IOException {

        Map<String, Object> map = Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap();
        map.put("xmi", new XMIResourceFactoryImpl());
        map.put("ecore", new EcoreResourceFactoryImpl());
        map.put("emftvm", new EMFTVMResourceFactoryImpl());

        ExecEnv env = EmftvmFactory.eINSTANCE.createExecEnv();
        ResourceSet rs = new ResourceSetImpl();

        // Register the metamodels into resource set
        EPackage FamiliesPkg = (EPackage) rs.getResource(resourceURI("/../Test/Families.ecore"), true).getContents()
                .get(0);
        EPackage.Registry.INSTANCE.put(FamiliesPkg.getNsURI(), FamiliesPkg);
        EPackage PersonsPkg = (EPackage) rs.getResource(resourceURI("/../Test/Persons.ecore"), true).getContents()
                .get(0);
        EPackage.Registry.INSTANCE.put(PersonsPkg.getNsURI(), PersonsPkg);

        // Load metamodels into execenv
        Metamodel familiesMetaModel = EmftvmFactory.eINSTANCE.createMetamodel();
        familiesMetaModel.setResource(rs.getResource(resourceURI("/../Test/Families.ecore"), true));
        env.registerMetaModel("Families", familiesMetaModel);

        Metamodel personsMetaModel = EmftvmFactory.eINSTANCE.createMetamodel();
        personsMetaModel.setResource(rs.getResource(resourceURI("/../Test/Persons.ecore"), true));
        env.registerMetaModel("Persons", personsMetaModel);

        String relativeInputPath = "/../Test/sample-Families.xmi";
        String relativeTracePath = "/../Test/traces.xmi";
        String relativeOutputPath = "/../Test/persons.xmi";

        // Load models
        URI inputUri = resourceURI(relativeInputPath);
        Model inModel = EmftvmFactory.eINSTANCE.createModel();
        inModel.setResource(rs.getResource(inputUri, true));
        env.registerInputModel("IN", inModel);

        URI uriTrace = resourceURI(relativeTracePath);
        Model traceOutModel = EmftvmFactory.eINSTANCE.createModel();
        traceOutModel.setResource(rs.createResource(uriTrace));
        env.registerOutputModel("trace", traceOutModel);

        URI uriOut = resourceURI(relativeOutputPath);
        Model outModel = EmftvmFactory.eINSTANCE.createModel();
        outModel.setResource(rs.createResource(uriOut));
        env.registerOutputModel("OUT", outModel);

        // Load and run module
        ModuleResolver mr = new DefaultModuleResolver("./../Test/emftvm/", rs);
        TimingData td = new TimingData();
        env.loadModule(mr, "Families2Persons");
        td.finishLoading();
        env.run(td);
        td.finish();

        // Save models
        inModel.getResource().save(Collections.emptyMap());
        traceOutModel.getResource().save(Collections.emptyMap());
        outModel.getResource().save(Collections.emptyMap());
    }
}

生成的跟踪模型片段如下:

<links>
      <sourceElements name="s" defaultFor="/">
        <object href="file:/C:/Users/XXXX/./../Test/sample-Families.xmi#/0/@father"/>
      </sourceElements>
      <targetElements name="t">
        <object href="file:/C:/Users/XXXX/./../Test/persons.xmi#/0"/>
      </targetElements>
    </links>
问题需求

如何修改Java代码,使编程执行ATL转换时生成的跟踪模型引用路径与Eclipse运行结果一致,使用相对路径?

已尝试方法
  • 将createFileURI()替换为createURI(),出现编译错误;
  • 在save()方法中使用不同XMLResource选项,无效果;
  • 直接向createFileURI()传入相对路径,出现文件未找到错误。

解决方案

问题核心在于EMFTVM生成跟踪引用时,会根据资源的URI类型决定使用绝对还是相对路径。当前代码通过createFileURI()生成了绝对文件URI,导致跟踪引用也采用绝对路径。要生成相对路径,需让所有资源的URI基于同一基准目录,并配置EMF使用相对路径规则。

步骤1:修正资源URI生成逻辑

放弃通过绝对路径拼接的方式,基于项目基准目录生成相对URI,避免冗余路径片段:

public static File baseDir;

static {
    try {
        // 获取标准基准目录,自动去除./../这类冗余片段
        baseDir = new File(".").getCanonicalFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static URI resourceURI(String relativePath) {
    File targetFile = new File(baseDir, relativePath);
    // 生成基于基准目录的相对URI
    return URI.createURI(targetFile.toURI().toString()).makeRelativeTo(baseDir.toURI());
}

步骤2:配置ResourceSet的URI转换器

给ResourceSet添加URI规范化配置,确保路径解析和生成的一致性:

ResourceSet rs = new ResourceSetImpl();
// 启用URI规范化,自动处理路径冗余
rs.getURIConverter().getOptions().put(URIConverter.OPTION_NORMALIZE, true);

步骤3:保存模型时强制使用相对路径

保存trace模型时,添加OPTION_USE_RELATIVE_LINKS选项,让EMF生成相对路径引用:

// 构建保存配置
Map<String, Object> saveOptions = new HashMap<>();
saveOptions.put(XMLResource.OPTION_USE_RELATIVE_LINKS, true);
saveOptions.put(XMLResource.OPTION_FORMATTED, true); // 可选,格式化输出XML

// 应用配置保存模型
traceOutModel.getResource().save(saveOptions);
outModel.getResource().save(saveOptions);
inModel.getResource().save(saveOptions);

完整修正后的关键代码片段

public class RunTestTransformation {

    public static File baseDir;

    static {
        try {
            baseDir = new File(".").getCanonicalFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static URI resourceURI(String relativePath) {
        File targetFile = new File(baseDir, relativePath);
        return URI.createURI(targetFile.toURI().toString()).makeRelativeTo(baseDir.toURI());
    }

    public static void main(String[] args) throws IOException {

        Map<String, Object> map = Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap();
        map.put("xmi", new XMIResourceFactoryImpl());
        map.put("ecore", new EcoreResourceFactoryImpl());
        map.put("emftvm", new EMFTVMResourceFactoryImpl());

        ExecEnv env = EmftvmFactory.eINSTANCE.createExecEnv();
        ResourceSet rs = new ResourceSetImpl();
        // 配置URI转换器,启用路径规范化
        rs.getURIConverter().getOptions().put(URIConverter.OPTION_NORMALIZE, true);

        // 注册元模型,调整相对路径格式
        EPackage FamiliesPkg = (EPackage) rs.getResource(resourceURI("Test/Families.ecore"), true).getContents().get(0);
        EPackage.Registry.INSTANCE.put(FamiliesPkg.getNsURI(), FamiliesPkg);
        EPackage PersonsPkg = (EPackage) rs.getResource(resourceURI("Test/Persons.ecore"), true).getContents().get(0);
        EPackage.Registry.INSTANCE.put(PersonsPkg.getNsURI(), PersonsPkg);

        // 加载元模型到执行环境
        Metamodel familiesMetaModel = EmftvmFactory.eINSTANCE.createMetamodel();
        familiesMetaModel.setResource(rs.getResource(resourceURI("Test/Families.ecore"), true));
        env.registerMetaModel("Families", familiesMetaModel);

        Metamodel personsMetaModel = EmftvmFactory.eINSTANCE.createMetamodel();
        personsMetaModel.setResource(rs.getResource(resourceURI("Test/Persons.ecore"), true));
        env.registerMetaModel("Persons", personsMetaModel);

        // 调整相对路径,去掉开头的/../冗余片段
        String relativeInputPath = "Test/sample-Families.xmi";
        String relativeTracePath = "Test/traces.xmi";
        String relativeOutputPath = "Test/persons.xmi";

        // 加载输入模型
        URI inputUri = resourceURI(relativeInputPath);
        Model inModel = EmftvmFactory.eINSTANCE.createModel();
        inModel.setResource(rs.getResource(inputUri, true));
        env.registerInputModel("IN", inModel);

        // 初始化跟踪模型
        URI uriTrace = resourceURI(relativeTracePath);
        Model traceOutModel = EmftvmFactory.eINSTANCE.createModel();
        traceOutModel.setResource(rs.createResource(uriTrace));
        env.registerOutputModel("trace", traceOutModel);

        // 初始化输出模型
        URI uriOut = resourceURI(relativeOutputPath);
        Model outModel = EmftvmFactory.eINSTANCE.createModel();
        outModel.setResource(rs.createResource(uriOut));
        env.registerOutputModel("OUT", outModel);

        // 加载并运行转换模块
        ModuleResolver mr = new DefaultModuleResolver("Test/emftvm/", rs);
        TimingData td = new TimingData();
        env.loadModule(mr, "Families2Persons");
        td.finishLoading();
        env.run(td);
        td.finish();

        // 保存模型,使用相对路径配置
        Map<String, Object> saveOptions = new HashMap<>();
        saveOptions.put(XMLResource.OPTION_USE_RELATIVE_LINKS, true);
        saveOptions.put(XMLResource.OPTION_FORMATTED, true);

        inModel.getResource().save(saveOptions);
        traceOutModel.getResource().save(saveOptions);
        outModel.getResource().save(saveOptions);
    }
}

原理说明

  1. 基准目录规范化:getCanonicalFile()会将当前目录转换为标准路径,自动去除./、../这类冗余片段,确保所有资源路径基于统一基准。
  2. 相对URI生成:makeRelativeTo()方法将绝对URI转换为相对于基准目录的相对URI,为后续生成相对引用打下基础。
  3. 保存选项控制:OPTION_USE_RELATIVE_LINKS是核心配置,告诉EMF在保存资源时,优先使用相对路径引用其他关联资源,最终生成与Eclipse运行一致的跟踪模型。

内容的提问来源于stack exchange,提问作者James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 04:57:33