Subversion Repositories eFlore/Applications.cel

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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