FeaturesPluginsDocs & SupportCommunityPartners

FeedReader

Auteur: Rich Unger
Cet article fut soumis lors du concours Win With NetBeans.

Un exemple et tutoriel de Plateforme NetBeans

Que fait FeedReader?

FeedReader est un simple browser pour les nouvelles RSS/Atom, modelé d'après le plugin Sage de Mozilla Firefox. Il est constitué de:

  • Une liste de fournisseurs (URLs vers les fichiers de descripteur rss/rdf/atom)

  • Une liste de titres de chaque fournisseur

  • Une fenêtre de navigateur (mozilla inclut dans une JFrame)

La frame intégrant Mozilla est fournie par la bibliothèque JDIC qui utilise des appels JNI. J'ai configuré cet exemple pour utiliser aussi bien les versions Linux et Windows des binaires JDIC.

A qui s'adresse ce tutoriel?

Le public cible est, évidemment, les personnes qui désirent concevoir des applications depuis la plateforme NetBeans. Je vais essayer de documenter en détail comment j'ai créé FeedReader, et ce que fait chaque ligne des fichiers manifests, layers et sources Java, surtout dans les cas où j'ai du faire quelque chose ... d'un peu quirky.

C'est également la deuxième raison de ce tutoriel. Tout ce qui est Quirkiness et workarounds seront mis en évidence dans une “Quirk Box”:

Vous devez faire quelque chose quirky pour arriver à vos fins.

Dans tous ces cas, je donne le lien du ticket approprié dans IssueZilla. En espérant que cela soit un document vivant, et que ces Quirk Boxes puissent disparaître avec le temps, donnant comme résultat une plateforme à partir de laquelle il est facile de créer des applications.

Pour Commencer

L'entièrerté du code source est disponible.

Pour ce tutoriel, il ne faut pas disposer d'un EDI particulier. En fait, disons qu'il faut juste un éditeur de texte, JDK 1.5.0 (un bogue dans 1.4.x empêche la compilation de cet exemple, bien qu'il peut tourner sous 1.4.x), et Apache Ant 1.6.2 ou supérieur. Pour simplifier mes exemples de ligne de commande, je présente du bash (unix ou cygwin). Faites vous-même la transposition en ligne de commande windows si vous le désirez. Cela marche impécablement bien, également.

Je ne désires pas que vous perturber en parlant de l'EDI NetBeans et de la plateforme NetBeans. Suivez tout d'abord cet tutoriel. Comprenez comment les modules fonctionnent. Ensuite, inquiétez-vous des outils (comme l'EDI NetBeans) pour que vous puissez les créer plus facilement.

La première chose qu'il vous faut pour commencer est une copie de la plateforme NetBeans. Vous n'avez pas besoin de la générer depuis le code source. Vous pouvez juste télécharger une version binaire et la désarchiver quelque part. J'ai mis la mienne dans /home/rich/netbeans.

Ensuite, il sera très utile d'avoir quelques code source d'exemple pour commencer. J'ai commencé avec mon cluster build harness. C'est utile parce qu'il a déjà des scripts Ant pour compiler et créer les modules, et il contient un module de démarrage, appelé 'snipe'. Bien sûr, maintenant que je l'ai écrit, vous pourrez facilement démarrer votre application avec l'arborescence de FeedReader.

J'ai désarchivé ma harness dans /home/rich/src/rss.

Finallement, vous devez configurer vos scripts Ant, pour qu'ils sachent où trouver votre plateforme netbeans:

Modifiez quelques lignes dans /home/rich/src/rss/nbbuild/user.build.properties:

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

Par conventions, les clusteurs reçoivent des numéros de versions à la fin, bien que cela ne soit pas strictement nécessaire.

Définition: Un cluster est un ensemble de modules et fichiers ressources associés. Une installation NetBeans peut être composés depuis un ensemble de clusters, qui sont sélectionnés lorsque vous lancez NetBeans. L'EDI NetBeans, par exemple, exécute les clusters platform4, ide4, nb4.0, et extra. L'idée derrière les clusters est que vous avoir une installation de la plateforme NetBeans et plusieurs applications partageant les mêmes clusters. Je peux par exemple installer le cluster rssreader1 dans l'installation de l'EDI Netbeans, et en exécutant deux lanceurs différents, j'obtiens l'EDI et le FeedReader, chacun partageant le cluster platform4, mais étant bien des applications totalement séparées.

Ensuite, modifiez quelques lignes dans /home/rich/src/rss/nbbuild/user.cluster.properties, dans le but de synchroniser le nom du cluster avec ce que nous venons de spécifier dans user.build.properties:

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

Maintenant, je peux exécuter ant dans le répertoire nbbuild/, et j'aurai un répertoire /home/rich/netbeans/rssreader1 qui contient le module snipe. Bien sûr, je ne désire pas un module snipe. Je vais donc tapez ant clean pour m'en débarrasser.

Il est temps maintenant de passer aux modules que l'on désire.

Library Modules

Vous pourriez livrer l'application FeedReader en un seul module. C'est juste que ce n'est pas très modulaire. Il se peut que FeedReader requière les bibliothèques JDOM, Rome, et JDIC. Si vous désirez étendre cette application avec plusieurs modules qui pourraient utiliser ces bibliothèques, il serait mieux de dépendre de seulement le module de la bibliothèque que de FeedReader dans son entièreté. Aussi, vous pouvez faire les modules de bibliothèques auto-chargeables.

Definition: Un module autochargeable est un module qui sera automatiquement chargé dans NetBeans lorsqu'il sera requis (par un autre module). En attendant ce moment, il ne prendra pas de mémoire à l'exécution.

Ajouter des Modules à l'Arborescence

Lorsque vous ajoutez un nouveau module, vous devez le faire connaitre au build build. Modifiez deux fichiers.

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

cluster.rssreader1=snipe, \
                   anothermodule, \
                   yetanother

Cela indique aux scripts quels modules sont dans votre cluster, pour qu'il puisse savoir comment construire le cluster en tant qu'unité, et dans quel répertoire installer les fichiers JAR en résultant.

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

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

Cela fait correspondre le nom du répertoire contenant le code source du module avec le nom connu par NetBeans au runtime (“cnb” est l'abbréviation de code-name-base).

JDOM

Je suppose que la plupart des lecteurs savent ce qu'est JDOM. C'est une API de parseur XML, et la seule raison pour laquelle FeedReader en a besoin est parce que la bibliothèque Rome l'utilise.

Commencons par ajouter “jdom” à la list de cluster et

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

à la liste de module. Maintenant, voici un extrait du fichier du module JDOM:

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

Examinons les un à la fois.

build.xml: Le script commence par importer un autre script appelé projectized.xml. La plupart du temps, c'est tout ce dont vous avez besoin dans le fichier build de votre module. Cependant, dans le module JDOM, vous devez inclure jdom.jar dans le packaging du module, en plus du propres JAR du module.

Quirk #1: You shouldn't have to override the build behavior in order to include additional libraries. You should be able to declare such dependencies in the project.xml file and the build scripts should know how to create the appropriate manifest entries and include them in the nbm file.

http://www.netbeans.org/issues/show_bug.cgi?id=52354

Donc, surchargeons 2. La target “files-init” est une copie exacte de la target de projectized.xml, avec cette ligne supplémentaire:

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

La target “files-init” permet aux scripts de savoir quels fichiers appartiennent au module. Ce sont les fichiers qui seront effacés lorsque vous exécuterez la target “clean”. Par convention, les JARS qui ne sont pas des modules vont dans le répertoire ext/.

L'autre target que vous devez surcharger est “netbeans-extra”. C'est un hook fournit par les scripts pour vous permettre de faire des choses comme la copie de fichiers, dans le processus de déployement.

<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>

Cela copie le fichier jdom.jar file dans /home/rich/netbeans/rssreader1/modules/ext.

manifest.mf: Chaque fichier JAR a besoin d'un manifest. Un module NetBeans est simplement un fichier jar dont le manifest contient au moins ces deux lignes:

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

La première ligne est juste le nom du module, et optionellement, une release-version. Remarquez que ce nom correspond au <cnb> (code-name-base) du fichier modules.xml.

La seconde ligne est la specification-version du module.

Un mot sur les numéros de version... Un module peut avoir trois numéros de version différents: un release-version, un specification-version, et un implementation-version.
Disons, par exemple, que le module A a release-version 1, specification version 2.0, et implementation-version beta3. Maintenant, module B déclare une dépendance sur le module A (dans son fichier project.xml). Il doit spécifier une version de release de 1. Il peut , optionellement, spécifier une dépendance sur le numéro de spécification 2.0. S'il le fait, et que l'auteur de A sort une version 2.1, c'est okay, la dépendance continue à fonctionner. Le contrat est que les classes de l'API publique exposées par le module A (voir l'élément <public-packages> du project.xml) ne va pas casser la compatibilité. Une dépendance sur la version de spécification ne donne à B que l'accès à ces classes d'API.
Si le module B spécifie une dépendance sur implementation-version beta3, il ne fonctionnera qu'avec cette version du module A. Cependant, il aura accès à toutes les classes publiques de ce module A. (Si vous recevez une NoClassDefFoundException lorsque vous exécutez votre module, cela peut être du au fait que vous essayez d'accéder à des classes non-API sans spécifier une dépendance d'implémentation.)

Plus que deux lignes supplémentaires dans le manifest:

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

La première ligne est completement optionelle. Elle pointe vers un bundle contenant d'autres entrées de manifest. Ces entrées sont toutes des chaines de caractères traduisible, comme un nom d'affichage et une description pour ce module.

La seconde ligne est l'entrée standard d'un manifest Class-Path, qui rajoute le fichier jdom.jar dans le classpath du fichier jar du module. Remarquez que le chemin “ext/jdom.jar” correspond à l'emplacement où vous avez copié jdom.jar dans le fichier build.xml.

Quirk #2: On ne devrait pas spécifier de Class-Path. Il devrait être généré automatiquement (voir Quirk #1).

nbproject/project.xml: C'est le fichier qui dit au script (et l'EDI si vous l'utilisez) comment générer des déclarations de dépendances et classpath. Ici, aussi, vous devez spécifier les imformations familières <code-name-base> et <path>. Ensuite, vous devez spécifier toutes dépendances sur d'autres modules. Maintenant, vu qu'il n'y a pas de code dans le module lui-même, et que jdom.jar ne demande pas d'autre code en dehors des classes du JRE, il n'y a pas de dépendance de modules à déclarer. Cependant, vous devez toujours déclarer une dépendance sur les classes de 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>

C'est un cas spécial. Si vous ne spécifiez pas cela, avec une specification-version récente, NetBeans va supposer qu'il s'agit d'un ancien module, et va charger automatiquement tout un série de dépendance au runtime, à des fins de backwards-compatibility.

Ensuite, vous devez déclarer le <public-packages> du module. Cela sert à deux choses: public packages sont visibles par d'autres modules qui déclarent des dépendances sur ce module, et ils constituent l'ensemble des packages qui veront san Javadoc générée lors de l'exécution de la target ant “javadoc”.

Vous pouvez soit spécifier chaque package individuellement:

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

Ou, vous pouvez mettre toute l'arborescence sur une seule ligne:

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

Cependant, cette méthode ne fonctionne pas avec la targer “javadoc”.

project.properties: A few more hints to the build process...

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

Les premières lignes indique que ce module est autoload. La seconde ligne est rajoutée au classpath de compilation. La troisième ligne est requise si vous utilisez méthode <subpackages> pour déclarer vos <public-packages> dans project.xml.

Quirk #3: Specifying module.javadoc.packages should not be necessary here. The build scripts should have a sane backoff, with the assumption that the user isn't interested in javadocs for this module. Right now, the build fails if this is not specified.

http://www.netbeans.org/issues/show_bug.cgi?id=52135

* Update: Fixed for NetBeans 4.1

Rome

Rome

La bilbiothèque Rome lit les services RSS et Atom (avec une très simple API, devrais-je rajouter). Le module Rome et le module JDOM differe en deux aspects. Le module Rome contient deux fichiers JAR (rome-0.4.jar and rome-fetcher-0.4.jar) au lieu d'un seul, et project.xml déclare une dépendance au module 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>

JDIC

La bibliothèque JDIC permet aux programmes Java de tirer avantage de certaines fonctionnalités natives du desktop comme browsers, mailers, system trays, and MIME type registries. FeedReader utilise un composant intégrant un browser natif pour rendre des pages web dans une JFrame avec le moteur de rendu de IE ou Mozilla..

Pour accomplir cela, JDIC effectue des appels Java Native Interface (JNI) à ces bibliothèques partagées (jdic.dll ou libjdic.so), ainsi que des exécutables natifs (IeEmbed.exe ou mozembed-linux-gtk2). Vous devez faire un peu de magie dans le fichier build du module pour rendre ces bibliothèques et exécutables disponible à NetBeans au runtime.

Notez l'ensemble de fichiers déclaré dans la tâche “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}"/>

A coté de la déclaration familière de fichiers Jar, il y a une liste de bibliothèques natives et exécutables dans lib/ et une paire de. La décision de mettre les éléments natifs dans ce répertoire particulier est quelque peu arbitraire, bien que ce soit l'emplacement suggéré dans le document d'architechture. La seule autre chose qui a besoin de connaître cet emplacement est le script de lancement.

Le shell script et batch script sont générés par la target shellscript du fichier build. Ces scripts lance la plateforme NetBeans avec le clusteur rssreader1 et les binaires jdic dans leur propre chemin.

Module FeedReader

Maintenant que nous avons tous les modules de bibliothèques nécessaires, vous êtes prêts pour faire un module qui fait vraiment quelque chose.

Commencez par créer les scripts et éléments déclaratifs du module. Le fichierbuild.xml est tout simplement vide, excepté la ligne d'import pour le standard “projectized.xml”. Le fichier nbproject/project.xml déclare simplement les dépendances sur vos 3 modules de bibliothèques, et n'a pas de paquetages publics (ces modules ne fournissent pas d'API pour d'autres modules). Le fichier nbproject/project.properties est également vide. Le fichier manifest.mf devrait également vous sembler famillier. Il n'y a qu'un seul nouvel élément ici:

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

Contrairement aux modules de bibliothèques, vous devez déclarer des choses concernant l'interface utilisateur du FeedReader dans un fichier layer, pour l'inclure dans le “system filesystem.”

Définition: Le system filesystem est où NetBeans stoque tous les paramètres systèmes, information GUI layout, actions, modèles. Et tout le reste, qui est requis pour maintenir l'état d'une installation NetBeans. Il comprend des fichiers bien réels situés sous le répertoire "config" de chaque cluster, le répertoire "config" du répertoire utilisateur de NetBeans, et les "fichiers" vituels déclarés dans les fichiers layer du module.

Fichier Layer

La première entrée est situé juste en dessous du dossier principal appelé Actions. C'est un repository pour les implémentations des javax.swing.Action. Ils peuvent être utilisés pour les éléments de menu, les boutons des barres d'outils et les raccourcis clavier. En ajoutant cette entrée, cela permettra aux utilisateurs d'assigner des raccourcis clavier à l'action ViewFeedsAction en sélectionnant “Tools ... Keyboard Shortcuts” depuis le menu.

La seconde entrée met l'action ViewFeedsAction dans le menu “View”. Notez la syntaxe de fichier "fantôme", ce qui correspond à un 'soft link' sous UNIX ou un raccourcis sous Windows..

Les autres entrées se trouvent sous le dossier principal Windows2. Ce dossier fournit à la plateforme des informations quant aux types de TopComponents qui seront instanciés, et où les mettre. (Incidemment, c'est appelé "Windows2" parce que "Windows" est l'ancien système de fenêtre d'avant NetBeans 3.6, qui est gardé pour des raisons de compatibilité.) FeedReader contient deux définitions de TopComponent: SiteListComponent et EntryListComponent. Ce dernier doit être ouvert dans l'emplacement par défaut (le centre), il n'est donc pas nécessaire de surcharger ce comportement dans le fichier layer.

Le SiteListComponent, cependant, sera situé sur le coté gauche, également appelé mode "explorer". Donc, rajoutez une entrée:

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

Cela déclare qu'un TopComponent avec id “rss_list” sera mis, par défaut, dans le mode “explorer”. (Incidemment, l'extension “wstcref” est l'abbréviation de Window System Top Component REFerence.) L'emplacement de “rss_list” dans le mode déclaré est dérivé du fichier 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>

La valeur de <tc-id id> doit correspondre avec le basename du fichier déclaré dans le layer (“rss_list”). Le même nom doit être présent dans le dossier Windows2/Components avec une extension “.settings”:

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

Le contenu du fichier de paramètres indique à NetBeans comment instancier une “rss_list”. Vous pouvez écrire le contenu de ce fichier à la main, ou vous pouvez enlever les commentaires de toute la section Windows2 de ce fichier layer, démarrer NetBeans, instancier un SiteListComponent en exécutant l'action ViewFeedsAction, et saisir le fichier de paramètres qui fut autogénéré dans $userdir/config/Windows2Local/Component. Remplacer ensuite la section <serialdata> avec:

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

Cela crée le composant en utilisant le constructeur par défaut de SiteListComponent. Si la classe requiert une méthode 'static factory', vous pouvez ajouter un attribut method:

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

ViewFeedsAction.java

Maintenant, tout ce qui reste à faire, c'est en fait le code Java. La première classe déclarée dans le fichier layer est ViewFeedsAction.java. C'est une simple sous-classe de CallableSystemAction, qui est une implémentation singletion de javax.swing.Action.

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

L'exécution de cette action va ouvrir et donner le focus à l'instance singleton de SiteListComponent (voir implémentation de SiteListComponent ci-dessous).

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

Le nom de l'action est stoquée dans Bundle.properties, ce qui permet sa traduction.

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

Vous devez changer ceci si vous avez un id JavaHelp ou une URL avec une aide plus spécifique pour cette action.

    protected boolean asynchronous() {
        return false;
    }

Voir l'entrée javadoc pour plus de détails.

SiteListComponent.java

Quirk #4: Some of the more confusing code in this class (the stuff dealing with IDs) has to do with ensuring a singleton instance of SiteListComponent, with the id specified in the layer file. Ideally, there should be a subclass of TopComponent called something like SingletonTopComponent, as this seems like a common thing to want to do.

http://www.netbeans.org/issues/show_bug.cgi?id=53252

Le code suivant assure un singleton:

    /** A hint to the window system for generating a unique id */
    private static final String PREFERRED_ID = "rss_list"; // NOI18N

    /** The actual id of the (singleton) instance */
    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 maintient une map de tous les TopComponents chargés en mémoire. La clé de cette map est ce que j'appelle l'“ID”. l'ID “preferred” est juste une allusion au système de fenêtre à utiliser lors de la création d'une nouvelle instancel. Il n'y a aucune garantie que cela fasse quelque chose. C'est généralement très utile pour que vous puissiez reconnaître le nom du composant si vous êtes en train de tracer les problèmes en dépouillant le contenu de $userdir/config/Windows2Local.

Le champ static s_id, cependant, représente l'ID d'une instance de SiteListComponent en mémoire. Par défaut, il vaut “rss_list” parce que c'est la valeur de l'ID donnée dans feedList.wstcref. Aussi, si c'est la première fois que l'utilisateur utilise ce module, le composant avec l'ID “rss_list” sera instancié déclarativement. Cependant, si l'utilisateur ferme ce composant et redémarre NetBeans, et ensuite réinvoque l'action ViewFeedsAction, le nouveau composant pourrait avoir un autre ID. C'est pourquoi la méthode getInstance() method peut assigner une nouvelle valeur à s_id.

Si l'ID change, il n'y a rien dans le fichier layer file pour indiquer que le nouveau composant apparaisse en mode “explorer”. Pour forcer cela, surchargez la méthode open():

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

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

Le restant de cette classe n'est pas spécifique à NetBeans. Le composant est consistuée d'une JList et de deux boutons: un pour rajouter un nouveau fournisseur, et un pour supprimer un fournisseur sélectionné. La liste est maintenue par un SiteListModel.

SiteListModel

La classe SiteListModel ajouter uniquement la sérialisation au model Swing DefaultListModel. A sa construction, il charge la liste des fournisseurs depuis le disque. Il met à jout la liste sur disque à chaque édition de la liste. (Overkill? Peut-être, mais cela ne sera jamais une très longue liste, et une fois que l'utilisateur a établi la liste, les éditions ne seront pas si fréquentes.)

Le plus important est de décider sérialiser la liste. En la mettant dans le system filesystem, vous pouvez faire en sorte que le fichier sérialisé fasse partie de l'arborescence de fichier dans $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;
    }

Le paramètre booléen create va déterminer si vous désirez créer le fichier s'il n'est pas déjà là (true pour l'écriture, false pour la lecture).

Aussi, la liste sera sérialisée dans le fichier $userdir/config/FeedReader/feeds.ser.

C'est une approche quelque peu bas niveau pour écrire la liste des "fournisseurs". C'est, en fait, une API NetBeans qui s'en charge pour vous. Vous pouvez implémenter la sérialisation en créant une sous-classe de SystemOption, et faire un override des méthodes readExternal() et writeExternal(). J'ai choisi d'écrire directement dans le système filesystem pour vous donner une meilleur idée de ce qui se passe derière la scène. Aussi, l'implémentation vous donne plus de flexibilité pour utiliser un format de fichier différent. Une fois que vous avez le FileObject retourné par getSerializedFile(), vous pourriez y écrire des données XML aussi facilement qu'en utilisant ObjectOutputStream.

EntryListComponent

Cette classe définit un composant qui qui s'ouvrira dans le mode centre, ou “éditeur”, et contient une liste de nouvelles depuis un simple fournisseur.

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

Il n'y a rien de funky avec les ID dans cette classe. Ils sont juste là pour agir en tant que conseil pour le système de fenêtrage.

    public static TopComponent getInstance(Feed feed)
    {
        // look for an open instance containing this 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;
                }
            }
        }
        
        // none found, make a new one
        return new EntryListComponent(feed);
    }

La méthode getInstance() assure que seule une instance existe pour chaque fournisseur unique. Ainsi, il peut y avoir plusieurs onglets ouverts en même temps, mais seulement un pour chaque URL de fournisseur.

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

Marquer le constructeur protected, pour que les clients aient à utiliser getInstance().

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

Donner le fournisseur à la JList, et indiquer le nom d'affichage, qui sera utilisé sur l'onglet components, et dans menus, etc.

    public int getPersistenceType() {
        return PERSISTENCE_NEVER;
    }

Lorsque vous fermez NetBeans, ne passez pas votre temps à sérialiser ces composants.

EntryList, BrowserFrame, Feed

Ces classes ne contiennent pas beaucoup de code spécifique à NetBeans. Je laisse les commentaires en ligne parler d'eux-même.

Essayez-le!

Dans le répertoire nbbuild/, exécutez ant. Ensuite exécutez /home/rich/netbeans/rssreader1/bin/rss-reader.sh (ou rss-reader.bat sous Windows). Vous devriez vois une liste vide sur la gauche, auquel vous pouvez ajouter des URLs de fournisseurs de nouvelles XML.



Un FeedReader en action! Il y a juste une chose ennuyante. Il semble toujours que les capacités RSS sont liées à NetBeans. Il y a toujours des boutons dans la barre d'outils et des entrées de menu qui ne sont pas du tout utile pour la lecture de RSS.

Apposer Sa Marque

Le Module de Marque de FeedReader est un module assez simple. Il n'y a aucun code Java. Juste un fichier layer qui cache toute une série de barre d'outils et entrée menu inutilisée:

<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">
            <!-- hide the default web browser that doesn't render stuff very well -->
            <file name="org-netbeans-core-actions-HTMLViewAction.instance_hidden"/>
        </folder>
        
        <folder name="Window">
            <file name="org-netbeans-core-actions-GlobalPropertiesAction.instance_hidden"/>
        </folder>
    </folder>

La question que je suis sûr que vous vous posez est, “Comment connait-il les noms de ces fichiers à cacher?” Il n'est certainement pas évident que, pou cacher l'entrée du menu “File ... Save”, vous avez besoin de spécifier <filename=”org-openide-actions-SaveAction.instance_hidden”/>. En fait, il y a deux façons. Vous pouvez rechercher dans le code source de NetBeans, après les fichiers layer dans lequel c'était déclaré à l'origine. Ou, vous pouvez utliser le Bean Browser, un outil inclut dans le module “Open APIs Support” (org-netbeans-modules-apisupport) disponible par le Centre de Mise à Jour de NetBeans. Cela crée un noeud dans la fenêtre “Windows...Runtime” appelé “Bean Browser” qui vous permet de naviguer parmis le système filesystem.

Petit Outil Très Utile.

A coté du fichier layer, il y a un autre aspet inhabituel au module de marque. Dans le script Ant, vous verrez les parties maintenant familières qui rajoutent des fichiers ressources à l'ensemble de fichiers déployés:

    <!-- 
    Identifie tous les fichiers à considérer comme faisant partie de ce module
    lorsque déployé
    -->
    <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"/>

            <!-- ajouts pour FeedReader commence ici -->
            <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 est un "hook" fournit pour rajouter une copie des fichiers . 
    -->
    <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>

Dans ce cas, vous ajoutez deux fichiers Jar: core_rss.jar et org-netbeans-core-windows_rss.jar. Ces jars correspondent à core.jar et org-netbeans-core-windows.jar. En fait, tout Jar de l'installation NetBeans standard peut être “branded” en plaçant un autre Jar relatif à l'original:

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

Rappelez vous que le script shell dans le module JDIC passe “–branding rss” comme paramètre. Le nom de la marque dans ce cas est donc “rss”.

En fait, tout fichier ressource peut avoit sa propre marque de fabrique. Les icônes, bundles, fichiers layers, etc. Dans core_rss.jar, J'ai marqué l'écran d'accueil et un resource bundle, qui contient les clefs qui affectent comment le splash screen est accueilli. Dans org-netbeans-core-windows_rss.jar, Je n'ai marqué qu'un seul fichier bundle, pour modifier le texte de la barre de titre principale.

Réessayez!

Maintenant, vous devriez voir un écran semblable à la copie d'écran au début de cet article. Et maintenant vous savez comment écrire un réelle application pour la plateforme NetBeans.

Questions?

Veuillez diriger vos questions vers dev@openide.netbeans.org (en anglais uniquement). Voyez http://www.netbeans.org/community/lists/top.html pour plus d'informations quant aux liste de mailing NetBeans.

Companion
Projects:
MySQL Database Server   Open JDK: an Open SourceJDK   GlassFish Community: an Open Source Application Server    Mobile & Embedded Community    Open Solaris   java.net - The Source for Java Technology Collaboration   Virtual Box - full virtualizer  Open ESB - The Open Enterprise Service Bus Powered by