Préférences

images/brownbelt.png

Les "préférences" sont un cas particulier de ressources. Elles ne sont pas directement liées à une application mais à un contexte d’exécution (c.a.d. liée à un utilisateur et/ou à une machine).

Dans le cas d’une ressource classique le déploiement se fera avec les codes utilisés (par exemple sur une machine serveur) alors qu’ici on va laisser l’utilisateur (ou l’administrateur système du site) gérer un contexte de préférences qui soit permanent et, éventuellement, commun à un ensemble d’applications (et donc, par exemple, liée à la machine "cliente" d’un serveur).

Principes

Les "préférences" d’un utilisateur sont des données rémanentes qui sont utilisées dans des applications pour personnaliser un comportement.

Les applications peuvent permettre à l’utilisateur de les modifier, et, bien entendu, ces données constituent des paramètres comportementaux de ces applications: taille préférée d’une fonte, couleur , etc..

Ces paramétrages, tels qu’implantés par le package java.util.prefs ont des caractéristiques particulières:

  • Ce sont des données non critiques : l’application doit pouvoir fonctionner si elle ne trouve pas le paramètre recherché.
  • Lorsqu’on demande une préférence on doit toujours fournir une valeur par défaut (qui sera renvoyée si la valeur recherchée n’est pas trouvée pour une raison quelconque).
  • Les types possibles pour ces données sont volontairement limités: boolean, int, long, float, double, String et byte[]. (voir ci-dessous contraintes du système de rémanence - en Anglais persistence -).
  • Ce sont des données rémanentes qui peuvent aussi être partagées par plusieurs applications:

    • Elles sont stockées de manière transparente à l’utilisateur et de manière spécifique au système (par exemple par défaut dans des registres sous Windows, dans une arborescence de fichiers sous Unix).
    • Il y a une désynchronisation entre les valeurs connues par une instance de J.V.M et les valeurs connues par le système de rémanence. Même les modifications sont asynchrones et ne sont pas garanties (seule la demande explicite d’harmonisation avec le stockage sous-jacent est susceptible de propager une erreur si ce stockage est indisponible).

Attention: java.util.prefs ne fait pas partie du module java.base mais du module java.prefs!)

Contextes de nommage

Spécifier une préférence c’est associer une valeur à une clef. Cette clef de nommage est forcément une chaîne de caractère.

Pour un code il est important de gérer ces associations dans un espace de nommage qu’il contrôle. A priori un espace "naturel" est lié au nom du package.

Les préférences d’un utilisateur sont organisées dans un arbre de contextes. L’organisation hiérarchique des contextes de préférences est associée à un arborescence de nommage des "noeuds" (node)

arbre preferences

Chaque noeud est représenté par une instance de la classe Preferences.

Sa désignation se fait par un cheminom absolu ("/org/gibis/ihm") ou relatif au noeud courant ("gibis/ihm" dans "/org").

[Avertissement]

Attention, pour des raisons de portabilité, il y a une limite à la longueur du "nom" d’un noeud (80!) -comme d’ailleurs à la longueur de la clef ou de la valeur: voir les constantes de la classe Preferences-

Il existe deux arborescences de contextes:

  • les préférences utilisateur
  • les préférences "système"

Obtention d’un contexte

Pour obtenir un contexte dans le cadre d’un code donné:

Preferences curPrf= Preferences.userNodeForPackage(MyClass.class);

Si le noeud associé au nom du package n’existe pas il est créé (ainsi que tous ses parents manquants), mais ce contexte ne deviendra rémanent que si une harmonisation avec le système de stockage devient nécessaire (ou est explicitement demandée). De la même manière il existe une méthode systemNodeForPackage(Class).

On peut obtenir directement la racine d’un des deux arbres de préférences:

Preferences racine = Preferences.userRoot();// par exemple

A partir d’un noeud on peut obtenir un autre noeud en utilisant son cheminom de désignation:

Preferences petitFils = curPrf.node("fils/petitfils");
Preferences absolu = curPrf.node("/fr/shadocks");

Dans le premier cas on a obtenu un descendant direct du noeud courant dans le second un noeud arbitrairement choisi dans l’arbre courant.

Consultation dans un contexte

A partir d’un noeud donné on peut consulter une préférence :

String imagePréférée = packNode.get("image", "defaultimg.gif") ;

Comme pour toute les consultations de valeur on doit donner une valeur par défaut. Certaines implantations de la rémanence sont autorisées à conserver cette valeur par défaut (mais ce n’est pas un comportement sur lequel il faut compter -de plus si une "valeur par défaut" stockée existe elle peut remplacer la valeur par défaut proposée!-).

On notera que, du fait de la désynchronisation, la valeur obtenue n’est pas nécessairement celle présente à l’instant t dans le système de rémanence: la valeur peut-être modifiée par une autre partie de l’application courante (bien entendu les codes des modifications sont synchronized) ou alors une autre JVM a concurrement modifié cette valeur dans le système de rémanence.

package org.gibis.ihm ;
import java.awt.* ;
import java.util.prefs.* ;
....
public class Fontes {
   public static Preferences curPrefs =
      Preferences.userNodeForPackage(Fontes.class);
   public static final String CLEF_NOM_FONTE = "nomfonte" ;
   public static final String NOM_FONTE_DEFAUT="monospaced";
.....
   public Font fontePréféréeUtilisateur() {
      String nom = curPrefs.get(
         CLEF_NOM_FONTE, NOM_FONTE_DEFAUT) ;
      int style = curPrefs.getInt(
         CLEF_STYLE_FONTE, Font.PLAIN);
      int taille = curPrefs.getInt(
         CLEF_TAILLE_FONTE, TAILLE_FONTE_DEF);
      return new Font(nom, style, taille) ;
   }
....

On aurait pu aussi stocker la représentation de la fonte sous forme d’une chaîne qu’on aurait ensuite analysée (voir java.util.StringTokenizer ou package java.util.regex)

Enregistrement d’une valeur

Dans le contexte d’un noeud donné on peut enregistrer une préférence:

// dans un interface graphique
   String nomImage = myJTextField.getText() ;
   packNode.put("image", monImage)

On dispose de méthodes pour enregistrer des valeurs ayant les types simples admis pour les références (putInt, putDouble, putByteArray, etc.).

Ici aussi il faut veiller aux problèmes de désynchronisation avec le système de rémanence. Il n’est pas certain que la valeur soit immédiatement enregistrée. Pire elle peut ne jamais l'être: si un code n’a pas le droit de modifier un noeud "système" l’exécution risque de ne pas voir passer d’exception (voir ci-après les synchronisations explicites).

....
   public static final String LOCALE = "locale" ;
.....
   public void mémoriseContexteCulturel(Locale loc) {
      curPrefs.put(LOCALE, loc.toString());
   }

(ici, typiquement, si on enregistre "fr_FR" comme valeur il faudra lors du get l’analyser pour reconstituer le Locale).

Une entrée du dictionnaire local peut être supprimée par:

curPrefs.remove(nomPropriété) ;

Une suppression radicale du dictionnaire par:

curPrefs.clear();

Harmonisation avec le système de rémanence

On peut explicitement demander des interactions avec le système de rémanence sous-jacent.

Ici ces invocations sont susceptibles de propager des exceptions de type BackingStoreException:

curPrefs.flush() ;

Sur cet appel le noeud courant et tous ses descendant sont sauvegardés par le système de rémanence (l’appel est synchrone: au retour le système est à jour). Si le noeud vient d'être créé le système de rémanence peut créer tous les ancêtres nécessaires à son existence.

curPrefs.sync() ;

Permet d’assurer que les prochaines consultations de valeurs refléterons tous les changements survenus dans le système sous-jacent (mises à jour par d’autres JVM ). Comme effet de bord cette méthode assure que les modifications locales sont aussi harmonisées (effet analogue à un flush() ).

Noter que l’exception IllegalStateException punit tout appel sur un noeud qui a été invalidé ( par ex. par removeNode()).

Noter également qu’un AccessControler qui n’a pas l’autorisation RuntimePermission("preferences") va déclencher une SecurityException sur toute demande d’obtention de noeud de l’arbre des préférences.