Xtext QuickFix触发ConcurrentModificationException问题求助
嘿,这个问题我太熟了——在Xtext里写QuickFix时碰ConcurrentModificationException简直是家常便饭,结合你给出的异常栈和@Fix(DslValidator.INVALID_NORMALISED_FLOW)这个修复方法的场景,我来给你拆解下原因和解决办法:
你遇到的这个异常,本质上是在遍历语义模型集合的同时,对该集合进行了增删修改。Xtext底层依赖EMF,而EMF的很多集合实现直接复用了Java的普通集合(比如ArrayList),这类集合不支持“遍历+修改”的并发操作——当迭代器正在遍历集合时,集合结构被修改,就会抛出这个异常。另外,QuickFix的执行上下文本身可能持有模型的迭代器,你的修改操作打断了Xtext内部的遍历流程,也会触发这个问题。
根据我踩过的坑,给你几个靠谱的解决方向:
1. 先复制集合再遍历
这是最直接的临时解决方案:把要遍历的集合先复制一份,遍历副本而不是原集合,这样修改原集合就不会影响遍历过程。示例代码:
@Fix(DslValidator.INVALID_NORMALISED_FLOW) def void fixNormalised_Param(Acceptor acceptor, Issue issue) { val targetContainer = issue.getEObject(DslPackage.Literals.FLOW_CONTAINER) // 复制集合到新列表 val elementsCopy = new ArrayList<>(targetContainer.getElements()) for (elem : elementsCopy) { // 这里可以安全地修改原集合,比如删除不符合要求的元素 if (elem.isInvalid()) { targetContainer.getElements().remove(elem) } } }
2. 用EMF Command框架封装修改
Xtext基于EMF的事务系统,用Command来封装修改操作是更规范的做法,EMF会自动处理并发和事务一致性问题,从根源避免这个异常。示例代码:
@Fix(DslValidator.INVALID_NORMALISED_FLOW) def void fixNormalised_Param(Acceptor acceptor, Issue issue, Injector injector) { val model = issue.getEObject(DslPackage.Literals.MY_MODEL) // 获取EMF的Command服务 val commandService = injector.getInstance(ICommandService.class) // 创建要执行的修改命令(比如添加一个新元素) val addCommand = AddCommand.create( model, DslPackage.Literals.MY_MODEL__NORMALISED_PARAMS, createValidParam() ) // 执行命令 commandService.execute(addCommand) }
3. 收集修改操作,延迟执行
如果必须在遍历中判断修改,可以先把要执行的操作(比如要删除的元素、要添加的元素)收集到一个临时列表里,等遍历完成后统一执行:
@Fix(DslValidator.INVALID_NORMALISED_FLOW) def void fixNormalised_Param(Acceptor acceptor, Issue issue) { val targetContainer = issue.getEObject(DslPackage.Literals.FLOW_CONTAINER) val toRemove = new ArrayList<FlowElement>() // 遍历原集合,收集需要删除的元素 for (elem : targetContainer.getElements()) { if (!isNormalised(elem)) { toRemove.add(elem) } } // 遍历完成后统一修改原集合 targetContainer.getElements().removeAll(toRemove) // 也可以统一添加新元素 targetContainer.getElements().addAll(createNormalisedElements()) }
4. 用Workspace事务封装修改
如果你的修改涉及到多个模型资源,或者需要确保操作的原子性,可以把修改逻辑放到IWorkspaceRunnable里,让Eclipse Workspace来管理事务:
@Fix(DslValidator.INVALID_NORMALISED_FLOW) def void fixNormalised_Param(Acceptor acceptor, Issue issue) { val model = issue.getEObject(DslPackage.Literals.MY_MODEL) val workspace = ResourcesPlugin.getWorkspace() try { workspace.run(monitor -> { // 这里写你的模型修改逻辑,比如调整参数、删除无效元素 model.setNormalised(true) }, null) } catch (CoreException e) { // 处理异常 e.printStackTrace() } }
修改完代码后,记得多测试几种场景:比如一次性触发多个该类型的错误、同时执行多个QuickFix,确保异常不再出现,并且模型修改符合预期。
内容的提问来源于stack exchange,提问作者Johan

