1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess;
18
19 import java.io.IOException;
20 import java.util.HashMap;
21 import java.util.Map;
22
23 import com.healthmarketscience.jackcess.complex.ComplexDataType;
24 import com.healthmarketscience.jackcess.impl.ColumnImpl;
25 import com.healthmarketscience.jackcess.impl.ComplexColumnDesc;
26 import com.healthmarketscience.jackcess.impl.DatabaseImpl;
27 import com.healthmarketscience.jackcess.impl.JetFormat;
28 import com.healthmarketscience.jackcess.impl.PropertyMapImpl;
29 import com.healthmarketscience.jackcess.impl.TableImpl;
30 import com.healthmarketscience.jackcess.impl.TableUpdater;
31 import com.healthmarketscience.jackcess.impl.ToStringBuilder;
32
33
34
35
36
37
38
39
40
41
42 public class ColumnBuilder {
43
44
45 private static final short UNSET_COLUMN_ID = (short)-1;
46
47
48 private String _name;
49
50 private DataType _type;
51
52 private Short _length;
53
54 private Byte _precision;
55
56 private Byte _scale;
57
58 private boolean _autoNumber;
59
60 private boolean _compressedUnicode;
61
62 private boolean _calculated;
63
64 private boolean _hyperlink;
65
66 private short _columnNumber;
67
68
69 private short _columnId = UNSET_COLUMN_ID;
70
71 private boolean _appendOnly;
72
73 private ComplexColumnDesc _complexDesc;
74
75 private ColumnImpl.SortOrder _sortOrder;
76
77 private Map<String,PropertyMap.Property> _props;
78
79
80 public ColumnBuilder(String name) {
81 this(name, null);
82 }
83
84 public ColumnBuilder(String name, DataType type) {
85 _name = name;
86 _type = type;
87 }
88
89 public String getName() {
90 return _name;
91 }
92
93
94
95
96 public ColumnBuilder setType(DataType type) {
97 _type = type;
98 return this;
99 }
100
101 public DataType getType() {
102 return _type;
103 }
104
105
106
107
108 public ColumnBuilder setSQLType(int type) throws IOException {
109 return setSQLType(type, 0, null);
110 }
111
112
113
114
115
116 public ColumnBuilder setSQLType(int type, int lengthInUnits)
117 throws IOException
118 {
119 return setSQLType(type, lengthInUnits, null);
120 }
121
122
123
124
125
126 public ColumnBuilder setSQLType(int type, int lengthInUnits,
127 Database.FileFormat fileFormat)
128 throws IOException
129 {
130 return setType(DataType.fromSQLType(type, lengthInUnits, fileFormat));
131 }
132
133
134
135
136 public ColumnBuilder setPrecision(int newPrecision) {
137 _precision = (byte)newPrecision;
138 return this;
139 }
140
141 public byte getPrecision() {
142 return ((_precision != null) ? _precision : (byte)_type.getDefaultPrecision());
143 }
144
145
146
147
148
149 public ColumnBuilder setMaxPrecision() {
150 if(_type.getHasScalePrecision()) {
151 setPrecision(_type.getMaxPrecision());
152 }
153 return this;
154 }
155
156
157
158
159 public ColumnBuilder setScale(int newScale) {
160 _scale = (byte)newScale;
161 return this;
162 }
163
164 public byte getScale() {
165 return ((_scale != null) ? _scale : (byte)_type.getDefaultScale());
166 }
167
168
169
170
171
172 public ColumnBuilder setMaxScale() {
173 if(_type.getHasScalePrecision()) {
174 setScale(_type.getMaxScale());
175 }
176 return this;
177 }
178
179
180
181
182 public ColumnBuilder setLength(int length) {
183 _length = (short)length;
184 return this;
185 }
186
187 public short getLength() {
188 return ((_length != null) ? _length :
189 (short)(!_type.isVariableLength() ? _type.getFixedSize() :
190 _type.getDefaultSize()));
191 }
192
193
194
195
196 public ColumnBuilder setLengthInUnits(int unitLength) {
197 return setLength(_type.fromUnitSize(unitLength));
198 }
199
200
201
202
203
204 public ColumnBuilder setMaxLength() {
205
206 if(_type.isVariableLength()) {
207 setLength(_type.getMaxSize());
208 }
209 return this;
210 }
211
212
213
214
215 public ColumnBuilder setAutoNumber(boolean autoNumber) {
216 _autoNumber = autoNumber;
217 return this;
218 }
219
220 public boolean isAutoNumber() {
221 return _autoNumber;
222 }
223
224
225
226
227 public ColumnBuilder setCompressedUnicode(boolean compressedUnicode) {
228 _compressedUnicode = compressedUnicode;
229 return this;
230 }
231
232 public boolean isCompressedUnicode() {
233 return _compressedUnicode;
234 }
235
236
237
238
239 public ColumnBuilder setCalculated(boolean calculated) {
240 _calculated = calculated;
241 return this;
242 }
243
244 public boolean isCalculated() {
245 return _calculated;
246 }
247
248
249
250
251
252 public ColumnBuilder setCalculatedInfo(String expression) {
253 setCalculated(true);
254 putProperty(PropertyMap.EXPRESSION_PROP, expression);
255 return putProperty(PropertyMap.RESULT_TYPE_PROP, getType().getValue());
256 }
257
258
259
260
261
262
263
264
265
266
267
268 public ColumnBuilder setMultiValue(DataType valueType) {
269 setType(DataType.COMPLEX_TYPE);
270 setAutoNumber(true);
271 _complexDesc = ComplexColumnDesc.multiValue(valueType);
272 return this;
273 }
274
275
276
277
278
279
280 public ColumnBuilder setValueList(String... values) {
281 StringBuilder rowSource = new StringBuilder();
282 for(String value : values) {
283 if(rowSource.length() > 0) {
284 rowSource.append(';');
285 }
286 rowSource.append('"').append(value.replace("\"", "\"\"")).append('"');
287 }
288 getMultiValueDesc().setRowSource("Value List", rowSource.toString(), 1);
289 return this;
290 }
291
292
293
294
295
296
297
298
299 public ColumnBuilder setRowSource(String tableOrQuery, int columnCount) {
300 getMultiValueDesc().setRowSource("Table/Query", tableOrQuery, columnCount);
301 return this;
302 }
303
304
305
306
307
308
309 public ColumnBuilder setAttachment() {
310 setType(DataType.COMPLEX_TYPE);
311 setAutoNumber(true);
312 _complexDesc = ComplexColumnDesc.attachment();
313 return this;
314 }
315
316
317
318
319
320
321
322 public ColumnBuilder setAppendOnly(boolean appendOnly) {
323 _appendOnly = appendOnly;
324 if(appendOnly) {
325 putProperty(PropertyMap.APPEND_ONLY_PROP, DataType.BOOLEAN, Boolean.TRUE);
326 } else if(_props != null) {
327
328
329 _props.remove(PropertyMap.APPEND_ONLY_PROP);
330 }
331 return this;
332 }
333
334 public boolean isAppendOnly() {
335 return _appendOnly;
336 }
337
338 private ComplexColumnDesc getMultiValueDesc() {
339 if((_complexDesc == null) ||
340 (_complexDesc.getType() != ComplexDataType.MULTI_VALUE)) {
341 throw new IllegalArgumentException(withErrorContext(
342 "Only a multi-value column has a row source, and setMultiValue " +
343 "declares one"));
344 }
345 return _complexDesc;
346 }
347
348
349
350
351
352
353
354 public ComplexColumnDesc getComplexDesc() {
355 return _complexDesc;
356 }
357
358 public boolean isVariableLength() {
359
360 return(getType().isVariableLength() || isCalculated());
361 }
362
363
364
365
366 public ColumnBuilder setHyperlink(boolean hyperlink) {
367 _hyperlink = hyperlink;
368 return this;
369 }
370
371 public boolean isHyperlink() {
372 return _hyperlink;
373 }
374
375
376
377
378
379
380
381 public ColumnBuilder putProperty(String name, Object value) {
382 return putProperty(name, null, value);
383 }
384
385
386
387
388 public ColumnBuilder putProperty(String name, DataType type, Object value) {
389 setProperty(name, PropertyMapImpl.createProperty(name, type, value));
390 return this;
391 }
392
393 public Map<String,PropertyMap.Property> getProperties() {
394 return _props;
395 }
396
397 private void setProperty(String name, PropertyMap.Property prop) {
398 if(prop == null) {
399 return;
400 }
401 if(_props == null) {
402 _props = new HashMap<>();
403 }
404 _props.put(name, prop);
405 }
406
407 private PropertyMap.Property getProperty(String name) {
408 return ((_props != null) ? _props.get(name) : null);
409 }
410
411
412
413
414
415 public ColumnBuilder setFromColumn(Column template)
416 throws IOException
417 {
418 DataType type = template.getType();
419 setType(type);
420 setLengthInUnits(template.getLengthInUnits());
421 setAutoNumber(template.isAutoNumber());
422 if(type.getHasScalePrecision()) {
423 setScale(template.getScale());
424 setPrecision(template.getPrecision());
425 }
426 setCalculated(template.isCalculated());
427 setCompressedUnicode(template.isCompressedUnicode());
428 setHyperlink(template.isHyperlink());
429 setAppendOnly(template.isAppendOnly());
430 _complexDesc = ComplexColumnDesc.fromColumn(template);
431 if(template instanceof ColumnImpl) {
432 setTextSortOrder(((ColumnImpl)template).getTextSortOrder());
433 }
434
435 PropertyMap colProps = template.getProperties();
436 for(PropertyMap.Property colProp : colProps) {
437
438 if(!PropertyMap.GUID_PROP.equalsIgnoreCase(colProp.getName())) {
439 setProperty(colProp.getName(), colProp);
440 }
441 }
442
443 return this;
444 }
445
446
447
448
449 public ColumnBuilderm/healthmarketscience/jackcess/ColumnBuilder.html#ColumnBuilder">ColumnBuilder setFromColumn(ColumnBuilder template) {
450 DataType type = template.getType();
451 _type = type;
452 _length = template._length;
453 _autoNumber = template._autoNumber;
454 if(type.getHasScalePrecision()) {
455 _scale = template._scale;
456 _precision = template._precision;
457 }
458 _calculated = template._calculated;
459 _compressedUnicode = template._compressedUnicode;
460 _hyperlink = template._hyperlink;
461 _appendOnly = template._appendOnly;
462
463
464 ComplexColumnDesc templateDesc = template.getComplexDesc();
465 _complexDesc = ((templateDesc != null) ? templateDesc.copy() : null);
466 _sortOrder = template._sortOrder;
467
468 if(template._props != null) {
469 _props = new HashMap<>(template._props);
470 }
471
472 return this;
473 }
474
475
476
477
478 public ColumnBuilder escapeName() {
479 _name = TableBuilder.escapeIdentifier(_name);
480 return this;
481 }
482
483
484
485
486 public short getColumnNumber() {
487 return _columnNumber;
488 }
489
490
491
492
493 public void setColumnNumber(short newColumnNumber) {
494 _columnNumber = newColumnNumber;
495 }
496
497
498
499
500 public short getColumnId() {
501 return ((_columnId != UNSET_COLUMN_ID) ? _columnId : _columnNumber);
502 }
503
504
505
506
507 public void setColumnId(short newColumnId) {
508 _columnId = newColumnId;
509 }
510
511
512
513
514 public ColumnImpl.SortOrder getTextSortOrder() {
515 return _sortOrder;
516 }
517
518
519
520
521 public void setTextSortOrder(ColumnImpl.SortOrder newTextSortOrder) {
522 _sortOrder = newTextSortOrder;
523 }
524
525
526
527
528 public boolean storeInNullMask() {
529 return (getType() == DataType.BOOLEAN);
530 }
531
532
533
534
535 public int getFixedDataSize() {
536 return _type.getFixedSize(_length);
537 }
538
539
540
541
542
543
544
545 public void validate(JetFormat format) {
546 DatabaseImpl.validateIdentifierName(
547 getName(), format.MAX_COLUMN_NAME_LENGTH, "column");
548
549 if(getType() == null) {
550 throw new IllegalArgumentException(withErrorContext("must have type"));
551 }
552 if(getType().isUnsupported()) {
553 throw new IllegalArgumentException(withErrorContext(
554 "Cannot create column with unsupported type " + getType()));
555 }
556 if(!format.isSupportedDataType(getType())) {
557 throw new IllegalArgumentException(withErrorContext(
558 "Database format " + format + " does not support type " + getType()));
559 }
560
561 if(!getType().isVariableLength()) {
562 if(getLength() < getType().getFixedSize()) {
563 throw new IllegalArgumentException(withErrorContext(
564 "Invalid fixed length size " + getLength()));
565 }
566 } else if(!getType().isLongValue()) {
567 if(!getType().isValidSize(getLength())) {
568 throw new IllegalArgumentException(withErrorContext(
569 "Var length must be from " + getType().getMinSize() + " to " +
570 getType().getMaxSize() + " inclusive, found " + getLength()));
571 }
572 }
573
574 if(getType().getHasScalePrecision()) {
575 if(!getType().isValidScale(getScale())) {
576 throw new IllegalArgumentException(withErrorContext(
577 "Scale must be from " + getType().getMinScale() + " to " +
578 getType().getMaxScale() + " inclusive, found " + getScale()));
579 }
580 if(!getType().isValidPrecision(getPrecision())) {
581 throw new IllegalArgumentException(withErrorContext(
582 "Precision must be from " + getType().getMinPrecision() + " to " +
583 getType().getMaxPrecision() + " inclusive, found " +
584 getPrecision()));
585 }
586 }
587
588 if(isAutoNumber()) {
589 if(!getType().mayBeAutoNumber()) {
590 throw new IllegalArgumentException(withErrorContext(
591 "Auto number column must be long integer or guid"));
592 }
593 }
594
595 if(isCompressedUnicode()) {
596 if(!getType().isTextual()) {
597 throw new IllegalArgumentException(withErrorContext(
598 "Only textual columns allow unicode compression (text/memo)"));
599 }
600 }
601
602 if(isHyperlink()) {
603 if(getType() != DataType.MEMO) {
604 throw new IllegalArgumentException(withErrorContext(
605 "Only memo columns can be hyperlinks"));
606 }
607 }
608
609 if(isAppendOnly()) {
610 if(getType() != DataType.MEMO) {
611 throw new IllegalArgumentException(withErrorContext(
612 "Only memo columns can keep a version history"));
613 }
614 }
615
616 if(getType() == DataType.COMPLEX_TYPE) {
617 ComplexColumnDesc complexDesc = getComplexDesc();
618 if(complexDesc == null) {
619 throw new IllegalArgumentException(withErrorContext(
620 "A complex column is declared with setMultiValue, setAttachment " +
621 "or, for a version history, setAppendOnly on a memo column"));
622 }
623 complexDesc.validate(getName());
624 }
625
626 if(isCalculated()) {
627 if(!format.isSupportedCalculatedDataType(getType())) {
628 throw new IllegalArgumentException(withErrorContext(
629 "Database format " + format + " does not support calculated type " +
630 getType()));
631 }
632
633
634 if(getProperty(PropertyMap.EXPRESSION_PROP) == null) {
635 throw new IllegalArgumentException(withErrorContext(
636 "No expression provided for calculated type " + getType()));
637 }
638
639
640 if(getProperty(PropertyMap.RESULT_TYPE_PROP) == null) {
641 putProperty(PropertyMap.RESULT_TYPE_PROP, getType().getValue());
642 }
643 }
644 }
645
646
647
648
649 public ColumnBuilder toColumn() {
650
651 return this;
652 }
653
654
655
656
657
658 public Column addToTable(Table table) throws IOException {
659 return addToTableDefinition(table);
660 }
661
662
663
664
665
666 public Column addToTableDefinition(TableDefinition table) throws IOException {
667 return new TableUpdater((TableImpl)table).addColumn(this);
668 }
669
670 @Override
671 public String toString() {
672 return ToStringBuilder.builder(this)
673 .append("name", _name)
674 .append("type", _type)
675 .append("number", _columnNumber)
676 .append("id", getColumnId())
677 .appendIfNotNull("length", _length)
678 .appendIfNotNull("precision", _precision)
679 .appendIfNotNull("scale", _scale)
680 .append("autoNumber", _autoNumber)
681 .append("compressedUnicode", _compressedUnicode)
682 .append("calculated", _calculated)
683 .append("hyperlink", _hyperlink)
684 .append("appendOnly", _appendOnly)
685 .appendIfNotNull("complex", getComplexDesc())
686 .appendIfNotNull("textSortOrder", _sortOrder)
687 .appendIfNotNull("props", _props)
688 .toString();
689 }
690
691 private String withErrorContext(String msg) {
692 return msg + "(Column=" + getName() + ")";
693 }
694 }