:: Enseignements :: ESIPE :: E4INFO :: 2024-2025 :: Java Avancé ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) |
Implantation de l'API DOM
|
Concepts : programmation orientée objet, pattern matching, liste et map, mutation, copie défensive encapsulation et vue
Pour l'ensemble des TDs de cette année, nous allons utiliser l'IDE Eclipse
(pas de problème si vous voulez en utiliser un autre pourvu qu'il sache
compléter automatiquement, lire le pom.xml de Maven et faire du refactoring).
La nouvelle version de Java est la version 23 mais elle ne sera disponible que dans 2 semaines.
Nous allons, pour l'instant nous contenter de la version 21 installée sur les machines de la fac
(ou 22 si vous êtes sur votre portable).
Eclipse s'exécute en tapant dans un terminal :
eclipse-light &
Après démarrage, indiquer le JDK 21 : dans Window > Preferences > Java > Installed JREs,
Le JDK 21 est présent dans le répertoire /usr/local/apps/java21.
Puis vérifier que le compilateur a bien été configuré en mode 21 :
dans Window > Preferences > Java > Compiler, le Compiler compliance level doit être à 21.
Chaque exercice vient avec des tests unitaires
JUnit
qui vous permettent de vérifier que votre implantation passe les tests.
Exercice 1 - Maven
Pas de Maven pour le TP1, on verra cela pour le TP 2.
Exercice 2 - Document Object Model
Le but de cet exercice est de réimplanter les méthodes
document.createElement(name, attributs),
document.getElementById(id)
et
element.appendChild(element2).
de l'API DOM.
Pour cela, nous allons créer une interface
DOMNode représentant un nœud d'un arbre DOM
(un nœud correspondant à un tag HTML comme P ou UL) avec un nom (
name comme "p", "ul", etc),
une
Map (nommée
attributes) qui associe à un nom d'attribut une valeur d'attribut correspondant
(par exemple, pour le tag HTML <a href="foo.txt">, le nom de l'attribut est "href" et sa valeur est "foo.txt")
et une liste de
DOMNode (nommée
children) qui correspond aux nœuds enfants du nœud courant.
Pour pouvoir créer un
DOMNode, nous allons aussi définir une classe
DOMDocument
qui en plus de pouvoir créer des nœuds (avec
createElement(name)) permet d'accéder à un noeud
par son
id (avec
getElementById(id)).
Toutes les classes,
record, etc... doivent être définis dans le package
fr.uge.dom.
-
Dans un premier temps, créer l'interface DOMNode avec une méthode name qui renvoie le nom
du nœud de l'arbre DOM. Créer la classe DOMDocument avec une méthode createElement(name)
qui créé un nouveau nœud. Il vous faut aussi créer une classe qui implante l'interface DOMNode,
on souhaite que cette classe soit la seule implantation possible de DOMNode.
On souhaite de plus que le document (DOMDocument) qui a servi a créé un nœud continue à exister
tant que le nœud existe.
Le code suivant devrait fonctionner
DOMDocument document = new DOMDocument();
DOMNode node = document.createElement("div");
System.out.println(node.name()); // div
Vérifier que les tests JUnit marqués "Q1" passent.
-
On souhaite maintenant pouvoir créer un nœud avec un nom et des attributs tel que le code ci-dessous fonctionne
var document = new DOMDocument();
var node = document.createElement("div", Map.of("color", "red", "visible", true));
System.out.println(node.name()); // div
System.out.println(node.attributes()); // {color=red, visible=true}
La valeur d'un attribut ne peut être qu'une chaine de caractères, un booléen, un entier, un entier long,
un flottant ou un flottant long.
Faite le nécessaire pour le code fonctionne ci-dessus fonctionne.
Vérifier que les tests JUnit marqués "Q2" passent.
Note : on veut aussi que les code que nous avons écrits précédemment (ici les tests de "Q1") continuent
de fonctionner (on veut être backward compatible).
-
On souhaite pouvoir afficher un nœud en utilisant le même format que le HTML.
var document = new DOMDocument();
var node = document.createElement("div", Map.of("foo", "bar"));
System.out.println(node) // <div foo="bar"></div>
Modifier votre code pour être capable d'afficher des nœuds au format HTML.
Vérifier que les tests JUnit marqués "Q3" passent.
Rappel : vous avez le droit d'utiliser un Stream pour que le code soit plus simple à écrire.
-
On souhaite ajouter une méthode getElementById qui renvoie un nœud par son id.
En HTML, un id doit être une chaine de caractère (non vide) et
si pour un même document, il y a plusieurs nœuds avec le même id seul
le premier nœud est enregistré avec cet id.
var document = new DOMDocument();
var node = document.createElement("div", Map.of("id", "foo42"));
System.out.println(document.getElementById("foo42")); // <div id="foo42"></div>
Quelle structure de données, doit-on utiliser pour permettre de trouver un nœud par son id ?
Ajouter la méthode getElementById dans la classe DOMDocument tel que le code
ci-dessus fonctionne.
Vérifier que les tests JUnit marqués "Q4" passent.
-
On souhaite pouvoir ajouter des fils à un nœud existant en utilisant la méthode
appendChild(child) et accéder à ces enfants en utilisant la méthode children().
Il ne doit être possible d'ajouter un nœud que si le nœud parent et le nœud enfant
sont issues du même document.
var document = new DOMDocument();
var parent = document.createElement("foo");
var child = document.createElement("bar");
parent.appendChild(child);
System.out.println(parent.children()); // <bar></bar>
Expliquer pourquoi on souhaite que la liste renvoyée par children() soit non modifiable ?
Sachant cela, implanter les méthodes children() et appendChild(child).
Vérifier que les tests JUnit marqués "Q5" passent.
Note: pour l'implantation de la méthode children, on pourra utiliser une vue pour éviter
de dupliquer trop d'objets.
-
En fait, on a oublié quelque chose, l'affichage ne prend pas en compte les enfant.
var document = new DOMDocument();
var parent = document.createElement("foo");
var child = document.createElement("bar");
parent.appendChild(child);
System.out.println(parent); // <foo><bar></bar></foo>
Modifier l'affichage pour que le code ci-dessus fonctionne.
Vérifier que les tests JUnit marqués "Q6" passent.
Note: comment éviter d'allouer plein de chaines de caractères intermédiaires ?
-
En fait, si on demande plusieurs fois l'affichage, on va à chaque fois recalculer celui-ci,
on souhaite améliorer cela en ajoutant un cache pour que le calcul pour un noeud
ne soit pas fait plus que de nécessaire.
Pour cela, on ajoute un champ cache qui va contenir la chaine de caractères correspondant à l'affichage,
donc si on demande de faire l'affichage plusieurs fois, la même chaine de caractères va être renvoyée.
Modifier votre code pour implanter cette amélioration.
Vérifier que les tests JUnit marqués "Q7" passent.
Note : votre implantation a surement un bug que l'on ne corrigera qu'à la question 9 ...
-
L'implantation de appendChild a un bug, un nœud peut appartenir à plusieurs parents,
par exemple en écrivant.
var document = new DOMDocument();
var child = document.createElement("bar");
var parent1 = document.createElement("parent1");
var parent2 = document.createElement("parent2");
parent1.appendChild(child);
parent2.appendChild(child);
La specification de appendChild indique que lorsque l'on ajoute un nœud à un parent,
si celui-ci a déjà un parent, alors il est d'abord retiré de l'ancien parent avant d'être ajouté
en tant qu'enfant du nouveau parent.
Implanter ce comportement.
Vérifier que les tests JUnit marqués "Q8" passent.
-
[Revision] Enfin, pour les plus balèzes, il reste deux problèmes à corriger
-
Avec createElement, on peut créer un cycle, avec un nœud étant son propre parent, il faut détecter
ces cas (il peut aussi être son grand parent, etc) et planter.
-
Lorsque l'on ajoute/retire un noeud, le cache n'est plus valide, il faut donc invalider les caches impactés
(pas tous !).
Modifier votre code pour corriger ses deux problèmes.
Vérifier que les tests JUnit marqués "Q9" passent.
Note: si vous ne voyez pas comment faire, ce n'est pas grave, vous pourrez revenir sur cette question
pendant vos révisions.
© Université de Marne-la-Vallée