StrSubstitutor Commons Lang

De EjnTricks

Afin de formatter une chaîne de caractères, il existe plusieurs moyen en standard dans Java, comme l'utilisation de la classe MessaeFormat ou plus simple la fonction format de la classe String.

La librairie Commons Lang fournie la classe StrSubstitutor qui va permettre de rendre plus lisible des squelettes de chaînes de caractères, avec des noms parlants de variables. Cependant, elle ne va pas permettre d'effectuer des opérations de formattage comme peut le faire la fonction String#format, et se contente de manipuler des chaînes de caractères.

Tous les exemples fournis sont sous la forme de tests unitaires.


Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Update icon.png Remplacement de variables

L'tilisation de la plus simple consiste à remplacer des marqueurs dans un modèle par des valeurs. Dans cet exemple, le modèle est de la forme ${name} - ${lastname}. Afin de replacer les variables, il suffit de construire une Map dons les clés correspondent au variables, et les valeurs au text de substitution. Le compilation du texte s'effectue en exécutant la fonction replace avec pour premier argument le modèle, et pour second la Map de valeur, comme illustrer dans l'extrait suivant.

    String pattern = "${name} - ${lastname}";
    Map<String, String> values = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
        { "name", "Etienne" }, { "lastname", "Jouvin" } });
    String actual = StrSubstitutor.replace(pattern, values);
    Assert.assertEquals("Etienne - Jouvin", actual);

A noter la valeur des marqueurs par défaut ${ et } pour identifier les variables.

Ces marqueurs sont suffisament peut courant pour en pas avoir à se poser de questions. Cependant, comment faire dans le cas où le modèle contient la suite ${ sans que cela ne représente un début de variable ? Comme dans de nombreux cas, il existe un caractère d'échappement qui est sans surprise le $ par défaut. Par analogie, c'est pareil que lorsqu'il faut doubler les quotes dans une requêtes SQL.

    String pattern = "${name} - ${lastname} : special car $${name}";
    Map<String, String> values = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
        { "name", "Etienne" }, { "lastname", "Jouvin" } });
    String actual = StrSubstitutor.replace(pattern, values);
    Assert.assertEquals("Etienne - Jouvin : special car ${name}", actual);

Warning-icon.png Attention si une valeur n'est pas fournie, la chaîne ne sera pas substituée, comme illustré dans l'exemple suivant où il manque une valeur pour la variable age.

    String pattern = "${name} - ${lastname} : age ${age}";
    Map<String, String> values = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
        { "name", "Etienne" }, { "lastname", "Jouvin" } });
    String actual = StrSubstitutor.replace(pattern, values);
    Assert.assertEquals("Etienne - Jouvin : age ${age}", actual);


Fork-icon.png Valeur par défaut

Le paragraphe précédent a mis en évidence une problème lorsqu'une variable n'est pas fournie. Or, il est possible d'indiquer dans le modèle, une valeur par défaut à prendre en compte lors de la compilation du modèle. Pour cela, il faut ajouter cette valeur après la combinaison :-.

L'exemple suivant démontre comment configurer la chaîne vide comme valeur par défaut.

    String pattern = "${name} - ${lastname} : age ${age:-}";
    Map<String, String> values = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
        { "name", "Etienne" }, { "lastname", "Jouvin" } });
    String actual = StrSubstitutor.replace(pattern, values);
    Assert.assertEquals("Etienne - Jouvin : age ${age}", actual);

Dans le modèle ${age:-}, la variable age ne peut être substituée, et sera remplacé par la configuration entre :- et }? soit une chaîne vide.

Un second exemple reprend le même principe mais avec une valeur par défaut "plus lisible".

    String pattern = "${name} - ${lastname} : age ${age:-unknown}";
    Map<String, String> values = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
        { "name", "Etienne" }, { "lastname", "Jouvin" } });
    String actual = StrSubstitutor.replace(pattern, values);
    Assert.assertEquals("Etienne - Jouvin : age unknown", actual);

Bien entendu, le paramétrage d'une valeur par défaut n'impacte pas la substitution quand la valeur est disponible, comme le démontre l'exemple ci dessous.

    String pattern = "${name} - ${lastname} : age ${age:-unknown}";
    Map<String, String> newValues = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
        { "name", "Etienne" }, { "lastname", "Jouvin" }, { "age", "Still Young" } });
    String actual = StrSubstitutor.replace(pattern, values);
    Assert.assertEquals("Etienne - Jouvin : age Still Young", actual);


Icon-Configuration-Settings.png Modification marqueurs

Dans les précédents exemples les marqueurs, de début et fin de variable, par défaut ont été utilisés. Cependant, il peut être nécessaire de spécifier des marqueurs spécifiques, pour du code existant par exemple.

Pour cela, il faut appeler un constructeur spécifique de la classe StrSubstitutor afin d'indiquer ces nouvelles valeurs. Dans les précédents exemples, la substitution a été réalisée à l'aide de la fonction statique replace, qui masque la création d'une instance StrSubstitutor.

L'extrait de code suivant met en évidence le paramétrage d'un marqueur de début avec ~[, le marqueur de fin ] et le caractère d'échappement /.

    String pattern = "~[name] - ~[lastname] : special car /~[name]";
    Map<String, String> newValues = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
        { "name", "Etienne" }, { "lastname", "Jouvin" } });
    String actual = new StrSubstitutor(values, "~[", "]", '/').replace(pattern);
    Assert.assertEquals("Etienne - Jouvin : special car ~[name]", actual);


Mylogs-icon.png Chargement de données

La substitution étant effectuée à partir une Map, il est très simple d'externaliser les données dans un fichier dit properties. En effet, le chargement de celui-ci dans une instance de Properties, implémentation de Map, pourra être utilisé comme argument de la substitution. Dans l'exemple suivant, le fichier strSubstitutor.properties, se trouvant dans le même package de la classe exécutée, est chargée et utilisé pour la substitution, avec le contenu est le suivant.

name=Etienne
lastname=Jouvin
    String pattern = "${name} - ${lastname}";
    Properties properties = new Properties();
    InputStream inputStream = this.getClass()
        .getResourceAsStream("strSubstitutor.properties");
    try {
      properties.load(inputStream);
    } finally {
      IOUtils.closeQuietly(inputStream);
    }

    String actual = StrSubstitutor.replace(pattern, values);
    Assert.assertEquals("Etienne - Jouvin", actual);

A noter, la fermeture du fichier est réalisée avec la classe IOUtils de la librairie Commons IO, pour s'afranchir des tests d'exception.


Icon-memory.png Chargement variables système

Comme pour le cas du fichier de propriétés, il est possible d'utiliser les variables système car elles sont disponibles depuis une Map. La classe StrSubstitutor propose la fonction replaceSystemProperties à laquelle il suffit de fournir le modèle. Dans l'exemple suivant, la valeur My value est injectée dans la variable système My Prop.

    System.setProperty("My Prop", "My value");
    String actual = StrSubstitutor.replaceSystemProperties("begin - ${My Prop} - end");
    Assert.assertEquals("begin - My value - end", actual);