DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> XML學習教程 >> XML詳解 >> 從 XMLBeans 接收事件
從 XMLBeans 接收事件
編輯:XML詳解     

XML 模式描述 XML 的類型、元素和結構。Simple API for XML(SAX)、Document Object Model(DOM)和 XML Object Model(XOM)等一般性工具很難簡便地使用這些信息。XMLBeans 這種數據綁定框架可以從 XML 模式創建 POJO,允許快速地讀取、操作和寫 XML。

  在處理 POJO 時,尤其是胖客戶機,需要知道對象什麼時候發生變化 — 通常稱為事件(eventing) 或通知(notification)。事件是 Model VIEw Controller(MVC)和 Model 2 模式的核心組件,這些模式的主要意圖是在圖形用戶界面(GUI)代碼和模型代碼之間形成松散耦合。我們將以 Sudoku 游戲為例,講解如何用 XMLBeans 擴展在基於 XML 的 Java 應用程序中添加事件功能。

  Sudoku 是一個 9 乘 9 的網格,它劃分為 9 個 3 乘 3 的子網格。在游戲開始時,一些單元格已經填上了 1 到 9 的數字。玩家的任務是根據以下限制填寫余下的單元格:

  所有單元格必須包含 1 到 9 的數字。這由 XMLBeans 生成的代碼處理。

  每行中的所有 9 個單元格必須是惟一的。

  每列中的所有 9 個單元格必須是惟一的。

  每個子網格中的所有 9 個單元格必須是惟一的。

  圖 1 給出 Sudoku 的游戲板。我們開發了一個應用程序,它使用 XMLBeans 事件功能檢驗是否滿足限制 2-4。如果不滿足某個限制,就更新視圖,把與限制沖突的單元格顯示為紅色。

  圖 1. Sudoku 游戲板示例

從 XMLBeans 接收事件

  關於 Sudoku 的歷史和策略的更多信息參見 參考資料。

  過程流概述

  Observer 設計模式可以把更改通知給對此感興趣的對象。本文中描述的 XMLBeans 事件代碼是這種設計模式的實例。這種模式的思想是,允許對象進行注冊,請求在運行時獲得 XMLObject 中模型更改的通知。按照這種方式,就可以在 Sudoku 單元格上連接監聽器,對每個更改進行檢驗。

  XMLBeans 允許使用一個配置文件(也稱為 XSDCONFIG 文件)定制代碼生成過程。XSDCONFIG 文件包含以下方面的 XML 配置:

  Java 包的名稱空間映射

  Java 類的類型和元素 QName 映射

  在生成的代碼中添加方法

  在模型更改之前和之後添加實現

  如果在編譯時知道對更改感興趣的對象,第 4 項 就支持為它們生成事件。但糟糕的是,通常不會提前知道這些對象。關於 XSDCONFIG 文件內容的更多信息參見 參考資料。

  為了啟用事件,需要實現 第 3 項 和 第 4 項。這需要完成以下四個步驟:

  編寫一個接口擴展,從而允許向 XMLObject 注冊監聽器。

  編寫一個 PrePostSet 擴展,它把模型更改通知監聽器。

  創建一個包含必要配置信息的 XSDCONFIG 文件。

  把關於 XSDCONFIG 文件的信息告訴 XMLBeans 實用程序 scomp。

  編寫一個接口擴展

  XMLBeans 的接口擴展特性允許在生成的 POJO 中添加具有定制實現的方法。XMLBeans 要求創建一個接口,並為這個接口中的每個方法提供靜態的實現。我們創建的接口放在 com.ibm.wsrc.XMLbeans.IModelChangeEmitter 中,它包含四個方法,見 清單 1。

  清單 1. com.ibm.wsrc.XMLbeans.IModelChangeEmitter.Java

public interface IModelChangeEmitter {
 public void fireModelChangeEvent(ModelChangeEvent event);
 public void addModelChangeListener(IModelChangeListener modelChangeListener);
 public void removeModelChangeListener(IModelChangeListener modelChangeListener);
 public boolean hasModelChangeListeners();
}

  這個接口有兩個生命周期管理監聽器方法, addModelChangeListener() 和 removeModelChangeListener();一個監聽器檢測,hasModelChangeListeners(),以及一個實用程序方法,fireModelChangeEvent(),這個方法用來把模型更改通知注冊的所有監聽器。

  當任何過程調用這些方法之一時,XMLBeans 把調用這個方法的 XmlObject 和參數傳遞給一個靜態實現。在這個示例中,靜態實現放在 com.ibm.xmlbeans.eventing.ModelChangeEmitterHandler.java 中。因為這是一個靜態實現,而且不能在 XMLObject 中存儲實例變量,所以需要在別處存儲對更改感興趣的對象的列表。在這個實現中,列表存儲在一個靜態 HashMap 中。清單 2 給出這個類中的 Java 代碼內容。

  清單 2. com.ibm.wsrc.XMLbeans.ModelChangeEmitterHandler.Java

public class ModelChangeEmitterHandler {
 private static HashMap<XMLObject, LinkedList<IModelChangeListener>> listeners =
  new HashMap<XMLObject, LinkedList<IModelChangeListener>>();
 public static void fireModelChangeEvent(XMLObject xo, ModelChangeEvent event) {
  LinkedList<IModelChangeListener> list = listeners.get(xo);
  if (list == null)
   return;
  for (IModelChangeListener listener : list)
   listener.modelChange(event);
 }
 public static void addModelChangeListener(XMLObject xo,
  IModelChangeListener modelChangeListener) {
  LinkedList<IModelChangeListener> list = listeners.get(xo);
  if (list == null) {
   list = new LinkedList<IModelChangeListener>();
   listeners.put(xo, list);
  }
  list.add(modelChangeListener);
 }
 public static void removeModelChangeListener(XMLObject xo,
  IModelChangeListener modelChangeListener) {
  LinkedList<IModelChangeListener> list = listeners.get(xo);
  if (list == null)
   return;
  list.remove(modelChangeListener);
  // remove references of list and XMLObject
  if (list.isEmpty())
   listeners.remove(xo);
 }
 public static boolean hasModelChangeListeners(XMLObject xo) {
  return listeners.containsKey(xo);
 }
}

  addModelChangeListener() 方法很好地演示了如何使用監聽器列表。addModelChangeListener() 方法首先檢查是否已經為這個 XMLObject 注冊了監聽器列表,如果這個列表不存在,就創建它。最後,在這個列表中添加監聽器。

  編寫 PrePostSet 擴展

  PrePostSet 擴展使我們能夠在 XMLBeans 中添加定制的處理。我們使用一個靜態類,其中包含在 XMLObject 更改之前和之後運行的方法,見 清單 3。

  清單 3. PrePostSet 擴展偽代碼

if (PrePostSetExtension.preSet())
 updateXMLObject();
PrePostSetExtension.postSet();

  靜態方法 preSet() 和 postSet() 具有相同的簽名:*Set(int opType, XMLObject xo, QName propertyName, boolean isAttr, int index)。初看上去,postSet() 方法似乎可以包含所有事件代碼。但是,postSet() 方法並不掌握觸發事件所需的所有信息。postSet() 需要向注冊的所有監聽器觸發一個事件,事件包含更新的對象、更新的操作、更新之前的值和目前的值。在 postSet() 方法中不知道以前的值。因此,需要靜態地存儲以前的對象內容。

  以前的值存儲在靜態 HashMap oldValues 中。在 preSet() 方法中,把原來的值復制到這個 HashMap 中;在 postSet() 方法中,執行實際的事件觸發操作。清單 4 給出 preSet() 方法的代碼。

  清單 4. com.ibm.XMLbeans.eventing.ModelChangePrePostHandler.preSet()

public static boolean preSet(int opType, XMLObject xo, QName propertyName,
 boolean isAttr, int index) {
 IModelChangeEmitter emitter = (IModelChangeEmitter)xo;
 if (!emitter.hasModelChangeListeners())
  return true;
 // get the child that will be changed
 XMLObject oldXO = null;
 if (isAttr)
  oldXO = xo.selectAttribute(propertyName);
 else {
  switch (opType) {
   case PrePostExtension.OperaTION_SET:
   case PrePostExtension.OperaTION_REMOVE:
    oldXO = getChildXO(xo, propertyName, index);
    break;
   case PrePostExtension.OperaTION_INSERT:
    break;
  }
 }
 // store the old value
 int hash = hashCode(xo, propertyName, isAttr, index);
 Object oldValue = null;
 if (oldXO == null)
  oldValue = null;
 else if (oldXO instanceof SimpleValue) {
  oldValue = ((SimpleValue)oldXO).getObjectValue();
  if (oldValue instanceof XMLObject)
   oldValue = ((XMLObject)oldValue).copy();
  } else
   oldValue = oldXO.copy();
 oldValues.put(hash, oldValue);
 return true;
}

  在這個方法中,代碼檢查模型對象上是否連接了任何監聽器。如果有監聽器,就緩存當前的屬性值。如果它不是屬性,就調用實用程序方法 getChildXO()。最後,如果它是非常量 XMLObject,就復制它的值。然後,把副本或原來的值存儲在 oldValues HashMap 中。清單 5 給出 postSet() 方法的代碼。

  清單 5. com.ibm.XMLbeans.eventing.ModelChangePrePostHandler.postSet()

public static boolean postSet(int opType, XMLObject xo, QName propertyName,
 boolean isAttr, int index) {
 IModelChangeEmitter emitter = (IModelChangeEmitter)xo;
 if (!emitter.hasModelChangeListeners())
  return true;
 // get the old value;
 int hash = hashCode(xo, propertyName, isAttr, index);
 Object oldValue = oldValues.get(hash);
 oldValues.remove(hash);
 ModelChangeEvent.Action action = ModelChangeEvent.Action.UPDATE;
 // get the new XML object
 XMLObject newXO = null;
 if (isAttr)
  newXO = xo.selectAttribute(propertyName);
 else {
  switch (opType) {
   case PrePostExtension.OperaTION_SET:
    newXO = getChildXO(xo, propertyName, index);
    break;
   case PrePostExtension.OperaTION_INSERT:
    XMLObject[] children = xo.selectChildren(propertyName);
    // on an insert, it is always the last element
    newXO = children[children.length - 1];
    action = ModelChangeEvent.Action.CREATE;
    break;
   case PrePostExtension.OperaTION_REMOVE:
    action = ModelChangeEvent.Action.DELETE;
    break;
  }
 }
 // get the new value
 Object newValue = null;
 if (newXO == null)
  newValue = null;
 else if (newXO instanceof SimpleValue)
  newValue = ((SimpleValue)newXO).getObjectValue();
 else
  newValue = newXO;
 // if newValue != oldValue
 if (((newValue == null) && (oldValue != null)) || ((newValue != null)
  && (!newValue.equals(oldValue))))
  emitter.fireModelChangeEvent(new ModelChangeEvent(propertyName,
   emitter, oldValue, newValue, action, index));
 return true;
}

  postSet() 方法首先檢查是否有任何監聽器連接這個 XmlObject。如果有,代碼就從 oldValues HashMap 獲取原來的值。它從當前的 XmlObject 獲取當前值並賦值給 newValue 變量。最後,postSet() 檢查 oldValue,確定它與 newValue 不相等。XMLObject 創建並觸發一個模型更改事件。清單 6 給出實用程序方法。

  清單 6. com.ibm.XMLbeans.eventing.ModelChangePrePostHandler 實用程序方法

private static XmlObject getChildXO(XMLObject xo, QName propertyName, int index) {
 XMLObject childXO = null;
 XMLObject[] values = xo.selectChildren(propertyName);
 if (values.length != 0) {
  if (index == -1)
   childXO = values[0];
  else if (index < values.length)
   childXO = values[index];
 }
 return childXO;
}
private static int hashCode(XMLObject xo, QName propertyName, boolean isAttr, int index) {
 final int prime = 31;
 int result = 1;
 result = prime * result + index;
 result = prime * result + (isAttr ? 1231 : 1237);
 result = prime * result + ((propertyName == null) ? 0 : propertyName.hashCode());
 result = prime * result + ((xo == null) ? 0 : xo.hashCode());
 return result;
}

  getChildXO() 方法從 XmlObject 獲取一個屬性值。如果返回多個值,就使用 selectChildren() 方法並獲取適當的子元素。hashCode() 方法為 XmlObject 和 XMLObject 的屬性計算一個惟一的散列值;Eclipse 會自動創建這個方法。

  配置 XMLBeans 的代碼生成

  為了在 XMLBeans 上實現事件,需要使用並配置接口擴展特性和 PrePostSet 擴展特性。清單 7 給出 XSDCONFIG 文件。

  清單 7. XMLBeans 事件的 XSDCONFIG 文件

<xb:config xmlns:xb="http://xml.apache.org/XMLbeans/2004/02/xbean/config">
 <xb:extension for="*">
  <xb:interface name="com.ibm.XMLbeans.eventing.IModelChangeEmitter">
   <xb:staticHandler>
    com.ibm.XMLbeans.eventing.ModelChangeEmitterHandler
   </xb:staticHandler>
  </xb:interface>
 </xb:extension>
 <xb:extension for="*">
  <xb:prePostSet>
   <xb:staticHandler>
    com.ibm.XMLbeans.eventing.ModelChangePrePostHandler
   </xb:staticHandler>
  </xb:prePostSet>
 </xb:extension>
</xb:config>

  xb:config 的第一個 xb:extension 元素包含接口擴展的配置。在這裡,它為生成的所有 XmlObject 定義接口來擴展 IModeChangeEmitter。XMLBeans 將運行 ModelChangeEmitterHandler 中的實現。

  xb:config 的第二個 xb:extension 元素包含 PrePostSet 擴展的配置。它讓 XMLBeans 在修改屬性之前和之後分別調用 ModelChangePrePostHandler 類中的 preSet() 和 postSet()。

  最後,為了把配置文件輸入生成器,需要在 scomp 的最後一個參數中指定 XSDCONFIG 文件。對於這個 Sudoku 游戲,在一個 Java 類中指定這個參數,見 清單 8。

  清單 8. com.ibm.XMLbeans.generator.SudokuSchemaGenerator.Java

public class SudokuSchemaGenerator {
 public static void main(String[] args) {
  String[] XMLBeanArgs = new String[] {
   "-d", "xb_bin",
   "-src", "xb_src",
   "-out", "lib/sudokuXB.jar",
   "-Javasource", "1.5",
   "schema/sudokuXBE.xsd",
   "schema/eventing.xsdconfig"
  };
  org.apache.xmlbeans.impl.tool.ScheMacompiler.main(XMLBeanArgs);
 }
}

  關於 scomp 和命令行參數的更多信息參見 參考資料。

  在運行這個命令之後,現在就有了來自 sudokuXBE.xsd 的 POJO,這些 POJO 在發生更改時可以通知對更改感興趣的對象。

  創建 Sudoku 游戲 RCP 應用程序

  Sudoku 游戲是一個 RCP 應用程序,當用戶在一個單元格中填寫了不合適的值時,這個程序會通知用戶。程序會根據行、列和子網格中的其他單元格檢查值是否符合限制。如果有任何值不符合限制,就把單元格顯示為紅色,見 圖 2。

  圖 2. Sudoku 游戲

從 XMLBeans 接收事件

  按照以下步驟實現這個游戲的圖形用戶界面(UI):

  創建一個代表游戲板的 Sudoku 模式。

  從這個模式生成一組包含事件功能的 XMLBeans。

  使用生成的 XMLBeans 創建一個 MVC 風格的應用程序。

  這個應用程序的設計原則是消除視圖和模型之間的耦合。控制器是 Standard Widget Toolkit(SWT)鍵監聽器,它首先修改模型。然後,模型把更改通知檢驗器。檢驗器檢查它們控制的單元格是否是有效的;如果有效,它們設置有效標志。然後,框架觸發另一個事件,視圖(單元格)會捕捉這個事件。這會更新單元格的背景顏色。圖 3 在一個流程圖中顯示這四個步驟。

  圖 3. Sudoku RCP 應用程序的事件流

從 XMLBeans 接收事件

  在 XML 模式中對 Sudoku 游戲進行建模

  模式定義很簡單,首先建立游戲板。游戲板包含 9 行和 9 列單元格,單元格只能包含 1 到 9 的值。使用這個簡單的定義創建模式。圖 4 所示的游戲板類型由 9 個行類型的元素組成。

  圖 4. BoardType XML 模式類型

從 XMLBeans 接收事件

  然後定義行類型,見 圖 5。這樣就足以定義整個 9 乘 9 的 Sudoku 網格。

  圖 5. RowType XML 模式類型

從 XMLBeans 接收事件

  行類型包含 9 個單元格類型的元素。通過把行類型劃分為 9 個單元格類型的元素,就可以定義行中每個單元格中的值,見 圖 6。

  圖 6. CellType XML 模式類型

從 XMLBeans 接收事件

  單元格類型是一個簡單的元素,其中可以包含 1 到 9 的值和點號(.),點號表示沒有值。單元格類型還有一個屬性,它根據行、列和子網格限制表示這個單元格是否有效,見 圖 7。

  圖 7. CellStringType XML 模式類型

從 XMLBeans 接收事件

  單元格字符串類型進一步定義單元格,把其中的值限制為 1 到 9 的值和點號。

  生成 XMLBeans

  通過使用這個模式定義和 配置 XMLBeans 的代碼生成 中定義的 XMLBeans 生成代碼,生成一組 POJO,它們代表這個模式並包含事件功能,當模型發生任何更改時,可以通知 UI。

  編寫應用程序 UI

  由於事件功能是生成的 XMLBeans 的組成部分,所以可以按照典型的 MVC 風格編寫 UI。模型的視圖是使用 Eclipse 平台開發的。這個 RCP 應用程序本質上是一個 Eclipse 插件,並在這個 Eclipse 插件啟動時初始化一個視圖。在創建每個 SWT 文本部件並把它添加到視圖中時,通過 text.addKeyListener() 方法調用添加一個鍵監聽器。這樣,當用戶在空單元格中輸入值時,就可以截取按鍵操作。見 清單 9。

  清單 9. com.ibm.XMLbeans.tutorial.VIEw.addKeyListeners().new KeyAdapter() {...}

public void keyPressed(KeyEvent e) {
 switch (e.keyCode) {
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
   if (text.getEditable()) {
    final CellStringType.Enum newValue =
     CellStringType.Enum.forString(String.valueOf(e.character));
    Job job = new Job("Run Validation") {
     protected IStatus run(IProgressMonitor arg0) {
      XbeUtilitIEs.getCell(Activator.getDefault().getBoard(), myRow, myCol)
       .setCellValue(newValue);
      return Status.OK_STATUS;
     }
    };
    text.setText(String.valueOf(e.character));
    job.schedule();
   }
   break;
  case SWT.BS:
  case SWT.DEL:
  case '.':
  case ' ':
   if (text.getEditable()) {
    Job job = new Job("Run Validation") {
     protected IStatus run(IProgressMonitor arg0) {
      XbeUtilitIEs.getCell(Activator.getDefault().getBoard(), myRow, myCol)
       .setCellValue(CellStringType.X);
      return Status.OK_STATUS;
     }
    };
    job.schedule();
    text.setText(" ");
   }
   break;
  // ... arrow navigation code ...
 }
 e.doit = false;
}

  然後通過 setCellValue(newValue) 調用設置模型中的值。這個調用會更新 XML 模式定義的模型。XMLBeans 事件代碼觸發一個事件,通知所有監聽器模型已經更改了。

  事件模型的關鍵部分是,根據單元格在行、列和子網格中的位置,通知單元格的有效性。在 modelChange(ModelChangeEvent event) 方法中,調用 runValidation() 方法來檢驗單元格的值,見 清單 10。

  清單 10. com.ibm.XMLbeans.listeners.CellValidatorListener.modelChange(ModelChangeEvent)

public void modelChange(ModelChangeEvent event) {
 if (XbeUtilitIEs.CELLVALUE_QNAME.equals(event.getPropertyName())) {
  // if the current cell value is set
  if (!CellStringType.X.equals(cell.getCellValue())) {
   if (cell.equals(event.getSource())) {
    // run manual validation
    int count = runValidation();
    numInvalid = count;
   } else {
    // another cell
    if (cell.getCellValue().equals(CellStringType.Enum.forString(
     (String)event.getNewValue())))
     numInvalid++;
    else {
     if (cell.getCellValue().equals(CellStringType.Enum.forString(
      (String)event.getOldValue())))
      numInvalid--;
    }
   }
 } else if (cell.equals(event.getSource()))
  numInvalid = 0;
 if (numInvalid == 0)
  cell.setValid(true);
 else if (numInvalid < 0) {
  Activator.getDefault().getLog().log(
   new Status(IStatus.WARNING, Activator.PLUGIN_ID,
   "Cell validator has a negative number of invalid cells. This is impossible."));
 } else
  cell.setValid(false);
 }
}

  可以看到,首先檢查更改了哪個屬性。在 清單 10 中,它應該是 CELLVALUE_QNAME 屬性。如果這個單元格是事件源,那麼調用 runValidation() 來針對所有條件執行檢驗。但是,如果事件源是另一個單元格,那麼只需檢查值是否不等於新值。如果相等,就需要把 numInvalid 加 1。如果值等於原來的值,就需要把 numInvalid 減 1。最後,如果 numInvalid 是零,就可以通過 cell.setValid(true) 把單元格的有效標志設置為 true。否則,需要通過 cell.setValid(false) 把單元格的有效標志設置為 false。通過這些調用設置單元格的有效標志之後,XMLBeans 事件機制觸發另一個事件。視圖監聽器處理這個事件,更新單元格的背景顏色,見 清單 11。

  清單 11. com.ibm.XMLbeans.tutorial.View.addTextNotifIEr(...).new IModelChangeListener() {...}.modelChange(ModelChangeEvent)

public void modelChange(ModelChangeEvent event) {
 if (XbeUtilitIEs.VALID_QNAME.equals(event.getPropertyName())) {
  // might not be in the display thread, need to run in it to
  // modify the background!
  text.getDisplay().asyncExec(new Runnable() {
   public void run() {
    if (cell.getValid())
     text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_WHITE));
    else
     text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
   }
  });
 }
}

  可以看到,當觸發 modelChange 事件時,單元格會得到通知,因為每個單元格中都添加了 modelChangeListener()。在處理這個事件時,單元格檢查它是否有效,並適當地設置對應的文本部件的背景顏色。

  結束語

  通過使用事件,可以采用簡單的編程模型,消除用戶界面對模型的依賴性。因此,可以快速創建 Sudoku 游戲應用程序,並避免視圖和模型之間的耦合。

  可以通過使用 XMLBeans 中的兩個擴展點(接口和 PrePostSet)在 XMLBeans 中添加事件功能。使用接口擴展在 XMLBeans 中添加監聽器,使用 PrePostSet 擴展捕捉更改並通知相關對象。最後,通過一個配置文件把這些擴展集成到 XMLBeans 生成過程中。

  下載

描述 名字 大小 下載方法 XMLBeans 事件和 Sudoku 游戲 Eclipse 項目 x-XMLbeanse/SudokuProject.zip 2694KB HTTP 用 XML 描述的 Sudoku 游戲板 x-XMLbeanse/SudokuBoards.zip 4KB HTTP 針對 Windows 編譯的 Sudoku 游戲 x-XMLbeanse/SudokuApplication-Win32.zip 146346KB HTTP


XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved