Eclipse EMF教程(下)

翻译自:https://eclipsesource.com/blogs/tutorials/emf-tutorial/
在接下来的部分中,我们将探索我们生成的代码的EMF API。

EMF API

在教程的这一部分,我们将探索EMF的API,包括生成的代码,以及EMF的实用程序类。让我们先看看生成的代码。

在我们的教程org.eclipse.example.bowling的模型插件中,您将找到所有模型实体的接口和实现。查看实体接口的轮廓可以发现,它包含我们在模型中定义的属性的getter和setters以及引用的getter。生成的EMF模型的所有实体都是EObject的子类。EObject包含基本功能–例如,更改通知机制。
Eclipse EMF教程(下)-LMLPHP
模型插件包含创建模型元素实体的工厂。请注意,EObjects的构造函数通常不是公共的。还请注意,工厂被许多框架用于其功能,例如反序列化。成功地改变这些方法需要一些仔细的计划。让我们使用工厂以编程方式创建一些实体,并使用它们的API来修改它们。我们将使用预先生成的测试插件来运行这个示例代码。如果您打开插件org . eclipse . example . bowling model . test,您会发现为模型的所有实体生成了一个测试类。通过添加以“test”开头的方法,您可以创建单个测试用例。测试用例可以通过右击测试class =>“Debug As“=>“JUnit Test“来启动。请注意,我们不会真正“测试”我们的模型。在这种情况下,测试用例只是探索和使用生成类的API的一种非常简单的方式。

在这个非常简单的例子中,我们将使用BowlingFactory创建一场比赛和一场游戏,添加对比赛的引用并检查游戏的双向更新。

public void testMatchupGameRef() {
   Matchup matchup = BowlingFactory.eINSTANCE.createMatchup();
   Game game = BowlingFactory.eINSTANCE.createGame();
   matchup.getGames().add(game);
   assertEquals(game.getMatchup(), matchup);
}

超类EObjects提供了许多以更通用的方式访问实体的方法。例如,我们将通过访问EContainer而不是getMatchup()方法来测试Matchup和Game之间的包含关系。

public void testMatchupGameRef() {
   Matchup matchup = BowlingFactory.eINSTANCE.createMatchup();
   Game game = BowlingFactory.eINSTANCE.createGame();
   matchup.getGames().add(game);
   assertEquals(game.eContainer(), matchup);
}

EObjects使用方法eSet()和eGet()提供对其属性的反射访问。这在以通用方式修改实体时非常有用。

public void testReflection() {
   EObject eObject = BowlingFactory.eINSTANCE.createPlayer();
   eObject.eSet(BowlingPackage.eINSTANCE.getPlayer_Name(), "Jonas");
   Player player = (Player) eObject;
   assertEquals("Jonas", player.getName());
}

关于可用电子属性和电子引用的信息,以及我们之前建模的所有其他概念,都可以通过EClass或EPackage访问。以下测试检查联赛的EReference的重数是否大于1。

public void testReflectiveInformation() {
   League league = BowlingFactory.eINSTANCE.createLeague();
   assertTrue(league.eClass().getEAllReferences().get(0).isMany());
   assertTrue(BowlingPackage.eINSTANCE.getLeague_Players().isMany());
}

EMF还支持模型实例的验证。例如,我们可以验证模型的约束,即一场比赛必须总是由两场比赛组成。

public void testValidation() {
   Matchup matchup = BowlingFactory.eINSTANCE.createMatchup();
   matchup.getGames().add(BowlingFactory.eINSTANCE.createGame());
   Diagnostic validate = Diagnostician.INSTANCE.validate(matchup);
   assertEquals(Diagnostic.ERROR, validate.getSeverity());
}

最后,EMF提供了许多实用程序类。一个非常重要的例子是EcoreUtil。浏览EcoreUtil的可用方法是值得的。我们将使用copy方法创建一个对象的副本。

public void testCopy() {
   Player player = BowlingFactory.eINSTANCE.createPlayer();
   player.setName("Jonas");
   Player copy = EcoreUtil.copy(player);
   assertNotSame(player, copy);
   assertEquals(player.getName(), copy.getName());
}

导入中间示例解决方案在我们继续本教程之前,请导入中间示例解决方案,该解决方案可以下载从我们的网站.

切换到一个空工作区(文件→切换工作区)并选择“导入”→“常规”→“将现有项目导入工作区”。选择“exampleSolution2.zip”并导入所有项目。

Eclipse EMF教程(下)-LMLPHP

适配器工厂

对于本教程的下一部分,理解AdapterFactories的概念非常重要。我们将进行基本介绍。中还介绍了更高级的概念这篇博文.

AdapterFactories的基本功能是为您提供特定用途所需的接口,例如UI中所需的ILabelProvider。EMF为您生成了很多这样的类。要检索正确的类,可以使用所需接口的AdapterFactory实现,例如AdapterFactoryLabelProvider。AdapterFactoryLabelProvider将使用AdapterFactory为所有EObjects检索生成的LabelProvider。

Eclipse EMF教程(下)-LMLPHP

EMF数据管理

在前面的章节中,我们已经展示了如何使用EMF生成结构化数据模型。在典型的应用程序中,必须存储这些数据模型,并且很可能还要对其进行版本控制和分发。有几个框架支持不同的用例。

默认情况下,EMF提供了将EObjects序列化为XMI文件的能力。在下面的例子中,我们将从一个文件中加载EObjects,然后保存它们。EMF还提供了修改模型的命令。命令很容易撤销。在本例中,我们将加载一个包含锦标赛的XMI文件。我们可以在锦标赛中添加新的比赛并撤销这些更改。完成后,我们可以将更改保存回文件。

对于本教程,我们在插件org . eclipse . example . bowling . tutorial中准备了一个示例对话框,该对话框已从示例解决方案中导入。您可以通过右键单击包含保龄球模型实例的文件并选择“教程”→“打开锦标赛示例对话框”来打开此对话框。在实现教程的下两个部分后,它将如下所示:
Eclipse EMF教程(下)-LMLPHP
在子类ExampleTournamentDialog中,本教程中将实现空的方法存根。这里需要注意的是,在本教程中,我们关注的是简单而不是完美的设计。此外,与本教程无关的所有内容都在一个名为AbstractTournamentExampleDialog的抽象基类中实现。

现在您需要打开ExampleTournamentDialog类。我们将实现loadContent方法,该方法通过打开示例视图来触发。此方法的目的是从文件中获取锦标赛,然后在示例视图中显示。为了简单起见,我们假设文件包含一个锦标赛,这个锦标赛是文件中的第一个元素。您可以使用生成的示例编辑器轻松创建这样的文件。

首先,我们创建一个编辑域。编辑域管理一组相互关联的模型以及运行来修改它们的命令。例如,它包含所有以前命令的堆栈。编辑域可以创建一个资源,该资源是用于存储对象的容器。可以保存和加载资源,并向其中添加内容。在这个例子中,我们获得了资源中的第一个EObject,假设它是一个锦标赛(Tournament ),并使它成为我们超类的成员。

@Override
protected void loadContent(IFile file) throws IOException {
  // Load Tournament from file and set it with setTournament
  AdapterFactoryEditingDomain domain = new AdapterFactoryEditingDomain(
   getAdapterFactory(),
   new BasicCommandStack());
  resource = domain.createResource(file.getFullPath().toString());
  resource.load(null);
  EObject eObject = resource.getContents().get(0);
  setTournament((Tournament) eObject);
}

加载内容后,我们将实现保存。这将通过在对话框中按OK来触发,并将序列化模型并将所有更改应用到文件。

@Override
protected void save() throws IOException {
   // save changes in the file
   resource.save(null);
}

现在我们想在锦标赛中增加一场比赛。为此,我们将使用一个命令。首先,我们使用适当的工厂创建一场比赛。按照惯例,工厂与模型的基础包同名。然后我们创建一个命令,将新创建的比赛添加到在上一步中从资源加载的锦标赛中。最后,我们在编辑域的命令堆栈上运行命令。

@Override
protected void addMatchup() {
 // add a new Matchup using a Command
 Matchup matchup = BowlingFactory.eINSTANCE.createMatchup();
 EditingDomain editingDomain = AdapterFactoryEditingDomain
.getEditingDomainFor(getTournament());
 Command command = AddCommand.create(editingDomain, getTournament(),
   BowlingPackage.eINSTANCE.getTournament_Matchups(),
   matchup);
 editingDomain.getCommandStack().execute(command);
}

此时,这些更改不会反映在对话框的用户界面中,但是我们将在教程的下一部分实现代码。

下一步是实现撤销。要撤消上一个命令,您只需在编辑域的命令堆栈上调用undo。

@Override
protected void undo() {
  // Undo the last change
  AdapterFactoryEditingDomain
    .getEditingDomainFor(getTournament())
    .getCommandStack().undo();
}

现在,启动保龄球应用程序并使用示例编辑器创建一个XMI文件。它应该包含一场锦标赛和几场比赛。右键单击该文件并选择“教程”→“打开示例锦标赛视图”。在此视图中,您可以添加新的锦标赛、撤销此操作并点击“确定”进行保存。您可以通过在Ecore编辑器中打开文件来验证结果。请再次注意,视图的UI还没有更新,但是我们将在教程的下一步初始化UI。

EMF监听器

在本节中,我们将把标签绑定到模型的顶部,显示已开启锦标赛中的比赛数量。每当比赛数量发生变化时,我们将使用通知机制来更新标签。其次,我们将在TreeViewer中填充比赛列表,并显示他们作为孩子的游戏。为了更新标签,我们将在视图中打开的锦标赛对象上注册一个侦听器。如果锦标赛对象发生变化,EMF运行时将始终通知该监听器。

@Override
protected void initializeListener() {
  // initialize a listener for the Label displaying the number of Matchups
  numberOfMatchupListener = new NumberofMatchupListener();
  getTournament().eAdapters().add(numberOfMatchupListener);//添加监听器
}

第二步,我们将实现侦听器本身。在notify方法中,我们首先检查更改是否是对比赛的引用,并因此影响了比赛的数量。如果是这种情况,我们通过updateNumberOfMatchups方法(在AbstractTournamentExampleView中实现)更新标签。

private final class NumberofMatchupListener extends AdapterImpl {
  // Implement a listener to update the Label. Call updateNumberOfMatchups
  @Override
  public void notifyChanged(Notification msg) {
    if (msg.getFeature() != null && msg.getFeature().equals(
      BowlingPackage.eINSTANCE.getTournament_Matchups())) {
      updateNumberOfMatchups();    }
    super.notifyChanged(msg);//通知监听器
  }
}

这就是手动实现侦听器的方式。为了在UI元素和数据模型之间创建双向更新的UI,我们建议使用EMF已经可用的数据绑定。在Eclipse数据绑定中,您可以将某个UI元素绑定到某个EAttribute或EReference,它将负责双向更新。

树查看器

接下来,我们将初始化TreeViewer以显示当前锦标赛的比赛及其子比赛。TreeViewer需要初始化三个东西:ContentProvider、LabelProvider和一个输入。ContentProvider通过提供getChildren()方法来定义树的结构。调用LabelProvider来获取要为一个节点显示的图标和文本。TreeViewer的输入是树的不可见根元素。显示在树根中的元素是该元素的子元素。在我们的例子中,输入是锦标赛。

Eclipse EMF教程(下)-LMLPHP
ContentProvider尤其是LabelProvider通常依赖于某个EClass。EMF为多种目的生成提供者,包括内容提供者和标签提供者。我们将使用前面解释的AdapterFactory概念来检索每个元素的正确提供者。最后,我们设置当前打开的锦标赛的输入。

@Override
protected void initializeTreeviewer(TreeViewer treeViewer) {
  // initialize a TreeViewer to show the Matchups
  // and Games of the opened Tournament
  AdapterFactoryLabelProvider labelProvider =
new AdapterFactoryLabelProvider(
getAdapterFactory());
  AdapterFactoryContentProvider contentProvider =
new AdapterFactoryContentProvider(
getAdapterFactory());
  treeViewer.setLabelProvider(labelProvider);
  treeViewer.setContentProvider(contentProvider);
  treeViewer.setInput(getTournament());
}

为了测试刚刚实现的UI特性,您需要重新启动bowling示例应用程序。要修改标签的外观,只需修改相应类的ItemProvider。让我们为比赛修改LabelProvider。若要修改EObjects在TreeViewer中的外观,可以调整生成的item provider MatchupItemProvider。我们将在示例中显示一场比赛中包含的游戏数量。将该方法标记为“未生成”,以防止它在下一次生成时被覆盖。

/**
* This returns the label text for the adapted class.
*
* @generated NOT
*/
@Override
public String getText(Object object) {
   if (object instanceof Matchup) {
   EList games = ((Matchup) object).getGames();
   if (games != null) {
   return "Matchup, Games: " + games.size();
   }
   }
   return getString("_UI_Matchup_type");
}

在运行的应用程序中,新的LabelProvider显示在锦标赛示例视图和Ecore编辑器中:
Eclipse EMF教程(下)-LMLPHP

最后一步,您应该在关闭视图时删除所有侦听器。请注意,LabelProvider和ContentProvider是模型上的注册侦听器,因此您也应该删除它们。

-----------------------------------------------------The End---------------------------------------

04-04 11:29