Получение уведомлений о событиях в XMLBeans

Совершенствование POJO-объектов XMLBeans с целью уведомления объектов-наблюдателей об изменениях в модели данных

XMLBeans - это отличная библиотека для связывания объектов Java™ с данными XML, но в ней не хватает возможности подписываться на уведомления об изменениях в модели. Однако этого можно добиться, изменив автоматически генерируемые простые Java-объекты (Plain Old Java Objects - POJO), в частности, добавив в них необходимые интерфейсы и функции для уведомления об изменениях. В этой статье мы продемонстрируем использование событий для проверки корректности введенных пользователем данных на примере приложения для игры в Судоку, созданном на основе платформы для разработки "толстых" клиентов (Rich Client Platform) в Eclipse.

Джейкоб Д. Эйзингер, штатный разработчик, IBM  

Photo of Jacob EisingerДжейкоб Эйзингер (Jacob Eisinger) в настоящее время занимается технологиями SOA (сервисно-ориентированная архитектура) следующего поколения в группе «Software Group Strategy, Emerging Standards» в IBM. Он является экспертом в области Web-сервисов, XML, Java, пивоварения, а также поклонником футбольной команды Hokie.



Винс К. Бранссен, разработчик-консультант, IBM  

Photo of Vince BrunssenВинс Бранссен (Vince Brunssen) возглавляет работу над прототипами, реализующими последние версии спецификаций Web-сервисов по мере их выпуска. Он является членом группы «Software Group Strategy, Emerging Standards» в IBM. В течение своей карьеры, он работал с различными технологиями, от создания пользовательских интерфейсов в Eclipse до серверных решений, использующих универсальный протокол обнаружения и интеграции (UDDI). При этом Винс играет в гольф, причем тоже на очень и очень хорошем уровне!



23.12.2009

XML-схемы описывают типы, элементы и структуры документов XML. Однако с этой информацией тяжело работать непосредственно через интерфейсы общего назначения, таких как "простой API для XML" (Simple API for XML - SAX), "объектная модель документов XML" (Document Object Model - DOM) или "объектная модель XML" (XML Object Model - XOM). Чтение, манипулирование и сохранение XML-данных значительно облегчается при использовании XMLBeans - инфраструктуры для связывания c данными, способной автоматически создавать классы POJO на основе XML-схемы.

При работе с POJO, особенно если приложение построено по принципу "толстого" клиента, важно вовремя узнавать об изменениях объектов. Часто этот процесс называют уведомлениями или подпиской на события. Работа с событиями занимает центральное место в парадигмах "модель-представление-контроллер" (Model View Controller - MVC) и Model 2, которые подчеркивают важность отделения графического интерфейса пользователя (GUI) от модели приложения. В этой статье мы расскажем о том, как добавить поддержку событий в Java-приложения, основанные на XML и использующие XMLBeans. В качестве примера будет выступать приложение для игры в Судоку.

В Судоку играют на поле 9 на 9 клеток, разделенном на 9 равных областей (3 на 3 клетки каждое). В начале игры несколько полей заполнены числами от 1 до 9. Задачей игрока является заполнение остальных полей, не нарушая при этом следующих правил:

  1. Все клетки должны быть заполнены цифрами от 1 до 9. За это отвечает код, генерируемый XMLBeans.
  2. Все 9 цифр в каждой строке должны быть разными.
  3. Все 9 цифр в каждом столбце должны быть разными.
  4. Все 9 цифр в каждой области должны быть разными.

Пример игрового поля показан на рисунке 1. Мы создали приложение, которое проверяет правила 2-4 на основе событий в XMLBeans. Если какое-то правило нарушается, то приложение показывает неправильно введенные клетки красным цветом.

Рисунок 1. Пример игрового поля Судоку
Sample Sudoku game board

Ссылки на источники информации об истории и стратегии игры в Судоку приведены в разделе Ресурсы.

Логика работы приложения

Для отправки уведомлений заинтересованным компонентам приложения служит паттерн "Наблюдатель" (Observer). Поддержка событий в XMLBeans, описываемых в данной статье, является ничем иным, как частным случаем этого паттерна проектирования. Основная идея заключается в том, чтобы дать возможность объектам подписываться на уведомления об изменении в модели приложения (экземплярах класса XmlObject) во время выполнения приложения. Таким образом, с клетками игрового поля Судоку можно ассоциировать объекты-слушатели, которые будет проверять выполнение правил при каждом изменении цифр.

В XMLBeans существует конфигурационный файл XSDCONFIG, редактируя который вы можете управлять процессом генерации кода. В данном XML-файле задаются настройки, позволяющие выполнять следующие действия:

  1. Устанавливать соответствие между пространствами имен XML и названиями пакетов в Java.
  2. Устанавливать соответствие между типами и квалифицированными именами элементов (QName) и именами Java-классов.
  3. Добавлять методы в генерируемый код.
  4. Добавлять фрагменты кода, которые будут выполняться непосредственно перед и сразу после изменения модели.

Благодаря последней возможности, можно регистрировать заинтересованные в событиях компоненты, однако только в том случае если эти компоненты известны на этапе компиляции. К сожалению, это не всегда так. За более подробной информацией о файле XSDCONFIG обратитесь к разделу Ресурсы.

Для реализации подписки на события необходимо воспользоваться возможностями 3 и 4. Для этого следует выполнить следующие шаги:

  1. Расширить интерфейс объектов, который бы позволял слушателям подписываться на события в объектах XmlObject.
  2. Расширить интерфейс PrePostSet для уведомления слушателей об изменениях в модели.
  3. Создать файл XSDCONFIG, содержащий всю необходимую конфигурационную информацию.
  4. Передать данный файл на вход утилиты scomp, входящую в поставку XMLBeans.

Расширение интерфейса объектов

Благодаря возможности расширять стандартные интерфейсы XMLBeans позволяет добавлять методы в автоматически сгенерированные объекты POJO. При создании интерфейса необходимо также создать класс с набором статических методов – по одному на каждый метод интерфейса. Наш интерфейс под именем com.ibm.wsrc.xmlbeans.IModelChangeEmitter, содержащий 4 метода, показан в листинге 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();
}

Дополнительный метод интерфейса IModelChangeEmitted

В случае необходимости в интерфейс IModelChangeEmitter можно добавить дополнительный метод, возвращающий список зарегистрированных слушателей.

Два метода интерфейса служат для добавления и удаления объектов-слушателей, реагирующих на события жизненного цикла (addModelChangeListener() и removeModelChangeListener()), метод для проверки наличия слушателя (hasModelChangeListeners()) и вспомогательный метод fireModelChangeEvent(), служащий для уведомления слушателей об изменении в модели.

В момент вызова одного из методов этого интерфейса из любого процесса, XMLBeans передает экземпляр XmlObject и параметры вызова в статическую реализацию данного метода. В нашем случае в качестве статической реализации выступает класс com.ibm.xmlbeans.eventing.ModelChangeEmitterHandler.java. Будучи статическим классом, он не может хранить список экземпляров XmlObject и зарегистрированных слушателей. Поэтому данный список хранится в статической переменной HashMap. Код данного класса приведен в листинге 2.

Листинг 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);

    // Удаление списка слушателей и ссылки на сам экземпляр XmlObject
    if (list.isEmpty())
      listeners.remove(xo);
  }

  public static boolean hasModelChangeListeners(XmlObject xo) {
    return listeners.containsKey(xo);
  }
}

Метод 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() невозможно получить доступ к значению поля объекта до изменения. Поэтому предыдущее состояние объекта должно сохраняться статически.

Предыдущие состояния сохраняются в статической переменной типа HashMap с именем oldValues. Метод preSet() копирует старое значение поля объекта в HashMap, в то время как метод postSet() генерирует само событие. Реализация метода preSet() показана в листинге 4.

Листинг 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;

  // Получение ссылки на изменяемый дочерний объект
  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;
    }
  }

  // Сохранение предыдущего значения
  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. Следующий метод (postSet()) приведен в листинге 5.

Листинг 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;

  // Получение предыдущего значения
  int hash = hashCode(xo, propertyName, isAttr, index);
  Object oldValue = oldValues.get(hash);
  oldValues.remove(hash);

  ModelChangeEvent.Action action = ModelChangeEvent.Action.UPDATE;

  // Получение нового экземпляра XmlObject
  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);

        // В случае вставки, данный экземпляр всегда последний
        newXO = children[children.length - 1];
        action = ModelChangeEvent.Action.CREATE;
        break;
      case PrePostExtension.OPERATION_REMOVE:
        action = ModelChangeEvent.Action.DELETE;
        break;
    }
  }

  // Получение нового значения
  Object newValue = null;
  if (newXO == null)
    newValue = null;
  else if (newXO instanceof SimpleValue)
    newValue = ((SimpleValue)newXO).getObjectValue();
  else
    newValue = newXO;

  // Если 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. Затем он получает текущее значение свойства и присваивает его переменной newValue. Далее, в случае, если старое и новое значения свойства не совпадают, метод postSet() генерирует событие изменения модели. Вспомогательные методы, используемые в двух последних листингах, показаны в листинге 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(), возвращающий список дочерних объектов. Если список содержит несколько объектов, то задачей getChildXO() является выбор нужного. Метод hashCode() вычисляет уникальный хэш-код для экземпляра XmlObject и его свойств. Данный метод создается автоматически при помощи Eclipse.


Конфигурирования процесса генерации объектов XMLBeans

Для поддержки механизма событий в XMLBeans необходимо правильно настроить используемые расширения интерфейсов объектов и PrePostSet. Подобное конфигурирование осуществляется в файле XSDCONFIG, как показано в листинге 7.

Листинг 7. Конфигурирование механизма событий в файле 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:extension, находящийся внутри xb:config, служит для конфигурирования расширения интерфейсов объектов. Он определяет, что интерфейс всех генерируемых экземпляров XmlObject должен быть расширением интерфейса IModeChangeEmitter. При этом реализация интерфейса объектов будет находиться в классе ModelChangeEmitterHandler.

Второй элемент xb:extension, находящийся внутри xb:config, отвечает за конфигурирование расширения PrePostSet. Таким образом, указывается, что методы preSet() и postSet() класса ModelChangePrePostHandler должны быть вызваны XMLBeans перед тем и после того, как свойство объекта будет модифицировано неким процессом.

Наконец, данный конфигурационный файл необходимо передать на вход генератора XMLBeans. Для этого служит последний параметр, принимаемый утилитой scomp. В нашем же случае генератор будет вызываться программно, как показано в листинге 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 и принимаемых ею параметрах приведены в разделе Ресурсы.

В результате запуска генератора был создан набор объектов POJO на основе схемы sudokuXBE.xsd. При этом поддерживается возможность уведомления слушателей об изменениях в POJO-объектах.


Создание приложения для игры в Судоку на основе RCP

Используя платформу RCP, мы далее создадим приложение для игры в Судоку, которое будет автоматически указывать игрокам на клетки, содержащие некорректные значения. Введенные значения будут проверяться с учетом содержимого остальных клеток в каждой строке, колонке и области игрового поля. В случае если любые две клетки будут содержать одинаковые значения, они будут закрашены красным цветом, как показано на рисунке 2.

Рисунок 2. Игра Судоку
Two screenshots of the Sudoku game in action.

Создание пользовательского интерфейса приложения заключается в следующем:

  1. Создание схемы Судоку, описывающей игровое поле.
  2. Генерация объектов XMLBeans с поддержкой механизма событий на основе созданной ранее схемы.
  3. Создание приложения по принципам MVC на основе сгенерированных объектов XMLBeans с поддержкой событий.

Главным архитектурным принципом приложения является отделение представления от модели. Приложение будет работать следующим образом: контроллер, реализованный на основе объекта SWT (Standard Widget Toolkit – стандартный инструментарий виджетов), обрабатывающего нажатие клавиш, будет модифицировать модель. В результате будут вызваны классы, реагирующие на подобные изменения. Их задачей является проверка того, что контролируемые ими клетки поля содержат корректные значения. Если это так, то они устанавливают соответствующий признак корректности. Установка флага приводит к возникновению еще одного события, которое перехватывается самими клетками (объектами слоя представления), которые обновляют свой цвет. Диаграмма, иллюстрирующая все четыре фазы данного процесса, показана на рисунке 3.

Рисунок 3. Последовательная обработка событий в приложении Судоку на основе RCP
Event flow through the Sudoku RCP application

Создание XML-схемы, моделирующей игру Судоку

Схема приложения довольно проста. Ее создание начинается с объекта, представляющего собой игровое поле, которое содержит девять клеток в длину и девять в ширину, причем каждая клетка может содержать значение от 1 до 9. Этого простого описания достаточно, чтобы создать схему. Как показано на рисунке 4, класс поля содержит в точности девять элементов, представляющих собой строки. В результате получается квадратное поле «девять на девять» для игры в Судоку.

Рисунок 4. Тип BoardType в XML-схеме
BoardType XML Schema type

Далее, на рисунке 5 показано, как определяется тип строки игрового поля.

Рисунок 5. Тип RowType в XML-схеме
RowType XML Schema type

Каждая строка содержит ровно девять клеток. Таким образом, можно задавать значения для каждой клетки в строке по отдельности (рисунок 6).

Рисунок 6. Тип CellType в XML-схеме
CellType XML Schema type

Каждая клетка представляет собой простой объект, способный содержать значение от 1 до 9, либо точку (.), обозначающую пустое значение. Также предусмотрен специальный атрибут для хранения признака корректности клетки в соответствии с ограничениями, накладываемые на каждую строку, колонку и область игрового поля (рисунок 7).

Рисунок 7. Тип CellStringType в XML-схеме
CellStringType XML Schema type

Кроме того, тип CellStringType переменной cellValue гарантирует то, что значением клетки может быть только цифра от 1 до 9 или точка.

Генерация объектов XMLBeans

Набор объектов POJO можно создать на основе показанной выше схемы в соответствии с тем, как описано в разделе "Конфигурирования процесса генерации объектов XMLBeans". Получившиеся объекты поддерживают механизм событий, позволяющий интерфейсу приложения получать уведомления об изменениях в модели.

Создание интерфейса приложения

Мы создадим графический интерфейс в классическом стиле MVC, пользуясь тем, что механизм событий поддерживается на уровне сгенерированных объектов XMLBeans. Для разработки слоя представления модели будет использована платформа Eclipse для разработки приложений RCP. Данная платформа, по сути, является подключаемым модулем 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;
    // ... управление навигацией при помощи стрелок ...
  }
  e.doit = false;
}

Значения клеток передаются в слой модели при помощи метода setCellValue(newValue), который обновляет состояние объектов, описанных в XML-схеме. Благодаря механизму событий в XMLBeans подобные вызовы будут приводить к генерации событий, при помощи которых зарегистрированные слушатели будут получать уведомления об изменении модели.

Центральное место в модели событий занимают уведомления о корректности или некорректности значений клеток. Данные заключения делаются на основе позиции клетки в строке, колонке и области (3х3) игрового поля. Реализация проверки находится в методе runValidation() (листинг 10), который вызывается из метода modelChange(ModelChangeEvent event).

Листинг 10. Метод com.ibm.xmlbeans.listeners.CellValidatorListener.modelChange(ModelChangeEvent)
public void modelChange(ModelChangeEvent event) {
  if (XbeUtilities.CELLVALUE_QNAME.equals(event.getPropertyName())) {
    // если изменено значение данной клетки
    if (!CellStringType.X.equals(cell.getCellValue())) {
      if (cell.equals(event.getSource())) {
        // запустить проверку
        int count = runValidation();
        numInvalid = count;
      } else {
        // если изменилась другая клетка
        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);
  }
}

Как видите, сначала проверяется, какое свойство объекта изменилось (в данном случае таким свойством является CELLVALUE_QNAME). Если событие было вызвано изменением данной клетки поля, то вызывается метод runValidation(), который проверяет все необходимые условия. Однако, если это какая-то другая клетка, то нужно просто проверить, что ее значение не равно новому значению измененной клетки. В противном случае, счетчик некорректных клеток (numInvalid) увеличивается на единицу. Если же значение данной клетки равно старому значению измененной клетки, то счетчик наоборот уменьшается на единицу. Наконец, если счетчик numInvalid оказывается равным нулю, то устанавливается флаг корректности клетки (cell.setValid(true)), а в противном случае – он сбрасывается (cell.setValid(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())) {
    // Данный фрагмент кода изменяет цвет клетки.
    // Его можно запустить в параллельном потоке.
    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() для каждой клетки. В процессе обработки события клетки проверяют флаг собственной корректности и устанавливают соответствующий цвет фона для текстового виджета.


Заключение

Механизм событий облегчает разработку приложений, модели которых не зависят от выбранного пользовательского интерфейса. С его помощью нам удалось быстро создать приложение для игры в Судоку, избежав лишнего связывания слоев модели и представления.

Мы добавили поддержку механизма событий в XMLBeans, расширив интерфейсы объектов и интерфейс PrePostSet. Расширенные интерфейсы объектов позволяют добавлять объекты-слушатели в объекты XMLBeans, а расширение PrePostSet служит для отслеживания изменений и уведомления всех заинтересованных компонентов. Кроме того, было показано, как при помощи конфигурационного файла можно интегрировать данные расширения в процесс генерации объектов XMLBeans.


Загрузка

ОписаниеИмяРазмер
Проект Eclipse для игры Судоку на основе XMLBeansx-xmlbeanse/SudokuProject.zip2694KБ
Описание игрового поля Судоку в XMLx-xmlbeanse/SudokuBoards.zip4KБ
Скомпилированный вариант игры Судоку для Windowsx-xmlbeanse/SudokuApplication-Win32.zip146346KБ

Ресурсы

Научиться

  • Оригинал статьи: Receive events from XMLBeans (Джейкоб Эйзингер, Винс Бранссен, developerWorks, январь 2008 г.). (EN)
  • Посетите сайт проекта Apache XMLBeans, на котором содержится документация, а также сама библиотека XMLBeans, с помощью которой можно работать с XML-данными, связывая их с объектами Java. (EN)
  • Обратитесь к странице Wiki, посвященной возможности расширения интерфейсов для объектов, автоматически генерируемых XMLBeans. (EN)
  • Ознакомьтесь со страницей Wiki, описывающей интерфейс PrePostSetFeature и рассказывающей о том, как вызывать определенные участки кода в ответ на изменения в сгенерированных объектах модели XMLBeans. (EN)
  • Прочитайте статью "Создание приложения для игры в Судоку при помощи XForms" (Николас Чейз, Nicolas Chase, developerWorks, февраль 2007 г.), в которой описывается использования XForms при разработке приложения для игры в Судоку. (EN)
  • Обратите внимание на статью "Разработка приложений с использованием XMLBeans" (Абхинав Чопра, Abhinav Chopra, developerWorks, сентябрь 2004 г.), в которой содержится введение в XMLBeans, а также рассказ о том, как XMLBeans произвела революцию в технологии связывания с данными. (EN)
  • Прочитайте руководство по установке XMLBeans, в которой описываются переменные окружения и другая информация, необходимая для работы с XMLBeans. (EN)
  • Прочитайте статью в Wikipedia, посвященную Судоку. В частности, в статье описывается история и стратегии игры в Судоку. (EN)
  • Узнайте больше о паттерне проектирования "Наблюдатель", который используется для реализации механизма событий в XMLBeans, прочитав статью в Wikipedia. (EN)
  • Ознакомьтесь с документацией scomp, в которой описываются параметры командной строки для утилиты, играющей роль компилятора схем и генератора объектов в XMLBeans. (EN)
  • Обратитесь к технической библиотеке XML, содержащей множество статей, советов, руководств, стандартов и справочников IBM Redbooks. (EN)
  • Технические мероприятия и Web-трансляции developerWorks: В этих разделах можно получить самую актуальную информацию о современных технологиях. (EN)
  • Обратитесь к магазину технической литературы, в котором представлены книги на данную и другие темы. (EN)

Получить продукты и технологии

  • Скачайте ознакомительные версии программного обеспечения IBM: Используйте в вашем следующем проекте ознакомительные версии ПО, которые можно скачать прямо с сайта IBM developerWorks. (EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML
ArticleID=458519
ArticleTitle=Получение уведомлений о событиях в XMLBeans
publish-date=12232009