Statistics
| Revision:

svn-gvsig-desktop / tags / v1_1_Build_1012 / libraries / libFMap / src / com / iver / cit / gvsig / fmap / layers / SelectableDataSource.java @ 12987

History | View | Annotate | Download (15.5 KB)

1 1100 fjp
/* gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
2
 *
3
 * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
18
 *
19
 * For more information, contact:
20
 *
21
 *  Generalitat Valenciana
22
 *   Conselleria d'Infraestructures i Transport
23
 *   Av. Blasco Ib??ez, 50
24
 *   46010 VALENCIA
25
 *   SPAIN
26
 *
27
 *      +34 963862235
28
 *   gvsig@gva.es
29
 *      www.gvsig.gva.es
30
 *
31
 *    or
32
 *
33
 *   IVER T.I. S.A
34
 *   Salamanca 50
35
 *   46005 Valencia
36
 *   Spain
37
 *
38
 *   +34 963163400
39
 *   dac@iver.es
40
 */
41 460 fernando
package com.iver.cit.gvsig.fmap.layers;
42
43 1828 fernando
import java.io.IOException;
44
45
import org.apache.log4j.Logger;
46 1836 fernando
import org.xml.sax.SAXException;
47 1828 fernando
48 5569 jmvivo
import com.hardcode.driverManager.Driver;
49 1828 fernando
import com.hardcode.driverManager.DriverLoadException;
50 460 fernando
import com.hardcode.gdbms.engine.data.DataSource;
51 546 fernando
import com.hardcode.gdbms.engine.data.DataSourceFactory;
52 6323 fjp
import com.hardcode.gdbms.engine.data.IDataSourceListener;
53 546 fernando
import com.hardcode.gdbms.engine.data.NoSuchTableException;
54 4863 fjp
import com.hardcode.gdbms.engine.data.SourceInfo;
55 1828 fernando
import com.hardcode.gdbms.engine.data.driver.DriverException;
56 2217 fernando
import com.hardcode.gdbms.engine.data.edition.DataWare;
57 1836 fernando
import com.hardcode.gdbms.engine.data.persistence.Memento;
58
import com.hardcode.gdbms.engine.data.persistence.MementoContentHandler;
59
import com.hardcode.gdbms.engine.data.persistence.MementoException;
60 2217 fernando
import com.hardcode.gdbms.engine.instruction.EvaluationException;
61 460 fernando
import com.hardcode.gdbms.engine.instruction.FieldNotFoundException;
62 1828 fernando
import com.hardcode.gdbms.engine.instruction.SemanticException;
63 460 fernando
import com.hardcode.gdbms.engine.values.Value;
64 2183 fernando
import com.hardcode.gdbms.engine.values.ValueCollection;
65 1828 fernando
import com.hardcode.gdbms.parser.ParseException;
66 4875 fjp
import com.iver.cit.gvsig.fmap.drivers.FieldDescription;
67 3963 caballero
import com.iver.cit.gvsig.fmap.layers.layerOperations.Selectable;
68 4875 fjp
import com.iver.utiles.NumberUtilities;
69 581 vcaballero
import com.iver.utiles.XMLEntity;
70 460 fernando
71
72 527 vcaballero
/**
73 1034 vcaballero
 * DataSource seleccionable.
74 527 vcaballero
 *
75 546 fernando
 * @author Fernando Gonz?lez Cort?s
76 527 vcaballero
 */
77 3963 caballero
public class SelectableDataSource implements DataSource,Selectable {
78 527 vcaballero
        private static Logger logger = Logger.getLogger(SelectableDataSource.class.getName());
79 460 fernando
        private SelectionSupport selectionSupport = new SelectionSupport();
80
        private DataSource dataSource;
81 4863 fjp
82
        private int[] mapping = null;
83 527 vcaballero
84 460 fernando
        /**
85 1034 vcaballero
         * Crea un nuevo SelectableDataSource.
86 527 vcaballero
         *
87 1034 vcaballero
         * @param name
88 460 fernando
         * @param ds
89 2565 fernando
         * @throws DriverException
90 460 fernando
         */
91 2565 fernando
        public SelectableDataSource(DataSource ds) throws DriverException {
92 460 fernando
                dataSource = ds;
93 11006 jmvivo
                // CHEMA: No deberiamos abrir el dataSource porque si
94
                // Lo tiene que hacer quien lo va a usar
95
                //dataSource.start();
96
97 4863 fjp
                // Creamos el mapping de campos externos que no muestran el PK.
98 6259 fjp
                mapExternalFields();
99
100
101
        }
102
103
        /**
104 6335 fjp
         * Maps real fields or "external" fields. We don't want to see virtual fields.
105 6259 fjp
         * @throws DriverException
106
         */
107
        public void mapExternalFields() throws DriverException {
108 11006 jmvivo
                //CHEMA: Abrimos y cerramos el dataSource para preparar
109
                //                el mapping
110
                this.dataSource.start();
111 4863 fjp
                int numExternalFields = 0;
112
                for (int i=0; i < dataSource.getFieldCount(); i++)
113
                {
114
                        if (!dataSource.isVirtualField(i))
115
                                numExternalFields++;
116
117
                }
118 3963 caballero
119 4863 fjp
                mapping = new int[numExternalFields];
120
                int j=0;
121
                for (int i=0; i < dataSource.getFieldCount(); i++)
122
                {
123
                        if (!dataSource.isVirtualField(i))
124
                                mapping[j++] = i;
125
126
                }
127 11006 jmvivo
                this.dataSource.stop();
128 460 fernando
        }
129 527 vcaballero
130 1836 fernando
        public static SelectableDataSource createSelectableDataSource(XMLEntity xml) throws NoSuchTableException, ParseException, DriverLoadException, DriverException, SemanticException, IOException, XMLException{
131 3963 caballero
132 1828 fernando
                SelectionSupport ss = new SelectionSupport();
133
                ss.setXMLEntity(xml.getChild(0));
134
                XMLEntity xmlDS = xml.getChild(1);
135 1836 fernando
                GDBMSParser parser = new GDBMSParser(xmlDS);
136
                MementoContentHandler gdbmsHandler = new MementoContentHandler();
137
                parser.setContentHandler(gdbmsHandler);
138
                try {
139
                        parser.parse();
140
                } catch (SAXException e) {
141
                        throw new XMLException(e);
142 1828 fernando
                }
143 2217 fernando
                SelectableDataSource sds;
144
        try {
145 11008 jmvivo
//                 CHEMA: AUTOMATIC DATA SOURCE
146
            //sds = new SelectableDataSource(gdbmsHandler.getDataSource(LayerFactory.getDataSourceFactory(), DataSourceFactory.MANUAL_OPENING));
147
            sds = new SelectableDataSource(gdbmsHandler.getDataSource(LayerFactory.getDataSourceFactory(), DataSourceFactory.AUTOMATIC_OPENING));
148 2217 fernando
        } catch (EvaluationException e1) {
149
            throw new XMLException(e1);
150
        }
151
        sds.selectionSupport=ss;
152 2183 fernando
                return sds;
153 1828 fernando
        }
154 1836 fernando
155 1828 fernando
        public void setDataSourceFactory(DataSourceFactory dsf) {
156
                dataSource.setDataSourceFactory(dsf);
157
        }
158 2217 fernando
        public void setSourceInfo(SourceInfo sourceInfo) {
159 1828 fernando
                dataSource.setSourceInfo(sourceInfo);
160
        }
161 460 fernando
        /**
162 1034 vcaballero
         * A?ade el soporte para la selecci?n.
163 527 vcaballero
         *
164 460 fernando
         * @param selectionSupport
165
         */
166
        public void setSelectionSupport(SelectionSupport selectionSupport) {
167
                this.selectionSupport = selectionSupport;
168
        }
169
170 527 vcaballero
        /**
171 1034 vcaballero
         * Devuelve el n?mero de campos.
172 527 vcaballero
         *
173 1034 vcaballero
         * @return N?mero de campos.
174 527 vcaballero
         *
175 1034 vcaballero
         * @throws DriverException
176 527 vcaballero
         */
177 460 fernando
        public int getFieldCount() throws DriverException {
178 4863 fjp
                // return dataSource.getFieldCount()-numVirtual;
179 6478 fjp
//                if (mapping.length != dataSource.getFieldCount())
180
//                {
181
//                        mapExternalFields();
182
//                        RuntimeException e = new RuntimeException("Recalculamos los campos de recordset!!");
183
//                        e.printStackTrace();
184
//                }
185 4863 fjp
                return mapping.length;
186 460 fernando
        }
187 527 vcaballero
188
        /**
189 8765 jjdelcerro
         * Return index field searching by its name
190 527 vcaballero
         *
191 8765 jjdelcerro
         * @param arg0 field name.
192 527 vcaballero
         *
193 8765 jjdelcerro
         * @return field index. -1 if not found
194 527 vcaballero
         *
195 1034 vcaballero
         * @throws DriverException
196
         * @throws FieldNotFoundException
197 527 vcaballero
         */
198
        public int getFieldIndexByName(String arg0)
199 1773 fernando
                throws DriverException {
200 4863 fjp
                int internal = dataSource.getFieldIndexByName(arg0);
201
                for (int i=0; i < mapping.length; i++)
202
                {
203
                        if (mapping[i] == internal)
204
                                return i;
205
                }
206
                return -1;
207 460 fernando
        }
208 527 vcaballero
209
        /**
210 1034 vcaballero
         * Devuelve el nombre del campo a partir del ?ndice.
211 527 vcaballero
         *
212 1034 vcaballero
         * @param arg0 ?ndice.
213 527 vcaballero
         *
214 1034 vcaballero
         * @return nombre del campo.
215 527 vcaballero
         *
216 1034 vcaballero
         * @throws DriverException
217 527 vcaballero
         */
218 460 fernando
        public String getFieldName(int arg0) throws DriverException {
219 4863 fjp
            // return dataSource.getFieldName(arg0);
220
                return dataSource.getFieldName(mapping[arg0]);
221 460 fernando
        }
222 527 vcaballero
223
        /**
224 1034 vcaballero
         * Devuelve el valor a partir del n?mro de fila y columna.
225 527 vcaballero
         *
226 1034 vcaballero
         * @param arg0 n?mero de registro.
227
         * @param arg1 n?mero de campo.
228 527 vcaballero
         *
229 1034 vcaballero
         * @return Valor.
230 527 vcaballero
         *
231 1034 vcaballero
         * @throws DriverException
232 527 vcaballero
         */
233 460 fernando
        public Value getFieldValue(long arg0, int arg1) throws DriverException {
234 4863 fjp
                return dataSource.getFieldValue(arg0, mapping[arg1]);
235
                // return dataSource.getFieldValue(arg0, arg1);
236 460 fernando
        }
237 527 vcaballero
238
        /**
239 1034 vcaballero
         * Devuelve el nombre del DataSource.
240 527 vcaballero
         *
241 1034 vcaballero
         * @return Nombre.
242 527 vcaballero
         */
243 460 fernando
        public String getName() {
244
                return dataSource.getName();
245
        }
246 527 vcaballero
247
        /**
248 2565 fernando
         * Devuelve el n?mero de filas en total.
249 527 vcaballero
         *
250 1034 vcaballero
         * @return n?mero de filas.
251 527 vcaballero
         *
252 1034 vcaballero
         * @throws DriverException
253 527 vcaballero
         */
254 460 fernando
        public long getRowCount() throws DriverException {
255
                return dataSource.getRowCount();
256
        }
257 527 vcaballero
258
        /**
259 1034 vcaballero
         * Inicializa el dataSource.
260 527 vcaballero
         *
261 1034 vcaballero
         * @throws DriverException
262 527 vcaballero
         */
263 460 fernando
        public void start() throws DriverException {
264 3076 fjp
                // logger.debug("dataSource.start()");
265 460 fernando
                dataSource.start();
266
        }
267 527 vcaballero
268
        /**
269 1034 vcaballero
         * Finaliza el DataSource.
270 527 vcaballero
         *
271 1034 vcaballero
         * @throws DriverException
272 527 vcaballero
         */
273 460 fernando
        public void stop() throws DriverException {
274 3076 fjp
                // logger.debug("dataSource.stop()");
275 460 fernando
                dataSource.stop();
276
        }
277
278
        /**
279 2183 fernando
         * A partir del XMLEntity se rellenan los atributos del DataSource.
280
         *
281
         * @param child
282
         */
283
        public void setXMLEntity03(XMLEntity child) {
284
                selectionSupport.setXMLEntity(child.getChild(0));
285
        }
286
287
        /**
288 527 vcaballero
         * Cuando ocurre un evento de cambio en la selecci?n, ?ste puede ser uno de
289
         * una gran cantidad de eventos. Con el fin de no propagar todos estos
290
         * eventos, se realiza la propagaci?n de manera manual al final de la
291
         * "r?faga" de eventos
292 460 fernando
         */
293
        public void fireSelectionEvents() {
294
                selectionSupport.fireSelectionEvents();
295
        }
296
297 527 vcaballero
        /**
298 1034 vcaballero
         * A?ade un nuevo Listener al SelectionSupport.
299 527 vcaballero
         *
300 1034 vcaballero
         * @param listener SelectionListener.
301 527 vcaballero
         */
302 460 fernando
        public void addSelectionListener(SelectionListener listener) {
303
                selectionSupport.addSelectionListener(listener);
304
        }
305
306 527 vcaballero
        /**
307 1034 vcaballero
         * Borra un Listener al SelectionSupport.
308 527 vcaballero
         *
309 1034 vcaballero
         * @param listener Listener a borrar.
310 527 vcaballero
         */
311 460 fernando
        public void removeSelectionListener(SelectionListener listener) {
312
                selectionSupport.removeSelectionListener(listener);
313
        }
314 527 vcaballero
315
        /**
316 1034 vcaballero
         * Borra la selecci?n.
317 527 vcaballero
         */
318 460 fernando
        public void clearSelection() {
319
                selectionSupport.clearSelection();
320
        }
321 527 vcaballero
322
        /**
323 1034 vcaballero
         * Develve un FBitSet con los ?ndices de los elementos seleccionados.
324 527 vcaballero
         *
325 1034 vcaballero
         * @return FBitset con los elementos seleccionados.
326 527 vcaballero
         */
327 884 fernando
        public FBitSet getSelection() {
328 460 fernando
                return selectionSupport.getSelection();
329
        }
330 1034 vcaballero
331
        /**
332
         * Devuelve el SelectionSupport.
333
         *
334
         * @return SelectinSuport.
335
         */
336
        public SelectionSupport getSelectionSupport() {
337 581 vcaballero
                return selectionSupport;
338
        }
339 1034 vcaballero
340 527 vcaballero
        /**
341 1034 vcaballero
         * Devuelve true si el elemento est? seleccionado.
342 527 vcaballero
         *
343 1034 vcaballero
         * @param recordIndex ?ndice del registro.
344 527 vcaballero
         *
345 1034 vcaballero
         * @return True si el registro est? seleccionado.
346 527 vcaballero
         */
347 460 fernando
        public boolean isSelected(int recordIndex) {
348
                return selectionSupport.isSelected(recordIndex);
349
        }
350 527 vcaballero
351
        /**
352 1034 vcaballero
         * Inserta una nueva selecci?n.
353 527 vcaballero
         *
354 1034 vcaballero
         * @param selection FBitSet.
355 527 vcaballero
         */
356 683 fernando
        public void setSelection(FBitSet selection) {
357 460 fernando
                selectionSupport.setSelection(selection);
358
        }
359 546 fernando
360 1828 fernando
        private void putMemento(XMLEntity xml) throws XMLException {
361
                try {
362 1836 fernando
                        GDBMSHandler handler = new GDBMSHandler();
363
                        Memento m = getMemento();
364
                        m.setContentHandler(handler);
365
                        m.getXML();
366
                        XMLEntity child = handler.getXMLEntity();
367 3963 caballero
368 1828 fernando
                        xml.addChild(child);
369
                } catch (MementoException e) {
370
                        throw new XMLException(e);
371 1836 fernando
                } catch (SAXException e) {
372
                        throw new XMLException(e);
373 1828 fernando
                }
374
        }
375
376 1034 vcaballero
        /**
377
         * Devuelve el XMLEntity con la informaci?n necesaria para reproducir el
378
         * DataSource.
379
         *
380
         * @return XMLEntity.
381 1828 fernando
         * @throws XMLException
382 1034 vcaballero
         */
383 1828 fernando
        public XMLEntity getXMLEntity() throws XMLException {
384 1034 vcaballero
                XMLEntity xml = new XMLEntity();
385 1094 vcaballero
                xml.putProperty("className",this.getClass().getName());
386 581 vcaballero
                xml.addChild(selectionSupport.getXMLEntity());
387 1828 fernando
                putMemento(xml);
388 1034 vcaballero
389 581 vcaballero
                return xml;
390
        }
391 732 fernando
392
        /**
393 884 fernando
         * @see com.hardcode.gdbms.engine.data.DataSource#getWhereFilter()
394
         */
395
        public long[] getWhereFilter() throws IOException {
396
                return dataSource.getWhereFilter();
397
        }
398 1653 fernando
399
        /**
400
         * @see com.hardcode.gdbms.engine.data.ReadDriver#getFieldType(int)
401
         */
402 1773 fernando
        public int getFieldType(int i) throws DriverException {
403 4863 fjp
                // return dataSource.getFieldType(i);
404
                return dataSource.getFieldType(mapping[i]);
405 1653 fernando
        }
406 1828 fernando
407
        /**
408
         * @see com.hardcode.gdbms.engine.data.DataSource#getDataSourceFactory()
409
         */
410
        public DataSourceFactory getDataSourceFactory() {
411
                return dataSource.getDataSourceFactory();
412
        }
413
414
        /**
415
         * @see com.hardcode.gdbms.engine.data.DataSource#getAsString()
416
         */
417
        public String getAsString() throws DriverException {
418
                return dataSource.getAsString();
419
        }
420 1831 fernando
421
        /**
422 2183 fernando
         * @throws DriverException
423 1831 fernando
         * @see com.hardcode.gdbms.engine.data.DataSource#remove()
424
         */
425 2183 fernando
        public void remove() throws DriverException {
426 1831 fernando
                dataSource.remove();
427
        }
428 1836 fernando
429
        /**
430
         * @see com.hardcode.gdbms.engine.data.DataSource#getMemento()
431
         */
432
        public Memento getMemento() throws MementoException {
433
                return dataSource.getMemento();
434
        }
435
436
        /**
437
         * @see com.hardcode.gdbms.engine.data.DataSource#getSourceInfo()
438
         */
439 2217 fernando
        public SourceInfo getSourceInfo() {
440 1836 fernando
                return dataSource.getSourceInfo();
441
        }
442 2183 fernando
443
    /**
444
     * @see com.hardcode.gdbms.engine.data.DataSource#getPrimaryKeys()
445
     */
446
    public int[] getPrimaryKeys() throws DriverException {
447
            return dataSource.getPrimaryKeys();
448
    }
449
450
    /**
451
     * @see com.hardcode.gdbms.engine.data.DataSource#getPKValue(long)
452
     */
453
    public ValueCollection getPKValue(long rowIndex) throws DriverException {
454
        return dataSource.getPKValue(rowIndex);
455
    }
456
457
    /**
458
     * @see com.hardcode.gdbms.engine.data.DataSource#getPKName(int)
459
     */
460
    public String getPKName(int fieldId) throws DriverException {
461
        return dataSource.getPKName(fieldId);
462
    }
463
464
    /**
465
     * @see com.hardcode.gdbms.engine.data.DataSource#getPKType(int)
466
     */
467
    public int getPKType(int i) throws DriverException {
468
        return dataSource.getPKType(i);
469
    }
470
471
    /**
472
     * @throws DriverException
473
     * @see com.hardcode.gdbms.engine.data.DataSource#getPKCardinality()
474
     */
475
    public int getPKCardinality() throws DriverException {
476
        return dataSource.getPKCardinality();
477
    }
478
479
    /**
480
     * @see com.hardcode.gdbms.engine.data.DataSource#getRow(long)
481
     */
482
    public Value[] getRow(long rowIndex) throws DriverException {
483 4863 fjp
            Value[] withoutVirtuals = new Value[mapping.length];
484
            Value[] internal = dataSource.getRow(rowIndex);
485
            for (int i=0; i < mapping.length; i++)
486
            {
487
                    withoutVirtuals[i] = internal[mapping[i]];
488
489
            }
490
        return withoutVirtuals;
491 2183 fernando
    }
492
493
    /**
494
     * @see com.hardcode.gdbms.engine.data.DataSource#getFieldNames()
495
     */
496
    public String[] getFieldNames() throws DriverException {
497 4863 fjp
            String[] fieldNames = new String[getFieldCount()];
498
                int j=0;
499
                for (int i=0; i < dataSource.getFieldCount(); i++)
500
                {
501
                        if (!dataSource.isVirtualField(i))
502
                                fieldNames[j++] = dataSource.getFieldName(i);
503
504
                }
505
        // return dataSource.getFieldNames();
506
            return fieldNames;
507 2183 fernando
    }
508
509
    /**
510
     * @see com.hardcode.gdbms.engine.data.DataSource#getPKNames()
511
     */
512
    public String[] getPKNames() throws DriverException {
513
        return dataSource.getPKNames();
514
    }
515
516
        public void removeLinksSelectionListener() {
517
                selectionSupport.removeLinkSelectionListener();
518
        }
519 2217 fernando
520
    /**
521 4053 fjp
     * @throws DriverException
522 2217 fernando
     * @see com.hardcode.gdbms.engine.data.DataSource#getDataWare(int)
523
     */
524 4053 fjp
    public DataWare getDataWare(int arg0) throws DriverException {
525 2217 fernando
        return dataSource.getDataWare(arg0);
526
    }
527 4863 fjp
528
        public int getFieldWidth(int i) throws DriverException {
529
                return dataSource.getFieldWidth(mapping[i]);
530
                // return dataSource.getFieldWidth(i);
531
        }
532
533
        public boolean isVirtualField(int fieldId) throws DriverException {
534
                return dataSource.isVirtualField(fieldId);
535
        }
536 4875 fjp
537
        /**
538
         * Useful to writers, to know the field definitions.
539
         * NOTE: Maximun precision: 6 decimals. (We may need to change this)
540
         * @return Description of non virtual fields
541
         * @throws DriverException
542
         */
543
        public FieldDescription[] getFieldsDescription() throws DriverException
544
        {
545
                int numFields = getFieldCount();
546
                FieldDescription[] fieldsDescrip = new FieldDescription[numFields];
547
                for (int i = 0; i < numFields; i++) {
548
                        fieldsDescrip[i] = new FieldDescription();
549
                        int type = getFieldType(i);
550
                        fieldsDescrip[i].setFieldType(type);
551
                        fieldsDescrip[i].setFieldName(getFieldName(i));
552
                        fieldsDescrip[i].setFieldLength(getFieldWidth(i));
553 6483 fjp
                        if (NumberUtilities.isNumeric(type))
554
                        {
555
                                if (!NumberUtilities.isNumericInteger(type))
556
                                        // TODO: If there is a lost in precision, this should be changed.
557
                                        fieldsDescrip[i].setFieldDecimalCount(6);
558
                        }
559
                        else
560 4875 fjp
                                fieldsDescrip[i].setFieldDecimalCount(0);
561 6628 fjp
                        // TODO: ?DEFAULTVALUE?
562
                        // fieldsDescrip[i].setDefaultValue(get)
563 4875 fjp
                }
564
                return fieldsDescrip;
565
        }
566 5569 jmvivo
567
        public Driver getDriver() {
568
                return this.dataSource.getDriver();
569
        }
570 6323 fjp
571
        public void reload() throws DriverException, IOException {
572
                dataSource.reload();
573 6326 fjp
                mapExternalFields();
574 6323 fjp
575
        }
576
577
        public void addDataSourceListener(IDataSourceListener listener) {
578
                dataSource.addDataSourceListener(listener);
579
580
        }
581
582
        public void removeDataSourceListener(IDataSourceListener listener) {
583
                dataSource.removeDataSourceListener(listener);
584
585
        }
586 460 fernando
}