I. Introduction▲
Cet article est le troisième d'une série consacrée à la bibliothèque Guava :
- introduction et installation ;
- collections ;
- programmation fonctionnelle ;
- utilitaires ;
- cache et concurrence ;
- tout pour vos Strings et primitifs ;
- un peu de math ;
- hash et I/O.
I-A. Versions des logiciels et bibliothèques utilisées▲
Pour écrire ce document, j'ai utilisé les versions suivantes :
- Java JDK 1.6.0_24-b07 ;
- Eclipse Indigo 3.7 JEE 64b ;
- Maven 3.0.3 ;
- JUnit 4.10 ;
- Guava 14.0.
J'utilise Java 6, car Java 7 n'est pas encore très répandu en entreprise. C'est ce que je vérifie durant mes conférences lorsque je demande qui utilise Java 7 sur ses serveurs de production, mais que très peu de mains se lèvent…
I-B. Mises à jour▲
1er septembre 2013 : création ;
6 octobre : Ajout du parseur CSV en annexes.
6 octobre : Ajout de la fonction reduce, proposée par Yann Caron.
II. Programmation fonctionnelle▲
Un des points qui reviennent souvent, lorsqu'on parle de Guava, c'est que la bibliothèque permet de s'adonner à la programmation fonctionnelle en Java sans avoir à attendre les Lambdas, prévus pour la version 8 du langage.
Je préfère le dire dès le départ, Guava ne s'inscrit pas en concurrence avec les Lambdas. L'équipe Guava a été très claire sur ce point. D'ailleurs, avec l'arrivée prochaine de Java 8, l'équipe a décidé de ne plus rien ajouter (hors débogage) sur le sujet de la programmation fonctionnelle.
Je voudrais insister également sur le fait qu'il ne faut chercher aucune optimisation dans la programmation fonctionnelle à travers Guava. Toutefois il est vrai que la bibliothèque va vous permettre de mieux organiser/découper votre code, ce qui va mécaniquement le rendre plus performant…
II-A. Predicates et Functions▲
En plus des « iterables », il n'y a que deux classes à connaître pour jouer avec la programmation fonctionnelle de Guava : le « Predicate » et la « Function. Ces deux objets définissent la méthode « apply(..) » qu'il faut redéfinir.
Dans un « Predicate », la méthode « apply(..) » renvoie un booléen :
Predicate<
Chien>
malePredicate =
new
Predicate<
Chien>(
) {
@Override
public
boolean
apply
(
Chien chien) {
return
chien.getSex
(
) ==
SexeEnum.MALE;
}
}
;
Dans une « Function », le type de retour est paramétré :
Function<
Chien, String>
chienNameFunction =
new
Function<
Chien, String>(
) {
@Override
public
String apply
(
Chien from) {
return
from.getName
(
);
}
}
;
Je devine que vous avez déjà deviné que les « Predicates » servent à filtrer, tandis que les « Functions » servent à transformer (ie. « mapper »).
II-B. Filtres▲
La définition d'un filtre est assez simple, car il suffit d'appliquer un « Predicate » à une collection. Il faut juste faire attention au fait que ça renvoie des itérables :
@Test
public
void
testFilterMale1
(
) {
// Arrange
final
List<
Chien>
chiens =
Lists.newArrayList
(
new
Chien
(
"Milou"
, MALE),
new
Chien
(
"Pluto"
, MALE),
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Rantanplan"
, MALE),
new
Chien
(
"Idefix"
, MALE));
final
int
nb =
5
;
// Act
final
Predicate<
Chien>
malePredicate =
new
Predicate<
Chien>(
) {
@Override
public
boolean
apply
(
Chien chien) {
return
chien.getSex
(
) ==
SexeEnum.MALE;
}
}
;
final
List<
Chien>
res =
Lists.newArrayList
(
Iterables.filter
(
chiens, malePredicate));
// Assert
Assert.assertEquals
(
nb, res.size
(
));
}
Je crois que c'est assez simple pour se passer d'explication ; la méthode « filter() » de Guava applique le prédicat « malePredicate » sur la collection « chiens ».
Les plus curieux d'entre vous trouveront un petit détail qui a son importance dans la documentation. Il est possible d'utiliser la classe « Collections2 » à la place de « Iterables ». La classe « Collections2 » renvoie donc une vue « live », non thread safe, des éléments filtrés tandis que Iterable renvoie une « copie ». La doc le dit elle-même, si une vue live n'est pas nécessaire alors l'utilisation de « Iterables » permettra d'avoir un programme plus rapide (dans la plupart des cas).
Ça marche aussi avec des maps, sur lesquelles on peut aussi traiter les clés :
@Test
public
void
testFilterSurMap
(
) {
// Arrange
final
Map<
String, Integer>
ages =
new
ImmutableMap.Builder<
String, Integer>(
)
.put
(
"Jean"
, 32
)
.put
(
"Paul"
, 12
)
.put
(
"Lucie"
, 37
)
.put
(
"Marie"
, 17
)
.put
(
"Bernard"
, 17
)
.put
(
"Toto"
, 17
)
.put
(
"Loulou"
, 17
)
.build
(
);
final
int
nb =
3
; // Jean, Paul et toto
// Act
final
Predicate<
String>
sizePredicate =
new
Predicate<
String>(
) {
@Override
public
boolean
apply
(
String from) {
return
from.length
(
) <
5
;
}
}
;
final
Map<
String, Integer>
filtered =
Maps.filterKeys
(
ages, sizePredicate);
// Assert
Assert.assertEquals
(
nb, filtered.size
(
));
}
II-B-1. lazy▲
À ce stade, il faut absolument comprendre que le filtre vous renvoie une « vue » et non une « simple » liste. Une vue est « lazy ». Ça veut dire que le filtre sera effectué à chaque fois que vous utiliserez la vue et pas seulement la première fois. En fait, le filtre est effectué à la demande. Si on ne demande rien alors il n'est pas affecté. Si on le demande trois fois, le filtre passe donc trois fois. Il n'y a pas de mise en cache (comme on pourrait en avoir avec JPA). Ajoutons un « println » pour bien voir que l'affichage et le filtre se font en même temps :
final
List<
Chien>
chiens =
Lists.newArrayList
(
new
Chien
(
"Milou"
, MALE),
new
Chien
(
"Pluto"
, MALE),
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Rantanplan"
, MALE),
new
Chien
(
"Idefix"
, MALE));
// Act
final
Predicate<
Chien>
malePredicate =
new
Predicate<
Chien>(
) {
@Override
public
boolean
apply
(
Chien chien) {
System.out.println
(
"apply"
);
return
chien.getSex
(
) ==
SexeEnum.MALE;
}
}
;
Iterable<
Chien>
iter =
Iterables.filter
(
chiens, malePredicate);
System.out.println
(
"Boucle 1"
);
for
(
Chien chien : iter) {
System.out.println
(
"1 : "
+
chien);
}
System.out.println
(
"Boucle 2"
);
for
(
Chien chien : iter) {
System.out.println
(
"2 : "
+
chien);
}
Boucle 1
apply
1
: Dog{name
=
Milou}
apply
1
: Dog{name
=
Pluto}
apply
apply
1
: Dog{name
=
Volt}
apply
1
: Dog{name
=
Rantanplan}
apply
1
: Dog{name
=
Idefix}
Boucle 2
apply
2
: Dog{name
=
Milou}
apply
2
: Dog{name
=
Pluto}
apply
apply
2
: Dog{name
=
Volt}
apply
2
: Dog{name
=
Rantanplan}
apply
2
: Dog{name
=
Idefix}
Vous remarquez que les affichages des chiens sont mélangés aux affichages des filtres. Ici le double affichage de « apply » correspond à Lassie, qui est une fille et qui est donc filtrée.
Vous remarquez aussi que les filtres sont réalisés pour chaque boucle. La parade la plus simple, pour ne faire les filtres qu'une seule fois, consiste à mettre le résultat dans une liste, à l'aide de « newArrayList(..) » qui accepte les itérables :
final
List<
Chien>
list =
Lists.newArrayList
(
Iterables.filter
(
chiens, malePredicate));
System.out.println
(
"Boucle 1"
);
for
(
Chien chien : list) {
System.out.println
(
"1 : "
+
chien);
}
System.out.println
(
"Boucle 2"
);
for
(
Chien chien : list) {
System.out.println
(
"2 : "
+
chien);
}
apply
apply
apply
apply
apply
apply
Boucle 1
1
: Dog{name
=
Milou}
1
: Dog{name
=
Pluto}
1
: Dog{name
=
Volt}
1
: Dog{name
=
Rantanplan}
1
: Dog{name
=
Idefix}
Boucle 2
2
: Dog{name
=
Milou}
2
: Dog{name
=
Pluto}
2
: Dog{name
=
Volt}
2
: Dog{name
=
Rantanplan}
2
: Dog{name
=
Idefix}
Cette fois, le filtre est donc réalisé une seule fois, même si on ne s'en sert pas. Il va donc falloir choisir si vous voulez une vue lazy ou une liste, selon que vous souhaitez l'utiliser une seule fois (voire aucune) ou à plusieurs reprises.
II-B-2. Prédicats tout prêts▲
Guava propose aussi des prédicats tout prêts. Ce sont des prédicats qui semblent relativement simples, mais qui sont en réalité très difficiles à programmer. Commençons avec le prédicat « in(..) » qui indique si un élément est présent dans la collection :
@Test
public
void
testIn
(
) {
// Arrange
final
List<
Chien>
chiens =
Lists.newArrayList
(
new
Chien
(
"Milou"
, MALE),
new
Chien
(
"Pluto"
, MALE),
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Rantanplan"
, MALE),
new
Chien
(
"Idefix"
, MALE));
final
String nom =
"Volt"
;
// Act
final
boolean
isIn =
Predicates.in
(
chiens).apply
(
new
Chien
(
nom));
// Assert
Assert.assertTrue
(
isIn);
}
Les prédicats tout prêts vous offrent seulement la mécanique technique. Il faut que votre objet soit correctement écrit, notamment qu'il possède les méthodes classiques « equals() » et « hashCode() ».
On peut en demander un peu plus, en composant plusieurs prédicats de ce type, et même sur des collections différentes :
@Test
public
void
testAnd
(
) {
// Arrange
final
List<
Chien>
list =
Lists.newArrayList
(
new
Chien
(
"Milou"
, MALE),
new
Chien
(
"Pluto"
, MALE),
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Rantanplan"
, MALE),
new
Chien
(
"Idefix"
, MALE));
final
Set<
Chien>
set =
Sets.newHashSet
(
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Medor"
, MALE));
final
String nom =
"Volt"
;
// Act
final
boolean
isIn =
and
(
in
(
list), in
(
set)).apply
(
new
Chien
(
nom));
// Assert
Assert.assertTrue
(
isIn);
}
Vous devinez déjà que vous pouvez utiliser les prédicats « or » et « not » en complément. Je vous épargne les explications évidentes.
II-B-3. Composition▲
Comme son nom l'indique, la méthode « compose() » renvoie la composition de deux fonctions. Comme le dit si bien la Javadoc, si on prend une fonction f(x) et une fonction g(x), alors la composition h(x) sera égale à g(f(x)). Bon, en le disant en français, ça veut juste dire que, pour chaque élément qu'on lui passe, ça applique une première fonction, puis ça applique la seconde fonction au résultat de la première : une composition quoi…
@Test
public
void
testCompo
(
) {
// Arrange
final
List<
Chien>
chiens =
Lists.newArrayList
(
new
Chien
(
"Milou"
, MALE),
new
Chien
(
"Pluto"
, MALE),
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Rantanplan"
, MALE),
new
Chien
(
"Idefix"
, MALE));
final
String enminuscule =
"milou"
;
final
Function<
Chien, Chien>
majusculiser =
new
Function<
Chien, Chien>(
) {
@Override
public
Chien apply
(
Chien from) {
return
new
Chien
(
from.getName
(
).substring
(
0
, 1
).toUpperCase
(
) +
from.getName
(
).substring
(
1
));
}
}
;
// Act
final
boolean
isIn =
compose
(
in
(
chiens), majusculiser).apply
(
new
Chien
(
enminuscule));
// Assert
Assert.assertTrue
(
isIn);
}
Ça pique un peu les yeux ? Voici ce que ça fait : dans un premier temps, ça passe le chien « milou » (avec tout le nom en minuscules) à la fonction. Celle-ci met en majuscule la première lettre, ce qui donne « Milou ». Le prédicat vérifie ensuite si un chien avec le nom « Milou » est présent dans la liste, ce qui est le cas.
C'est un peu difficile à utiliser et je vous invite à voir comment vous pouvez faire mieux et plus simple à l'aide des « FluentIterables » expliqués dans le prochain chapitre.
II-C. Transformations▲
Si vous avez compris le chapitre précédent, dédié aux filtres, vous allez vite comprendre celui-ci, car c'est la même chose, un niveau au-dessus, et pour faire des transformations.
Plus concrètement, l'idée va être de transformer une liste d'objets en une vue d'un autre type :
@Test
public
void
testTransform
(
) {
// Arrange
final
List<
Chien>
chiens =
Lists.newArrayList
(
new
Chien
(
"Milou"
, MALE),
new
Chien
(
"Pluto"
, MALE),
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Rantanplan"
, MALE),
new
Chien
(
"Idefix"
, MALE));
final
List<
String>
expected =
Lists.newArrayList
(
"Milou"
, "Pluto"
, "Lassie"
, "Volt"
, "Rantanplan"
, "Idefix"
);
// Act
final
List<
String>
noms =
Lists.transform
(
chiens, new
Function<
Chien, String>(
) {
@Override
public
String apply
(
Chien from) {
return
from.getName
(
);
}
}
);
// Assert
Assert.assertEquals
(
expected, noms);
}
De manière générale, j'utilise toujours l'utilitaire « Lists », car il renvoie directement une liste. On peut toutefois passer par « Iterables » :
@Test
public
void
testTransformToSuperDog
(
) {
// Arrange
final
Set<
Chien>
chiens =
Sets.newHashSet
(
new
Chien
(
"Milou"
, MALE),
new
Chien
(
"Pluto"
, MALE),
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Rantanplan"
, MALE),
new
Chien
(
"Idefix"
, MALE));
final
String expectedPower =
"Code en Java"
;
// Act
final
Function<
Chien, SuperDog>
chienToSuperDogFunc =
new
Function<
Chien, SuperDog>(
) {
@Override
public
SuperDog apply
(
Chien from) {
final
SuperDog to =
new
SuperDog
(
);
to.setName
(
from.getName
(
));
final
List<
String>
powers =
Lists.newArrayList
(
"Vole"
, "Code en Java"
, "Court vite"
);
to.setPowers
(
powers);
return
to;
}
}
;
final
Iterable<
SuperDog>
iter =
Iterables.transform
(
chiens, chienToSuperDogFunc);
// Assert
for
(
SuperDog sd : iter) {
Assert.assertTrue
(
sd.getPowers
(
).contains
(
expectedPower));
}
}
Bien entendu, tout comme pour les filtres, ça retourne une vue lazy. Là encore il faudra décider de la stratégie à adopter.
II-D. FluentIterables▲
La plupart du temps, vous pourrez vous contenter d'un filtre ou d'une transformation. Mais vous aurez parfois besoin d'utiliser les deux. Pensez au « filter-map-reduce »… Bien entendu, il est toujours possible de les enchaîner en deux temps :
@Test
public
void
testFilterMap
(
) {
// Arrange
final
List<
Chien>
chiens =
Lists.newArrayList
(
new
Chien
(
"Milou"
, MALE),
new
Chien
(
"Pluto"
, MALE),
new
Chien
(
"Lassie"
, FEMALE),
new
Chien
(
"Volt"
, MALE),
new
Chien
(
"Rantanplan"
, MALE),
new
Chien
(
"Idefix"
, MALE));
final
List<
String>
expected =
Lists.newArrayList
(
"Milou"
, "Pluto"
, "Volt"
, "Rantanplan"
, "Idefix"
);
// Act
final
Predicate<
Chien>
malePredicate =
new
Predicate<
Chien>(
) {
@Override
public
boolean
apply
(
Chien chien) {
System.out.println
(
"apply predicate"
);
return
chien.getSex
(
) ==
SexeEnum.MALE;
}
}
;
final
Function<
Chien, String>
toNameFunc =
new
Function<
Chien, String>(
) {
@Override
public
String apply
(
Chien from) {
System.out.println
(
"apply function"
);
return
from.getName
(
);
}
}
;
final
Iterable<
Chien>
filtered =
Iterables.filter
(
chiens, malePredicate);
final
Iterable<
String>
mapped =
Iterables.transform
(
filtered, toNameFunc);
final
List<
String>
noms =
Lists.newArrayList
(
mapped);
// Assert
Assert.assertEquals
(
expected, noms);
}
Bon, ce n'est pas la fin du monde de le faire en deux temps, mais on voudrait vraiment pouvoir tout faire d'un coup. Dans ce cas, on va plutôt utiliser « FluentIterable » qui permet d'enchaîner les opérations :
final
List<
String>
noms =
FluentIterable.from
(
chiens)
.filter
(
malePredicate)
.transform
(
toNameFunc)
.toList
(
);
Ici, je demande à Guava d'enchaîner le filtre (toujours bon de le mettre en premier), puis la transformation puis de me donner ça sous la forme de liste (immutable) pour bien finir. Si vous avez déjà regardé à quoi vont ressembler les lambdas de Java 8, ça devrait vous parler.
Et encore une fois, n'oubliez pas que c'est « lazy »…
À lire, un billet de blog intitulé « FluentIterable sur mon chien Guava ».
II-E. Et le reduce▲
Il ne manque que le « reduce » du « filter-map-reduce » pour que le tableau soit complet. Guava ne propose pas spécifiquement cette fonction mais il est possible de s'en tirer. Voyons comment avoir les âges de nos chiens, avec une moyenne au milieu :
List<
Dog>
dogs =
Lists.newArrayList
(
new
Dog
(
"effy"
, Dog.Gender.MALE, 5
),
new
Dog
(
"wolf"
, Dog.Gender.MALE, 7
),
new
Dog
(
"lili"
, Dog.Gender.FEMALE, 7
),
new
Dog
(
"poupette"
, Dog.Gender.FEMALE, 10
),
new
Dog
(
"rouquette"
, Dog.Gender.FEMALE, 11
),
new
Dog
(
"rouky"
, Dog.Gender.MALE, 8
),
new
Dog
(
"athos"
, Dog.Gender.MALE, 3
));
//final float average = 0;
Optional<
Float>
average =
FluentIterable
.from
(
dogs)
.filter
(
new
Predicate<
Dog>(
) {
@Override
public
boolean
apply
(
Dog t) {
// filter keep only males
return
t.getGender
(
) ==
Dog.Gender.MALE;
}
}
)
.transform
(
new
Function<
Dog, Float>(
) {
int
index =
0
;
float
previousAverage =
0
;
@Override
public
Float apply
(
Dog f) {
float
age =
f.getAge
(
);
// calculate
float
prevSum =
previousAverage *
index; // step 1
index++
;
float
newSum =
prevSum +
age; // step 2
float
newAverage =
newSum /
index; // step 3
previousAverage =
newAverage;
return
newAverage;
}
}
)
.last
(
);
float
test =
(
5
+
7
+
8
+
3
) /
4
F;
System.out.println
(
test);
System.out.println
(
"Average of male ages : "
+
average);
Vous trouvez que ça pique les yeux ? Alors autant programmer carrément le « reduce » :
public
class
Iterables2 {
private
Iterables2
(
) {}
public
static
<
F extends
Object>
F reduce
(
Iterable<
F>
itrbl, Function<
List<
F>
, ? extends
F>
fnctn) {
F prevResult =
null
;
F result =
null
;
for
(
F elem : itrbl) {
if
(
prevResult ==
null
) {
result =
elem;
}
else
{
result =
fnctn.apply
(
Lists.<
F>
newArrayList
(
prevResult, elem));
}
prevResult =
result;
}
return
result;
}
}
Du coup, le code s'en trouve simplifié :
float
average =
Iterables2.reduce
(
FluentIterable
.from
(
dogs)
.filter
(
new
Predicate<
Dog>(
) {
@Override
public
boolean
apply
(
Dog t) {
// filter keep only males
return
t.getGender
(
) ==
Dog.Gender.MALE;
}
}
)
.transform
(
new
Function<
Dog, Float>(
) {
int
index =
0
;
float
previousAverage =
0
;
@Override
public
Float apply
(
Dog f) {
return
Integer.valueOf
(
f.getAge
(
)).floatValue
(
);
}
}
), new
Function<
List<
Float>
, Float>(
) {
int
currentIndex =
1
;
@Override
public
Float apply
(
List<
Float>
f) {
// recursive serie
float
prevResult =
f.get
(
0
);
float
currentAge =
f.get
(
1
);
currentIndex++
;
// calculate
float
prevSum =
prevResult *
(
currentIndex -
1
); // step 1
float
newSum =
prevSum +
currentAge; // step 2
float
newAverage =
newSum /
currentIndex; // step 3
return
newAverage;
}
}
);
III. Conclusion▲
Vous savez maintenant de quoi il retourne quand on parle de cette fabuleuse programmation fonctionnelle que vous pouvez faire à l'aide de Guava. C'est très puissant et très simple. N'hésitez pas à consulter les autres épisodes de cette série pour découvrir les fonctionnalités fantastiques de la bibliothèque.
Vos retours nous aident à améliorer nos publications. N'hésitez donc pas à commenter cet article sur le forum : 1 commentaire
IV. Remerciements▲
D'abord j'adresse mes remerciements à l'équipe Guava, chez Google, pour avoir développé une bibliothèque aussi utile et pour la maintenir. Je n'oublie pas tous les contributeurs qui participent notamment sur le forum Guava.
Plus spécifiquement en ce qui concerne cet article, je tiens à remercier l'équipe de Developpez.com et plus particulièrement Bernard Le Roux, Ricky81, Mickael Baron, Yann Caron, Logan, et Claude Leloup.
V. Annexes▲
V-A. Liens▲
Guava : https://code.google.com/p/guava-libraries/
Article « Simplifier le code de vos beans Java à l'aide de Commons Lang, Guava et Lombok »:
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/simplifier-code-guava-lombok/
Blog sur Guava : https://blog.developpez.com/guava/
Article « J2SE 1.5 Tiger » par Lionel Roux :
https://lroux.developpez.com/article/java/tiger/
Article « Présentation de Java SE 7 » par F. Martini (adiGuba) :
https://adiguba.developpez.com/tutoriels/java/7/
Article « Fonction Object Design pattern - Tutoriel sur les foncteurs » par Yann Caron :
https://caron-yann.developpez.com/tutoriels/java/fonction-object-design-pattern-attendant-closures-java-8/
V-B. Liens personnels▲
Retrouvez ma page et mes autres articles sur Developpez.com à l'adresse
https://thierry-leriche-dessirier.developpez.com/#page_articlesTutoriels
Suivez-moi sur Twitter : @thierryleriche (https://twitter.com/thierryleriche)@thierryleriche
V-C. Le parseur de CSV avec Guava▲
Voici le code d'un parseur de fichier CSV, proposé par Yann Caron :
String csv =
""
+
"element 1.1, element 1.2, element 1.3, element 1.4
\n
"
+
"element 2.1, element 2.2
\n
"
+
"element 3.1, element 3.2, element 3.3
\n
"
+
"element 4.1, element 4.2, element 4.3, element 4.4, element 4.5
\n
"
+
""
;
Iterables.all
(
Splitter.on
(
'
\n
'
)
.split
(
csv),
new
Predicate<
String>(
) {
// parse row
@Override
public
boolean
apply
(
String t) {
// parse item
Iterables.all
(
Splitter.on
(
','
)
.trimResults
(
)
.omitEmptyStrings
(
)
.split
(
t),
new
Predicate<
String>(
) {
@Override
public
boolean
apply
(
String t) {
System.out.println
(
"Parse : "
+
t);
return
true
;
}
}
);
return
true
;
}
}
);