FeedReader

Автор: Рих Унгер (Rich Unger)
Статья написана в рамках соревнования Победа с NetBeans

Пример и учебный курс по платформе NetBeans

Какова функция FeedReader?

FeedReader представляет собой простейший обозреватель каналов RSS/Atom, построенный по принципу подключаемого модуля Sage для Mozilla Firefox. FeedReader состоит из следующих элементов:

  • список каналов (адресов URL, указывающих на файлы описания rss/rdf/atom);

  • список заголовков из каждого канала;

  • окно обозревателя (mozilla, встроенная во фрейм JFrame).

Встроенный фрейм mozilla предоставляется библиотекой JDIC, использующей вызовы JNI. Этот пример настроен для работы с двоичными файлами JDIC как для ОС Linux, так и для ОС Windows.

Кому предназначен этот учебный курс?

Этот курс предназначен в первую очередь для разработчиков приложений на платформе NetBeans. Предпринимается попытка полного документирования создания приложения FeedReader и назначения каждой строки манифестов, файлов слоя, исходных файлов Java, особенно в случаях, когда пришлось применять неочевидные решения.

Такова вторичная задача этого учебного курса. Неочевидные решения и способы обхода проблем выделяются "рамкой хитростей":

для выполнения этого действия необходимо сделать кое-что хитрое.

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

Начало работы

Приведен полный исходный код.

В настоящем учебном курсе не предполагается использование какой-либо конкретной среды IDE. Предположим даже, что используется текстовый редактор, пакет JDK 1.5.0 (ошибка версии 1.4.x не позволяет скомпилировать этот пример, однако он выполняется в 1.4.x) и Apache Ant версии 1.6.2 или более поздней. Для упрощения примеров работы с командной строкой подразумевается использование интерпретатора команд bash (в Unix или Cygwin). Желающие могут переводить на язык интерпретатора командной строки Windows. Это тоже сработает.

Хотелось бы избежать смешивания среды IDE NetBeans и платформы NetBeans. Сначала пройдите этот учебный курс. Добейтесь понимания принципа действия модулей. И только потом можно задумываться о средствах (например, среде IDE NetBeans) для снижения трудоемкости разработки.

Прежде всего для начала работы потребуется копия платформы NetBeans. Ее необязательно строить из исходного кода. Можно просто загрузить двоичный выпуск и распаковать его в удобном месте. Например, в папке /home/rich/netbeans.

Затем неплохо было бы раздобыть примеров исходного кода. Автор, например, начинал с инвентаря для построения кластера. Это было достаточно удобно, поскольку уже были созданы скрипты построения Ant для построения и упаковки модулей, и был создан модуль запуска "snipe". Разумеется, с тем же успехом можно начать приложение с дерева исходного кода для FeedReader.

Инвентарь для построения кластера был распакован автором в каталог /home/rich/src/rss.

Наконец, потребуется настройка скриптов построения для обнаружения установленной платформы NetBeans.

Измените несколько строк в файле /home/rich/src/rss/nbbuild/user.build.properties:

netbeans.dest.dir=/home/rich/netbeans
clustername=rssreader1

Имена кластеров традиционно снабжаются обозначением версии в конце, однако это не строгое требование.

Определение: кластер — это набор модулей и связанных с ними файлов ресурсов. Установка NetBeans может состоять из набора кластеров, которые выбираются при запуске NetBeans. В среде IDE NetBeans, например, выполняются кластеры platform4, ide4, nb4.0 и extra. Идея кластеров заключается в том, чтобы использовать единственную установку платформы NetBeans с несколькими брэндированными приложениями, использующими одни и те же кластеры. Например, можно установить кластер rssreader1 в установку среды IDE NetBeans и посредством двух разных скриптов запуска запускать среду IDE и FeedReader, причем каждое из приложений использовало бы кластер platform4, однако в остальном приложения оставались бы раздельными.

Теперь изменим несколько строк в файле /home/rich/src/rss/nbbuild/user.cluster.properties для синхронизации имени кластера с указанным в файле user.build.properties:

user.cluster=cluster.rssreader1
cluster.rssreader1.dir=rssreader1
cluster.rssreader1= snipe

Теперь при запуске средства ant в каталоге nbbuild/ создается каталог /home/rich/netbeans/rssreader1 с модулем snipe. Разумеется, модуль snipe не требуется, поэтому необходимо ввести команду ant clean для его устранения.

Пора перейти к действительно необходимым модулям.

Модули библиотек

Все приложение FeedReader можно упаковать в один модуль. Но это было бы... не слишком... модульно. Так вышло, что FeedReader требует библиотек JDOM, Rome и JDIC. Если требуется расширить это приложение дополнительными модулями, которые могут использовать эти библиотеки, предпочтительна зависимость только от одного модуля библиотеки, а не от всего приложения FeedReader. Кроме того, модули библиотек могут загружаться автоматически.

Определение: модуль автоматической загрузки — это модуль, который загружается NetBeans, когда он требуется (для другого модуля). Пока модуль не требуется, он не занимает память.

Добавление модулей к дереву исходного кода

При добавлении модуля необходимо предоставить информацию о нем инвентарю построения. Измените два файла.

/home/rich/src/rss/nbbuild/user.cluster.properties:

cluster.rssreader1=snipe, \
                   anothermodule, \
                   yetanother

Таким образом скриптам построения предоставляется список модулей в кластере, и кластер может быть построен в виде блока в каталоге, указанном как местоположение итоговых файлов JAR.

/home/rich/src/rss/nbbuild/modules.xml:

<module>
  <path>snipe</path>
  <cnb>org.netbeans.modules.snipe</cnb>
</module>

Таким образом имя каталога, содержащего исходный код модуля, ставится в соответствие с именем, под которым этот каталог будет известен во время выполнения ("cnb" расшифровывается как "code-name-base" — основа кодового имени).

JDOM

Вероятно, большинство читателей знакомо с JDOM. Это интерфейс API для синтаксического анализа XML, который требуется для FeedReader исключительно потому, что он используется библиотекой Rome.

Начнем с добавления "jdom" к списку кластера и

<module>
  <path>jdom</path>
  <cnb>org.jdom.api</cnb>
</module>

к списку модулей. Ниже приведен список файлов модулей JDOM:

build.xml
manifest.mf
nbproject/project.xml
nbproject/project.properties
lib/jdom.jar
src/org/jdom/api/Bundle.properties

Рассмотрим их по одному.

build.xml: в начале файла построения выполняется импорт другого файла построения с именем projectized.xml. Обычно это все, что требуется в файле построения модуля. Однако в модуле JDOM необходимо добавить к пакету модуля файл jdom.jar в дополнение к собственному файлу jar модуля.

Хитрость №1: для включения дополнительных библиотек не должно требоваться переопределение поведения построения. Такие зависимости должны объявляться в файле project.xml, и скрипты построения должны быть способны определить способ создания соответствующих записей манифеста, включаемых в файл nbm.

https://netbeans.org/bugzilla/show_bug.cgi?id=52354

Итак, переопределите две цели. Цель "files-init" представляет собой точную копию цели из файла projectized.xml, в которую добавлена следующая строка:

<include name="${nb.modules.dir}/ext/jdom.jar"/>

Цель "files-init" предоставляет скриптам построения список файлов, принадлежащих к модулю. Эти файлы удаляются при выполнении цели "clean". По традиции файлы jar, которые не являются модулями, располагаются в каталоге ext/.

Также необходимо переопределить цель "netbeans-extra". Это привязка, предоставляемая скриптами построения для выполнения таких действий как копирование файлов в ходе процесса развертывания.

<target name="netbeans-extra" depends="init">
    <mkdir dir="${netbeans.dest.dir}/${cluster.dir}/${nb.modules.dir}/ext"/>
    <copy todir="${netbeans.dest.dir}/${cluster.dir}/${nb.modules.dir}/ext">
      <fileset dir="lib">
        <include name="jdom.jar"/>
      </fileset>
    </copy>
</target>

При этом файл jdom.jar копируется в каталог /home/rich/netbeans/rssreader1/modules/ext.

manifest.mf: для каждого файла jar требуется манифест. Модуль NetBeans представляет собой просто файл jar, в манифесте которого есть по крайней мере следующие две строки:

OpenIDE-Module: org.jdom.api/1
OpenIDE-Module-Specification-Version: 1.0

Первая строка — имя модуля и, возможно, версия выпуска (release-version). Обратите внимание на то, что это имя совпадает с <cnb> (основа кодового имени) из файла modules.xml.

Вторая строка — версия спецификации модуля specification-version.

Остановимся подробнее на номерах версий... С модулем могут быть связаны 3 разных номера версии: release-version, specification-version и implementation-version (версия реализации).
Предположим, например, что модуль A обладает версией выпуска 1, версией спецификации 2.0 и версией реализации beta3. Модуль B в свою очередь декларирует (в своем файле project.xml) зависимость от модуля A. В модуле обязательно должна быть указана версия выпуска 1. В необязательном порядке может быть указана зависимость от версии спецификации 2.0. Если такая зависимость указана, а автор модуля A выпустит версию 2.1, зависимость не нарушится. Соглашение заключается в том, что публичные классы интерфейса API, предъявляемые модулем A (см. элемент <public-packages> в файле project.xml) не нарушают совместимость. Зависимость от версии спецификации лишь предоставляет модулю B доступ к этим классам интерфейса API.
Если для модуля B указана зависимость от версии реализации beta3, этот модуль будет работать только с такой версией модуля A. Однако модуль B будет иметь доступ ко всем публичным классам модуля A. (Если при выполнении модуля выдается исключение NoClassDefFoundException, это может быть связано с попыткой доступа к классам, не являющимся частью интерфейса API, без указания зависимости реализации).

Добавьте 2 строки к манифесту:

OpenIDE-Module-Localizing-Bundle: org/jdom/api/Bundle.properties
Class-Path: ext/jdom.jar

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

Вторая строка — это стандартная запись пути к классам манифеста, которая располагает файл jdom.jar в пути к классам файла jar модуля. Обратите внимание, что путь "ext/jdom.jar" совпадает с местоположением файла jdom.jar в файле build.xml.

Хитрость №2: следовало бы обойтись без указания пути к классам. Он должен генерироваться автоматически (обратитесь к Хитрости №1).

nbproject/project.xml: этот файл указывает скриптам построения (и среде IDE, если таковая используется), каким образом следует генерировать декларации о зависимости и пути к классам. Здесь также должны быть указаны уже знакомые нам данные <code-name-base> и <path>. Затем необходимо указать все зависимости от других модулей. Поскольку в самом модуле код отсутствует, а файл jdom.jar не требует какого-либо кода кроме базовых классов JRE, объявлять здесь нечего. Тем не менее, всегда необходимо объявлять зависимость от базовых классов OpenAPI:

<dependency>
  <code-name-base>org.openide</code-name-base>
    <build-prerequisite/>
    <compile-dependency/>
    <run-dependency>
      <release-version>1</release-version>
      <specification-version>4.5</specification-version>
  </run-dependency>
</dependency>

Это нечто вроде особого случая. Если эти зависимости не указаны, причем со свежей версией спецификации, NetBeans считает, что модуль старый, и автоматически загружает при выполнении набор зависимостей в целях обеспечения обратной совместимости.

Затем необходимо объявить для модуля <public-packages>. Таким образом решаются две задачи: публичные пакеты становятся доступными для других модулей, объявивших зависимость от данного модуля, и они формируют набор пакетов для генерации документации Javadoc при выполнении цели Ant "javadoc".

Можно либо указать каждый пакет отдельно:

<public-packages>
  <package>org.jdom</package>
  <package>org.jdom.adapters</package>
  <package>org.jdom.input</package>
  ...
</public-packages>

либо все дерево одной строкой:

<public-packages>
  <subpackages>org.jdom</subpackages>
</public-packages>

Этот метод, однако, не работает с целью "javadoc".

project.properties: еще несколько подсказок для процесса построения...

is.autoload=true
cp.extra=lib/jdom.jar
module.javadoc.packages=org.jdom

В первой строке модуль объявляется как автоматически загружаемый (autoload). Вторая строка добавляется к пути к классам при компиляции. Третья строка требуется, если публичные пакеты (<public-packages>) в файле project.xml объявлены с помощью метода <subpackages>.

Хитрость №3: здесь можно было бы обойтись без указания module.javadoc.packages. Скрипты построения должны обладать адекватным режимом "отступления" в предположении, что пользователь не заинтересован в документации Javadoc для этого модуля. На момент публикации если эта строка не указана, построение прерывается.

https://netbeans.org/bugzilla/show_bug.cgi?id=52135

* Обновление: исправлено в выпуске NetBeans 4.1

Rome

Rome

Библиотека Rome считывает каналы RSS и Atom (необходимо добавить, что интерфейс API очень прост). Модуль Rome и модуль JDOM отличаются только в двух отношениях. В модуль Rome входят два файла jar (rome-0.4.jar и rome-fetcher-0.4.jar) вместо одного, а в файле project.xml объявляется зависимость от модуля JDOM:

<dependency>
  <code-name-base>org.jdom.api</code-name-base>
  <build-prerequisite/>
  <compile-dependency/>
  <run-dependency>
    <release-version>1</release-version>
    <specification-version>1.0</specification-version>
  </run-dependency>
</dependency>

JDI

Библиотека JDIC позволяет программам на Java пользоваться такими средствами настольной среды как обозреватели, почтовые программы, системные области уведомлений и реестры типов MIME. В FeedReader для визуализации веб-страниц во фрейме JFrame с помощью механизма визуализации IE или Mozilla используется встроенный компонент обозревателя.

Для этого из JDIC выполняются вызовы Java Native Interface (JNI) к общей библиотеке (jdic.dll или libjdic.so), а также выполняются исполняемые файлы (IeEmbed.exe или mozembed-linux-gtk2). Чтобы эти библиотеки и исполняемые файлы были доступны для NetBeans во время выполнения, в файле компоновки модуля нужно немного пошаманить.

Обратите внимание на набор файлов, объявленных в задаче "files-init":

<include name="${nb.modules.dir}/ext/jdic.jar"/>
<include name="lib/libjdic.so"/>
<include name="lib/libmozembed-linux-gtk1.2.so"/>
<include name="lib/libmozembed-linux-gtk2.so"/>
<include name="lib/mozembed-linux-gtk1.2"/>
<include name="lib/mozembed-linux-gtk2"/>
<include name="lib/jdic.dll"/>
<include name="lib/IeEmbed.exe"/>
<include name="lib/nspr4.dll"/>
<include name="bin/${shell.script}"/>
<include name="bin/${batch.script}"/>

Помимо уже знакомого объявления файла jar указан ряд собственных библиотек и исполняемых файлов из каталога lib/ и пара скрипов. Решение расположить собственные файлы в этом конкретном каталоге несколько произвольно, однако именно это местоположение предлагается в документе по архитектуре. Это местоположение должно быть известно еще в одном месте — для скриптов запуска.

Скрипт интерпретатора команд и пакетный скрипт генерируются целью shellscript файла построения. Эти скрипты запускают платформу NetBeans с кластером rssreader1 и двоичными файлами jdic по соответствующим путям.

Модуль FeedReader

Теперь, когда собраны все требуемые модули библиотек, можно наконец создать модуль, который что-то делает.

Начнем с создания скриптов построения и декларативных элементов модуля. Файл build.xml девственно чист и содержит лишь одну строку импорта для стандартного файла "projectized.xml". Файл nbproject/project.xml просто объявляет зависимости от трех модулей библиотек и не содержит публичных пакетов (в этих модулях не предоставляется интерфейсов API для других модулей). Файл nbproject/project.properties также пуст. Файл manifest.mf, вероятно, также выглядит знакомо. В нем всего один новый элемент:

OpenIDE-Module-Layer: org/netbeans/modules/feedreader/resources/layer.xml

В отличие от модулей библиотек в файле слоя содержатся объявления о пользовательском интерфейсе FeedReader для включения в "системную файловую систему".

Определение: в системной файловой системе хранятся все системные параметры настройки, данные о компоновке графического интерфейса пользователя, действия, шаблоны и практически все что требуется для поддержания состояния установки NetBeans. Эта системная файловая система состоит из настоящих файлов, расположенных в каталоге "config" каждого кластера и в каталоге "config" пользовательского каталога NetBeans, а также из виртуальных "файлов", объявленных в файлах слоя модуля.

Файл слоя

Первая запись расположена в папке верхнего уровня Actions. Это репозиторий для реализаций javax.swing.Action. Эти реализации могут использоваться для пунктов меню, кнопок панелей инструментов и сочетаний клавиш. Добавление этой записи позволяет пользователям присваивать ViewFeedsAction сочетания клавиш путем выбора пункта меню "Сервис ... Сочетания клавиш"

Вторая запись размещает ViewFeedsAction в меню "Вид". Обратите внимание на синтаксис теневого файла, который аналогичен символьной ссылке в UNIX или ярлыку в Windows.

Остальные записи располагаются в папке верхнего уровня Windows2. В этой папке содержатся сведения о типах компонентов TopComponent, для которых должны быть созданы экземпляры, и о местах их размещения. (Кстати папка называется "Windows2", поскольку "Windows" — это старая оконная система до выпуска 3.6, оставленная ради обратной совместимости). В FeedReader содержатся два определения TopComponent: SiteListComponent и EntryListComponent. Последний предназначен для открытия в местоположении по умолчанию (по центру), поэтому переопределять это поведение в файле слоя не требуется.

SiteListComponent, однако, должен быть пристроен к левому краю, т.е. в режиме "проводника". Поэтому добавим следующую запись:

<folder name="Modes">
    <folder name="explorer">            
        <file name="rss_list.wstcref" url="feedList.wstcref"/>
    </folder>
</folder>

Таким образом объявляется, что TopComponent с идентификатором “rss_list” должен быть пристроен по умолчанию в режиме проводника ("explorer"). (Кстати, расширение "wstcref" расшифровывается как Window System Top Component REFerence — ссылка на верхний компонент оконной системы). Расположение "rss_list" внутри объявленного режима выполняется на основании файла feedList.wstcref:

<tc-ref version="2.0">
    <module name="org.netbeans.modules.feedreader/1" spec="1.0" />
    <tc-id id="rss_list" />
    <state opened="true" />
</tc-ref>

Значение <tc-id id> должно совпадать с базовым именем файла, объявленного в слое ("rss_list"). Это имя также должно присутствовать в папке Windows2/Components с расширением “.settings”:

<folder name="Components">
    <file name="rss_list.settings" url="feedList.settings" />
</folder>

Содержимое файла параметров настройки определяет способ создания экземпляра "rss_list". Содержимое этого файла можно написать вручную, либо закомментировать весь раздел файла слоя "Windows2", запустить NetBeans, создать экземпляр SiteListComponent путем запуска ViewFeedsAction и записать автоматически сгенерированный файл параметров настройки из каталога $userdir/config/Windows2Local/Component. Затем замените раздел <serialdata> следующим текстом:

<instance class="org.netbeans.modules.feedreader.SiteListComponent"/>

Таким образом создается компонент с помощью конструктора по умолчанию компонента SiteListComponent. Если для класса требуется статический фабричный метод, можно добавить атрибут метода:

<instance class="org.netbeans.modules.feedreader.SiteListComponent" method=”makeSiteListComponent”/>

ViewFeedsAction.java

Теперь остается создать собственно код Java. Первый класс, объявленный в файле слоя — ViewFeedsAction.java. Это простой подкласс класса CallableSystemAction, представляющего собой реализацию javax.swing.Action в виде единичного класса.

    public void performAction() {
        SiteListComponent.activate();
    }

В результате выполнения этого действия открывается и получает фокус единичный экземпляр класса SiteListComponent (обратитесь к реализации SiteListComponent ниже).

    public String getName() {
        return NbBundle.getMessage(SiteListComponent.class, "SLC_title");
    }

Имя действия сохраняется в Bundle.properties, поэтому его можно локализовать.

    public HelpCtx getHelpCtx() {
        return HelpCtx.DEFAULT_HELP;
    }

Этот код необходимо изменить, если существует более конкретная справка по этому действию в виде идентификатора JavaHelp или адреса URL.

    protected boolean asynchronous() {
        return false;
    }

Более подробные сведения приведены в записи документации.

SiteListComponent.java

Хитрость №4: сравнительно непонятный код в этом классе (связанный с идентификаторами) обеспечивает единичность экземпляра SiteListComponent, идентификатор которого указан в файле слоя. В идеале должен бы существовать подкласс класса TopComponent, называемый, например, SingletonTopComponent, поскольку описываемая ситуация представляется распространенной.

https://netbeans.org/bugzilla/show_bug.cgi?id=53252

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

    /** Подсказка оконной системе для генерации уникального идентификатора */
    private static final String PREFERRED_ID = "rss_list"; // NOI18N
    
    /** Фактический идентификатор (единичного) экземпляра */
    private static String s_id = PREFERRED_ID;

    ...

    public static synchronized SiteListComponent getInstance()
    {
        TopComponent c;
        c = WindowManager.getDefault().findTopComponent(s_id);
        if (c == null)
        {
            c = new SiteListComponent();
            s_id = WindowManager.getDefault().findTopComponentID(c);
        }
        return (SiteListComponent)c;
    }

    ...

    protected String preferredID() { 
        return PREFERRED_ID;
    }

В NetBeans ведется карта всех компонентов TopComponent в памяти. Ключ к этой карте здесь называется "идентификатором". "предпочтительный" идентификатор — это просто подсказка, используемая оконной системой при создании нового экземпляра. Гарантировать, что эта подсказка возымеет какое-либо действие, невозможно. Просто обычно удобно иметь узнаваемое имя компонента при отслеживании проблем по содержимому $userdir/config/Windows2Local.

Статическое поле s_id, однако, представляет собой идентификатор единственного экземпляра SiteListComponent в памяти. По умолчанию установлено значение "rss_list", поскольку это значение идентификатора указано в feedList.wstcref. Следовательно, если пользователь использует модуль впервые, экземпляр компонента с идентификатором "rss_list" создается декларативно. Однако если пользователь закроет этот компонент, перезапустит NetBeans, а затем снова вызовет ViewFeedsAction, новый компонент может получить другой идентификатор. Именно поэтому метод getInstance() может присвоить s_id новое значение.

Если идентификатор действительно изменится, в файле слоя не будет никаких указаний на необходимость пристроить компонент в режиме "проводника". Для принудительной реализации такого поведения необходимо переопределить метод open():

    private static final String MODE = "explorer"; // NOI18N

    public void open()
    {
        Mode m = WindowManager.getDefault().findMode(MODE);
        m.dockInto(this);
        super.open();
    }

Остальная часть класса не является специфичной для NetBeans. Компонент состоит из JList и двух кнопок: одна из них добавляет канал, а вторая — удаляет выделенный канал. Со списком связан класс SiteListModel.

SiteListModel

Класс SiteListModel добавляет сериализацию к классу Swing DefaultListModel. После выполнения конструктора загружается список каналов с диска. Список на диске обновляется при каждой правке списка. (Чересчур? Возможно, однако список вряд ли будет слишком длинным, и после первоначальной настройки редко будет меняться).

Важно решить, где должна выполняться сериализация списка. Размещение его в системной файловой системе позволяет сделать сериализованный файл частью структуры файлов в $userdir/config:

    private static final String DIR = "FeedReader"; //NOI18N
    private static final String FILENAME = "feeds.ser"; //NOI18N

    private FileObject getSerializedFile(boolean create) throws IOException
    {
        FileSystem sysFs = Repository.getDefault().getDefaultFileSystem();
        FileObject dir = sysFs.findResource(DIR);
        if (dir == null)
        {
            if (create)
                dir = sysFs.getRoot().createFolder(DIR);
            else
                return null;
        }
        
        FileObject fo = dir.getFileObject(FILENAME);
        if (fo == null)
        {
            if (create)
                fo = dir.createData(FILENAME);
            else
                return null;
        }
        
        return fo;
    }

Булев параметр create определяет, требуется ли создать файл, если он отсутствует (true для записи, false для чтения).

Итак, сериализованный список оказывается в каталоге $userdir/config/FeedReader/feeds.ser.

Это несколько низкоуровневый подход к записи списка каналов. На самом деле существует интерфейс API NetBeans, способный взять на себя часть действий в этом направлении. Сериализацию можно реализовать путем создания подкласса SystemOption и переопределения методов readExternal() и writeExternal(). Запись напрямую в системную файловую систему выбрана в этом учебном курсе, чтобы более наглядно продемонстрировать "внутренности" реализации. Кроме того, такая реализация предоставляет дополнительную гибкость: можно использовать другой формат файла. После получения объекта FileObject от метода getSerializedFile() для записи в него данных XML достаточно использовать ObjectOutputStream.

EntryListComponent

Этот класс определяет компонент, открывающийся по центру в режиме "редактора" и содержащий список новостей из отдельного канала.

public class EntryListComponent extends TopComponent 
{
    protected static final String PREFERRED_ID = "rss_entry_list"; //NOI18N
    
    protected String preferredID() { 
        return PREFERRED_ID;
    } 

В этом классе нет никаких фокусов с идентификаторами. Они просто используются как подсказки для оконной системы.

    public static TopComponent getInstance(Feed feed)
    {
        // поиск открытого экземпляра, содержащего этот канал
        Iterator opened = TopComponent.getRegistry().getOpened().iterator();
        while (opened.hasNext())
        {
            Object tc = opened.next();
            if (tc instanceof EntryListComponent)
            {
                EntryListComponent elc = (EntryListComponent)tc;
                if (feed.equals(elc.m_feed))
                {
                    elc.initData(feed);
                    return elc;
                }
            }
        }
        
        // не найдено, создаем новый
        return new EntryListComponent(feed);
    }

Метод getInstance() обеспечивает уникальность экземпляра для каждого отдельного канала. Итак, может быть одновременно открыто несколько вкладок, но по одной для каждого адреса URL канала.

    
    protected EntryListComponent(Feed feed) 
    {
        super();
        initComponents();
        if (feed != null)
            initData(feed);
    }

Конструктор должен быть защищенным, поэтому клиенты вынуждены использовать getInstance().

    
    protected void initData(Feed feed)
    {
        m_list.setFeed(feed);
        setDisplayName(feed.toString());
        
        m_feed = feed;
    }

Передайте канал в JList и установите отображаемое имя для использования на вкладке компонентов, в оконных меню и т.д.

    public int getPersistenceType() {
        return PERSISTENCE_NEVER;
    }

При закрытии NetBeans эти компоненты не нуждаются в сериализации.

EntryList, BrowserFrame, Feed

В этих классах мало кода, специфичного для NetBeans. Комментарии в коде говорят сами за себя.

Попробуйте сами!

Запустите ant в каталоге nbbuild/. Затем запустите /home/rich/netbeans/rssreader1/bin/rss-reader.sh (или rss-reader.bat в Windows). Слева откроется пустой список, в который можно добавить адреса URL каналов.



FeedReader работает! Лишь один тревожный факт. Все это выглядит как поддержка RSS, неловко притуленная к NetBeans. Налицо кнопки панели инструментов и пункты меню, не имеющие никакого отношения к чтению RSS.

Брэндинг

Модуль брэндинга FeedReader весьма прост. В нем вообще нет кода Java. Только файл слоя, скрывающий ряд лишних пунктов меню и кнопок панели инструментов.

    <folder name="Toolbars">
        <folder name="File_hidden"/>
        <folder name="Edit_hidden"/>
    </folder>
    
    <folder name="Menu">
        <folder name="File">
            <file name="org-openide-actions-SaveAction.instance_hidden"/>
            <file name="org-openide-actions-SaveAllAction.instance_hidden"/>
            <file name="org-netbeans-core-actions-RefreshAllFilesystemsAction.instance_hidden"/>
            <file name="org-openide-actions-PageSetupAction.instance_hidden"/>
            <file name="org-openide-actions-PrintAction.instance_hidden"/>
        </folder>
        
        <folder name="Edit_hidden"/>
        
        <folder name="View">
            <!-- скрыть обозреватель по умолчанию, который неважно работает -->
            <file name="org-netbeans-core-actions-HTMLViewAction.instance_hidden"/>
        </folder>
        
        <folder name="Window">
            <file name="org-netbeans-core-actions-GlobalPropertiesAction.instance_hidden"/>
        </folder>
    </folder>

Несомненно, читателю любопытно, откуда автору известны названия всех этих скрываемых файлов. Не вполне очевидно, например, что для скрытия пункта меню "Файл ... Сохранить" необходимо указать <file name=”org-openide-actions-SaveAction.instance_hidden”/>. Существуют два способа. Можно порыться в исходном коде NetBeans в поисках файла слоя, в котором требуемый элемент объявлен. Либо можно воспользоваться средством "Bean Browser", которое включено в модуль "Open APIs Support" (org-netbeans-modules-apisupport) в центре обновления NetBeans. Это средство создает узел "Bean Browser" в окне "Окна ... Среда выполнения", который позволяет выполнять обзор системной файловой системы.

Очень полезное средство.

Кроме файла слоя у модуля брэндинга есть еще один необычный аспект. В скрипте построения видны знакомые разделы, добавляющие файлы ресурсов к развернутому набору файлов:

    <!-- 
    Перечень файлов, которые считаются частью модуля при развертывании 
    -->
    <target name="files-init" depends="basic-init">
        <patternset id="module.files">
            <include name="${module.jar}"/>
            <include name="${javahelp.jar}" if="has.javahelp"/>
            <include name="${nb.system.dir}/Modules/${code.name.base.dashes}.xml"/>

            <!-- Начало перечня дополнений к FeedReader -->
            <include name="${nb.lib.dir}/locale/core_rss.jar"/>
            <include name="${nb.modules.dir}/locale/org-netbeans-core-windows_rss.jar"/>
        </patternset>
    </target>

    <!-- 
    netbeans-extra представляет собой привязку для копирования файлов. 
    -->
    <target name="netbeans-extra" depends="init">
        <mkdir dir="${netbeans.dest.dir}/${cluster.dir}/${nb.lib.dir}/locale"/>
        <mkdir dir="${netbeans.dest.dir}/${cluster.dir}/${nb.modules.dir}/locale"/>

        <jar destfile="${netbeans.dest.dir}/${cluster.dir}/${nb.lib.dir}/locale/core_rss.jar" 
             basedir="core"/>
        <jar destfile="${netbeans.dest.dir}/${cluster.dir}/${nb.modules.dir}/locale/org-netbeans-core-windows_rss.jar" 
             basedir="core-windows"/>
    </target>

В этом случае добавляются два файла jar: core_rss.jar и org-netbeans-core-windows_rss.jar. Эти файлы jars соответствуют файлам core.jar и org-netbeans-core-windows.jar. На самом деле любой файл jar в стандартной установке NetBeans можно подвергнуть "брэндингу" путем размещения другого файла jar относительно оригинала:

/path/to/original.jar
/path/to/locale/original_brandname.jar

Напомним, что скрипту интерпретатора команд в модуле JDIC передается параметр "–branding rss". Таким образом "имя брэнда" в данном случае — "rss".

Брэндинг возможен практически для любого файла ресурсов. Для значков, пакетов, файлов слоя и т.д. В файле core_rss.jar брэндинг применен только к экрану заставки и к пакету ресурсов, содержащему значения, определяющие отображение экрана заставки. В файле org-netbeans-core-windows_rss.jar брэндирован только один пакет для переопределения текста главного заголовка.

Попробуйте снова!

Теперь должен открыться экран, подобный показанному на снимке экрана в начале статьи. Итак, в статье продемонстрировано создание настоящего приложения на платформе NetBeans.

Вопросы?

Направляйте вопросы в список рассылки . Сведения о списках рассылки NetBeans приведены по адресу https://netbeans.org/community/lists/top.html.