forked from FasterXML/jackson-dataformats-text
-
Notifications
You must be signed in to change notification settings - Fork 0
/
CsvMapper.java
584 lines (519 loc) · 20.7 KB
/
CsvMapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
package com.fasterxml.jackson.dataformat.csv;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.cfg.MapperBuilder;
import com.fasterxml.jackson.databind.cfg.MapperBuilderState;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.util.NameTransformer;
import com.fasterxml.jackson.databind.util.SimpleLookupCache;
import com.fasterxml.jackson.databind.util.ViewMatcher;
import java.util.Collection;
/**
* Specialized {@link ObjectMapper}, with extended functionality to
* produce {@link CsvSchema} instances out of POJOs.
*/
public class CsvMapper extends ObjectMapper
{
private static final long serialVersionUID = 1;
/**
* Base implementation for "Vanilla" {@link ObjectMapper}, used with
* CSV backend.
*
* @since 3.0
*/
public static class Builder extends MapperBuilder<CsvMapper, Builder>
{
public Builder(CsvFactory f) {
super(f);
}
public Builder(StateImpl state) {
super(state);
}
@Override
public CsvMapper build() {
return new CsvMapper(this);
}
@Override
protected MapperBuilderState _saveState() {
// nothing extra, just format features
return new StateImpl(this);
}
/*
/******************************************************************
/* Format features
/******************************************************************
*/
public Builder enable(CsvParser.Feature... features) {
for (CsvParser.Feature f : features) {
_formatReadFeatures |= f.getMask();
}
return this;
}
public Builder disable(CsvParser.Feature... features) {
for (CsvParser.Feature f : features) {
_formatReadFeatures &= ~f.getMask();
}
return this;
}
public Builder configure(CsvParser.Feature feature, boolean state)
{
if (state) {
_formatReadFeatures |= feature.getMask();
} else {
_formatReadFeatures &= ~feature.getMask();
}
return this;
}
public Builder enable(CsvGenerator.Feature... features) {
for (CsvGenerator.Feature f : features) {
_formatWriteFeatures |= f.getMask();
}
return this;
}
public Builder disable(CsvGenerator.Feature... features) {
for (CsvGenerator.Feature f : features) {
_formatWriteFeatures &= ~f.getMask();
}
return this;
}
public Builder configure(CsvGenerator.Feature feature, boolean state)
{
if (state) {
_formatWriteFeatures |= feature.getMask();
} else {
_formatWriteFeatures &= ~feature.getMask();
}
return this;
}
protected static class StateImpl extends MapperBuilderState
implements java.io.Serializable // important!
{
private static final long serialVersionUID = 3L;
public StateImpl(Builder src) {
super(src);
}
// We also need actual instance of state as base class can not implement logic
// for reinstating mapper (via mapper builder) from state.
@Override
protected Object readResolve() {
return new Builder(this).build();
}
}
}
/*
/**********************************************************************
/* Caching of schemas
/**********************************************************************
*/
/**
* Simple caching for schema instances, given that they are relatively expensive
* to construct; this one is for "loose" (non-typed) schemas
*/
protected final SimpleLookupCache<JavaType,CsvSchema> _untypedSchemas;
/**
* Simple caching for schema instances, given that they are relatively expensive
* to construct; this one is for typed schemas
*/
protected final SimpleLookupCache<JavaType,CsvSchema> _typedSchemas;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
public CsvMapper() {
this(new Builder(new CsvFactory()));
}
public CsvMapper(CsvFactory f) {
this(new Builder(f));
}
/**
* @since 3.0
*/
public CsvMapper(CsvMapper.Builder b) {
super(b);
_untypedSchemas = new SimpleLookupCache<JavaType,CsvSchema>(8,32);
_typedSchemas = new SimpleLookupCache<JavaType,CsvSchema>(8,32);
}
public static CsvMapper.Builder builder() {
return new CsvMapper.Builder(new CsvFactory());
}
public static CsvMapper.Builder builder(CsvFactory streamFactory) {
return new CsvMapper.Builder(streamFactory);
}
@SuppressWarnings("unchecked")
@Override
public Builder rebuild() {
return new Builder((Builder.StateImpl) _savedBuilderState);
}
/*
/**********************************************************************
/* Life-cycle, shared "vanilla" (default configuration) instance
/**********************************************************************
*/
/**
* Accessor method for getting globally shared "default" {@link CsvMapper}
* instance: one that has default configuration, no modules registered, no
* config overrides. Usable mostly when dealing "untyped" or Tree-style
* content reading and writing.
*/
public static CsvMapper shared() {
return SharedWrapper.wrapped();
}
/*
/**********************************************************************
/* Life-cycle: JDK serialization support
/**********************************************************************
*/
// 27-Feb-2018, tatu: Not sure why but it seems base class definitions
// are not sufficient alone; sub-classes must re-define.
@Override
protected Object writeReplace() {
return _savedBuilderState;
}
@Override
protected Object readResolve() {
throw new IllegalStateException("Should never deserialize `"+getClass().getName()+"` directly");
}
/*
/**********************************************************************
/* Additional typed accessors
/**********************************************************************
*/
/**
* Overridden with more specific type, since factory we have
* is always of type {@link CsvFactory}
*/
@Override
public CsvFactory tokenStreamFactory() {
return (CsvFactory) _streamFactory;
}
/*
/**********************************************************************
/* Additional ObjectReader factory methods
/**********************************************************************
*/
/**
* Convenience method which is functionally equivalent to:
*<pre>
* reader(pojoType).withSchema(schemaFor(pojoType));
*</pre>
* that is, constructs a {@link ObjectReader} which both binds to
* specified type and uses "loose" {@link CsvSchema} introspected from
* specified type (one without strict inferred typing).
*<p>
* @param pojoType Type used both for data-binding (result type) and for
* schema introspection. NOTE: must NOT be an array or Collection type, since
* these only make sense for data-binding (like arrays of objects to bind),
* but not for schema construction (no CSV types can be mapped to arrays
* or Collections)
*/
public ObjectReader readerWithSchemaFor(Class<?> pojoType)
{
JavaType type = constructType(pojoType);
/* sanity check: not useful for structured types, since
* schema type will need to differ from data-bind type
*/
if (type.isArrayType() || type.isCollectionLikeType()) {
throw new IllegalArgumentException("Type can NOT be a Collection or array type");
}
return readerFor(type).with(schemaFor(type));
}
/**
* Convenience method which is functionally equivalent to:
*<pre>
* reader(pojoType).withSchema(typedSchemaFor(pojoType));
*</pre>
* that is, constructs a {@link ObjectReader} which both binds to
* specified type and uses "strict" {@link CsvSchema} introspected from
* specified type (one where typing is inferred).
*/
public ObjectReader readerWithTypedSchemaFor(Class<?> pojoType)
{
JavaType type = constructType(pojoType);
// sanity check: not useful for structured types, since
// schema type will need to differ from data-bind type
if (type.isArrayType() || type.isCollectionLikeType()) {
throw new IllegalArgumentException("Type can NOT be a Collection or array type");
}
return readerFor(type).with(typedSchemaFor(type));
}
/*
/**********************************************************************
/* Additional ObjectWriter factory methods
/**********************************************************************
*/
/**
* Convenience method which is functionally equivalent to:
*<pre>
* writer(pojoType).with(schemaFor(pojoType));
*</pre>
* that is, constructs a {@link ObjectWriter} which both binds to
* specified type and uses "loose" {@link CsvSchema} introspected from
* specified type (one without strict inferred typing).
*<p>
* @param pojoType Type used both for data-binding (result type) and for
* schema introspection. NOTE: must NOT be an array or Collection type, since
* these only make sense for data-binding (like arrays of objects to bind),
* but not for schema construction (no root-level CSV types can be mapped to arrays
* or Collections)
*/
public ObjectWriter writerWithSchemaFor(Class<?> pojoType)
{
JavaType type = constructType(pojoType);
// sanity check as per javadoc above
if (type.isArrayType() || type.isCollectionLikeType()) {
throw new IllegalArgumentException("Type can NOT be a Collection or array type");
}
return writerFor(type).with(schemaFor(type));
}
/**
* Convenience method which is functionally equivalent to:
*<pre>
* writer(pojoType).with(typedSchemaFor(pojoType));
*</pre>
* that is, constructs a {@link ObjectWriter} which both binds to
* specified type and uses "strict" {@link CsvSchema} introspected from
* specified type (one where typing is inferred).
*/
public ObjectWriter writerWithTypedSchemaFor(Class<?> pojoType)
{
JavaType type = constructType(pojoType);
// sanity check as per javadoc above
if (type.isArrayType() || type.isCollectionLikeType()) {
throw new IllegalArgumentException("Type can NOT be a Collection or array type");
}
return writerFor(type).with(typedSchemaFor(type));
}
/*
/**********************************************************************
/* CsvSchema construction; overrides, new methods
/**********************************************************************
*/
/**
* Convenience method that is same as
*<pre>
* CsvSchema.emptySchema().withHeader();
*</pre>
* and returns a {@link CsvSchema} instance that uses default configuration
* with additional setting that the first content line contains intended
* column names.
*/
public CsvSchema schemaWithHeader() {
return CsvSchema.emptySchema().withHeader();
}
/**
* Convenience method that is same as
*<pre>
* CsvSchema.emptySchema()
*</pre>
* that is, returns an "empty" Schema; one with default values and no
* column definitions.
*/
public CsvSchema schema() {
return CsvSchema.emptySchema();
}
/**
* Method that can be used to determine a CSV schema to use for given
* POJO type, using default serialization settings including ordering.
* Definition will not be strictly typed (that is, all columns are
* just defined to be exposed as String tokens).
*/
public CsvSchema schemaFor(JavaType pojoType) {
return _schemaFor(pojoType, _untypedSchemas, false, null);
}
public CsvSchema schemaForWithView(JavaType pojoType, Class<?> view) {
return _schemaFor(pojoType, _untypedSchemas, false, view);
}
public final CsvSchema schemaFor(Class<?> pojoType) {
return _schemaFor(constructType(pojoType), _untypedSchemas, false, null);
}
public final CsvSchema schemaForWithView(Class<?> pojoType, Class<?> view) {
return _schemaFor(constructType(pojoType), _untypedSchemas, false, view);
}
public final CsvSchema schemaFor(TypeReference<?> pojoTypeRef) {
return _schemaFor(constructType(pojoTypeRef.getType()), _untypedSchemas, false, null);
}
public final CsvSchema schemaForWithView(TypeReference<?> pojoTypeRef, Class<?> view) {
return _schemaFor(constructType(pojoTypeRef.getType()), _untypedSchemas, false, view);
}
/**
* Method that can be used to determine a CSV schema to use for given
* POJO type, using default serialization settings including ordering.
* Definition WILL be strictly typed: that is, code will try to
* determine type limitations which may make parsing more efficient
* (especially for numeric types like java.lang.Integer).
*/
public CsvSchema typedSchemaFor(JavaType pojoType) {
return _schemaFor(pojoType, _typedSchemas, true, null);
}
public CsvSchema typedSchemaForWithView(JavaType pojoType, Class<?> view) {
return _schemaFor(pojoType, _typedSchemas, true, view);
}
public final CsvSchema typedSchemaFor(Class<?> pojoType) {
return _schemaFor(constructType(pojoType), _typedSchemas, true, null);
}
public final CsvSchema typedSchemaForWithView(Class<?> pojoType, Class<?> view) {
return _schemaFor(constructType(pojoType), _typedSchemas, true, view);
}
public final CsvSchema typedSchemaFor(TypeReference<?> pojoTypeRef) {
return _schemaFor(constructType(pojoTypeRef.getType()), _typedSchemas, true, null);
}
public final CsvSchema typedSchemaForWithView(TypeReference<?> pojoTypeRef, Class<?> view) {
return _schemaFor(constructType(pojoTypeRef.getType()), _typedSchemas, true, view);
}
/*
/**********************************************************************
/* Internal methods
/**********************************************************************
*/
protected CsvSchema _schemaFor(JavaType pojoType, SimpleLookupCache<JavaType,CsvSchema> schemas,
boolean typed, Class<?> view)
{
synchronized (schemas) {
CsvSchema s = schemas.get(pojoType);
if (s != null) {
return s;
}
}
// 15-Oct-2019, tatu: Since 3.0, need context for introspection
final SerializerProvider ctxt = _serializerProvider();
CsvSchema.Builder builder = CsvSchema.builder();
_addSchemaProperties(ctxt, builder, typed, pojoType, null, view);
CsvSchema result = builder.build();
synchronized (schemas) {
schemas.put(pojoType, result);
}
return result;
}
protected boolean _nonPojoType(JavaType t)
{
if (t.isPrimitive() || t.isEnumType()) {
return true;
}
Class<?> raw = t.getRawClass();
// Wrapper types for numbers
if (Number.class.isAssignableFrom(raw)) {
if ((raw == Byte.class)
|| (raw == Short.class)
|| (raw == Character.class)
|| (raw == Integer.class)
|| (raw == Long.class)
|| (raw == Float.class)
|| (raw == Double.class)
) {
return true;
}
}
// Some other well-known non-POJO types
if ((raw == Boolean.class)
|| (raw == String.class)
) {
return true;
}
return false;
}
protected void _addSchemaProperties(SerializerProvider ctxt, CsvSchema.Builder builder,
boolean typed, JavaType pojoType, NameTransformer unwrapper, Class<?> view)
{
// 09-Aug-2015, tatu: From [dataformat-csv#87], realized that one can not have
// real schemas for primitive/wrapper
if (_nonPojoType(pojoType)) {
return;
}
BeanDescription beanDesc = ctxt.introspectBeanDescription(pojoType);
final AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
for (BeanPropertyDefinition prop : beanDesc.findProperties()) {
if (view != null) {
Class<?>[] views = prop.findViews();
if (views == null) {
views = beanDesc.findDefaultViews();
}
if (!ViewMatcher.construct(views).isVisibleForView(view)) {
continue;
}
}
// ignore setter-only properties:
if (!prop.couldSerialize()) {
continue;
}
// [dataformat-csv#15]: handle unwrapped props
AnnotatedMember m = prop.getPrimaryMember();
if (m != null) {
NameTransformer nextUnwrapper = intr.findUnwrappingNameTransformer(ctxt.getConfig(),
prop.getPrimaryMember());
if (nextUnwrapper != null) {
if (unwrapper != null) {
nextUnwrapper = NameTransformer.chainedTransformer(unwrapper, nextUnwrapper);
}
JavaType nextType = m.getType();
_addSchemaProperties(ctxt, builder, typed, nextType, nextUnwrapper, view);
continue;
}
}
// Then name wrapping/unwrapping
String name = prop.getName();
if (unwrapper != null) {
name = unwrapper.transform(name);
}
if (typed && m != null) {
builder.addColumn(name, _determineType(m.getRawType()));
} else {
builder.addColumn(name);
}
}
}
// should not be null since couldSerialize() returned true, so:
protected CsvSchema.ColumnType _determineType(Class<?> propType)
{
// very first thing: arrays
if (propType.isArray()) {
// one exception; byte[] assumed to come in as Base64 encoded
if (propType == byte[].class) {
return CsvSchema.ColumnType.STRING;
}
return CsvSchema.ColumnType.ARRAY;
}
// First let's check certain cases that ought to be just presented as Strings...
if (propType == String.class
|| propType == Character.TYPE
|| propType == Character.class) {
return CsvSchema.ColumnType.STRING;
}
if (propType == Boolean.class
|| propType == Boolean.TYPE) {
return CsvSchema.ColumnType.BOOLEAN;
}
// all primitive types are good for NUMBER, since 'char', 'boolean' handled above
if (propType.isPrimitive()) {
return CsvSchema.ColumnType.NUMBER;
}
if (Number.class.isAssignableFrom(propType)) {
return CsvSchema.ColumnType.NUMBER;
}
if (Collection.class.isAssignableFrom(propType)) { // since 2.5
return CsvSchema.ColumnType.ARRAY;
}
// but in general we will just do what we can:
return CsvSchema.ColumnType.NUMBER_OR_STRING;
}
/*
/**********************************************************
/* Helper class(es)
/**********************************************************
*/
/**
* Helper class to contain dynamically constructed "shared" instance of
* mapper, should one be needed via {@link #shared}.
*/
private final static class SharedWrapper {
private final static CsvMapper MAPPER = CsvMapper.builder().build();
public static CsvMapper wrapped() { return MAPPER; }
}
}