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