/** Une solution au TP DA XML - implémentation avec une collection de type Map Olivier Capuoozzo - 13 septembre 2002 */ import java.io.*; import java.util.*; public class VentesCSVtoXMLFile { // format d'un ligne du fichier CSV : public;code;qte;;mois;annee[;commentaire] Map dicoProduitsMois; // une liste des produits mis à jour durant la lecture du fchier csv. String nameFileSource; // nom du fichier csv à traiter String nameFileDest; // nom du fichier résultat // construteur public VentesCSVtoXMLFile(String nameFile) { nameFileSource = nameFile; nameFileDest = changeExtension(nameFileSource, "xml"); dicoProduitsMois = new TreeMap(); } // définition d'une classe interne. // Une instance de la classe englobante pourra exercer un // acces direct aux attributs des objets de cette classe, même // si ceux-ci sont déclarés privé (private). class ProduitMois { private String _public; private String _code; private int _qte; private String _mois; private String _annee; private String _commentaire; private String _cle; ProduitMois(String[] infosproduit) { _public=infosproduit[0]; _code=infosproduit[1]; _qte=Integer.parseInt(infosproduit[2]); _mois=infosproduit[3]; _annee=infosproduit[4]; if (infosproduit.length>5) _commentaire=infosproduit[5]; else _commentaire=""; _cle = _code.trim().toUpperCase() + _mois.trim().toUpperCase() + _annee.trim().toUpperCase(); } void update(ProduitMois produitAvecMemeCle) { // assert(this._cle.equals(produitAvecMemeCle._cle)); this._qte += produitAvecMemeCle._qte; this._commentaire += produitAvecMemeCle._commentaire; } public String toString() { return " \r\n" +" "+ this._public + "\r\n" +" " + this._qte + "\r\n" +" " + this._code + "\r\n" +" " + this._mois + "\r\n" +" " + this._annee + "\r\n" +" " + this._commentaire + "\r\n" +" \r\n"; } public boolean equals(Object o) { if (this==o) return true; if (getClass()==o.getClass()) { ProduitMois p = (ProduitMois) o; return _cle.equals(p._cle); } return false; } }//ProduitMois // méthodes /** @param nameFile le nom du fichier initial @param newExt la nouvelle extension @return le nom du fichier initial avec une nouvelle extension. Dans le cas où il n'y a pas d'extension dans le nom initial, l'extension est ajoutée. exemple : changeExtension("toto.txt", "xml") rend "toto.xml" exemple : changeExtension("toto", "xml") rend "toto.xml" exemple : changeExtension("", "xml") rend ".xml" */ public String changeExtension(String nameFile, String newExt) { String res; int posPt = nameFile.lastIndexOf('.'); if (posPt > 0) res = nameFile.substring(0, posPt) + "." + newExt; else res = nameFile + "." + newExt; return res; } protected String debutXML(){ return " \r\n" + "\r\n" + "\r\n"; } protected String finXML(){ return "\r\n"; } /** Met à jour la collection des produits. @param nouveauProduitCandidat, un représentant d'une ligne du fichier csv Si un produit de meme cle est déja présent dans la collection, celui-ci est mis à jour, sinon nouveauProduitCandidat est ajouté à la collection. */ protected void updateDicoProduitsMois(ProduitMois nouveauProduitCandidat){ String cle = nouveauProduitCandidat._cle; if (dicoProduitsMois.containsKey(cle)) { ProduitMois produitMemeCle = (ProduitMois) dicoProduitsMois.get(cle); produitMemeCle.update(nouveauProduitCandidat); } else { dicoProduitsMois.put(cle, nouveauProduitCandidat); } } public void go() { try { FileReader fr = new FileReader(nameFileSource); BufferedReader br = new BufferedReader(fr); File file = new File(nameFileDest); if (file.exists()) { BufferedReader clav = new BufferedReader(new InputStreamReader(System.in)); System.out.print(nameFileDest + " existe, voulez-vous l'écraser ? (O/N) : "); String rep = clav.readLine(); if (rep == null) rep = "N"; if (!("O".equals(rep.toUpperCase()))){ return; } } PrintWriter out = new PrintWriter(new FileWriter(nameFileDest)); String line; while ((line = br.readLine()) != null) { updateDicoProduitsMois(new ProduitMois(line.split(";"))); } out.print(debutXML()); Iterator iterateur = dicoProduitsMois.values().iterator(); while (iterateur.hasNext()) { out.print(iterateur.next()); } out.print(finXML()); out.flush(); } catch (java.io.FileNotFoundException e){ System.out.println(nameFileSource + " : fichier introuvable."); } catch (ArrayIndexOutOfBoundsException e){ System.out.println(nameFileSource + " : mauvais format de fichier."); } catch (java.io.IOException e) { System.out.println(e); } } // go public static void main (String[] args) { if (args.length == 1) new VentesCSVtoXMLFile(args[0]).go(); else System.out.println("USAGE : " + new VentesCSVtoXMLFile("").getClass().getName() + " nomFivchierCSVventes"); } } // VentesCSVtoXMLFile /* /////////////////////////////////////////////// Répondez aux questions suivantes 1/ 1.1/ Quel est le rôle de l'attribut _cle de la classe ProduitMois ? ==> identifie un produit/mois (voir les données du problème) 1.2/ Pouvait-on s'en passer, comment ? (détailler votre réponse) ==> _cle est un attribut dérivé d'autres attribut (code, mois annee). ==> On peut proposer une méthode qui construise la clé à chaque appel. 1.3/ Expliquez les avantages et inconvénients de chacune des solutions ==> solution attribut : inconvénient : ajoute un attribut d'instance (prend de la place mémoire) necessite une écriture à chaque mise à jour de l'attribut avantage : ne doit pas être calculé à chaque lecture (plus rapide) ==> solution méthode : inconvénient : doit être caculé à chaque lecture (consomme du temps cpu) avantage : n'introduit pas un attribut d'instance suppémentaire (gain d'espace mémoire) Remarque : Constat bien connu en informatique où le temps et l'espace sont liés: le gain du temps tend à augmenter l'espace mémoire, et, inversement, une économie d'espace tend à consommer davantage de temps cpu. 1.4/ Donner les raisons d'en préférer une pour cette application. ==> la solution de l'attribut parait préférable car il n'est jamais mis à jour et de plus la surcharge d'occupation mémoire est négligeable et ephémère (pas de persistance). 2/ Donnez une implémentation de la méthode : void update(ProduitMois produitAvecMemeCle) de la classe ProduitMois. void update(ProduitMois produitAvecMemeCle) { // assert(this.getCle().equals(produitAvecMemeCle.getCle()) this.qte += produitAvecMemeCle._qte; this.commentaire += produitAvecMemeCle._commentaire; } Remarque : Il fallait penser à concaténer aussi le commentaire afin de ne pas predre d'information. 3/ Donnez une implémentation de la méthode : ProduitMois getProduitMoisMemeCleDansListe(ProduitMois unProd) de la classe VentesCSVtoXML. /** Recherche un produit ayant la même clé que le produit passé en argument. Un produit est identifié par son code, mois et année. Nous nommons _cle la concaténation de ces attributs dans la classe ProduitMois. @param unProd le produit à rechercher dans la liste. @return la référence au premier objet trouvé dans la liste ayant même clé que unProd. Retourne null si non trouvé. * / private ProduitMois getProduitMoisMemeCleDansListe(ProduitMois unProd) { int nbProd = listeProduitsMois.size(); for (int i=0; i < nbProd ; i++) { if (unProd.equals(listeProduitsMois.get(i))) return (ProduitMois) listeProduitsMois.get(i); } return null; } 4/ Dans quelle(s) condition(s), l'exception ArrayIndexOutOfBoundsException peut-elle être déclenchée ? Donner un exemple. Ce "test" vous parait-il suffisamment répondre au point 1 de l'extension du cas d'utilisation ? Donner un exemple. ==> L'exception est déclenchée dans le corps du constructeur ProduitMois, qui reçoit un tableau (array) de chaînes de caractères (String[] infosproduit). Il n'y a ici aucune précaution quant à la longueur de ce tableau, si ce n'est le test au bout de la cinquième instruction. Ce test est insuffisant, car, par exemple, si le fichier contient plus de champs que nécessaire et que les 5 premiers sont compatibles avec ceux attendus, le programme n'y verra que du feux. Le problème de conversion d'une chaîne en entier (NumberFormatException) n'est pas pris en compte. ... 5/ Déterminer quelle instruction déclenche l'exécution de la méthode toString() d'un objet de type ProduitMois. Justifier votre réponse en fournissant la chaîne des appels de méthodes menant à l'exécution de cette méthode. Aidez-vous de l'aide en ligne. méthode go() : while (iterateur.hasNext()) { out.print(iterateur.next()); } La méthode print de out est responsable de l'appel à toString(). La classe de l'objet out est: java.io.PrintWriter. L'aide en ligne nous informe que cette méthode traite une chaîne retournée par la méthode String.valueOf(Object). Allons voir ce que dit l'aide en ligne sur cette méthode de classe : public static String valueOf(Object obj) Returns : if the argument is null, then a string equal to "null"; otherwise, the value of obj.toString() is returned. C'est donc cette méthode de classe qui est responsable de l'appel de notre méthode toString(), définie dans Object, mais que nous avons redéfinie dans ProduitMois, sous-classe de Object. 6/ Quel(s) avantage(s) y a-t-il à utiliser une TreeMap au lieu d'un ArrayList ? La recherche de la présence d'une clé est pris en charge par le composant. Un TreeMap garantit un coût temps de l'odre de log(n) pour les opérations containsKey, get, put et remove, où n est le nombre d'élément dans la collection. (Voir doc en ligne). Un TreeMap est une sorte de dictionnaire, ce qui nous prémunit des doublons de clé dans la collection. Nous allégeons également le code en supprimant la méthode de recherche d'un produit dans la liste. On utilisera la méthode get après s'être assuré que l'objet recherché est bien là. (voir le code) */