EMF 是 Eclipse 組織推出的建模框架。它能夠幫助我們將模型(UML, XSD等)轉換成為健壯且功能豐富的Java 代碼。通過使用 EMF,我們編寫的程序能免費的獲得一個健壯的模型層,它通常比我們自己手工編寫的模型層更為健壯。事實上,有很多商業產品都使用了 EMF 來作為其模型層。由於 EMF 的廣泛使用,Eclipse 組織為其推出了眾多的周邊模塊。
1 介紹
由於EMF(全稱Eclipse Modeling Framework)在Java陣營中的廣泛使用,用戶迫切的需要更多基於EMF的功能。因而,Eclipse組織為其推出了眾多的周邊模塊。例如目前已經較為成熟的GEF(Graphical Editing Framework)和GMF (Graphical Modeling Framework)就能幫助用戶開發基於EMF的圖形編輯器。事實上,基於EMF的新技術遠不止GEF和GMF。EMFT (Eclipse Modeling Framework Technology) 是Eclipse專門用來發展基於EMF的新技術的專門項目。今天我們將要介紹的EMF Query就是EMFT的一個子組件。我們可以使用它來對EMF模型進行查詢,從而降低了處理復雜模型的難度。
2 建立Library模型
在介紹EMF的文章中,最常用的例子是Library樣例。Library模型的UML圖如下所示: (本文中用到的ecore model和源代碼在附件emfquery.zip中)
圖 1 Library模型
正如我們所看到的,Library例子相當簡單,它僅僅包含三個類:Library, Writer, Book,以及一個BookCategory枚舉類型。在使用EMF時,我們首先需要獲得一個ECore模型,這個Ecore模型將用於定義用戶模型(例如Library模型)的metadata。我們可以從頭開始創建一個ECore模型,也可以通過別的模型導入。如果使用Rational家族中的產品進行UML建模,那麼我們能夠在Rational產品中直接將UML模型導出為ECore模型。另外,我們也可以通過創建XSD文檔或者Annotated Java文件,並且利用EMF自帶的向導轉換為ECore模型。
在獲得了Ecore模型之後,我們在Eclipse中創建一個Java項目。在"New Java Project"向導中,我們將工程的名稱設置為test.emf.query,並且我們應當選擇分離源代碼目錄和輸出目錄。我們在新建好的 test.emf.query項目中建立一個新的model目錄,並將library.ecore文件保存到這個目錄中。
為了生成模型的Java實現,我們首先需要利用EMF提供的向導將.ecore模型轉化為.genmodel模型。這可以通過如圖 2所示的"New EMF Model"向導來進行。
圖 2 使用"New EMF Model"向導建立新項目
我們將Library.genmodel生成到model目錄下,並雙擊其進行編輯。在.genmodel的編輯器中,我們選擇Library包,並修改其"Base Package"屬性為emf.model。這個屬性會影響生成的Java代碼的包名稱。
圖 3 使用"New EMF Model"向導建立新項目
接下來,我們就可以生成Library模型的Java實現了。在Library包上單擊右鍵,並在彈出菜單中選擇Generate Model Code項,在test.emf.query項目中生成Library的Java實現。經過這一步之後,我們的test.emf.query將會變成一個插件項目,這並不意味著EMF只能作為插件被使用。對於插件項目而言,我們可以更為方便的設置對插件的依賴關系。現在我們通過雙擊打開 plugin.xml,然後進入Dependencies標簽頁,並添加對org.eclipse.emf.query插件的依賴,如圖 4所示。
圖 4 添加對EMF Query的依賴
在進行完迄今為止的這些步驟之後,我們可以開始編寫代碼來測試EMF Query的功能了。
3 使用EMF Query查詢
EMF Query是一個非常容易使用的框架,Query框架將遍歷模型中的每個元素,而用戶需要做的工作是編寫一個繼承自Condition的類來判斷模型中的元素是否應該出現在查詢的結果中。在EMF Query框架中,已經提供了大量我們立刻可用的Condition子類。
圖 5 使用Hierachy視圖查看Condition類型
通常情況下,我們既可以使用已有的Condition類也可以創建新的Condition類。對於在EMF模型中進行查詢而言,我們最常用到的是EObjectCondition類及其子類。
表 1 用於EMF的一些Condition類
類名
用途
EObjectCondition
所有用於檢查EMF模型的條件類都應該繼承EObjectCondition類。這個條件類接收一個PruneHandler類的對象。PruneHandler用於判斷是否需要處理當前正在比較的EMF對象的子對象。
EObjectAttributeValueCondition
這個條件類用於判斷當前正在處理的EMF對象的屬性是否滿足特定的條件。這個條件類接收一個PruneHandler對象和另一個Condition對象。傳入的Condition對象將被用於對屬性的值進行判斷。
EObjectReferencerCondition
這個條件類用於判斷當前正在處理的EMF對象是否引用了一個特定的EMF對象。
當我們了解了一些常用的Condition之後,我們就可以開始編寫代碼來對我們的EMF模型進行查詢了。下面的代碼演示了如何利用EObjectAttributeValueCondition和EObjectReferencerCondition來進行模型的查詢。
代碼 1 查詢超過500頁的書籍
public Collection queryLargeBook(EObject root) {
SELECT select = new SELECT(new FROM(root), new WHERE(
new EObjectAttributeValueCondition(LibraryPackage.eINSTANCE
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
.getBook_Pages(), new NumberCondition.IntegerValue(
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
new Integer(500), new Integer(Integer.MAX_VALUE)))));
|-------10--------20--------30--------40--------50--------60--------70--------80--------9|
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
return select.execute();
}
在上面的代碼中,我們檢查每個Book對象的Pages屬性。對於Pages屬性的檢查是通過一個NumberCondition對象進行的。
代碼 2 查詢某個作者創作的所有書籍
public Collection queryBookByWriter(EObject root, Writer writer) {
SELECT select = new SELECT(new FROM(root), new WHERE(
new EObjectReferencerCondition(writer)));
return select.execute();
}
在上面的代碼中,我們查詢了一個作者所寫的所有書籍。除了使用已有的條件類之外,我們也可以創建新的條件類,例如,如果希望查詢所有具有三個作者的書籍,那麼我們可以編寫如下的條件類:
代碼 3 查詢擁有三個作者的所有書籍
private class ThreeWriterCondition extends EObjectCondition {
public ThreeWriterCondition() {
super(PruneHandler.NEVER);
}
@Override
public boolean isSatisfied(EObject eObject) {
if (eObject instanceof Book) {
Book book = (Book) eObject;
List writers = book.getWriter();
if (writers.size() == 3)
return true;
}
return false;
}
}
自己編寫的Condition類並沒有什麼特殊之處,其使用方法與庫中的條件類是完全一樣的:
代碼 4 使用新建的Condition類
public Collection queryBookWithThreeWriters(EObject root) {
SELECT select = new SELECT(new FROM(root), new WHERE(
new ThreeWriterCondition()));
return select.execute();
}
4 測試結果
最後,我們可以使用JUnit來測試我們的查詢代碼。由於我們的項目基於JDK 5.0開發,因而我們可以使用JUnit 4.0來進行測試。JUnit 4.0充分利用了JDK 5.0中引入的Annotation機制,在代碼編寫方面與之前的版本有著相當大的區別。當然,它還是一樣的簡單易用。下面就是我們用於測試三個 Query函數的測試方法。
代碼 5 使用JUnit 4.0測試查詢
@Test
public void testQueryLargeBook() {
Collection books = query.queryLargeBook(library);
if (books.size() < 1)
fail("No large book is found");
for (Iterator iter = books.iterator(); iter.hasNext();) {
Book element = (Book) iter.next();
if (element.getPages() < 500)
fail("Small book is found in the result");
}
}
@Test
public void testQueryBookByWriter() {
Collection books = query.queryBookByWriter(library, writer);
if (books.size() < 1)
fail("No book is found");
for (Iterator iter = books.iterator(); iter.hasNext();) {
Book book = (Book) iter.next();
if (!book.getWriter().contains(writer))
fail("Found a book which is not authored by "
+ writer.getName());
}
}
@Test
public void testQueryBookWithThreeWriters() {
Collection books = query.queryBookWithThreeWriters(library);
if (books.size() < 1)
fail("No book is found");
for (Iterator iter = books.iterator(); iter.hasNext();) {
Book book = (Book) iter.next();
if (book.getWriter().size() != 3)
fail("Found a book which has less than three authors");
}
}
在編寫測試用例時,JUnit 4.0不再強制要求我們遵守方法的命名規則。相反,只要用@Test標注過的方法,就是測試方法。另外,在測試執行之前,我們首先需要生成一個測試用的模型,這可以通過下面的代碼來完成:
代碼 6 生成測試用Library模型
@Before
public void setUp() throws Exception {
lp = LibraryPackage.eINSTANCE;
lf = LibraryFactory.eINSTANCE;
library = lf.createLibrary();
writer = lf.createWriter();
writer.setName("James Gan");
Writer writer1 = lf.createWriter();
writer1.setName("Ping Hao");
library.getWriters().add(writer1);
Writer writer2 = lf.createWriter();
writer2.setName("Qiu Qiu");
library.getWriters().add(writer2);
Book book = lf.createBook();
book.setTitle("How to Query with EMF Query");
book.setPages(1000);
book.getWriter().add(writer);
library.getBooks().add(book);
Book book1 = lf.createBook();
book1.setTitle("How to play basketball");
book1.setPages(1000);
book1.getWriter().add(writer);
book1.getWriter().add(writer1);
book1.getWriter().add(writer2);
library.getBooks().add(book1);
library.getWriters().add(writer);
query = new QueryLibrary();
}
和@Test一樣,@Before也是JUnit中的Anotation類,它表示被標注的方法將會在測試方法之前執行,用於進行測試場景的設置等工作。最後,我們運行JUnit進行測試,可以獲得如所圖 6示結果,所有方法均通過測試。
圖 6 查看測試結果
5 結論
本文通過一個簡單的例子介紹了使用EMF Query進行模型查詢的基本過程。EMF Query是EMF Technology項目的一個重要的子項目。除了本文介紹的Query組件之外,EMF Technology還包含了其他一些有趣的主題,例如OCL,Teneo,Transaction,Validation和Net4J等。對模型驅動開發感興趣的朋友不妨訪問http://www.eclipse.org/emft 已獲得更進一步的信息。
本文配套源碼