View Javadoc
1   /*
2   Copyright (c) 2005 Health Market Science, Inc.
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.ObjectOutputStream;
23  import java.io.ObjectStreamException;
24  import java.io.Reader;
25  import java.io.Serializable;
26  import java.math.BigDecimal;
27  import java.math.BigInteger;
28  import java.nio.ByteBuffer;
29  import java.nio.ByteOrder;
30  import java.nio.CharBuffer;
31  import java.nio.charset.Charset;
32  import java.time.DateTimeException;
33  import java.time.Duration;
34  import java.time.Instant;
35  import java.time.LocalDate;
36  import java.time.LocalDateTime;
37  import java.time.LocalTime;
38  import java.time.ZoneId;
39  import java.time.ZonedDateTime;
40  import java.time.temporal.ChronoUnit;
41  import java.time.temporal.TemporalAccessor;
42  import java.time.temporal.TemporalQueries;
43  import java.util.Calendar;
44  import java.util.Collection;
45  import java.util.Date;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.TimeZone;
49  import java.util.UUID;
50  import java.util.regex.Matcher;
51  import java.util.regex.Pattern;
52  
53  import com.healthmarketscience.jackcess.Column;
54  import com.healthmarketscience.jackcess.ColumnBuilder;
55  import com.healthmarketscience.jackcess.DataType;
56  import com.healthmarketscience.jackcess.DateTimeType;
57  import com.healthmarketscience.jackcess.InvalidValueException;
58  import com.healthmarketscience.jackcess.PropertyMap;
59  import com.healthmarketscience.jackcess.Table;
60  import com.healthmarketscience.jackcess.complex.ComplexColumnInfo;
61  import com.healthmarketscience.jackcess.complex.ComplexValue;
62  import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
63  import com.healthmarketscience.jackcess.expr.Identifier;
64  import com.healthmarketscience.jackcess.impl.complex.ComplexValueForeignKeyImpl;
65  import com.healthmarketscience.jackcess.impl.expr.NumberFormatter;
66  import com.healthmarketscience.jackcess.util.ColumnValidator;
67  import com.healthmarketscience.jackcess.util.SimpleColumnValidator;
68  import org.apache.commons.lang3.builder.ToStringBuilder;
69  import org.apache.commons.logging.Log;
70  import org.apache.commons.logging.LogFactory;
71  
72  /**
73   * Access database column definition
74   * @author Tim McCune
75   * @usage _intermediate_class_
76   */
77  public class ColumnImpl implements Column, Comparable<ColumnImpl>, DateTimeContext
78  {
79  
80    protected static final Log LOG = LogFactory.getLog(ColumnImpl.class);
81  
82    /**
83     * Placeholder object for adding rows which indicates that the caller wants
84     * the RowId of the new row.  Must be added as an extra value at the end of
85     * the row values array.
86     * @see TableImpl#asRowWithRowId
87     * @usage _intermediate_field_
88     */
89    public static final Object RETURN_ROW_ID = "<RETURN_ROW_ID>";
90  
91    /**
92     * Access stores numeric dates in days.  Java stores them in milliseconds.
93     */
94    private static final long MILLISECONDS_PER_DAY = (24L * 60L * 60L * 1000L);
95    private static final long SECONDS_PER_DAY = (24L * 60L * 60L);
96    private static final long NANOS_PER_SECOND = 1_000_000_000L;
97    private static final long NANOS_PER_MILLI = 1_000_000L;
98    private static final long MILLIS_PER_SECOND = 1000L;
99  
100   /**
101    * Access starts counting dates at Dec 30, 1899 (note, this strange date
102    * seems to be caused by MS compatibility with Lotus-1-2-3 and incorrect
103    * leap years).  Java starts counting at Jan 1, 1970.  This is the # of
104    * millis between them for conversion.
105    */
106   static final long MILLIS_BETWEEN_EPOCH_AND_1900 =
107     25569L * MILLISECONDS_PER_DAY;
108 
109   public static final LocalDate BASE_LD = LocalDate.of(1899, 12, 30);
110   public static final LocalTime BASE_LT = LocalTime.of(0, 0);
111   public static final LocalDateTime BASE_LDT = LocalDateTime.of(BASE_LD, BASE_LT);
112 
113   private static final LocalDate BASE_EXT_LD = LocalDate.of(1, 1, 1);
114   private static final LocalTime BASE_EXT_LT = LocalTime.of(0, 0);
115   private static final LocalDateTime BASE_EXT_LDT =
116     LocalDateTime.of(BASE_EXT_LD, BASE_EXT_LT);
117   private static final byte[] EXT_LDT_TRAILER = {':', '7', 0x00};
118 
119   private static final DateTimeFactory DEF_DATE_TIME_FACTORY =
120     new DefaultDateTimeFactory();
121 
122   static final DateTimeFactory LDT_DATE_TIME_FACTORY =
123     new LDTDateTimeFactory();
124 
125   /**
126    * mask for the fixed len bit
127    * @usage _advanced_field_
128    */
129   public static final byte FIXED_LEN_FLAG_MASK = (byte)0x01;
130 
131   /**
132    * mask for the auto number bit
133    * @usage _advanced_field_
134    */
135   public static final byte AUTO_NUMBER_FLAG_MASK = (byte)0x04;
136 
137   /**
138    * mask for the auto number guid bit
139    * @usage _advanced_field_
140    */
141   public static final byte AUTO_NUMBER_GUID_FLAG_MASK = (byte)0x40;
142 
143   /**
144    * mask for the hyperlink bit (on memo types)
145    * @usage _advanced_field_
146    */
147   public static final byte HYPERLINK_FLAG_MASK = (byte)0x80;
148 
149   /**
150    * mask for the "is updatable" field bit
151    * @usage _advanced_field_
152    */
153   public static final byte UPDATABLE_FLAG_MASK = (byte)0x02;
154 
155   // some other flags?
156   // 0x10: replication related field (or hidden?)
157 
158   protected static final byte COMPRESSED_UNICODE_EXT_FLAG_MASK = (byte)0x01;
159   private static final byte CALCULATED_EXT_FLAG_MASK = (byte)0xC0;
160 
161   static final byte NUMERIC_NEGATIVE_BYTE = (byte)0x80;
162 
163   /** the value for the "general" sort order */
164   private static final short GENERAL_SORT_ORDER_VALUE = 1033;
165 
166   /**
167    * the "general" text sort order, version (access 1997)
168    * @usage _intermediate_field_
169    */
170   public static final SortOrder GENERAL_97_SORT_ORDER =
171     new SortOrder(GENERAL_SORT_ORDER_VALUE, (short)-1);
172 
173   /**
174    * the "general" text sort order, legacy version (access 2000-2007)
175    * @usage _intermediate_field_
176    */
177   public static final SortOrder GENERAL_LEGACY_SORT_ORDER =
178     new SortOrder(GENERAL_SORT_ORDER_VALUE, (short)0);
179 
180   /**
181    * the "general" text sort order, latest version (access 2010+)
182    * @usage _intermediate_field_
183    */
184   public static final SortOrder GENERAL_SORT_ORDER =
185     new SortOrder(GENERAL_SORT_ORDER_VALUE, (short)1);
186 
187   /** pattern matching textual guid strings (allows for optional surrounding
188       '{' and '}') */
189   private static final Pattern GUID_PATTERN = Pattern.compile("\\s*[{]?([\\p{XDigit}]{8})-([\\p{XDigit}]{4})-([\\p{XDigit}]{4})-([\\p{XDigit}]{4})-([\\p{XDigit}]{12})[}]?\\s*");
190 
191   /** header used to indicate unicode text compression */
192   private static final byte[] TEXT_COMPRESSION_HEADER =
193   { (byte)0xFF, (byte)0XFE };
194   private static final char MIN_COMPRESS_CHAR = 1;
195   private static final char MAX_COMPRESS_CHAR = 0xFF;
196 
197   /** auto numbers must be > 0 */
198   static final int INVALID_AUTO_NUMBER = 0;
199 
200   static final int INVALID_LENGTH = -1;
201 
202 
203   /** owning table */
204   private final TableImpl _table;
205   /** Whether or not the column is of variable length */
206   private final boolean _variableLength;
207   /** Whether or not the column is an autonumber column */
208   private final boolean _autoNumber;
209   /** Whether or not the column is a calculated column */
210   private final boolean _calculated;
211   /** Data type */
212   private final DataType _type;
213   /** Maximum column length */
214   private final short _columnLength;
215   /** 0-based column number */
216   private final short _columnNumber;
217   /** index of the data for this column within a list of row data */
218   private int _columnIndex;
219   /** display index of the data for this column */
220   private final int _displayIndex;
221   /** Column name */
222   private final String _name;
223   /** the offset of the fixed data in the row */
224   private final int _fixedDataOffset;
225   /** the index of the variable length data in the var len offset table */
226   private final int _varLenTableIndex;
227   /** the auto number generator for this column (if autonumber column) */
228   private final AutoNumberGenerator _autoNumberGenerator;
229   /** properties for this column, if any */
230   private PropertyMap _props;
231   /** Validator for writing new values */
232   private ColumnValidator _validator = SimpleColumnValidator.INSTANCE;
233   /** default value generator */
234   private ColDefaultValueEvalContext _defValue;
235   /** length of the column in units, lazily computed */
236   private int _lengthInUnits = INVALID_LENGTH;
237 
238   /**
239    * @usage _advanced_method_
240    */
241   protected ColumnImpl(TableImpl table, String name, DataType type,
242                        int colNumber, int fixedOffset, int varLenIndex) {
243     _table = table;
244     _name = name;
245     _type = type;
246 
247     if(!_type.isVariableLength()) {
248       _columnLength = (short)type.getFixedSize();
249     } else {
250       _columnLength = (short)type.getMaxSize();
251     }
252     _variableLength = type.isVariableLength();
253     _autoNumber = false;
254     _calculated = false;
255     _autoNumberGenerator = null;
256     _columnNumber = (short)colNumber;
257     _columnIndex = colNumber;
258     _displayIndex = colNumber;
259     _fixedDataOffset = fixedOffset;
260     _varLenTableIndex = varLenIndex;
261   }
262 
263   /**
264    * Read a column definition in from a buffer
265    * @usage _advanced_method_
266    */
267   ColumnImpl(InitArgs args)
268     throws IOException
269   {
270     _table = args.table;
271     _name = args.name;
272     _displayIndex = args.displayIndex;
273     _type = args.type;
274 
275     _columnNumber = args.buffer.getShort(
276         args.offset + getFormat().OFFSET_COLUMN_NUMBER);
277     _columnLength = args.buffer.getShort(
278         args.offset + getFormat().OFFSET_COLUMN_LENGTH);
279 
280     _variableLength = ((args.flags & FIXED_LEN_FLAG_MASK) == 0);
281     _autoNumber = ((args.flags &
282                     (AUTO_NUMBER_FLAG_MASK | AUTO_NUMBER_GUID_FLAG_MASK)) != 0);
283     _calculated = ((args.extFlags & CALCULATED_EXT_FLAG_MASK) != 0);
284 
285     _autoNumberGenerator = createAutoNumberGenerator();
286 
287     _varLenTableIndex = args.buffer.getShort(
288         args.offset + getFormat().OFFSET_COLUMN_VARIABLE_TABLE_INDEX);
289     _fixedDataOffset = args.buffer.getShort(
290           args.offset + getFormat().OFFSET_COLUMN_FIXED_DATA_OFFSET);
291   }
292 
293   /**
294    * Creates the appropriate ColumnImpl class and reads a column definition in
295    * from a buffer
296    * @param table owning table
297    * @param buffer Buffer containing column definition
298    * @param offset Offset in the buffer at which the column definition starts
299    * @usage _advanced_method_
300    */
301   public static ColumnImpl create(TableImpl table, ByteBuffer buffer,
302                                   int offset, String name, int displayIndex)
303     throws IOException
304   {
305     InitArgs args = new InitArgs(table, buffer, offset, name, displayIndex);
306 
307     boolean calculated = ((args.extFlags & CALCULATED_EXT_FLAG_MASK) != 0);
308     byte colType = args.colType;
309     if(calculated) {
310       // "real" data type is in the "result type" property
311       PropertyMap colProps = table.getPropertyMaps().get(name);
312       Byte resultType = (Byte)colProps.getValue(PropertyMap.RESULT_TYPE_PROP);
313       if(resultType != null) {
314         colType = resultType;
315       }
316     }
317 
318     try {
319       args.type = DataType.fromByte(colType);
320     } catch(IOException e) {
321       LOG.warn(withErrorContext("Unsupported column type " + colType,
322                                 table.getDatabase(), table.getName(), name));
323       boolean variableLength = ((args.flags & FIXED_LEN_FLAG_MASK) == 0);
324       args.type = (variableLength ? DataType.UNSUPPORTED_VARLEN :
325                    DataType.UNSUPPORTED_FIXEDLEN);
326       return new UnsupportedColumnImpl(args);
327     }
328 
329     if(calculated) {
330       return CalculatedColumnUtil.create(args);
331     }
332 
333     switch(args.type) {
334     case TEXT:
335       return new TextColumnImpl(args);
336     case MEMO:
337       return new MemoColumnImpl(args);
338     case COMPLEX_TYPE:
339       return new ComplexColumnImpl(args);
340     default:
341       // fall through
342     }
343 
344     if(args.type.getHasScalePrecision()) {
345       return new NumericColumnImpl(args);
346     }
347     if(args.type.isLongValue()) {
348       return new LongValueColumnImpl(args);
349     }
350 
351     return new ColumnImpl(args);
352   }
353 
354   /**
355    * Sets the usage maps for this column.
356    */
357   void setUsageMaps(UsageMap./../../com/healthmarketscience/jackcess/impl/UsageMap.html#UsageMap">UsageMap ownedPages, UsageMap freeSpacePages) {
358     // base does nothing
359   }
360 
361   void collectUsageMapPages(Collection<Integer> pages) {
362     // base does nothing
363   }
364 
365   /**
366    * Secondary column initialization after the table is fully loaded.
367    */
368   void postTableLoadInit() throws IOException {
369     // base does nothing
370   }
371 
372   @Override
373   public TableImpl getTable() {
374     return _table;
375   }
376 
377   @Override
378   public DatabaseImpl getDatabase() {
379     return getTable().getDatabase();
380   }
381 
382   /**
383    * @usage _advanced_method_
384    */
385   public JetFormat getFormat() {
386     return getDatabase().getFormat();
387   }
388 
389   /**
390    * @usage _advanced_method_
391    */
392   public PageChannel getPageChannel() {
393     return getDatabase().getPageChannel();
394   }
395 
396   @Override
397   public String getName() {
398     return _name;
399   }
400 
401   @Override
402   public boolean isVariableLength() {
403     return _variableLength;
404   }
405 
406   @Override
407   public boolean isAutoNumber() {
408     return _autoNumber;
409   }
410 
411   /**
412    * @usage _advanced_method_
413    */
414   public short getColumnNumber() {
415     return _columnNumber;
416   }
417 
418   @Override
419   public int getColumnIndex() {
420     return _columnIndex;
421   }
422 
423   /**
424    * @usage _advanced_method_
425    */
426   public void setColumnIndex(int newColumnIndex) {
427     _columnIndex = newColumnIndex;
428   }
429 
430   /**
431    * @usage _advanced_method_
432    */
433   public int getDisplayIndex() {
434     return _displayIndex;
435   }
436 
437   @Override
438   public DataType getType() {
439     return _type;
440   }
441 
442   @Override
443   public int getSQLType() throws IOException {
444     return _type.getSQLType();
445   }
446 
447   @Override
448   public boolean isCompressedUnicode() {
449     return false;
450   }
451 
452   @Override
453   public byte getPrecision() {
454     return (byte)getType().getDefaultPrecision();
455   }
456 
457   @Override
458   public byte getScale() {
459     return (byte)getType().getDefaultScale();
460   }
461 
462   /**
463    * @usage _intermediate_method_
464    */
465   public SortOrder getTextSortOrder() {
466     return null;
467   }
468 
469   /**
470    * @usage _intermediate_method_
471    */
472   public short getTextCodePage() {
473     return 0;
474   }
475 
476   @Override
477   public short getLength() {
478     return _columnLength;
479   }
480 
481   @Override
482   public final short getLengthInUnits() {
483     if(_lengthInUnits == INVALID_LENGTH) {
484       _lengthInUnits = calcLengthInUnits();
485     }
486     return (short)_lengthInUnits;
487   }
488 
489   protected int calcLengthInUnits() {
490     return getType().toUnitSize(getLength(), getFormat());
491   }
492 
493   @Override
494   public boolean isCalculated() {
495     return _calculated;
496   }
497 
498   /**
499    * @usage _advanced_method_
500    */
501   public int getVarLenTableIndex() {
502     return _varLenTableIndex;
503   }
504 
505   /**
506    * @usage _advanced_method_
507    */
508   public int getFixedDataOffset() {
509     return _fixedDataOffset;
510   }
511 
512   protected int getFixedDataSize() {
513     return _type.getFixedSize(_columnLength);
514   }
515 
516   protected Charset getCharset() {
517     return getDatabase().getCharset();
518   }
519 
520   @Override
521   public TimeZone getTimeZone() {
522     return getDatabase().getTimeZone();
523   }
524 
525   @Override
526   public ZoneId getZoneId() {
527     return getDatabase().getZoneId();
528   }
529 
530   @Override
531   public DateTimeFactory getDateTimeFactory() {
532     return getDatabase().getDateTimeFactory();
533   }
534 
535   @Override
536   public boolean isAppendOnly() {
537     return (getVersionHistoryColumn() != null);
538   }
539 
540   @Override
541   public ColumnImpl getVersionHistoryColumn() {
542     return null;
543   }
544 
545   /**
546    * Returns the number of database pages owned by this column.
547    * @usage _intermediate_method_
548    */
549   public int getOwnedPageCount() {
550     return 0;
551   }
552 
553   /**
554    * @usage _advanced_method_
555    */
556   public void setVersionHistoryColumn(ColumnImpl versionHistoryCol) {
557     throw new UnsupportedOperationException();
558   }
559 
560   @Override
561   public boolean isHyperlink() {
562     return false;
563   }
564 
565   @Override
566   public ComplexColumnInfo<? extends ComplexValue> getComplexInfo() {
567     return null;
568   }
569 
570   void initColumnValidator() throws IOException {
571 
572     if(getDatabase().isReadOnly()) {
573       // validators are irrelevant for read-only databases
574       return;
575     }
576 
577     // first initialize any "external" (user-defined) validator
578     setColumnValidator(null);
579 
580     // next, initialize any "internal" (property defined) validators
581     reloadPropertiesValidators();
582   }
583 
584   void reloadPropertiesValidators() throws IOException {
585 
586     if(isAutoNumber()) {
587       // none of the props stuff applies to autonumber columns
588       return;
589     }
590 
591     if(isCalculated()) {
592 
593       CalcColEvalContext calcCol = null;
594 
595       if(getDatabase().isEvaluateExpressions()) {
596 
597         // init calc col expression evaluator
598         PropertyMap props = getProperties();
599         String calcExpr = (String)props.getValue(PropertyMap.EXPRESSION_PROP);
600         calcCol = new CalcColEvalContext(this).setExpr(calcExpr);
601       }
602 
603       setCalcColEvalContext(calcCol);
604 
605       // none of the remaining props stuff applies to calculated columns
606       return;
607     }
608 
609     // discard any existing internal validators and re-compute them
610     // (essentially unwrap the external validator)
611     _validator = getColumnValidator();
612     _defValue = null;
613 
614     PropertyMap props = getProperties();
615 
616     // if the "required" property is enabled, add appropriate validator
617     boolean required = (Boolean)props.getValue(PropertyMap.REQUIRED_PROP,
618                                                Boolean.FALSE);
619     if(required) {
620       _validator = new RequiredColValidator(_validator);
621     }
622 
623     // if the "allow zero len" property is disabled (textual columns only),
624     // add appropriate validator
625     boolean allowZeroLen =
626       !getType().isTextual() ||
627       (Boolean)props.getValue(PropertyMap.ALLOW_ZERO_LEN_PROP,
628                               Boolean.TRUE);
629     if(!allowZeroLen) {
630       _validator = new NoZeroLenColValidator(_validator);
631     }
632 
633     // only check for props based exprs if this is enabled
634     if(!getDatabase().isEvaluateExpressions()) {
635       return;
636     }
637 
638     String exprStr = PropertyMaps.getTrimmedStringProperty(
639         props, PropertyMap.VALIDATION_RULE_PROP);
640 
641     if(exprStr != null) {
642       String helpStr = PropertyMaps.getTrimmedStringProperty(
643           props, PropertyMap.VALIDATION_TEXT_PROP);
644 
645       _validator = new ColValidatorEvalContext(this)
646         .setExpr(exprStr, helpStr)
647         .toColumnValidator(_validator);
648     }
649 
650     String defValueStr = PropertyMaps.getTrimmedStringProperty(
651         props, PropertyMap.DEFAULT_VALUE_PROP);
652     if(defValueStr != null) {
653       _defValue = new ColDefaultValueEvalContext(this)
654         .setExpr(defValueStr);
655     }
656   }
657 
658   void propertiesUpdated() throws IOException {
659     reloadPropertiesValidators();
660   }
661 
662   @Override
663   public ColumnValidator getColumnValidator() {
664     // unwrap any "internal" validator
665     return ((_validator instanceof InternalColumnValidator) ?
666             ((InternalColumnValidator)_validator).getExternal() : _validator);
667   }
668 
669   @Override
670   public void setColumnValidator(ColumnValidator newValidator) {
671 
672     if(isAutoNumber()) {
673       // cannot set autonumber validator (autonumber values are controlled
674       // internally)
675       if(newValidator != null) {
676         throw new IllegalArgumentException(withErrorContext(
677                 "Cannot set ColumnValidator for autonumber columns"));
678       }
679       // just leave default validator instance alone
680       return;
681     }
682 
683     if(newValidator == null) {
684       newValidator = getDatabase().getColumnValidatorFactory()
685         .createValidator(this);
686       if(newValidator == null) {
687         newValidator = SimpleColumnValidator.INSTANCE;
688       }
689     }
690 
691     // handle delegation if "internal" validator in use
692     if(_validator instanceof InternalColumnValidator) {
693       ((InternalColumnValidator)_validator).setExternal(newValidator);
694     } else {
695       _validator = newValidator;
696     }
697   }
698 
699   byte getOriginalDataType() {
700     return _type.getValue();
701   }
702 
703   private AutoNumberGenerator createAutoNumberGenerator() {
704     if(!_autoNumber || (_type == null)) {
705       return null;
706     }
707 
708     switch(_type) {
709     case LONG:
710       return new LongAutoNumberGenerator();
711     case GUID:
712       return new GuidAutoNumberGenerator();
713     case COMPLEX_TYPE:
714       return new ComplexTypeAutoNumberGenerator();
715     default:
716       LOG.warn(withErrorContext("Unknown auto number column type " + _type));
717       return new UnsupportedAutoNumberGenerator(_type);
718     }
719   }
720 
721   /**
722    * Returns the AutoNumberGenerator for this column if this is an autonumber
723    * column, {@code null} otherwise.
724    * @usage _advanced_method_
725    */
726   public AutoNumberGenerator getAutoNumberGenerator() {
727     return _autoNumberGenerator;
728   }
729 
730   @Override
731   public PropertyMap getProperties() throws IOException {
732     if(_props == null) {
733       _props = getTable().getPropertyMaps().get(getName());
734     }
735     return _props;
736   }
737 
738   @Override
739   public Object setRowValue(Object[] rowArray, Object value) {
740     rowArray[_columnIndex] = value;
741     return value;
742   }
743 
744   @Override
745   public Object setRowValue(Map<String,Object> rowMap, Object value) {
746     rowMap.put(_name, value);
747     return value;
748   }
749 
750   @Override
751   public Object getRowValue(Object[] rowArray) {
752     return rowArray[_columnIndex];
753   }
754 
755   @Override
756   public Object getRowValue(Map<String,?> rowMap) {
757     return rowMap.get(_name);
758   }
759 
760   public boolean storeInNullMask() {
761     return (getType() == DataType.BOOLEAN);
762   }
763 
764   public boolean writeToNullMask(Object value) {
765     return toBooleanValue(value);
766   }
767 
768   public Object readFromNullMask(boolean isNull) {
769     return Boolean.valueOf(!isNull);
770   }
771 
772   /**
773    * Deserialize a raw byte value for this column into an Object
774    * @param data The raw byte value
775    * @return The deserialized Object
776    * @usage _advanced_method_
777    */
778   public Object read(byte[] data) throws IOException {
779     return read(data, PageChannel.DEFAULT_BYTE_ORDER);
780   }
781 
782   /**
783    * Deserialize a raw byte value for this column into an Object
784    * @param data The raw byte value
785    * @param order Byte order in which the raw value is stored
786    * @return The deserialized Object
787    * @usage _advanced_method_
788    */
789   public Object read(byte[] data, ByteOrder order) throws IOException {
790     ByteBuffer buffer = ByteBuffer.wrap(data).order(order);
791 
792     switch(getType()) {
793     case BOOLEAN:
794       throw new IOException(withErrorContext("Tried to read a boolean from data instead of null mask."));
795     case BYTE:
796       return Byte.valueOf(buffer.get());
797     case INT:
798       return Short.valueOf(buffer.getShort());
799     case LONG:
800       return Integer.valueOf(buffer.getInt());
801     case DOUBLE:
802       return Double.valueOf(buffer.getDouble());
803     case FLOAT:
804       return Float.valueOf(buffer.getFloat());
805     case SHORT_DATE_TIME:
806       return readDateValue(buffer);
807     case BINARY:
808       return data;
809     case TEXT:
810       return decodeTextValue(data);
811     case MONEY:
812       return readCurrencyValue(buffer);
813     case NUMERIC:
814       return readNumericValue(buffer);
815     case GUID:
816       return readGUIDValue(buffer, order);
817     case EXT_DATE_TIME:
818       return readExtendedDateValue(buffer);
819     case UNKNOWN_0D:
820     case UNKNOWN_11:
821       // treat like "binary" data
822       return data;
823     case COMPLEX_TYPE:
824       return new ComplexValueForeignKeyImpl(this, buffer.getInt());
825     case BIG_INT:
826       return Long.valueOf(buffer.getLong());
827     default:
828       throw new IOException(withErrorContext("Unrecognized data type: " + _type));
829     }
830   }
831 
832   /**
833    * Decodes "Currency" values.
834    *
835    * @param buffer Column value that points to currency data
836    * @return BigDecimal representing the monetary value
837    * @throws IOException if the value cannot be parsed
838    */
839   private BigDecimal readCurrencyValue(ByteBuffer buffer)
840     throws IOException
841   {
842     if(buffer.remaining() != 8) {
843       throw new IOException(withErrorContext("Invalid money value"));
844     }
845 
846     return new BigDecimal(BigInteger.valueOf(buffer.getLong(0)), 4);
847   }
848 
849   /**
850    * Writes "Currency" values.
851    */
852   private void writeCurrencyValue(ByteBuffer buffer, Object value)
853     throws IOException
854   {
855     Object inValue = value;
856     try {
857       BigDecimal decVal = toBigDecimal(value);
858       inValue = decVal;
859 
860       // adjust scale (will cause the an ArithmeticException if number has too
861       // many decimal places)
862       decVal = decVal.setScale(4);
863 
864       // now, remove scale and convert to long (this will throw if the value is
865       // too big)
866       buffer.putLong(decVal.movePointRight(4).longValueExact());
867     } catch(ArithmeticException e) {
868       throw new IOException(
869           withErrorContext("Currency value '" + inValue + "' out of range"), e);
870     }
871   }
872 
873   /**
874    * Decodes a NUMERIC field.
875    */
876   private BigDecimal readNumericValue(ByteBuffer buffer)
877   {
878     boolean negate = (buffer.get() != 0);
879 
880     byte[] tmpArr = ByteUtil.getBytes(buffer, 16);
881 
882     if(buffer.order() != ByteOrder.BIG_ENDIAN) {
883       fixNumericByteOrder(tmpArr);
884     }
885 
886     return toBigDecimal(tmpArr, negate, getScale());
887   }
888 
889   static BigDecimal toBigDecimal(byte[] bytes, boolean negate, int scale)
890   {
891     if((bytes[0] & 0x80) != 0) {
892       // the data is effectively unsigned, but the BigInteger handles it as
893       // signed twos complement.  we need to add an extra byte to the input so
894       // that it will be treated as unsigned
895       bytes = ByteUtil.copyOf(bytes, 0, bytes.length + 1, 1);
896     }
897     BigInteger intVal = new BigInteger(bytes);
898     if(negate) {
899       intVal = intVal.negate();
900     }
901     return new BigDecimal(intVal, scale);
902   }
903 
904   /**
905    * Writes a numeric value.
906    */
907   private void writeNumericValue(ByteBuffer buffer, Object value)
908     throws IOException
909   {
910     Object inValue = value;
911     try {
912       BigDecimal decVal = toBigDecimal(value);
913       inValue = decVal;
914 
915       int signum = decVal.signum();
916       if(signum < 0) {
917         decVal = decVal.negate();
918       }
919 
920       // write sign byte
921       buffer.put((signum < 0) ? NUMERIC_NEGATIVE_BYTE : 0);
922 
923       // adjust scale according to this column type (will cause the an
924       // ArithmeticException if number has too many decimal places)
925       decVal = decVal.setScale(getScale());
926 
927       // check precision
928       if(decVal.precision() > getPrecision()) {
929         throw new InvalidValueException(withErrorContext(
930             "Numeric value is too big for specified precision "
931             + getPrecision() + ": " + decVal));
932       }
933 
934       // convert to unscaled BigInteger, big-endian bytes
935       byte[] intValBytes = toUnscaledByteArray(
936           decVal, getType().getFixedSize() - 1);
937       if(buffer.order() != ByteOrder.BIG_ENDIAN) {
938         fixNumericByteOrder(intValBytes);
939       }
940       buffer.put(intValBytes);
941     } catch(ArithmeticException e) {
942       throw new IOException(
943           withErrorContext("Numeric value '" + inValue + "' out of range"), e);
944     }
945   }
946 
947   byte[] toUnscaledByteArray(BigDecimal decVal, int maxByteLen)
948     throws IOException
949   {
950     // convert to unscaled BigInteger, big-endian bytes
951     byte[] intValBytes = decVal.unscaledValue().toByteArray();
952     if(intValBytes.length > maxByteLen) {
953       if((intValBytes[0] == 0) && ((intValBytes.length - 1) == maxByteLen)) {
954         // in order to not return a negative two's complement value,
955         // toByteArray() may return an extra leading 0 byte.  we are working
956         // with unsigned values, so we can drop the extra leading 0
957         intValBytes = ByteUtil.copyOf(intValBytes, 1, maxByteLen);
958       } else {
959         throw new InvalidValueException(withErrorContext(
960                                   "Too many bytes for valid BigInteger?"));
961       }
962     } else if(intValBytes.length < maxByteLen) {
963       intValBytes = ByteUtil.copyOf(intValBytes, 0, maxByteLen,
964                                     (maxByteLen - intValBytes.length));
965     }
966     return intValBytes;
967   }
968 
969   /**
970    * Decodes a date value.
971    */
972   private Object readDateValue(ByteBuffer buffer) {
973     long dateBits = buffer.getLong();
974     return getDateTimeFactory().fromDateBits(this, dateBits);
975   }
976 
977   /**
978    * Decodes an "extended" date/time value.
979    */
980   private static Object readExtendedDateValue(ByteBuffer buffer) {
981     // format: <19digits>:<19digits>:7 0x00
982     long numDays = readExtDateLong(buffer, 19);
983     buffer.get();
984     long seconds = readExtDateLong(buffer, 12);
985     // there are 7 fractional digits
986     long nanos = readExtDateLong(buffer, 7) * 100L;
987     ByteUtil.forward(buffer, EXT_LDT_TRAILER.length);
988 
989     return BASE_EXT_LDT
990       .plusDays(numDays)
991       .plusSeconds(seconds)
992       .plusNanos(nanos);
993   }
994 
995   /**
996    * Reads the given number of ascii encoded characters as a long value.
997    */
998   private static long readExtDateLong(ByteBuffer buffer, int numChars) {
999     long val = 0L;
1000     for(int i = 0; i < numChars; ++i) {
1001       char digit = (char)buffer.get();
1002       long inc = digit - '0';
1003       val = (val * 10L) + inc;
1004     }
1005     return val;
1006   }
1007 
1008   /**
1009    * Returns a java long time value converted from an access date double.
1010    * @usage _advanced_method_
1011    */
1012   public long fromDateDouble(double value) {
1013     return fromDateDouble(value, getTimeZone());
1014   }
1015 
1016   private static long fromDateDouble(double value, TimeZone tz) {
1017     long localTime = fromLocalDateDouble(value);
1018     return localTime - getFromLocalTimeZoneOffset(localTime, tz);
1019   }
1020 
1021   static long fromLocalDateDouble(double value) {
1022     long datePart = ((long)value) * MILLISECONDS_PER_DAY;
1023 
1024     // the fractional part of the double represents the time.  it is always
1025     // a positive fraction of the day (even if the double is negative),
1026     // _not_ the time distance from zero (as one would expect with "normal"
1027     // numbers).  therefore, we need to do a little number logic to convert
1028     // the absolute time fraction into a normal distance from zero number.
1029     long timePart = Math.round((Math.abs(value) % 1.0d) *
1030                                MILLISECONDS_PER_DAY);
1031 
1032     long time = datePart + timePart;
1033     return time - MILLIS_BETWEEN_EPOCH_AND_1900;
1034   }
1035 
1036   public static LocalDateTime ldtFromLocalDateDouble(double value) {
1037     Duration dateTimeOffset = durationFromLocalDateDouble(value);
1038     return BASE_LDT.plus(dateTimeOffset);
1039   }
1040 
1041   private static Duration durationFromLocalDateDouble(double value) {
1042     long dateSeconds = ((long)value) * SECONDS_PER_DAY;
1043 
1044     // the fractional part of the double represents the time.  it is always
1045     // a positive fraction of the day (even if the double is negative),
1046     // _not_ the time distance from zero (as one would expect with "normal"
1047     // numbers).  therefore, we need to do a little number logic to convert
1048     // the absolute time fraction into a normal distance from zero number.
1049 
1050     double secondsDouble = (Math.abs(value) % 1.0d) * SECONDS_PER_DAY;
1051     long timeSeconds = (long)secondsDouble;
1052     long timeMillis = (long)(roundToMillis(secondsDouble % 1.0d) *
1053                              MILLIS_PER_SECOND);
1054 
1055     return Duration.ofSeconds(dateSeconds + timeSeconds,
1056                               timeMillis * NANOS_PER_MILLI);
1057   }
1058 
1059   /**
1060    * Writes a date value.
1061    */
1062   private void writeDateValue(ByteBuffer buffer, Object value)
1063     throws InvalidValueException
1064   {
1065     if(value == null) {
1066       buffer.putDouble(0d);
1067     } else if(value instanceof DateExt) {
1068       // this is a Date value previously read from readDateValue().  use the
1069       // original bits to store the value so we don't lose any precision
1070       buffer.putLong(((DateExt)value).getDateBits());
1071     } else {
1072       buffer.putDouble(toDateDouble(value));
1073     }
1074   }
1075 
1076   /**
1077    * Writes an "extended" date/time value.
1078    */
1079   private void writeExtendedDateValue(ByteBuffer buffer, Object value)
1080   {
1081     LocalDateTime ldt = BASE_EXT_LDT;
1082     if(value != null) {
1083       ldt = toLocalDateTime(value, this);
1084     }
1085 
1086     LocalDate ld = ldt.toLocalDate();
1087     LocalTime lt = ldt.toLocalTime();
1088 
1089     long numDays = BASE_EXT_LD.until(ld, ChronoUnit.DAYS);
1090     long numSeconds = BASE_EXT_LT.until(lt, ChronoUnit.SECONDS);
1091     long nanos = lt.getNano();
1092 
1093     // format: <19digits>:<19digits>:7 0x00
1094     writeExtDateLong(buffer, numDays, 19);
1095     buffer.put((byte)':');
1096     writeExtDateLong(buffer, numSeconds, 12);
1097     // there are 7 fractional digits
1098     writeExtDateLong(buffer, (nanos / 100L), 7);
1099 
1100     buffer.put(EXT_LDT_TRAILER);
1101   }
1102 
1103   /**
1104    * Writes the given long value as the given number of ascii encoded
1105    * characters.
1106    */
1107   private static void writeExtDateLong(
1108       ByteBuffer buffer, long val, int numChars) {
1109     // we write the desired number of digits in reverse order
1110     int end = buffer.position();
1111     int start = end + numChars - 1;
1112     for(int i = start; i >= end; --i) {
1113       char digit = (char)('0' + (char)(val % 10L));
1114       buffer.put(i, (byte)digit);
1115       val /= 10L;
1116     }
1117     ByteUtil.forward(buffer, numChars);
1118   }
1119 
1120   /**
1121    * Returns an access date double converted from a java Date/Calendar/Number
1122    * time value.
1123    * @usage _advanced_method_
1124    */
1125   public double toDateDouble(Object value)
1126     throws InvalidValueException
1127   {
1128     try {
1129       return toDateDouble(value, this);
1130     } catch(IllegalArgumentException iae) {
1131       throw new InvalidValueException(withErrorContext(iae.getMessage()), iae);
1132     }
1133   }
1134 
1135   /**
1136    * Returns an access date double converted from a java
1137    * Date/Calendar/Number/Temporal time value.
1138    * @usage _advanced_method_
1139    */
1140   private static double toDateDouble(Object value, DateTimeContext dtc) {
1141     return dtc.getDateTimeFactory().toDateDouble(value, dtc);
1142   }
1143 
1144   static LocalDateTime toLocalDateTime(
1145       Object value, DateTimeContext dtc) {
1146     if(value instanceof TemporalAccessor) {
1147       return temporalToLocalDateTime((TemporalAccessor)value, dtc);
1148     }
1149     Instant inst = Instant.ofEpochMilli(toDateLong(value));
1150     return LocalDateTime.ofInstant(inst, dtc.getZoneId());
1151   }
1152 
1153   private static LocalDateTime temporalToLocalDateTime(
1154       TemporalAccessor value, DateTimeContext dtc) {
1155 
1156     // handle some common Temporal types
1157     if(value instanceof LocalDateTime) {
1158       return (LocalDateTime)value;
1159     }
1160     if(value instanceof ZonedDateTime) {
1161       // if the temporal value has a timezone, convert it to this db's timezone
1162       return ((ZonedDateTime)value).withZoneSameInstant(
1163           dtc.getZoneId()).toLocalDateTime();
1164     }
1165     if(value instanceof Instant) {
1166       return LocalDateTime.ofInstant((Instant)value, dtc.getZoneId());
1167     }
1168     if(value instanceof LocalDate) {
1169       return ((LocalDate)value).atTime(BASE_LT);
1170     }
1171     if(value instanceof LocalTime) {
1172       return ((LocalTime)value).atDate(BASE_LD);
1173     }
1174 
1175     // generic handling for many other Temporal types
1176     try {
1177 
1178       LocalDate ld = value.query(TemporalQueries.localDate());
1179       if(ld == null) {
1180         ld = BASE_LD;
1181       }
1182       LocalTime lt = value.query(TemporalQueries.localTime());
1183       if(lt == null) {
1184         lt = BASE_LT;
1185       }
1186       ZoneId zone = value.query(TemporalQueries.zone());
1187       if(zone != null) {
1188         // the Temporal has a zone, see if it is the right zone.  if not,
1189         // adjust it
1190         ZoneId zoneId = dtc.getZoneId();
1191         if(!zoneId.equals(zone)) {
1192           return ZonedDateTime.of(ld, lt, zone).withZoneSameInstant(zoneId)
1193             .toLocalDateTime();
1194         }
1195       }
1196 
1197       return LocalDateTime.of(ld, lt);
1198 
1199     } catch(DateTimeException | ArithmeticException e) {
1200       throw new IllegalArgumentException(
1201           "Unsupported temporal type " + value.getClass(), e);
1202     }
1203   }
1204 
1205   private static Instant toInstant(TemporalAccessor value, DateTimeContext dtc) {
1206     if(value instanceof ZonedDateTime) {
1207       return ((ZonedDateTime)value).toInstant();
1208     }
1209     if(value instanceof Instant) {
1210       return (Instant)value;
1211     }
1212     return temporalToLocalDateTime(value, dtc).atZone(dtc.getZoneId())
1213       .toInstant();
1214   }
1215 
1216   static double toLocalDateDouble(long time) {
1217     time += MILLIS_BETWEEN_EPOCH_AND_1900;
1218 
1219     if(time < 0L) {
1220       // reverse the crazy math described in fromLocalDateDouble
1221       long timePart = -time % MILLISECONDS_PER_DAY;
1222       if(timePart > 0) {
1223         time -= (2 * (MILLISECONDS_PER_DAY - timePart));
1224       }
1225     }
1226 
1227     return time / (double)MILLISECONDS_PER_DAY;
1228   }
1229 
1230   public static double toDateDouble(LocalDateTime ldt) {
1231     Duration dateTimeOffset = Duration.between(BASE_LDT, ldt);
1232     return toLocalDateDouble(dateTimeOffset);
1233   }
1234 
1235   private static double toLocalDateDouble(Duration time) {
1236     long dateTimeSeconds = time.getSeconds();
1237     long timeSeconds = dateTimeSeconds % SECONDS_PER_DAY;
1238     if(timeSeconds < 0) {
1239       timeSeconds += SECONDS_PER_DAY;
1240     }
1241     long dateSeconds = dateTimeSeconds - timeSeconds;
1242     long timeNanos = time.getNano();
1243 
1244     // we have a difficult choice to make here between keeping a value which
1245     // most accurately represents the bits saved and rounding to a value that
1246     // would match what the user would expect too see.  since we do a double
1247     // to long conversion, we end up in a situation where the value might be
1248     // 19.9999 seconds.  access will display this as 20 seconds (access seems
1249     // to only record times to second precision).  if we return 19.9999, then
1250     // when the value is written back out it will be exactly the same double
1251     // (good), but will display as 19 seconds (bad because it looks wrong to
1252     // the user).  on the flip side, if we round, the value will display
1253     // "correctly" to the user, but if the value is written back out, it will
1254     // be a slightly different double value.  this may not be a problem for
1255     // most situations, but may result in incorrect index based lookups.  in
1256     // the old date time handling we use DateExt to store the original bits.
1257     // in jdk8, we cannot extend LocalDateTime.  for now, we will try
1258     // returning the value rounded to milliseconds (technically still more
1259     // precision than access uses but more likely to round trip to the same
1260     // value).
1261     double timeDouble = ((roundToMillis((double)timeNanos / NANOS_PER_SECOND) +
1262                           timeSeconds) / SECONDS_PER_DAY);
1263 
1264     double dateDouble = ((double)dateSeconds / SECONDS_PER_DAY);
1265 
1266     if(dateSeconds < 0) {
1267       timeDouble = -timeDouble;
1268     }
1269 
1270     return dateDouble + timeDouble;
1271   }
1272 
1273   /**
1274    * Rounds the given decimal to milliseconds (3 decimal places) using the
1275    * standard access rounding mode.
1276    */
1277   private static double roundToMillis(double dbl) {
1278     return ((dbl == 0d) ? dbl :
1279             new BigDecimal(dbl).setScale(3, NumberFormatter.ROUND_MODE)
1280             .doubleValue());
1281   }
1282 
1283   /**
1284    * @return an appropriate Date long value for the given object
1285    */
1286   private static long toDateLong(Object value) {
1287     return ((value instanceof Date) ?
1288             ((Date)value).getTime() :
1289             ((value instanceof Calendar) ?
1290              ((Calendar)value).getTimeInMillis() :
1291              ((Number)value).longValue()));
1292   }
1293 
1294   /**
1295    * Gets the timezone offset from UTC to local time for the given time
1296    * (including DST).
1297    */
1298   private static long getToLocalTimeZoneOffset(long time, TimeZone tz) {
1299     return tz.getOffset(time);
1300   }
1301 
1302   /**
1303    * Gets the timezone offset from local time to UTC for the given time
1304    * (including DST).
1305    */
1306   private static long getFromLocalTimeZoneOffset(long time, TimeZone tz) {
1307     // getting from local time back to UTC is a little wonky (and not
1308     // guaranteed to get you back to where you started).  apply the zone
1309     // offset first to get us closer to the original time
1310     return tz.getOffset(time - tz.getRawOffset());
1311   }
1312 
1313   /**
1314    * Decodes a GUID value.
1315    */
1316   private static String readGUIDValue(ByteBuffer buffer, ByteOrder order)
1317   {
1318     if(order != ByteOrder.BIG_ENDIAN) {
1319       byte[] tmpArr = ByteUtil.getBytes(buffer, 16);
1320 
1321         // the first 3 guid components are integer components which need to
1322         // respect endianness, so swap 4-byte int, 2-byte int, 2-byte int
1323       ByteUtil.swap4Bytes(tmpArr, 0);
1324       ByteUtil.swap2Bytes(tmpArr, 4);
1325       ByteUtil.swap2Bytes(tmpArr, 6);
1326       buffer = ByteBuffer.wrap(tmpArr);
1327     }
1328 
1329     StringBuilder sb = new StringBuilder(22);
1330     sb.append("{");
1331     sb.append(ByteUtil.toHexString(buffer, 0, 4,
1332                                    false));
1333     sb.append("-");
1334     sb.append(ByteUtil.toHexString(buffer, 4, 2,
1335                                    false));
1336     sb.append("-");
1337     sb.append(ByteUtil.toHexString(buffer, 6, 2,
1338                                    false));
1339     sb.append("-");
1340     sb.append(ByteUtil.toHexString(buffer, 8, 2,
1341                                    false));
1342     sb.append("-");
1343     sb.append(ByteUtil.toHexString(buffer, 10, 6,
1344                                    false));
1345     sb.append("}");
1346     return (sb.toString());
1347   }
1348 
1349   /**
1350    * Writes a GUID value.
1351    */
1352   private void writeGUIDValue(ByteBuffer buffer, Object value)
1353     throws IOException
1354   {
1355     Matcher m = GUID_PATTERN.matcher(toCharSequence(value));
1356     if(!m.matches()) {
1357       throw new InvalidValueException(
1358           withErrorContext("Invalid GUID: " + value));
1359     }
1360 
1361     ByteBuffer origBuffer = null;
1362     byte[] tmpBuf = null;
1363     if(buffer.order() != ByteOrder.BIG_ENDIAN) {
1364       // write to a temp buf so we can do some swapping below
1365       origBuffer = buffer;
1366       tmpBuf = new byte[16];
1367       buffer = ByteBuffer.wrap(tmpBuf);
1368     }
1369 
1370     ByteUtil.writeHexString(buffer, m.group(1));
1371     ByteUtil.writeHexString(buffer, m.group(2));
1372     ByteUtil.writeHexString(buffer, m.group(3));
1373     ByteUtil.writeHexString(buffer, m.group(4));
1374     ByteUtil.writeHexString(buffer, m.group(5));
1375 
1376     if(tmpBuf != null) {
1377       // the first 3 guid components are integer components which need to
1378       // respect endianness, so swap 4-byte int, 2-byte int, 2-byte int
1379       ByteUtil.swap4Bytes(tmpBuf, 0);
1380       ByteUtil.swap2Bytes(tmpBuf, 4);
1381       ByteUtil.swap2Bytes(tmpBuf, 6);
1382       origBuffer.put(tmpBuf);
1383     }
1384   }
1385 
1386   /**
1387    * Returns {@code true} if the given value is a "guid" value.
1388    */
1389   static boolean isGUIDValue(Object value) throws IOException {
1390     return GUID_PATTERN.matcher(toCharSequence(value)).matches();
1391   }
1392 
1393   /**
1394    * Returns a default value for this column
1395    */
1396   public Object generateDefaultValue() throws IOException {
1397     return ((_defValue != null) ? _defValue.eval() : null);
1398   }
1399 
1400   /**
1401    * Passes the given obj through the currently configured validator for this
1402    * column and returns the result.
1403    */
1404   public Object validate(Object obj) throws IOException {
1405     return _validator.validate(this, obj);
1406   }
1407 
1408   /**
1409    * Returns the context used to manage calculated column values.
1410    */
1411   protected CalcColEvalContext getCalculationContext() {
1412     throw new UnsupportedOperationException();
1413   }
1414 
1415   protected void setCalcColEvalContext(CalcColEvalContext calcCol) {
1416     throw new UnsupportedOperationException();
1417   }
1418 
1419   /**
1420    * Serialize an Object into a raw byte value for this column in little
1421    * endian order
1422    * @param obj Object to serialize
1423    * @return A buffer containing the bytes
1424    * @usage _advanced_method_
1425    */
1426   public ByteBuffer write(Object obj, int remainingRowLength)
1427     throws IOException
1428   {
1429     return write(obj, remainingRowLength, PageChannel.DEFAULT_BYTE_ORDER);
1430   }
1431 
1432   /**
1433    * Serialize an Object into a raw byte value for this column
1434    * @param obj Object to serialize
1435    * @param order Order in which to serialize
1436    * @return A buffer containing the bytes
1437    * @usage _advanced_method_
1438    */
1439   public ByteBuffer write(Object obj, int remainingRowLength, ByteOrder order)
1440     throws IOException
1441   {
1442     if(isRawData(obj)) {
1443       // just slap it right in (not for the faint of heart!)
1444       return ByteBuffer.wrap(((RawData)obj).getBytes());
1445     }
1446 
1447     return writeRealData(obj, remainingRowLength, order);
1448   }
1449 
1450   protected ByteBuffer writeRealData(Object obj, int remainingRowLength,
1451                                      ByteOrder order)
1452     throws IOException
1453   {
1454     if(!isVariableLength() || !getType().isVariableLength()) {
1455       return writeFixedLengthField(obj, order);
1456     }
1457 
1458     // this is an "inline" var length field
1459     switch(getType()) {
1460     case NUMERIC:
1461       // don't ask me why numerics are "var length" columns...
1462       ByteBuffer buffer = PageChannel.createBuffer(
1463           getType().getFixedSize(), order);
1464       writeNumericValue(buffer, obj);
1465       buffer.flip();
1466       return buffer;
1467 
1468     case TEXT:
1469       return encodeTextValue(
1470           obj, 0, getLengthInUnits(), false).order(order);
1471 
1472     case BINARY:
1473     case UNKNOWN_0D:
1474     case UNSUPPORTED_VARLEN:
1475       // should already be "encoded"
1476       break;
1477     default:
1478       throw new RuntimeException(withErrorContext(
1479               "unexpected inline var length type: " + getType()));
1480     }
1481 
1482     ByteBuffer buffer = ByteBuffer.wrap(toByteArray(obj)).order(order);
1483     return buffer;
1484   }
1485 
1486   /**
1487    * Serialize an Object into a raw byte value for this column
1488    * @param obj Object to serialize
1489    * @param order Order in which to serialize
1490    * @return A buffer containing the bytes
1491    * @usage _advanced_method_
1492    */
1493   protected ByteBuffer writeFixedLengthField(Object obj, ByteOrder order)
1494     throws IOException
1495   {
1496     int size = getFixedDataSize();
1497 
1498     ByteBuffer buffer = writeFixedLengthField(
1499         obj, PageChannel.createBuffer(size, order));
1500     buffer.flip();
1501     return buffer;
1502   }
1503 
1504   protected ByteBuffer writeFixedLengthField(Object obj, ByteBuffer buffer)
1505     throws IOException
1506   {
1507     // since booleans are not written by this method, it's safe to convert any
1508     // incoming boolean into an integer.
1509     obj = booleanToInteger(obj);
1510 
1511     switch(getType()) {
1512     case BOOLEAN:
1513       //Do nothing
1514       break;
1515     case  BYTE:
1516       buffer.put(toNumber(obj).byteValue());
1517       break;
1518     case INT:
1519       buffer.putShort(toNumber(obj).shortValue());
1520       break;
1521     case LONG:
1522       buffer.putInt(toNumber(obj).intValue());
1523       break;
1524     case MONEY:
1525       writeCurrencyValue(buffer, obj);
1526       break;
1527     case FLOAT:
1528       buffer.putFloat(toNumber(obj).floatValue());
1529       break;
1530     case DOUBLE:
1531       buffer.putDouble(toNumber(obj).doubleValue());
1532       break;
1533     case SHORT_DATE_TIME:
1534       writeDateValue(buffer, obj);
1535       break;
1536     case TEXT:
1537       // apparently text numeric values are also occasionally written as fixed
1538       // length...
1539       int numChars = getLengthInUnits();
1540       // force uncompressed encoding for fixed length text
1541       buffer.put(encodeTextValue(obj, numChars, numChars, true));
1542       break;
1543     case GUID:
1544       writeGUIDValue(buffer, obj);
1545       break;
1546     case NUMERIC:
1547       // yes, that's right, occasionally numeric values are written as fixed
1548       // length...
1549       writeNumericValue(buffer, obj);
1550       break;
1551     case BINARY:
1552     case UNKNOWN_0D:
1553     case UNKNOWN_11:
1554     case COMPLEX_TYPE:
1555       buffer.putInt(toNumber(obj).intValue());
1556       break;
1557     case BIG_INT:
1558       buffer.putLong(toNumber(obj).longValue());
1559       break;
1560     case EXT_DATE_TIME:
1561       writeExtendedDateValue(buffer, obj);
1562       break;
1563     case UNSUPPORTED_FIXEDLEN:
1564       byte[] bytes = toByteArray(obj);
1565       if(bytes.length != getLength()) {
1566         throw new InvalidValueException(withErrorContext(
1567                                   "Invalid fixed size binary data, size "
1568                                   + getLength() + ", got " + bytes.length));
1569       }
1570       buffer.put(bytes);
1571       break;
1572     default:
1573       throw new IOException(withErrorContext(
1574                                 "Unsupported data type: " + getType()));
1575     }
1576     return buffer;
1577   }
1578 
1579   /**
1580    * Decodes a compressed or uncompressed text value.
1581    */
1582   String decodeTextValue(byte[] data)
1583   {
1584     // see if data is compressed.  the 0xFF, 0xFE sequence indicates that
1585     // compression is used (sort of, see algorithm below)
1586     boolean isCompressed = ((data.length > 1) &&
1587                             (data[0] == TEXT_COMPRESSION_HEADER[0]) &&
1588                             (data[1] == TEXT_COMPRESSION_HEADER[1]));
1589 
1590     if(isCompressed) {
1591 
1592       // this is a whacky compression combo that switches back and forth
1593       // between compressed/uncompressed using a 0x00 byte (starting in
1594       // compressed mode)
1595       StringBuilder textBuf = new StringBuilder(data.length);
1596       // start after two bytes indicating compression use
1597       int dataStart = TEXT_COMPRESSION_HEADER.length;
1598       int dataEnd = dataStart;
1599       boolean inCompressedMode = true;
1600       while(dataEnd < data.length) {
1601         if(data[dataEnd] == (byte)0x00) {
1602 
1603           // handle current segment
1604           decodeTextSegment(data, dataStart, dataEnd, inCompressedMode,
1605                             textBuf);
1606           inCompressedMode = !inCompressedMode;
1607           ++dataEnd;
1608           dataStart = dataEnd;
1609 
1610         } else {
1611           ++dataEnd;
1612         }
1613       }
1614       // handle last segment
1615       decodeTextSegment(data, dataStart, dataEnd, inCompressedMode, textBuf);
1616 
1617       return textBuf.toString();
1618 
1619     }
1620 
1621     return decodeUncompressedText(data, getCharset());
1622   }
1623 
1624   /**
1625    * Decodes a segnment of a text value into the given buffer according to the
1626    * given status of the segment (compressed/uncompressed).
1627    */
1628   private void decodeTextSegment(byte[] data, int dataStart, int dataEnd,
1629                                  boolean inCompressedMode,
1630                                  StringBuilder textBuf)
1631   {
1632     if(dataEnd <= dataStart) {
1633       // no data
1634       return;
1635     }
1636     int dataLength = dataEnd - dataStart;
1637 
1638     if(inCompressedMode) {
1639       byte[] tmpData = new byte[dataLength * 2];
1640       int tmpIdx = 0;
1641       for(int i = dataStart; i < dataEnd; ++i) {
1642         tmpData[tmpIdx] = data[i];
1643         tmpIdx += 2;
1644       }
1645       data = tmpData;
1646       dataStart = 0;
1647       dataLength = data.length;
1648     }
1649 
1650     textBuf.append(decodeUncompressedText(data, dataStart, dataLength,
1651                                           getCharset()));
1652   }
1653 
1654   /**
1655    * @param textBytes bytes of text to decode
1656    * @return the decoded string
1657    */
1658   private static CharBuffer decodeUncompressedText(
1659       byte[] textBytes, int startPos, int length, Charset charset)
1660   {
1661     return charset.decode(ByteBuffer.wrap(textBytes, startPos, length));
1662   }
1663 
1664   /**
1665    * Encodes a text value, possibly compressing.
1666    */
1667   ByteBuffer encodeTextValue(Object obj, int minChars, int maxChars,
1668                              boolean forceUncompressed)
1669     throws IOException
1670   {
1671     CharSequence text = toCharSequence(obj);
1672     if((text.length() > maxChars) || (text.length() < minChars)) {
1673       throw new InvalidValueException(withErrorContext(
1674                             "Text is wrong length for " + getType() +
1675                             " column, max " + maxChars
1676                             + ", min " + minChars + ", got " + text.length()));
1677     }
1678 
1679     // may only compress if column type allows it
1680     if(!forceUncompressed && isCompressedUnicode() &&
1681        (text.length() <= getFormat().MAX_COMPRESSED_UNICODE_SIZE) &&
1682        isUnicodeCompressible(text)) {
1683 
1684       byte[] encodedChars = new byte[TEXT_COMPRESSION_HEADER.length +
1685                                      text.length()];
1686       encodedChars[0] = TEXT_COMPRESSION_HEADER[0];
1687       encodedChars[1] = TEXT_COMPRESSION_HEADER[1];
1688       for(int i = 0; i < text.length(); ++i) {
1689         encodedChars[i + TEXT_COMPRESSION_HEADER.length] =
1690           (byte)text.charAt(i);
1691       }
1692       return ByteBuffer.wrap(encodedChars);
1693     }
1694 
1695     return encodeUncompressedText(text, getCharset());
1696   }
1697 
1698   /**
1699    * Returns {@code true} if the given text can be compressed using compressed
1700    * unicode, {@code false} otherwise.
1701    */
1702   private static boolean isUnicodeCompressible(CharSequence text) {
1703     // only attempt to compress > 2 chars (compressing less than 3 chars would
1704     // not result in a space savings due to the 2 byte compression header)
1705     if(text.length() <= TEXT_COMPRESSION_HEADER.length) {
1706       return false;
1707     }
1708     // now, see if it is all compressible characters
1709     for(int i = 0; i < text.length(); ++i) {
1710       char c = text.charAt(i);
1711       if((c < MIN_COMPRESS_CHAR) || (c > MAX_COMPRESS_CHAR)) {
1712         return false;
1713       }
1714     }
1715     return true;
1716   }
1717 
1718   /**
1719    * Constructs a byte containing the flags for this column.
1720    */
1721   private static byte getColumnBitFlags(ColumnBuilder col) {
1722     byte flags = UPDATABLE_FLAG_MASK;
1723     if(!col.isVariableLength()) {
1724       flags |= FIXED_LEN_FLAG_MASK;
1725     }
1726     if(col.isAutoNumber()) {
1727       byte autoNumFlags = 0;
1728       switch(col.getType()) {
1729       case LONG:
1730       case COMPLEX_TYPE:
1731         autoNumFlags = AUTO_NUMBER_FLAG_MASK;
1732         break;
1733       case GUID:
1734         autoNumFlags = AUTO_NUMBER_GUID_FLAG_MASK;
1735         break;
1736       default:
1737         // unknown autonum type
1738       }
1739       flags |= autoNumFlags;
1740     }
1741     if(col.isHyperlink()) {
1742       flags |= HYPERLINK_FLAG_MASK;
1743     }
1744     return flags;
1745   }
1746 
1747   @Override
1748   public String toString() {
1749     ToStringBuilder sb = CustomToStringStyle.builder(this)
1750       .append("name", "(" + _table.getName() + ") " + _name);
1751     byte typeValue = getOriginalDataType();
1752     sb.append("type", "0x" + Integer.toHexString(typeValue) +
1753               " (" + _type + ")")
1754       .append("number", _columnNumber)
1755       .append("length", _columnLength)
1756       .append("variableLength", _variableLength);
1757     if(_calculated) {
1758       sb.append("calculated", _calculated)
1759         .append("expression",
1760                 CustomToStringStyle.ignoreNull(getCalculationContext()));
1761     }
1762     if(_type.isTextual()) {
1763       sb.append("compressedUnicode", isCompressedUnicode())
1764         .append("textSortOrder", getTextSortOrder());
1765       if(getTextCodePage() > 0) {
1766         sb.append("textCodePage", getTextCodePage());
1767       }
1768       if(isAppendOnly()) {
1769         sb.append("appendOnly", isAppendOnly());
1770       }
1771       if(isHyperlink()) {
1772         sb.append("hyperlink", isHyperlink());
1773       }
1774     }
1775     if(_type.getHasScalePrecision()) {
1776       sb.append("precision", getPrecision())
1777         .append("scale", getScale());
1778     }
1779     if(_autoNumber) {
1780       sb.append("lastAutoNumber", _autoNumberGenerator.getLast());
1781     }
1782     sb.append("complexInfo", CustomToStringStyle.ignoreNull(getComplexInfo()))
1783       .append("validator", CustomToStringStyle.ignoreNull(
1784                   ((_validator != SimpleColumnValidator.INSTANCE) ?
1785                    _validator : null)))
1786       .append("defaultValue", CustomToStringStyle.ignoreNull(_defValue));
1787     return sb.toString();
1788   }
1789 
1790   /**
1791    * @param textBytes bytes of text to decode
1792    * @param charset relevant charset
1793    * @return the decoded string
1794    * @usage _advanced_method_
1795    */
1796   public static String decodeUncompressedText(byte[] textBytes,
1797                                               Charset charset)
1798   {
1799     return decodeUncompressedText(textBytes, 0, textBytes.length, charset)
1800       .toString();
1801   }
1802 
1803   /**
1804    * @param text Text to encode
1805    * @param charset database charset
1806    * @return A buffer with the text encoded
1807    * @usage _advanced_method_
1808    */
1809   public static ByteBuffer encodeUncompressedText(CharSequence text,
1810                                                   Charset charset)
1811   {
1812     CharBuffer cb = ((text instanceof CharBuffer) ?
1813                      (CharBuffer)text : CharBuffer.wrap(text));
1814     return charset.encode(cb);
1815   }
1816 
1817 
1818   /**
1819    * Orders Columns by column number.
1820    * @usage _general_method_
1821    */
1822   @Override
1823   public int compareTo(ColumnImpl other) {
1824     if (_columnNumber > other.getColumnNumber()) {
1825       return 1;
1826     } else if (_columnNumber < other.getColumnNumber()) {
1827       return -1;
1828     } else {
1829       return 0;
1830     }
1831   }
1832 
1833   /**
1834    * @param columns A list of columns in a table definition
1835    * @return The number of variable length columns found in the list
1836    * @usage _advanced_method_
1837    */
1838   public static short countVariableLength(List<ColumnBuilder> columns) {
1839     short rtn = 0;
1840     for (ColumnBuilder col : columns) {
1841       if (col.isVariableLength()) {
1842         rtn++;
1843       }
1844     }
1845     return rtn;
1846   }
1847 
1848   /**
1849    * @return an appropriate BigDecimal representation of the given object.
1850    *         <code>null</code> is returned as 0 and Numbers are converted
1851    *         using their double representation.
1852    */
1853   BigDecimal toBigDecimal(Object value)
1854   {
1855     return toBigDecimal(value, getDatabase());
1856   }
1857 
1858   /**
1859    * @return an appropriate BigDecimal representation of the given object.
1860    *         <code>null</code> is returned as 0 and Numbers are converted
1861    *         using their double representation.
1862    */
1863   static BigDecimal toBigDecimal(Object value, DatabaseImpl db)
1864   {
1865     if(value == null) {
1866       return BigDecimal.ZERO;
1867     } else if(value instanceof BigDecimal) {
1868       return (BigDecimal)value;
1869     } else if(value instanceof BigInteger) {
1870       return new BigDecimal((BigInteger)value);
1871     } else if(value instanceof Number) {
1872       return new BigDecimal(((Number)value).doubleValue());
1873     } else if(value instanceof Boolean) {
1874       // access seems to like -1 for true and 0 for false
1875       return ((Boolean)value) ? BigDecimal.valueOf(-1) : BigDecimal.ZERO;
1876     } else if(value instanceof Date) {
1877       return new BigDecimal(toDateDouble(value, db));
1878     } else if(value instanceof LocalDateTime) {
1879       return new BigDecimal(toDateDouble((LocalDateTime)value));
1880     }
1881     return new BigDecimal(value.toString());
1882   }
1883 
1884   /**
1885    * @return an appropriate Number representation of the given object.
1886    *         <code>null</code> is returned as 0 and Strings are parsed as
1887    *         Doubles.
1888    */
1889   private Number toNumber(Object value)
1890   {
1891     return toNumber(value, getDatabase());
1892   }
1893 
1894   /**
1895    * @return an appropriate Number representation of the given object.
1896    *         <code>null</code> is returned as 0 and Strings are parsed as
1897    *         Doubles.
1898    */
1899   private static Number toNumber(Object value, DatabaseImpl db)
1900   {
1901     if(value == null) {
1902       return BigDecimal.ZERO;
1903     } else if(value instanceof Number) {
1904       return (Number)value;
1905     } else if(value instanceof Boolean) {
1906       // access seems to like -1 for true and 0 for false
1907       return ((Boolean)value) ? -1 : 0;
1908     } else if(value instanceof Date) {
1909       return toDateDouble(value, db);
1910     } else if(value instanceof LocalDateTime) {
1911       return toDateDouble((LocalDateTime)value);
1912     }
1913     return Double.valueOf(value.toString());
1914   }
1915 
1916   /**
1917    * @return an appropriate CharSequence representation of the given object.
1918    * @usage _advanced_method_
1919    */
1920   public static CharSequence toCharSequence(Object value)
1921     throws IOException
1922   {
1923     if(value == null) {
1924       return null;
1925     } else if(value instanceof CharSequence) {
1926       return (CharSequence)value;
1927     } else if(SqlHelper.INSTANCE.isClob(value)) {
1928       return SqlHelper.INSTANCE.getClobString(value);
1929     } else if(value instanceof Reader) {
1930       char[] buf = new char[8 * 1024];
1931       StringBuilder sout = new StringBuilder();
1932       Reader in = (Reader)value;
1933       int read = 0;
1934       while((read = in.read(buf)) != -1) {
1935         sout.append(buf, 0, read);
1936       }
1937       return sout;
1938     }
1939 
1940     return value.toString();
1941   }
1942 
1943   /**
1944    * @return an appropriate byte[] representation of the given object.
1945    * @usage _advanced_method_
1946    */
1947   public static byte[] toByteArray(Object value)
1948     throws IOException
1949   {
1950     if(value == null) {
1951       return null;
1952     } else if(value instanceof byte[]) {
1953       return (byte[])value;
1954     } else if(value instanceof InMemoryBlob) {
1955       return ((InMemoryBlob)value).getBytes();
1956     } else if(SqlHelper.INSTANCE.isBlob(value)) {
1957       return SqlHelper.INSTANCE.getBlobBytes(value);
1958     }
1959 
1960     ByteArrayOutputStream bout = new ByteArrayOutputStream();
1961 
1962     if(value instanceof InputStream) {
1963       ByteUtil.copy((InputStream)value, bout);
1964     } else {
1965       // if all else fails, serialize it
1966       ObjectOutputStream oos = new ObjectOutputStream(bout);
1967       oos.writeObject(value);
1968       oos.close();
1969     }
1970 
1971     return bout.toByteArray();
1972   }
1973 
1974   /**
1975    * Interpret a boolean value (null == false)
1976    * @usage _advanced_method_
1977    */
1978   public static boolean toBooleanValue(Object obj) {
1979     if(obj == null) {
1980       return false;
1981     } else if(obj instanceof Boolean) {
1982       return ((Boolean)obj).booleanValue();
1983     } else if(obj instanceof Number) {
1984       // Access considers 0 as "false"
1985       if(obj instanceof BigDecimal) {
1986         return (((BigDecimal)obj).compareTo(BigDecimal.ZERO) != 0);
1987       }
1988       if(obj instanceof BigInteger) {
1989         return (((BigInteger)obj).compareTo(BigInteger.ZERO) != 0);
1990       }
1991       return (((Number)obj).doubleValue() != 0.0d);
1992     }
1993     return Boolean.parseBoolean(obj.toString());
1994   }
1995 
1996   /**
1997    * Swaps the bytes of the given numeric in place.
1998    */
1999   private static void fixNumericByteOrder(byte[] bytes)
2000   {
2001     // fix endianness of each 4 byte segment
2002     for(int i = 0; i < bytes.length; i+=4) {
2003       ByteUtil.swap4Bytes(bytes, i);
2004     }
2005   }
2006 
2007   /**
2008    * Treat booleans as integers (access-style).
2009    */
2010   protected static Object booleanToInteger(Object obj) {
2011     if (obj instanceof Boolean) {
2012       obj = ((Boolean) obj) ? -1 : 0;
2013     }
2014     return obj;
2015   }
2016 
2017   /**
2018    * Returns a wrapper for raw column data that can be written without
2019    * understanding the data.  Useful for wrapping unparseable data for
2020    * re-writing.
2021    */
2022   public static RawData rawDataWrapper(byte[] bytes) {
2023     return new RawData(bytes);
2024   }
2025 
2026   /**
2027    * Returns {@code true} if the given value is "raw" column data,
2028    * {@code false} otherwise.
2029    * @usage _advanced_method_
2030    */
2031   public static boolean isRawData(Object value) {
2032     return(value instanceof RawData);
2033   }
2034 
2035   /**
2036    * Writes the column definitions into a table definition buffer.
2037    * @param buffer Buffer to write to
2038    */
2039   protected static void writeDefinitions(TableCreator creator, ByteBuffer buffer)
2040   {
2041     // we specifically put the "long variable" values after the normal
2042     // variable length values so that we have a better chance of fitting it
2043     // all (because "long variable" values can go in separate pages)
2044     int longVariableOffset = creator.countNonLongVariableLength();
2045     creator.setColumnOffsets(0, 0, longVariableOffset);
2046 
2047     for (ColumnBuilder col : creator.getColumns()) {
2048       writeDefinition(creator, col, buffer);
2049     }
2050 
2051     for (ColumnBuilder col : creator.getColumns()) {
2052       TableImpl.writeName(buffer, col.getName(), creator.getCharset());
2053     }
2054   }
2055 
2056   protected static void writeDefinition(
2057       TableMutator mutator, ColumnBuilder col, ByteBuffer buffer)
2058   {
2059     TableMutator.ColumnOffsets colOffsets = mutator.getColumnOffsets();
2060 
2061     buffer.put(col.getType().getValue());
2062     buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER);  //constant magic number
2063     buffer.putShort(col.getColumnNumber());  //Column Number
2064 
2065     buffer.putShort(colOffsets.getNextVariableOffset(col));
2066 
2067     buffer.putShort(col.getColumnNumber()); //Column Number again
2068 
2069     if(col.getType().isTextual()) {
2070       // this will write 4 bytes (note we don't support writing dbs which
2071       // use the text code page)
2072       writeSortOrder(buffer, col.getTextSortOrder(), mutator.getFormat());
2073     } else {
2074       // note scale/precision not stored for calculated numeric fields
2075       if(col.getType().getHasScalePrecision() && !col.isCalculated()) {
2076         buffer.put(col.getPrecision());  // numeric precision
2077         buffer.put(col.getScale());  // numeric scale
2078       } else {
2079         buffer.put((byte) 0x00); //unused
2080         buffer.put((byte) 0x00); //unused
2081       }
2082       buffer.putShort((short) 0); //Unknown
2083     }
2084 
2085     buffer.put(getColumnBitFlags(col)); // misc col flags
2086 
2087     // note access doesn't seem to allow unicode compression for calced fields
2088     if(col.isCalculated()) {
2089       buffer.put(CALCULATED_EXT_FLAG_MASK);
2090     } else if (col.isCompressedUnicode()) {  //Compressed
2091       buffer.put(COMPRESSED_UNICODE_EXT_FLAG_MASK);
2092     } else {
2093       buffer.put((byte)0);
2094     }
2095 
2096     buffer.putInt(0); //Unknown, but always 0.
2097 
2098     //Offset for fixed length columns
2099     if(col.isVariableLength()) {
2100       buffer.putShort((short) 0);
2101     } else {
2102       buffer.putShort(colOffsets.getNextFixedOffset(col));
2103     }
2104 
2105     if(!col.getType().isLongValue()) {
2106       short length = col.getLength();
2107       if(col.isCalculated()) {
2108         // calced columns have additional value overhead
2109         if(!col.getType().isVariableLength() ||
2110            col.getType().getHasScalePrecision()) {
2111           length = CalculatedColumnUtil.CALC_FIXED_FIELD_LEN;
2112         } else {
2113           length += CalculatedColumnUtil.CALC_EXTRA_DATA_LEN;
2114         }
2115       }
2116       buffer.putShort(length); //Column length
2117     } else {
2118       buffer.putShort((short)0x0000); // unused
2119     }
2120   }
2121 
2122   protected static void writeColUsageMapDefinitions(
2123       TableCreator creator, ByteBuffer buffer)
2124   {
2125     // write long value column usage map references
2126     for(ColumnBuilder lvalCol : creator.getLongValueColumns()) {
2127       writeColUsageMapDefinition(creator, lvalCol, buffer);
2128     }
2129   }
2130 
2131   protected static void writeColUsageMapDefinition(
2132       TableMutator creator, ColumnBuilder lvalCol, ByteBuffer buffer)
2133   {
2134     TableMutator.ColumnState colState = creator.getColumnState(lvalCol);
2135 
2136     buffer.putShort(lvalCol.getColumnNumber());
2137 
2138     // owned pages umap (both are on same page)
2139     buffer.put(colState.getUmapOwnedRowNumber());
2140     ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber());
2141     // free space pages umap
2142     buffer.put(colState.getUmapFreeRowNumber());
2143     ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber());
2144   }
2145 
2146   /**
2147    * Reads the sort order info from the given buffer from the given position.
2148    */
2149   static SortOrder readSortOrder(ByteBuffer buffer, int position,
2150                                  JetFormat format)
2151   {
2152     short value = buffer.getShort(position);
2153 
2154     if(value == 0) {
2155       // probably a file we wrote, before handling sort order
2156       return format.DEFAULT_SORT_ORDER;
2157     }
2158 
2159     short version = format.DEFAULT_SORT_ORDER.getVersion();
2160     if(format.SIZE_SORT_ORDER == 4) {
2161       version = buffer.get(position + 3);
2162     }
2163 
2164     if(value == GENERAL_SORT_ORDER_VALUE) {
2165       if(version == GENERAL_SORT_ORDER.getVersion()) {
2166         return GENERAL_SORT_ORDER;
2167       }
2168       if(version == GENERAL_LEGACY_SORT_ORDER.getVersion()) {
2169         return GENERAL_LEGACY_SORT_ORDER;
2170       }
2171       if(version == GENERAL_97_SORT_ORDER.getVersion()) {
2172         return GENERAL_97_SORT_ORDER;
2173       }
2174     }
2175     return new SortOrder(value, version);
2176   }
2177 
2178   /**
2179    * Reads the column cade page info from the given buffer, if supported for
2180    * this db.
2181    */
2182   static short readCodePage(ByteBuffer buffer, int offset, JetFormat format)
2183   {
2184       int cpOffset = format.OFFSET_COLUMN_CODE_PAGE;
2185       return ((cpOffset >= 0) ? buffer.getShort(offset + cpOffset) : 0);
2186   }
2187 
2188   /**
2189    * Read the extra flags field for a column definition.
2190    */
2191   static byte readExtraFlags(ByteBuffer buffer, int offset, JetFormat format)
2192   {
2193     int extFlagsOffset = format.OFFSET_COLUMN_EXT_FLAGS;
2194     return ((extFlagsOffset >= 0) ? buffer.get(offset + extFlagsOffset) : 0);
2195   }
2196 
2197   /**
2198    * Writes the sort order info to the given buffer at the current position.
2199    */
2200   private static void writeSortOrder(ByteBuffer buffer, SortOrder sortOrder,
2201                                      JetFormat format) {
2202     if(sortOrder == null) {
2203       sortOrder = format.DEFAULT_SORT_ORDER;
2204     }
2205     buffer.putShort(sortOrder.getValue());
2206     if(format.SIZE_SORT_ORDER == 4) {
2207       buffer.put((byte)0x00); // unknown
2208       buffer.put((byte)sortOrder.getVersion());
2209     }
2210   }
2211 
2212   /**
2213    * Returns {@code true} if the value is immutable, {@code false} otherwise.
2214    * This only handles values that are returned from the {@link #read} method.
2215    */
2216   static boolean isImmutableValue(Object value) {
2217     // for now, the only mutable value this class returns is byte[]
2218     return !(value instanceof byte[]);
2219   }
2220 
2221   /**
2222    * Converts the given value to the "internal" representation for the given
2223    * data type.
2224    */
2225   public static Object toInternalValue(DataType dataType, Object value,
2226                                        DatabaseImpl db)
2227     throws IOException
2228   {
2229     return toInternalValue(dataType, value, db, null);
2230   }
2231 
2232   static Object toInternalValue(DataType dataType, Object value,
2233                                 DatabaseImpl db,
2234                                 ColumnImpl.DateTimeFactory factory)
2235     throws IOException
2236   {
2237     if(value == null) {
2238       return null;
2239     }
2240 
2241     switch(dataType) {
2242     case BOOLEAN:
2243       return ((value instanceof Boolean) ? value : toBooleanValue(value));
2244     case BYTE:
2245       return ((value instanceof Byte) ? value : toNumber(value, db).byteValue());
2246     case INT:
2247       return ((value instanceof Short) ? value :
2248               toNumber(value, db).shortValue());
2249     case LONG:
2250       return ((value instanceof Integer) ? value :
2251               toNumber(value, db).intValue());
2252     case MONEY:
2253       return toBigDecimal(value, db);
2254     case FLOAT:
2255       return ((value instanceof Float) ? value :
2256               toNumber(value, db).floatValue());
2257     case DOUBLE:
2258       return ((value instanceof Double) ? value :
2259               toNumber(value, db).doubleValue());
2260     case SHORT_DATE_TIME:
2261       if(factory == null) {
2262         factory = db.getDateTimeFactory();
2263       }
2264       return factory.toInternalValue(db, value);
2265     case TEXT:
2266     case MEMO:
2267     case GUID:
2268       return ((value instanceof String) ? value :
2269               toCharSequence(value).toString());
2270     case NUMERIC:
2271       return toBigDecimal(value, db);
2272     case COMPLEX_TYPE:
2273       // leave alone for now?
2274       return value;
2275     case BIG_INT:
2276       return ((value instanceof Long) ? value :
2277               toNumber(value, db).longValue());
2278     case EXT_DATE_TIME:
2279       return toLocalDateTime(value, db);
2280     default:
2281       // some variation of binary data
2282       return toByteArray(value);
2283     }
2284   }
2285 
2286   protected static DateTimeFactory getDateTimeFactory(DateTimeType type) {
2287     return ((type == DateTimeType.LOCAL_DATE_TIME) ?
2288             LDT_DATE_TIME_FACTORY : DEF_DATE_TIME_FACTORY);
2289   }
2290 
2291   String withErrorContext(String msg) {
2292     return withErrorContext(msg, getDatabase(), getTable().getName(), getName());
2293   }
2294 
2295   boolean isThisColumn(Identifier identifier) {
2296     return(getTable().isThisTable(identifier) &&
2297            getName().equalsIgnoreCase(identifier.getObjectName()));
2298   }
2299 
2300   private static String withErrorContext(
2301       String msg, DatabaseImpl db, String tableName, String colName) {
2302     return msg + " (Db=" + db.getName() + ";Table=" + tableName + ";Column=" +
2303       colName + ")";
2304   }
2305 
2306   /**
2307    * Date subclass which stashes the original date bits, in case we attempt to
2308    * re-write the value (will not lose precision).  Also, this implementation
2309    * is immutable.
2310    */
2311   @SuppressWarnings("deprecation")
2312   private static final class DateExt extends Date
2313   {
2314     private static final long serialVersionUID = 0L;
2315 
2316     /** cached bits of the original date value */
2317     private transient final long _dateBits;
2318 
2319     private DateExt(long time, long dateBits) {
2320       super(time);
2321       _dateBits = dateBits;
2322     }
2323 
2324     public long getDateBits() {
2325       return _dateBits;
2326     }
2327 
2328     @Override
2329     public void setDate(int time) {
2330       throw new UnsupportedOperationException();
2331     }
2332 
2333     @Override
2334     public void setHours(int time) {
2335       throw new UnsupportedOperationException();
2336     }
2337 
2338     @Override
2339     public void setMinutes(int time) {
2340       throw new UnsupportedOperationException();
2341     }
2342 
2343     @Override
2344     public void setMonth(int time) {
2345       throw new UnsupportedOperationException();
2346     }
2347 
2348     @Override
2349     public void setSeconds(int time) {
2350       throw new UnsupportedOperationException();
2351     }
2352 
2353     @Override
2354     public void setYear(int time) {
2355       throw new UnsupportedOperationException();
2356     }
2357 
2358     @Override
2359     public void setTime(long time) {
2360       throw new UnsupportedOperationException();
2361     }
2362 
2363     private Object writeReplace() throws ObjectStreamException {
2364       // if we are going to serialize this Date, convert it back to a normal
2365       // Date (in case it is restored outside of the context of jackcess)
2366       return new Date(super.getTime());
2367     }
2368   }
2369 
2370   /**
2371    * Wrapper for raw column data which can be re-written.
2372    */
2373   private static final class RawData implements Serializable, InMemoryBlob
2374   {
2375     private static final long serialVersionUID = 0L;
2376 
2377     private final byte[] _bytes;
2378 
2379     private RawData(byte[] bytes) {
2380       _bytes = bytes;
2381     }
2382 
2383     @Override
2384     public byte[] getBytes() {
2385       return _bytes;
2386     }
2387 
2388     @Override
2389     public String toString() {
2390       return CustomToStringStyle.valueBuilder(this)
2391         .append(null, getBytes())
2392         .toString();
2393     }
2394 
2395     private Object writeReplace() throws ObjectStreamException {
2396       // if we are going to serialize this, convert it back to a normal
2397       // byte[] (in case it is restored outside of the context of jackcess)
2398       return getBytes();
2399     }
2400   }
2401 
2402   /**
2403    * Base class for the supported autonumber types.
2404    * @usage _advanced_class_
2405    */
2406   public abstract class AutoNumberGenerator
2407   {
2408     protected AutoNumberGenerator() {}
2409 
2410     /**
2411      * Returns the last autonumber generated by this generator.  Only valid
2412      * after a call to {@link Table#addRow}, otherwise undefined.
2413      */
2414     public abstract Object getLast();
2415 
2416     /**
2417      * Returns the next autonumber for this generator.
2418      * <p>
2419      * <i>Warning, calling this externally will result in this value being
2420      * "lost" for the table.</i>
2421      */
2422     public abstract Object getNext(TableImpl.WriteRowState writeRowState);
2423 
2424     /**
2425      * Returns a valid autonumber for this generator.
2426      * <p>
2427      * <i>Warning, calling this externally may result in this value being
2428      * "lost" for the table.</i>
2429      */
2430     public abstract Object handleInsert(
2431         TableImpl.WriteRowState writeRowState, Object inRowValue)
2432       throws IOException;
2433 
2434     /**
2435      * Restores a previous autonumber generated by this generator.
2436      */
2437     public abstract void restoreLast(Object last);
2438 
2439     /**
2440      * Returns the type of values generated by this generator.
2441      */
2442     public abstract DataType getType();
2443   }
2444 
2445   private final class LongAutoNumberGenerator extends AutoNumberGenerator
2446   {
2447     private LongAutoNumberGenerator() {}
2448 
2449     @Override
2450     public Object getLast() {
2451       // the table stores the last long autonumber used
2452       return getTable().getLastLongAutoNumber();
2453     }
2454 
2455     @Override
2456     public Object getNext(TableImpl.WriteRowState writeRowState) {
2457       // the table stores the last long autonumber used
2458       return getTable().getNextLongAutoNumber();
2459     }
2460 
2461     @Override
2462     public Object handleInsert(TableImpl.WriteRowState writeRowState,
2463                                Object inRowValue)
2464       throws IOException
2465     {
2466       int inAutoNum = toNumber(inRowValue).intValue();
2467       if(inAutoNum <= INVALID_AUTO_NUMBER &&
2468          !getTable().isAllowAutoNumberInsert()) {
2469         throw new InvalidValueException(withErrorContext(
2470                 "Invalid auto number value " + inAutoNum));
2471       }
2472       // the table stores the last long autonumber used
2473       getTable().adjustLongAutoNumber(inAutoNum);
2474       return inAutoNum;
2475     }
2476 
2477     @Override
2478     public void restoreLast(Object last) {
2479       if(last instanceof Integer) {
2480         getTable().restoreLastLongAutoNumber((Integer)last);
2481       }
2482     }
2483 
2484     @Override
2485     public DataType getType() {
2486       return DataType.LONG;
2487     }
2488   }
2489 
2490   private final class GuidAutoNumberGenerator extends AutoNumberGenerator
2491   {
2492     private Object _lastAutoNumber;
2493 
2494     private GuidAutoNumberGenerator() {}
2495 
2496     @Override
2497     public Object getLast() {
2498       return _lastAutoNumber;
2499     }
2500 
2501     @Override
2502     public Object getNext(TableImpl.WriteRowState writeRowState) {
2503       // format guids consistently w/ Column.readGUIDValue()
2504       _lastAutoNumber = "{" + UUID.randomUUID() + "}";
2505       return _lastAutoNumber;
2506     }
2507 
2508     @Override
2509     public Object handleInsert(TableImpl.WriteRowState writeRowState,
2510                                Object inRowValue)
2511       throws IOException
2512     {
2513       _lastAutoNumber = toCharSequence(inRowValue);
2514       return _lastAutoNumber;
2515     }
2516 
2517     @Override
2518     public void restoreLast(Object last) {
2519       _lastAutoNumber = null;
2520     }
2521 
2522     @Override
2523     public DataType getType() {
2524       return DataType.GUID;
2525     }
2526   }
2527 
2528   private final class ComplexTypeAutoNumberGenerator extends AutoNumberGenerator
2529   {
2530     private ComplexTypeAutoNumberGenerator() {}
2531 
2532     @Override
2533     public Object getLast() {
2534       // the table stores the last ComplexType autonumber used
2535       return getTable().getLastComplexTypeAutoNumber();
2536     }
2537 
2538     @Override
2539     public Object getNext(TableImpl.WriteRowState writeRowState) {
2540       // same value is shared across all ComplexType values in a row
2541       int nextComplexAutoNum = writeRowState.getComplexAutoNumber();
2542       if(nextComplexAutoNum <= INVALID_AUTO_NUMBER) {
2543         // the table stores the last ComplexType autonumber used
2544         nextComplexAutoNum = getTable().getNextComplexTypeAutoNumber();
2545         writeRowState.setComplexAutoNumber(nextComplexAutoNum);
2546       }
2547       return new ComplexValueForeignKeyImpl(ColumnImpl.this,
2548                                             nextComplexAutoNum);
2549     }
2550 
2551     @Override
2552     public Object handleInsert(TableImpl.WriteRowState writeRowState,
2553                                Object inRowValue)
2554       throws IOException
2555     {
2556       ComplexValueForeignKey inComplexFK = null;
2557       if(inRowValue instanceof ComplexValueForeignKey) {
2558         inComplexFK = (ComplexValueForeignKey)inRowValue;
2559       } else {
2560         inComplexFK = new ComplexValueForeignKeyImpl(
2561             ColumnImpl.this, toNumber(inRowValue).intValue());
2562       }
2563 
2564       if(inComplexFK.getColumn() != ColumnImpl.this) {
2565         throw new InvalidValueException(withErrorContext(
2566                 "Wrong column for complex value foreign key, found " +
2567                 inComplexFK.getColumn().getName()));
2568       }
2569       if(inComplexFK.get() < 1) {
2570         throw new InvalidValueException(withErrorContext(
2571                 "Invalid complex value foreign key value " + inComplexFK.get()));
2572       }
2573       // same value is shared across all ComplexType values in a row
2574       int prevRowValue = writeRowState.getComplexAutoNumber();
2575       if(prevRowValue <= INVALID_AUTO_NUMBER) {
2576         writeRowState.setComplexAutoNumber(inComplexFK.get());
2577       } else if(prevRowValue != inComplexFK.get()) {
2578         throw new InvalidValueException(withErrorContext(
2579                 "Inconsistent complex value foreign key values: found " +
2580                 prevRowValue + ", given " + inComplexFK));
2581       }
2582 
2583       // the table stores the last ComplexType autonumber used
2584       getTable().adjustComplexTypeAutoNumber(inComplexFK.get());
2585 
2586       return inComplexFK;
2587     }
2588 
2589     @Override
2590     public void restoreLast(Object last) {
2591       if(last instanceof ComplexValueForeignKey) {
2592         getTable().restoreLastComplexTypeAutoNumber(
2593             ((ComplexValueForeignKey)last).get());
2594       }
2595     }
2596 
2597     @Override
2598     public DataType getType() {
2599       return DataType.COMPLEX_TYPE;
2600     }
2601   }
2602 
2603   private final class UnsupportedAutoNumberGenerator extends AutoNumberGenerator
2604   {
2605     private final DataType _genType;
2606 
2607     private UnsupportedAutoNumberGenerator(DataType genType) {
2608       _genType = genType;
2609     }
2610 
2611     @Override
2612     public Object getLast() {
2613       return null;
2614     }
2615 
2616     @Override
2617     public Object getNext(TableImpl.WriteRowState writeRowState) {
2618       throw new UnsupportedOperationException();
2619     }
2620 
2621     @Override
2622     public Object handleInsert(TableImpl.WriteRowState writeRowState,
2623                                Object inRowValue) {
2624       throw new UnsupportedOperationException();
2625     }
2626 
2627     @Override
2628     public void restoreLast(Object last) {
2629       throw new UnsupportedOperationException();
2630     }
2631 
2632     @Override
2633     public DataType getType() {
2634       return _genType;
2635     }
2636   }
2637 
2638 
2639   /**
2640    * Information about the sort order (collation) for a textual column.
2641    * @usage _intermediate_class_
2642    */
2643   public static final class SortOrder
2644   {
2645     private final short _value;
2646     private final short _version;
2647 
2648     public SortOrder(short value, short version) {
2649       _value = value;
2650       _version = version;
2651     }
2652 
2653     public short getValue() {
2654       return _value;
2655     }
2656 
2657     public short getVersion() {
2658       return _version;
2659     }
2660 
2661     @Override
2662     public int hashCode() {
2663       return _value;
2664     }
2665 
2666     @Override
2667     public boolean equals(Object o) {
2668       return ((this == o) ||
2669               ((o != null) && (getClass() == o.getClass()) &&
2670                (_value == ((SortOrder)o)._value) &&
2671                (_version == ((SortOrder)o)._version)));
2672     }
2673 
2674     @Override
2675     public String toString() {
2676       return CustomToStringStyle.valueBuilder(this)
2677         .append(null, _value + "(" + _version + ")")
2678         .toString();
2679     }
2680   }
2681 
2682   /**
2683    * Utility struct for passing params through ColumnImpl constructors.
2684    */
2685   static final class InitArgs
2686   {
2687     public final TableImpl table;
2688     public final ByteBuffer buffer;
2689     public final int offset;
2690     public final String name;
2691     public final int displayIndex;
2692     public final byte colType;
2693     public final byte flags;
2694     public final byte extFlags;
2695     public DataType type;
2696 
2697     InitArgs(TableImpl table, ByteBuffer buffer, int offset, String name,
2698              int displayIndex) {
2699       this.table = table;
2700       this.buffer = buffer;
2701       this.offset = offset;
2702       this.name = name;
2703       this.displayIndex = displayIndex;
2704 
2705       this.colType = buffer.get(offset + table.getFormat().OFFSET_COLUMN_TYPE);
2706       this.flags = buffer.get(offset + table.getFormat().OFFSET_COLUMN_FLAGS);
2707       this.extFlags = readExtraFlags(buffer, offset, table.getFormat());
2708     }
2709   }
2710 
2711   /**
2712    * "Internal" column validator for columns with the "required" property
2713    * enabled.
2714    */
2715   private static final class RequiredColValidator extends InternalColumnValidator
2716   {
2717     private RequiredColValidator(ColumnValidator delegate) {
2718       super(delegate);
2719     }
2720 
2721     @Override
2722     protected Object internalValidate(Column col, Object val)
2723       throws IOException
2724     {
2725       if(val == null) {
2726         throw new InvalidValueException(
2727             ((ColumnImpl)col).withErrorContext(
2728                 "Missing value for required column"));
2729       }
2730       return val;
2731     }
2732 
2733     @Override
2734     protected void appendToString(StringBuilder sb) {
2735       sb.append("required=true");
2736     }
2737   }
2738 
2739   /**
2740    * "Internal" column validator for text columns with the "allow zero len"
2741    * property disabled.
2742    */
2743   private static final class NoZeroLenColValidator extends InternalColumnValidator
2744   {
2745     private NoZeroLenColValidator(ColumnValidator delegate) {
2746       super(delegate);
2747     }
2748 
2749     @Override
2750     protected Object internalValidate(Column col, Object val)
2751       throws IOException
2752     {
2753       CharSequence valStr = ColumnImpl.toCharSequence(val);
2754       // oddly enough null is allowed for non-zero len strings
2755       if((valStr != null) && valStr.length() == 0) {
2756         throw new InvalidValueException(
2757             ((ColumnImpl)col).withErrorContext(
2758                 "Zero length string is not allowed"));
2759       }
2760       return valStr;
2761     }
2762 
2763     @Override
2764     protected void appendToString(StringBuilder sb) {
2765       sb.append("allowZeroLength=false");
2766     }
2767   }
2768 
2769   /**
2770    * Factory which handles date/time values appropriately for a DateTimeType.
2771    */
2772   protected static abstract class DateTimeFactory
2773   {
2774     public abstract DateTimeType getType();
2775 
2776     public abstract Object fromDateBits(ColumnImpl col, long dateBits);
2777 
2778     public abstract double toDateDouble(Object value, DateTimeContext dtc);
2779 
2780     public abstract Object toInternalValue(DatabaseImpl db, Object value);
2781   }
2782 
2783   /**
2784    * Factory impl for legacy Date handling.
2785    */
2786   private static final class DefaultDateTimeFactory extends DateTimeFactory
2787   {
2788     @Override
2789     public DateTimeType getType() {
2790       return DateTimeType.DATE;
2791     }
2792 
2793     @Override
2794     public Object fromDateBits(ColumnImpl col, long dateBits) {
2795       long time = col.fromDateDouble(
2796           Double.longBitsToDouble(dateBits));
2797       return new DateExt(time, dateBits);
2798     }
2799 
2800     @Override
2801     public double toDateDouble(Object value, DateTimeContext dtc) {
2802       // ZoneId and TimeZone have different rules for older timezones, so we
2803       // need to consistently use one or the other depending on the date/time
2804       // type
2805       long time = 0L;
2806       if(value instanceof TemporalAccessor) {
2807         time = toInstant((TemporalAccessor)value, dtc).toEpochMilli();
2808       } else {
2809         time = toDateLong(value);
2810       }
2811       // seems access stores dates in the local timezone.  guess you just
2812       // hope you read it in the same timezone in which it was written!
2813       time += getToLocalTimeZoneOffset(time, dtc.getTimeZone());
2814       return toLocalDateDouble(time);
2815     }
2816 
2817     @Override
2818     public Object toInternalValue(DatabaseImpl db, Object value) {
2819       return ((value instanceof Date) ? value :
2820               new Date(toDateLong(value)));
2821     }
2822   }
2823 
2824   /**
2825    * Factory impl for LocalDateTime handling.
2826    */
2827   private static final class LDTDateTimeFactory extends DateTimeFactory
2828   {
2829     @Override
2830     public DateTimeType getType() {
2831       return DateTimeType.LOCAL_DATE_TIME;
2832     }
2833 
2834     @Override
2835     public Object fromDateBits(ColumnImpl col, long dateBits) {
2836       return ldtFromLocalDateDouble(Double.longBitsToDouble(dateBits));
2837     }
2838 
2839     @Override
2840     public double toDateDouble(Object value, DateTimeContext dtc) {
2841       // ZoneId and TimeZone have different rules for older timezones, so we
2842       // need to consistently use one or the other depending on the date/time
2843       // type
2844       if(!(value instanceof TemporalAccessor)) {
2845         value = Instant.ofEpochMilli(toDateLong(value));
2846       }
2847       return ColumnImpl.toDateDouble(
2848           temporalToLocalDateTime((TemporalAccessor)value, dtc));
2849     }
2850 
2851     @Override
2852     public Object toInternalValue(DatabaseImpl db, Object value) {
2853       return toLocalDateTime(value, db);
2854     }
2855   }
2856 
2857   /** internal interface for types which hold bytes in memory */
2858   static interface InMemoryBlob {
2859     public byte[] getBytes() throws IOException;
2860   }
2861 }