Subversion Repositories eFlore/Applications.cel

Rev

Rev 639 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
199 david 1
package org.tela_botanica.client.vues.observation.filtres;
91 jpm 2
 
3
import java.util.Comparator;
4
import java.util.Iterator;
5
 
683 aurelien 6
import org.tela_botanica.client.Util;
91 jpm 7
import org.tela_botanica.client.interfaces.Filtrable;
8
import org.tela_botanica.client.interfaces.Rafraichissable;
9
import org.tela_botanica.client.modeles.DateObservation;
639 aurelien 10
import org.tela_botanica.client.modeles.ListeDate;
91 jpm 11
import org.tela_botanica.client.modeles.Observation;
12
import org.tela_botanica.client.observation.ObservationMediateur;
13
 
14
import com.google.gwt.core.client.GWT;
15
import com.gwtext.client.data.Node;
16
import com.gwtext.client.data.NodeTraversalCallback;
17
import com.gwtext.client.data.Tree;
18
import com.gwtext.client.widgets.Component;
19
import com.gwtext.client.widgets.Panel;
20
import com.gwtext.client.widgets.event.PanelListenerAdapter;
21
import com.gwtext.client.widgets.tree.TreeNode;
22
import com.gwtext.client.widgets.tree.TreePanel;
23
import com.gwtext.client.widgets.tree.event.TreeNodeListenerAdapter;
24
import com.gwtext.client.widgets.tree.event.TreePanelListenerAdapter;
25
import com.gwtext.client.core.EventObject;
26
 
27
/**
28
 * fenêtre de recherche affichant l'arbre des mots clés en lecture et un bouton
29
 * cliquable
30
 *
31
 * @author aurelien
32
 *
33
 */
34
public class ArbreDateObservationFiltreVue extends Panel implements Rafraichissable,
35
		Filtrable {
36
 
37
	/**
38
	 * Le médiateur associé à la vue
39
	 */
40
	private ObservationMediateur	observationMediateur		= null;
41
 
42
	/**
43
	 * Les localites en cours
44
	 */
45
	private String donneesDateEnCours = "";
46
 
47
	/**
48
	 * Le treepanel qui affiche l'arbre
49
	 */
50
	private TreePanel arbreDonneesDates = null;
51
 
52
	/**
53
	 * La structure de donnees qui stocke l'arbre. Utilisee a ce niveau car trop liee a la vue
54
	 */
55
 
56
	private Tree donneesDates = new Tree();
57
 
58
	/**
59
	 * booléen d'initialisation
60
	 */
61
	private boolean estInstancie = false;
62
 
63
	/**
64
	 * booléen d'etat
65
	 */
66
	private boolean filtreModifie = false;
67
 
68
	private boolean arbreCharge = false ;
69
 
70
	private String nomFiltre = "" ;
71
 
72
	/**
73
	 * Constructeur sans argument (privé car ne doit pas être utilisé)
74
	 */
75
	@SuppressWarnings("unused")
76
	private ArbreDateObservationFiltreVue() {
77
		super();
78
	}
79
 
80
	/**
81
	 * Constructeur avec paramètres
82
	 *
83
	 * @param im
84
	 *            le médiateur à associer
85
	 */
86
	public ArbreDateObservationFiltreVue(ObservationMediateur obs) {
87
 
88
		// on crée le panel
89
		super("Dates");
90
 
91
		this.observationMediateur = obs;
92
 
93
		arbreDonneesDates = new TreePanel();
94
 
95
		this.setPaddings(5);
96
 
97
		this.setCollapsible(true);
202 david 98
		this.setAutoScroll(true);
99 jpm 99
 
91 jpm 100
		// on ajoute les listeners
101
		ajouterListenersPanel();
102
		estInstancie = false;
103
	}
104
 
105
	/**
106
	 * Ajoute les listeners pour le rendu du panel
107
	 */
108
	private void ajouterListenersPanel() {
109
		  this.addListener(new PanelListenerAdapter() {
110
 
111
			// on instancie réellement les composants au moment du rendu pour
112
			// accélérer l'affichage
113
			// et éviter des bugs
114
			public void onRender(Component component) {
115
 
116
				// on interdit le drag and drop dans l'arbre
117
				arbreDonneesDates.setEnableDD(false);
118
				arbreDonneesDates.setId("x-view-tree-filter-date");
119
 
202 david 120
 
91 jpm 121
				arbreDonneesDates.setBorder(false);
122
 
123
				// on crée une racine pour l'arbre
124
				TreeNode root = new TreeNode("Dates");
125
				root.setId("racine_date");
166 aurelien 126
				String[] usObject = { "Dates", "Dates", "racine_date" };
91 jpm 127
				root.setUserObject(usObject);
128
 
129
				arbreDonneesDates.setRootNode(root);
130
				arbreDonneesDates.setRootVisible(true);
131
				arbreDonneesDates.setBorder(false);
132
				root.setExpandable(true) ;
133
 
134
				add(arbreDonneesDates);
135
 
136
				// on ajoute les listeners d'évenements
137
				ajouterListeners();
138
 
139
 
140
				// enfin on considère le composant comme instancié
141
				estInstancie = true;
142
 
143
 
144
			}
145
 
146
		});
147
	}
148
 
149
 
150
 
151
	/**
152
	 * ajoute les listeners pour les boutons et le cochage des entites
153
	 */
154
	private void ajouterListeners() {
155
 
156
		arbreDonneesDates.addListener(new TreePanelListenerAdapter() {
157
 
158
			public void onClick(TreeNode node, EventObject e) {
683 aurelien 159
				gererClicNoeud(node);
91 jpm 160
			}
161
 
162
		}) ;
163
 
164
		arbreDonneesDates.getRootNode().addListener(new TreeNodeListenerAdapter() {
165
 
166
			public void onExpand(Node node) {
167
				if(!arbreCharge)
168
				{
169
					observationMediateur.obtenirDatesObservation() ;
170
					arbreCharge = true ;
171
				}
172
			}
173
 
174
		}) ;
175
	}
140 aurelien 176
 
683 aurelien 177
	private void gererClicNoeud(TreeNode node) {
178
 
179
		mettreAJourValeurEnCours(node);
180
		observationMediateur.obtenirNombreObservation() ;
181
	}
182
 
183
	private void mettreAJourValeurEnCours(TreeNode node) {
184
 
185
		nomFiltre = "" ;
186
		donneesDateEnCours = "" ;
187
		String nomPere = "" ;
188
		String nomGrandPere = "" ;
189
 
190
		switch(node.getDepth())
191
		{
192
			case 0:
193
				if(arbreCharge) {
194
					nomFiltre = "";
195
					donneesDateEnCours = "";
196
				}
197
				break;
198
			case 3: nomFiltre += "annee,mois,jour";
199
				nomPere = ((String[])node.getParentNode().getUserObject())[1] ;
200
				nomGrandPere = ((String[])node.getParentNode().getParentNode().getUserObject())[1] ;
201
				donneesDateEnCours += nomGrandPere+","+nomPere+","+((String[])node.getUserObject())[1] ;
202
				break;
203
			case 2: nomFiltre += "annee,mois";
204
				nomPere = ((String[])node.getParentNode().getUserObject())[1] ;
205
				donneesDateEnCours += nomPere+","+((String[])node.getUserObject())[1] ;
206
				break;
207
			case 1: nomFiltre += "annee";
208
				donneesDateEnCours += ((String[])node.getUserObject())[1] ;
209
				break;
210
			default:
211
				break;
212
		}
213
 
214
		filtreModifie = true ;
215
	}
216
 
140 aurelien 217
	public void initialiser() {
218
 
219
		arbreCharge = false ;
263 aurelien 220
		donneesDateEnCours = "";
140 aurelien 221
		arbreDonneesDates.collapseAll();
222
		// on vide l'ancien arbre
223
		Node[] rootChild = arbreDonneesDates.getRootNode().getChildNodes();
224
		for (int i = 0; i < rootChild.length; i++) {
225
 
226
			rootChild[i].remove();
227
		}
228
 
229
		arbreDonneesDates.getRootNode().addListener(new TreeNodeListenerAdapter() {
230
 
231
			public void onExpand(Node node) {
232
				if(!arbreCharge)
233
				{
234
					observationMediateur.obtenirDatesObservation() ;
235
					arbreCharge = true ;
236
				}
237
			}
238
 
239
		}) ;
240
	}
91 jpm 241
 
242
	/**
243
	 * Méthode héritée de l'interface rafraichissable
244
	 */
245
	public void rafraichir(Object nouvelleDonnees,
246
			boolean repandreRaffraichissement) {
247
 
639 aurelien 248
		if (nouvelleDonnees instanceof ListeDate) {
140 aurelien 249
 
91 jpm 250
			String annee=null;
251
			String mois=null;
252
			String jour=null;
253
 
639 aurelien 254
			ListeDate data = (ListeDate) nouvelleDonnees ;
140 aurelien 255
 
91 jpm 256
				// on crée un arbre vide
257
				TreeNode root = new TreeNode();
258
				root.setId("racine_date");
259
				root.setText("Dates");
166 aurelien 260
				String[] usObjRoot = { "Dates", "Dates", "racine_date"};
91 jpm 261
				root.setUserObject(usObjRoot);
166 aurelien 262
				Tree nouvelArbre = new Tree();
263
				nouvelArbre.setRootNode(root);
91 jpm 264
 
265
			// on la parse et on récupère les informations qui nous interessent
266
			for (Iterator<String> it= data.keySet().iterator(); it.hasNext();) {
267
 
268
				DateObservation ent=(DateObservation) data.get(it.next());
269
 
270
				annee= ent.getAnnee() ;
271
				mois= ent.getMois() ;
683 aurelien 272
				String moisLettre = Util.renvoyerMois(Integer.parseInt(mois)) ;
91 jpm 273
				jour= ent.getJour() ;
274
 
275
				if(annee.contains("0000")) {
276
					annee="Inconnue" ;
277
				}
278
 
279
				if(jour.contains("00")) {
280
					jour="Inconnue" ;
281
				}
282
 
166 aurelien 283
				Node noeudMemeAnnee = nouvelArbre.getNodeById(""+annee);
91 jpm 284
				// si la région existe déjà
94 jpm 285
					if(noeudMemeAnnee != null)
91 jpm 286
					{
287
						// on teste si la localité existe
166 aurelien 288
						Node noeudMemeMois = nouvelArbre.getNodeById(""+(annee+mois));
94 jpm 289
						if(noeudMemeMois != null)
91 jpm 290
						{
291
							// enfin on teste si le lieu dit existe
166 aurelien 292
							Node noeudMemeJour = nouvelArbre.getNodeById(""+(annee+mois+jour));
94 jpm 293
							if(noeudMemeJour != null)
91 jpm 294
							{
295
								// tous les noeuds existent déjà, normalement ça ne devrait pas arriver
296
							}
297
							else
298
							{
299
								// enfin on ne crée que le noeud du lieu dit
94 jpm 300
								TreeNode node_jour = new TreeNode();
301
								node_jour.setId(""+(annee+mois+jour));
302
								node_jour.setText(jour);
303
								noeudMemeMois.appendChild(node_jour) ;
166 aurelien 304
								String[] usObj = {jour,jour,annee+mois+jour};
94 jpm 305
								node_jour.setUserObject(usObj);
91 jpm 306
							}
307
						}
308
						else
309
						{
94 jpm 310
							TreeNode node_mois = new TreeNode();
311
							node_mois.setId(""+(annee+mois));
312
							node_mois.setText(moisLettre);
313
							noeudMemeAnnee.appendChild(node_mois) ;
166 aurelien 314
							String[] usObj = {moisLettre,mois,annee+mois};
94 jpm 315
							node_mois.setUserObject(usObj);
91 jpm 316
 
94 jpm 317
							TreeNode node_jour = new TreeNode();
318
							node_jour.setId(""+(annee+mois+jour));
319
							node_jour.setText(jour);
320
							node_mois.appendChild(node_jour) ;
166 aurelien 321
							String[] usObj2 = {jour,jour,annee+mois+jour};
94 jpm 322
							node_jour.setUserObject(usObj2);
91 jpm 323
 
324
						}
325
					}
326
					else
327
					{
94 jpm 328
						TreeNode node_annee = new TreeNode();
329
						node_annee.setId(""+annee);
330
						node_annee.setText(annee);
331
						root.appendChild(node_annee) ;
166 aurelien 332
						String[] usObj = {annee, annee, annee};
94 jpm 333
						node_annee.setUserObject(usObj);
91 jpm 334
 
94 jpm 335
						TreeNode node_mois = new TreeNode();
336
						node_mois.setId(""+(annee+mois));
337
						node_mois.setText(moisLettre);
338
						node_annee.appendChild(node_mois) ;
166 aurelien 339
						String[] usObj2 = {moisLettre,mois,annee+mois};
94 jpm 340
						node_mois.setUserObject(usObj2);
91 jpm 341
 
94 jpm 342
						TreeNode node_jour = new TreeNode();
343
						node_jour.setId(""+(annee+mois+jour));
344
						node_jour.setText(jour);
345
						node_mois.appendChild(node_jour) ;
166 aurelien 346
						String[] usObj3 = {jour,jour,annee+mois+jour};
94 jpm 347
						node_jour.setUserObject(usObj3);
91 jpm 348
					}
349
 
350
				}
351
 
166 aurelien 352
 
353
				// on trie
354
				root.sort(comparerNoeuds()) ;
355
 
91 jpm 356
				// on vide tous les noeuds
357
				arbreDonneesDates.getRootNode().eachChild(new NodeTraversalCallback() {
358
 
359
					public boolean execute(Node node) {
360
 
361
						node.remove();
362
						return true;
363
					}
364
 
365
				});
366
 
367
				// et on recopie le nouvel arbre
166 aurelien 368
				copierFilsNoeud(nouvelArbre.getRootNode(), arbreDonneesDates
91 jpm 369
						.getRootNode());
94 jpm 370
 
91 jpm 371
				// si l'arbre n'était pas encore considéré comme instancié
372
				if (!estInstancie) {
373
					// on signale que oui
374
					estInstancie = true;
375
				}
109 aurelien 376
 
91 jpm 377
 
378
				// l'état du filtre est réinitialisé
379
				filtreModifie = false;
380
				//show() ;
381
				doLayout();
382
 
383
			}
384
 
94 jpm 385
		if(nouvelleDonnees instanceof Observation)
128 aurelien 386
		{
387
			// si l'arbre n'est pas encore chargé, on ne tient pas compte de l'ajout
388
			// l'arbre complet sera de toute façon renvoyé plus tard lors du premier chargement
389
			// de l'arbre
390
			if(!arbreCharge) {
391
				return;
392
			}
393
 
94 jpm 394
			Observation ent = (Observation)nouvelleDonnees ;
395
			String dateSpl[] = ent.getDate().split("/") ;
91 jpm 396
 
94 jpm 397
			String annee= dateSpl[2];
398
			String mois= dateSpl[1];
399
			String jour= dateSpl[0];
400
 
683 aurelien 401
			String moisLettre = Util.renvoyerMois(Integer.parseInt(mois)) ;
91 jpm 402
 
403
			Node root = arbreDonneesDates.getRootNode() ;
404
 
405
			if(annee.contains("0000") || annee.equals(null)) {
406
				annee="Inconnue" ;
407
			}
408
 
409
			if(jour.contains("00") || jour.equals(null)) {
410
				jour="Inconnue" ;
411
			}
412
 
166 aurelien 413
			Node noeudMemeAnnee = arbreDonneesDates.getNodeById(""+annee);
91 jpm 414
			// si la région existe déjà
94 jpm 415
				if(noeudMemeAnnee != null)
416
				{
91 jpm 417
					// on teste si la localité existe
166 aurelien 418
					Node noeudMemeMois = arbreDonneesDates.getNodeById(""+(annee+mois));
94 jpm 419
					if(noeudMemeMois != null)
91 jpm 420
					{
421
						// enfin on teste si le lieu dit existe
166 aurelien 422
						Node noeudMemeJour = arbreDonneesDates.getNodeById(""+(annee+mois+jour));
94 jpm 423
						if(noeudMemeJour != null)
91 jpm 424
						{
425
							// tous les noeuds existent déjà, normalement ça ne devrait pas arriver
426
						}
427
						else
428
						{
429
							// enfin on ne crée que le noeud du lieu dit
94 jpm 430
							TreeNode node_jour = new TreeNode();
431
							node_jour.setId(""+(annee+mois+jour));
432
							node_jour.setText(jour);
166 aurelien 433
							String[] usObj = {jour,jour,annee+mois+jour};
94 jpm 434
							node_jour.setUserObject(usObj);
435
							noeudMemeMois.appendChild(node_jour) ;
436
 
437
							root.sort(comparerNoeuds()) ;
91 jpm 438
						}
439
					}
440
					else
441
					{
94 jpm 442
						TreeNode node_mois = new TreeNode();
443
						node_mois.setId(""+(annee+mois));
444
						node_mois.setText(moisLettre);
166 aurelien 445
						String[] usObj = {moisLettre,mois,annee+mois};
94 jpm 446
						node_mois.setUserObject(usObj);
447
						noeudMemeAnnee.appendChild(node_mois) ;
91 jpm 448
 
94 jpm 449
						TreeNode node_jour = new TreeNode();
450
						node_jour.setId(""+(annee+mois+jour));
451
						node_jour.setText(jour);
166 aurelien 452
						String[] usObj2 = {jour,jour,annee+mois+jour};
94 jpm 453
						node_jour.setUserObject(usObj2);
454
						node_mois.appendChild(node_jour) ;
455
 
456
						root.sort(comparerNoeuds()) ;
91 jpm 457
					}
458
			}
459
			else
460
			{
461
 
94 jpm 462
				TreeNode node_annee = new TreeNode();
463
				node_annee.setId(""+annee);
464
				node_annee.setText(annee);
166 aurelien 465
				String[] usObj = {annee,annee,annee};
94 jpm 466
				node_annee.setUserObject(usObj);
467
				root.appendChild(node_annee) ;
91 jpm 468
 
94 jpm 469
				TreeNode node_mois = new TreeNode();
470
				node_mois.setId(""+(annee+mois));
471
				node_mois.setText(moisLettre);
166 aurelien 472
				String[] usObj2 = {moisLettre,mois,annee+mois};
94 jpm 473
				node_mois.setUserObject(usObj2);
474
				node_annee.appendChild(node_mois) ;
91 jpm 475
 
94 jpm 476
				TreeNode node_jour = new TreeNode();
477
				node_jour.setId(""+(annee+mois+jour));
478
				node_jour.setText(jour);
166 aurelien 479
				String[] usObj3 = {jour,jour,annee+mois+jour};
94 jpm 480
				node_jour.setUserObject(usObj3);
481
				node_mois.appendChild(node_jour) ;
91 jpm 482
 
483
				// TODO : améliorer la compararaison des noeuds
484
				root.sort(comparerNoeuds()) ;
485
			}
486
 
487
				arbreDonneesDates.doLayout() ;
488
		}
489
	}
490
 
491
 
492
	/**
493
	 * Accesseur pour le panneau contenant l'arbre
494
	 *
495
	 * @return le panneau de l'arbre des mots clés
496
	 */
497
	public TreePanel getArbreMotsCles() {
498
		return arbreDonneesDates;
499
	}
500
 
501
	/**
502
	 * Méthode héritée de Filtrable renvoie le nom du filtre
503
	 */
504
	public String renvoyerNomFiltre() {
505
 
506
		return "Dates";
507
	}
508
 
509
	/**
510
	 * Renvoie un tableau contenant le nom du champ à filtrer et la valeur
511
	 *
512
	 * @return un tableau contenant le nom du champ à filtrer et sa valeur
513
	 */
514
	public String[] renvoyerValeursAFiltrer() {
515
 
516
		valider();
517
 
518
		String valeursFiltrees[] = {nomFiltre, donneesDateEnCours } ;
519
 
520
		return valeursFiltrees;
521
	}
522
 
523
	/**
524
	 * Fonction récursive qui prend deux noeuds d'arbre en paramètre et crée un
525
	 * copie du sous arbre du premier noeud, qu'elle concatène au deuxième
526
	 *
527
	 * @param ndPereOriginal
528
	 *            le père des noeuds de l'arbre original
529
	 * @param ndPereCopie
530
	 *            le père qui va recevoir les copies
531
	 */
532
	private void copierFilsNoeud(Node ndPereOriginal, TreeNode ndPereCopie) {
533
		if (ndPereCopie != null && ndPereOriginal != null) {
534
			Node[] ndNodeFils = ndPereOriginal.getChildNodes();
535
 
536
			for (int i = 0; i < ndNodeFils.length; i++) {
537
 
538
				String[] usObj = (String[]) ndNodeFils[i].getUserObject();
539
				TreeNode child = new TreeNode(usObj[0]);
540
				child.setUserObject(usObj);
166 aurelien 541
				child.setId(""+usObj[2]);
91 jpm 542
				ndPereCopie.appendChild(child);
543
 
544
				if (!ndNodeFils[i].isLeaf()) {
545
					copierFilsNoeud(ndNodeFils[i], child);
546
				}
547
 
548
			}
549
		}
550
	}
551
 
552
	/**
553
	 * Méthode héritée de Filtrable Renvoie l'état du filtre (modifié ou non)
554
	 */
555
	public boolean renvoyerEtatFiltre() {
556
 
557
		return filtreModifie;
558
	}
559
 
560
	public void valider() {
561
 
562
		if (estInstancie) {
563
 
564
		}
565
	}
566
 
567
	public Comparator<TreeNode> comparerNoeuds()
568
	{
569
		return new Comparator<TreeNode>() {
570
 
571
			public int compare(TreeNode o1, TreeNode o2) {
572
 
94 jpm 573
				String n1 = ((String[])o1.getUserObject())[1] ;
574
				String n2 = ((String[])o2.getUserObject())[1] ;
91 jpm 575
 
576
				return n1.compareTo(n2) ;
577
			}
578
 
579
		} ;
580
	}
581
 
104 jpm 582
	public void raz() {
583
 
584
		arbreCharge = false ;
140 aurelien 585
		arbreDonneesDates.collapseAll();
586
		arbreDonneesDates.clear();
104 jpm 587
 
588
		TreeNode root = new TreeNode("Dates");
589
		root.setId("racine_date");
166 aurelien 590
		String[] usObject = { "Dates", "Dates", "racine_date" };
104 jpm 591
		root.setUserObject(usObject);
91 jpm 592
 
104 jpm 593
		arbreDonneesDates.setRootNode(root);
594
 
595
		arbreDonneesDates.getRootNode().addListener(new TreeNodeListenerAdapter() {
596
 
597
			public void onExpand(Node node) {
598
				if(!arbreCharge)
599
				{
600
					observationMediateur.obtenirDatesObservation() ;
601
					arbreCharge = true ;
602
				}
603
			}
604
 
605
		}) ;
606
 
607
		this.doLayout() ;
608
 
263 aurelien 609
		donneesDateEnCours = "";
610
 
104 jpm 611
	}
612
 
683 aurelien 613
	public void viderFiltre() {
614
		arbreDonneesDates.getSelectionModel().clearSelections();
615
		donneesDateEnCours = "";
616
	}
104 jpm 617
 
683 aurelien 618
	public void viderFiltre(String nom) {
619
 
620
		final int profondeur = calculerProfondeurPourNomFiltre(nom);
621
 
622
		// on vide tous les noeuds
623
		arbreDonneesDates.getRootNode().cascade(new NodeTraversalCallback() {
624
 
625
			public boolean execute(Node node) {
626
 
627
				boolean continuer = true;
628
 
629
				TreeNode noeudArbreEncours = (TreeNode)node;
630
 
631
				if(arbreDonneesDates.getSelectionModel().isSelected(noeudArbreEncours)) {
632
 
633
					int profondeurDepart = noeudArbreEncours.getDepth();
634
 
635
					for(int profondeurNoeudArbreEncours = profondeurDepart; profondeurNoeudArbreEncours >= profondeur; profondeurNoeudArbreEncours--) {
636
						noeudArbreEncours = (TreeNode)noeudArbreEncours.getParentNode();
637
					}
638
 
639
					arbreDonneesDates.getSelectionModel().select(noeudArbreEncours);
640
					mettreAJourValeurEnCours(noeudArbreEncours);
641
 
642
					continuer = false;
643
				}
644
 
645
				return continuer;
646
			}
647
 
648
		});
649
	}
650
 
651
	private int calculerProfondeurPourNomFiltre(String nom) {
652
 
653
		int profondeur = 0;
654
 
655
		if(nom.equals("annee")) {
656
			profondeur = 1;
657
		}
658
 
659
		if(nom.equals("mois")) {
660
			profondeur = 2;
661
		}
662
 
663
		if(nom.equals("jour")) {
664
			profondeur = 3;
665
		}
666
 
667
		return profondeur;
668
	}
91 jpm 669
}