Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.dal / org.gvsig.fmap.dal.file / org.gvsig.fmap.dal.file.csv / src / main / java / org / gvsig / fmap / dal / store / csv / CSVStoreProvider.java @ 44947

History | View | Annotate | Download (46.4 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License as published by the Free Software
8
 * Foundation; either version 3 of the License, or (at your option) any later
9
 * version.
10
 *
11
 * This program is distributed in the hope that it will be useful, but WITHOUT
12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14
 * details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with
17
 * this program; if not, write to the Free Software Foundation, Inc., 51
18
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 *
20
 * For any additional information, do not hesitate to contact us at info AT
21
 * gvsig.com, or visit our website www.gvsig.com.
22
 */
23
package org.gvsig.fmap.dal.store.csv;
24

    
25
import java.io.File;
26
import java.io.FileInputStream;
27
import java.io.FileNotFoundException;
28
import java.io.FileReader;
29
import java.io.IOException;
30
import java.io.InputStreamReader;
31
import java.net.URI;
32
import java.net.URL;
33
import java.nio.charset.Charset;
34
import java.util.ArrayList;
35
import java.util.HashMap;
36
import java.util.Iterator;
37
import java.util.List;
38
import java.util.Locale;
39
import java.util.Map;
40

    
41
import org.apache.commons.io.FilenameUtils;
42
import org.apache.commons.io.IOUtils;
43
import org.apache.commons.lang3.StringUtils;
44
import org.cresques.cts.IProjection;
45
import org.gvsig.fmap.dal.DALLocator;
46
import org.gvsig.fmap.dal.DataManager;
47
import org.gvsig.fmap.dal.DataServerExplorer;
48
import org.gvsig.fmap.dal.DataStore;
49
import org.gvsig.fmap.dal.DataStoreNotification;
50
import org.gvsig.fmap.dal.DataTypes;
51
import org.gvsig.fmap.dal.FileHelper;
52
import org.gvsig.fmap.dal.exception.DataException;
53
import org.gvsig.fmap.dal.exception.InitializeException;
54
import org.gvsig.fmap.dal.exception.OpenException;
55
import org.gvsig.fmap.dal.exception.ReadException;
56
import org.gvsig.fmap.dal.exception.ValidateDataParametersException;
57
import org.gvsig.fmap.dal.feature.EditableFeature;
58
import org.gvsig.fmap.dal.feature.EditableFeatureAttributeDescriptor;
59
import org.gvsig.fmap.dal.feature.EditableFeatureType;
60
import org.gvsig.fmap.dal.feature.Feature;
61
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
62
import org.gvsig.fmap.dal.feature.FeatureAttributeEmulator;
63
import org.gvsig.fmap.dal.feature.FeatureSet;
64
import org.gvsig.fmap.dal.feature.FeatureStore;
65
import org.gvsig.fmap.dal.feature.FeatureType;
66
import org.gvsig.fmap.dal.feature.exception.PerformEditingException;
67
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
68
import org.gvsig.fmap.dal.feature.spi.FeatureStoreProviderServices;
69
import org.gvsig.fmap.dal.feature.spi.memory.AbstractMemoryStoreProvider;
70
import org.gvsig.fmap.dal.resource.ResourceAction;
71
import org.gvsig.fmap.dal.resource.file.FileResource;
72
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
73
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
74
import org.gvsig.fmap.dal.serverexplorer.filesystem.FilesystemServerExplorer;
75
import org.gvsig.fmap.dal.serverexplorer.filesystem.FilesystemServerExplorerParameters;
76
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
77
import org.gvsig.fmap.dal.store.csv.AutomaticDetectionOfTypes.DetectedValue;
78
import org.gvsig.fmap.dal.store.csv.simplereaders.CSVReader;
79
import org.gvsig.fmap.dal.store.csv.simplereaders.FixedLenReader;
80
import org.gvsig.fmap.dal.store.csv.simplereaders.SimpleReader;
81
import org.gvsig.fmap.geom.Geometry;
82
import org.gvsig.fmap.geom.GeometryLocator;
83
import org.gvsig.fmap.geom.GeometryManager;
84
import org.gvsig.fmap.geom.GeometryUtils;
85
import org.gvsig.fmap.geom.aggregate.MultiPoint;
86
import org.gvsig.fmap.geom.primitive.Envelope;
87
import org.gvsig.fmap.geom.primitive.Point;
88
import org.gvsig.fmap.geom.type.GeometryType;
89
import org.gvsig.tools.ToolsLocator;
90
import org.gvsig.tools.dataTypes.DataType;
91
import org.gvsig.tools.dataTypes.DataTypesManager;
92
import org.gvsig.tools.dataTypes.Coercion;
93
import org.gvsig.tools.dispose.DisposableIterator;
94
import org.gvsig.tools.dynobject.Tags;
95
import org.gvsig.tools.dynobject.exception.DynFieldNotFoundException;
96
import org.gvsig.tools.evaluator.AbstractEvaluator;
97
import org.gvsig.tools.evaluator.EvaluatorData;
98
import org.gvsig.tools.evaluator.EvaluatorException;
99
import org.gvsig.tools.exception.BaseException;
100
import org.gvsig.tools.exception.NotYetImplemented;
101
import org.gvsig.tools.persistence.PersistentState;
102
import org.gvsig.tools.persistence.exception.PersistenceException;
103
import org.gvsig.tools.task.SimpleTaskStatus;
104
import org.gvsig.tools.task.TaskStatusManager;
105
import org.gvsig.tools.visitor.VisitCanceledException;
106
import org.gvsig.tools.visitor.Visitor;
107
import org.slf4j.Logger;
108
import org.slf4j.LoggerFactory;
109
import org.supercsv.prefs.CsvPreference;
110
import org.gvsig.tools.dataTypes.CoercionContext;
111

    
112
@SuppressWarnings("UseSpecificCatch")
113
public class CSVStoreProvider extends AbstractMemoryStoreProvider implements
114
        ResourceConsumer {
115

    
116
    private static final Logger LOGGER = LoggerFactory.getLogger(CSVStoreProvider.class);
117

    
118
    public static final String NAME = DataStore.CSV_PROVIDER_NAME;
119
    public static final String DESCRIPTION = "CSV file";
120

    
121
    public static final String METADATA_DEFINITION_NAME = NAME;
122

    
123
    private ResourceProvider resource;
124

    
125
    private long counterNewsOIDs = 0;
126
    private Envelope envelope;
127
    private boolean need_calculate_envelope = false;
128
    private final SimpleTaskStatus taskStatus;
129
    private final CSVFeatureWriter writer;
130

    
131
    public CSVStoreProvider(CSVStoreParameters parameters,
132
            DataStoreProviderServices storeServices) throws InitializeException {
133
        super(
134
                parameters,
135
                storeServices,
136
                FileHelper.newMetadataContainer(METADATA_DEFINITION_NAME)
137
        );
138
        this.writer = new CSVFeatureWriter();
139
        TaskStatusManager manager = ToolsLocator.getTaskStatusManager();
140
        this.taskStatus = manager.createDefaultSimpleTaskStatus("CSV");
141

    
142
        counterNewsOIDs = 0;
143

    
144
        File file = getCSVParameters().getFile();
145
        resource = this.createResource(
146
                FileResource.NAME,
147
                new Object[]{file.getAbsolutePath()}
148
        );
149

    
150
        resource.addConsumer(this);
151

    
152
        initializeFeatureTypes();
153
    }
154

    
155
    private CSVStoreParameters getCSVParameters() {
156
        return (CSVStoreParameters) this.getParameters();
157
    }
158

    
159
    @Override
160
    public String getProviderName() {
161
        return NAME;
162
    }
163

    
164
    @Override
165
    public boolean allowWrite() {
166
        return true;
167
    }
168

    
169
    private String getFullFileName() {
170
        // Usar solo para mostrar mensajes en el logger.
171
        String s;
172
        try {
173
            s = getCSVParameters().getFile().getAbsolutePath();
174
        } catch (Exception e2) {
175
            s = "(unknow)";
176
        }
177
        return s;
178
    }
179

    
180
    @Override
181
    public void open() throws OpenException {
182
        if (this.data != null) {
183
            return;
184
        }
185
        this.data = new ArrayList<>();
186
        resource.setData(new HashMap());
187
        counterNewsOIDs = 0;
188
        try {
189
            loadFeatures();
190
        } catch (RuntimeException e) {
191
            LOGGER.debug("Can't load features from CSV '" + getFullFileName() + "'.", e);
192
            throw e;
193
        } catch (Exception e) {
194
            LOGGER.debug("Can't load features from CSV '" + getFullFileName() + "'.", e);
195
            throw new RuntimeException(e);
196
        }
197
    }
198

    
199
    @Override
200
    public DataServerExplorer getExplorer() throws ReadException {
201
        DataManager manager = DALLocator.getDataManager();
202
        FilesystemServerExplorerParameters params;
203
        try {
204
            params = (FilesystemServerExplorerParameters) manager
205
                    .createServerExplorerParameters(FilesystemServerExplorer.NAME);
206
            params.setRoot(this.getCSVParameters().getFile().getParent());
207
            return manager.openServerExplorer(FilesystemServerExplorer.NAME, params);
208
        } catch (DataException e) {
209
            throw new ReadException(this.getProviderName(), e);
210
        } catch (ValidateDataParametersException e) {
211
            throw new ReadException(this.getProviderName(), e);
212
        }
213

    
214
    }
215

    
216
    @Override
217
    public void performChanges(Iterator deleteds, Iterator inserteds, Iterator updateds, Iterator originalFeatureTypesUpdated) throws PerformEditingException {
218

    
219
        try {
220
            this.taskStatus.add();
221
            taskStatus.message("_preparing");
222
            this.getResource().closeRequest();
223
            getResource().execute(new ResourceAction() {
224
                @Override
225
                public Object run() throws Exception {
226
                    FeatureSet features = null;
227
                    DisposableIterator it = null;
228
                    try {
229
                        File file = (File) resource.get();
230

    
231
                        features
232
                                = getStoreServices().getFeatureStore()
233
                                        .getFeatureSet();
234
                        List<FeatureProvider> newdata = new ArrayList<FeatureProvider>();
235
                        FeatureType ftype = getStoreServices().getDefaultFeatureType();
236
                        writer.initialize(getCSVParameters(), file, ftype, getCSVPreferences());
237
                        writer.begin();
238
                        it = features.fastIterator();
239
                        taskStatus.setRangeOfValues(0, 0);
240
                        long counter = 0;
241
                        while (it.hasNext()) {
242
                            taskStatus.setCurValue(counter++);
243
                            FeatureProvider feature = getStoreServices().getFeatureProviderFromFeature(
244
                                    (org.gvsig.fmap.dal.feature.Feature) it.next());
245
                            writer.add(feature);
246
                            if (feature.getOID() == null) {
247
                                LOGGER.warn("feature without OID");
248
                                feature.setOID(createNewOID());
249
                            }
250
                            newdata.add(feature);
251
                        }
252
                        data = newdata;
253
                        if (writer.getEnvelope() != null) {
254
                            envelope = writer.getEnvelope().getGeometry().getEnvelope();
255
                        }
256
                        resource.notifyChanges();
257
                        writer.end();
258
                    } finally {
259
                        if (it != null) {
260
                            it.dispose();
261
                        }
262
                        if (features != null) {
263
                            features.dispose();
264
                        }
265
                    }
266
                    return null;
267
                }
268

    
269
            });
270
            this.taskStatus.terminate();
271
        } catch (Exception e) {
272
            this.taskStatus.abort();
273
            throw new PerformEditingException(getResource().toString(), e);
274
        } finally {
275
            this.taskStatus.remove();
276
        }
277
    }
278

    
279
    private CsvPreference getCSVPreferences() {
280
        CSVReader reader = new CSVReader(getCSVParameters());
281
        return reader.getCSVPreferences();
282
    }
283

    
284
    @Override
285
    public boolean closeResourceRequested(ResourceProvider resource) {
286
        return true;
287
    }
288

    
289
    @Override
290
    public int getOIDType() {
291
        return DataTypes.LONG;
292
    }
293

    
294
    @Override
295
    public boolean supportsAppendMode() {
296
        return true;
297
    }
298

    
299
    @Override
300
    public void append(final FeatureProvider featureProvider) {
301
        //todo
302
        getResource().execute(new ResourceAction() {
303
            @Override
304
            public Object run() throws Exception {
305
                //writer.append(getStoreServices().createFeature(featureProvider));
306
                writer.add(featureProvider);
307
                return null;
308
            }
309
        });
310
    }
311

    
312
    @Override
313
    public void beginAppend() throws DataException {
314
        this.close();
315
        getResource().execute(new ResourceAction() {
316
            public Object run() throws Exception {
317
                writer.initialize(
318
                        getCSVParameters(),
319
                        getCSVParameters().getFile(),
320
                        getFeatureStore().getDefaultFeatureType(),
321
                        getCSVPreferences()
322
                );
323
                writer.beginAppend();
324
                return null;
325
            }
326
        });
327

    
328
    }
329

    
330
    @Override
331
    public void endAppend() {
332
        try {
333
            getResource().execute(new ResourceAction() {
334
                public Object run() throws Exception {
335
                    writer.end();
336
                    resource.notifyChanges(); //resourcesNotifyChanges();
337
                    counterNewsOIDs = -1;
338
                    return null;
339
                }
340
            });
341
            writer.end();
342
        } catch (PerformEditingException ex) {
343
            LOGGER.warn("Not been able to end append '" + this.getFullName() + "'.", ex);
344
        }
345
    }
346

    
347
    public void saveToState(PersistentState state) throws PersistenceException {
348
        throw new NotYetImplemented();
349
    }
350

    
351
    public void loadFromState(PersistentState state) throws PersistenceException {
352
        throw new NotYetImplemented();
353
    }
354

    
355
    @Override
356
    public Object createNewOID() {
357
        return counterNewsOIDs++;
358
    }
359

    
360
    protected void initializeFeatureTypes() throws InitializeException {
361
        try {
362
            this.open();
363
        } catch (OpenException e) {
364
            throw new InitializeException(this.getProviderName(), e);
365
        }
366
    }
367

    
368
    @Override
369
    public Envelope getEnvelope() throws DataException {
370
        this.open();
371
        if (this.envelope != null) {
372
            return this.envelope;
373
        }
374
        if (!this.need_calculate_envelope) {
375
            return null;
376
        }
377
        FeatureStore fs = this.getFeatureStore();
378
        FeatureType ft = fs.getDefaultFeatureType();
379
        FeatureAttributeDescriptor fad = ft.getAttributeDescriptor(ft.getDefaultGeometryAttributeIndex());
380

    
381
        try {
382
            this.envelope = GeometryLocator.getGeometryManager().createEnvelope(fad.getGeomType().getSubType());
383
            fs.accept(new Visitor() {
384
                @Override
385
                public void visit(Object obj) throws VisitCanceledException, BaseException {
386
                    Feature f = (Feature) obj;
387
                    Geometry geom = f.getDefaultGeometry();
388
                    if (geom != null) {
389
                        envelope.add(geom.getEnvelope());
390
                    }
391
                }
392
            });
393
        } catch (BaseException e) {
394
            LOGGER.warn("Can't calculate the envelope of CSV file '" + this.getFullName() + "'.", e);
395
            this.envelope = null;
396
        }
397

    
398
        this.need_calculate_envelope = false;
399
        return this.envelope;
400
    }
401

    
402
    @Override
403
    public Object getDynValue(String name) throws DynFieldNotFoundException {
404
        if (DataStore.METADATA_ENVELOPE.equalsIgnoreCase(name)) {
405
            try {
406
                return this.getEnvelope();
407
            } catch (DataException e) {
408
                return null;
409
            }
410
        } else {
411
            if (DataStore.METADATA_CRS.equalsIgnoreCase(name)) {
412
                IProjection pro = CSVStoreParameters.getCRS(this.getCSVParameters());
413
                if (pro != null) {
414
                    return pro;
415
                }
416
            }
417
        }
418
        return super.getDynValue(name);
419
    }
420

    
421
    @Override
422
    public void resourceChanged(ResourceProvider resource) {
423
        this.getStoreServices().notifyChange(
424
                DataStoreNotification.RESOURCE_CHANGED,
425
                resource);
426
    }
427

    
428
    @Override
429
    public Object getSourceId() {
430
        return this.getCSVParameters().getFile();
431
    }
432

    
433
    @Override
434
    public String getName() {
435
        String name = this.getCSVParameters().getFile().getName();
436
        return FilenameUtils.getBaseName(name);
437
    }
438

    
439
    @Override
440
    public String getFullName() {
441
        return this.getCSVParameters().getFile().getAbsolutePath();
442
    }
443

    
444
    @Override
445
    public ResourceProvider getResource() {
446
        return resource;
447
    }
448

    
449
    private boolean isEmpty(String s) {
450
        if (s == null) {
451
            return true;
452
        }
453
        return s.trim().length() == 0;
454
    }
455

    
456
    private void init(CSVStoreParameters parameters, DataStoreProviderServices storeServices) {
457
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
458
    }
459
    
460
    public static class FieldTypeParser {
461

    
462
        public String name = null;
463
        public int type = DataTypes.STRING;
464
        public int size = 0;
465
        public int geomType = Geometry.TYPES.GEOMETRY;
466
        public int geomSubtype = Geometry.SUBTYPES.GEOM2D;
467
        public Map<String,String> tags = new HashMap<>();
468
        public Map<String,String> assignments = new HashMap<>();
469
        
470
        // Valores obtenidos desde la deteccion automatica desde los datos
471
        public DetectedValue detectedValue = null ;
472

    
473
        private String typename = "string";
474

    
475
        private final String fullFileName;
476
        private final String providerName;
477
        
478
        FieldTypeParser(String providerName, String fullFileName) {
479
            this.providerName = providerName;
480
            this.fullFileName = fullFileName;
481
        }
482

    
483
        public String getProviderName() {
484
            return this.providerName;
485
        }
486
        
487
        public String getFullFileName() {
488
            return this.fullFileName;
489
        }
490
        
491
        public void clear() {
492
            this.name = null;
493
            this.type = DataTypes.STRING;
494
            this.size = 0;
495
            this.tags = new HashMap<>();
496
            this.assignments = new HashMap<>();
497
        }
498

    
499
        public void copyFrom(FieldTypeParser other) {
500
            this.name = other.name;
501
            this.type = other.type;
502
            this.size = other.size;
503
            this.tags = new HashMap<>();
504
            this.tags.putAll(other.tags);
505
            this.assignments = new HashMap<>();
506
            this.assignments.putAll(other.assignments);
507
        }
508

    
509
        private int getType(String value) {
510
            DataTypesManager dataTypesManager = ToolsLocator.getDataTypesManager();
511
            return dataTypesManager.getType(typename);
512
        }
513

    
514
        private String[] split(String value, String separators) {
515
            int firstSeparatorPosition = 1000000;
516
            Character sep = null;
517
            for (char ch : separators.toCharArray()) {
518
                int pos = value.indexOf(ch);
519
                if( pos>0 && pos<firstSeparatorPosition ) {
520
                    sep = ch;
521
                    firstSeparatorPosition = pos;
522
                }
523
            }
524
            if( sep == null ) {
525
                return new String[] { value };
526
            }
527
            return value.split("["+sep+"]");
528
        }
529

    
530
        @SuppressWarnings("UseSpecificCatch")
531
        public boolean parse(String value) {
532
            String[] args;
533
            if ( value.contains("__") ) {
534
                args = value.split("__");
535
            } else {
536
                args = split(value, ":/#@!;-");
537
                if( args.length == 1 ) {
538
                    this.name = value;
539
                    return true;
540
                }
541
            }                        
542
            int n = 0;
543
            this.name = StringUtils.trim(args[n++]);
544
            if( n >= args.length ) {
545
                return true;
546
            }
547
            this.typename = StringUtils.trim(args[n++]);
548
            this.type = this.getType(this.typename);
549
            if ( this.type == DataTypes.INVALID ) {
550
                this.geomType = GeometryUtils.getGeometryType(this.typename);
551
                if( this.geomType==Geometry.TYPES.UNKNOWN )  {
552
                    this.type = DataTypes.STRING;
553
                    LOGGER.info("Type '" + this.typename + "' not valid for attribute '" + value + "' in '"+getProviderName()+"' file '" + getFullFileName() + "'.");
554
                } else {
555
                    this.typename = "GEOMETRY";
556
                    this.type = DataTypes.GEOMETRY;
557
                }
558
            }
559
            this.size = 0;
560

    
561
            while (n < args.length) {
562
                String option = StringUtils.trim(args[n++].toLowerCase());
563
                switch (option) {
564
                    case "size":
565
                        try {
566
                            this.size = Integer.parseInt(args[n++]);
567
                        } catch (Exception ex) {
568
                            LOGGER.warn("Ignore incorrect field size for field " + value + " in '"+getProviderName()+"' file '" + getFullFileName() + "'.", ex);
569
                        }
570
                        break;
571
                    case "tag": {
572
                            String x = StringUtils.trim(args[n++]);
573
                            int pos = x.indexOf("=");
574
                            if( pos < 0 ) {
575
                                this.tags.put(x, null);
576
                            } else {
577
                                this.tags.put( 
578
                                        StringUtils.trim(StringUtils.substring(x, 0, pos)),
579
                                        StringUtils.trim(StringUtils.substring(x, pos+1))
580
                                );
581
                            }
582
                            break;
583
                        }
584
                    case "set": {
585
                            String x = StringUtils.trim(args[n++]);
586
                            int pos = x.indexOf("=");
587
                            if( pos < 0 ) {
588
                                this.assignments.put(x, null);
589
                            } else {
590
                                this.assignments.put( 
591
                                        StringUtils.trim(StringUtils.substring(x, 0, pos)),
592
                                        StringUtils.trim(StringUtils.substring(x, pos+1))
593
                                );
594
                            }
595
                            break;
596
                        }
597
                    default:
598
                        if( StringUtils.isNumeric(option) && this.size == 0 ) {
599
                          try {
600
                              this.size = Integer.parseInt(option);
601
                          } catch (Exception ex) {
602
                              LOGGER.warn("Ignore incorrect field size for field " + value + " in '"+getProviderName()+"' file '" + getFullFileName() + "'.", ex);
603
                          }
604
                          break;
605
                        }
606
                        LOGGER.warn("Illegal argumente '"+option+"' for field '"+this.name+"' in '"+getProviderName()+"' file '" + getFullFileName() + "' ("+value+").");
607
                }
608
            }
609
            return true;
610
        }
611

    
612
    }
613

    
614
    private EditableFeatureType getFeatureType(String headers[], AutomaticDetectionOfTypes.DetectedValue automaticTypes[]) {
615
        EditableFeatureType fType = getStoreServices().createFeatureType(this.getName());
616
        fType.setHasOID(true);
617

    
618
        FieldTypeParser[] fieldTypes = new FieldTypeParser[headers.length];
619
        //
620
        // Calculamos cuales pueden ser los tipos de datos
621
        //
622
        for (int i = 0; i < fieldTypes.length; i++) {
623
            fieldTypes[i] = new FieldTypeParser(getProviderName(), getFullFileName());
624
        }
625

    
626
        // Asuminos los tipos pasados por parametro, que se supone
627
        // son los detectados automaticamente.
628
        if (automaticTypes != null) {
629
            for (int i = 0; i < fieldTypes.length && i < automaticTypes.length; i++) {
630
                fieldTypes[i].detectedValue = automaticTypes[i];
631
                fieldTypes[i].type = automaticTypes[i].getType();
632
            }
633
        }
634
        // Luego probamos con lo que diga las cabezeras del CVS, sobreescribiendo
635
        // los tipos anteriores en caso de definirse en la cabezara.
636
        for (int i = 0; i < fieldTypes.length; i++) {
637
            if (!fieldTypes[i].parse(headers[i])) {
638
                continue;
639
            }
640

    
641
        }
642

    
643
        // Y por ultimo hacemos caso a lo que se haya especificado en los parametros
644
        // de apertura del CSV, teniendo esto prioridad sobre todo.
645
        String param_types_def = CSVStoreParameters.getRawFieldTypes(this.getParameters());
646
        if (StringUtils.isNotBlank(param_types_def)) {
647
            String sep = CSVStoreParameters.getDelimiter(param_types_def);
648
            if (StringUtils.isNotBlank(sep)) {
649
                String[] param_types = param_types_def.split(sep);
650
                FieldTypeParser parser = new FieldTypeParser(getProviderName(), getFullFileName());
651
                for (String param_type : param_types) {
652
                    parser.clear();
653
                    parser.parse(param_type);
654
                    for (FieldTypeParser fieldType : fieldTypes) {
655
                        if (StringUtils.equalsIgnoreCase(fieldType.name, parser.name)) {
656
                            fieldType.copyFrom(parser);
657
                            break;
658
                        }
659
                    }
660
                }
661
            }
662
        }
663
        //
664
        // Una vez ya sabemos los tipos de datos rellenamos el feature-type
665
        //
666
        for (FieldTypeParser fieldType : fieldTypes) {
667
            EditableFeatureAttributeDescriptor fad = fType.add(fieldType.name, fieldType.type);
668
            if( fieldType.detectedValue!=null ) {
669
                fad.setDisplaySize(Math.max(fieldType.detectedValue.getDisplaySize(), fieldType.size));
670
                fad.setSize(Math.max(fieldType.detectedValue.getDisplaySize(), fieldType.size));
671
                if( fad.getPrecision()<fieldType.detectedValue.getPrecision() ) {
672
                    fad.setPrecision(fieldType.detectedValue.getPrecision());
673
                }
674
                if( fad.getScale()<fieldType.detectedValue.getScale()) {
675
                    fad.setScale(fieldType.detectedValue.getScale());
676
                }
677
            } else {
678
                fad.setDisplaySize(fieldType.size);
679
            }
680
            if (fieldType.type == DataTypes.GEOMETRY ) {
681
                fad.setGeometryType(fieldType.geomType, fieldType.geomSubtype);
682
                if( fType.getDefaultGeometryAttributeName() == null ) {
683
                    fType.setDefaultGeometryAttributeName(fieldType.name);
684
                }
685
            }
686
            Locale locale = CSVStoreParameters.getLocale(this.getParameters());
687
            fad.setLocale(locale);
688
            for (Map.Entry<String, String> entry : fieldType.assignments.entrySet()) {
689
                try {
690
                    switch(entry.getKey().toLowerCase()) {
691
                        case "expression":
692
                            // Los campos calculados los procesamos en una segunda
693
                            // pasada, cuando ya estan definidos el resto de los campos
694
                            // ya que pueden requerir campos que aun no se han definido.
695
                            break;
696
                        default:
697
                            fad.set(entry.getKey(), entry.getValue());
698
                    }
699
                } catch (Exception ex) {
700
                    LOGGER.warn("Can't set property '"+entry.getKey()+"' of '"+fad.getName()+"'.", ex);
701
                }
702
            }            
703
            Tags tags = fad.getTags();
704
            for (Map.Entry<String, String> entry : fieldType.tags.entrySet()) {
705
                tags.set(entry.getKey(), entry.getValue());
706
            }
707
        }
708
        // Processamos ahora los campos calculados
709
        for (FieldTypeParser fieldType : fieldTypes) {
710
            EditableFeatureAttributeDescriptor fad = fType.getEditableAttributeDescriptor(fieldType.name);
711
            for (Map.Entry<String, String> entry : fieldType.assignments.entrySet()) {
712
                try {
713
                    switch(entry.getKey().toLowerCase()) {
714
                        case "expression":
715
                            fad.set(entry.getKey(), entry.getValue());
716
                            break;
717
                    }
718
                } catch (Exception ex) {
719
                    LOGGER.warn("Can't set property '"+entry.getKey()+"' in '"+fad.getName()+"' of '"+getFullFileName()+"'.", ex);
720
                }
721
            }
722
        }
723
        String[] pointDimensionNames = CSVStoreParameters.getPointDimensionNames(this.getParameters());
724
        if ( pointDimensionNames != null ) {
725
            PointAttributeEmulator emulator = new PointAttributeEmulator(pointDimensionNames);
726
            String columnName = CSVStoreParameters.getPointColumnName(this.getParameters());
727
            if( StringUtils.isBlank(columnName) ) {
728
                columnName = "the_geom";
729
            }
730
            EditableFeatureAttributeDescriptor attr = fType.add(columnName, DataTypes.GEOMETRY, emulator);
731
            GeometryManager geommgr = GeometryLocator.getGeometryManager();
732
            GeometryType gt;
733
            try {
734
                if ( emulator.fieldNames != null && emulator.fieldNames.length <= 2 ) {
735
                        gt = geommgr.getGeometryType(Geometry.TYPES.GEOMETRY, Geometry.SUBTYPES.GEOM2D);
736
                } else {
737
                        gt = geommgr.getGeometryType(Geometry.TYPES.GEOMETRY, Geometry.SUBTYPES.GEOM3D);
738
                }
739
                attr.setGeometryType(gt);
740
            } catch (Exception e) {
741
                LOGGER.warn("Can't set geometry type for the calculated field in '"+getProviderName()+"' file '" + getFullFileName() + "'.", e);
742
            }
743
        }        
744
        
745
        String geometry_column = CSVStoreParameters.getGeometryColumn(this.getParameters());
746
        if (!StringUtils.isEmpty(geometry_column)) {
747
            EditableFeatureAttributeDescriptor attr = (EditableFeatureAttributeDescriptor) fType.get(geometry_column);
748
            if (attr != null && attr.getType() != DataTypes.GEOMETRY) {
749
                attr.setDataType(DataTypes.GEOMETRY);
750
                GeometryManager geommgr = GeometryLocator.getGeometryManager();
751
                GeometryType gt;
752
                try {
753
                    gt = geommgr.getGeometryType(Geometry.TYPES.GEOMETRY, Geometry.SUBTYPES.UNKNOWN);
754
                    attr.setGeometryType(gt);
755
                } catch (Exception e) {
756
                    LOGGER.warn("Can't set geometry type for the calculated field in CSV file '" + getFullFileName() + "'.", e);
757
                }
758
                fType.setDefaultGeometryAttributeName(geometry_column);
759
            }
760
        }
761
        return fType;
762
    }
763

    
764
    static class PointAttributeEmulator implements FeatureAttributeEmulator {
765

    
766
        private static final Logger logger = LoggerFactory.getLogger(ToPointEvaluaror.class);
767

    
768
        private static final int XNAME = 0;
769
        private static final int YNAME = 1;
770
        private static final int ZNAME = 2;
771

    
772
        private final GeometryManager geommgr;
773
        private final String[] fieldNames;
774
        private final Coercion toDouble;
775
        private final DataType dataType;
776
        private int errorcount = 0;
777

    
778
        public PointAttributeEmulator(String[] pointDimensionNames) {
779
            if (pointDimensionNames.length > 2) {
780
                this.fieldNames = new String[3];
781
                this.fieldNames[ZNAME] = pointDimensionNames[2];
782
            } else {
783
                this.fieldNames = new String[2];
784
            }
785
            this.fieldNames[XNAME] = pointDimensionNames[0];
786
            this.fieldNames[YNAME] = pointDimensionNames[1];
787
            this.geommgr = GeometryLocator.getGeometryManager();
788
            DataTypesManager datatypeManager = ToolsLocator.getDataTypesManager();
789

    
790
            this.toDouble = datatypeManager.getCoercion(DataTypes.DOUBLE);
791
            this.dataType = datatypeManager.get(DataTypes.GEOMETRY);
792
        }
793

    
794
        @Override
795
        public Object get(Feature feature) {
796
            try {
797
                Object valueX = feature.get(this.fieldNames[XNAME]);
798
                valueX = toDouble.coerce(valueX);
799
                if (valueX == null) {
800
                    return null;
801
                }
802
                Object valueY = feature.get(this.fieldNames[YNAME]);
803
                valueY = toDouble.coerce(valueY);
804
                if (valueY == null) {
805
                    return null;
806
                }
807
                Object valueZ = null;
808
                if (this.fieldNames.length > 2) {
809
                    valueZ = toDouble.coerce(feature.get(this.fieldNames[ZNAME]));
810
                    if (valueZ == null) {
811
                        return null;
812
                    }
813
                }
814

    
815
                double x = ((Double) valueX);
816
                double y = ((Double) valueY);
817
                Point point = geommgr.createPoint(x, y, Geometry.SUBTYPES.GEOM3D);
818
                if (this.fieldNames.length > 2) {
819
                    double z = ((Double) valueZ);
820
                    point.setCoordinateAt(2, z);
821
                }
822
                return point;
823
            } catch (Exception ex) {
824
                if (++errorcount < 5) {
825
                    logger.warn("[" + errorcount + "] Can't create point in CSV provider. XNAME='"
826
                            + this.fieldNames[XNAME] + "', YNAME='" + this.fieldNames[XNAME] + "' feature=" + feature.toString(), ex);
827
                }
828
                return null;
829
            }
830
        }
831

    
832
        public void set(EditableFeature feature, Object value) {
833
            if (value == null) {
834
                return;
835
            }
836
            Point point = null;
837
            if (value instanceof MultiPoint) {
838
                point = (Point) ((MultiPoint) value).getPrimitiveAt(0);
839
            } else {
840
                point = (Point) value;
841
            }
842
            feature.set(this.fieldNames[XNAME], point.getX());
843
            feature.set(this.fieldNames[YNAME], point.getY());
844
            if (this.fieldNames.length > 2) {
845
                feature.set(this.fieldNames[ZNAME], point.getCoordinateAt(2));
846
            }
847
        }
848

    
849
        @Override
850
        public boolean allowSetting() {
851
            return true;
852
        }
853

    
854
        @Override
855
        public String[] getRequiredFieldNames() {
856
            return this.fieldNames;
857
        }
858

    
859
    }
860

    
861
    static class ToPointEvaluaror extends AbstractEvaluator {
862

    
863
        private static final Logger logger = LoggerFactory.getLogger(ToPointEvaluaror.class);
864

    
865
        private GeometryManager geommgr = null;
866
        private String xname = null;
867
        private String yname = null;
868
        private String zname = null;
869
        private Coercion toDouble;
870
        private int errorcount = 0;
871

    
872
        ToPointEvaluaror(String[] pointDimensionNames) {
873
            this.xname = pointDimensionNames[0];
874
            this.yname = pointDimensionNames[1];
875
            if (pointDimensionNames.length > 2) {
876
                this.zname = pointDimensionNames[2];
877
            }
878
            this.geommgr = GeometryLocator.getGeometryManager();
879
            this.toDouble = ToolsLocator.getDataTypesManager().getCoercion(DataTypes.DOUBLE);
880
        }
881

    
882
        @Override
883
        public Object evaluate(EvaluatorData data) throws EvaluatorException {
884
            try {
885
                double x = ((Double) toDouble.coerce(data.getDataValue(xname))).doubleValue();
886
                double y = ((Double) toDouble.coerce(data.getDataValue(yname))).doubleValue();
887
                Point point = geommgr.createPoint(x, y, Geometry.SUBTYPES.GEOM3D);
888
                if (zname != null) {
889
                    double z = ((Double) toDouble.coerce(data.getDataValue(zname))).doubleValue();
890
                    point.setCoordinateAt(2, z);
891
                }
892
                return point;
893
            } catch (Exception ex) {
894
                if (++errorcount < 5) {
895
                    logger.warn("[" + errorcount + "] Can't create point in CSV provider. XNAME='"
896
                            + xname + "', YNAME='" + yname + "', ZNAME='" + zname + "', data=" + data.toString());
897
                }
898
                return null;
899
            }
900
        }
901

    
902
        @Override
903
        public String getName() {
904
            return "ToPointEvaluaror";
905
        }
906

    
907
    }
908

    
909
    private SimpleReader getSimpleReader(InputStreamReader in) {
910
        SimpleReader reader;
911
        if (CSVStoreParameters.getRawFieldsDefinition(getCSVParameters()) != null) {
912
            reader = new FixedLenReader(in, getCSVParameters());
913
        } else {
914
            reader = new CSVReader(in, getCSVParameters());
915
        }
916
        return reader;
917
    }
918

    
919
    private String getFixedHeader(int column) {
920
        char[] header = new char[3];
921

    
922
        String s = String.format("%03d", column);
923
        header[0] = (char) (s.charAt(0) + 17);
924
        header[1] = (char) (s.charAt(1) + 17);
925
        header[2] = (char) (s.charAt(2) + 17);
926
        return String.valueOf(header);
927
    }
928

    
929
    private String[] getFixedHeaders(int count) {
930
        String[] headers = new String[count];
931
        for (int i = 0; i < headers.length; i++) {
932
            headers[i] = getFixedHeader(i);
933
        }
934
        return headers;
935
    }
936

    
937
    private InputStreamReader openFile(File f, String charsetName) throws FileNotFoundException {
938
        Charset charset = Charset.defaultCharset();
939
        FileInputStream fis = new FileInputStream(f);
940
        if (!StringUtils.isEmpty(charsetName)) {
941
            if (Charset.isSupported(charsetName)) {
942
                try {
943
                    charset = Charset.forName(charsetName);
944
                } catch (Throwable th) {
945
                    LOGGER.warn("Can't use charset '" + charsetName + "' for read csv '" + this.getFullFileName() + "'.", th);
946
                }
947
            } else {
948
                LOGGER.warn("charset '" + charsetName + "' not supported for read csv '" + this.getFullFileName() + "'.");
949
            }
950
        }
951
        InputStreamReader isr = new InputStreamReader(fis, charset);
952
        return isr;
953
    }
954

    
955
    private void loadFeatures() {
956
        InputStreamReader in = null;
957
        SimpleReader reader = null;
958
        try {
959
            String headers[] = null;
960
            FeatureStoreProviderServices store = this.getStoreServices();
961

    
962
            boolean ignore_errors = CSVStoreParameters.getIgnoreErrors(getCSVParameters());
963

    
964
            in = openFile(
965
                    this.getCSVParameters().getFile(),
966
                    CSVStoreParameters.getCharset(this.getCSVParameters())
967
            );
968

    
969
            reader = getSimpleReader(in);
970

    
971
            headers = CSVStoreParameters.getHeaders(getCSVParameters());
972
            if (headers == null) {
973
                if (CSVStoreParameters.isFirstLineHeader(getCSVParameters())) {
974
                    headers = reader.getHeader();
975
                    if (headers == null) {
976
                        if (CSVStoreParameters.getIgnoreErrors(getCSVParameters())) {
977
                            headers = getFixedHeaders(reader.getColumnsCount());
978
                        } else {
979
                            String msg = "Can't retrieve header from csv file '"
980
                                    + this.getCSVParameters().getFile()
981
                                            .getAbsolutePath()
982
                                    + "' and not specified in the parameters.";
983
                            LOGGER.warn(msg);
984
                            throw new RuntimeException(msg);
985
                        }
986
                    }
987
                } else {
988
                    headers = getFixedHeaders(reader.getColumnsCount());
989
                }
990
            } else {
991
                if (CSVStoreParameters.isFirstLineHeader(getCSVParameters())) {
992
                    reader.getHeader(); // Skip and ignore the header of file
993
                }
994
            }
995

    
996
            AutomaticDetectionOfTypes.DetectedValue[] detectedTypes = automaticDetectionOfTypes(headers);
997
            if (detectedTypes != null && detectedTypes.length > headers.length) {
998
                // Se han detectado mas columnas que las que hay en la cabezera,
999
                // a?adimos mas columnas a la cabezera.
1000
                String[] headers2 = new String[detectedTypes.length];
1001
                for (int i = 0; i < headers2.length; i++) {
1002
                    if (i < headers.length) {
1003
                        headers2[i] = headers[i];
1004
                    } else {
1005
                        headers2[i] = getFixedHeader(i);
1006
                    }
1007
                }
1008
                headers = headers2;
1009
            }
1010
            for (int i = 0; i < headers.length; i++) {
1011
                if (StringUtils.isEmpty(headers[i])) {
1012
                    headers[i] = getFixedHeader(i);
1013
                }
1014
            }
1015
            // Initialize the feature types
1016
            EditableFeatureType edftype = this.getFeatureType(headers, detectedTypes);
1017
            FeatureType ftype = edftype.getNotEditableCopy();
1018
            List<FeatureType> ftypes = new ArrayList<>();
1019
            ftypes.add(ftype);
1020
            store.setFeatureTypes(ftypes, ftype);
1021

    
1022
            Coercion coercion[] = new Coercion[ftype.size()];
1023
            CoercionContext coercionContext[] = new CoercionContext[ftype.size()];
1024
            int sizes[] = new int[ftype.size()];
1025
            for (int i = 0; i < ftype.size(); i++) {
1026
                sizes[i] = -1;
1027
                FeatureAttributeDescriptor ad = ftype.getAttributeDescriptor(i);
1028
                coercion[i] = ad.getCoercion();
1029
                coercionContext[i] = ad.getCoercionContext();
1030
                switch (ad.getDataType().getType()) {
1031
                    case DataTypes.INT:
1032
                        sizes[i] = 0;
1033
                        break;
1034
                    case DataTypes.LONG:
1035
                        sizes[i] = 0;
1036
                        break;
1037
                    case DataTypes.STRING:
1038
                        if (ad.getSize() == 0) {
1039
                            // Es un string y no tiene un size asignado.
1040
                            // Lo ponemos a cero para calcularlo.
1041
                            sizes[i] = 0;
1042
                        }
1043
                        break;
1044
                    case DataTypes.URL:
1045
                    case DataTypes.URI:
1046
                    case DataTypes.FILE:
1047
                        sizes[i] = 0;
1048
                }
1049
            }
1050
            if (ftype.getDefaultGeometryAttributeName() != null) {
1051
                this.need_calculate_envelope = true;
1052
            }
1053

    
1054
            Locale locale = CSVStoreParameters.getLocale(getCSVParameters());
1055
            taskStatus.message("_loading");
1056
            int count = 0;
1057

    
1058
            int count_errors = 0;
1059

    
1060
            List<String> row = reader.read();
1061

    
1062
            int skipLines = CSVStoreParameters.getSkipLines(getCSVParameters());
1063
            if (skipLines > 0) {
1064
                row = reader.skip(skipLines);
1065
            }
1066
            int limit = CSVStoreParameters.getLimit(getCSVParameters());
1067
            while (row != null) {
1068
                taskStatus.setCurValue(++count);
1069
                FeatureProvider feature = this.createFeatureProvider(ftype);
1070
                for (int i = 0; i < row.size(); i++) {
1071
                    Object rawvalue = row.get(i);
1072
                    try {
1073
                        Object value = null;
1074
                        if( coercion[i]!=null ) {
1075
                          value = coercion[i].coerce(rawvalue, coercionContext[i]);
1076
                        }
1077
                        feature.set(i, value);
1078
                        if (sizes[i] >= 0
1079
                                && (value instanceof String || value instanceof URL
1080
                                || value instanceof URI || value instanceof File)) {
1081
                            int x = value.toString().length();
1082
                            if (sizes[i] < x) {
1083
                                sizes[i] = x;
1084
                            }
1085
                        }
1086
                    } catch (Exception ex) {
1087
                        if (!ignore_errors) {
1088
                            throw ex;
1089
                        }
1090
                        if (count_errors++ < 10) {
1091
                            LOGGER.warn("Can't load value of attribute " + i + " in row " + count + ".", ex);
1092
                        }
1093
                        if (count_errors == 10) {
1094
                            LOGGER.info("Too many errors, suppress messages.");
1095
                        }
1096
                    }
1097
                }
1098
                this.addFeatureProvider(feature);
1099
                if (limit > 0) {
1100
                    if (limit < this.data.size()) {
1101
                        break;
1102
                    }
1103
                }
1104
                row = reader.read();
1105
            }
1106
            for (int i = 0; i < ftype.size(); i++) {
1107
                if (sizes[i] > 0) {
1108
                    EditableFeatureAttributeDescriptor efad = ((EditableFeatureAttributeDescriptor) edftype.getAttributeDescriptor(i));
1109
                    efad.setSize(sizes[i]);
1110
                }
1111
            }
1112
            // Volvemos a asignar al store el featuretype, ya que puede
1113
            // haber cambiado.
1114
            ftype = edftype.getNotEditableCopy();
1115
            ftypes = new ArrayList<>();
1116
            ftypes.add(ftype);
1117
            store.setFeatureTypes(ftypes, ftype);
1118

    
1119
            taskStatus.terminate();
1120
        } catch (Exception ex) {
1121
            int lineno = 0;
1122
            if (reader != null) {
1123
                lineno = reader.getLine();
1124
            }
1125
            throw new RuntimeException("Problems reading file '" + getFullFileName() + "' near line " + lineno + ".", ex);
1126

    
1127
        } finally {
1128
            if (reader != null) {
1129
                try {
1130
                    reader.close();
1131
                } catch (Exception ex) {
1132
                    // Do nothing
1133
                }
1134
                reader = null;
1135
            }
1136
            if (in != null) {
1137
                try {
1138
                    in.close();
1139
                } catch (Exception ex) {
1140
                    // Do nothing
1141
                }
1142
                in = null;
1143
            }
1144
        }
1145
    }
1146

    
1147
    private AutomaticDetectionOfTypes.DetectedValue[] automaticDetectionOfTypes(String[] headers) throws IOException {
1148
        boolean automatic_types_detection = CSVStoreParameters.getAutomaticTypesDetection(getCSVParameters());
1149
        if (!automatic_types_detection) {
1150
            return null;
1151
        }
1152
        AutomaticDetectionOfTypes.DetectedValue[] types = null;
1153

    
1154
        FileReader in = null;
1155
        SimpleReader reader = null;
1156

    
1157
        try {
1158
            in = new FileReader(this.getCSVParameters().getFile());
1159
            reader = getSimpleReader(in);
1160
            AutomaticDetectionOfTypes x = new AutomaticDetectionOfTypes(
1161
                    this.getFullFileName()
1162
            );
1163
            types = x.detect(
1164
                    headers.length,
1165
                    reader,
1166
                    CSVStoreParameters.isFirstLineHeader(getCSVParameters()),
1167
                    CSVStoreParameters.getLocale(getCSVParameters())
1168
            );
1169
        } catch (Exception ex) {
1170
            int lineno = 0;
1171
            if (reader != null) {
1172
                lineno = reader.getLine();
1173
            }
1174
            throw new RuntimeException("Problems reading file '" + getFullFileName() + "' near line " + lineno + ".", ex);
1175

    
1176
        } finally {
1177
            IOUtils.closeQuietly(reader);
1178
            IOUtils.closeQuietly(in);
1179
        }
1180
        return types;
1181
    }
1182

    
1183
}