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 @ 44408

History | View | Annotate | Download (45.5 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.simplereaders.CSVReader;
78
import org.gvsig.fmap.dal.store.csv.simplereaders.FixedLenReader;
79
import org.gvsig.fmap.dal.store.csv.simplereaders.SimpleReader;
80
import org.gvsig.fmap.geom.Geometry;
81
import org.gvsig.fmap.geom.GeometryLocator;
82
import org.gvsig.fmap.geom.GeometryManager;
83
import org.gvsig.fmap.geom.GeometryUtils;
84
import org.gvsig.fmap.geom.aggregate.MultiPoint;
85
import org.gvsig.fmap.geom.primitive.Envelope;
86
import org.gvsig.fmap.geom.primitive.Point;
87
import org.gvsig.fmap.geom.type.GeometryType;
88
import org.gvsig.tools.ToolsLocator;
89
import org.gvsig.tools.dataTypes.DataType;
90
import org.gvsig.tools.dataTypes.DataTypesManager;
91
import org.gvsig.tools.dataTypes.DataTypesManager.Coercion;
92
import org.gvsig.tools.dataTypes.DataTypesManager.CoercionWithLocale;
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

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

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

    
117
    public static final String NAME = "CSV";
118
    public static final String DESCRIPTION = "CSV file";
119

    
120
    public static final String METADATA_DEFINITION_NAME = NAME;
121

    
122
    private ResourceProvider resource;
123

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

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

    
141
        counterNewsOIDs = 0;
142

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

    
149
        resource.addConsumer(this);
150

    
151
        initializeFeatureTypes();
152
    }
153

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

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

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

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

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

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

    
213
    }
214

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

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

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

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

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

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

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

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

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

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

    
327
    }
328

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
460
        public String name = null;
461
        public int type = DataTypes.STRING;
462
        public int size = 0;
463
        public int geomType = Geometry.TYPES.GEOMETRY;
464
        public int geomSubtype = Geometry.SUBTYPES.GEOM2D;
465
        public Map<String,String> tags = new HashMap<>();
466
        public Map<String,String> assignments = new HashMap<>();
467

    
468
        private String typename = "string";
469

    
470
        FieldTypeParser() {
471
        }
472

    
473
        public void clear() {
474
            this.name = null;
475
            this.type = DataTypes.STRING;
476
            this.size = 0;
477
            this.tags = new HashMap<>();
478
            this.assignments = new HashMap<>();
479
        }
480

    
481
        public void copyFrom(FieldTypeParser other) {
482
            this.name = other.name;
483
            this.type = other.type;
484
            this.size = other.size;
485
            this.tags = new HashMap<>();
486
            this.tags.putAll(other.tags);
487
            this.assignments = new HashMap<>();
488
            this.assignments.putAll(other.assignments);
489
        }
490

    
491
        private int getType(String value) {
492
            DataTypesManager dataTypesManager = ToolsLocator.getDataTypesManager();
493
            return dataTypesManager.getType(typename);
494
        }
495

    
496
        private String[] split(String value, String separators) {
497
            int firstSeparatorPosition = 1000000;
498
            Character sep = null;
499
            for (char ch : separators.toCharArray()) {
500
                int pos = value.indexOf(ch);
501
                if( pos>0 && pos<firstSeparatorPosition ) {
502
                    sep = ch;
503
                    firstSeparatorPosition = pos;
504
                }
505
            }
506
            if( sep == null ) {
507
                return new String[] { value };
508
            }
509
            return value.split("["+sep+"]");
510
        }
511

    
512
        @SuppressWarnings("UseSpecificCatch")
513
        public boolean parse(String value) {
514
            String[] args;
515
            if ( value.contains("__") ) {
516
                args = value.split("__");
517
            } else {
518
                args = split(value, ":/#@!;-");
519
                if( args.length == 1 ) {
520
                    this.name = value;
521
                    return true;
522
                }
523
            }                        
524
            int n = 0;
525
            this.name = args[n++];
526
            if( n >= args.length ) {
527
                return true;
528
            }
529
            this.typename = args[n++];
530
            this.type = this.getType(this.typename);
531
            if ( this.type == DataTypes.INVALID ) {
532
                this.geomType = GeometryUtils.getGeometryType(this.typename);
533
                if( this.geomType==Geometry.TYPES.UNKNOWN )  {
534
                    this.type = DataTypes.STRING;
535
                    LOGGER.info("Type '" + this.typename + "' not valid for attribute '" + value + "' in '"+getProviderName()+"' file '" + getFullFileName() + "'.");
536
                } else {
537
                    this.typename = "GEOMETRY";
538
                    this.type = DataTypes.GEOMETRY;
539
                }
540
            }
541
            switch(this.type) {
542
                case DataTypes.STRING:
543
                    this.size = 50;
544
                    break;
545
                case DataTypes.INT:
546
                    this.size = 10;
547
                    break;
548
                case DataTypes.LONG:
549
                    this.size = 20;
550
                    break;
551
                case DataTypes.FLOAT:
552
                    this.size = 10;
553
                    break;
554
                case DataTypes.DOUBLE:
555
                    this.size = 20;
556
                    break;
557
                default:
558
                    this.size = 0;
559
            }
560
            while (n < args.length) {
561
                String option = args[n++].toLowerCase();
562
                switch (option) {
563
                    case "size":
564
                        try {
565
                            this.size = Integer.parseInt(args[n++]);
566
                        } catch (Exception ex) {
567
                            LOGGER.warn("Ignore incorrect field size for field " + value + " in '"+getProviderName()+"' file '" + getFullFileName() + "'.", ex);
568
                        }
569
                        break;
570
                    case "tag": {
571
                            String x = args[n++];
572
                            int pos = x.indexOf("=");
573
                            if( pos < 0 ) {
574
                                this.tags.put(x, null);
575
                            } else {
576
                                this.tags.put( 
577
                                        StringUtils.substring(x, 0, pos),
578
                                        StringUtils.substring(x, pos+1)
579
                                );
580
                            }
581
                            break;
582
                        }
583
                    case "set": {
584
                            String x = args[n++];
585
                            int pos = x.indexOf("=");
586
                            if( pos < 0 ) {
587
                                this.assignments.put(x, null);
588
                            } else {
589
                                this.assignments.put( 
590
                                        StringUtils.substring(x, 0, pos),
591
                                        StringUtils.substring(x, pos+1)
592
                                );
593
                            }
594
                            break;
595
                        }
596
                    default:
597
                        LOGGER.warn("Illegal argumente '"+option+"' for field '"+this.name+"' in '"+getProviderName()+"' file '" + getFullFileName() + "' ("+value+").");
598
                }
599
            }
600
            return true;
601
        }
602

    
603
    }
604

    
605
    private EditableFeatureType getFeatureType(String headers[], int automaticTypes[]) {
606
        EditableFeatureType fType = getStoreServices().createFeatureType(this.getName());
607
        fType.setHasOID(true);
608

    
609
        FieldTypeParser[] fieldTypes = new FieldTypeParser[headers.length];
610
        //
611
        // Calculamos cuales pueden ser los tipos de datos
612
        //
613
        for (int i = 0; i < fieldTypes.length; i++) {
614
            fieldTypes[i] = new FieldTypeParser();
615
        }
616

    
617
        // Asuminos los tipos pasados por parametro, que se supone
618
        // son los detectados automaticamente.
619
        if (automaticTypes != null) {
620
            for (int i = 0; i < fieldTypes.length && i < automaticTypes.length; i++) {
621
                fieldTypes[i].type = automaticTypes[i];
622
            }
623
        }
624
        // Luego probamos con lo que diga las cabezeras del CVS, sobreescribiendo
625
        // los tipos anteriores en caso de definirse en la cabezara.
626
        for (int i = 0; i < fieldTypes.length; i++) {
627
            if (!fieldTypes[i].parse(headers[i])) {
628
                continue;
629
            }
630

    
631
        }
632

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

    
737
    static class PointAttributeEmulator implements FeatureAttributeEmulator {
738

    
739
        private static final Logger logger = LoggerFactory.getLogger(ToPointEvaluaror.class);
740

    
741
        private static final int XNAME = 0;
742
        private static final int YNAME = 1;
743
        private static final int ZNAME = 2;
744

    
745
        private final GeometryManager geommgr;
746
        private final String[] fieldNames;
747
        private final Coercion toDouble;
748
        private final DataType dataType;
749
        private int errorcount = 0;
750

    
751
        public PointAttributeEmulator(String[] pointDimensionNames) {
752
            if (pointDimensionNames.length > 2) {
753
                this.fieldNames = new String[3];
754
                this.fieldNames[ZNAME] = pointDimensionNames[2];
755
            } else {
756
                this.fieldNames = new String[2];
757
            }
758
            this.fieldNames[XNAME] = pointDimensionNames[0];
759
            this.fieldNames[YNAME] = pointDimensionNames[1];
760
            this.geommgr = GeometryLocator.getGeometryManager();
761
            DataTypesManager datatypeManager = ToolsLocator.getDataTypesManager();
762

    
763
            this.toDouble = datatypeManager.getCoercion(DataTypes.DOUBLE);
764
            this.dataType = datatypeManager.get(DataTypes.GEOMETRY);
765
        }
766

    
767
        @Override
768
        public Object get(Feature feature) {
769
            try {
770
                Object valueX = feature.get(this.fieldNames[XNAME]);
771
                valueX = toDouble.coerce(valueX);
772
                if (valueX == null) {
773
                    return null;
774
                }
775
                Object valueY = feature.get(this.fieldNames[YNAME]);
776
                valueY = toDouble.coerce(valueY);
777
                if (valueY == null) {
778
                    return null;
779
                }
780
                Object valueZ = null;
781
                if (this.fieldNames.length > 2) {
782
                    valueZ = toDouble.coerce(feature.get(this.fieldNames[ZNAME]));
783
                    if (valueZ == null) {
784
                        return null;
785
                    }
786
                }
787

    
788
                double x = ((Double) valueX);
789
                double y = ((Double) valueY);
790
                Point point = geommgr.createPoint(x, y, Geometry.SUBTYPES.GEOM3D);
791
                if (this.fieldNames.length > 2) {
792
                    double z = ((Double) valueZ);
793
                    point.setCoordinateAt(2, z);
794
                }
795
                return point;
796
            } catch (Exception ex) {
797
                if (++errorcount < 5) {
798
                    logger.warn("[" + errorcount + "] Can't create point in CSV provider. XNAME='"
799
                            + this.fieldNames[XNAME] + "', YNAME='" + this.fieldNames[XNAME] + "' feature=" + feature.toString(), ex);
800
                }
801
                return null;
802
            }
803
        }
804

    
805
        public void set(EditableFeature feature, Object value) {
806
            if (value == null) {
807
                return;
808
            }
809
            Point point = null;
810
            if (value instanceof MultiPoint) {
811
                point = (Point) ((MultiPoint) value).getPrimitiveAt(0);
812
            } else {
813
                point = (Point) value;
814
            }
815
            feature.set(this.fieldNames[XNAME], point.getX());
816
            feature.set(this.fieldNames[YNAME], point.getY());
817
            if (this.fieldNames.length > 2) {
818
                feature.set(this.fieldNames[ZNAME], point.getCoordinateAt(2));
819
            }
820
        }
821

    
822
        @Override
823
        public boolean allowSetting() {
824
            return true;
825
        }
826

    
827
        @Override
828
        public String[] getRequiredFieldNames() {
829
            return this.fieldNames;
830
        }
831

    
832
    }
833

    
834
    static class ToPointEvaluaror extends AbstractEvaluator {
835

    
836
        private static final Logger logger = LoggerFactory.getLogger(ToPointEvaluaror.class);
837

    
838
        private GeometryManager geommgr = null;
839
        private String xname = null;
840
        private String yname = null;
841
        private String zname = null;
842
        private Coercion toDouble;
843
        private int errorcount = 0;
844

    
845
        ToPointEvaluaror(String[] pointDimensionNames) {
846
            this.xname = pointDimensionNames[0];
847
            this.yname = pointDimensionNames[1];
848
            if (pointDimensionNames.length > 2) {
849
                this.zname = pointDimensionNames[2];
850
            }
851
            this.geommgr = GeometryLocator.getGeometryManager();
852
            this.toDouble = ToolsLocator.getDataTypesManager().getCoercion(DataTypes.DOUBLE);
853
        }
854

    
855
        @Override
856
        public Object evaluate(EvaluatorData data) throws EvaluatorException {
857
            try {
858
                double x = ((Double) toDouble.coerce(data.getDataValue(xname))).doubleValue();
859
                double y = ((Double) toDouble.coerce(data.getDataValue(yname))).doubleValue();
860
                Point point = geommgr.createPoint(x, y, Geometry.SUBTYPES.GEOM3D);
861
                if (zname != null) {
862
                    double z = ((Double) toDouble.coerce(data.getDataValue(zname))).doubleValue();
863
                    point.setCoordinateAt(2, z);
864
                }
865
                return point;
866
            } catch (Exception ex) {
867
                if (++errorcount < 5) {
868
                    logger.warn("[" + errorcount + "] Can't create point in CSV provider. XNAME='"
869
                            + xname + "', YNAME='" + yname + "', ZNAME='" + zname + "', data=" + data.toString());
870
                }
871
                return null;
872
            }
873
        }
874

    
875
        @Override
876
        public String getName() {
877
            return "ToPointEvaluaror";
878
        }
879

    
880
    }
881

    
882
    private SimpleReader getSimpleReader(InputStreamReader in) {
883
        SimpleReader reader;
884
        if (CSVStoreParameters.getRawFieldsDefinition(getCSVParameters()) != null) {
885
            reader = new FixedLenReader(in, getCSVParameters());
886
        } else {
887
            reader = new CSVReader(in, getCSVParameters());
888
        }
889
        return reader;
890
    }
891

    
892
    private String getFixedHeader(int column) {
893
        char[] header = new char[3];
894

    
895
        String s = String.format("%03d", column);
896
        header[0] = (char) (s.charAt(0) + 17);
897
        header[1] = (char) (s.charAt(1) + 17);
898
        header[2] = (char) (s.charAt(2) + 17);
899
        return String.valueOf(header);
900
    }
901

    
902
    private String[] getFixedHeaders(int count) {
903
        String[] headers = new String[count];
904
        for (int i = 0; i < headers.length; i++) {
905
            headers[i] = getFixedHeader(i);
906
        }
907
        return headers;
908
    }
909

    
910
    private InputStreamReader openFile(File f, String charsetName) throws FileNotFoundException {
911
        Charset charset = Charset.defaultCharset();
912
        FileInputStream fis = new FileInputStream(f);
913
        if (!StringUtils.isEmpty(charsetName)) {
914
            if (Charset.isSupported(charsetName)) {
915
                try {
916
                    charset = Charset.forName(charsetName);
917
                } catch (Throwable th) {
918
                    LOGGER.warn("Can't use charset '" + charsetName + "' for read csv '" + this.getFullFileName() + "'.", th);
919
                }
920
            } else {
921
                LOGGER.warn("charset '" + charsetName + "' not supported for read csv '" + this.getFullFileName() + "'.");
922
            }
923
        }
924
        InputStreamReader isr = new InputStreamReader(fis, charset);
925
        return isr;
926
    }
927

    
928
    private void loadFeatures() {
929
        InputStreamReader in = null;
930
        SimpleReader reader = null;
931
        try {
932
            String headers[] = null;
933
            FeatureStoreProviderServices store = this.getStoreServices();
934

    
935
            boolean ignore_errors = CSVStoreParameters.getIgnoreErrors(getCSVParameters());
936

    
937
            in = openFile(
938
                    this.getCSVParameters().getFile(),
939
                    CSVStoreParameters.getCharset(this.getCSVParameters())
940
            );
941

    
942
            reader = getSimpleReader(in);
943

    
944
            headers = CSVStoreParameters.getHeaders(getCSVParameters());
945
            if (headers == null) {
946
                if (CSVStoreParameters.isFirstLineHeader(getCSVParameters())) {
947
                    headers = reader.getHeader();
948
                    if (headers == null) {
949
                        if (CSVStoreParameters.getIgnoreErrors(getCSVParameters())) {
950
                            headers = getFixedHeaders(reader.getColumnsCount());
951
                        } else {
952
                            String msg = "Can't retrieve header from csv file '"
953
                                    + this.getCSVParameters().getFile()
954
                                            .getAbsolutePath()
955
                                    + "' and not specified in the parameters.";
956
                            LOGGER.warn(msg);
957
                            throw new RuntimeException(msg);
958
                        }
959
                    }
960
                } else {
961
                    headers = getFixedHeaders(reader.getColumnsCount());
962
                }
963
            } else {
964
                if (CSVStoreParameters.isFirstLineHeader(getCSVParameters())) {
965
                    reader.getHeader(); // Skip and ignore the header of file
966
                }
967
            }
968

    
969
            int[] detectedTypes = automaticDetectionOfTypes(headers);
970
            if (detectedTypes != null && detectedTypes.length > headers.length) {
971
                // Se han detectado mas columnas que las que hay en la cabezera,
972
                // a?adimos mas columnas a la cabezera.
973
                String[] headers2 = new String[detectedTypes.length];
974
                for (int i = 0; i < headers2.length; i++) {
975
                    if (i < headers.length) {
976
                        headers2[i] = headers[i];
977
                    } else {
978
                        headers2[i] = getFixedHeader(i);
979
                    }
980
                }
981
                headers = headers2;
982
            }
983
            for (int i = 0; i < headers.length; i++) {
984
                if (StringUtils.isEmpty(headers[i])) {
985
                    headers[i] = getFixedHeader(i);
986
                }
987
            }
988
            // Initialize the feature types
989
            EditableFeatureType edftype = this.getFeatureType(headers, detectedTypes);
990
            FeatureType ftype = edftype.getNotEditableCopy();
991
            List<FeatureType> ftypes = new ArrayList<>();
992
            ftypes.add(ftype);
993
            store.setFeatureTypes(ftypes, ftype);
994

    
995
            Coercion coercion[] = new Coercion[ftype.size()];
996
            int sizes[] = new int[ftype.size()];
997
            for (int i = 0; i < ftype.size(); i++) {
998
                sizes[i] = -1;
999
                FeatureAttributeDescriptor ad = ftype.getAttributeDescriptor(i);
1000
                coercion[i] = ad.getDataType().getCoercion();
1001
                switch (ad.getDataType().getType()) {
1002
                    case DataTypes.INT:
1003
                        sizes[i] = 10;
1004
                        break;
1005
                    case DataTypes.LONG:
1006
                        sizes[i] = 20;
1007
                        break;
1008
                    case DataTypes.STRING:
1009
                        if (ad.getSize() == 0) {
1010
                            // Es un string y no tiene un size asignado.
1011
                            // Lo ponemos a cero para calcularlo.
1012
                            sizes[i] = 0;
1013
                        }
1014
                        break;
1015
                    case DataTypes.URL:
1016
                    case DataTypes.URI:
1017
                    case DataTypes.FILE:
1018
                        sizes[i] = 0;
1019
                }
1020
            }
1021
            if (ftype.getDefaultGeometryAttributeName() != null) {
1022
                this.need_calculate_envelope = true;
1023
            }
1024

    
1025
            Locale locale = CSVStoreParameters.getLocale(getCSVParameters());
1026
            taskStatus.message("_loading");
1027
            int count = 0;
1028

    
1029
            int count_errors = 0;
1030

    
1031
            List<String> row = reader.read();
1032

    
1033
            int skipLines = CSVStoreParameters.getSkipLines(getCSVParameters());
1034
            if (skipLines > 0) {
1035
                row = reader.skip(skipLines);
1036
            }
1037
            int limit = CSVStoreParameters.getLimit(getCSVParameters());
1038
            while (row != null) {
1039
                taskStatus.setCurValue(++count);
1040
                FeatureProvider feature = this.createFeatureProvider(ftype);
1041
                for (int i = 0; i < row.size(); i++) {
1042
                    Object rawvalue = row.get(i);
1043
                    try {
1044
                        Object value = null;
1045
                        if (locale != null && coercion[i] instanceof CoercionWithLocale) {
1046
                            value = ((CoercionWithLocale) (coercion[i])).coerce(rawvalue, locale);
1047
                        } else {
1048
                            value = coercion[i].coerce(rawvalue);
1049
                        }
1050
                        feature.set(i, value);
1051
                        if (sizes[i] >= 0
1052
                                && (value instanceof String || value instanceof URL
1053
                                || value instanceof URI || value instanceof File)) {
1054
                            int x = value.toString().length();
1055
                            if (sizes[i] < x) {
1056
                                sizes[i] = x;
1057
                            }
1058
                        }
1059
                    } catch (Exception ex) {
1060
                        if (!ignore_errors) {
1061
                            throw ex;
1062
                        }
1063
                        if (count_errors++ < 10) {
1064
                            LOGGER.warn("Can't load value of attribute " + i + " in row " + count + ".", ex);
1065
                        }
1066
                        if (count_errors == 10) {
1067
                            LOGGER.info("Too many errors, suppress messages.");
1068
                        }
1069
                    }
1070
                }
1071
                this.addFeatureProvider(feature);
1072
                if (limit > 0) {
1073
                    if (limit < this.data.size()) {
1074
                        break;
1075
                    }
1076
                }
1077
                row = reader.read();
1078
            }
1079
            for (int i = 0; i < ftype.size(); i++) {
1080
                if (sizes[i] > 0) {
1081
                    EditableFeatureAttributeDescriptor efad = ((EditableFeatureAttributeDescriptor) edftype.getAttributeDescriptor(i));
1082
                    efad.setSize(sizes[i]);
1083
                }
1084
            }
1085
            // Volvemos a asignar al store el featuretype, ya que puede
1086
            // haber cambiado.
1087
            ftype = edftype.getNotEditableCopy();
1088
            ftypes = new ArrayList<>();
1089
            ftypes.add(ftype);
1090
            store.setFeatureTypes(ftypes, ftype);
1091

    
1092
            taskStatus.terminate();
1093
        } catch (Exception ex) {
1094
            int lineno = 0;
1095
            if (reader != null) {
1096
                lineno = reader.getLine();
1097
            }
1098
            throw new RuntimeException("Problems reading file '" + getFullFileName() + "' near line " + lineno + ".", ex);
1099

    
1100
        } finally {
1101
            if (reader != null) {
1102
                try {
1103
                    reader.close();
1104
                } catch (Exception ex) {
1105
                    // Do nothing
1106
                }
1107
                reader = null;
1108
            }
1109
            if (in != null) {
1110
                try {
1111
                    in.close();
1112
                } catch (Exception ex) {
1113
                    // Do nothing
1114
                }
1115
                in = null;
1116
            }
1117
        }
1118
    }
1119

    
1120
    private int[] automaticDetectionOfTypes(String[] headers) throws IOException {
1121
        boolean automatic_types_detection = CSVStoreParameters.getAutomaticTypesDetection(getCSVParameters());
1122
        if (!automatic_types_detection) {
1123
            return null;
1124
        }
1125
        int[] types = null;
1126

    
1127
        FileReader in = null;
1128
        SimpleReader reader = null;
1129

    
1130
        try {
1131
            in = new FileReader(this.getCSVParameters().getFile());
1132
            reader = getSimpleReader(in);
1133
            AutomaticDetectionOfTypes x = new AutomaticDetectionOfTypes(
1134
                    this.getFullFileName()
1135
            );
1136
            types = x.detect(
1137
                    headers.length,
1138
                    reader,
1139
                    CSVStoreParameters.isFirstLineHeader(getCSVParameters()),
1140
                    CSVStoreParameters.getLocale(getCSVParameters())
1141
            );
1142
        } catch (Exception ex) {
1143
            int lineno = 0;
1144
            if (reader != null) {
1145
                lineno = reader.getLine();
1146
            }
1147
            throw new RuntimeException("Problems reading file '" + getFullFileName() + "' near line " + lineno + ".", ex);
1148

    
1149
        } finally {
1150
            IOUtils.closeQuietly(reader);
1151
            IOUtils.closeQuietly(in);
1152
        }
1153
        return types;
1154
    }
1155

    
1156
    private boolean isValidLong(String s) {
1157
        if (s == null) {
1158
            return true;
1159
        }
1160
        s = s.trim().toLowerCase();
1161
        if (s.isEmpty()) {
1162
            return true;
1163
        }
1164
        try {
1165
            if (s.startsWith("0x")) {
1166
                Long.valueOf(s.substring(2), 16);
1167
            } else {
1168
                Long.valueOf(s);
1169
            }
1170
            return true;
1171
        } catch (Exception ex) {
1172
            return false;
1173
        }
1174
    }
1175

    
1176
    private boolean isValidInteger(String s) {
1177
        if (s == null) {
1178
            return true;
1179
        }
1180
        s = s.trim().toLowerCase();
1181
        if (s.isEmpty()) {
1182
            return true;
1183
        }
1184
        try {
1185
            if (s.startsWith("0x")) {
1186
                Integer.valueOf(s.substring(2), 16);
1187
            } else {
1188
                Integer.valueOf(s);
1189
            }
1190
            return true;
1191
        } catch (Exception ex) {
1192
            return false;
1193
        }
1194
    }
1195

    
1196
}